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-----