Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEd Merks2022-01-22 10:00:54 +0000
committerEd Merks2022-02-02 13:01:01 +0000
commit98957cf13269d710fba4a1c02a7bdc4c36a3c015 (patch)
tree360694c03fca73d3c94b6c5ec9f5f310e9e4e3dd
parent54518da44ea75b793fdc819eb6f18d3727f970d7 (diff)
downloadrt.equinox.p2-98957cf13269d710fba4a1c02a7bdc4c36a3c015.tar.gz
rt.equinox.p2-98957cf13269d710fba4a1c02a7bdc4c36a3c015.tar.xz
rt.equinox.p2-98957cf13269d710fba4a1c02a7bdc4c36a3c015.zip
Bug 578322 - Provide a more flexible mechanism for managing and locatingY20220203-0600Y20220202-0910I20220203-0300I20220202-1800
PGP public keys Provide org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService and its default registered implementation DefaultPGPPublicKeyService that supports network access to zero or more key servers with offline caching, caching of all locally added keys, and access to keys in the GPG pubring. Extend ValidationDialogServiceUI to make it aware of the agent such that it can access the PGPPublicKeyService to provide web-of-trust details to the user. Ensure that PGPPublicKeyStore properly handles multiple keys per key ID. Provide access to the target artifact descriptor via SimpleArtifactRepository.ArtifactOutputStream.getAdapter so that the special case handling of PGP properties in MirrorRequest.getDestinationDescriptor can be moved to PGPSignatureVerifier.close() to ensure that signatures and the keys used to verify them follow the artifact during a mirror request. Simplify CertificateChecker such that it expects the keys used for signature verification to be present in the artifact properties. Ensure that PGPSignatureVerifier can deal with multiple keys with the same key ID. Also verify key expiration (log a warning) and key revocation (abort the download), guarded by system properties p2.pgp.verifyExpiration and p2.pgp.verifyRevocation to disable the checks. Fix TrustPreferencePage to store keys in a file based on the fingerprint rather than the key ID and to present to the user the fingerprint rather than the key ID. Add a .options file to provide access to the debug/tracing options. Change-Id: I8c50ce886b9af175db129c7508774d00972a0432 Signed-off-by: Ed Merks <ed.merks@gmail.com> Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.p2/+/189910 Tested-by: Equinox Bot <equinox-bot@eclipse.org> Reviewed-by: Mickael Istria <mistria@redhat.com>
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/Messages.java4
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPPublicKeyStore.java22
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java220
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/messages.properties8
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/MirrorRequest.java9
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/simple/SimpleArtifactRepository.java10
-rw-r--r--bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CertificateChecker.java68
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/.options3
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF14
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/OSGI-INF/pgpPublicKeyService.xml8
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/build.properties3
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/pom.xml2
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/PGPKeyServiceComponent.java22
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/helpers/DebugHelper.java5
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/provisional/p2/repository/DefaultPGPPublicKeyService.java867
-rw-r--r--bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/p2/repository/spi/PGPPublicKeyService.java231
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/processors/PGPSignatureVerifierTest.java45
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPVerifierTest.java33
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/CertificateCheckerTest.java119
-rw-r--r--bundles/org.eclipse.equinox.p2.ui.sdk/META-INF/MANIFEST.MF3
-rw-r--r--bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/ProvSDKMessages.java2
-rw-r--r--bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/TrustPreferencePage.java17
-rw-r--r--bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/messages.properties4
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/META-INF/MANIFEST.MF1
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ServiceUIComponent.java5
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ValidationDialogServiceUI.java56
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java12
27 files changed, 1541 insertions, 252 deletions
diff --git a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/Messages.java b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/Messages.java
index 4ae7cc402..f02a44008 100644
--- a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/Messages.java
+++ b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/Messages.java
@@ -15,6 +15,10 @@ import org.eclipse.osgi.util.NLS;
public class Messages extends NLS {
private static final String BUNDLE_NAME = "org.eclipse.equinox.internal.p2.artifact.processors.pgp.messages"; //$NON-NLS-1$
+ public static String Error_SignatureAfterKeyExpiration;
+
+ public static String Error_SignatureAfterKeyRevocation;
+
public static String Error_SignatureAndFileDontMatch;
public static String Error_CouldNotLoadSignature;
diff --git a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPPublicKeyStore.java b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPPublicKeyStore.java
index d49e0ad11..c5f9d0196 100644
--- a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPPublicKeyStore.java
+++ b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPPublicKeyStore.java
@@ -14,6 +14,7 @@ import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
@@ -21,20 +22,21 @@ import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.internal.p2.artifact.repository.Activator;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
public class PGPPublicKeyStore {
- private Map<Long, PGPPublicKey> keys = new HashMap<>();
+ private Map<String, PGPPublicKey> keys = new HashMap<>();
public PGPPublicKey addKey(PGPPublicKey key) {
if (key == null) {
return null;
}
- PGPPublicKey alreadyStoredKey = keys.putIfAbsent(key.getKeyID(), key);
+ PGPPublicKey alreadyStoredKey = keys.putIfAbsent(PGPPublicKeyService.toHex(key.getFingerprint()), key);
return alreadyStoredKey == null ? key : alreadyStoredKey;
}
- public PGPPublicKey getKey(long id) {
- return keys.get(id);
+ public Collection<PGPPublicKey> getKeys(long id) {
+ return keys.values().stream().filter(key -> key.getKeyID() == id).collect(Collectors.toList());
}
public void addKeys(String... armoredPublicKeys) {
@@ -63,9 +65,7 @@ public class PGPPublicKeyStore {
public String toArmoredString() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ArmoredOutputStream armoredOut = new ArmoredOutputStream(out);
- // PGPPublicKeyRing ring = new PGPPublicKeyRing(new ArrayList<>(keys.values()));
- // ring.encode(armoredOut);
- for (PGPPublicKey key : keys.values()) {
+ for (PGPPublicKey key : all()) {
key.encode(armoredOut);
}
armoredOut.close();
@@ -74,14 +74,14 @@ public class PGPPublicKeyStore {
}
public void remove(PGPPublicKey selectedKey) {
- keys.remove(selectedKey.getKeyID());
+ keys.remove(PGPPublicKeyService.toHex(selectedKey.getFingerprint()));
}
public void add(File file) {
try (InputStream stream = new FileInputStream(file)) {
readPublicKeys(stream).forEach(this::addKey);
} catch (IOException e) {
- // TODO: how to log?
+ LogHelper.log(new Status(IStatus.ERROR, Activator.ID, "Could not read PGP key from " + file, e)); //$NON-NLS-1$
}
}
@@ -109,9 +109,7 @@ public class PGPPublicKeyStore {
res.add((PGPPublicKey) o);
}
});
- } catch (IOException e) {
- LogHelper.log(new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e));
- } catch (PGPRuntimeOperationException e) {
+ } catch (IOException | PGPRuntimeOperationException e) {
LogHelper.log(new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e));
}
return res;
diff --git a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java
index 9c8caf9f9..6fc1dc597 100644
--- a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java
+++ b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java
@@ -13,21 +13,28 @@ package org.eclipse.equinox.internal.p2.artifact.processors.pgp;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
+import java.util.Map.Entry;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.PGPContentVerifier;
+import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
import org.eclipse.core.runtime.*;
import org.eclipse.equinox.internal.p2.artifact.repository.Activator;
+import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.provisional.p2.artifact.repository.processing.ProcessingStep;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.repository.artifact.*;
+import org.eclipse.equinox.p2.repository.artifact.spi.ArtifactDescriptor;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
import org.eclipse.osgi.util.NLS;
/**
- * This processing step verifies PGP signatures are correct (ie artifact was not
- * tampered during fetch). Note that is does <b>not</b> deal with trust. Dealing
- * with trusted signers is done as part of CheckTrust touchpoint and phase.
+ * This processing step verifies PGP signatures are correct (i.e., the artifact
+ * was not tampered during fetch). Note that is does <b>not</b> deal with trust.
+ * Dealing with trusted signers is done as part of CheckTrust and touchpoint
+ * phase.
*/
public final class PGPSignatureVerifier extends ProcessingStep {
@@ -38,65 +45,25 @@ public final class PGPSignatureVerifier extends ProcessingStep {
*/
public static final String ID = "org.eclipse.equinox.p2.processing.PGPSignatureCheck"; //$NON-NLS-1$
- public static final PGPPublicKeyStore KNOWN_KEYS = new PGPPublicKeyStore();
-
public static final String PGP_SIGNER_KEYS_PROPERTY_NAME = "pgp.publicKeys"; //$NON-NLS-1$
+
public static final String PGP_SIGNATURES_PROPERTY_NAME = "pgp.signatures"; //$NON-NLS-1$
- private Collection<PGPSignature> signaturesToVerify;
+
+ private PGPPublicKeyService keyService;
+
+ private IArtifactDescriptor sourceDescriptor;
+
+ private Map<PGPSignature, List<PGPContentVerifier>> signaturesToVerify = new LinkedHashMap<>();
+
+ private Map<PGPContentVerifier, PGPPublicKey> verifierKeys = new LinkedHashMap<>();
+
+ private List<OutputStream> signatureVerifiers = new ArrayList<>();
public PGPSignatureVerifier() {
super();
link(nullOutputStream(), new NullProgressMonitor()); // this is convenience for tests
}
- public static Map<PGPPublicKey, Set<PGPPublicKey>> getVerifiedKnownKeyCertifications() {
- Map<PGPPublicKey, Set<PGPPublicKey>> result = new LinkedHashMap<>();
- for (PGPPublicKey key : KNOWN_KEYS.all()) {
- Set<PGPPublicKey> certifications = new LinkedHashSet<>();
- for (Iterator<PGPSignature> signatures = key.getSignatures(); signatures.hasNext();) {
- PGPSignature signature = signatures.next();
- long signingKeyID = signature.getKeyID();
- PGPPublicKey signingKey = KNOWN_KEYS.getKey(signingKeyID);
- if (signingKey != null) {
- switch (signature.getSignatureType()) {
- case PGPSignature.SUBKEY_BINDING:
- case PGPSignature.PRIMARYKEY_BINDING: {
- try {
- signature.init(new BcPGPContentVerifierBuilderProvider(), signingKey);
- if (signature.verifyCertification(signingKey, key)) {
- certifications.add(signingKey);
- }
- } catch (PGPException e) {
- //$FALL-THROUGH$
- }
- break;
- }
- case PGPSignature.DEFAULT_CERTIFICATION:
- case PGPSignature.NO_CERTIFICATION:
- case PGPSignature.CASUAL_CERTIFICATION:
- case PGPSignature.POSITIVE_CERTIFICATION: {
- for (Iterator<String> userIDs = key.getUserIDs(); userIDs.hasNext();) {
- String userID = userIDs.next();
- try {
- signature.init(new BcPGPContentVerifierBuilderProvider(), signingKey);
- if (signature.verifyCertification(userID, key)) {
- certifications.add(signingKey);
- break;
- }
- } catch (PGPException e) {
- //$FALL-THROUGH$
- }
- }
- break;
- }
- }
- }
- }
- result.put(key, certifications);
- }
- return result;
- }
-
public static Collection<PGPSignature> getSignatures(IArtifactDescriptor artifact)
throws IOException, PGPException {
String signatureText = unnormalizedPGPProperty(artifact.getProperty(PGP_SIGNATURES_PROPERTY_NAME));
@@ -125,36 +92,60 @@ public final class PGPSignatureVerifier extends ProcessingStep {
public void initialize(IProvisioningAgent agent, IProcessingStepDescriptor descriptor,
IArtifactDescriptor context) {
super.initialize(agent, descriptor, context);
+
+ sourceDescriptor = context;
+ keyService = agent.getService(PGPPublicKeyService.class);
+
// 1. verify declared public keys have signature from a trusted key, if so, add to KeyStore
-// 2. verify artifact signature matches signture of given keys, and at least 1 of this key is trusted
+// 2. verify artifact signature matches signature of given keys, and at least 1 of this key is trusted
String signatureText = unnormalizedPGPProperty(context.getProperty(PGP_SIGNATURES_PROPERTY_NAME));
if (signatureText == null) {
setStatus(Status.OK_STATUS);
return;
}
+
+ Collection<PGPSignature> signatures;
try {
- signaturesToVerify = getSignatures(context);
+ signatures = getSignatures(context);
} catch (Exception ex) {
setStatus(new Status(IStatus.ERROR, Activator.ID, Messages.Error_CouldNotLoadSignature, ex));
return;
}
- if (signaturesToVerify.isEmpty()) {
+
+ if (signatures.isEmpty()) {
setStatus(Status.OK_STATUS);
return;
}
IArtifactRepository repository = context.getRepository();
- KNOWN_KEYS.addKeys(context.getProperty(PGP_SIGNER_KEYS_PROPERTY_NAME),
- repository != null ? repository.getProperty(PGP_SIGNER_KEYS_PROPERTY_NAME) : null);
- for (PGPSignature signature : signaturesToVerify) {
- PGPPublicKey publicKey = KNOWN_KEYS.getKey(signature.getKeyID());
- if (publicKey == null) {
+
+ PGPPublicKeyStore.readPublicKeys(context.getProperty(PGP_SIGNER_KEYS_PROPERTY_NAME))
+ .forEach(keyService::addKey);
+ if (repository != null) {
+ PGPPublicKeyStore.readPublicKeys(repository.getProperty(PGP_SIGNER_KEYS_PROPERTY_NAME))
+ .forEach(keyService::addKey);
+ }
+
+ for (PGPSignature signature : signatures) {
+ long keyID = signature.getKeyID();
+ Collection<PGPPublicKey> keys = keyService.getKeys(keyID);
+ if (keys.isEmpty()) {
setStatus(new Status(IStatus.ERROR, Activator.ID,
- NLS.bind(Messages.Error_publicKeyNotFound, Long.toHexString(signature.getKeyID()))));
+ NLS.bind(Messages.Error_publicKeyNotFound, PGPPublicKeyService.toHex(keyID))));
return;
}
+
try {
- signature.init(new BcPGPContentVerifierBuilderProvider(), publicKey);
+ PGPContentVerifierBuilder verifierBuilder = new BcPGPContentVerifierBuilderProvider()
+ .get(signature.getKeyAlgorithm(), signature.getHashAlgorithm());
+ List<PGPContentVerifier> verifiers = new ArrayList<>();
+ signaturesToVerify.put(signature, verifiers);
+ for (PGPPublicKey key : keys) {
+ PGPContentVerifier verifier = verifierBuilder.build(key);
+ verifierKeys.put(verifier, key);
+ verifiers.add(verifier);
+ signatureVerifiers.add(verifier.getOutputStream());
+ }
} catch (PGPException ex) {
setStatus(new Status(IStatus.ERROR, Activator.ID, ex.getMessage(), ex));
return;
@@ -184,51 +175,94 @@ public final class PGPSignatureVerifier extends ProcessingStep {
}
@Override
- public void write(int b) {
- if (signaturesToVerify != null) {
- signaturesToVerify.iterator().forEachRemaining(signature -> signature.update((byte) b));
- }
-
- }
-
- @Override
- public void write(byte[] b) throws IOException {
+ public void write(int b) throws IOException {
getDestination().write(b);
- if (signaturesToVerify != null) {
- signaturesToVerify.iterator().forEachRemaining(signature -> signature.update(b));
+ for (OutputStream verifier : signatureVerifiers) {
+ verifier.write(b);
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
getDestination().write(b, off, len);
- if (signaturesToVerify != null) {
- signaturesToVerify.iterator().forEachRemaining(signature -> signature.update(b, off, len));
+ for (OutputStream verifier : signatureVerifiers) {
+ verifier.write(b, off, len);
}
}
@Override
- public void close() {
- if (!getStatus().isOK()) {
- return;
- }
- if (signaturesToVerify == null || signaturesToVerify.isEmpty()) {
- return;
- }
- Iterator<PGPSignature> iterator = signaturesToVerify.iterator();
- while (iterator.hasNext()) {
- PGPSignature signature = iterator.next();
- try {
- if (!signature.verify()) {
+ public void close() throws IOException {
+ try {
+ if (!getStatus().isOK()) {
+ return;
+ }
+
+ if (signaturesToVerify.isEmpty()) {
+ return;
+ }
+
+ PGPPublicKeyStore keyStore = new PGPPublicKeyStore();
+ for (Entry<PGPSignature, List<PGPContentVerifier>> entry : signaturesToVerify.entrySet()) {
+ PGPSignature signature = entry.getKey();
+ List<PGPContentVerifier> verifiers = entry.getValue();
+ boolean verified = false;
+ for (PGPContentVerifier verifier : verifiers) {
+ try {
+ verifier.getOutputStream().write(signature.getSignatureTrailer());
+ if (verifier.verify(signature.getSignature())) {
+ PGPPublicKey verifyingKey = verifierKeys.get(verifier);
+ if (!Boolean.FALSE.toString()
+ .equalsIgnoreCase(System.getProperty("p2.pgp.verifyExpiration"))) { //$NON-NLS-1$
+ if (PGPPublicKeyService.compareSignatureTimeToKeyValidityTime(signature,
+ verifyingKey) != 0) {
+ LogHelper.log(new Status(IStatus.WARNING, Activator.ID,
+ NLS.bind(Messages.Error_SignatureAfterKeyExpiration, PGPPublicKeyService
+ .toHex(verifyingKey.getFingerprint()).toUpperCase(Locale.ROOT))));
+ }
+ }
+
+ if (!Boolean.FALSE.toString()
+ .equalsIgnoreCase(System.getProperty("p2.pgp.verifyRevocation"))) { //$NON-NLS-1$
+ if (!keyService.isCreatedBeforeRevocation(signature, verifyingKey)) {
+ setStatus(new Status(IStatus.ERROR, Activator.ID,
+ NLS.bind(Messages.Error_SignatureAfterKeyRevocation, PGPPublicKeyService
+ .toHex(verifyingKey.getFingerprint()).toUpperCase(Locale.ROOT))));
+ return;
+ }
+ }
+
+ keyStore.addKey(verifyingKey);
+ verified = true;
+ break;
+ }
+ } catch (PGPException ex) {
+ LogHelper.log(new Status(IStatus.ERROR, Activator.ID, ex.getMessage(), ex));
+ }
+ }
+
+ if (!verified) {
setStatus(new Status(IStatus.ERROR, Activator.ID, Messages.Error_SignatureAndFileDontMatch));
return;
}
- } catch (PGPException ex) {
- setStatus(new Status(IStatus.ERROR, Activator.ID, ex.getMessage(), ex));
- return;
}
+
+ // Update the destination artifact descriptor with the signatures that have been
+ // verified and the keys used for that verification.
+ OutputStream destination = getDestination();
+ if (destination instanceof IAdaptable) {
+ ArtifactDescriptor destinationDescriptor = ((IAdaptable) destination)
+ .getAdapter(ArtifactDescriptor.class);
+ destinationDescriptor.setProperty(PGP_SIGNATURES_PROPERTY_NAME,
+ sourceDescriptor.getProperty(PGP_SIGNATURES_PROPERTY_NAME));
+ destinationDescriptor.setProperty(PGP_SIGNER_KEYS_PROPERTY_NAME, keyStore.toArmoredString());
+ }
+
+ setStatus(Status.OK_STATUS);
+ } finally
+
+ {
+ super.close();
}
- setStatus(Status.OK_STATUS);
}
}
diff --git a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/messages.properties b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/messages.properties
index 90d95f756..1d12500af 100644
--- a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/messages.properties
+++ b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/messages.properties
@@ -9,6 +9,8 @@
# SPDX-License-Identifier: EPL-2.0
###############################################################################
-Error_SignatureAndFileDontMatch=Signature is invalid for current content.
-Error_CouldNotLoadSignature=Could not load signature
-Error_publicKeyNotFound=Public key not found for {0}.
+Error_SignatureAfterKeyRevocation=The signature is created after the revocation time of the signature's key {0}
+Error_SignatureAfterKeyExpiration=The signature is created after the expiration time of the signature's key {0}
+Error_SignatureAndFileDontMatch=The signature is invalid for current content
+Error_CouldNotLoadSignature=The signatures could not be loaded
+Error_publicKeyNotFound=A public key with ID {0} needed to verify the signatures could not be found
diff --git a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/MirrorRequest.java b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/MirrorRequest.java
index d9f4dfe22..ccd932110 100644
--- a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/MirrorRequest.java
+++ b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/MirrorRequest.java
@@ -23,7 +23,6 @@ import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.runtime.*;
-import org.eclipse.equinox.internal.p2.artifact.processors.pgp.PGPSignatureVerifier;
import org.eclipse.equinox.internal.p2.artifact.repository.simple.SimpleArtifactDescriptor;
import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
import org.eclipse.equinox.internal.p2.repository.Transport;
@@ -201,14 +200,6 @@ public class MirrorRequest extends ArtifactRequest {
((ArtifactDescriptor) destinationDescriptor).addProperties(targetDescriptorProperties);
if (targetRepositoryProperties != null && destinationDescriptor instanceof SimpleArtifactDescriptor)
((SimpleArtifactDescriptor) destinationDescriptor).addRepositoryProperties(targetRepositoryProperties);
- if (isCanonical && destinationDescriptor instanceof ArtifactDescriptor) {
- // keep some safety properties
- if (sourceDescriptor.getProperty(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME) != null) {
- ((ArtifactDescriptor) destinationDescriptor)
- .addProperties(Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME,
- sourceDescriptor.getProperty(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME)));
- }
- }
return destinationDescriptor;
}
diff --git a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/simple/SimpleArtifactRepository.java b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/simple/SimpleArtifactRepository.java
index d627642d0..1de35b41f 100644
--- a/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/simple/SimpleArtifactRepository.java
+++ b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/simple/SimpleArtifactRepository.java
@@ -121,7 +121,7 @@ public class SimpleArtifactRepository extends AbstractArtifactRepository impleme
private long cacheTimestamp = 0l;
- public class ArtifactOutputStream extends OutputStream implements IStateful {
+ public class ArtifactOutputStream extends OutputStream implements IStateful, IAdaptable {
private boolean closed;
private long count = 0;
private IArtifactDescriptor descriptor;
@@ -205,6 +205,14 @@ public class SimpleArtifactRepository extends AbstractArtifactRepository impleme
public void setFirstLink(OutputStream value) {
firstLink = value;
}
+
+ @Override
+ public <T> T getAdapter(Class<T> adapter) {
+ if (adapter.isInstance(descriptor)) {
+ return adapter.cast(descriptor);
+ }
+ return null;
+ }
}
// TODO: optimize
diff --git a/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CertificateChecker.java b/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CertificateChecker.java
index 34aa50fc9..6f669e182 100644
--- a/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CertificateChecker.java
+++ b/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CertificateChecker.java
@@ -21,7 +21,7 @@ import java.util.Map.Entry;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import org.bouncycastle.openpgp.*;
+import org.bouncycastle.openpgp.PGPPublicKey;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.equinox.internal.p2.artifact.processors.pgp.PGPPublicKeyStore;
@@ -32,6 +32,7 @@ import org.eclipse.equinox.p2.core.UIServices.TrustInfo;
import org.eclipse.equinox.p2.engine.IProfile;
import org.eclipse.equinox.p2.engine.ProfileScope;
import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
import org.eclipse.osgi.service.security.TrustEngine;
import org.eclipse.osgi.signedcontent.*;
import org.eclipse.osgi.util.NLS;
@@ -60,6 +61,7 @@ public class CertificateChecker {
*/
private Map<IArtifactDescriptor, File> artifacts = new HashMap<>();
private final IProvisioningAgent agent;
+ private final PGPPublicKeyService keyService;
// Lazily loading
private Supplier<PGPPublicKeyStore> trustedKeys = new Supplier<>() {
@@ -80,6 +82,8 @@ public class CertificateChecker {
public CertificateChecker(IProvisioningAgent agent) {
this.agent = agent;
artifacts = new HashMap<>();
+ keyService = agent.getService(PGPPublicKeyService.class);
+
}
public IStatus start() {
@@ -104,7 +108,8 @@ public class CertificateChecker {
if (artifacts.isEmpty() || serviceUI == null) {
return status;
}
- Set<Long> trustedKeysIds = new HashSet<>();
+ Set<PGPPublicKey> trustedKeySet = new HashSet<>();
+ boolean isTrustedKeySetInitialized = false;
for (Entry<IArtifactDescriptor, File> artifact : artifacts.entrySet()) {
File artifactFile = artifact.getValue();
try {
@@ -128,24 +133,25 @@ public class CertificateChecker {
}
}
} else {
- Collection<PGPSignature> signatures = PGPSignatureVerifier.getSignatures(artifact.getKey());
- if (!signatures.isEmpty()) {
- if (trustedKeysIds.isEmpty() && !trustedKeys.get().isEmpty()) {
- trustedKeysIds.addAll(trustedKeys.get().all().stream()
- .map(PGPPublicKey::getKeyID).map(Long::valueOf).collect(Collectors.toSet()));
+ // The keys are in this destination artifact's properties if and only if the
+ // PGPSignatureVerifier verified the signatures against these keys.
+ List<PGPPublicKey> verifiedKeys = PGPPublicKeyStore
+ .readPublicKeys(
+ artifact.getKey().getProperty(PGPSignatureVerifier.PGP_SIGNER_KEYS_PROPERTY_NAME))
+ .stream().map(keyService::addKey).collect(Collectors.toList());
+ if (!verifiedKeys.isEmpty()) {
+ if (!isTrustedKeySetInitialized) {
+ isTrustedKeySetInitialized = true;
+ trustedKeySet.addAll(trustedKeys.get().all());
}
- if (signatures.stream().map(PGPSignature::getKeyID).noneMatch(trustedKeysIds::contains)) {
- untrustedPGPArtifacts.put(artifact.getKey(),
- signatures.stream().map(PGPSignature::getKeyID)
- .map(id -> findKey(id, artifact.getKey()))
- .filter(Objects::nonNull)
- .collect(Collectors.toList()));
+ if (verifiedKeys.stream().noneMatch(trustedKeySet::contains)) {
+ untrustedPGPArtifacts.put(artifact.getKey(), verifiedKeys);
}
} else {
unsigned.put(artifact.getKey(), artifactFile);
}
}
- } catch (GeneralSecurityException | PGPException e) {
+ } catch (GeneralSecurityException e) {
return new Status(IStatus.ERROR, EngineActivator.ID, Messages.CertificateChecker_SignedContentError, e);
} catch (IOException e) {
return new Status(IStatus.ERROR, EngineActivator.ID, Messages.CertificateChecker_SignedContentIOError, e);
@@ -231,10 +237,9 @@ public class CertificateChecker {
untrustedCertificates.remove(trustedCertificate);
}
}
- trustedKeysIds
- .addAll(trustInfo.getTrustedPGPKeys().stream().map(PGPPublicKey::getKeyID).collect(Collectors.toSet()));
- untrustedPGPArtifacts.values().removeIf(
- pgpKeys -> pgpKeys.stream().anyMatch(untrusted -> trustedKeysIds.contains(untrusted.getKeyID())));
+
+ trustedKeySet.addAll(trustInfo.getTrustedPGPKeys());
+ untrustedPGPArtifacts.values().removeIf(pgpKeys -> pgpKeys.stream().anyMatch(trustedKeySet::contains));
// If there is still untrusted content, cancel the operation
if (!untrustedCertificates.isEmpty() || !untrustedPGPArtifacts.isEmpty()) {
@@ -259,18 +264,6 @@ public class CertificateChecker {
return status;
}
-
- private PGPPublicKey findKey(long id, IArtifactDescriptor artifact) {
- PGPPublicKey key = PGPSignatureVerifier.KNOWN_KEYS.getKey(id);
- if (key != null) {
- return key;
- }
- // in case keys from artifact were not imported yet in keystore, add them
- PGPSignatureVerifier.KNOWN_KEYS
- .addKeys(artifact.getProperty(PGPSignatureVerifier.PGP_SIGNER_KEYS_PROPERTY_NAME));
- return PGPSignatureVerifier.KNOWN_KEYS.getKey(id);
- }
-
private IStatus persistTrustedCertificates(Certificate[] trustedCertificates) {
if (trustedCertificates == null)
// I'm pretty sure this would be a bug; trustedCertificates should never be null here.
@@ -326,15 +319,17 @@ public class CertificateChecker {
public PGPPublicKeyStore buildPGPTrustore() {
PGPPublicKeyStore trustStore = new PGPPublicKeyStore();
if (profile != null) {
- trustStore.addKeys(profile.getProperty(TRUSTED_KEY_STORE_PROPERTY));
+ // PGPPublicKeyStore.readPublicKeys(profile.getProperty(TRUSTED_KEY_STORE_PROPERTY)).stream().map(keyService::addKey).forEach(trustStore::addKey);
ProfileScope profileScope = new ProfileScope(agent.getService(IAgentLocation.class),
profile.getProfileId());
- trustStore.addKeys(profileScope.getNode(EngineActivator.ID).get(TRUSTED_KEY_STORE_PROPERTY, null));
+ PGPPublicKeyStore
+ .readPublicKeys(profileScope.getNode(EngineActivator.ID).get(TRUSTED_KEY_STORE_PROPERTY, null))
+ .stream().map(keyService::addKey).forEach(trustStore::addKey);
}
// load from bundles providing capability
for (IConfigurationElement extension : RegistryFactory.getRegistry()
- .getConfigurationElementsFor(EngineActivator.ID + ".pgp")) {
- if ("trustedKeys".equals(extension.getName())) {
+ .getConfigurationElementsFor(EngineActivator.ID + ".pgp")) { //$NON-NLS-1$
+ if ("trustedKeys".equals(extension.getName())) { //$NON-NLS-1$
String pathInBundle = extension.getAttribute("path"); //$NON-NLS-1$
if (pathInBundle != null) {
Stream.of(EngineActivator.getContext().getBundles())
@@ -343,7 +338,8 @@ public class CertificateChecker {
.filter(Objects::nonNull) //
.forEach(url -> {
try (InputStream stream = url.openStream()) {
- PGPPublicKeyStore.readPublicKeys(stream).forEach(trustStore::addKey);
+ PGPPublicKeyStore.readPublicKeys(stream).stream().map(keyService::addKey)
+ .forEach(trustStore::addKey);
} catch (IOException e) {
DebugHelper.debug(DEBUG_PREFIX, e.getMessage());
}
@@ -380,7 +376,7 @@ public class CertificateChecker {
// profile.query(QueryUtil.ALL_UNITS, new NullProgressMonitor()).forEach(
// iu ->
// store.addAll(PGPSignatureVerifier.readPublicKeys(iu.getProperty(TRUSTED_KEY_STORE_PROPERTY))));
- trustStore.all().forEach(PGPSignatureVerifier.KNOWN_KEYS::addKey);
+ // trustStore.all().forEach(PGPSignatureVerifier.KNOWN_KEYS::addKey);
return trustStore;
}
diff --git a/bundles/org.eclipse.equinox.p2.repository/.options b/bundles/org.eclipse.equinox.p2.repository/.options
new file mode 100644
index 000000000..c2d3dc538
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.repository/.options
@@ -0,0 +1,3 @@
+org.eclipse.equinox.p2.repository/credentials/debug = false
+org.eclipse.equinox.p2.repository/transport/debug = false
+org.eclipse.equinox.p2.repository/keyservice/debug = false
diff --git a/bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF
index ce71e8628..0f0800872 100644
--- a/bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.repository/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.equinox.p2.repository;singleton:=true
-Bundle-Version: 2.5.300.qualifier
+Bundle-Version: 2.6.0.qualifier
Bundle-Activator: org.eclipse.equinox.internal.p2.repository.Activator
Bundle-Vendor: %providerName
Bundle-Localization: plugin
@@ -42,6 +42,16 @@ Require-Bundle: org.eclipse.equinox.common,
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Import-Package: javax.crypto,
+ org.bouncycastle;version="1.69.0",
+ org.bouncycastle.bcpg;version="1.69.0",
+ org.bouncycastle.gpg.keybox;version="1.69.0",
+ org.bouncycastle.gpg.keybox.jcajce;version="1.69.0",
+ org.bouncycastle.openpgp;version="1.69.0",
+ org.bouncycastle.openpgp.jcajce;version="1.69.0",
+ org.bouncycastle.openpgp.operator;version="1.69.0",
+ org.bouncycastle.openpgp.operator.bc;version="1.69.0",
+ org.bouncycastle.openpgp.operator.jcajce;version="1.69.0",
+ org.bouncycastle.util.encoders;version="1.69.0",
org.eclipse.core.runtime.jobs,
org.eclipse.core.runtime.preferences;version="3.2.0",
org.eclipse.equinox.internal.p2.core,
@@ -61,5 +71,5 @@ Import-Package: javax.crypto,
org.osgi.service.packageadmin;version="1.2.0",
org.osgi.service.prefs;version="1.0.0",
org.osgi.util.tracker;version="1.4.0"
-Service-Component: OSGI-INF/cacheManager.xml
+Service-Component: OSGI-INF/cacheManager.xml, OSGI-INF/pgpPublicKeyService.xml
Automatic-Module-Name: org.eclipse.equinox.p2.repository
diff --git a/bundles/org.eclipse.equinox.p2.repository/OSGI-INF/pgpPublicKeyService.xml b/bundles/org.eclipse.equinox.p2.repository/OSGI-INF/pgpPublicKeyService.xml
new file mode 100644
index 000000000..e303cd93e
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.repository/OSGI-INF/pgpPublicKeyService.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="org.eclipse.equinox.p2.repository.spi.pgpPublicKeyService">
+ <implementation class="org.eclipse.equinox.internal.p2.repository.PGPKeyServiceComponent"/>
+ <service>
+ <provide interface="org.eclipse.equinox.p2.core.spi.IAgentServiceFactory"/>
+ </service>
+ <property name="p2.agent.servicename" type="String" value="org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService"/>
+</scr:component>
diff --git a/bundles/org.eclipse.equinox.p2.repository/build.properties b/bundles/org.eclipse.equinox.p2.repository/build.properties
index f3b28f666..89e396d06 100644
--- a/bundles/org.eclipse.equinox.p2.repository/build.properties
+++ b/bundles/org.eclipse.equinox.p2.repository/build.properties
@@ -17,6 +17,7 @@ bin.includes = META-INF/,\
about.html,\
plugin.properties,\
OSGI-INF/cacheManager.xml,\
- OSGI-INF/
+ OSGI-INF/,\
+ .options
src.includes = about.html
source.. = src/
diff --git a/bundles/org.eclipse.equinox.p2.repository/pom.xml b/bundles/org.eclipse.equinox.p2.repository/pom.xml
index f80aa9c5c..f094cebab 100644
--- a/bundles/org.eclipse.equinox.p2.repository/pom.xml
+++ b/bundles/org.eclipse.equinox.p2.repository/pom.xml
@@ -9,6 +9,6 @@
</parent>
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.p2.repository</artifactId>
- <version>2.5.300-SNAPSHOT</version>
+ <version>2.6.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/PGPKeyServiceComponent.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/PGPKeyServiceComponent.java
new file mode 100644
index 000000000..dfb73bc90
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/PGPKeyServiceComponent.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Eclipse contributors and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.repository;
+
+import org.eclipse.equinox.internal.provisional.p2.repository.DefaultPGPPublicKeyService;
+import org.eclipse.equinox.p2.core.IProvisioningAgent;
+import org.eclipse.equinox.p2.core.spi.IAgentServiceFactory;
+
+public class PGPKeyServiceComponent implements IAgentServiceFactory {
+ @Override
+ public Object createService(IProvisioningAgent agent) {
+ return new DefaultPGPPublicKeyService(agent);
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/helpers/DebugHelper.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/helpers/DebugHelper.java
index bb422888a..e4955cefe 100644
--- a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/helpers/DebugHelper.java
+++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/p2/repository/helpers/DebugHelper.java
@@ -25,15 +25,18 @@ public class DebugHelper {
public static final boolean DEBUG_REPOSITORY_CREDENTIALS;
public static final boolean DEBUG_REPOSITORY_TRANSPORT;
+ public static final boolean DEBUG_KEY_SERVICE;
static {
DebugOptions options = ServiceHelper.getService(Activator.getContext(), DebugOptions.class);
if (options != null) {
DEBUG_REPOSITORY_CREDENTIALS = options.getBooleanOption(Activator.ID + "/credentials/debug", false); //$NON-NLS-1$
DEBUG_REPOSITORY_TRANSPORT = options.getBooleanOption(Activator.ID + "/transport/debug", false); //$NON-NLS-1$
+ DEBUG_KEY_SERVICE = options.getBooleanOption(Activator.ID + "/keyservice/debug", false); //$NON-NLS-1$
} else {
DEBUG_REPOSITORY_CREDENTIALS = false;
DEBUG_REPOSITORY_TRANSPORT = false;
+ DEBUG_KEY_SERVICE = false;
}
}
@@ -50,7 +53,7 @@ public class DebugHelper {
System.out.println(buffer.toString());
}
- public static void debug(String name, String message, Object[] keyValueArray) {
+ public static void debug(String name, String message, Object... keyValueArray) {
if (keyValueArray == null || keyValueArray.length == 0)
debug(name, message);
else {
diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/provisional/p2/repository/DefaultPGPPublicKeyService.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/provisional/p2/repository/DefaultPGPPublicKeyService.java
new file mode 100644
index 000000000..97cccc805
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/internal/provisional/p2/repository/DefaultPGPPublicKeyService.java
@@ -0,0 +1,867 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Eclipse contributors and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.equinox.internal.provisional.p2.repository;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.ByteBuffer;
+import java.nio.file.*;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.util.*;
+import java.util.function.*;
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.gpg.keybox.*;
+import org.bouncycastle.gpg.keybox.jcajce.JcaKeyBoxBuilder;
+import org.bouncycastle.openpgp.*;
+import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPGPContentVerifierBuilderProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
+import org.eclipse.equinox.internal.p2.repository.Transport;
+import org.eclipse.equinox.internal.p2.repository.helpers.DebugHelper;
+import org.eclipse.equinox.p2.core.IAgentLocation;
+import org.eclipse.equinox.p2.core.IProvisioningAgent;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
+
+/**
+ * @since 2.6
+ */
+public class DefaultPGPPublicKeyService extends PGPPublicKeyService {
+
+ /**
+ * Enable debug tracing either via debug options or via a system property.
+ */
+ private static final boolean DEBUG_KEY_SERVICE = DebugHelper.DEBUG_KEY_SERVICE
+ || Boolean.TRUE.toString().equalsIgnoreCase(System.getProperty("p2.keyserver.debug")); //$NON-NLS-1$
+
+ /**
+ * The system property used to initialized the {@link #keyServer}.
+ */
+ private static final String KEY_SERVERS_PROPERTY = "p2.keyservers"; //$NON-NLS-1$
+
+ /**
+ * The system property used to determine where to look for the GPG pubring.
+ *
+ * @see #getGPPDirectory()
+ */
+ private static final String GPG_HOME_PROPERTY = "p2.gpg.home"; //$NON-NLS-1$
+
+ /**
+ * The system property used to determine whether to enable GPG pubring lookup.
+ *
+ * @see #gpg
+ * @see #setGPG(boolean)
+ */
+ private static final String GPG_PROPERTY = "p2.gpg"; //$NON-NLS-1$
+
+ /**
+ * The number of elapsed milliseconds after which keys cached from a key server
+ * are considered stale such that they will be re-fetched if possible.
+ */
+ private static final long STALE_AFTER_MILLIS = Long.getLong("p2.keyserver.cache.stale", 24) * 1000 * 60 * 60; //$NON-NLS-1$
+
+ /**
+ * Reuse p2's transport layer for fetching keys from the key server.
+ */
+ private final Transport transport;
+
+ /**
+ * Keys {@link #addKey(PGPPublicKey) added} to this key service are cached via
+ * this map.
+ */
+ private final Map<Long, LocalKeyCache> localKeys = new LinkedHashMap<>();
+
+ /**
+ * A folder with locally cached keys, indexed on {@link PGPPublicKey#getKeyID()
+ * key ID}.
+ */
+ private final Path keyCache;
+
+ /**
+ * The current key servers.
+ */
+ private final Map<String, PGPKeyServer> keyServers = new LinkedHashMap<>();
+
+ /**
+ * Whether to load from GPG's pubring.
+ */
+ private boolean gpg;
+
+ /**
+ * Creates an instance associated with the given agent.
+ *
+ * @param agent the agent for which a key service is provided.
+ */
+ public DefaultPGPPublicKeyService(IProvisioningAgent agent) {
+ IAgentLocation agentLocation = agent.getService(IAgentLocation.class);
+ URI dataArea = agentLocation.getDataArea(org.eclipse.equinox.internal.p2.repository.Activator.ID);
+ keyCache = Paths.get(dataArea).resolve("pgp"); //$NON-NLS-1$
+ try {
+ Files.createDirectories(keyCache);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ if (DEBUG_KEY_SERVICE) {
+ DebugHelper.debug("KeyServer", "Cache", "location", keyCache); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ String keyServersProperty = System.getProperty(KEY_SERVERS_PROPERTY, ""); //$NON-NLS-1$
+ if (!keyServersProperty.isBlank()) {
+ Set<String> keyServersSet = new LinkedHashSet<>();
+ for (String keyServer : keyServersProperty.split("[,; \t]+")) { //$NON-NLS-1$
+ if (!keyServer.isEmpty()) {
+ keyServersSet.add(keyServer);
+ }
+ }
+
+ setKeyServers(keyServersSet);
+ }
+
+ setGPG(Boolean.TRUE.toString().equalsIgnoreCase(System.getProperty(GPG_PROPERTY, Boolean.TRUE.toString()))
+ || !System.getProperty(GPG_HOME_PROPERTY, "").isBlank()); //$NON-NLS-1$
+
+ transport = agent.getService(Transport.class);
+ }
+
+ public Set<String> getKeyServers() {
+ return Collections.unmodifiableSet(keyServers.keySet());
+ }
+
+ public void setKeyServers(Set<String> keyServers) {
+ Map<String, PGPKeyServer> newKeyServers = new LinkedHashMap<>();
+ for (String keyServer : keyServers) {
+ PGPKeyServer pgpKeyServer = this.keyServers.get(keyServer);
+ if (pgpKeyServer == null) {
+ pgpKeyServer = new PGPKeyServer(keyServer, this.keyCache) {
+ @Override
+ protected boolean isStale(Path path) {
+ return DefaultPGPPublicKeyService.this.isStale(path);
+ }
+
+ @Override
+ protected IStatus download(URI uri, OutputStream receiver, IProgressMonitor monitor) {
+ return DefaultPGPPublicKeyService.this.download(uri, receiver, monitor);
+ }
+
+ @Override
+ protected void log(Throwable throwable) {
+ DefaultPGPPublicKeyService.this.log(throwable);
+ }
+ };
+ }
+ newKeyServers.put(keyServer, pgpKeyServer);
+ }
+
+ this.keyServers.clear();
+ this.keyServers.putAll(newKeyServers);
+ }
+
+ @Override
+ public PGPPublicKey getKey(String fingerprint) {
+ int length = fingerprint.length();
+ if (length >= 16) {
+ long keyID = Long.parseLong(fingerprint.substring(length - 16, length), 16);
+ Collection<PGPPublicKey> keys = getKeys(keyID);
+ for (PGPPublicKey key : keys) {
+ if (toHex(key.getFingerprint()).equalsIgnoreCase(fingerprint)) {
+ return key;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<PGPPublicKey> getKeys(long keyID) {
+ List<PGPPublicKey> keys = new ArrayList<>();
+ for (PGPKeyServer keyServer : keyServers.values()) {
+ keys.addAll(keyServer.getKeys(keyID));
+ }
+
+ LocalKeyCache localKeyCache = localKeys.get(keyID);
+ if (localKeyCache != null) {
+ keys.addAll(localKeyCache.get());
+ }
+
+ keys.addAll(getDefaultKeys(keyID));
+
+ return reconcileKeys(keys);
+ }
+
+ public boolean isGGP() {
+ return gpg;
+ }
+
+ public void setGPG(boolean gpg) {
+ this.gpg = gpg;
+ }
+
+ protected List<PGPPublicKey> getDefaultKeys(long keyID) {
+ return gpg ? getGPGPubringKeys(keyID) : Collections.emptyList();
+ }
+
+ protected List<PGPPublicKey> reconcileKeys(List<PGPPublicKey> keys) {
+ if (keys.size() <= 1) {
+ return new ArrayList<>(keys);
+ }
+
+ Map<ByteBuffer, PGPPublicKey> encodings = new LinkedHashMap<>();
+ Map<ByteBuffer, PGPPublicKey> fingerprints = new LinkedHashMap<>();
+ for (PGPPublicKey key : keys) {
+ try {
+ ByteBuffer encoding = ByteBuffer.wrap(key.getEncoded());
+ PGPPublicKey existingKey = encodings.put(encoding, key);
+ if (existingKey == null) {
+ ByteBuffer fingerprint = ByteBuffer.wrap(key.getFingerprint());
+ PGPPublicKey otherKey = fingerprints.put(fingerprint, key);
+ if (otherKey != null) {
+ fingerprints.put(fingerprint, choose(otherKey, key));
+ }
+ }
+ } catch (IOException e) {
+ log(e);
+ }
+ }
+
+ return new ArrayList<>(fingerprints.values());
+ }
+
+ /**
+ * While {@link #reconcileKeys(List) reconciling}, when two keys have the same
+ * fingerprint, this method must be chosen in favor of the other to be retained
+ * in the result.
+ *
+ * @param key1 the first key from which to choose.
+ * @param key2 the second key from which to choose.
+ * @return the key with the newest or most complete details.
+ */
+ protected PGPPublicKey choose(PGPPublicKey key1, PGPPublicKey key2) {
+ // Favor the one with the newest information.
+ long signatureTime1 = getNewestSignature(key1);
+ long signatureTime2 = getNewestSignature(key2);
+ if (signatureTime1 > signatureTime2) {
+ return key1;
+ } else if (signatureTime1 < signatureTime2) {
+ return key2;
+ }
+
+ // Favor the one with the most information.
+ int signatureCount1 = getSignatureCount(key1);
+ int signatureCount2 = getSignatureCount(key2);
+ if (signatureCount1 > signatureCount2) {
+ return key1;
+ } else if (signatureCount1 < signatureCount2) {
+ return key2;
+ }
+
+ return key1;
+ }
+
+ protected static int getSignatureCount(PGPPublicKey key) {
+ int result = 0;
+ for (Iterator<PGPSignature> signatures = key.getSignatures(); signatures.hasNext(); signatures.next()) {
+ ++result;
+ }
+ for (Iterator<PGPSignature> signatures = key.getKeySignatures(); signatures.hasNext(); signatures.next()) {
+ ++result;
+ }
+ return result;
+ }
+
+ protected static long getNewestSignature(PGPPublicKey key) {
+ long result = 0;
+ for (Iterator<PGPSignature> signatures = key.getSignatures(); signatures.hasNext();) {
+ PGPSignature signature = signatures.next();
+ long time = signature.getCreationTime().getTime();
+ result = Math.max(result, time);
+ }
+ for (Iterator<PGPSignature> signatures = key.getKeySignatures(); signatures.hasNext();) {
+ PGPSignature signature = signatures.next();
+ long time = signature.getCreationTime().getTime();
+ result = Math.max(result, time);
+ }
+
+ return result;
+ }
+
+ @Override
+ public PGPPublicKey addKey(PGPPublicKey key) {
+ long keyID = key.getKeyID();
+ LocalKeyCache localKeyCache = localKeys.get(keyID);
+ if (localKeyCache == null) {
+ String hexKeyID = toHex(keyID);
+ Path cache = keyCache.resolve(hexKeyID + ".asc"); //$NON-NLS-1$
+ localKeyCache = new LocalKeyCache(cache) {
+ @Override
+ protected List<PGPPublicKey> reconcileKeys(List<PGPPublicKey> keys) {
+ return DefaultPGPPublicKeyService.this.reconcileKeys(keys);
+ }
+
+ @Override
+ protected void log(Throwable throwable) {
+ DefaultPGPPublicKeyService.this.log(throwable);
+ }
+ };
+ localKeys.put(keyID, localKeyCache);
+ }
+ localKeyCache.add(key);
+
+ Collection<PGPPublicKey> keys = getKeys(keyID);
+ byte[] fingerprint = key.getFingerprint();
+ for (PGPPublicKey otherKey : keys) {
+ if (Arrays.equals(otherKey.getFingerprint(), fingerprint)) {
+ return otherKey;
+ }
+ }
+
+ // We should never get this far.
+ return key;
+ }
+
+ protected boolean isStale(Path path) {
+ try {
+ FileTime lastModifiedTime = Files.getLastModifiedTime(path);
+ long lastModified = lastModifiedTime.toMillis();
+ long currentTime = System.currentTimeMillis();
+ return currentTime - lastModified > STALE_AFTER_MILLIS;
+ } catch (IOException e) {
+ return true;
+ }
+ }
+
+ @Override
+ public Set<PGPPublicKey> getVerifiedCertifications(PGPPublicKey key) {
+ Set<PGPPublicKey> certifications = new LinkedHashSet<>();
+ LOOP: for (Iterator<PGPSignature> signatures = key.getSignatures(); signatures.hasNext();) {
+ PGPSignature signature = signatures.next();
+ long signingKeyID = signature.getKeyID();
+ for (PGPPublicKey signingKey : getKeys(signingKeyID)) {
+ switch (signature.getSignatureType()) {
+ case PGPSignature.SUBKEY_BINDING:
+ case PGPSignature.PRIMARYKEY_BINDING: {
+ try {
+ signature.init(new BcPGPContentVerifierBuilderProvider(), signingKey);
+ if (signature.verifyCertification(signingKey, key)
+ && isCreatedBeforeRevocation(signature, signingKey)) {
+ certifications.add(signingKey);
+ continue LOOP;
+ }
+ } catch (PGPException e) {
+ //$FALL-THROUGH$
+ }
+ break;
+ }
+ case PGPSignature.DEFAULT_CERTIFICATION:
+ case PGPSignature.NO_CERTIFICATION:
+ case PGPSignature.CASUAL_CERTIFICATION:
+ case PGPSignature.POSITIVE_CERTIFICATION: {
+ for (Iterator<String> userIDs = key.getUserIDs(); userIDs.hasNext();) {
+ String userID = userIDs.next();
+ try {
+ signature.init(new BcPGPContentVerifierBuilderProvider(), signingKey);
+ if (signature.verifyCertification(userID, key)
+ && isCreatedBeforeRevocation(signature, signingKey)) {
+ certifications.add(signingKey);
+ continue LOOP;
+ }
+ } catch (PGPException e) {
+ //$FALL-THROUGH$
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ return certifications;
+ }
+
+ @Override
+ public Date getVerifiedRevocationDate(PGPPublicKey key) {
+ for (Iterator<PGPSignature> signatures = key.getSignatures(); signatures.hasNext();) {
+ PGPSignature signature = signatures.next();
+ long signingKeyID = signature.getKeyID();
+ for (PGPPublicKey signingKey : getKeys(signingKeyID)) {
+ switch (signature.getSignatureType()) {
+ case PGPSignature.KEY_REVOCATION:
+ case PGPSignature.CERTIFICATION_REVOCATION: {
+ try {
+ signature.init(new BcPGPContentVerifierBuilderProvider(), signingKey);
+ if (signature.verifyCertification(key)) {
+ return signature.getCreationTime();
+ }
+ } catch (PGPException e) {
+ //$FALL-THROUGH$
+ }
+ break;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ protected Collection<PGPPublicKey> fetchKeys(URI uri, Path cache) throws IOException {
+ try {
+ ByteArrayOutputStream reciever = new ByteArrayOutputStream();
+ IStatus download = download(uri, reciever, new NullProgressMonitor());
+ if (!download.isOK()) {
+ Throwable exception = download.getException();
+ if (exception != null) {
+ throw new IOException(download.getMessage(), exception);
+ }
+ throw new IOException(download.getMessage());
+ }
+ List<PGPPublicKey> result = new ArrayList<>();
+ byte[] bytes = reciever.toByteArray();
+ try (InputStream input = new ArmoredInputStream(new ByteArrayInputStream(bytes))) {
+ result.addAll(loadKeys(input));
+ }
+
+ try (OutputStream out = newAtomicOutputStream(cache)) {
+ out.write(bytes);
+ }
+
+ return result;
+ } catch (IOException ex) {
+ if (Files.isRegularFile(cache)) {
+ try (InputStream input = new ArmoredInputStream(new BufferedInputStream(Files.newInputStream(cache)))) {
+ return loadKeys(input);
+ } catch (IOException ex1) {
+ try {
+ // Assume the cache is corrupt so delete it.
+ Files.delete(cache);
+ } catch (IOException ex2) {
+ log(ex2);
+ }
+ // Rethrow original network failure exception
+ throw new IOException("Error while processing " + uri + " as well while processing the cache " //$NON-NLS-1$ //$NON-NLS-2$
+ + cache + ": " + ex1.getMessage(), ex); //$NON-NLS-1$
+ }
+ }
+ throw new IOException("Error while processing " + uri, ex); //$NON-NLS-1$
+ }
+ }
+
+ protected IStatus download(URI uri, OutputStream receiver, IProgressMonitor monitor) {
+ return transport.download(uri, receiver, monitor);
+ }
+
+ protected void log(Throwable throwable) {
+ if (DEBUG_KEY_SERVICE) {
+ LogHelper.log(new Status(IStatus.ERROR, org.eclipse.equinox.internal.p2.repository.Activator.ID,
+ throwable.getMessage(), throwable));
+ }
+ }
+
+ protected static OutputStream newAtomicOutputStream(Path cache) throws IOException {
+ Path temp = Files.createTempFile(cache.getParent(), "out", ".tmp"); //$NON-NLS-1$ //$NON-NLS-2$
+ return new BufferedOutputStream(Files.newOutputStream(temp)) {
+ @Override
+ public void close() throws IOException {
+ super.close();
+ Files.move(temp, cache, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
+ }
+ };
+ }
+
+ protected static List<PGPPublicKey> loadKeys(InputStream input) throws IOException {
+ try {
+ List<PGPPublicKey> result = new ArrayList<>();
+ for (Object o : new JcaPGPObjectFactory(input)) {
+ if (o instanceof PGPPublicKeyRingCollection) {
+ collectKeys((PGPPublicKeyRingCollection) o, result::add);
+ } else if (o instanceof PGPPublicKeyRing) {
+ collectKeys((PGPPublicKeyRing) o, result::add);
+ } else if (o instanceof PGPPublicKey) {
+ result.add((PGPPublicKey) o);
+ }
+ }
+ return result;
+ } catch (RuntimeException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ private static void collectKeys(PGPPublicKeyRingCollection pgpPublicKeyRingCollection,
+ Consumer<PGPPublicKey> collector) {
+ pgpPublicKeyRingCollection.forEach(keyring -> collectKeys(keyring, collector));
+ }
+
+ private static void collectKeys(PGPPublicKeyRing pgpPublicKeyRing, Consumer<PGPPublicKey> collector) {
+ pgpPublicKeyRing.getPublicKeys().forEachRemaining(collector::accept);
+ }
+
+ private static abstract class LocalKeyCache {
+ private Path cache;
+ private FileTime lastModifiedTime;
+ private List<PGPPublicKey> keys;
+
+ public LocalKeyCache(Path cache) {
+ this.cache = cache;
+ }
+
+ protected abstract void log(Throwable throwable);
+
+ protected abstract List<PGPPublicKey> reconcileKeys(List<PGPPublicKey> keysToReconcile);
+
+ public List<PGPPublicKey> get() {
+ if (keys != null) {
+ try {
+ FileTime newLastModifiedTime = Files.getLastModifiedTime(cache);
+ if (lastModifiedTime == null || lastModifiedTime.compareTo(newLastModifiedTime) < 0) {
+ lastModifiedTime = newLastModifiedTime;
+ } else {
+ return keys;
+ }
+ } catch (Exception e) {
+ //$FALL-THROUGH$
+ }
+ }
+
+ if (!Files.isRegularFile(cache)) {
+ return List.of();
+ }
+
+ try (InputStream input = new ArmoredInputStream(new BufferedInputStream(Files.newInputStream(cache)))) {
+ keys = loadKeys(input);
+ return keys;
+ } catch (IOException ex) {
+ log(ex);
+ try {
+ // Assume the cache is corrupt so delete it.
+ Files.delete(cache);
+ } catch (IOException ex2) {
+ log(ex2);
+ }
+ return List.of();
+ }
+ }
+
+ public void add(PGPPublicKey key) {
+ List<PGPPublicKey> oldKeys = get();
+ List<PGPPublicKey> newKeys = new ArrayList<>(oldKeys);
+ newKeys.add(key);
+ newKeys = reconcileKeys(newKeys);
+ if (!oldKeys.equals(newKeys)) {
+ try (OutputStream underlyingStream = newAtomicOutputStream(cache);
+ OutputStream output = new ArmoredOutputStream(underlyingStream)) {
+ for (PGPPublicKey newKey : newKeys) {
+ newKey.encode(output);
+ }
+ } catch (IOException e) {
+ log(e);
+ return;
+ }
+ keys = newKeys;
+ }
+ }
+ }
+
+ private static abstract class PGPKeyServer {
+ private final Map<Long, List<PGPPublicKey>> keyIDMap = new LinkedHashMap<>();
+
+ private final String keyServer;
+
+ private final Path keyCache;
+
+ public PGPKeyServer(String keyServer, Path baseCache) {
+ this.keyServer = keyServer;
+ keyCache = baseCache.resolve(keyServer.replace(':', '_'));
+ if (!Files.isDirectory(this.keyCache)) {
+ try {
+ Files.createDirectory(keyCache);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ protected abstract boolean isStale(Path path);
+
+ protected abstract IStatus download(URI uri, OutputStream receiver, IProgressMonitor monitor);
+
+ protected abstract void log(Throwable throwable);
+
+ public List<PGPPublicKey> getKeys(long keyID) {
+ List<PGPPublicKey> keys = keyIDMap.get(keyID);
+ String hexKeyID = toHex(keyID);
+ Path cache = keyCache.resolve(hexKeyID + ".asc"); //$NON-NLS-1$
+ boolean needsRemoteFetch = !Files.isRegularFile(cache) || isStale(cache);
+ if (keys == null || needsRemoteFetch) {
+ try {
+ Iterable<PGPPublicKey> fetchedKeys;
+ if (needsRemoteFetch) {
+ String link = "https://" + keyServer + "/pks/lookup?op=get&search=0x" + hexKeyID; //$NON-NLS-1$ //$NON-NLS-2$
+ if (DEBUG_KEY_SERVICE) {
+ DebugHelper.debug("KeyServer", "Searching", "uri", link); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ URI uri = new URI(link);
+ fetchedKeys = fetchKeys(uri, cache);
+ } else {
+ try (InputStream input = new ArmoredInputStream(
+ new BufferedInputStream(Files.newInputStream(cache)))) {
+ fetchedKeys = loadKeys(input);
+ }
+ }
+
+ List<PGPPublicKey> newKeys = new ArrayList<>();
+ for (PGPPublicKey fetchedKey : fetchedKeys) {
+ long fetchedKeyID = fetchedKey.getKeyID();
+ if (fetchedKeyID == keyID) {
+ newKeys.add(fetchedKey);
+ }
+ }
+
+ keyIDMap.put(keyID, newKeys);
+ keys = newKeys;
+ } catch (URISyntaxException | IOException e) {
+ log(e);
+ if (keys == null || keys.isEmpty()) {
+ List<PGPPublicKey> newKeys = List.of();
+ keyIDMap.put(keyID, newKeys);
+ keys = newKeys;
+ }
+ }
+ }
+
+ return Collections.unmodifiableList(keys);
+ }
+
+ protected Collection<PGPPublicKey> fetchKeys(URI uri, Path cache) throws IOException {
+ try {
+ ByteArrayOutputStream reciever = new ByteArrayOutputStream();
+ IStatus download = download(uri, reciever, new NullProgressMonitor());
+ if (!download.isOK()) {
+ // If the file is not found, save an empty file to prevent repeated attempts to
+ // download from this URI.
+ Throwable exception = download.getException();
+ if (exception instanceof FileNotFoundException) {
+ log(exception);
+ } else {
+ if (exception != null) {
+ throw new IOException(download.getMessage(), exception);
+ }
+ throw new IOException(download.getMessage());
+ }
+ }
+ List<PGPPublicKey> result;
+ byte[] bytes = reciever.toByteArray();
+ try {
+ try (InputStream input = new ArmoredInputStream(new ByteArrayInputStream(bytes))) {
+ result = loadKeys(input);
+ }
+ } catch (IOException ex) {
+ log(ex);
+ // If the bytes can't be processed cache an empty file to prevent repeated
+ // attempts.
+ bytes = new byte[0];
+ result = List.of();
+ }
+
+ try (OutputStream out = newAtomicOutputStream(cache)) {
+ out.write(bytes);
+ }
+
+ return result;
+ } catch (IOException ex) {
+ // If the key server fails, load the cache if it exists.
+ if (Files.isRegularFile(cache)) {
+ try (InputStream input = new ArmoredInputStream(
+ new BufferedInputStream(Files.newInputStream(cache)))) {
+ return loadKeys(input);
+ } catch (IOException ex1) {
+ try {
+ // Assume the cache is corrupt so delete it.
+ Files.delete(cache);
+ } catch (IOException ex2) {
+ log(ex2);
+ }
+ // Rethrow original network failure exception with additional details
+ throw new IOException("Error while processing " + uri + " as well while processing the cache " //$NON-NLS-1$ //$NON-NLS-2$
+ + cache + ": " + ex1.getMessage(), ex); //$NON-NLS-1$
+ }
+ }
+ throw new IOException("Error while processing " + uri, ex); //$NON-NLS-1$
+ }
+ }
+
+ }
+
+ private static List<PGPPublicKey> getGPGPubringKeys(long keyID) {
+ return GPGPubringCache.getKeys(keyID);
+ }
+
+ private static class GPGPubringCache {
+ private static final Supplier<PGPPublicKeyRingCollection> GPG_PUBRING = getGPGPubring();
+ private static volatile PGPPublicKeyRingCollection cachePubring;
+ private static volatile Map<Long, List<PGPPublicKey>> cache;
+
+ public static List<PGPPublicKey> getKeys(long keyID) {
+ PGPPublicKeyRingCollection pubring = GPG_PUBRING.get();
+ if (pubring != cachePubring) {
+ Map<Long, List<PGPPublicKey>> newCache = new LinkedHashMap<>();
+ for (Iterator<PGPPublicKeyRing> keyRings = pubring.getKeyRings(); keyRings.hasNext();) {
+ for (PGPPublicKey key : keyRings.next()) {
+ long keyID2 = key.getKeyID();
+ List<PGPPublicKey> keys = newCache.computeIfAbsent(keyID2, it -> new ArrayList<>());
+ keys.add(key);
+ }
+ }
+ cache = newCache;
+ cachePubring = pubring;
+ }
+
+ List<PGPPublicKey> result = cache.get(keyID);
+ return result == null ? List.of() : result;
+ }
+ }
+
+ private static abstract class GPGPubringSupplier implements Supplier<PGPPublicKeyRingCollection> {
+
+ private final Path pubring;
+
+ private PGPPublicKeyRingCollection keyRingCollection;
+
+ private FileTime lastModifiedTime;
+
+ public GPGPubringSupplier(Path pubring) {
+ this.pubring = pubring;
+ try {
+ keyRingCollection = new PGPPublicKeyRingCollection(Collections.emptyList());
+ } catch (IOException | PGPException e) {
+ // Cannot happen for an empty collection.
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public PGPPublicKeyRingCollection get() {
+ try {
+ FileTime newLastModifiedTime = Files.getLastModifiedTime(pubring);
+ if (lastModifiedTime == null || lastModifiedTime.compareTo(newLastModifiedTime) < 0) {
+ lastModifiedTime = newLastModifiedTime;
+ keyRingCollection = buildPubring();
+ }
+ } catch (Exception e) {
+ //$FALL-THROUGH$
+ }
+ return keyRingCollection;
+ }
+
+ protected abstract PGPPublicKeyRingCollection buildPubring() throws Exception;
+ }
+
+ private static Supplier<PGPPublicKeyRingCollection> getGPGPubring() {
+ Path gpgDirectory = getGPPDirectory();
+ Path pubringGpg = gpgDirectory.resolve("pubring.gpg"); //$NON-NLS-1$
+ Path pubringKbx = gpgDirectory.resolve("pubring.kbx"); //$NON-NLS-1$
+
+ if (Files.isRegularFile(pubringGpg)) {
+ return new GPGPubringSupplier(pubringGpg) {
+ @Override
+ protected PGPPublicKeyRingCollection buildPubring() throws Exception {
+ try (InputStream input = new BufferedInputStream(Files.newInputStream(pubringGpg))) {
+ PGPPublicKeyRingCollection keyRingCollection = new PGPPublicKeyRingCollection(input,
+ new JcaKeyFingerprintCalculator());
+ return keyRingCollection;
+ }
+ }
+ };
+ } else if (Files.isRegularFile(pubringKbx)) {
+ return new GPGPubringSupplier(pubringKbx) {
+ @Override
+ protected PGPPublicKeyRingCollection buildPubring() throws Exception {
+ try (InputStream input = new BufferedInputStream(Files.newInputStream(pubringKbx))) {
+ KeyBox keyBox = new JcaKeyBoxBuilder().build(input);
+ List<PGPPublicKeyRing> pgpPublicKeyRings = new ArrayList<>();
+ for (KeyBlob keyBlob : keyBox.getKeyBlobs()) {
+ switch (keyBlob.getType()) {
+ case OPEN_PGP_BLOB: {
+ PGPPublicKeyRing pgpPublicKeyRing = ((PublicKeyRingBlob) keyBlob).getPGPPublicKeyRing();
+ pgpPublicKeyRings.add(pgpPublicKeyRing);
+ }
+ default: {
+ //$FALL-THROUGH$
+ }
+ }
+ }
+ PGPPublicKeyRingCollection keyRingCollection = new PGPPublicKeyRingCollection(
+ pgpPublicKeyRings);
+ return keyRingCollection;
+ }
+ }
+ };
+ } else {
+ PGPPublicKeyRingCollection empty;
+ try {
+ empty = new PGPPublicKeyRingCollection(Collections.emptyList());
+ } catch (IOException | PGPException e) {
+ // Cannot happen for an empty collection.
+ throw new RuntimeException(e);
+ }
+ return () -> empty;
+ }
+ }
+
+ @SuppressWarnings("nls")
+ private static Path getGPPDirectory() {
+ // Handle ~ as might be used on macos and linux.
+ Function<String, Path> resolveTilde = s -> {
+ if (s.startsWith("~/") || s.startsWith("~" + File.separatorChar)) {
+ return new File(System.getProperty("user.home"), s.substring(2)).getAbsoluteFile().toPath();
+ }
+ return Paths.get(s);
+ };
+
+ // Allow the user to specify the GPG home used by p2 specifically.
+ Path path = checkDirectory(System.getProperty(GPG_HOME_PROPERTY), resolveTilde);
+ if (path != null) {
+ return path;
+ }
+
+ path = checkDirectory(System.getenv("GNUPGHOME"), resolveTilde);
+ if (path != null) {
+ return path;
+ }
+
+ if ("win32".equals(System.getProperty("osgi.os"))) {
+ // On Windows prefer %APPDATA%\gnupg if it exists, even if Cygwin is used.
+ path = checkDirectory(System.getenv("APPDATA"), //$NON-NLS-1$
+ s -> Paths.get(s).resolve("gnupg")); //$NON-NLS-1$
+ if (path != null) {
+ return path;
+ }
+ }
+ // All systems, including Cygwin and even Windows if %APPDATA%\gnupg doesn't
+ // exist.
+ return resolveTilde.apply("~/.gnupg"); //$NON-NLS-1$
+ }
+
+ private static Path checkDirectory(String dir, Function<String, Path> toPath) {
+ if (dir != null && !dir.isBlank()) {
+ try {
+ Path directory = toPath.apply(dir);
+ if (Files.isDirectory(directory)) {
+ return directory;
+ }
+ } catch (RuntimeException e) {
+ //$FALL-THROUGH$
+ }
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/p2/repository/spi/PGPPublicKeyService.java b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/p2/repository/spi/PGPPublicKeyService.java
new file mode 100644
index 000000000..508ac65fc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.repository/src/org/eclipse/equinox/p2/repository/spi/PGPPublicKeyService.java
@@ -0,0 +1,231 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Eclipse contributors and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.equinox.p2.repository.spi;
+
+import java.util.*;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.util.encoders.Hex;
+import org.eclipse.equinox.p2.core.IProvisioningAgent;
+import org.eclipse.equinox.p2.core.spi.IAgentServiceFactory;
+
+/**
+ * A service for managing and searching {@link PGPPublicKey keys}.
+ * Implementations may make use of a
+ * <a href="https://datatracker.ietf.org/doc/html/draft-shaw-openpgp-hkp-00">key
+ * server</a> to fetch up-to-date information about keys. Implementations should
+ * generally provide support for caching and efficient lookup of keys,
+ * especially lookup based on {@link PGPPublicKey#getKeyID() key ID} because
+ * signatures generally use {@link PGPSignature#getKeyID() key IDs} and this is
+ * the primary use case.
+ *
+ * <p>
+ * Implementors of this service are responsible for registering the
+ * implementation with the {@link IProvisioningAgent provisioning agent} either
+ * {@link IProvisioningAgent#registerService(String, Object) explicitly} or via
+ * an {@link IAgentServiceFactory agent service factory}.
+ * </p>
+ *
+ * @see PGPPublicKey#getKeyID()
+ * @see PGPSignature#getKeyID()
+ * @see IAgentServiceFactory
+ * @see IProvisioningAgent#registerService(String, Object)
+ *
+ * @since 2.6
+ */
+public abstract class PGPPublicKeyService {
+ /**
+ * The name used for obtaining a reference to the key service.
+ *
+ * @see IProvisioningAgent#getService(Class)
+ * @see IProvisioningAgent#getService(String)
+ */
+ public static final String SERVICE_NAME = PGPPublicKeyService.class.getName();
+
+ /**
+ * Returns the key associated with the given
+ * {@link PGPPublicKey#getFingerprint() fingerprint}.
+ *
+ * @param fingerprint the fingerprint for which to search.
+ * @return the key with the matching fingerprint.
+ *
+ * @see PGPPublicKey#getFingerprint()
+ */
+ public PGPPublicKey getKey(byte[] fingerprint) {
+ return getKey(toHex(fingerprint));
+ }
+
+ /**
+ * Returns the key associated with the given
+ * {@link PGPPublicKey#getFingerprint() fingerprint} represented as a
+ * {@link #toHex(byte[]) hexadecimal} string value.
+ *
+ * @param fingerprint the fingerprint for which to search.
+ * @return the key with the matching fingerprint.
+ *
+ * @see PGPPublicKey#getFingerprint()
+ * @see #toHex(byte[])
+ */
+ public abstract PGPPublicKey getKey(String fingerprint);
+
+ /**
+ * Returns the keys associated with the given {@link PGPPublicKey#getKeyID() key
+ * ID}. In general, key ID collisions are possible so implementations must be
+ * tolerant of that.
+ *
+ * @param keyID the key ID for which to search.
+ * @return the keys with the matching key IDs.
+ *
+ * @see PGPPublicKey#getKeyID()
+ * @see PGPSignature#getKeyID()
+ */
+ public abstract Collection<PGPPublicKey> getKeys(long keyID);
+
+ /**
+ * Adds the given key to this key service. An implementations may fetch more
+ * up-to-date information about this key from a key server and may return a
+ * different key than the one passed in here. In general an implementation may
+ * also return an existing key, with the same fingerprint, already known to the
+ * key service.
+ *
+ * @param key the key to add.
+ * @return the normalized key available in this key service.
+ */
+ public abstract PGPPublicKey addKey(PGPPublicKey key);
+
+ /**
+ * Returns the set of keys that have been verified to have signed the given key.
+ * These are the links in the web of trust.
+ *
+ * @param key the key for which to find keys that have signed it.
+ * @return the set of keys that have been verified to have signed the given key.
+ *
+ * @see PGPSignature#verifyCertification(String, PGPPublicKey)
+ * @see PGPSignature#verifyCertification(PGPPublicKey, PGPPublicKey)
+ */
+ public abstract Set<PGPPublicKey> getVerifiedCertifications(PGPPublicKey key);
+
+ /**
+ * If this key has a revocation signature that is verified to have been signed
+ * by the public key of that revocation signature, this returns the
+ * {@link PGPSignature#getCreationTime() creation time} of that signature,
+ * otherwise it returns <code>null</code>.
+ *
+ * @param key the key to test for revocation.
+ * @return when this key was verifiably revoked, or <code>null</code> if it is
+ * not revoked.
+ *
+ * @see PGPSignature#getKeyID()
+ * @see PGPPublicKey#hasRevocation()
+ * @see PGPSignature#getCreationTime()
+ * @see PGPSignature#KEY_REVOCATION
+ * @see PGPSignature#SUBKEY_REVOCATION
+ */
+ public abstract Date getVerifiedRevocationDate(PGPPublicKey key);
+
+ /**
+ * Returns whether the signature's {@link PGPSignature creation time} is before
+ * the key's {@link #getVerifiedRevocationDate(PGPPublicKey) verified revocation
+ * date}, if that key has one.
+ *
+ * @param signature the signature to test.
+ * @param key the corresponding key of this signature against which to
+ * test.
+ * @return <code>true</code> if the signature was created before the key was
+ * revoked or if the key is not revoked, <code>false</code> otherwise.
+ *
+ * @throws IllegalArgumentException if the signature's
+ * {@link PGPSignature#getKeyID() key} is not
+ * the same as the key's
+ * {@link PGPPublicKey#getKeyID() key ID}
+ */
+ public boolean isCreatedBeforeRevocation(PGPSignature signature, PGPPublicKey key) {
+ if (signature.getKeyID() != key.getKeyID()) {
+ throw new IllegalArgumentException("The signature's key ID must be the same as the key's key ID"); //$NON-NLS-1$
+ }
+ Date verifiedRevocationDate = getVerifiedRevocationDate(key);
+ if (verifiedRevocationDate != null) {
+ long signatureCreationTime = signature.getCreationTime().getTime();
+ if (signatureCreationTime >= verifiedRevocationDate.getTime()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the hexadecimal representation of the given bytes.
+ *
+ * @param bytes the bytes to convert to a hexadecimal representation.
+ * @return the hexadecimal representation of the given bytes.
+ */
+ public static String toHex(byte[] bytes) {
+ return Hex.toHexString(bytes);
+ }
+
+ /**
+ * Returns the hexadecimal representation of the given long value, typically a
+ * key ID, padded with leading zeros to a length of 16.
+ *
+ * @param keyID the long value, typically a key ID, to convert to a hexadecimal
+ * representation.
+ * @return the hexadecimal representation of the given long value, padded with
+ * leading zeros.
+ */
+ public static String toHex(long keyID) {
+ return String.format("%1$016X", keyID); //$NON-NLS-1$
+ }
+
+ /**
+ * If the signature's {@link PGPSignature#getCreationTime() creation time} is
+ * before the key's {@link PGPPublicKey#getCreationTime() creation time}, this
+ * returns a negative value equal to the number of milliseconds that the
+ * signature was created before the key was created. If the key has a
+ * {@link PGPPublicKey#getValidSeconds() validity period}, i.e., if the key
+ * expires, and the signature's creation time is after the key's expiration,
+ * returns a positive value equal to the number of milliseconds that the
+ * signature was created after the key's expiration. Otherwise, the signature
+ * was created during the period of time that the key was valid and this returns
+ * <code>0</code>.
+ *
+ * @param signature the signature to test.
+ * @param key the corresponding key of this signature against which to
+ * test.
+ * @return a negative value representing the number of milliseconds the
+ * signature was created before the key was created, a positive value
+ * representing the number of milliseconds the signature as created
+ * after the key expired, or <code>0</code> if the signature was created
+ * during the valid period of the key.
+ * @throws IllegalArgumentException if the signature's
+ * {@link PGPSignature#getKeyID() key} is not
+ * the same as the key's
+ * {@link PGPPublicKey#getKeyID() key ID}
+ */
+ public static long compareSignatureTimeToKeyValidityTime(PGPSignature signature, PGPPublicKey key) {
+ if (signature.getKeyID() != key.getKeyID()) {
+ throw new IllegalArgumentException("The signature's key ID must be the same as the key's key ID"); //$NON-NLS-1$
+ }
+ long keyCreationTime = key.getCreationTime().getTime();
+ long signatureCreationTime = signature.getCreationTime().getTime();
+ long delta = signatureCreationTime - keyCreationTime;
+ if (delta < 0) {
+ return delta;
+ }
+ long validSeconds = key.getValidSeconds();
+ if (validSeconds != 0) {
+ delta = signatureCreationTime - (keyCreationTime + validSeconds * 1000);
+ if (delta > 0) {
+ return delta;
+ }
+ }
+ return 0;
+ }
+}
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/processors/PGPSignatureVerifierTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/processors/PGPSignatureVerifierTest.java
index 36b50a545..2dd294f99 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/processors/PGPSignatureVerifierTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/processors/PGPSignatureVerifierTest.java
@@ -18,26 +18,49 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Files;
+import java.util.Set;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IStatus;
-import org.eclipse.equinox.internal.p2.artifact.processors.pgp.Messages;
import org.eclipse.equinox.internal.p2.artifact.processors.pgp.PGPSignatureVerifier;
import org.eclipse.equinox.internal.p2.metadata.ArtifactKey;
+import org.eclipse.equinox.internal.provisional.p2.repository.DefaultPGPPublicKeyService;
+import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
import org.eclipse.equinox.p2.repository.artifact.IProcessingStepDescriptor;
import org.eclipse.equinox.p2.repository.artifact.spi.ArtifactDescriptor;
import org.eclipse.equinox.p2.repository.artifact.spi.ProcessingStepDescriptor;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
+import org.eclipse.equinox.p2.tests.TestAgentProvider;
import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
public class PGPSignatureVerifierTest {
+ @Rule
+ public TestAgentProvider agentProvider = new TestAgentProvider();
+
+ @Before
+ public void initialize() {
+ try {
+ PGPPublicKeyService keyService = agentProvider.getService(PGPPublicKeyService.class);
+ if (keyService instanceof DefaultPGPPublicKeyService) {
+ DefaultPGPPublicKeyService defaultPGPPublicKeyService = (DefaultPGPPublicKeyService) keyService;
+ defaultPGPPublicKeyService.setKeyServers(Set.of());
+ defaultPGPPublicKeyService.setGPG(false);
+ }
+ } catch (ProvisionException e) {
+ //$FALL-THROUGH$
+ }
+ }
+
// @formatter:off
/*
- * About test keys:
- * * Install the public&private keys locally
- * * then generate signatures with eg `gpg -u signer2@fakeuser.eclipse.org -a --output signed_by_signer_2 --detach-sig testArtifact`
+ * About test keys: * Install the public&private keys locally * then generate
+ * signatures with eg `gpg -u signer2@fakeuser.eclipse.org -a --output
+ * signed_by_signer_2 --detach-sig testArtifact`
*/
// @formatter:on
@@ -58,8 +81,9 @@ public class PGPSignatureVerifierTest {
public void testOK() throws Exception {
IProcessingStepDescriptor processingStepDescriptor = new ProcessingStepDescriptor(null, null, false);
IArtifactDescriptor artifact = createArtifact("signed_by_signer_1", "public_signer1.pgp");
+ @SuppressWarnings("resource")
PGPSignatureVerifier verifier = new PGPSignatureVerifier();
- verifier.initialize(null, processingStepDescriptor, artifact);
+ verifier.initialize(agentProvider.getAgent(), processingStepDescriptor, artifact);
Assert.assertTrue(verifier.getStatus().toString(), verifier.getStatus().isOK());
try (InputStream bytes = getClass().getResourceAsStream("testArtifact")) {
bytes.transferTo(verifier);
@@ -74,10 +98,10 @@ public class PGPSignatureVerifierTest {
IProcessingStepDescriptor processingStepDescriptor = new ProcessingStepDescriptor(null, null, false);
IArtifactDescriptor artifact = createArtifact("signed_by_signer_1", "public_signer2.pgp");
try (PGPSignatureVerifier verifier = new PGPSignatureVerifier()) {
- verifier.initialize(null, processingStepDescriptor, artifact);
+ verifier.initialize(agentProvider.getAgent(), processingStepDescriptor, artifact);
IStatus status = verifier.getStatus();
assertEquals(IStatus.ERROR, status.getSeverity());
- assertTrue(status.getMessage().contains("Public key not found for"));
+ assertTrue(status.getMessage().matches("A public key.*could not be found.*"));
}
}
@@ -86,7 +110,7 @@ public class PGPSignatureVerifierTest {
IProcessingStepDescriptor processingStepDescriptor = new ProcessingStepDescriptor(null, null, false);
IArtifactDescriptor artifact = createArtifact("signed_by_signer_1_tampered", "public_signer1.pgp");
try (PGPSignatureVerifier verifier = new PGPSignatureVerifier()) {
- verifier.initialize(null, processingStepDescriptor, artifact);
+ verifier.initialize(agentProvider.getAgent(), processingStepDescriptor, artifact);
// signature has random modification, making it invalid by itself
Assert.assertFalse(verifier.getStatus().isOK());
}
@@ -96,8 +120,9 @@ public class PGPSignatureVerifierTest {
public void testSignatureForAnotherArtifact() throws Exception {
IProcessingStepDescriptor processingStepDescriptor = new ProcessingStepDescriptor(null, null, false);
IArtifactDescriptor artifact = createArtifact("signed_by_signer_1_otherArtifact", "public_signer1.pgp");
+ @SuppressWarnings("resource")
PGPSignatureVerifier verifier = new PGPSignatureVerifier();
- verifier.initialize(null, processingStepDescriptor, artifact);
+ verifier.initialize(agentProvider.getAgent(), processingStepDescriptor, artifact);
Assert.assertTrue(verifier.getStatus().isOK());
try (InputStream bytes = getClass().getResourceAsStream("testArtifact")) {
bytes.transferTo(verifier);
@@ -106,6 +131,6 @@ public class PGPSignatureVerifierTest {
verifier.close();
IStatus status = verifier.getStatus();
assertEquals(IStatus.ERROR, status.getSeverity());
- assertTrue(status.getMessage().contains(Messages.Error_SignatureAndFileDontMatch));
+ assertTrue(status.getMessage().matches(".*signature.*invalid.*"));
}
}
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPVerifierTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPVerifierTest.java
index c010af306..fe9d5fc7a 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPVerifierTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPVerifierTest.java
@@ -10,14 +10,22 @@
*******************************************************************************/
package org.eclipse.equinox.p2.tests.artifact.repository;
+import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.stream.Stream;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.equinox.internal.p2.artifact.processors.pgp.PGPSignatureVerifier;
import org.eclipse.equinox.internal.p2.artifact.repository.MirrorRequest;
import org.eclipse.equinox.internal.p2.metadata.ArtifactKey;
+import org.eclipse.equinox.internal.provisional.p2.repository.DefaultPGPPublicKeyService;
+import org.eclipse.equinox.p2.core.IAgentLocation;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
import org.junit.Before;
import org.junit.Test;
@@ -30,7 +38,6 @@ public class PGPVerifierTest extends AbstractProvisioningTest {
@Before
public void setUp() throws Exception {
super.setUp();
- PGPSignatureVerifier.KNOWN_KEYS.clear();
}
private void loadPGPTestRepo(String repoName) throws Exception {
@@ -53,6 +60,26 @@ public class PGPVerifierTest extends AbstractProvisioningTest {
}
private IStatus performMirrorFrom(String repoName) throws Exception {
+ // Clear the remembered keys/cache of the agent.
+ IAgentLocation agentLocation = getAgent().getService(IAgentLocation.class);
+ Path repositoryCache = Paths
+ .get(agentLocation.getDataArea(org.eclipse.equinox.internal.p2.repository.Activator.ID));
+ if (Files.isDirectory(repositoryCache)) {
+ try (Stream<Path> walk = Files.walk(repositoryCache)) {
+ walk.sorted(Comparator.reverseOrder()).forEach(path -> {
+ try {
+ Files.delete(path);
+ } catch (IOException e) {
+ // Ignore
+ }
+ });
+ }
+ }
+ DefaultPGPPublicKeyService keyService = new DefaultPGPPublicKeyService(getAgent());
+ keyService.setGPG(false);
+ keyService.setKeyServers(Set.of());
+
+ getAgent().registerService(PGPPublicKeyService.SERVICE_NAME, keyService);
loadPGPTestRepo(repoName);
ArtifactKey key = new ArtifactKey("osgi.bundle", "blah", Version.create("1.0.0.123456"));
MirrorRequest mirrorRequest = new MirrorRequest(key, targetRepo, NO_PROPERTIES, NO_PROPERTIES, getTransport());
@@ -64,7 +91,7 @@ public class PGPVerifierTest extends AbstractProvisioningTest {
public void testMissingPublicKey() throws Exception {
IStatus mirrorStatus = performMirrorFrom("repoMissingPublicKey");
assertNotOK(mirrorStatus);
- assertTrue(mirrorStatus.toString().contains("Public key not found"));
+ assertTrue(mirrorStatus.toString().matches(".*public key.*not be found.*"));
}
@Override
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/CertificateCheckerTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/CertificateCheckerTest.java
index 346d6eea2..ba56cc6ea 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/CertificateCheckerTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/CertificateCheckerTest.java
@@ -16,8 +16,12 @@ package org.eclipse.equinox.p2.tests.engine;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.security.cert.Certificate;
+import java.util.Comparator;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.equinox.internal.p2.artifact.processors.pgp.PGPSignatureVerifier;
import org.eclipse.equinox.internal.p2.core.AgentLocation;
@@ -25,13 +29,14 @@ import org.eclipse.equinox.internal.p2.core.ProvisioningAgent;
import org.eclipse.equinox.internal.p2.engine.EngineActivator;
import org.eclipse.equinox.internal.p2.engine.phases.CertificateChecker;
import org.eclipse.equinox.internal.p2.metadata.ArtifactKey;
+import org.eclipse.equinox.internal.provisional.p2.repository.DefaultPGPPublicKeyService;
import org.eclipse.equinox.p2.core.IAgentLocation;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.core.UIServices;
-import org.eclipse.equinox.p2.engine.IProfile;
import org.eclipse.equinox.p2.engine.IProfileRegistry;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.equinox.p2.repository.artifact.spi.ArtifactDescriptor;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
import org.eclipse.equinox.p2.tests.TestActivator;
import org.eclipse.equinox.p2.tests.TestData;
@@ -89,8 +94,35 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
+ "2XpuKxPXGavKfpSvssVQIZ6aWi5W6wp5lZAQQddZvYAv3Gi5CZZcUT7ayFJYdD23\n"
+ "p9jz76G7MXm0f0uNT9B57T72QryokUIEIJYsCb6lNjWUQB4cd0+JesM7sHwweOQ3\n"
+ "7iTFc+WgVJkP0e695mm1tcvtQHUPbIItYJUsndyLgGInzglxN8+F4U4k8uapydI9\n"
- + "RmV2NVAifYp8z95Am5AnlG8lqjwrWk5bMbJH82QsQESrNT/h\n" + "=8Vrn\n"
- + "-----END PGP SIGNATURE-----\n";
+ + "RmV2NVAifYp8z95Am5AnlG8lqjwrWk5bMbJH82QsQESrNT/h\n" + "=8Vrn\n" + "-----END PGP SIGNATURE-----\n";
+
+ private static final String PGP_SIGNER2_PUBLIC_KEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n" + "\r\n"
+ + "mQINBGHUyxYBEADATeNx4XA4H2fP9mD5xwlIyh7qvHLezgXpqCwNS9ATqBwnfrCV\r\n"
+ + "06a+pfSLsLoXrP/sdaB5WhijfuxTis18RMfoDVwGMRqyD2GiBCl2vwJDg3BUwHnc\r\n"
+ + "H7W6XkWKO7dkPmF+TUbD3cTWZ7cvrPmMjinmXaq8htuktGuE2VEGZRnuG1m+ChDM\r\n"
+ + "PnSb1ioFS2+MJv13P2fagVk2qC95DkPJGpMk3CY3ghLDEaY/KaJl+8axAlUUUk9N\r\n"
+ + "d3k/KVxxf5k3g26EVQkWWgH2mmolptGO101xW64iked97Cy4NK2yafOF/wmpsavx\r\n"
+ + "PGpOewnDgAJBBPkum6mPH0vcOZgxmLyh4uqfPfr3IaBQlbJLN2dXaDsV83j5t1wZ\r\n"
+ + "2qxOPcWBfORm6W7dC0TQgCXbEG0geMBpJtvnMX83Q2ORqDpjbHRJsV2k+8NxaXON\r\n"
+ + "pYXGr+Lj/9n0xfNEDXhCdGab0XP2tVZ5jfr2OQ5dAomEaPqK5Kq7akoWMddpDLNC\r\n"
+ + "G4IvH8G0cxwruJk00uwd6Nd2NVqVMRYCsBbA89VanUnutLUIpVnnOAetlX9blXHO\r\n"
+ + "JtmiCPGgHyp+iYGhKYVzfuZQyFhonbi0AhidJDvbHsoLT3p4Mcog1B9y6MODBE7R\r\n"
+ + "jwrU+qMqYsYeFhGYKbYyXv9TfEyUvtCQ/GnTtRJAQyicFdOdbK37WecS6QARAQAB\r\n"
+ + "tDdFY2xpcHNlIHAyIHRlc3QgU2lnbmVyIDIgPHNpZ25lcjJAZmFrZXVzZXIuZWNs\r\n"
+ + "aXBzZS5vcmc+iQJYBBMBCABCFiEEzZ1aK4a8T+GDlFHh4vaU9BsKs3AFAmHUyxYC\r\n"
+ + "GwMFCQPCZwAFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4HAheAAAoJEOL2lPQbCrNw\r\n"
+ + "wH4QAIiCaw1mREgt4ldz7hQvmGxdMuQwVKZPzbOIAlYbZBo0q9SmeMf/CBCO90hg\r\n"
+ + "LFmJmsZV4KUU5NKI7UwkDVrpUCl00Ok6+gtiUTId2tRcwXI+25I478VX27j6OkQj\r\n"
+ + "7Xr6giv8cn8nstt5CF6xxeqrxvpmnZC0u30jE6CL6SdXSd0vViFDPQj3KgGJCRc9\r\n"
+ + "St+LHB3XJTsadihzQnscqI4E2i5Z3Uj1GogqxtR59M1NAXubl5dySM0qHhwn8O+6\r\n"
+ + "lcgCCeuyMLLde1M1v8w07jdRUM+IFqHrRnE89EPH3MQeZbQ3UfFXK2r7wx2BJCqL\r\n"
+ + "Irtn68kz834ByKchGR6DqaAw0q+iF2QkgzYxpwai41BgmtUCYnj+HxQNIF4KTzDe\r\n"
+ + "nd8mDAPWttGCoVuV2Tyu9peYOaqyAZ2PZwUEH5MqihPCbenU17RLXzRu/IT/SH47\r\n"
+ + "NGrN3yKGgLZr2EVWPWFibpoxP4G4NUCHsY75uiL2EWPVSjK/+OOeHIE5k3U3lYwB\r\n"
+ + "7clhBwMkIhQHJ+a0SHRkKixkwrQDw4veKY4LaD0NCBLHFoV5L9orH1ToGM729kr/\r\n"
+ + "+4I1VQFkL3KvfLjmRbTUgwHeqEquQ96JtqowbNwlpujfHXQKDNsuiKGP5OazXll5\r\n"
+ + "sH2CR7e4ePqhhzxjLvi9e/79Khq+08eqllS3rs06EXEAJYTo\r\n" + "=807V\r\n"
+ + "-----END PGP PUBLIC KEY BLOCK-----\r\n" + "";
private static final String PGP_SIGNER2_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" + "\n"
+ "iQIzBAABCAAdFiEEzZ1aK4a8T+GDlFHh4vaU9BsKs3AFAmHUy4UACgkQ4vaU9BsK\n"
+ "s3DjuhAAvlCtqhK/7/aAG0/cXtlpu0fPC176OmEmBGTjCsrGdWwuRHsqXbLnMBVZ\n"
@@ -131,14 +163,22 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
CertificateTestService serviceUI;
File unsigned;
private ProvisioningAgent testAgent;
+ private Path agentLocation;
@Override
protected void setUp() throws Exception {
serviceUI = new CertificateTestService();
testAgent = new ProvisioningAgent();
testAgent.registerService(UIServices.SERVICE_NAME, serviceUI);
+ agentLocation = Files.createTempDirectory("certificateTest");
+ testAgent.setLocation(agentLocation.toUri());
testAgent.setBundleContext(TestActivator.getContext());
checker = new CertificateChecker(testAgent);
+ PGPPublicKeyService keyService = testAgent.getService(PGPPublicKeyService.class);
+ if (keyService instanceof DefaultPGPPublicKeyService) {
+ ((DefaultPGPPublicKeyService) keyService).setKeyServers(Set.of());
+ }
+
try {
unsigned = TestData.getFile("CertificateChecker", "unsigned.jar");
} catch (IOException e) {
@@ -148,12 +188,26 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
assertTrue("1.0", unsigned.exists());
}
+ @Override
+ protected void tearDown() throws Exception {
+ try (Stream<Path> walk = Files.walk(agentLocation)) {
+ walk.sorted(Comparator.reverseOrder()).forEach(path -> {
+ try {
+ Files.delete(path);
+ } catch (IOException e) {
+ // Ignore
+ }
+ });
+ }
+ }
+
/**
- * Tests that installing unsigned content is not allowed when the policy says it must fail.
+ * Tests that installing unsigned content is not allowed when the policy says it
+ * must fail.
*/
public void testPolicyAllow() {
try {
- //if the service is consulted it will say no
+ // if the service is consulted it will say no
serviceUI.unsignedReturnValue = false;
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_ALLOW);
checker.add(Map.of(new ArtifactDescriptor(new ArtifactKey("what", "ever", Version.create("1"))), unsigned));
@@ -165,7 +219,8 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
}
/**
- * Tests that installing unsigned content is not allowed when the policy says it must fail.
+ * Tests that installing unsigned content is not allowed when the policy says it
+ * must fail.
*/
public void testPolicyFail() {
try {
@@ -180,7 +235,8 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
}
/**
- * Tests that installing unsigned content with the "prompt" policy and the prompt succeeds.
+ * Tests that installing unsigned content with the "prompt" policy and the
+ * prompt succeeds.
*/
public void testPolicyPromptSuccess() {
try {
@@ -208,7 +264,8 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
}
/**
- * Tests that installing unsigned content with the "prompt" policy and the prompt says no.
+ * Tests that installing unsigned content with the "prompt" policy and the
+ * prompt says no.
*/
public void testPolicyPromptCancel() {
try {
@@ -224,16 +281,17 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
}
/**
- * Tests that trust checks that occur in a headless environment are properly treated
- * as permissive, but not persistent, the same way as it would be if the service registration
- * were not there.
+ * Tests that trust checks that occur in a headless environment are properly
+ * treated as permissive, but not persistent, the same way as it would be if the
+ * service registration were not there.
*/
public void testBug291049() {
try {
// Intentionally replace our service with a null service
testAgent.registerService(UIServices.SERVICE_NAME, null);
checker.add(Map.of(new ArtifactDescriptor(new ArtifactKey("what", "ever", Version.create("1"))), unsigned));
- // TODO need to add some untrusted files here, too. To prove that we treated them as trusted temporarily
+ // TODO need to add some untrusted files here, too. To prove that we treated
+ // them as trusted temporarily
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_PROMPT);
IStatus result = checker.start();
assertTrue("1.0", result.isOK());
@@ -255,10 +313,9 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
unsigned = TestData.getFile("pgp/repoPGPOK/plugins", "blah_1.0.0.123456.jar");
ArtifactDescriptor artifactDescriptor = new ArtifactDescriptor(
new ArtifactKey("what", "ever", Version.create("1")));
- artifactDescriptor.addProperties(
- Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME, PGP_SIGNER1_SIGNATURE, //
- PGPSignatureVerifier.PGP_SIGNER_KEYS_PROPERTY_NAME,
- PGP_SIGNER1_PUBLIC_KEY));
+ artifactDescriptor
+ .addProperties(Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME, PGP_SIGNER1_SIGNATURE, //
+ PGPSignatureVerifier.PGP_SIGNER_KEYS_PROPERTY_NAME, PGP_SIGNER1_PUBLIC_KEY));
checker.add(Map.of(artifactDescriptor, unsigned));
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_PROMPT);
IStatus result = checker.start();
@@ -269,40 +326,14 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
}
}
- public void testPGPSignedArtifactTrustedKeyInProfile() throws ProvisionException, IOException {
- try {
- // create a test profile
- testAgent.registerService("FORCED_SELF", IProfileRegistry.SELF);
- testAgent.registerService(IAgentLocation.SERVICE_NAME, new AgentLocation(
- Files.createTempDirectory(
- CertificateCheckerTest.class.getName() + "testPGPSignedArtifactTrustedKey-profile")
- .toUri()));
- IProfile profile = testAgent.getService(IProfileRegistry.class).addProfile(IProfileRegistry.SELF,
- Map.of(CertificateChecker.TRUSTED_KEY_STORE_PROPERTY, PGP_SIGNER1_PUBLIC_KEY));
- unsigned = TestData.getFile("pgp/repoPGPOK/plugins", "blah_1.0.0.123456.jar");
- ArtifactDescriptor artifactDescriptor = new ArtifactDescriptor(
- new ArtifactKey("what", "ever", Version.create("1")));
- artifactDescriptor.addProperties(
- Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME, PGP_SIGNER1_SIGNATURE));
- checker.add(Map.of(artifactDescriptor, unsigned));
- checker.setProfile(profile);
-
- System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_PROMPT);
- IStatus result = checker.start();
- assertTrue(result.isOK());
- assertFalse(serviceUI.wasPrompted);
- } finally {
- System.getProperties().remove(EngineActivator.PROP_UNSIGNED_POLICY);
- }
- }
-
public void testPGPSignedArtifactTrustedKeyInProvideCapability() throws IOException {
try {
unsigned = TestData.getFile("pgp/repoPGPOK/plugins", "blah_1.0.0.123456.jar");
ArtifactDescriptor artifactDescriptor = new ArtifactDescriptor(
new ArtifactKey("what", "ever", Version.create("1")));
artifactDescriptor
- .addProperties(Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME, PGP_SIGNER2_SIGNATURE));
+ .addProperties(Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME, PGP_SIGNER2_SIGNATURE,
+ PGPSignatureVerifier.PGP_SIGNER_KEYS_PROPERTY_NAME, PGP_SIGNER2_PUBLIC_KEY));
checker.add(Map.of(artifactDescriptor, unsigned));
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_PROMPT);
IStatus result = checker.start();
diff --git a/bundles/org.eclipse.equinox.p2.ui.sdk/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.ui.sdk/META-INF/MANIFEST.MF
index 35df9354d..4293409db 100644
--- a/bundles/org.eclipse.equinox.p2.ui.sdk/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.ui.sdk/META-INF/MANIFEST.MF
@@ -16,15 +16,16 @@ Import-Package: javax.xml.parsers,
org.bouncycastle.openpgp;version="1.69.0",
org.eclipse.compare;resolution:=optional,
org.eclipse.compare.structuremergeviewer;resolution:=optional,
+ org.eclipse.equinox.internal.p2.artifact.processors.pgp,
org.eclipse.equinox.internal.p2.engine.phases,
org.eclipse.equinox.p2.core;version="[2.0.0,3.0.0)",
- org.eclipse.equinox.internal.p2.artifact.processors.pgp,
org.eclipse.equinox.p2.engine;version="[2.0.0,3.0.0)",
org.eclipse.equinox.p2.engine.query;version="[2.0.0,3.0.0)",
org.eclipse.equinox.p2.metadata;version="[2.0.0,3.0.0)",
org.eclipse.equinox.p2.operations;version="[2.0.0,3.0.0)",
org.eclipse.equinox.p2.planner;version="2.0.0",
org.eclipse.equinox.p2.query;version="[2.0.0,3.0.0)",
+ org.eclipse.equinox.p2.repository.spi;version="2.0.0",
org.eclipse.osgi.util;version="1.1.0",
org.osgi.framework;version="1.6.0",
org.w3c.dom,
diff --git a/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/ProvSDKMessages.java b/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/ProvSDKMessages.java
index bf0a1956c..6e6c6c2e5 100644
--- a/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/ProvSDKMessages.java
+++ b/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/ProvSDKMessages.java
@@ -52,7 +52,7 @@ public class ProvSDKMessages extends NLS {
public static String RemediationOperation_ResolveJobTask;
public static String TrustPreferencePage_title;
public static String TrustPreferencePage_export;
- public static String TrustPreferencePage_idColumn;
+ public static String TrustPreferencePage_fingerprintColumn;
public static String TrustPreferencePage_userColumn;
public static String TrustPreferencePage_fileExportTitle;
public static String TrustPreferencePage_pgpIntro;
diff --git a/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/TrustPreferencePage.java b/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/TrustPreferencePage.java
index f875fd6dc..9812d1cfb 100644
--- a/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/TrustPreferencePage.java
+++ b/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/TrustPreferencePage.java
@@ -13,6 +13,7 @@ package org.eclipse.equinox.internal.p2.ui.sdk;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.eclipse.core.runtime.IStatus;
@@ -22,6 +23,7 @@ import org.eclipse.equinox.internal.p2.engine.phases.CertificateChecker;
import org.eclipse.equinox.internal.p2.ui.ProvUIActivator;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.engine.IProfileRegistry;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.viewers.*;
@@ -66,11 +68,10 @@ public class TrustPreferencePage extends PreferencePage implements IWorkbenchPre
idColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
- return Long.toHexString(((PGPPublicKey) element).getKeyID()).toUpperCase();
+ return PGPPublicKeyService.toHex(((PGPPublicKey) element).getFingerprint()).toUpperCase(Locale.ROOT);
}
});
- idColumn.getColumn().setWidth(16 * 10); // number of chars in a key Id * some heuristic of width
- idColumn.getColumn().setText(ProvSDKMessages.TrustPreferencePage_idColumn);
+ idColumn.getColumn().setText(ProvSDKMessages.TrustPreferencePage_fingerprintColumn);
TableViewerColumn userColumn = new TableViewerColumn(viewer, SWT.NONE);
userColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
@@ -80,7 +81,6 @@ public class TrustPreferencePage extends PreferencePage implements IWorkbenchPre
return String.join(",", userIds); //$NON-NLS-1$
}
});
- userColumn.getColumn().setWidth(400);
userColumn.getColumn().setText(ProvSDKMessages.TrustPreferencePage_userColumn);
viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
IProvisioningAgent provisioningAgent = ProvSDKUIActivator.getDefault().getProvisioningAgent();
@@ -89,6 +89,13 @@ public class TrustPreferencePage extends PreferencePage implements IWorkbenchPre
.setProfile(provisioningAgent.getService(IProfileRegistry.class).getProfile(IProfileRegistry.SELF));
trustedKeys = certificateChecker.buildPGPTrustore();
viewer.setInput(trustedKeys.all());
+
+ idColumn.getColumn().pack();
+ userColumn.getColumn().pack();
+ if (userColumn.getColumn().getWidth() < idColumn.getColumn().getWidth()) {
+ userColumn.getColumn().setWidth(idColumn.getColumn().getWidth());
+ }
+
Composite buttonComposite = createVerticalButtonBar(res);
buttonComposite.setLayoutData(new GridData(SWT.DEFAULT, SWT.BEGINNING, false, false));
Button exportButton = new Button(buttonComposite, SWT.PUSH);
@@ -102,7 +109,7 @@ public class TrustPreferencePage extends PreferencePage implements IWorkbenchPre
FileDialog dialog = new FileDialog(getShell(), SWT.SAVE);
dialog.setText(ProvSDKMessages.TrustPreferencePage_fileExportTitle);
dialog.setFilterExtensions(new String[] { "*.asc" }); //$NON-NLS-1$
- dialog.setFileName(Long.toHexString(key.getKeyID()).toUpperCase() + ".asc"); //$NON-NLS-1$
+ dialog.setFileName(PGPPublicKeyService.toHex(key.getFingerprint()).toUpperCase(Locale.ROOT) + ".asc"); //$NON-NLS-1$
String path = dialog.open();
if (path == null) {
return;
diff --git a/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/messages.properties b/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/messages.properties
index 0e344af3d..196da4f9c 100644
--- a/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/messages.properties
+++ b/bundles/org.eclipse.equinox.p2.ui.sdk/src/org/eclipse/equinox/internal/p2/ui/sdk/messages.properties
@@ -37,7 +37,7 @@ RemediationOperation_ResolveJobName=Searching alternate solutions...
RemediationOperation_ResolveJobTask=Some items cannot be at the highest version. Searching for the highest common denominator ...
TrustPreferencePage_title=Trust
TrustPreferencePage_export=\uD83D\uDCE5 E&xport...
-TrustPreferencePage_idColumn=Id
+TrustPreferencePage_fingerprintColumn=Fingerprint
TrustPreferencePage_userColumn=User
TrustPreferencePage_fileExportTitle=Export PGP public key
TrustPreferencePage_fileImportTitle=Import PGP public key to trust
@@ -45,4 +45,4 @@ TrustPreferencePage_addPGPKeyButtonLabel=\u2795 &Add...
TrustPreferencePage_removePGPKeyButtonLabel=\uD83D\uDDD1\uFE0F &Remove
TrustPreferencePage_pgpIntro=The following PGP public keys are considered as trusted.\n\
-Artifacts that are signed and verified by one of those keys will be trusted and installed without further trust confirmation request. \ No newline at end of file
+Artifacts that are signed and verified by one of these keys will be trusted and installed without further trust confirmation. \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.ui/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.ui/META-INF/MANIFEST.MF
index 9a353459c..bbf2e9b30 100644
--- a/bundles/org.eclipse.equinox.p2.ui/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.ui/META-INF/MANIFEST.MF
@@ -62,6 +62,7 @@ Import-Package: javax.xml.parsers,
org.eclipse.equinox.p2.repository;version="[2.0.0,3.0.0)",
org.eclipse.equinox.p2.repository.artifact;version="[2.0.0,3.0.0)",
org.eclipse.equinox.p2.repository.metadata;version="[2.0.0,3.0.0)",
+ org.eclipse.equinox.p2.repository.spi;version="2.0.0",
org.osgi.framework;version="1.6.0",
org.osgi.service.packageadmin;version="1.2.0",
org.w3c.dom,
diff --git a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ServiceUIComponent.java b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ServiceUIComponent.java
index fcea5da5a..cad8f35b9 100644
--- a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ServiceUIComponent.java
+++ b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ServiceUIComponent.java
@@ -13,9 +13,8 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.ui;
-import org.eclipse.equinox.p2.core.UIServices;
-
import org.eclipse.equinox.p2.core.IProvisioningAgent;
+import org.eclipse.equinox.p2.core.UIServices;
import org.eclipse.equinox.p2.core.spi.IAgentServiceFactory;
/**
@@ -26,7 +25,7 @@ public class ServiceUIComponent implements IAgentServiceFactory {
@Override
public Object createService(IProvisioningAgent agent) {
- return new ValidationDialogServiceUI();
+ return new ValidationDialogServiceUI(agent);
}
}
diff --git a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ValidationDialogServiceUI.java b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ValidationDialogServiceUI.java
index e0dd88651..877f1550d 100644
--- a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ValidationDialogServiceUI.java
+++ b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ValidationDialogServiceUI.java
@@ -21,10 +21,11 @@ import java.util.List;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.equinox.internal.p2.artifact.processors.pgp.PGPSignatureVerifier;
import org.eclipse.equinox.internal.p2.ui.dialogs.TrustCertificateDialog;
import org.eclipse.equinox.internal.p2.ui.dialogs.UserValidationDialog;
+import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.core.UIServices;
+import org.eclipse.equinox.p2.repository.spi.PGPPublicKeyService;
import org.eclipse.equinox.p2.ui.LoadMetadataRepositoryJob;
import org.eclipse.jface.dialogs.*;
import org.eclipse.jface.viewers.TreeNode;
@@ -43,6 +44,16 @@ import org.eclipse.ui.PlatformUI;
*/
public class ValidationDialogServiceUI extends UIServices {
+ private final IProvisioningAgent agent;
+
+ public ValidationDialogServiceUI() {
+ this(null);
+ }
+
+ public ValidationDialogServiceUI(IProvisioningAgent agent) {
+ this.agent = agent;
+ }
+
static final class MessageDialogWithLink extends MessageDialog {
private final String linkText;
@@ -211,37 +222,42 @@ public class ValidationDialogServiceUI extends UIServices {
}
if (!untrustedPublicKeys.isEmpty()) {
- Map<PGPPublicKey, Set<PGPPublicKey>> verifiedKnownKeyCertifications = PGPSignatureVerifier
- .getVerifiedKnownKeyCertifications();
+ PGPPublicKeyService keyService = agent == null ? null : agent.getService(PGPPublicKeyService.class);
for (PGPPublicKey key : untrustedPublicKeys) {
- children[i] = createTreeNode(key, verifiedKnownKeyCertifications, new HashSet<>());
+ TreeNode treeNode = new TreeNode(key);
+ children[i] = treeNode;
+ expandChildren(treeNode, key, keyService, new HashSet<>(), Integer.getInteger("p2.pgp.trust.depth", 3)); //$NON-NLS-1$
i++;
}
}
return children;
}
- private TreeNode createTreeNode(PGPPublicKey key,
- Map<PGPPublicKey, Set<PGPPublicKey>> verifiedKnownKeyCertification, Set<PGPPublicKey> visited) {
- if (visited.add(key)) {
- TreeNode result = new TreeNode(key);
- List<TreeNode> children = new ArrayList<>();
- Set<PGPPublicKey> visitedChildren = new LinkedHashSet<>();
- Set<PGPPublicKey> certifications = verifiedKnownKeyCertification.get(key);
- if (certifications != null) {
+ private void expandChildren(TreeNode result, PGPPublicKey key, PGPPublicKeyService keyService,
+ Set<PGPPublicKey> visited, int remainingDepth) {
+ if (keyService != null && remainingDepth > 0 && visited.add(key)) {
+ Set<PGPPublicKey> certifications = keyService.getVerifiedCertifications(key);
+ if (certifications != null && !certifications.isEmpty()) {
+ List<TreeNode> children = new ArrayList<>();
for (PGPPublicKey certifyingKey : certifications) {
- if (!visited.contains(certifyingKey) && visitedChildren.add(certifyingKey)) {
- children.add(createTreeNode(certifyingKey, verifiedKnownKeyCertification, visited));
+ if (visited.add(certifyingKey)) {
+ TreeNode treeNode = new TreeNode(certifyingKey);
+ children.add(treeNode);
}
}
+
+ if (!children.isEmpty()) {
+ result.setChildren(children.toArray(TreeNode[]::new));
+ children.forEach(child -> {
+ child.setParent(result);
+ PGPPublicKey certifyingKey = (PGPPublicKey) child.getValue();
+ visited.remove(certifyingKey);
+ expandChildren(child, certifyingKey, keyService, visited, remainingDepth - 1);
+ visited.add(certifyingKey);
+ });
+ }
}
- if (!children.isEmpty()) {
- result.setChildren(children.toArray(TreeNode[]::new));
- children.forEach(child -> child.setParent(result));
- }
- return result;
}
- return null;
}
@Override
diff --git a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java
index fa4395143..6ef4bec66 100644
--- a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java
+++ b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java
@@ -289,17 +289,16 @@ public class TrustCertificateDialog extends SelectionDialog {
listViewer.setContentProvider(contentProvider);
TableViewerColumn typeColumn = new TableViewerColumn(listViewer, SWT.NONE);
- typeColumn.getColumn().setWidth(80);
typeColumn.getColumn().setText(ProvUIMessages.TrustCertificateDialog_ObjectType);
typeColumn.setLabelProvider(new PGPOrX509ColumnLabelProvider(key -> "PGP", cert -> "x509")); //$NON-NLS-1$ //$NON-NLS-2$
+
TableViewerColumn idColumn = new TableViewerColumn(listViewer, SWT.NONE);
- idColumn.getColumn().setWidth(200);
idColumn.getColumn().setText(ProvUIMessages.TrustCertificateDialog_Id);
idColumn.setLabelProvider(new PGPOrX509ColumnLabelProvider(TrustCertificateDialog::userFriendlyFingerPrint,
cert -> cert.getSerialNumber().toString()));
+
TableViewerColumn signerColumn = new TableViewerColumn(listViewer, SWT.NONE);
signerColumn.getColumn().setText(ProvUIMessages.TrustCertificateDialog_Name);
- signerColumn.getColumn().setWidth(400);
signerColumn.setLabelProvider(new PGPOrX509ColumnLabelProvider(pgp -> {
java.util.List<String> users = new ArrayList<>();
pgp.getUserIDs().forEachRemaining(users::add);
@@ -309,9 +308,9 @@ public class TrustCertificateDialog extends SelectionDialog {
return principalHelper.getCN() + "; " + principalHelper.getOU() + "; " //$NON-NLS-1$ //$NON-NLS-2$
+ principalHelper.getO();
}));
+
TableViewerColumn validColumn = new TableViewerColumn(listViewer, SWT.NONE);
validColumn.getColumn().setText(ProvUIMessages.TrustCertificateDialog_dates);
- validColumn.getColumn().setWidth(100);
validColumn.setLabelProvider(new PGPOrX509ColumnLabelProvider(pgp -> {
if (pgp.getCreationTime().after(Date.from(Instant.now()))) {
return NLS.bind(ProvUIMessages.TrustCertificateDialog_NotYetValidStartDate, pgp.getCreationTime());
@@ -354,6 +353,11 @@ public class TrustCertificateDialog extends SelectionDialog {
listViewer.setInput(inputElement);
+ typeColumn.getColumn().pack();
+ idColumn.getColumn().pack();
+ signerColumn.getColumn().pack();
+ validColumn.getColumn().pack();
+
if (!getInitialElementSelections().isEmpty()) {
checkInitialSelections();
}

Back to the top