Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMickael Istria2021-04-13 20:42:05 +0000
committerMickael Istria2021-06-15 13:34:02 +0000
commit45af7098437c45a46e7637d9aeb0177ca998e209 (patch)
tree0d50d16f52f6f79fc49979661462666823c91f37
parentddd6fcd94de359b9861685fa325fe8778398c691 (diff)
downloadrt.equinox.p2-45af7098437c45a46e7637d9aeb0177ca998e209.tar.gz
rt.equinox.p2-45af7098437c45a46e7637d9aeb0177ca998e209.tar.xz
rt.equinox.p2-45af7098437c45a46e7637d9aeb0177ca998e209.zip
Bug 572816 - p2 strategy to trust PGP signatures
This makes users declare whether PGP keys are trusted or not at installation, and to skip installation if one artifact has no signature/signer being trusted. * Propagate the pgp.signatures on local artifact description, so it's usable for CheckTrust * Add support in the Trust model for PGP keys * Add (limited) support for PGP approval in TrustCertificationDialog * Skip installation is PGP Keys are not trusted (similarly to certificates). Current limitations: * Dialog doesn't show whether a subset of PGP Keys is sufficient to complete installation (eg 1 artifact may have mulitple signature, only 1 is necessary to be approved for installation to complete, dialog doesn't show that and gives impression all keys need to be approved) * The dialog doesn't give any form of hint about how to decide whether to trust a key or net (eg check PGP key registries and so on); but it's also the case for certificates apparently... Change-Id: I65f698c7412027fedefc28ddfaa344caa6bfecdc # Conflicts: # bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.p2/+/179275 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/META-INF/MANIFEST.MF4
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/pom.xml2
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java74
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/repository/MirrorRequest.java15
-rw-r--r--bundles/org.eclipse.equinox.p2.core/META-INF/MANIFEST.MF8
-rw-r--r--bundles/org.eclipse.equinox.p2.core/pom.xml2
-rw-r--r--bundles/org.eclipse.equinox.p2.core/src/org/eclipse/equinox/p2/core/UIServices.java51
-rw-r--r--bundles/org.eclipse.equinox.p2.engine/META-INF/MANIFEST.MF4
-rw-r--r--bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/InstallableUnitOperand.java9
-rw-r--r--bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CertificateChecker.java188
-rw-r--r--bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CheckTrust.java22
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPTest.java9
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/engine/CertificateCheckerTest.java17
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/eclipse/CheckTrustActionTest.java12
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/artifacts.xml16
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/content.xml49
-rw-r--r--bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/plugins/blah_1.0.0.123456.jarbin0 -> 2101 bytes
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF4
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.eclipse/pom.xml2
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/ActionConstants.java1
-rw-r--r--bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/CheckTrustAction.java20
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/META-INF/MANIFEST.MF7
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/pom.xml2
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ProvUIMessages.java3
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ValidationDialogServiceUI.java112
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/dialogs/TrustCertificateDialog.java88
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/messages.properties9
-rw-r--r--bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/viewers/CertificateLabelProvider.java34
28 files changed, 563 insertions, 201 deletions
diff --git a/bundles/org.eclipse.equinox.p2.artifact.repository/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.artifact.repository/META-INF/MANIFEST.MF
index 6c49a76b0..6023a8c18 100644
--- a/bundles/org.eclipse.equinox.p2.artifact.repository/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.artifact.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.artifact.repository;singleton:=true
-Bundle-Version: 1.4.100.qualifier
+Bundle-Version: 1.4.200.qualifier
Bundle-Activator: org.eclipse.equinox.internal.p2.artifact.repository.Activator
Bundle-Vendor: %providerName
Bundle-Localization: plugin
@@ -10,7 +10,7 @@ Export-Package: org.eclipse.equinox.internal.p2.artifact.processing;x-friends:="
org.eclipse.equinox.internal.p2.artifact.processors.checksum;x-friends:="org.eclipse.equinox.p2.publisher",
org.eclipse.equinox.internal.p2.artifact.processors.md5;x-internal:=true,
org.eclipse.equinox.internal.p2.artifact.processors.pack200;x-friends:="org.eclipse.equinox.p2.artifact.processors,org.eclipse.equinox.p2.artifact.optimizers",
- org.eclipse.equinox.internal.p2.artifact.processors.pgp;x-internal:=true,
+ org.eclipse.equinox.internal.p2.artifact.processors.pgp;x-friends:="org.eclipse.equinox.p2.engine",
org.eclipse.equinox.internal.p2.artifact.repository;
x-friends:="org.eclipse.equinox.p2.publisher,
org.eclipse.equinox.p2.reconciler.dropins,
diff --git a/bundles/org.eclipse.equinox.p2.artifact.repository/pom.xml b/bundles/org.eclipse.equinox.p2.artifact.repository/pom.xml
index 4826e17d2..3e4f3e6cd 100644
--- a/bundles/org.eclipse.equinox.p2.artifact.repository/pom.xml
+++ b/bundles/org.eclipse.equinox.p2.artifact.repository/pom.xml
@@ -9,6 +9,6 @@
</parent>
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.p2.artifact.repository</artifactId>
- <version>1.4.100-SNAPSHOT</version>
+ <version>1.4.200-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
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 e81fc0e43..38439d650 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
@@ -12,6 +12,7 @@ package org.eclipse.equinox.internal.p2.artifact.processors.pgp;
import java.io.*;
import java.util.*;
+import java.util.stream.Collectors;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
@@ -40,43 +41,53 @@ public final class PGPSignatureVerifier extends ProcessingStep {
*/
public static final String ID = "org.eclipse.equinox.p2.processing.PGPSignatureCheck"; //$NON-NLS-1$
+ private static Map<Long, PGPPublicKey> knownKeys = new HashMap<>();
+
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 List<PGPSignature> signaturesToVerify;
+ private Collection<PGPSignature> signaturesToVerify;
public PGPSignatureVerifier() {
super();
link(nullOutputStream(), new NullProgressMonitor()); // this is convenience for tests
}
- @Override
- public void initialize(IProvisioningAgent agent, IProcessingStepDescriptor descriptor,
- IArtifactDescriptor context) {
- super.initialize(agent, descriptor, context);
-// 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
- String signatureText = unnormalizedPGPProperty(context.getProperty(PGP_SIGNATURES_PROPERTY_NAME));
+ private static Collection<PGPSignature> getSignatures(IArtifactDescriptor artifact)
+ throws IOException, PGPException {
+ String signatureText = unnormalizedPGPProperty(artifact.getProperty(PGP_SIGNATURES_PROPERTY_NAME));
if (signatureText == null) {
- setStatus(Status.OK_STATUS);
- return;
+ return Collections.emptyList();
}
- signaturesToVerify = new ArrayList<>();
+ List<PGPSignature> res = new ArrayList<>();
try (InputStream in = new ArmoredInputStream(new ByteArrayInputStream(signatureText.getBytes()))) {
JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(in);
Object o = pgpFactory.nextObject();
- PGPSignatureList signatureList;
+ PGPSignatureList signatureList = new PGPSignatureList(new PGPSignature[0]);
if (o instanceof PGPCompressedData) {
PGPCompressedData pgpCompressData = (PGPCompressedData) o;
pgpFactory = new JcaPGPObjectFactory(pgpCompressData.getDataStream());
signatureList = (PGPSignatureList) pgpFactory.nextObject();
} else if (o instanceof PGPSignatureList) {
signatureList = (PGPSignatureList) o;
- } else {
- setStatus(new Status(IStatus.ERROR, Activator.ID,
- Messages.Error_CouldNotLoadSignature));
- return;
}
- signatureList.iterator().forEachRemaining(signaturesToVerify::add);
+ signatureList.iterator().forEachRemaining(res::add);
+ }
+ return res;
+ }
+
+ @Override
+ public void initialize(IProvisioningAgent agent, IProcessingStepDescriptor descriptor,
+ IArtifactDescriptor context) {
+ super.initialize(agent, descriptor, context);
+// 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
+ String signatureText = unnormalizedPGPProperty(context.getProperty(PGP_SIGNATURES_PROPERTY_NAME));
+ if (signatureText == null) {
+ setStatus(Status.OK_STATUS);
+ return;
+ }
+ try {
+ signaturesToVerify = getSignatures(context);
} catch (Exception ex) {
setStatus(new Status(IStatus.ERROR, Activator.ID, Messages.Error_CouldNotLoadSignature, ex));
return;
@@ -87,10 +98,10 @@ public final class PGPSignatureVerifier extends ProcessingStep {
}
IArtifactRepository repository = context.getRepository();
- Map<Long, PGPPublicKey> signerKeys = readPublicKeys(context.getProperty(PGP_SIGNER_KEYS_PROPERTY_NAME),
- repository != null ? repository.getProperty(PGP_SIGNER_KEYS_PROPERTY_NAME) : null);
+ knownKeys.putAll(readPublicKeys(context.getProperty(PGP_SIGNER_KEYS_PROPERTY_NAME),
+ repository != null ? repository.getProperty(PGP_SIGNER_KEYS_PROPERTY_NAME) : null));
for (PGPSignature signature : signaturesToVerify) {
- PGPPublicKey publicKey = signerKeys.get(signature.getKeyID());
+ PGPPublicKey publicKey = knownKeys.get(signature.getKeyID());
if (publicKey == null) {
setStatus(new Status(IStatus.ERROR, Activator.ID,
NLS.bind(Messages.Error_publicKeyNotFound, signature.getKeyID())));
@@ -114,7 +125,7 @@ public final class PGPSignatureVerifier extends ProcessingStep {
* @param pgpSignaturesPropertyName
* @return fixed PGP armored blocks
*/
- private String unnormalizedPGPProperty(String value) {
+ private static String unnormalizedPGPProperty(String value) {
if (value == null) {
return null;
}
@@ -124,7 +135,7 @@ public final class PGPSignatureVerifier extends ProcessingStep {
.replace("-----END\nPGP\nPUBLIC\nKEY\nBLOCK-----", "-----END PGP PUBLIC KEY BLOCK-----"); //$NON-NLS-1$ //$NON-NLS-2$
}
- private Map<Long, PGPPublicKey> readPublicKeys(String armoredPublicKeyring) {
+ private static Map<Long, PGPPublicKey> readPublicKeys(String armoredPublicKeyring) {
if (armoredPublicKeyring == null) {
return Collections.emptyMap();
}
@@ -154,7 +165,7 @@ public final class PGPSignatureVerifier extends ProcessingStep {
}
@Override
- public void write(int b) throws IOException {
+ public void write(int b) {
if (signaturesToVerify != null) {
signaturesToVerify.iterator().forEachRemaining(signature -> signature.update((byte) b));
}
@@ -200,4 +211,21 @@ public final class PGPSignatureVerifier extends ProcessingStep {
}
setStatus(Status.OK_STATUS);
}
+
+ public static Collection<PGPPublicKey> getSigners(IArtifactDescriptor artifact) {
+ try {
+ return getSignatures(artifact).stream() //
+ .mapToLong(PGPSignature::getKeyID) //
+ .mapToObj(Long::valueOf) //
+ .map(knownKeys::get) //
+ .filter(Objects::nonNull).collect(Collectors.toSet());
+ } catch (IOException | PGPException e) {
+ LogHelper.log(new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e));
+ return Collections.emptyList();
+ }
+ }
+
+ public static void discardKnownKeys() {
+ knownKeys.clear();
+ }
}
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 965277d1c..d9f4dfe22 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,6 +23,7 @@ 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;
@@ -151,7 +152,7 @@ public class MirrorRequest extends ArtifactRequest {
return;
}
- IArtifactDescriptor destinationDescriptor = getDestinationDescriptor(descriptor);
+ IArtifactDescriptor destinationDescriptor = getDestinationDescriptor(descriptor, descriptor == canonical);
IStatus status = transfer(destinationDescriptor, descriptor, monitor);
// if ok, cancelled or transfer has already been done with the canonical form return with status set
if (status.getSeverity() == IStatus.CANCEL) {
@@ -176,7 +177,7 @@ public class MirrorRequest extends ArtifactRequest {
return;
}
- IStatus canonicalStatus = transfer(getDestinationDescriptor(canonical), canonical, monitor);
+ IStatus canonicalStatus = transfer(getDestinationDescriptor(canonical, true), canonical, monitor);
// To prevent the optimized transfer status severity from dominating the canonical, only merge
// if the canonical severity is equal to or higher than the optimized transfer severity.
if (canonicalStatus.getSeverity() < status.getSeverity())
@@ -185,7 +186,7 @@ public class MirrorRequest extends ArtifactRequest {
setResult(new MultiStatus(Activator.ID, canonicalStatus.getCode() != 0 ? canonicalStatus.getCode() : status.getCode(), new IStatus[] {status, canonicalStatus}, Messages.MirrorRequest_multipleDownloadProblems, null));
}
- private IArtifactDescriptor getDestinationDescriptor(IArtifactDescriptor sourceDescriptor) {
+ private IArtifactDescriptor getDestinationDescriptor(IArtifactDescriptor sourceDescriptor, boolean isCanonical) {
// Get the descriptor to use to store the artifact
// Since we are mirroring, ensure we clear out data from the original descriptor that may
// not apply in the new repo location.
@@ -200,6 +201,14 @@ 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.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.core/META-INF/MANIFEST.MF
index d8b8128f1..19cf0c2f6 100644
--- a/bundles/org.eclipse.equinox.p2.core/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.equinox.p2.core;singleton:=true
-Bundle-Version: 2.7.0.qualifier
+Bundle-Version: 2.8.0.qualifier
Bundle-ClassPath: .
Bundle-Activator: org.eclipse.equinox.internal.p2.core.Activator
Bundle-Vendor: %providerName
@@ -64,13 +64,15 @@ Export-Package: org.eclipse.equinox.internal.p2.core;x-friends:="org.eclipse.equ
org.eclipse.equinox.p2.updatesite,
org.eclipse.equinox.p2.director.app,
org.eclipse.equinox.p2.transport.ecf",
- org.eclipse.equinox.p2.core;version="2.0.0",
+ org.eclipse.equinox.p2.core;version="2.7.0",
org.eclipse.equinox.p2.core.spi;version="2.1.0"
Require-Bundle: org.eclipse.equinox.common;bundle-version="[3.5.0,4.0.0)"
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Service-Component: OSGI-INF/eventBus.xml, OSGI-INF/agentProvider.xml
-Import-Package: org.eclipse.osgi.framework.eventmgr;version="1.2.0",
+Import-Package: org.bouncycastle.bcpg;version="1.65.0",
+ org.bouncycastle.openpgp;version="1.65.0",
+ org.eclipse.osgi.framework.eventmgr;version="1.2.0",
org.eclipse.osgi.framework.log;version="1.0.0",
org.eclipse.osgi.service.debug;version="1.0.0",
org.eclipse.osgi.util;version="1.0.0",
diff --git a/bundles/org.eclipse.equinox.p2.core/pom.xml b/bundles/org.eclipse.equinox.p2.core/pom.xml
index 34dd2a2a1..3cac38cdd 100644
--- a/bundles/org.eclipse.equinox.p2.core/pom.xml
+++ b/bundles/org.eclipse.equinox.p2.core/pom.xml
@@ -9,6 +9,6 @@
</parent>
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.p2.core</artifactId>
- <version>2.7.0-SNAPSHOT</version>
+ <version>2.8.0-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/bundles/org.eclipse.equinox.p2.core/src/org/eclipse/equinox/p2/core/UIServices.java b/bundles/org.eclipse.equinox.p2.core/src/org/eclipse/equinox/p2/core/UIServices.java
index 2e6e2d97b..f2e27a243 100644
--- a/bundles/org.eclipse.equinox.p2.core/src/org/eclipse/equinox/p2/core/UIServices.java
+++ b/bundles/org.eclipse.equinox.p2.core/src/org/eclipse/equinox/p2/core/UIServices.java
@@ -15,6 +15,9 @@
package org.eclipse.equinox.p2.core;
import java.security.cert.Certificate;
+import java.util.Collection;
+import java.util.Collections;
+import org.bouncycastle.openpgp.PGPPublicKey;
/**
* Service used for prompting for user information from within lower level code.
@@ -74,11 +77,30 @@ public abstract class UIServices {
*/
public static class TrustInfo {
private final Certificate[] trustedCertificates;
+ private final Collection<PGPPublicKey> trustedPGPKeys;
private final boolean saveTrustedCertificates;
private final boolean trustUnsigned;
public TrustInfo(Certificate[] trusted, boolean save, boolean trustUnsigned) {
this.trustedCertificates = trusted;
+ this.trustedPGPKeys = Collections.emptyList();
+ this.saveTrustedCertificates = save;
+ this.trustUnsigned = trustUnsigned;
+ }
+
+ /**
+ *
+ * @param trusted
+ * @param trustedPGPKeys
+ * @param save
+ * @param trustUnsigned
+ * @since 2.8
+ */
+ public TrustInfo(Collection<Certificate> trustedCertificates, Collection<PGPPublicKey> trustedPGPKeys,
+ boolean save,
+ boolean trustUnsigned) {
+ this.trustedCertificates = trustedCertificates.toArray(Certificate[]::new);
+ this.trustedPGPKeys = trustedPGPKeys;
this.saveTrustedCertificates = save;
this.trustUnsigned = trustUnsigned;
}
@@ -95,6 +117,15 @@ public abstract class UIServices {
}
/**
+ *
+ * @return the trusted PGP keys
+ * @since 2.8
+ */
+ public Collection<PGPPublicKey> getTrustedPGPKeys() {
+ return trustedPGPKeys;
+ }
+
+ /**
* Return a boolean indicating whether the trusted certificates should
* be persisted for future operations.
*
@@ -160,4 +191,24 @@ public abstract class UIServices {
public void showInformationMessage(String title, String text, String linkText) {
System.out.println(text);
}
+
+ /**
+ * Opens a UI prompt to capture information about trusted content.
+ *
+ * @param untrustedChain - an array of certificate chains for which there is
+ * no current trust anchor. May be <code>null</code>,
+ * which means there are no untrusted certificate
+ * chains.
+ * @param untrustedPGPKeys
+ * @param unsignedDetail - an array of strings, where each String describes
+ * content that is not signed. May be <code>null</code>,
+ * which means there is no unsigned content
+ * @return the TrustInfo that describes the user's choices for trusting
+ * certificates and unsigned content.
+ * @since 2.8
+ */
+ public TrustInfo getTrustInfo(Certificate[][] unTrustedCertificateChains, Collection<PGPPublicKey> untrustedPGPKeys,
+ String[] details) {
+ return getTrustInfo(unTrustedCertificateChains, details);
+ }
}
diff --git a/bundles/org.eclipse.equinox.p2.engine/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.engine/META-INF/MANIFEST.MF
index fdc95836c..83eacd2af 100644
--- a/bundles/org.eclipse.equinox.p2.engine/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.engine/META-INF/MANIFEST.MF
@@ -14,7 +14,7 @@ Export-Package: org.eclipse.equinox.internal.p2.engine;
org.eclipse.equinox.p2.ui.sdk.scheduler,
org.eclipse.pde.build,
org.eclipse.equinox.p2.director.app",
- org.eclipse.equinox.internal.p2.engine.phases;x-friends:="org.eclipse.equinox.p2.director.app,org.eclipse.equinox.p2.repository.tools,org.eclipse.equinox.p2.ui.sdk.scheduler",
+ org.eclipse.equinox.internal.p2.engine.phases;x-friends:="org.eclipse.equinox.p2.director.app,org.eclipse.equinox.p2.repository.tools,org.eclipse.equinox.p2.ui.sdk.scheduler,org.eclipse.equinox.p2.touchpoint.eclipse",
org.eclipse.equinox.p2.engine;version="2.2.0",
org.eclipse.equinox.p2.engine.query;version="2.0.0",
org.eclipse.equinox.p2.engine.spi;version="2.0.0"
@@ -26,8 +26,10 @@ Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Service-Component: OSGI-INF/profileRegistry.xml, OSGI-INF/engine.xml
Import-Package: javax.xml.parsers,
+ org.bouncycastle.openpgp;version="1.65.0",
org.eclipse.core.internal.preferences,
org.eclipse.core.runtime.preferences,
+ org.eclipse.equinox.internal.p2.artifact.processors.pgp,
org.eclipse.equinox.internal.p2.core.helpers,
org.eclipse.equinox.internal.p2.metadata,
org.eclipse.equinox.internal.p2.metadata.index,
diff --git a/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/InstallableUnitOperand.java b/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/InstallableUnitOperand.java
index 6fea6e340..8b91ed338 100644
--- a/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/InstallableUnitOperand.java
+++ b/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/InstallableUnitOperand.java
@@ -27,7 +27,7 @@ public class InstallableUnitOperand extends Operand {
* Creates a new operand that represents replacing an installable unit
* with another. At least one of the provided installable units must be
* non-null.
- *
+ *
* @param first The installable unit being removed, or <code>null</code>
* @param second The installable unit being added, or <code>null</code>
*/
@@ -38,10 +38,17 @@ public class InstallableUnitOperand extends Operand {
this.second = second;
}
+ /**
+ *
+ * @return The installable unit being removed, or <code>null</code>
+ */
public IInstallableUnit first() {
return first;
}
+ /**
+ * @return The installable unit being added, or <code>null</code>
+ */
public IInstallableUnit second() {
return second;
}
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 7e1e729a8..964072ce8 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
@@ -18,12 +18,17 @@ import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.util.*;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import org.bouncycastle.openpgp.PGPPublicKey;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.internal.p2.artifact.processors.pgp.PGPSignatureVerifier;
import org.eclipse.equinox.internal.p2.engine.*;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
import org.eclipse.equinox.p2.core.UIServices;
import org.eclipse.equinox.p2.core.UIServices.TrustInfo;
+import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
import org.eclipse.osgi.service.security.TrustEngine;
import org.eclipse.osgi.signedcontent.*;
import org.eclipse.osgi.util.NLS;
@@ -32,13 +37,17 @@ import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
/**
- * Checks the certificates on a set of files or artifacts and reports back any problems
- * with unsigned artifacts, untrusted certificates, or tampered content.
+ * Checks the certificates or PGP signatures on a set of files or artifacts and
+ * reports back any problems with unsigned artifacts, untrusted certificates, or
+ * tampered content.
*/
public class CertificateChecker {
private static final String DEBUG_PREFIX = "certificate checker"; //$NON-NLS-1$
- private ArrayList<File> artifacts;
+ /**
+ * Stores artifacts to check
+ */
+ private Map<IArtifactDescriptor, File> artifacts = new HashMap<>();
private final IProvisioningAgent agent;
public CertificateChecker() {
@@ -47,7 +56,28 @@ public class CertificateChecker {
public CertificateChecker(IProvisioningAgent agent) {
this.agent = agent;
- artifacts = new ArrayList<>();
+ artifacts = new HashMap<>();
+ }
+
+ private static class PGPPublicKeyEntry {
+ public final PGPPublicKey key;
+
+ public PGPPublicKeyEntry(PGPPublicKey key) {
+ this.key = key;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PGPPublicKeyEntry)) {
+ return false;
+ }
+ return key.getKeyID() == ((PGPPublicKeyEntry) obj).key.getKeyID();
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(key.getKeyID());
+ }
}
public IStatus start() {
@@ -63,66 +93,70 @@ public class CertificateChecker {
private IStatus checkCertificates(SignedContentFactory verifierFactory) {
UIServices serviceUI = agent.getService(UIServices.class);
- SignedContent content = null;
- SignerInfo[] signerInfo = null;
- ArrayList<Certificate> untrusted = new ArrayList<>();
- ArrayList<File> unsigned = new ArrayList<>();
+ ArrayList<Certificate> untrustedCertificates = new ArrayList<>();
+ Map<IArtifactDescriptor, Collection<PGPPublicKey>> untrustedPGPArtifacts = new HashMap<>();
+ Map<PGPPublicKeyEntry, Collection<IArtifactDescriptor>> untrustedPGPKeys = new HashMap<>();
+ Map<IArtifactDescriptor, File> unsigned = new HashMap<>();
ArrayList<Certificate[]> untrustedChain = new ArrayList<>();
Map<Certificate, Collection<File>> untrustedArtifacts = new HashMap<>();
IStatus status = Status.OK_STATUS;
- if (artifacts.size() == 0 || serviceUI == null)
+ if (artifacts.isEmpty() || serviceUI == null) {
return status;
- checkArtifacts: for (File artifact : artifacts) {
+ }
+ for (Entry<IArtifactDescriptor, File> artifact : artifacts.entrySet()) {
+ File artifactFile = artifact.getValue();
try {
- content = verifierFactory.getSignedContent(artifact);
- if (!content.isSigned()) {
- unsigned.add(artifact);
- continue;
+ SignedContent content = verifierFactory.getSignedContent(artifactFile);
+ if (content.isSigned()) {
+ SignerInfo[] signerInfo = content.getSignerInfos();
+ if (Arrays.stream(signerInfo).noneMatch(SignerInfo::isTrusted)) {
+ // Only record the untrusted elements if there are no trusted elements.
+ for (SignerInfo element : signerInfo) {
+ if (!element.isTrusted()) {
+ Certificate[] certificateChain = element.getCertificateChain();
+ if (!untrustedCertificates.contains(certificateChain[0])) {
+ untrustedCertificates.add(certificateChain[0]);
+ untrustedChain.add(certificateChain);
+ }
+ if (DebugHelper.DEBUG_CERTIFICATE_CHECKER_UNTRUSTED) {
+ untrustedArtifacts.computeIfAbsent(certificateChain[0], key -> new ArrayList<>())
+ .add(artifactFile);
+ }
+ }
+ }
+ }
+ } else {
+ Collection<PGPPublicKey> signers = PGPSignatureVerifier.getSigners(artifact.getKey());
+ if (!signers.isEmpty()) {
+ if (signers.stream().noneMatch(this::isTrusted)) {
+ untrustedPGPArtifacts.putIfAbsent(artifact.getKey(), signers);
+ signers.forEach(signer -> untrustedPGPKeys
+ .computeIfAbsent(new PGPPublicKeyEntry(signer), key -> new HashSet<>())
+ .add(artifact.getKey()));
+ }
+ } else {
+ unsigned.put(artifact.getKey(), artifactFile);
+ }
}
- signerInfo = content.getSignerInfos();
+
} 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);
}
-
- // Determine if any element is trusted.
- for (SignerInfo element : signerInfo) {
- if (element.isTrusted()) {
- continue checkArtifacts;
- }
- }
-
- // Only record the untrusted elements if there are no trusted elements.
- for (SignerInfo element : signerInfo) {
- if (!element.isTrusted()) {
- Certificate[] certificateChain = element.getCertificateChain();
- if (!untrusted.contains(certificateChain[0])) {
- untrusted.add(certificateChain[0]);
- untrustedChain.add(certificateChain);
- }
- if (DebugHelper.DEBUG_CERTIFICATE_CHECKER_UNTRUSTED) {
- if (untrustedArtifacts.containsKey(certificateChain[0])) {
- untrustedArtifacts.get(certificateChain[0]).add(artifact);
- } else {
- untrustedArtifacts.put(certificateChain[0], new ArrayList<>(Arrays.asList(artifact)));
- }
- }
- }
- }
}
// log the unsigned artifacts if requested
if (DebugHelper.DEBUG_CERTIFICATE_CHECKER_UNSIGNED && !unsigned.isEmpty()) {
StringBuilder message = new StringBuilder("The following artifacts are unsigned:\n"); //$NON-NLS-1$
- for (File file : unsigned) {
+ for (File file : unsigned.values()) {
message.append(NLS.bind(" {0}\n", file.getPath())); //$NON-NLS-1$
}
DebugHelper.debug(DEBUG_PREFIX, message.toString());
}
// log the untrusted certificates if requested
- if (DebugHelper.DEBUG_CERTIFICATE_CHECKER_UNTRUSTED && !untrusted.isEmpty()) {
+ if (DebugHelper.DEBUG_CERTIFICATE_CHECKER_UNTRUSTED && !untrustedCertificates.isEmpty()) {
StringBuilder message = new StringBuilder("The following certificates are untrusted:\n"); //$NON-NLS-1$
for (Certificate cert : untrustedArtifacts.keySet()) {
message.append(cert.toString() + "\n"); //$NON-NLS-1$
@@ -133,41 +167,41 @@ public class CertificateChecker {
}
DebugHelper.debug(DEBUG_PREFIX, message.toString());
}
+ if (DebugHelper.DEBUG_CERTIFICATE_CHECKER_UNTRUSTED && !untrustedPGPKeys.isEmpty()) {
+ StringBuilder message = new StringBuilder("The following PGP Keys are untrusted:\n"); //$NON-NLS-1$
+ for (Entry<PGPPublicKeyEntry, Collection<IArtifactDescriptor>> entry : untrustedPGPKeys.entrySet()) {
+ message.append(entry.getKey().key.getKeyID() + "\n"); //$NON-NLS-1$
+ message.append(" used by the following artifacts:\n"); //$NON-NLS-1$
+ for (IArtifactDescriptor artifact : entry.getValue()) {
+ message.append(NLS.bind(" {0}\n", artifact.getArtifactKey())); //$NON-NLS-1$
+ }
+ }
+ DebugHelper.debug(DEBUG_PREFIX, message.toString());
+ }
String policy = getUnsignedContentPolicy();
//if there is unsigned content and we should never allow it, then fail without further checking certificates
- if (!unsigned.isEmpty() && EngineActivator.UNSIGNED_FAIL.equals(policy))
+ if (!unsigned.isEmpty() && EngineActivator.UNSIGNED_FAIL.equals(policy)) {
return new Status(IStatus.ERROR, EngineActivator.ID, NLS.bind(Messages.CertificateChecker_UnsignedNotAllowed, unsigned));
-
- String[] details;
- // If we always allow unsigned content, or we don't have any, we don't prompt the user about it
- if (EngineActivator.UNSIGNED_ALLOW.equals(policy) || unsigned.isEmpty())
- details = null;
- else {
- details = new String[unsigned.size()];
- for (int i = 0; i < details.length; i++) {
- details[i] = unsigned.get(i).toString();
- }
- }
- Certificate[][] unTrustedCertificateChains;
- if (untrusted.isEmpty()) {
- unTrustedCertificateChains = null;
- } else {
- unTrustedCertificateChains = new Certificate[untrustedChain.size()][];
- for (int i = 0; i < untrustedChain.size(); i++) {
- unTrustedCertificateChains[i] = untrustedChain.get(i);
- }
}
+ String[] details = EngineActivator.UNSIGNED_ALLOW.equals(policy) || unsigned.isEmpty() ? null
+ : unsigned.values().stream().map(Object::toString).toArray(String[]::new);
+ Certificate[][] unTrustedCertificateChains = untrustedCertificates.isEmpty() ? null
+ : untrustedChain.toArray(Certificate[][]::new);
// If there was no unsigned content, and nothing untrusted, no need to prompt.
- if (details == null && unTrustedCertificateChains == null)
+ if (details == null && unTrustedCertificateChains == null && untrustedPGPArtifacts.isEmpty()) {
return status;
+ }
- TrustInfo trustInfo = serviceUI.getTrustInfo(unTrustedCertificateChains, details);
+ TrustInfo trustInfo = serviceUI.getTrustInfo(unTrustedCertificateChains,
+ untrustedPGPKeys.keySet().stream().map(entry -> entry.key).collect(Collectors.toUnmodifiableList()),
+ details);
// If user doesn't trust unsigned content, cancel the operation
- if (!trustInfo.trustUnsignedContent())
+ if (!unsigned.isEmpty() && !trustInfo.trustUnsignedContent()) {
return Status.CANCEL_STATUS;
+ }
Certificate[] trustedCertificates = trustInfo.getTrustedCertificates();
// If we had untrusted chains and nothing was trusted, cancel the operation
@@ -177,16 +211,22 @@ public class CertificateChecker {
// Anything that was trusted should be removed from the untrusted list
if (trustedCertificates != null) {
for (Certificate trustedCertificate : trustedCertificates) {
- untrusted.remove(trustedCertificate);
+ untrustedCertificates.remove(trustedCertificate);
}
}
+ Collection<PGPPublicKey> trustedPGPKeys = trustInfo.getTrustedPGPKeys();
+ untrustedPGPArtifacts.values().removeIf(pgpKeys -> !Collections.disjoint(pgpKeys, trustedPGPKeys));
+ trustedPGPKeys.stream().map(PGPPublicKeyEntry::new).forEach(untrustedPGPKeys::remove);
// If there is still untrusted content, cancel the operation
- if (untrusted.size() > 0)
+ if (!untrustedCertificates.isEmpty() || !untrustedPGPKeys.isEmpty()) {
return new Status(IStatus.CANCEL, EngineActivator.ID, Messages.CertificateChecker_CertificateRejected);
+ }
// If we should persist the trusted certificates, add them to the trust engine
- if (trustInfo.persistTrust())
+ if (trustInfo.persistTrust()) {
return persistTrustedCertificates(trustedCertificates);
+ // do not persist PGP key at the moment
+ }
return status;
}
@@ -235,14 +275,12 @@ public class CertificateChecker {
}
- public void add(File toAdd) {
- artifacts.add(toAdd);
+ public void add(Map<IArtifactDescriptor, File> toAdd) {
+ artifacts.putAll(toAdd);
}
- public void add(Object[] toAdd) {
- for (Object element : toAdd) {
- if (element instanceof File)
- add((File) element);
- }
+ private boolean isTrusted(PGPPublicKey pgppublickey) {
+ return false;
}
}
+
diff --git a/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CheckTrust.java b/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CheckTrust.java
index 531f95331..e885ad1f6 100644
--- a/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CheckTrust.java
+++ b/bundles/org.eclipse.equinox.p2.engine/src/org/eclipse/equinox/internal/p2/engine/phases/CheckTrust.java
@@ -7,7 +7,7 @@
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
- *
+ *
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
@@ -25,6 +25,7 @@ import org.eclipse.equinox.p2.engine.PhaseSetFactory;
import org.eclipse.equinox.p2.engine.spi.ProvisioningAction;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.metadata.ITouchpointType;
+import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
/**
* An install phase that checks if the certificates used to sign the artifacts
@@ -32,7 +33,13 @@ import org.eclipse.equinox.p2.metadata.ITouchpointType;
*/
public class CheckTrust extends InstallableUnitPhase {
- public static final String PARM_ARTIFACT_FILES = "artifactFiles"; //$NON-NLS-1$
+ /**
+ * Parameter used to populate/get artifacts to check trust. The value for this
+ * property is <code>Map&lt;IArtifactDescriptor, File></code>.
+ *
+ * @see org.eclipse.equinox.internal.p2.touchpoint.eclipse.actions.CheckTrustAction
+ */
+ public static final String PARM_ARTIFACTS = "artifacts"; //$NON-NLS-1$
public CheckTrust(int weight) {
super(PhaseSetFactory.PHASE_CHECK_TRUST, weight);
@@ -46,15 +53,14 @@ public class CheckTrust extends InstallableUnitPhase {
@Override
protected IStatus completePhase(IProgressMonitor monitor, IProfile profile, Map<String, Object> parameters) {
@SuppressWarnings("unchecked")
- Collection<File> artifactRequests = (Collection<File>) parameters.get(PARM_ARTIFACT_FILES);
+ Map<IArtifactDescriptor, File> artifactRequests = (Map<IArtifactDescriptor, File>) parameters
+ .get(PARM_ARTIFACTS);
IProvisioningAgent agent = (IProvisioningAgent) parameters.get(PARM_AGENT);
// Instantiate a check trust manager
CertificateChecker certificateChecker = new CertificateChecker(agent);
- certificateChecker.add(artifactRequests.toArray());
- IStatus status = certificateChecker.start();
-
- return status;
+ certificateChecker.add(artifactRequests);
+ return certificateChecker.start();
}
@Override
@@ -86,7 +92,7 @@ public class CheckTrust extends InstallableUnitPhase {
@Override
protected IStatus initializePhase(IProgressMonitor monitor, IProfile profile, Map<String, Object> parameters) {
- parameters.put(PARM_ARTIFACT_FILES, new ArrayList<File>());
+ parameters.put(PARM_ARTIFACTS, new HashMap<IArtifactDescriptor, File>());
return super.initializePhase(monitor, profile, parameters);
}
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPTest.java
index bd2e3e2ad..595a6239f 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/artifact/repository/PGPTest.java
@@ -13,17 +13,26 @@ package org.eclipse.equinox.p2.tests.artifact.repository;
import java.nio.file.Files;
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.p2.metadata.Version;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
+import org.junit.Before;
import org.junit.Test;
public class PGPTest extends AbstractProvisioningTest {
IArtifactRepository targetRepo = null;
IArtifactRepository sourceRepo = null;
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ PGPSignatureVerifier.discardKnownKeys();
+ }
+
private void loadPGPTestRepo(String repoName) throws Exception {
sourceRepo = getArtifactRepositoryManager().loadRepository(
getTestData("Test repository for PGP", "testData/pgp/" + repoName).toURI(), new NullProgressMonitor());
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 79c58faf5..dd7cc84ad 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,11 +16,15 @@ package org.eclipse.equinox.p2.tests.engine;
import java.io.File;
import java.io.IOException;
import java.security.cert.Certificate;
+import java.util.Map;
import org.eclipse.core.runtime.IStatus;
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.p2.core.UIServices;
+import org.eclipse.equinox.p2.metadata.Version;
+import org.eclipse.equinox.p2.repository.artifact.spi.ArtifactDescriptor;
import org.eclipse.equinox.p2.tests.AbstractProvisioningTest;
import org.eclipse.equinox.p2.tests.TestActivator;
import org.eclipse.equinox.p2.tests.TestData;
@@ -80,7 +84,7 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
//if the service is consulted it will say no
serviceUI.unsignedReturnValue = false;
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_ALLOW);
- checker.add(unsigned);
+ checker.add(Map.of(new ArtifactDescriptor(new ArtifactKey("what", "ever", Version.create("1"))), unsigned));
IStatus result = checker.start();
assertEquals("1.0", IStatus.OK, result.getSeverity());
} finally {
@@ -94,7 +98,7 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
public void testPolicyFail() {
try {
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_FAIL);
- checker.add(unsigned);
+ checker.add(Map.of(new ArtifactDescriptor(new ArtifactKey("what", "ever", Version.create("1"))), unsigned));
IStatus result = checker.start();
assertEquals("1.0", IStatus.ERROR, result.getSeverity());
@@ -110,7 +114,7 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
try {
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_PROMPT);
serviceUI.unsignedReturnValue = true;
- checker.add(unsigned);
+ checker.add(Map.of(new ArtifactDescriptor(new ArtifactKey("what", "ever", Version.create("1"))), unsigned));
IStatus result = checker.start();
assertEquals("1.0", IStatus.OK, result.getSeverity());
assertTrue("1.1", serviceUI.wasPrompted);
@@ -125,7 +129,7 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
public void testPolicyDefault() {
System.getProperties().remove(EngineActivator.PROP_UNSIGNED_POLICY);
serviceUI.unsignedReturnValue = true;
- checker.add(unsigned);
+ checker.add(Map.of(new ArtifactDescriptor(new ArtifactKey("what", "ever", Version.create("1"))), unsigned));
IStatus result = checker.start();
assertEquals("1.0", IStatus.OK, result.getSeverity());
assertTrue("1.1", serviceUI.wasPrompted);
@@ -138,7 +142,7 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
try {
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_PROMPT);
serviceUI.unsignedReturnValue = false;
- checker.add(unsigned);
+ checker.add(Map.of(new ArtifactDescriptor(new ArtifactKey("what", "ever", Version.create("1"))), unsigned));
IStatus result = checker.start();
assertEquals("1.0", IStatus.CANCEL, result.getSeverity());
assertTrue("1.1", serviceUI.wasPrompted);
@@ -156,7 +160,7 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
try {
// Intentionally replace our service with a null service
testAgent.registerService(UIServices.SERVICE_NAME, null);
- checker.add(unsigned);
+ 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
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_PROMPT);
IStatus result = checker.start();
@@ -165,4 +169,5 @@ public class CertificateCheckerTest extends AbstractProvisioningTest {
System.getProperties().remove(EngineActivator.PROP_UNSIGNED_POLICY);
}
}
+
}
diff --git a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/eclipse/CheckTrustActionTest.java b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/eclipse/CheckTrustActionTest.java
index fbc3fc5f4..b1cade2a0 100644
--- a/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/eclipse/CheckTrustActionTest.java
+++ b/bundles/org.eclipse.equinox.p2.tests/src/org/eclipse/equinox/p2/tests/touchpoint/eclipse/CheckTrustActionTest.java
@@ -14,7 +14,9 @@
package org.eclipse.equinox.p2.tests.touchpoint.eclipse;
import java.io.File;
-import java.util.*;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.equinox.internal.p2.engine.InstallableUnitOperand;
import org.eclipse.equinox.internal.p2.engine.phases.CheckTrust;
@@ -65,7 +67,7 @@ public class CheckTrustActionTest extends AbstractProvisioningTest {
Map<String, Object> parameters = new HashMap<>();
parameters.put(ActionConstants.PARM_AGENT, getAgent());
parameters.put(ActionConstants.PARM_PROFILE, profile);
- parameters.put(CheckTrust.PARM_ARTIFACT_FILES, new ArrayList<>());
+ parameters.put(CheckTrust.PARM_ARTIFACTS, new HashMap<>());
EclipseTouchpoint touchpoint = new EclipseTouchpoint();
touchpoint.initializePhase(null, profile, "test", parameters);
InstallableUnitOperand operand = new InstallableUnitOperand(null, iu);
@@ -73,13 +75,13 @@ public class CheckTrustActionTest extends AbstractProvisioningTest {
touchpoint.initializeOperand(profile, parameters);
parameters = Collections.unmodifiableMap(parameters);
- assertFalse(((List<?>) parameters.get(CheckTrust.PARM_ARTIFACT_FILES)).contains(osgiTarget));
+ assertFalse(((Map<?, File>) parameters.get(CheckTrust.PARM_ARTIFACTS)).values().contains(osgiTarget));
CheckTrustAction action = new CheckTrustAction();
action.execute(parameters);
- assertTrue(((List<?>) parameters.get(CheckTrust.PARM_ARTIFACT_FILES)).contains(osgiTarget));
+ assertTrue(((Map<?, File>) parameters.get(CheckTrust.PARM_ARTIFACTS)).values().contains(osgiTarget));
// does nothing so should not alter parameters
action.undo(parameters);
- assertTrue(((List<?>) parameters.get(CheckTrust.PARM_ARTIFACT_FILES)).contains(osgiTarget));
+ assertTrue(((Map<?, File>) parameters.get(CheckTrust.PARM_ARTIFACTS)).values().contains(osgiTarget));
}
} \ No newline at end of file
diff --git a/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/artifacts.xml b/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/artifacts.xml
new file mode 100644
index 000000000..68b911bfa
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/artifacts.xml
@@ -0,0 +1,16 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<?artifactRepository version='1.1.0'?>
+<repository name='file:/home/mistria/sandbox/repo/ - artifacts' type='org.eclipse.equinox.p2.artifact.repository.simpleRepository' version='1'>
+ <properties size='2'>
+ <property name='p2.timestamp' value='1618433231272'/>
+ <property name='p2.compressed' value='false'/>
+ </properties>
+ <mappings size='3'>
+ <rule filter='(&amp; (classifier=osgi.bundle))' output='${repoUrl}/plugins/${id}_${version}.jar'/>
+ <rule filter='(&amp; (classifier=binary))' output='${repoUrl}/binary/${id}_${version}'/>
+ <rule filter='(&amp; (classifier=org.eclipse.update.feature))' output='${repoUrl}/features/${id}_${version}.jar'/>
+ </mappings>
+ <artifacts size='1'>
+ <artifact classifier='osgi.bundle' id='blah' version='1.0.0.123456'/>
+ </artifacts>
+</repository>
diff --git a/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/content.xml b/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/content.xml
new file mode 100644
index 000000000..db8f67ba2
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/content.xml
@@ -0,0 +1,49 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<?metadataRepository version='1.2.0'?>
+<repository name='file:/home/mistria/sandbox/repo/ - metadata' type='org.eclipse.equinox.internal.p2.metadata.repository.LocalMetadataRepository' version='1'>
+ <properties size='2'>
+ <property name='p2.timestamp' value='1618433231272'/>
+ <property name='p2.compressed' value='false'/>
+ </properties>
+ <units size='2'>
+ <unit id='blah' version='1.0.0.123456' singleton='false'>
+ <update id='blah' range='[0.0.0,1.0.0.123456)' severity='0'/>
+ <provides size='4'>
+ <provided namespace='org.eclipse.equinox.p2.iu' name='blah' version='1.0.0.123456'/>
+ <provided namespace='osgi.bundle' name='blah' version='1.0.0.123456'/>
+ <provided namespace='osgi.identity' name='blah' version='1.0.0.123456'>
+ <properties size='1'>
+ <property name='type' value='osgi.bundle'/>
+ </properties>
+ </provided>
+ <provided namespace='org.eclipse.equinox.p2.eclipse.type' name='bundle' version='1.0.0'/>
+ </provides>
+ <artifacts size='1'>
+ <artifact classifier='osgi.bundle' id='blah' version='1.0.0.123456'/>
+ </artifacts>
+ <touchpoint id='org.eclipse.equinox.p2.osgi' version='1.0.0'/>
+ <touchpointData size='1'>
+ <instructions size='2'>
+ <instruction key='zipped'>
+ true
+ </instruction>
+ <instruction key='manifest'>
+ Bundle-SymbolicName: blah&#xA;Bundle-Version: 1.0.0.qualifier&#xA;
+ </instruction>
+ </instructions>
+ </touchpointData>
+ </unit>
+ <unit id='Category' version='0.0.0'>
+ <properties size='3'>
+ <property name='org.eclipse.equinox.p2.type.category' value='true'/>
+ </properties>
+ <provides size='1'>
+ <provided namespace='org.eclipse.equinox.p2.iu' name='Category' version='0.0.0'/>
+ </provides>
+ <requires size='1'>
+ <required namespace='org.eclipse.equinox.p2.iu' name='blah' range='[1.0.0.123456,1.0.0.123456]'/>
+ </requires>
+ <touchpoint id='null' version='0.0.0'/>
+ </unit>
+ </units>
+</repository>
diff --git a/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/plugins/blah_1.0.0.123456.jar b/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/plugins/blah_1.0.0.123456.jar
new file mode 100644
index 000000000..81573b2aa
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.tests/testData/CertificateChecker/selfsigned/plugins/blah_1.0.0.123456.jar
Binary files differ
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF
index 5c3932fcc..71901cee2 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.equinox.p2.touchpoint.eclipse;singleton:=true
-Bundle-Version: 2.3.0.qualifier
+Bundle-Version: 2.3.100.qualifier
Bundle-Activator: org.eclipse.equinox.internal.p2.touchpoint.eclipse.Activator
Bundle-Vendor: %providerName
Bundle-Localization: plugin
@@ -19,8 +19,10 @@ Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ActivationPolicy: lazy
Import-Package: javax.xml.parsers,
org.eclipse.equinox.frameworkadmin;version="[2.0.0,3.0.0)",
+ org.eclipse.equinox.internal.p2.artifact.processors.pgp,
org.eclipse.equinox.internal.p2.core.helpers,
org.eclipse.equinox.internal.p2.engine,
+ org.eclipse.equinox.internal.p2.engine.phases,
org.eclipse.equinox.internal.p2.garbagecollector,
org.eclipse.equinox.internal.p2.metadata,
org.eclipse.equinox.internal.provisional.frameworkadmin,
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/pom.xml b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/pom.xml
index 7fc6bae69..12d492815 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/pom.xml
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/pom.xml
@@ -9,6 +9,6 @@
</parent>
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.p2.touchpoint.eclipse</artifactId>
- <version>2.3.0-SNAPSHOT</version>
+ <version>2.3.100-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/ActionConstants.java b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/ActionConstants.java
index ee394cfcf..ec918b53c 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/ActionConstants.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/ActionConstants.java
@@ -17,7 +17,6 @@ public class ActionConstants {
public static final String PARM_AGENT = "agent"; //$NON-NLS-1$
public static final String PARM_AT_ARTIFACT = "@artifact"; //$NON-NLS-1$
- public static final String PARM_ARTIFACT_FILES = "artifactFiles"; //$NON-NLS-1$
public static final String PARM_ARTIFACT_REQUESTS = "artifactRequests"; //$NON-NLS-1$
public static final String PARM_BUNDLE = "bundle"; //$NON-NLS-1$
public static final String PARM_FEATURE = "feature"; //$NON-NLS-1$
diff --git a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/CheckTrustAction.java b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/CheckTrustAction.java
index 201535517..7c7b51c17 100644
--- a/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/CheckTrustAction.java
+++ b/bundles/org.eclipse.equinox.p2.touchpoint.eclipse/src/org/eclipse/equinox/internal/p2/touchpoint/eclipse/actions/CheckTrustAction.java
@@ -18,6 +18,7 @@ import java.util.Collection;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.internal.p2.engine.phases.CheckTrust;
import org.eclipse.equinox.internal.p2.touchpoint.eclipse.EclipseTouchpoint;
import org.eclipse.equinox.internal.p2.touchpoint.eclipse.Util;
import org.eclipse.equinox.p2.core.IProvisioningAgent;
@@ -26,6 +27,8 @@ import org.eclipse.equinox.p2.engine.spi.ProvisioningAction;
import org.eclipse.equinox.p2.metadata.IArtifactKey;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.query.QueryUtil;
+import org.eclipse.equinox.p2.repository.artifact.IArtifactDescriptor;
+import org.eclipse.equinox.p2.repository.artifact.IFileArtifactRepository;
/**
* This action collects the set of bundle files on which the signature trust
@@ -47,14 +50,21 @@ public class CheckTrustAction extends ProvisioningAction {
if (!profile.available(QueryUtil.createIUQuery(iu), null).isEmpty())
return null;
@SuppressWarnings("unchecked")
- Collection<File> bundleFiles = (Collection<File>) parameters.get(ActionConstants.PARM_ARTIFACT_FILES);
+ Map<IArtifactDescriptor, File> bundleFiles = (Map<IArtifactDescriptor, File>) parameters
+ .get(CheckTrust.PARM_ARTIFACTS);
Collection<IArtifactKey> artifacts = iu.getArtifacts();
- if (artifacts == null)
+ if (artifacts == null) {
return null;
+ }
+ IFileArtifactRepository repo = Util.getAggregatedBundleRepository(agent, profile);
for (IArtifactKey key : artifacts) {
- File bundleFile = Util.getArtifactFile(agent, key, profile);
- if (!bundleFiles.contains(bundleFile))
- bundleFiles.add(bundleFile);
+ for (IArtifactDescriptor descriptor : repo.getArtifactDescriptors(key)) {
+ IFileArtifactRepository currentRepo = descriptor.getRepository() instanceof IFileArtifactRepository
+ ? (IFileArtifactRepository) descriptor.getRepository()
+ : repo;
+ File artifactFile = currentRepo.getArtifactFile(descriptor);
+ bundleFiles.put(descriptor, artifactFile);
+ }
}
return null;
}
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 270399d37..f5f8f384b 100644
--- a/bundles/org.eclipse.equinox.p2.ui/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.equinox.p2.ui/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %bundleName
Bundle-SymbolicName: org.eclipse.equinox.p2.ui;singleton:=true
-Bundle-Version: 2.7.100.qualifier
+Bundle-Version: 2.7.200.qualifier
Bundle-Activator: org.eclipse.equinox.internal.p2.ui.ProvUIActivator
Bundle-Vendor: %providerName
Bundle-Localization: plugin
@@ -36,6 +36,9 @@ Require-Bundle: org.eclipse.ui;bundle-version="3.107.0",
org.eclipse.equinox.security.ui;bundle-version="[1.0.0,2.0.0)",
org.eclipse.e4.ui.dialogs;bundle-version="1.1.600"
Import-Package: javax.xml.parsers,
+ org.bouncycastle.bcpg;version="1.65.0",
+ org.bouncycastle.openpgp;version="1.65.0",
+ org.bouncycastle.util;version="1.65.1",
org.eclipse.equinox.internal.p2.artifact.repository,
org.eclipse.equinox.internal.p2.core.helpers,
org.eclipse.equinox.internal.p2.director,
@@ -46,7 +49,7 @@ Import-Package: javax.xml.parsers,
org.eclipse.equinox.internal.provisional.configurator,
org.eclipse.equinox.internal.provisional.p2.core.eventbus,
org.eclipse.equinox.internal.provisional.p2.repository,
- org.eclipse.equinox.p2.core;version="[2.0.0,3.0.0)",
+ org.eclipse.equinox.p2.core;version="[2.7.0,3.0.0)",
org.eclipse.equinox.p2.core.spi;version="[2.0.0,3.0.0)",
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)",
diff --git a/bundles/org.eclipse.equinox.p2.ui/pom.xml b/bundles/org.eclipse.equinox.p2.ui/pom.xml
index 5954a5e0b..770c5b81a 100644
--- a/bundles/org.eclipse.equinox.p2.ui/pom.xml
+++ b/bundles/org.eclipse.equinox.p2.ui/pom.xml
@@ -19,6 +19,6 @@
</parent>
<groupId>org.eclipse.equinox</groupId>
<artifactId>org.eclipse.equinox.p2.ui</artifactId>
- <version>2.7.100-SNAPSHOT</version>
+ <version>2.7.200-SNAPSHOT</version>
<packaging>eclipse-plugin</packaging>
</project>
diff --git a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ProvUIMessages.java b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ProvUIMessages.java
index 07ad9b6c1..bcee1d0ef 100644
--- a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ProvUIMessages.java
+++ b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/ProvUIMessages.java
@@ -277,6 +277,9 @@ public class ProvUIMessages extends NLS {
public static String TrustCertificateDialog_AcceptSelectedButtonLabel;
public static String TrustCertificateDialog_SelectAll;
public static String TrustCertificateDialog_DeselectAll;
+ public static String TrustCertificateDialog_ObjectType;
+ public static String TrustCertificateDialog_Id;
+ public static String TrustCertificateDialog_Name;
// Operations
public static String UpdateManagerCompatibility_ExportSitesTitle;
public static String UpdateManagerCompatibility_ImportSitesTitle;
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 1c9135553..482fe4844 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
@@ -16,15 +16,17 @@ package org.eclipse.equinox.internal.p2.ui;
import java.net.URL;
import java.security.cert.Certificate;
+import java.util.*;
+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.ui.dialogs.TrustCertificateDialog;
import org.eclipse.equinox.internal.p2.ui.dialogs.UserValidationDialog;
-import org.eclipse.equinox.internal.p2.ui.viewers.CertificateLabelProvider;
import org.eclipse.equinox.p2.core.UIServices;
import org.eclipse.equinox.p2.ui.LoadMetadataRepositoryJob;
import org.eclipse.jface.dialogs.*;
-import org.eclipse.jface.viewers.*;
+import org.eclipse.jface.viewers.TreeNode;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
@@ -34,17 +36,19 @@ import org.eclipse.swt.widgets.*;
import org.eclipse.ui.PlatformUI;
/**
- * The default GUI-based implementation of {@link UIServices}.
- * The service declaration is made in the serviceui_component.xml file.
-
+ * The default GUI-based implementation of {@link UIServices}. The service
+ * declaration is made in the serviceui_component.xml file.
+ *
*/
public class ValidationDialogServiceUI extends UIServices {
static final class MessageDialogWithLink extends MessageDialog {
private final String linkText;
- MessageDialogWithLink(Shell parentShell, String dialogTitle, Image dialogTitleImage, String dialogMessage, int dialogImageType, String[] dialogButtonLabels, int defaultIndex, String linkText) {
- super(parentShell, dialogTitle, dialogTitleImage, dialogMessage, dialogImageType, dialogButtonLabels, defaultIndex);
+ MessageDialogWithLink(Shell parentShell, String dialogTitle, Image dialogTitleImage, String dialogMessage,
+ int dialogImageType, String[] dialogButtonLabels, int defaultIndex, String linkText) {
+ super(parentShell, dialogTitle, dialogTitleImage, dialogMessage, dialogImageType, dialogButtonLabels,
+ defaultIndex);
this.linkText = linkText;
}
@@ -73,7 +77,8 @@ public class ValidationDialogServiceUI extends UIServices {
*/
static class OkCancelErrorDialog extends ErrorDialog {
- public OkCancelErrorDialog(Shell parentShell, String dialogTitle, String message, IStatus status, int displayMask) {
+ public OkCancelErrorDialog(Shell parentShell, String dialogTitle, String message, IStatus status,
+ int displayMask) {
super(parentShell, dialogTitle, message, status, displayMask);
}
@@ -94,7 +99,8 @@ public class ValidationDialogServiceUI extends UIServices {
PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
Shell shell = ProvUI.getDefaultParentShell();
String message = NLS.bind(ProvUIMessages.ServiceUI_LoginDetails, location);
- UserValidationDialog dialog = new UserValidationDialog(shell, ProvUIMessages.ServiceUI_LoginRequired, null, message);
+ UserValidationDialog dialog = new UserValidationDialog(shell, ProvUIMessages.ServiceUI_LoginRequired,
+ null, message);
int dialogCode = dialog.open();
if (dialogCode == Window.OK) {
result[0] = dialog.getResult();
@@ -116,23 +122,37 @@ public class ValidationDialogServiceUI extends UIServices {
@Override
public TrustInfo getTrustInfo(Certificate[][] untrustedChains, final String[] unsignedDetail) {
+ return getTrustInfo(untrustedChains, Collections.emptyList(), unsignedDetail);
+ }
+
+ @Override
+ public TrustInfo getTrustInfo(Certificate[][] untrustedChains, Collection<PGPPublicKey> untrustedPublicKeys,
+ final String[] unsignedDetail) {
+ if (untrustedChains == null) {
+ untrustedChains = new Certificate[][] {};
+ }
boolean trustUnsigned = true;
boolean persistTrust = false;
- Certificate[] trusted = new Certificate[0];
- // Some day we may summarize all of this in one UI, or perhaps we'll have a preference to honor regarding
- // unsigned content. For now we prompt separately first as to whether unsigned detail should be trusted
+ List<Certificate> trustedCertificates = new ArrayList<>();
+ List<PGPPublicKey> trustedKeys = new ArrayList<>();
+ // Some day we may summarize all of this in one UI, or perhaps we'll have a
+ // preference to honor regarding
+ // unsigned content. For now we prompt separately first as to whether unsigned
+ // detail should be trusted
if (!isHeadless() && unsignedDetail != null && unsignedDetail.length > 0) {
- final boolean[] result = new boolean[] {false};
+ final boolean[] result = new boolean[] { false };
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
@Override
public void run() {
Shell shell = ProvUI.getDefaultParentShell();
- OkCancelErrorDialog dialog = new OkCancelErrorDialog(shell, ProvUIMessages.ServiceUI_warning_title, null, createStatus(), IStatus.WARNING);
+ OkCancelErrorDialog dialog = new OkCancelErrorDialog(shell, ProvUIMessages.ServiceUI_warning_title,
+ null, createStatus(), IStatus.WARNING);
result[0] = dialog.open() == IDialogConstants.OK_ID;
}
private IStatus createStatus() {
- MultiStatus parent = new MultiStatus(ProvUIActivator.PLUGIN_ID, 0, ProvUIMessages.ServiceUI_unsigned_message, null);
+ MultiStatus parent = new MultiStatus(ProvUIActivator.PLUGIN_ID, 0,
+ ProvUIMessages.ServiceUI_unsigned_message, null);
for (String element : unsignedDetail) {
parent.add(new Status(IStatus.WARNING, ProvUIActivator.PLUGIN_ID, element));
}
@@ -141,47 +161,57 @@ public class ValidationDialogServiceUI extends UIServices {
});
trustUnsigned = result[0];
}
- // For now, there is no need to show certificates if there was unsigned content and we don't trust it.
+ // For now, there is no need to show certificates if there was unsigned content
+ // and we don't trust it.
if (!trustUnsigned)
- return new TrustInfo(trusted, persistTrust, trustUnsigned);
-
- // We've established trust for unsigned content, now examine the untrusted chains
- if (!isHeadless() && untrustedChains != null && untrustedChains.length > 0) {
-
- final Object[] result = new Object[1];
- final TreeNode[] input = createTreeNodes(untrustedChains);
+ return new TrustInfo(trustedCertificates, trustedKeys, persistTrust, trustUnsigned);
+ // We've established trust for unsigned content, now examine the untrusted
+ // chains
+ if (!isHeadless() && (untrustedChains.length > 0 || !untrustedPublicKeys.isEmpty())) {
+ final TrustCertificateDialog[] dialog = new TrustCertificateDialog[1];
+ final TreeNode[] input = createTreeNodes(untrustedChains, untrustedPublicKeys);
PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
Shell shell = ProvUI.getDefaultParentShell();
- ILabelProvider labelProvider = new CertificateLabelProvider();
- TreeNodeContentProvider contentProvider = new TreeNodeContentProvider();
- TrustCertificateDialog trustCertificateDialog = new TrustCertificateDialog(shell, input, labelProvider, contentProvider);
+ TrustCertificateDialog trustCertificateDialog = new TrustCertificateDialog(shell, input);
+ dialog[0] = trustCertificateDialog;
trustCertificateDialog.open();
- Certificate[] values = new Certificate[trustCertificateDialog.getResult() == null ? 0 : trustCertificateDialog.getResult().length];
- for (int i = 0; i < values.length; i++) {
- values[i] = (Certificate) ((TreeNode) trustCertificateDialog.getResult()[i]).getValue();
- }
- result[0] = values;
});
persistTrust = true;
- trusted = (Certificate[]) result[0];
+ if (dialog[0].getResult() != null) {
+ for (Object o : dialog[0].getResult()) {
+ if (o instanceof TreeNode) {
+ o = ((TreeNode) o).getValue();
+ }
+ if (o instanceof Certificate) {
+ trustedCertificates.add((Certificate) o);
+ } else if (o instanceof PGPPublicKey) {
+ trustedKeys.add((PGPPublicKey) o);
+ }
+ }
+ }
}
- return new TrustInfo(trusted, persistTrust, trustUnsigned);
+ return new TrustInfo(trustedCertificates, trustedKeys, persistTrust, trustUnsigned);
}
- private TreeNode[] createTreeNodes(Certificate[][] certificates) {
- TreeNode[] children = new TreeNode[certificates.length];
- for (int i = 0; i < certificates.length; i++) {
+ private TreeNode[] createTreeNodes(Certificate[][] certificates, Collection<PGPPublicKey> untrustedPublicKeys) {
+ TreeNode[] children = new TreeNode[certificates.length + untrustedPublicKeys.size()];
+ int i = 0;
+ for (i = 0; i < certificates.length; i++) {
TreeNode head = new TreeNode(certificates[i][0]);
TreeNode parent = head;
children[i] = head;
for (int j = 0; j < certificates[i].length; j++) {
TreeNode node = new TreeNode(certificates[i][j]);
node.setParent(parent);
- parent.setChildren(new TreeNode[] {node});
+ parent.setChildren(new TreeNode[] { node });
parent = node;
}
}
+ for (PGPPublicKey key : untrustedPublicKeys) {
+ children[i] = new TreeNode(key);
+ i++;
+ }
return children;
}
@@ -197,7 +227,8 @@ public class ValidationDialogServiceUI extends UIServices {
else
message = NLS.bind(ProvUIMessages.ProvUIMessages_NotAccepted_EnterFor_0, location);
- UserValidationDialog dialog = new UserValidationDialog(previousInfo, shell, ProvUIMessages.ServiceUI_LoginRequired, null, message);
+ UserValidationDialog dialog = new UserValidationDialog(previousInfo, shell,
+ ProvUIMessages.ServiceUI_LoginRequired, null, message);
int dialogCode = dialog.open();
if (dialogCode == Window.OK) {
result[0] = dialog.getResult();
@@ -216,14 +247,15 @@ public class ValidationDialogServiceUI extends UIServices {
return;
}
PlatformUI.getWorkbench().getDisplay().syncExec(() -> {
- MessageDialog dialog = new MessageDialogWithLink(ProvUI.getDefaultParentShell(), title, null, text, MessageDialog.INFORMATION, new String[] {IDialogConstants.OK_LABEL}, 0, linkText);
+ MessageDialog dialog = new MessageDialogWithLink(ProvUI.getDefaultParentShell(), title, null, text,
+ MessageDialog.INFORMATION, new String[] { IDialogConstants.OK_LABEL }, 0, linkText);
dialog.open();
});
}
private boolean isHeadless() {
// If there is no UI available and we are still the IServiceUI,
- // assume that the operation should proceed. See
+ // assume that the operation should proceed. See
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=291049
return !PlatformUI.isWorkbenchRunning();
}
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 460592263..a37911a92 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
@@ -21,11 +21,15 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.function.Function;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.openpgp.PGPPublicKey;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.internal.p2.ui.ProvUIActivator;
import org.eclipse.equinox.internal.p2.ui.ProvUIMessages;
import org.eclipse.equinox.internal.p2.ui.viewers.CertificateLabelProvider;
+import org.eclipse.equinox.internal.provisional.security.ui.X500PrincipalHelper;
import org.eclipse.equinox.internal.provisional.security.ui.X509CertificateViewDialog;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
@@ -45,29 +49,51 @@ public class TrustCertificateDialog extends SelectionDialog {
private Object inputElement;
private IStructuredContentProvider contentProvider;
- private ILabelProvider labelProvider;
- private final static int SIZING_SELECTION_WIDGET_HEIGHT = 250;
- private final static int SIZING_SELECTION_WIDGET_WIDTH = 300;
+ private static final int SIZING_SELECTION_WIDGET_HEIGHT = 250;
+ private static final int SIZING_SELECTION_WIDGET_WIDTH = 300;
CheckboxTableViewer listViewer;
private TreeViewer certificateChainViewer;
- private Button detailsButton;
protected TreeNode parentElement;
protected Object selectedCertificate;
+ private Button detailsButton;
- public TrustCertificateDialog(Shell parentShell, Object input, ILabelProvider labelProvider,
- ITreeContentProvider contentProvider) {
+ public TrustCertificateDialog(Shell parentShell, Object input) {
super(parentShell);
inputElement = input;
- this.contentProvider = contentProvider;
- this.labelProvider = labelProvider;
+ this.contentProvider = new TreeNodeContentProvider();
setTitle(ProvUIMessages.TrustCertificateDialog_Title);
setMessage(ProvUIMessages.TrustCertificateDialog_Message);
setShellStyle(SWT.DIALOG_TRIM | SWT.MODELESS | SWT.RESIZE | getDefaultOrientation());
}
+ private static class PGPOrX509ColumnLabelProvider extends ColumnLabelProvider {
+ private Function<PGPPublicKey, String> pgpMap;
+ private Function<X509Certificate, String> x509map;
+
+ public PGPOrX509ColumnLabelProvider(Function<PGPPublicKey, String> pgpMap,
+ Function<X509Certificate, String> x509map) {
+ this.pgpMap = pgpMap;
+ this.x509map = x509map;
+ }
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof TreeNode) {
+ element = ((TreeNode) element).getValue();
+ }
+ if (element instanceof PGPPublicKey) {
+ return pgpMap.apply((PGPPublicKey) element);
+ }
+ if (element instanceof X509Certificate) {
+ return x509map.apply((X509Certificate) element);
+ }
+ return super.getText(element);
+ }
+ }
+
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = createUpperDialogArea(parent);
@@ -94,6 +120,7 @@ public class TrustCertificateDialog extends SelectionDialog {
listViewer.addDoubleClickListener(getDoubleClickListener());
listViewer.addSelectionChangedListener(getParentSelectionListener());
createButtons(composite);
+ detailsButton.setEnabled(selectedCertificate instanceof X509Certificate);
return composite;
}
@@ -140,11 +167,12 @@ public class TrustCertificateDialog extends SelectionDialog {
if (selectedCertificate instanceof TreeNode) {
o = ((TreeNode) selectedCertificate).getValue();
}
+ FileDialog destination = new FileDialog(detailsButton.getShell(), SWT.SAVE);
+ destination.setText(ProvUIMessages.TrustCertificateDialog_Export);
if (o instanceof X509Certificate) {
X509Certificate cert = (X509Certificate) o;
- FileDialog destination = new FileDialog(detailsButton.getShell(), SWT.SAVE);
destination.setFilterExtensions(new String[] { "*.der" }); //$NON-NLS-1$
- destination.setText(ProvUIMessages.TrustCertificateDialog_Export);
+ destination.setFileName(cert.getSerialNumber().toString() + ".der"); //$NON-NLS-1$
String path = destination.open();
if (path == null) {
return;
@@ -156,6 +184,21 @@ public class TrustCertificateDialog extends SelectionDialog {
ProvUIActivator.getDefault().getLog()
.log(new Status(IStatus.ERROR, ProvUIActivator.PLUGIN_ID, ex.getMessage(), ex));
}
+ } else if (o instanceof PGPPublicKey) {
+ PGPPublicKey key = (PGPPublicKey) o;
+ destination.setFilterExtensions(new String[] { "*.asc" }); //$NON-NLS-1$
+ destination.setFileName(key.getKeyID() + ".asc"); //$NON-NLS-1$
+ String path = destination.open();
+ if (path == null) {
+ return;
+ }
+ File destinationFile = new File(path);
+ try (OutputStream output = new ArmoredOutputStream(new FileOutputStream(destinationFile))) {
+ output.write(key.getEncoded());
+ } catch (IOException ex) {
+ ProvUIActivator.getDefault().getLog()
+ .log(new Status(IStatus.ERROR, ProvUIActivator.PLUGIN_ID, ex.getMessage(), ex));
+ }
}
}
@@ -163,6 +206,7 @@ public class TrustCertificateDialog extends SelectionDialog {
public void widgetSelected(SelectionEvent e) {
widgetDefaultSelected(e);
}
+
});
}
@@ -177,8 +221,29 @@ public class TrustCertificateDialog extends SelectionDialog {
data.widthHint = SIZING_SELECTION_WIDGET_WIDTH;
listViewer.getTable().setLayoutData(data);
- listViewer.setLabelProvider(labelProvider);
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(key -> Long.toString(key.getKeyID()),
+ 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);
+ return String.join(",", users); //$NON-NLS-1$
+ }, x509 -> {
+ X500PrincipalHelper principalHelper = new X500PrincipalHelper(x509.getSubjectX500Principal());
+ return principalHelper.getCN() + "; " + principalHelper.getOU() + "; " //$NON-NLS-1$ //$NON-NLS-2$
+ + principalHelper.getO();
+ }));
+ listViewer.getTable().setHeaderVisible(true);
addSelectionButtons(composite);
@@ -242,6 +307,7 @@ public class TrustCertificateDialog extends SelectionDialog {
ISelection selection = event.getSelection();
if (selection instanceof StructuredSelection) {
selectedCertificate = ((StructuredSelection) selection).getFirstElement();
+ detailsButton.setEnabled(selectedCertificate instanceof X509Certificate);
}
};
}
diff --git a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/messages.properties b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/messages.properties
index 8dd78ac93..561551af2 100644
--- a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/messages.properties
+++ b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/messages.properties
@@ -278,8 +278,11 @@ RollbackProfileElement_CurrentInstallation=Current Installation
TrustCertificateDialog_Details=\uD83D\uDD0D &Details...
TrustCertificateDialog_Export=\uD83D\uDCE5 E&xport...
-TrustCertificateDialog_Title=Certificates
-TrustCertificateDialog_Message=Do you trust these certificates?
-TrustCertificateDialog_AcceptSelectedButtonLabel=&Accept selected
+TrustCertificateDialog_Title=Trust
+TrustCertificateDialog_Message=Do you trust these signers?
+TrustCertificateDialog_AcceptSelectedButtonLabel=&Trust selected
TrustCertificateDialog_SelectAll=&Select All
TrustCertificateDialog_DeselectAll=&Deselect All
+TrustCertificateDialog_ObjectType=Type
+TrustCertificateDialog_Id=Id
+TrustCertificateDialog_Name=Name
diff --git a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/viewers/CertificateLabelProvider.java b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/viewers/CertificateLabelProvider.java
index 16246ed34..9eb67d01e 100644
--- a/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/viewers/CertificateLabelProvider.java
+++ b/bundles/org.eclipse.equinox.p2.ui/src/org/eclipse/equinox/internal/p2/ui/viewers/CertificateLabelProvider.java
@@ -13,8 +13,8 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.ui.viewers;
-import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
+import org.bouncycastle.openpgp.PGPPublicKey;
import org.eclipse.equinox.internal.provisional.security.ui.X500PrincipalHelper;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.graphics.Image;
@@ -31,17 +31,37 @@ public class CertificateLabelProvider implements ILabelProvider {
@Override
public String getText(Object element) {
- Certificate cert = null;
if (element instanceof TreeNode) {
- cert = (Certificate) ((TreeNode) element).getValue();
- }
- if (cert != null) {
- X500PrincipalHelper principalHelper = new X500PrincipalHelper(((X509Certificate) cert).getSubjectX500Principal());
- return principalHelper.getCN() + "; " + principalHelper.getOU() + "; " + principalHelper.getO(); //$NON-NLS-1$ //$NON-NLS-2$
+ Object o = ((TreeNode) element).getValue();
+ if (o instanceof X509Certificate) {
+ X509Certificate cert = (X509Certificate) o;
+ X500PrincipalHelper principalHelper = new X500PrincipalHelper(cert.getSubjectX500Principal());
+ return principalHelper.getCN() + "; " + principalHelper.getOU() + "; " //$NON-NLS-1$ //$NON-NLS-2$
+ + principalHelper.getO();
+ } else if (o instanceof PGPPublicKey) {
+ return userFriendlyFingerPrint((PGPPublicKey) o);
+ }
}
return ""; //$NON-NLS-1$
}
+ private String userFriendlyFingerPrint(PGPPublicKey key) {
+ if (key == null) {
+ return null;
+ }
+ StringBuilder builder = new StringBuilder();
+ boolean spaceSuffix = false;
+ for (byte b : key.getFingerprint()) {
+ builder.append(String.format("%02X", Byte.toUnsignedInt(b))); //$NON-NLS-1$
+ if (spaceSuffix) {
+ builder.append(' ');
+ }
+ spaceSuffix = !spaceSuffix;
+ }
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ }
+
@Override
public void addListener(ILabelProviderListener listener) {
// do nothing

Back to the top