Bug 577248 - Bundles can contribute trusted PGP keys as extensions
Change-Id: I8ea41d0829f2ab80a0edfcd4fe4a9892b80432b4
Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.p2/+/189270
Tested-by: Equinox Bot <equinox-bot@eclipse.org>
Reviewed-by: Mickael Istria <mistria@redhat.com>
diff --git a/bundles/org.eclipse.equinox.p2.engine/plugin.xml b/bundles/org.eclipse.equinox.p2.engine/plugin.xml
index 3d469f4..cee2c38 100644
--- a/bundles/org.eclipse.equinox.p2.engine/plugin.xml
+++ b/bundles/org.eclipse.equinox.p2.engine/plugin.xml
@@ -4,6 +4,7 @@
<extension-point id="touchpoints" name="Touchpoints" schema="schema/touchpoints.exsd"/>
<extension-point id="actions" name="Actions" schema="schema/actions.exsd"/>
+<extension-point id="pgp" name="PGP" schema="schema/pgp.exsd"/>
<extension
id="metadataRepository"
point="org.eclipse.equinox.p2.metadata.repository.metadataRepositories">
diff --git a/bundles/org.eclipse.equinox.p2.engine/schema/pgp.exsd b/bundles/org.eclipse.equinox.p2.engine/schema/pgp.exsd
new file mode 100644
index 0000000..fcb06d0
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.engine/schema/pgp.exsd
@@ -0,0 +1,107 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.equinox.p2.engine" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+ <appInfo>
+ <meta.schema plugin="org.eclipse.equinox.p2.engine" id="pgp" name="PGP"/>
+ </appInfo>
+ <documentation>
+ Configure PGP keys and trust for p2 operations
+ </documentation>
+ </annotation>
+
+ <element name="extension">
+ <annotation>
+ <appInfo>
+ <meta.element />
+ </appInfo>
+ </annotation>
+ <complexType>
+ <sequence minOccurs="0" maxOccurs="unbounded">
+ <element ref="trustedKeys"/>
+ </sequence>
+ <attribute name="point" type="string" use="required">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="id" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ </annotation>
+ </attribute>
+ <attribute name="name" type="string">
+ <annotation>
+ <documentation>
+
+ </documentation>
+ <appInfo>
+ <meta.attribute translatable="true"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <element name="trustedKeys">
+ <annotation>
+ <documentation>
+ PGP Keys that are trusted by default.
+ </documentation>
+ </annotation>
+ <complexType>
+ <attribute name="path" type="string">
+ <annotation>
+ <documentation>
+ Path to a file containing PGP keys in armored form.
+ </documentation>
+ <appInfo>
+ <meta.attribute kind="resource"/>
+ </appInfo>
+ </annotation>
+ </attribute>
+ </complexType>
+ </element>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="since"/>
+ </appInfo>
+ <documentation>
+ 2.7.300
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="examples"/>
+ </appInfo>
+ <documentation>
+ [Enter extension point usage example here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="apiinfo"/>
+ </appInfo>
+ <documentation>
+ [Enter API information here.]
+ </documentation>
+ </annotation>
+
+ <annotation>
+ <appInfo>
+ <meta.section type="implementation"/>
+ </appInfo>
+ <documentation>
+ [Enter information about supplied implementation of this extension point.]
+ </documentation>
+ </annotation>
+
+
+</schema>
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 48c1792..09a0686 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
@@ -13,14 +13,14 @@
*******************************************************************************/
package org.eclipse.equinox.internal.p2.engine.phases;
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.util.*;
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.eclipse.core.runtime.*;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
@@ -55,6 +55,7 @@
private Map<IArtifactDescriptor, File> artifacts = new HashMap<>();
private final IProvisioningAgent agent;
+ // Lazily loading
private Supplier<PGPPublicKeyStore> trustedKeys = new Supplier<>() {
private PGPPublicKeyStore cache = null;
@@ -307,13 +308,59 @@
public PGPPublicKeyStore buildPGPTrustore() {
PGPPublicKeyStore trustStore = new PGPPublicKeyStore();
- IProfile profile = agent.getService(IProfileRegistry.class).getProfile(IProfileRegistry.SELF);
- if (profile != null) {
- trustStore.addKeys(profile.getProperty(TRUSTED_KEY_STORE_PROPERTY));
- ProfileScope profileScope = new ProfileScope(agent.getService(IAgentLocation.class),
- profile.getProfileId());
- trustStore.addKeys(profileScope.getNode(EngineActivator.ID).get(TRUSTED_KEY_STORE_PROPERTY, null));
+ // load from profile properties
+ if (agent != null && agent.getService(IAgentLocation.SERVICE_NAME) != null) {
+ IProfile profile = agent.getService(IProfileRegistry.class).getProfile(IProfileRegistry.SELF);
+ if (profile != null) {
+ trustStore.addKeys(profile.getProperty(TRUSTED_KEY_STORE_PROPERTY));
+ ProfileScope profileScope = new ProfileScope(agent.getService(IAgentLocation.class),
+ profile.getProfileId());
+ trustStore.addKeys(profileScope.getNode(EngineActivator.ID).get(TRUSTED_KEY_STORE_PROPERTY, null));
+ }
}
+ // load from bundles providing capability
+ for (IConfigurationElement extension : RegistryFactory.getRegistry()
+ .getConfigurationElementsFor(EngineActivator.ID + ".pgp")) {
+ if ("trustedKeys".equals(extension.getName())) {
+ String pathInBundle = extension.getAttribute("path"); //$NON-NLS-1$
+ if (pathInBundle != null) {
+ Stream.of(EngineActivator.getContext().getBundles())
+ .filter(bundle -> bundle.getSymbolicName().equals(extension.getContributor().getName()))
+ .map(bundle -> bundle.getEntry(pathInBundle)) //
+ .filter(Objects::nonNull) //
+ .forEach(url -> {
+ try (InputStream stream = url.openStream()) {
+ PGPPublicKeyStore.readPublicKeys(stream).forEach(trustStore::addKey);
+ } catch (IOException e) {
+ DebugHelper.debug(DEBUG_PREFIX, e.getMessage());
+ }
+ });
+ }
+ }
+ }
+// FrameworkWiring wiring = EngineActivator.getContext().getBundle(Constants.SYSTEM_BUNDLE_ID)
+// .adapt(FrameworkWiring.class);
+// if (wiring != null) {
+// Requirement pgpSignatureRequirements = ModuleContainer.createRequirement(
+// "org.eclipse.equinox.p2.pgp.trustedPublicKeys", //$NON-NLS-1$
+// Map.of(), Map.of());
+// for (BundleCapability capability : wiring.findProviders(pgpSignatureRequirements)) {
+// String pathInBundle = (String) capability.getAttributes().get("path"); //$NON-NLS-1$
+// if (pathInBundle != null) {
+// URL key = capability.getRevision().getBundle().getEntry(pathInBundle);
+// if (key != null) {
+// try (InputStream stream = key.openStream()) {
+// PGPPublicKeyStore.readPublicKeys(stream).forEach(trustStore::addKey);
+// } catch (IOException e) {
+// DebugHelper.debug(DEBUG_PREFIX, e.getMessage());
+// }
+// }
+// }
+// }
+
+// }
+
+ // load from p2 metadata of installed artifacts
//// SECURITY ISSUE: next lines become an attack vector as we have no guarantee
//// the metadata of those IUs is safe/were signed.
//// https://bugs.eclipse.org/bugs/show_bug.cgi?id=576705#c4
diff --git a/bundles/org.eclipse.equinox.p2.tests/plugin.xml b/bundles/org.eclipse.equinox.p2.tests/plugin.xml
index 841b529..8576995 100644
--- a/bundles/org.eclipse.equinox.p2.tests/plugin.xml
+++ b/bundles/org.eclipse.equinox.p2.tests/plugin.xml
@@ -153,4 +153,10 @@
suffix="@testArtifactRepositoryRegistry">
</filter>
</extension>
+ <extension
+ point="org.eclipse.equinox.p2.engine.pgp">
+ <trustedKeys
+ path="testData/pgp/signer2-publickey.asc">
+ </trustedKeys>
+ </extension>
</plugin>
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 5f4049a..c251e5c 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
@@ -39,7 +39,7 @@
* Tests for {@link CertificateChecker}.
*/
public class CertificateCheckerTest extends AbstractProvisioningTest {
- private static final String PGP_PUBLIC_KEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + "\n"
+ private static final String PGP_SIGNER1_PUBLIC_KEY = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + "\n"
+ "mQGNBGB1bugBDADQ5l7YnS9hNFRkBKSrvVNHt/TxeHaNNIHkdTC56I1QdThsOt4Y\n"
+ "oQRI27AEOaY1GFEi6+QqwxALcMMMSTgkCRs2NFGqlWMVzNYE7bJMWChVa7uQ/9CG\n"
+ "1HRbXwVwQx3hFgU4kmw1Kl/IH4LX76d9gAMyFANPjYZJSjbAv54wOlKruDRgpQFF\n"
@@ -78,7 +78,7 @@
+ "cKLSMOR+Tji6n7FjOcVl6VoDKTjdxj9OgAlbZ7W9jEArrUjDdCk/m4jq9h9phpli\n"
+ "BHoul/nauwtlUnQes1V+39Rk9l7gddKWg3dlwg6CjB5MkmcaeyxgANcyKgrunpg=\n" + "=JYpC\n"
+ "-----END PGP PUBLIC KEY BLOCK-----\n";
- private static final String PGP_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" + "\n"
+ private static final String PGP_SIGNER1_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" + "\n"
+ "iQHRBAABCAA7FiEE6ZbGcKp/ZUCb9dtWE5442Q3tEfAFAmB4Bf8dHHNpZ25lcjFA\n"
+ "ZmFrZXVzZXIuZWNsaXBzZS5vcmcACgkQE5442Q3tEfBPuAwAhE4zA7BswKFhEtzm\n"
+ "DS3EbyRr/U13sV01YxqGtxYDCfrOt8TGVPXJSvo0AVP4vLFc5b+0jtVFoarFJNBu\n"
@@ -90,6 +90,19 @@
+ "7iTFc+WgVJkP0e695mm1tcvtQHUPbIItYJUsndyLgGInzglxN8+F4U4k8uapydI9\n"
+ "RmV2NVAifYp8z95Am5AnlG8lqjwrWk5bMbJH82QsQESrNT/h\n" + "=8Vrn\n"
+ "-----END PGP SIGNATURE-----\n";
+ private static final String PGP_SIGNER2_SIGNATURE = "-----BEGIN PGP SIGNATURE-----\n" + "\n"
+ + "iQIzBAABCAAdFiEEzZ1aK4a8T+GDlFHh4vaU9BsKs3AFAmHUy4UACgkQ4vaU9BsK\n"
+ + "s3DjuhAAvlCtqhK/7/aAG0/cXtlpu0fPC176OmEmBGTjCsrGdWwuRHsqXbLnMBVZ\n"
+ + "0D1m38MDvuYZfJuP7arw7USKp+Jy54Bv/YwvHLl74YTx1BN9hAN9QvyLxLZOjdm1\n"
+ + "/ipiWUuUgGa/brxEZNQqSR0w17TqXkIJHeFFS3T/rNH/Ckom5vQhAEm9HwJYeGdt\n"
+ + "tJ5BUl7BS1rrEOs+xmzqLu6AaERREc5gGRniJe7aP2Ke+/wL6oOLG3v/6vsJSM2e\n"
+ + "t+Olo4Ugc6JbdNrwvTO8kkTxsi0gp2CPhKl3RZVnbGoe4tXHawmk2V3eVTa0w6iP\n"
+ + "ARPJ/xH2dDsRi4Kz3OkcyQOI24jGmaqpUrx3+f2BnEbcVs4cHIJc+O2gh1WUz6uY\n"
+ + "Zw7qtK0W3H+E7RuJLCScgasPZZPBzyA6B3o2J3bfXnG5r41aJEuiq3otgllrBakG\n"
+ + "u7fX00h8lylgRrlCb4mquZxxRsrl+ac6U5eYdDMkK5VNkXgrus8FedIh3vmqI7RR\n"
+ + "ou9GEjho4kebt1Y1yTAQnxWBtTUG2hFt6VirKydI7+ZcCZmbD9lrZT6xVQOCkyUQ\n"
+ + "Cwy7vNPWkpMBBRVLoaThD2+7znDpb6wNYf9mDCcCK8tyuVCDSYEFX6jlqv1yfpNl\n"
+ + "QlYE1biLAHl09NH397Ta/9cWdfu68I7Mv4Ev2zu45OGa83h820M=\n" + "=9Ha/\n" + "-----END PGP SIGNATURE-----\n";
class CertificateTestService extends UIServices {
public boolean unsignedReturnValue = true;
@@ -242,9 +255,9 @@
ArtifactDescriptor artifactDescriptor = new ArtifactDescriptor(
new ArtifactKey("what", "ever", Version.create("1")));
artifactDescriptor.addProperties(
- Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME, PGP_SIGNATURE, //
+ Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME, PGP_SIGNER1_SIGNATURE, //
PGPSignatureVerifier.PGP_SIGNER_KEYS_PROPERTY_NAME,
- PGP_PUBLIC_KEY));
+ PGP_SIGNER1_PUBLIC_KEY));
checker.add(Map.of(artifactDescriptor, unsigned));
System.getProperties().setProperty(EngineActivator.PROP_UNSIGNED_POLICY, EngineActivator.UNSIGNED_PROMPT);
IStatus result = checker.start();
@@ -264,13 +277,29 @@
CertificateCheckerTest.class.getName() + "testPGPSignedArtifactTrustedKey-profile")
.toUri()));
testAgent.getService(IProfileRegistry.class).addProfile(IProfileRegistry.SELF,
- Map.of(CertificateChecker.TRUSTED_KEY_STORE_PROPERTY, PGP_PUBLIC_KEY));
-
+ 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_SIGNATURE));
+ Map.of(PGPSignatureVerifier.PGP_SIGNATURES_PROPERTY_NAME, PGP_SIGNER1_SIGNATURE));
+ checker.add(Map.of(artifactDescriptor, unsigned));
+ 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));
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.tests/testData/pgp/signer2-publickey.asc b/bundles/org.eclipse.equinox.p2.tests/testData/pgp/signer2-publickey.asc
new file mode 100644
index 0000000..0218bdc
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.tests/testData/pgp/signer2-publickey.asc
@@ -0,0 +1,29 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBGHUyxYBEADATeNx4XA4H2fP9mD5xwlIyh7qvHLezgXpqCwNS9ATqBwnfrCV
+06a+pfSLsLoXrP/sdaB5WhijfuxTis18RMfoDVwGMRqyD2GiBCl2vwJDg3BUwHnc
+H7W6XkWKO7dkPmF+TUbD3cTWZ7cvrPmMjinmXaq8htuktGuE2VEGZRnuG1m+ChDM
+PnSb1ioFS2+MJv13P2fagVk2qC95DkPJGpMk3CY3ghLDEaY/KaJl+8axAlUUUk9N
+d3k/KVxxf5k3g26EVQkWWgH2mmolptGO101xW64iked97Cy4NK2yafOF/wmpsavx
+PGpOewnDgAJBBPkum6mPH0vcOZgxmLyh4uqfPfr3IaBQlbJLN2dXaDsV83j5t1wZ
+2qxOPcWBfORm6W7dC0TQgCXbEG0geMBpJtvnMX83Q2ORqDpjbHRJsV2k+8NxaXON
+pYXGr+Lj/9n0xfNEDXhCdGab0XP2tVZ5jfr2OQ5dAomEaPqK5Kq7akoWMddpDLNC
+G4IvH8G0cxwruJk00uwd6Nd2NVqVMRYCsBbA89VanUnutLUIpVnnOAetlX9blXHO
+JtmiCPGgHyp+iYGhKYVzfuZQyFhonbi0AhidJDvbHsoLT3p4Mcog1B9y6MODBE7R
+jwrU+qMqYsYeFhGYKbYyXv9TfEyUvtCQ/GnTtRJAQyicFdOdbK37WecS6QARAQAB
+tDdFY2xpcHNlIHAyIHRlc3QgU2lnbmVyIDIgPHNpZ25lcjJAZmFrZXVzZXIuZWNs
+aXBzZS5vcmc+iQJYBBMBCABCFiEEzZ1aK4a8T+GDlFHh4vaU9BsKs3AFAmHUyxYC
+GwMFCQPCZwAFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4HAheAAAoJEOL2lPQbCrNw
+wH4QAIiCaw1mREgt4ldz7hQvmGxdMuQwVKZPzbOIAlYbZBo0q9SmeMf/CBCO90hg
+LFmJmsZV4KUU5NKI7UwkDVrpUCl00Ok6+gtiUTId2tRcwXI+25I478VX27j6OkQj
+7Xr6giv8cn8nstt5CF6xxeqrxvpmnZC0u30jE6CL6SdXSd0vViFDPQj3KgGJCRc9
+St+LHB3XJTsadihzQnscqI4E2i5Z3Uj1GogqxtR59M1NAXubl5dySM0qHhwn8O+6
+lcgCCeuyMLLde1M1v8w07jdRUM+IFqHrRnE89EPH3MQeZbQ3UfFXK2r7wx2BJCqL
+Irtn68kz834ByKchGR6DqaAw0q+iF2QkgzYxpwai41BgmtUCYnj+HxQNIF4KTzDe
+nd8mDAPWttGCoVuV2Tyu9peYOaqyAZ2PZwUEH5MqihPCbenU17RLXzRu/IT/SH47
+NGrN3yKGgLZr2EVWPWFibpoxP4G4NUCHsY75uiL2EWPVSjK/+OOeHIE5k3U3lYwB
+7clhBwMkIhQHJ+a0SHRkKixkwrQDw4veKY4LaD0NCBLHFoV5L9orH1ToGM729kr/
++4I1VQFkL3KvfLjmRbTUgwHeqEquQ96JtqowbNwlpujfHXQKDNsuiKGP5OazXll5
+sH2CR7e4ePqhhzxjLvi9e/79Khq+08eqllS3rs06EXEAJYTo
+=807V
+-----END PGP PUBLIC KEY BLOCK-----