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;