diff options
author | Mickael Istria | 2021-04-18 07:21:11 +0000 |
---|---|---|
committer | Mickael Istria | 2021-04-18 16:36:23 +0000 |
commit | b577f0cf8929a45bc95526fbeb2c25b08177b320 (patch) | |
tree | 783e3fb9ba831d99d66a2f289461f21e3a08aeb4 /bundles/org.eclipse.equinox.p2.artifact.repository/src/org/eclipse/equinox/internal/p2/artifact/processors/pgp/PGPSignatureVerifier.java | |
parent | 6b5269b19fefdfc34c819212c5fe6c0f838e349f (diff) | |
download | rt.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.java | 203 |
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); + } +} |