Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMickael Istria2021-04-18 07:21:11 +0000
committerMickael Istria2021-04-18 16:36:23 +0000
commitb577f0cf8929a45bc95526fbeb2c25b08177b320 (patch)
tree783e3fb9ba831d99d66a2f289461f21e3a08aeb4 /bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java
parent6b5269b19fefdfc34c819212c5fe6c0f838e349f (diff)
downloadrt.equinox.p2-b577f0cf8929a45bc95526fbeb2c25b08177b320.tar.gz
rt.equinox.p2-b577f0cf8929a45bc95526fbeb2c25b08177b320.tar.xz
rt.equinox.p2-b577f0cf8929a45bc95526fbeb2c25b08177b320.zip
Revert "Revert "Bug 570907 - A processingStep to verify PGP signatures
in artifact"" This reverts commit 6b5269b19fefdfc34c819212c5fe6c0f838e349f. This adds the necessary bouncycastle bundles to the p2 feature Change-Id: I4b5f8f761e4e683d35fef2ea0ce00bc3d3b34fa2 Reviewed-on: https://git.eclipse.org/r/c/equinox/rt.equinox.p2/+/179465 Tested-by: Mickael Istria <mistria@redhat.com> Reviewed-by: Mickael Istria <mistria@redhat.com>
Diffstat (limited to 'bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java')
-rw-r--r--bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java203
1 files changed, 203 insertions, 0 deletions
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
new file mode 100644
index 000000000..e81fc0e43
--- /dev/null
+++ b/bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java
@@ -0,0 +1,203 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.equinox.internal.p2.artifact.processors.pgp;
+
+import java.io.*;
+import java.util.*;
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openpgp.*;
+import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.bouncycastle.openpgp.jcajce.JcaPGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
+import org.eclipse.core.runtime.*;
+import org.eclipse.equinox.internal.p2.artifact.repository.Activator;
+import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
+import org.eclipse.equinox.internal.provisional.p2.artifact.repository.processing.ProcessingStep;
+import org.eclipse.equinox.p2.core.IProvisioningAgent;
+import org.eclipse.equinox.p2.repository.artifact.*;
+import org.eclipse.osgi.util.NLS;
+
+/**
+ * This processing step verifies PGP signatures are correct (ie artifact was not
+ * tampered during fetch). Note that is does <b>not</b> deal with trust. Dealing
+ * with trusted signers is done as part of CheckTrust touchpoint and phase.
+ */
+public final class PGPSignatureVerifier extends ProcessingStep {
+
+ /**
+ * ID of the registering
+ * <code>org.eclipse.equinox.p2.artifact.repository.processingSteps</tt>
+ * extension.
+ */
+ public static final String ID = "org.eclipse.equinox.p2.processing.PGPSignatureCheck"; //$NON-NLS-1$
+
+ 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;
+
+ 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));
+ if (signatureText == null) {
+ setStatus(Status.OK_STATUS);
+ return;
+ }
+ signaturesToVerify = new ArrayList<>();
+ try (InputStream in = new ArmoredInputStream(new ByteArrayInputStream(signatureText.getBytes()))) {
+ JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(in);
+ Object o = pgpFactory.nextObject();
+ PGPSignatureList signatureList;
+ 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);
+ } catch (Exception ex) {
+ setStatus(new Status(IStatus.ERROR, Activator.ID, Messages.Error_CouldNotLoadSignature, ex));
+ return;
+ }
+ if (signaturesToVerify.isEmpty()) {
+ setStatus(Status.OK_STATUS);
+ return;
+ }
+
+ 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);
+ for (PGPSignature signature : signaturesToVerify) {
+ PGPPublicKey publicKey = signerKeys.get(signature.getKeyID());
+ if (publicKey == null) {
+ setStatus(new Status(IStatus.ERROR, Activator.ID,
+ NLS.bind(Messages.Error_publicKeyNotFound, signature.getKeyID())));
+ return;
+ }
+ try {
+ signature.init(new JcaPGPContentVerifierBuilderProvider().setProvider(new BouncyCastleProvider()),
+ publicKey);
+ } catch (PGPException ex) {
+ setStatus(new Status(IStatus.ERROR, Activator.ID, ex.getMessage(), ex));
+ return;
+ }
+ }
+ }
+
+ /**
+ * See // https://www.w3.org/TR/1998/REC-xml-19980210#AVNormalize, newlines
+ * replaced by spaces by parser, needs to be restored
+ *
+ * @param context
+ * @param pgpSignaturesPropertyName
+ * @return fixed PGP armored blocks
+ */
+ private String unnormalizedPGPProperty(String value) {
+ if (value == null) {
+ return null;
+ }
+ return value.replace(' ', '\n').replace("-----BEGIN\nPGP\nSIGNATURE-----", "-----BEGIN PGP SIGNATURE-----") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("-----END\nPGP\nSIGNATURE-----", "-----END PGP SIGNATURE-----") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("-----BEGIN\nPGP\nPUBLIC\nKEY\nBLOCK-----", "-----BEGIN PGP PUBLIC KEY BLOCK-----") //$NON-NLS-1$ //$NON-NLS-2$
+ .replace("-----END\nPGP\nPUBLIC\nKEY\nBLOCK-----", "-----END PGP PUBLIC KEY BLOCK-----"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private Map<Long, PGPPublicKey> readPublicKeys(String armoredPublicKeyring) {
+ if (armoredPublicKeyring == null) {
+ return Collections.emptyMap();
+ }
+ Map<Long, PGPPublicKey> res = new HashMap<>();
+ try (InputStream stream = PGPUtil
+ .getDecoderStream(new ByteArrayInputStream(unnormalizedPGPProperty(armoredPublicKeyring).getBytes()))) {
+ PGPPublicKeyRingCollection pgpPub = new JcaPGPPublicKeyRingCollection(stream);
+
+ pgpPub.getKeyRings().forEachRemaining(kRing ->
+ kRing.getPublicKeys().forEachRemaining(key -> res.put(key.getKeyID(), key))
+ );
+ } catch (IOException | PGPException e) {
+ LogHelper.log(new Status(IStatus.ERROR, Activator.ID, e.getMessage(), e));
+ }
+ return res;
+
+ }
+
+ private Map<Long, PGPPublicKey> readPublicKeys(String... armoredPublicKeys) {
+ Map<Long, PGPPublicKey> keys = new HashMap<>();
+ for (String armoredKey : armoredPublicKeys) {
+ if (armoredKey != null) {
+ keys.putAll(readPublicKeys(armoredKey));
+ }
+ }
+ return keys;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ if (signaturesToVerify != null) {
+ signaturesToVerify.iterator().forEachRemaining(signature -> signature.update((byte) b));
+ }
+
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ getDestination().write(b);
+ if (signaturesToVerify != null) {
+ signaturesToVerify.iterator().forEachRemaining(signature -> signature.update(b));
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ getDestination().write(b, off, len);
+ if (signaturesToVerify != null) {
+ signaturesToVerify.iterator().forEachRemaining(signature -> signature.update(b, off, len));
+ }
+ }
+
+ @Override
+ public void close() {
+ if (!getStatus().isOK()) {
+ return;
+ }
+ if (signaturesToVerify == null || signaturesToVerify.isEmpty()) {
+ return;
+ }
+ Iterator<PGPSignature> iterator = signaturesToVerify.iterator();
+ while (iterator.hasNext()) {
+ PGPSignature signature = iterator.next();
+ try {
+ if (!signature.verify()) {
+ setStatus(new Status(IStatus.ERROR, Activator.ID, Messages.Error_SignatureAndFileDontMatch));
+ return;
+ }
+ } catch (PGPException ex) {
+ setStatus(new Status(IStatus.ERROR, Activator.ID, ex.getMessage(), ex));
+ return;
+ }
+ }
+ setStatus(Status.OK_STATUS);
+ }
+}

Back to the top