diff --git a/core/pom.xml b/core/pom.xml index 9f0d690..ca1aa77 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -13,12 +13,8 @@ - org.bouncycastle - bcprov-jdk18on - - - org.bouncycastle - bcpg-jdk18on + org.pgpainless + pgpainless-core diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/PgpHelper.java b/core/src/main/java/org/eclipse/packager/security/pgp/PgpHelper.java index b7169e0..15f6f53 100644 --- a/core/src/main/java/org/eclipse/packager/security/pgp/PgpHelper.java +++ b/core/src/main/java/org/eclipse/packager/security/pgp/PgpHelper.java @@ -33,6 +33,10 @@ import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRingCollection; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.pgpainless.PGPainless; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.util.KeyIdUtil; +import org.pgpainless.util.Passphrase; public final class PgpHelper { private PgpHelper() { @@ -133,4 +137,23 @@ public static PGPSecretKey loadSecretKey(final InputStream input, final String k return null; } + + public static PGPSecretKeyRing loadSecretKeyRing(InputStream inputStream) + throws IOException { + return PGPainless.readKeyRing().secretKeyRing(inputStream); + } + + public static long parseKeyId(String keyId) { + if (keyId == null) { + return 0L; + } + return KeyIdUtil.fromLongKeyId(keyId); + } + + public static SecretKeyRingProtector protectorFromPassword(String password) { + if (password == null || password.isEmpty()) { + return SecretKeyRingProtector.unprotectedKeys(); + } + return SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(password)); + } } diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/SigningStream.java b/core/src/main/java/org/eclipse/packager/security/pgp/SigningStream.java index 825fe22..7655f13 100644 --- a/core/src/main/java/org/eclipse/packager/security/pgp/SigningStream.java +++ b/core/src/main/java/org/eclipse/packager/security/pgp/SigningStream.java @@ -26,6 +26,12 @@ import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; +/** + * Signing Stream. + * + * @deprecated use {@link SigningStream2} instead. + */ +@Deprecated public class SigningStream extends OutputStream { private final OutputStream stream; diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/SigningStream2.java b/core/src/main/java/org/eclipse/packager/security/pgp/SigningStream2.java new file mode 100644 index 0000000..bb6c08a --- /dev/null +++ b/core/src/main/java/org/eclipse/packager/security/pgp/SigningStream2.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2015, 2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.packager.security.pgp; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Objects; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.DocumentSignatureType; +import org.pgpainless.encryption_signing.EncryptionResult; +import org.pgpainless.encryption_signing.EncryptionStream; +import org.pgpainless.encryption_signing.ProducerOptions; +import org.pgpainless.encryption_signing.SigningOptions; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.ArmoredOutputStreamFactory; + +public class SigningStream2 extends OutputStream { + private final OutputStream stream; + + private final PGPSecretKeyRing secretKeys; + + private final SecretKeyRingProtector protector; + + private final long keyId; + + private final boolean inline; + + private boolean initialized; + + private EncryptionStream signingStream; + + /** + * Create a new signing stream + * + * @param stream the actual output stream + * @param secretKeys the signing key ring + * @param protector protector to unlock the signing key + * @param inline whether to sign inline or just write the signature + * @param version the optional version which will be in the signature comment + */ + public SigningStream2(final OutputStream stream, + final PGPSecretKeyRing secretKeys, + final SecretKeyRingProtector protector, + final long keyId, + final boolean inline, + final String version) { + this.stream = stream; + this.secretKeys = secretKeys; + this.protector = protector; + this.keyId = keyId; + this.inline = inline; + + ArmoredOutputStreamFactory.setVersionInfo(version); + } + + /** + * Create a new signing stream + * + * @param stream the actual output stream + * @param secretKeys the signing key ring + * @param protector protector to unlock the signing key + * @param inline whether to sign inline or just write the signature + */ + public SigningStream2(final OutputStream stream, + final PGPSecretKeyRing secretKeys, + final SecretKeyRingProtector protector, + final long keyId, + final boolean inline) { + this(stream, secretKeys, protector, keyId, inline, null); + } + + protected void testInit() throws IOException { + if (this.initialized) { + return; + } + + this.initialized = true; + + try { + long signingKeyId = keyId; + if (signingKeyId == 0) { + List signingKeys = PGPainless.inspectKeyRing(secretKeys).getSigningSubkeys(); + if (signingKeys.isEmpty()) { + throw new PGPException("No available signing subkey found."); + } + // Sign with first signing subkey found + signingKeyId = signingKeys.get(0).getKeyID(); + } + + if (inline) { + + SigningOptions signingOptions = SigningOptions.get(); + if (keyId != 0) { + signingOptions.addInlineSignature(protector, secretKeys, signingKeyId); + } else { + signingOptions.addInlineSignature(protector, secretKeys, DocumentSignatureType.BINARY_DOCUMENT); + } + ProducerOptions producerOptions = ProducerOptions.sign(signingOptions) + .setCleartextSigned(); + + signingStream = PGPainless.encryptAndOrSign() + .onOutputStream(stream) // write data and sig to the output stream + .withOptions(producerOptions); + + } else { + + SigningOptions signingOptions = SigningOptions.get(); + if (keyId != 0) { + signingOptions.addDetachedSignature(protector, secretKeys, keyId); + } else { + signingOptions.addDetachedSignature(protector, secretKeys, DocumentSignatureType.BINARY_DOCUMENT); + } + ProducerOptions producerOptions = ProducerOptions.sign(signingOptions); + + signingStream = PGPainless.encryptAndOrSign() + .onOutputStream( + // do not output the plaintext data, just emit the signature in close() + new OutputStream() { + @Override + public void write(int i) throws IOException { + // Ignore data + } + }) + .withOptions(producerOptions); + } + } catch (final PGPException e) { + throw new IOException(e); + } + } + + @Override + public void write(final int b) throws IOException { + write(new byte[] { (byte) b }); + } + + @Override + public void write(final byte[] b, final int off, final int len) throws IOException { + Objects.requireNonNull(b); + + testInit(); + + signingStream.write(b, off, len); + } + + @Override + public void close() throws IOException { + testInit(); + + signingStream.close(); + + if (this.inline) { + return; + } + + EncryptionResult result = signingStream.getResult(); + final PGPSignature signature = result.getDetachedSignatures().flatten().iterator().next(); + ArmoredOutputStream armoredOutput = ArmoredOutputStreamFactory.get(stream); + signature.encode(new BCPGOutputStream(armoredOutput)); + armoredOutput.close(); + + super.close(); + } +} diff --git a/pom.xml b/pom.xml index 15c61ea..50cd9c9 100644 --- a/pom.xml +++ b/pom.xml @@ -118,16 +118,10 @@ - - - org.bouncycastle - bcprov-jdk18on - ${bouncycastle.version} - - org.bouncycastle - bcpg-jdk18on - ${bouncycastle.version} + org.pgpainless + pgpainless-core + 1.6.7 diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpHeaderSignatureProcessor.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpHeaderSignatureProcessor.java new file mode 100644 index 0000000..54730ff --- /dev/null +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpHeaderSignatureProcessor.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.packager.rpm.signature; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.eclipse.packager.rpm.RpmSignatureTag; +import org.eclipse.packager.rpm.header.Header; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.DocumentSignatureType; +import org.pgpainless.encryption_signing.EncryptionResult; +import org.pgpainless.encryption_signing.EncryptionStream; +import org.pgpainless.encryption_signing.ProducerOptions; +import org.pgpainless.encryption_signing.SigningOptions; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An RSA signature processor for the header section only. + */ +public class PgpHeaderSignatureProcessor implements SignatureProcessor { + private final static Logger logger = LoggerFactory.getLogger(PgpHeaderSignatureProcessor.class); + + private final PGPSecretKeyRing secretKeys; + + private final SecretKeyRingProtector protector; + + private final long keyId; + + private PGPSignature signature; + + private byte[] value; + + public PgpHeaderSignatureProcessor(final PGPSecretKeyRing secretKeys, + final SecretKeyRingProtector protector, + final long keyId) { + this.secretKeys = Objects.requireNonNull(secretKeys); + this.protector = Objects.requireNonNull(protector); + this.keyId = keyId; + } + + public PgpHeaderSignatureProcessor(final PGPSecretKeyRing secretKeys, SecretKeyRingProtector protector) { + this(secretKeys, protector, 0); + } + + public PgpHeaderSignatureProcessor(final PGPSecretKeyRing secretKeys) { + this(secretKeys, SecretKeyRingProtector.unprotectedKeys()); + } + + @Override + public void feedHeader(final ByteBuffer header) { + try { + OutputStream sink = new OutputStream() { + @Override + public void write(int i) throws IOException { + // ignore "ciphertext" + } + }; + SigningOptions signingOptions = SigningOptions.get(); + if (keyId == 0) { + signingOptions.addDetachedSignature(protector, secretKeys, DocumentSignatureType.BINARY_DOCUMENT); + } else { + signingOptions.addDetachedSignature(protector, secretKeys, keyId); + } + EncryptionStream signingStream = PGPainless.encryptAndOrSign() + .onOutputStream(sink) + .withOptions(ProducerOptions.sign(signingOptions)); + + if (header.hasArray()) { + signingStream.write(header.array(), header.position(), header.remaining()); + } else { + final byte[] buffer = new byte[header.remaining()]; + header.get(buffer); + signingStream.write(buffer); + } + + signingStream.close(); + EncryptionResult result = signingStream.getResult(); + + this.signature = result.getDetachedSignatures().flatten().iterator().next(); + this.value = signature.getEncoded(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void feedPayloadData(final ByteBuffer data) { + // we only work on the header data + } + + @Override + public void finish(final Header signature) { + switch (this.signature.getKeyAlgorithm()) { + // RSA + case PublicKeyAlgorithmTags.RSA_GENERAL: // 1 + logger.info("RSA HEADER: {}", this.value); + signature.putBlob(RpmSignatureTag.RSAHEADER, this.value); + break; + + // DSA + case PublicKeyAlgorithmTags.DSA: // 17 + case PublicKeyAlgorithmTags.EDDSA_LEGACY: // 22 + logger.info("DSA HEADER: {}", this.value); + signature.putBlob(RpmSignatureTag.DSAHEADER, this.value); + break; + + default: + throw new RuntimeException("Unsupported public key algorithm id: " + this.signature.getKeyAlgorithm()); + } + + } +} diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessor.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessor.java new file mode 100644 index 0000000..dc140d6 --- /dev/null +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessor.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.eclipse.packager.rpm.signature; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.eclipse.packager.rpm.RpmSignatureTag; +import org.eclipse.packager.rpm.header.Header; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.DocumentSignatureType; +import org.pgpainless.encryption_signing.EncryptionResult; +import org.pgpainless.encryption_signing.EncryptionStream; +import org.pgpainless.encryption_signing.ProducerOptions; +import org.pgpainless.encryption_signing.SigningOptions; +import org.pgpainless.key.protection.SecretKeyRingProtector; + +public class PgpSignatureProcessor implements SignatureProcessor { + + private final EncryptionStream signingStream; + + /** + * Signature Processor using a non-protected signing key. + * + * @param signingKey signing key + */ + public PgpSignatureProcessor(final PGPSecretKeyRing signingKey) { + this(signingKey, SecretKeyRingProtector.unprotectedKeys()); + } + + /** + * Signature Processor using a PGP signing key that can be unlocked by the + * protector. + * + * @param signingKey signing key + * @param protector protector to unlock the signing key + */ + public PgpSignatureProcessor(final PGPSecretKeyRing signingKey, final SecretKeyRingProtector protector) { + this(signingKey, protector, 0); + } + + /** + * Signature Processor using a PGP signing key that can be unlocked by the + * protector. + * The signing key to use is determined by the given key-id. If the id is 0, the + * signing subkey is auto-detected. + * + * @param signingKey signing key + * @param protector protector to unlock the signing key + * @param keyId id of the signing subkey (or 0 for autodetect) + */ + public PgpSignatureProcessor(final PGPSecretKeyRing signingKey, final SecretKeyRingProtector protector, long keyId) { + OutputStream sink = new OutputStream() { + @Override + public void write(int i) throws IOException { + // get rid of the "ciphertext" + } + }; + + try { + SigningOptions signingOptions = SigningOptions.get(); + if (keyId != 0) { + signingOptions.addDetachedSignature(protector, signingKey, keyId); + } else { + signingOptions.addDetachedSignature(protector, signingKey, DocumentSignatureType.BINARY_DOCUMENT); + } + this.signingStream = PGPainless.encryptAndOrSign() + .onOutputStream(sink) + .withOptions(ProducerOptions.sign(signingOptions)); + } catch (PGPException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void feedHeader(ByteBuffer header) { + feedData(header); + } + + @Override + public void feedPayloadData(ByteBuffer data) { + feedData(data); + } + + private void feedData(ByteBuffer data) { + try { + if (data.hasArray()) { + signingStream.write(data.array(), data.position(), data.remaining()); + } else { + final byte[] buffer = new byte[data.remaining()]; + data.get(buffer); + signingStream.write(buffer); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void finish(Header signature) { + try { + signingStream.close(); + EncryptionResult result = signingStream.getResult(); + PGPSignature sig = result.getDetachedSignatures().flatten().iterator().next(); + signature.putBlob(RpmSignatureTag.PGP, sig.getEncoded()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessor.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessor.java index c7443cc..073db5a 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessor.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessor.java @@ -8,7 +8,7 @@ * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * mat1e, Groupe EDF - initial API and implementation + * mat1e, Groupe EDF - initial API and implementation ********************************************************************************/ package org.eclipse.packager.rpm.signature; @@ -24,15 +24,7 @@ import java.util.List; import org.apache.commons.io.IOUtils; -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; -import org.eclipse.packager.rpm.HashAlgorithm; import org.eclipse.packager.rpm.RpmSignatureTag; import org.eclipse.packager.rpm.Rpms; import org.eclipse.packager.rpm.header.Header; @@ -40,10 +32,12 @@ import org.eclipse.packager.rpm.info.RpmInformation; import org.eclipse.packager.rpm.info.RpmInformations; import org.eclipse.packager.rpm.parse.RpmInputStream; +import org.eclipse.packager.security.pgp.PgpHelper; +import org.pgpainless.key.protection.SecretKeyRingProtector; /** * Sign existing RPM file by calling - * {@link #perform(File, InputStream, String, OutputStream, HashAlgorithm)} + * {@link #perform(File, InputStream, String, OutputStream)} */ public class RpmFileSignatureProcessor { @@ -62,10 +56,9 @@ private RpmFileSignatureProcessor() { * @param passphrase : passphrase to decrypt the private key * @param out : {@link OutputStream} to write to * @throws IOException - * @throws PGPException */ - public static void perform(File rpm, InputStream privateKeyIn, String passphrase, OutputStream out, HashAlgorithm hashAlgorithm) - throws IOException, PGPException { + public static void perform(File rpm, InputStream privateKeyIn, String passphrase, OutputStream out) + throws IOException { final long leadLength = 96; long signatureHeaderStart = 0L; @@ -81,8 +74,8 @@ public static void perform(File rpm, InputStream privateKeyIn, String passphrase throw new IOException("The file " + rpm.getName() + " does not exist"); } - // Extract private key - PGPPrivateKey privateKey = getPrivateKey(privateKeyIn, passphrase); + PGPSecretKeyRing secretKeys = PgpHelper.loadSecretKeyRing(privateKeyIn); + SecretKeyRingProtector protector = PgpHelper.protectorFromPassword(passphrase); // Get the information of the RPM try (RpmInputStream rpmIn = new RpmInputStream(new FileInputStream(rpm))) { @@ -96,7 +89,7 @@ public static void perform(File rpm, InputStream privateKeyIn, String passphrase } if (signatureHeaderStart == 0L || signatureHeaderLength == 0L || payloadHeaderStart == 0L - || payloadHeaderLength == 0L || payloadStart == 0L || archiveSize == 0L) { + || payloadHeaderLength == 0L || payloadStart == 0L || archiveSize == 0L) { throw new IOException("Unable to read " + rpm.getName() + " informations."); } @@ -109,7 +102,7 @@ public static void perform(File rpm, InputStream privateKeyIn, String passphrase IOUtils.readFully(channelIn, payloadHeaderBuff); ByteBuffer payloadBuff = ByteBuffer.allocate((int) payloadSize); IOUtils.readFully(channelIn, payloadBuff); - signatureHeader = getSignature(privateKey, payloadHeaderBuff, payloadBuff, archiveSize, hashAlgorithm); + signatureHeader = getSignature(secretKeys, protector, payloadHeaderBuff, payloadBuff, archiveSize); } // Write to the OutputStream @@ -127,18 +120,18 @@ public static void perform(File rpm, InputStream privateKeyIn, String passphrase * "https://rpm-software-management.github.io/rpm/manual/format.html">https://rpm-software-management.github.io/rpm/manual/format.html *

* - * @param privateKey : private key already extracted + * @param secretKeys signing key + * @param protector signing key protector * @param payloadHeader : Payload's header as {@link ByteBuffer} * @param payload : Payload as {@link ByteBuffer} * @param archiveSize : archiveSize retrieved in {@link RpmInformation} - * @param hashAlgorithm * @return the signature header as a bytes array * @throws IOException */ - private static byte[] getSignature(PGPPrivateKey privateKey, ByteBuffer payloadHeader, ByteBuffer payload, - long archiveSize, HashAlgorithm hashAlgorithm) throws IOException { + private static byte[] getSignature(PGPSecretKeyRing secretKeys, SecretKeyRingProtector protector, ByteBuffer payloadHeader, ByteBuffer payload, + long archiveSize) throws IOException { Header signatureHeader = new Header<>(); - List signatureProcessors = getSignatureProcessors(privateKey, hashAlgorithm); + List signatureProcessors = getSignatureProcessors(secretKeys, protector); payloadHeader.flip(); payload.flip(); for (SignatureProcessor processor : signatureProcessors) { @@ -183,37 +176,18 @@ private static byte[] safeReadBuffer(ByteBuffer buf) throws IOException { * {@link SignatureProcessors} *

* - * @param privateKey : the private key, already extracted + * @param secretKeys signing secret key + * @param protector protector to unlock the secret key * @return {@link List} of {@link SignatureProcessor} */ - private static List getSignatureProcessors(PGPPrivateKey privateKey, HashAlgorithm hashAlgorithm) { + private static List getSignatureProcessors(PGPSecretKeyRing secretKeys, SecretKeyRingProtector protector) { List signatureProcessors = new ArrayList<>(); signatureProcessors.add(SignatureProcessors.size()); signatureProcessors.add(SignatureProcessors.sha256Header()); signatureProcessors.add(SignatureProcessors.sha1Header()); signatureProcessors.add(SignatureProcessors.md5()); signatureProcessors.add(SignatureProcessors.payloadSize()); - signatureProcessors.add(new RsaSignatureProcessor(privateKey, hashAlgorithm)); + signatureProcessors.add(new PgpSignatureProcessor(secretKeys, protector)); return signatureProcessors; } - - /** - *

- * Decrypt and retrieve the private key - *

- * - * @param privateKeyIn : InputStream containing the encrypted private key - * @param passphrase : passphrase to decrypt private key - * @return private key as {@link PGPPrivateKey} - * @throws PGPException : if the private key cannot be extrated - * @throws IOException : if error happened with InputStream - */ - private static PGPPrivateKey getPrivateKey(InputStream privateKeyIn, String passphrase) - throws PGPException, IOException { - ArmoredInputStream armor = new ArmoredInputStream(privateKeyIn); - PGPSecretKeyRing secretKeyRing = new BcPGPSecretKeyRing(armor); - PGPSecretKey secretKey = secretKeyRing.getSecretKey(); - return secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) - .build(passphrase.toCharArray())); - } } diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/RsaHeaderSignatureProcessor.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/RsaHeaderSignatureProcessor.java index 7bd2b26..c8c104b 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/signature/RsaHeaderSignatureProcessor.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/RsaHeaderSignatureProcessor.java @@ -29,7 +29,10 @@ /** * An RSA signature processor for the header section only. + * + * @deprecated use {@link PgpHeaderSignatureProcessor} instead. */ +@Deprecated public class RsaHeaderSignatureProcessor implements SignatureProcessor { private final static Logger logger = LoggerFactory.getLogger(RsaHeaderSignatureProcessor.class); @@ -50,7 +53,7 @@ public RsaHeaderSignatureProcessor(final PGPPrivateKey privateKey, final HashAlg } public RsaHeaderSignatureProcessor(final PGPPrivateKey privateKey) { - this(privateKey, HashAlgorithmTags.SHA1); + this(privateKey, HashAlgorithmTags.SHA256); } @Override diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/RsaSignatureProcessor.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/RsaSignatureProcessor.java index 91f4335..1ba88b9 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/signature/RsaSignatureProcessor.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/RsaSignatureProcessor.java @@ -29,7 +29,10 @@ /** * An RSA signature processor for both header and payload. + * + * @deprecated use {@link PgpSignatureProcessor} instead. */ +@Deprecated public class RsaSignatureProcessor implements SignatureProcessor { private final static Logger logger = LoggerFactory.getLogger(RsaSignatureProcessor.class); @@ -53,7 +56,7 @@ public RsaSignatureProcessor(final PGPPrivateKey privateKey, final HashAlgorithm } public RsaSignatureProcessor(final PGPPrivateKey privateKey) { - this(privateKey, HashAlgorithmTags.SHA1); + this(privateKey, HashAlgorithmTags.SHA256); } @Override diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/yum/RepositoryCreator.java b/rpm/src/main/java/org/eclipse/packager/rpm/yum/RepositoryCreator.java index 6bb9600..50015ae 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/yum/RepositoryCreator.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/yum/RepositoryCreator.java @@ -14,6 +14,7 @@ package org.eclipse.packager.rpm.yum; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.time.Instant; import java.util.Arrays; @@ -41,6 +42,7 @@ import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.eclipse.packager.io.IOConsumer; import org.eclipse.packager.io.OutputSpooler; import org.eclipse.packager.io.SpoolOutTarget; @@ -50,7 +52,10 @@ import org.eclipse.packager.rpm.info.RpmInformation; import org.eclipse.packager.rpm.info.RpmInformation.Changelog; import org.eclipse.packager.rpm.info.RpmInformation.Dependency; +import org.eclipse.packager.security.pgp.PgpHelper; import org.eclipse.packager.security.pgp.SigningStream; +import org.eclipse.packager.security.pgp.SigningStream2; +import org.pgpainless.key.protection.SecretKeyRingProtector; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -434,19 +439,41 @@ public Builder setXmlContext(final XmlContext xmlContext) { return this; } - public Builder setSigning(final Function signingStreamCreator) { - this.signingStreamCreator = signingStreamCreator; - return this; - } - + /** + * Enable signing using an (unprotected) PGP private key. + * + * @param privateKey unlocked PGP private key + * @return builder + * @deprecated use {@link #setSigning(PGPSecretKeyRing)} instead. + */ + @Deprecated public Builder setSigning(final PGPPrivateKey privateKey) { - return setSigning(privateKey, HashAlgorithmTags.SHA1); + return setSigning(privateKey, HashAlgorithmTags.SHA256); } + /** + * Enable signing using a PGP private key using the given digest algorithm. + * + * @param privateKey unlocked PGP private key + * @param hashAlgorithm digest algorithm to be used in the signature + * @return builder + * @deprecated use {@link #setSigning(PGPSecretKeyRing)} instead. + */ + @Deprecated public Builder setSigning(final PGPPrivateKey privateKey, final HashAlgorithm hashAlgorithm) { return setSigning(privateKey, hashAlgorithm.getValue()); } + /** + * Enable signing using a PGP private key, using the given digest algorithm. + * + * @param privateKey unlocked PGP private key + * @param digestAlgorithm digest algorithm to be used in the signature + * @return builder + * @deprecated use {@link #setSigning(PGPSecretKeyRing, SecretKeyRingProtector)} + * instead. + */ + @Deprecated public Builder setSigning(final PGPPrivateKey privateKey, final int digestAlgorithm) { if (privateKey != null) { this.signingStreamCreator = output -> new SigningStream(output, privateKey, digestAlgorithm, false); @@ -456,6 +483,100 @@ public Builder setSigning(final PGPPrivateKey privateKey, final int digestAlgori return this; } + /** + * Enable signing using an (unprotected) PGP secret key. + * + * @param secretKeys secret key + * @return builder + */ + public Builder setSigning(final PGPSecretKeyRing secretKeys) { + return setSigning(secretKeys, SecretKeyRingProtector.unprotectedKeys()); + } + + /** + * Enable signing using a PGP secret key. + * + * @param secretKeys secret key + * @param protector protector to unlock the secret key + * @return builder + */ + public Builder setSigning(final PGPSecretKeyRing secretKeys, SecretKeyRingProtector protector) { + return setSigning(secretKeys, protector, 0); + } + + /** + * Enable signing using a PGP secret key. + * + * @param secretKeys secret key + * @param protector protector to unlock the secret key + * @param keyId ID of the signing subkey, or 0 if the signing subkey is + * auto-detected + * @return builder + */ + public Builder setSigning(final PGPSecretKeyRing secretKeys, SecretKeyRingProtector protector, long keyId) { + if (secretKeys != null) { + return setSigning(output -> new SigningStream2(output, secretKeys, protector, keyId, false)); + } else { + this.signingStreamCreator = null; + } + return this; + } + + /** + * Enable signing using a PGP secret key. + * + * @param keyInputStream input stream containing the (unprotected) secret key. + * @return builder + * @throws IOException if the key cannot be parsed + */ + public Builder setSigning(final InputStream keyInputStream) + throws IOException { + return setSigning(keyInputStream, SecretKeyRingProtector.unprotectedKeys()); + } + + /** + * Enable signing using a PGP secret key. + * + * @param keyInputStream input stream containing the secret key. + * @param keyProtector protector to unlock the secret key. + * @return builder + * @throws IOException if the key cannot be parsed + */ + public Builder setSigning(final InputStream keyInputStream, + final SecretKeyRingProtector keyProtector) + throws IOException { + return setSigning(keyInputStream, keyProtector, 0); + } + + /** + * Enable signing using a PGP secret key. + * + * @param keyInputStream input stream containing the secret key. + * @param keyProtector protector to unlock the secret key. + * @param keyId ID of the signing subkey, or 0 for auto-detect + * @return builder + * @throws IOException if the key cannot be parsed + */ + public Builder setSigning(final InputStream keyInputStream, + final SecretKeyRingProtector keyProtector, + final long keyId) + throws IOException { + PGPSecretKeyRing secretKeys = PgpHelper.loadSecretKeyRing(keyInputStream); + return setSigning(secretKeys, keyProtector, keyId); + } + + /** + * Apply PGP signing to the data stream. + * + * @param signingStreamCreator function that wraps the output stream into a + * signing stream. + * @return builder + */ + public Builder setSigning(final Function signingStreamCreator) { + this.signingStreamCreator = signingStreamCreator; + return this; + } + public RepositoryCreator build() { return new RepositoryCreator(this.target, this.xmlContext == null ? new DefaultXmlContext() : this.xmlContext, this.signingStreamCreator); } diff --git a/rpm/src/test/java/org/eclipse/packager/rpm/WriterTest.java b/rpm/src/test/java/org/eclipse/packager/rpm/WriterTest.java index cc2977a..6707bb1 100644 --- a/rpm/src/test/java/org/eclipse/packager/rpm/WriterTest.java +++ b/rpm/src/test/java/org/eclipse/packager/rpm/WriterTest.java @@ -41,7 +41,7 @@ import org.eclipse.packager.rpm.deps.RpmDependencyFlags; import org.eclipse.packager.rpm.header.Header; import org.eclipse.packager.rpm.parse.RpmInputStream; -import org.eclipse.packager.rpm.signature.RsaHeaderSignatureProcessor; +import org.eclipse.packager.rpm.signature.PgpHeaderSignatureProcessor; import org.eclipse.packager.security.pgp.PgpHelper; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -194,7 +194,10 @@ public void test3() throws IOException, PGPException { if (keyId != null && keyChain != null) { try (InputStream stream = Files.newInputStream(Paths.get(keyChain))) { - builder.addSignatureProcessor(new RsaHeaderSignatureProcessor(PgpHelper.loadPrivateKey(stream, keyId, keyPassphrase), HashAlgorithm.from(System.getProperty("writerTest.hashAlgo")))); + builder.addSignatureProcessor(new PgpHeaderSignatureProcessor( + PgpHelper.loadSecretKeyRing(stream), + PgpHelper.protectorFromPassword(keyPassphrase), + PgpHelper.parseKeyId(keyId))); } } diff --git a/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java b/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java index a3931cb..36d742c 100644 --- a/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java +++ b/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java @@ -8,8 +8,8 @@ * SPDX-License-Identifier: EPL-2.0 * * Contributors: - * mat1e, Groupe EDF - initial API and implementation - * Jens Reimann, Red Hat, Inc + * mat1e, Groupe EDF - initial API and implementation + * Jens Reimann, Red Hat, Inc ********************************************************************************/ package org.eclipse.packager.rpm.signature; @@ -28,7 +28,6 @@ import java.util.Optional; import org.bouncycastle.openpgp.PGPException; -import org.eclipse.packager.rpm.HashAlgorithm; import org.eclipse.packager.rpm.RpmSignatureTag; import org.eclipse.packager.rpm.Rpms; import org.eclipse.packager.rpm.parse.InputHeader; @@ -74,9 +73,9 @@ public void testSigningExistingRpm() throws IOException, PGPException { signedRpm.createNewFile(); try (FileOutputStream resultOut = new FileOutputStream(signedRpm); - InputStream privateKeyStream = new FileInputStream(private_key)) { + InputStream privateKeyStream = new FileInputStream(private_key)) { // Sign the RPM - RpmFileSignatureProcessor.perform(rpm, privateKeyStream, passPhrase, resultOut, HashAlgorithm.SHA256); + RpmFileSignatureProcessor.perform(rpm, privateKeyStream, passPhrase, resultOut); // Read the initial (unsigned) rpm file RpmInputStream initialRpm = new RpmInputStream(new FileInputStream(rpm)); @@ -130,7 +129,8 @@ public void verifyRpmSignature() throws Exception { String publicKeyName = publicKey.getName(); String rpmFileName = signedRpm.getName(); - // prepare the script for validating the signature, this includes importing the key and running a verbose check + // prepare the script for validating the signature, this includes importing the + // key and running a verbose check String script = String.format("rpm --import /%s && rpm --verbose --checksig /%s", publicKeyName, rpmFileName); // SElinux labeling @@ -142,14 +142,14 @@ public void verifyRpmSignature() throws Exception { } }); - - // create the actual command, which we run inside a container, to not mess up the host systems RPM configuration and + // create the actual command, which we run inside a container, to not mess up + // the host systems RPM configuration and // because this gives us a predictable RPM version. String[] command = new String[] { - CONTAINER, "run", "-tiq", "--rm", - "-v", publicKey + ":/" + publicKeyName + mountSuffix, - "-v", signedRpm + ":/" + rpmFileName + mountSuffix, - "registry.access.redhat.com/ubi9/ubi-minimal:latest", "bash", "-c", script + CONTAINER, "run", "-tiq", "--rm", + "-v", publicKey + ":/" + publicKeyName + mountSuffix, + "-v", signedRpm + ":/" + rpmFileName + mountSuffix, + "registry.access.redhat.com/ubi9/ubi-minimal:latest", "bash", "-c", script }; // dump command for local testing @@ -176,7 +176,7 @@ private static void dumpCommand(String[] command) { private static String run(String... command) throws IOException, InterruptedException { Process process = new ProcessBuilder(command) - .start(); + .start(); String stdout = new String(ByteStreams.toByteArray(process.getInputStream())); process.waitFor(); return stdout;