From d81bbd759326dd3913629ea7040142aec69ba3d6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 2 Apr 2024 16:56:29 +0200 Subject: [PATCH 01/16] Introduce PgpSignerFactory This factory can be used to create signing streams --- .../security/pgp/PgpSignerFactory.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerFactory.java diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerFactory.java b/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerFactory.java new file mode 100644 index 0000000..a7d247c --- /dev/null +++ b/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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.OutputStream; +import java.util.function.Function; + +/** + * Factory for signing streams. + */ +public abstract class PgpSignerFactory { + + protected final boolean inlineSigned; + + public PgpSignerFactory(boolean inlineSigned) { + this.inlineSigned = inlineSigned; + } + + /** + * Decide, which hash algorithm to use. + * + * @param hashAlgorithm algorithm ID of the signature digest algorithm + */ + public abstract void setHashAlgorithm(int hashAlgorithm); + + /** + * Return a {@link Function} that wraps an {@link OutputStream} into a signing stream. + * + * @return transforming function + */ + public abstract Function createSigningStream(); +} From ca41eb90d822b82456d6e1b8abceb4100c6f7d75 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Tue, 2 Apr 2024 16:57:35 +0200 Subject: [PATCH 02/16] Implement BC implementation of PgpSignerFactory Use it in RepostoryCreator --- .../security/pgp/BcPgpSignerFactory.java | 50 +++++++++++++++++++ .../packager/rpm/yum/RepositoryCreator.java | 17 ++++--- 2 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerFactory.java diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerFactory.java b/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerFactory.java new file mode 100644 index 0000000..27ea475 --- /dev/null +++ b/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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 org.bouncycastle.openpgp.PGPPrivateKey; + +import java.io.OutputStream; +import java.util.function.Function; + +/** + * Implementation of {@link PgpSignerFactory} that depends on Bouncy Castle directly. + * Here, the user needs to pass in the {@link PGPPrivateKey} and digest algorithm they want to use + * for signing explicitly. + */ +public class BcPgpSignerFactory extends PgpSignerFactory { + + private final PGPPrivateKey privateKey; + private int hashAlgorithm; + + public BcPgpSignerFactory(PGPPrivateKey privateKey, int hashAlgorithmId, boolean inlineSigned) { + super(inlineSigned); + this.setHashAlgorithm(hashAlgorithmId); + this.privateKey = privateKey; + } + + @Override + public void setHashAlgorithm(int hashAlgorithm) { + this.hashAlgorithm = hashAlgorithm; + } + + @Override + public Function createSigningStream() { + if (privateKey == null) { + return null; + } + + return outputStream -> new SigningStream(outputStream, privateKey, hashAlgorithm, inlineSigned); + } +} 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..1621831 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 @@ -50,7 +50,8 @@ 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.SigningStream; +import org.eclipse.packager.security.pgp.BcPgpSignerFactory; +import org.eclipse.packager.security.pgp.PgpSignerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -439,21 +440,23 @@ public Builder setSigning(final Function signingStre return this; } + public Builder setSigning(PgpSignerFactory signingFactory) { + return setSigning(signingFactory.createSigningStream()); + } + + @Deprecated public Builder setSigning(final PGPPrivateKey privateKey) { return setSigning(privateKey, HashAlgorithmTags.SHA1); } + @Deprecated public Builder setSigning(final PGPPrivateKey privateKey, final HashAlgorithm hashAlgorithm) { return setSigning(privateKey, hashAlgorithm.getValue()); } + @Deprecated public Builder setSigning(final PGPPrivateKey privateKey, final int digestAlgorithm) { - if (privateKey != null) { - this.signingStreamCreator = output -> new SigningStream(output, privateKey, digestAlgorithm, false); - } else { - this.signingStreamCreator = null; - } - return this; + return setSigning(new BcPgpSignerFactory(privateKey, digestAlgorithm, false)); } public RepositoryCreator build() { From 17ba07ecd500700ae8f1fffa132f7947d3223d65 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 Apr 2024 21:59:27 +0200 Subject: [PATCH 03/16] Add PgpHelper method to read PGPSecretKeyRings by (sub-)key-id --- .../packager/security/pgp/PgpHelper.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) 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..8934753 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 @@ -133,4 +133,34 @@ public static PGPSecretKey loadSecretKey(final InputStream input, final String k return null; } + + public static PGPSecretKeyRing loadSecretKeyRing(final InputStream input, final String keyId) throws IOException, PGPException { + final long keyIdNum = Long.parseUnsignedLong(keyId, 16); + + final BcPGPSecretKeyRingCollection keyrings = new BcPGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input)); + + final Iterator keyRingIter = keyrings.getKeyRings(); + while (keyRingIter.hasNext()) { + final PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyRingIter.next(); + + final Iterator secretKeyIterator = secretKeyRing.getSecretKeys(); + while (secretKeyIterator.hasNext()) { + final PGPSecretKey key = (PGPSecretKey) secretKeyIterator.next(); + + if (!key.isSigningKey()) { + continue; + } + + final long shortId = key.getKeyID() & 0xFFFFFFFFL; + + if (key.getKeyID() != keyIdNum && shortId != keyIdNum) { + continue; + } + + return secretKeyRing; + } + } + + return null; + } } From 481a5e85bb2d6e0ba3dd3529b838984f2f3c1d6d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 Apr 2024 22:00:18 +0200 Subject: [PATCH 04/16] Add missing GPG RpmSignatureTag --- .../main/java/org/eclipse/packager/rpm/RpmSignatureTag.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java b/rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java index 3ea3338..35dd645 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java @@ -25,8 +25,9 @@ public enum RpmSignatureTag implements RpmBaseTag { SHA256HEADER(273), SIZE(1000), - PGP(1002), + PGP(1002), // RSA MD5(1004), + GPG(1005), // EcDSA and DSA, see https://rpm-software-management.github.io/rpm/manual/format_v4.html PAYLOAD_SIZE(1007), LONGSIZE(5009); From 787fda728c92c6cb42faf789759e3d9b39fc9a98 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 Apr 2024 22:01:15 +0200 Subject: [PATCH 05/16] Add Factory classes for PgpSignerCreator instances --- ...erFactory.java => BcPgpSignerCreator.java} | 26 ++++++---- .../pgp/BcPgpSignerCreatorFactory.java | 50 +++++++++++++++++++ ...gnerFactory.java => PgpSignerCreator.java} | 15 ++---- .../security/pgp/PgpSignerCreatorFactory.java | 32 ++++++++++++ 4 files changed, 102 insertions(+), 21 deletions(-) rename core/src/main/java/org/eclipse/packager/security/pgp/{BcPgpSignerFactory.java => BcPgpSignerCreator.java} (60%) create mode 100644 core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreatorFactory.java rename core/src/main/java/org/eclipse/packager/security/pgp/{PgpSignerFactory.java => PgpSignerCreator.java} (71%) create mode 100644 core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerCreatorFactory.java diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerFactory.java b/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreator.java similarity index 60% rename from core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerFactory.java rename to core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreator.java index 27ea475..616def2 100644 --- a/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerFactory.java +++ b/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreator.java @@ -19,26 +19,30 @@ import java.util.function.Function; /** - * Implementation of {@link PgpSignerFactory} that depends on Bouncy Castle directly. + * Implementation of {@link PgpSignerCreator} that depends on Bouncy Castle directly. * Here, the user needs to pass in the {@link PGPPrivateKey} and digest algorithm they want to use * for signing explicitly. */ -public class BcPgpSignerFactory extends PgpSignerFactory { +public class BcPgpSignerCreator extends PgpSignerCreator { private final PGPPrivateKey privateKey; - private int hashAlgorithm; - - public BcPgpSignerFactory(PGPPrivateKey privateKey, int hashAlgorithmId, boolean inlineSigned) { + private final int hashAlgorithm; + + /** + * Construct a {@link PgpSignerCreator} that uses Bouncy Castle classes directly and signs + * using a {@link SigningStream}. + * + * @param privateKey private signing key + * @param hashAlgorithmId OpenPGP hash algorithm ID of the digest algorithm to use for signing + * @param inlineSigned if true, use the cleartext signature framework to sign data inline. + * Otherwise, sign using detached signatures. + */ + public BcPgpSignerCreator(PGPPrivateKey privateKey, int hashAlgorithmId, boolean inlineSigned) { super(inlineSigned); - this.setHashAlgorithm(hashAlgorithmId); + this.hashAlgorithm = hashAlgorithmId; this.privateKey = privateKey; } - @Override - public void setHashAlgorithm(int hashAlgorithm) { - this.hashAlgorithm = hashAlgorithm; - } - @Override public Function createSigningStream() { if (privateKey == null) { diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreatorFactory.java b/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreatorFactory.java new file mode 100644 index 0000000..688fb68 --- /dev/null +++ b/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreatorFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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 org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; + +import java.util.NoSuchElementException; + +/** + * Implementation of the {@link PgpSignerCreatorFactory} that uses BC. + */ +public class BcPgpSignerCreatorFactory implements PgpSignerCreatorFactory { + + @Override + public PgpSignerCreator getSignerCreator( + PGPSecretKeyRing signingKey, + long signingKeyId, + char[] passphrase, + int hashAlgorithm, + boolean inlineSigned) { + PGPSecretKey key = signingKey.getSecretKey(signingKeyId); + if (key == null) { + throw new NoSuchElementException("No such signing key"); + } + try { + PGPPrivateKey privateKey = key.extractPrivateKey( + new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) + .build(passphrase)); + return new BcPgpSignerCreator(privateKey, hashAlgorithm, inlineSigned); + } catch (PGPException e) { + throw new RuntimeException("Could not unlock private key."); + } + } +} diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerFactory.java b/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerCreator.java similarity index 71% rename from core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerFactory.java rename to core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerCreator.java index a7d247c..2a437f7 100644 --- a/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerFactory.java +++ b/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerCreator.java @@ -17,25 +17,20 @@ import java.util.function.Function; /** - * Factory for signing streams. + * Factory for creating signing streams. */ -public abstract class PgpSignerFactory { +public abstract class PgpSignerCreator { protected final boolean inlineSigned; - public PgpSignerFactory(boolean inlineSigned) { + public PgpSignerCreator(boolean inlineSigned) { this.inlineSigned = inlineSigned; } - /** - * Decide, which hash algorithm to use. - * - * @param hashAlgorithm algorithm ID of the signature digest algorithm - */ - public abstract void setHashAlgorithm(int hashAlgorithm); - /** * Return a {@link Function} that wraps an {@link OutputStream} into a signing stream. + * This method has no arguments (key, algorithms etc.) to be implementation agnostic. + * Subclasses shall pass those details as constructor arguments. * * @return transforming function */ diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerCreatorFactory.java b/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerCreatorFactory.java new file mode 100644 index 0000000..11b1305 --- /dev/null +++ b/core/src/main/java/org/eclipse/packager/security/pgp/PgpSignerCreatorFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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 org.bouncycastle.openpgp.PGPSecretKeyRing; + +/** + * Factory interface for instantiating {@link PgpSignerCreator} classes. + * This class acts as the public interface for choosing the OpenPGP signing backend. + * By default, Bouncy Castle is used via {@link BcPgpSignerCreatorFactory}. + * TODO: Use dependency injection to allow optional dependencies to replace the default instance. + */ +public interface PgpSignerCreatorFactory { + + PgpSignerCreator getSignerCreator( + PGPSecretKeyRing signingKey, + long signingKeyId, + char[] passphrase, + int hashAlgorithm, + boolean inlineSigned); +} From 77d1ef08b10878ef79ccacd06e5adc8d8500456a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 Apr 2024 22:01:51 +0200 Subject: [PATCH 06/16] Add PgpSignatureProcessorFactory class and Bc implementation --- .../BcPgpSignatureProcessorFactory.java | 46 +++++++++++++++++++ .../PgpSignatureProcessorFactory.java | 36 +++++++++++++++ .../packager/rpm/yum/RepositoryCreator.java | 8 ++-- 3 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java create mode 100644 rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessorFactory.java diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java new file mode 100644 index 0000000..8b98450 --- /dev/null +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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 org.bouncycastle.openpgp.PGPPrivateKey; + +/** + * Implementation of the {@link PgpSignatureProcessorFactory} that uses Bouncy Castle directly for signing. + */ +public class BcPgpSignatureProcessorFactory extends PgpSignatureProcessorFactory { + + private final PGPPrivateKey privateKey; + private final int hashAlgorithm; + + /** + * Create a new factory. + * + * @param privateKey private signing key + * @param hashAlgorithm OpenPgp hash algorithm ID of the digest algorithm used for signing + */ + public BcPgpSignatureProcessorFactory(PGPPrivateKey privateKey, int hashAlgorithm) { + this.privateKey = privateKey; + this.hashAlgorithm = hashAlgorithm; + } + + @Override + public SignatureProcessor createHeaderSignatureProcessor() { + return new RsaHeaderSignatureProcessor(privateKey, hashAlgorithm); + } + + @Override + public SignatureProcessor createSignatureProcessor() { + return new RsaSignatureProcessor(privateKey, hashAlgorithm); + } +} diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessorFactory.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessorFactory.java new file mode 100644 index 0000000..895bbd9 --- /dev/null +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessorFactory.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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; + +/** + * Factory for creating OpenPGP signing-related {@link SignatureProcessor} instances. + * By default, packager will use {@link BcPgpSignatureProcessorFactory}. + * TODO: Use Dependency Injection to allow for dynamic replacing of the factory instance. + */ +public abstract class PgpSignatureProcessorFactory { + + /** + * Create a {@link SignatureProcessor} for signing the header. + * + * @return header signature processor + */ + public abstract SignatureProcessor createHeaderSignatureProcessor(); + + /** + * Create a {@link SignatureProcessor} for signing both header and data. + * + * @return signature processor + */ + public abstract SignatureProcessor createSignatureProcessor(); +} 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 1621831..f8622ed 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 @@ -50,8 +50,8 @@ 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.BcPgpSignerFactory; -import org.eclipse.packager.security.pgp.PgpSignerFactory; +import org.eclipse.packager.security.pgp.BcPgpSignerCreator; +import org.eclipse.packager.security.pgp.PgpSignerCreator; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -440,7 +440,7 @@ public Builder setSigning(final Function signingStre return this; } - public Builder setSigning(PgpSignerFactory signingFactory) { + public Builder setSigning(PgpSignerCreator signingFactory) { return setSigning(signingFactory.createSigningStream()); } @@ -456,7 +456,7 @@ public Builder setSigning(final PGPPrivateKey privateKey, final HashAlgorithm ha @Deprecated public Builder setSigning(final PGPPrivateKey privateKey, final int digestAlgorithm) { - return setSigning(new BcPgpSignerFactory(privateKey, digestAlgorithm, false)); + return setSigning(new BcPgpSignerCreator(privateKey, digestAlgorithm, false)); } public RepositoryCreator build() { From d034c728b8e2875c03f217f5ca43c2029adc4aaa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 Apr 2024 22:02:35 +0200 Subject: [PATCH 07/16] Add pgpainless_signer module containing implementations of SignatureProcessors and SignerCreators --- pgpainless_signer/pom.xml | 83 +++++++++ .../PGPainlessHeaderSignatureProcessor.java | 75 ++++++++ .../PGPainlessSignatureProcessor.java | 122 +++++++++++++ .../PGPainlessSignatureProcessorFactory.java | 43 +++++ .../pgpainless/PGPainlessSignerCreator.java | 165 ++++++++++++++++++ .../PGPainlessSignerCreatorFactory.java | 24 +++ .../pgpainless/AbstractSigningTest.java | 141 +++++++++++++++ .../HeaderSignatureProcessorTest.java | 55 ++++++ pom.xml | 7 + 9 files changed, 715 insertions(+) create mode 100644 pgpainless_signer/pom.xml create mode 100644 pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java create mode 100644 pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java create mode 100644 pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java create mode 100644 pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java create mode 100644 pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java create mode 100644 pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java create mode 100644 pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java diff --git a/pgpainless_signer/pom.xml b/pgpainless_signer/pom.xml new file mode 100644 index 0000000..4c447cf --- /dev/null +++ b/pgpainless_signer/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + org.eclipse.packager + packager + 0.20.1-SNAPSHOT + + + packager-pgpainless_signer + Eclipse Packager :: PGPainless Signer + + + + org.eclipse.packager + packager-core + ${project.version} + + + org.eclipse.packager + packager-rpm + ${project.version} + + + org.pgpainless + pgpainless-core + + + org.slf4j + slf4j-api + + + org.apache.commons + commons-compress + + + com.google.guava + guava + + + org.tukaani + xz + + + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + + + ch.qos.logback + logback-classic + test + + + + + + + org.apache.felix + maven-bundle-plugin + + + + + diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java b/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java new file mode 100644 index 0000000..3553081 --- /dev/null +++ b/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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.pgpainless; + +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.encryption_signing.EncryptionResult; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class PGPainlessHeaderSignatureProcessor extends PGPainlessSignatureProcessor { + + private final Logger logger = LoggerFactory.getLogger(PGPainlessHeaderSignatureProcessor.class); + + public PGPainlessHeaderSignatureProcessor(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector, int hashAlgorithm) { + super(key, keyProtector, hashAlgorithm); + } + + @Override + public Logger getLogger() { + return logger; + } + + @Override + public void feedPayloadData(ByteBuffer data) { + // We only work on header data + } + + @Override + public void finish(Header signature) { + try { + signingStream.close(); + EncryptionResult result = signingStream.getResult(); + PGPSignature pgpSignature = result.getDetachedSignatures().flatten().iterator().next(); + byte[] value = pgpSignature.getEncoded(); + switch (pgpSignature.getKeyAlgorithm()) { + // RSA + case PublicKeyAlgorithmTags.RSA_GENERAL: // 1 + getLogger().info("RSA HEADER: {}", value); + signature.putBlob(RpmSignatureTag.RSAHEADER, value); + break; + + // DSA + case PublicKeyAlgorithmTags.DSA: // 17 + case PublicKeyAlgorithmTags.EDDSA_LEGACY: // 22 + getLogger().info("DSA HEADER: {}", value); + signature.putBlob(RpmSignatureTag.DSAHEADER, value); + break; + + default: + throw new RuntimeException("Unsupported public key algorithm id: " + pgpSignature.getKeyAlgorithm()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java b/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java new file mode 100644 index 0000000..70a38e1 --- /dev/null +++ b/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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.pgpainless; + +import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +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.eclipse.packager.rpm.signature.SignatureProcessor; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.CompressionAlgorithm; +import org.pgpainless.algorithm.HashAlgorithm; +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; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +public class PGPainlessSignatureProcessor implements SignatureProcessor { + + protected final EncryptionStream signingStream; + private final Logger logger = LoggerFactory.getLogger(PGPainlessSignatureProcessor.class); + + public PGPainlessSignatureProcessor(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector, int hashAlgorithm) { + OutputStream sink = new OutputStream() { + @Override + public void write(int i) { + // Discard plaintext + } + }; + try { + signingStream = PGPainless.encryptAndOrSign() + .onOutputStream(sink) + .withOptions( + ProducerOptions.sign( + SigningOptions.get() + .overrideHashAlgorithm(HashAlgorithm.requireFromId(hashAlgorithm)) + .addDetachedSignature(keyProtector, key) + ).setAsciiArmor(false) + .overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED) + ); + } 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); + } + } + + protected Logger getLogger() { + return logger; + } + + @Override + public void finish(Header signature) { + try { + signingStream.close(); + EncryptionResult result = signingStream.getResult(); + PGPSignature pgpSignature = result.getDetachedSignatures().flatten().iterator().next(); + byte[] value = pgpSignature.getEncoded(); + switch (pgpSignature.getKeyAlgorithm()) { + // RSA + case PublicKeyAlgorithmTags.RSA_GENERAL: // 1 + getLogger().info("RSA: {}", value); + signature.putBlob(RpmSignatureTag.PGP, value); + break; + + // DSA + case PublicKeyAlgorithmTags.DSA: // 17 + case PublicKeyAlgorithmTags.EDDSA_LEGACY: // 22 + getLogger().info("DSA: {}", value); + signature.putBlob(RpmSignatureTag.GPG, value); + break; + + default: + throw new RuntimeException("Unsupported public key algorithm id: " + pgpSignature.getKeyAlgorithm()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java b/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java new file mode 100644 index 0000000..4b6e3b9 --- /dev/null +++ b/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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.pgpainless; + +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.eclipse.packager.rpm.signature.PgpSignatureProcessorFactory; +import org.eclipse.packager.rpm.signature.SignatureProcessor; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.Passphrase; + +public class PGPainlessSignatureProcessorFactory extends PgpSignatureProcessorFactory { + + private final PGPSecretKeyRing key; + private final SecretKeyRingProtector keyProtector; + private final int hashAlgorithm; + + public PGPainlessSignatureProcessorFactory(PGPSecretKeyRing key, char[] passphrase, int hashAlgorithm) { + this.key = key; + this.keyProtector = SecretKeyRingProtector.unlockAnyKeyWith(new Passphrase(passphrase)); + this.hashAlgorithm = hashAlgorithm; + } + + @Override + public SignatureProcessor createHeaderSignatureProcessor() { + return new PGPainlessHeaderSignatureProcessor(key, keyProtector, hashAlgorithm); + } + + @Override + public SignatureProcessor createSignatureProcessor() { + return new PGPainlessSignatureProcessor(key, keyProtector, hashAlgorithm); + } +} diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java b/pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java new file mode 100644 index 0000000..9b4253c --- /dev/null +++ b/pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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.pgpainless; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.eclipse.packager.security.pgp.BcPgpSignerCreator; +import org.eclipse.packager.security.pgp.PgpSignerCreator; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.DocumentSignatureType; +import org.pgpainless.algorithm.HashAlgorithm; +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; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Set; +import java.util.function.Function; + +/** + * {@link PgpSignerCreator} that uses PGPainless as a wrapper around Bouncy Castle. + * Contrary to {@link BcPgpSignerCreator}, this class validates that the + * signing key is healthy and ensures that safe algorithms are used. + */ +public class PGPainlessSignerCreator extends PgpSignerCreator { + + private final SigningOptions signing = SigningOptions.get(); + + /** + * Create a new {@link PGPainlessSignerCreator}. + * If inlineSigned is true, the output will be an inline-signed message using the + * Cleartext Signature Framework (CSF). + * Else it will be ASCII armored detached signatures. + * + * @param inlineSigned whether we want to CSF inline sign or detached sign + */ + public PGPainlessSignerCreator(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector, int hashAlgorithm, boolean inlineSigned) { + super(inlineSigned); + signing.overrideHashAlgorithm(HashAlgorithm.requireFromId(hashAlgorithm)); + try { + if (inlineSigned) { + signing.addInlineSignature(keyProtector, key, DocumentSignatureType.BINARY_DOCUMENT); + } else { + signing.addDetachedSignature(keyProtector, key, DocumentSignatureType.BINARY_DOCUMENT); + } + } catch (PGPException e) { + throw new RuntimeException(e); + } + } + + @Override + public Function createSigningStream() { + return outputStream -> { + ProducerOptions options = ProducerOptions.sign(signing) + .setAsciiArmor(true); + + if (inlineSigned) { + options.setCleartextSigned(); + } + + try { + return new PGPainlessSigningStream(options, outputStream); + } catch (IOException | PGPException e) { + throw new RuntimeException(e); + } + }; + } + + /** + * Depending on whether we inline-sign, we either write the plaintext signed using the + * Cleartext Signature Framework, or just the ASCII armored detached signature(s). + */ + public static class PGPainlessSigningStream extends OutputStream { + + private final ProducerOptions options; + private final EncryptionStream signingStream; + private final OutputStream outputStream; // if we detached-sign, we emit signatures here + + public PGPainlessSigningStream(ProducerOptions options, OutputStream outputStream) + throws PGPException, IOException { + this.options = options; + this.outputStream = outputStream; + + if (options.isCleartextSigned()) { + // emit CSF-wrapped plaintext with inline signatures + this.signingStream = PGPainless.encryptAndOrSign() + .onOutputStream(outputStream) + .withOptions(options); + } else { + // Emit just the detached, armored signatures to output stream + OutputStream plaintextSink = new OutputStream() { + @Override + public void write(int i) { + // Discard plaintext bytes + } + }; + this.signingStream = PGPainless.encryptAndOrSign() + // TODO: Replace with .discardOutput() with PGPainless 1.6.8+ + .onOutputStream(plaintextSink) + .withOptions(options); + } + } + + @Override + public void write(int i) throws IOException { + signingStream.write(i); + } + + @Override + public void write(byte[] b) throws IOException { + signingStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + signingStream.write(b, off, len); + } + + @Override + public void flush() throws IOException { + signingStream.flush(); + } + + @Override + public void close() throws IOException { + signingStream.close(); + + // If we detached-sign, emit signatures + if (!options.isCleartextSigned()) { + ArmoredOutputStream armorOut = ArmoredOutputStreamFactory.get(outputStream); + + EncryptionResult result = signingStream.getResult(); + result.getDetachedSignatures().flatten().forEach(sig -> { + try { + sig.encode(armorOut); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + armorOut.close(); + } + } + + public Set getDetachedSignatures() { + return signingStream.getResult().getDetachedSignatures().flatten(); + } + } +} diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java b/pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java new file mode 100644 index 0000000..03e7836 --- /dev/null +++ b/pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java @@ -0,0 +1,24 @@ +package org.eclipse.packager.security.pgp.pgpainless; + +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.eclipse.packager.security.pgp.PgpSignerCreator; +import org.eclipse.packager.security.pgp.PgpSignerCreatorFactory; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.Passphrase; + +public class PGPainlessSignerCreatorFactory implements PgpSignerCreatorFactory { + + @Override + public PgpSignerCreator getSignerCreator( + PGPSecretKeyRing signingKey, + long signingKeyId, + char[] passphrase, + int hashAlgorithm, + boolean inlineSigned) { + return new PGPainlessSignerCreator( + signingKey, + SecretKeyRingProtector.unlockAnyKeyWith(new Passphrase(passphrase)), + hashAlgorithm, + inlineSigned); + } +} diff --git a/pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java b/pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java new file mode 100644 index 0000000..268631c --- /dev/null +++ b/pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java @@ -0,0 +1,141 @@ +package org.eclipse.packager.rpm.signature.pgpainless; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class AbstractSigningTest { + + public static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: OpenPGP.js v4.10.10\n" + + "Comment: https://openpgpjs.org\n" + + "\n" + + "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\n" + + "25uiY4mR\n" + + "=iZMt\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + public static final char[] PASSPHRASE = "testkey".toCharArray(); + + public PGPSecretKeyRing readSecretKey() throws IOException { + ByteArrayInputStream bIn = new ByteArrayInputStream(KEY.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream armorIn = new ArmoredInputStream(bIn); + BCPGInputStream bcIn = new BCPGInputStream(armorIn); + PGPObjectFactory objectFactory = new BcPGPObjectFactory(bcIn); + return (PGPSecretKeyRing) objectFactory.nextObject(); + } +} diff --git a/pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java b/pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java new file mode 100644 index 0000000..171b36b --- /dev/null +++ b/pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java @@ -0,0 +1,55 @@ +package org.eclipse.packager.rpm.signature.pgpainless; + +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.eclipse.packager.rpm.RpmSignatureTag; +import org.eclipse.packager.rpm.header.Header; +import org.eclipse.packager.rpm.signature.BcPgpSignatureProcessorFactory; +import org.eclipse.packager.rpm.signature.PgpSignatureProcessorFactory; +import org.eclipse.packager.rpm.signature.SignatureProcessor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class HeaderSignatureProcessorTest extends AbstractSigningTest { + + private static final List instances = new ArrayList<>(); + + @BeforeEach + public void setup() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = readSecretKey(); + + // BC + PGPSecretKey signingKey = secretKeys.getSecretKey(); + PGPPrivateKey privateKey = signingKey.extractPrivateKey( + new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) + .build(PASSPHRASE)); + instances.add(new BcPgpSignatureProcessorFactory(privateKey, HashAlgorithmTags.SHA256)); + + // PGPainless + instances.add(new PGPainlessSignatureProcessorFactory(secretKeys, PASSPHRASE, HashAlgorithmTags.SHA256)); + } + + @Test + public void test() { + for (PgpSignatureProcessorFactory instance : instances) { + SignatureProcessor processor = instance.createHeaderSignatureProcessor(); + processor.feedHeader(ByteBuffer.wrap("Hello World!".getBytes(StandardCharsets.UTF_8))); + Header header = new Header<>(); + processor.finish(header); + assertNotNull(header.get(RpmSignatureTag.RSAHEADER), instance.getClass().getName()); + } + } +} diff --git a/pom.xml b/pom.xml index 15c61ea..36ede81 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,7 @@ core deb rpm + pgpainless_signer @@ -80,6 +81,7 @@ 33.0.0-jre 5.8.2 1.5.0 + 1.6.7 2.0.12 1.9 @@ -130,6 +132,11 @@ ${bouncycastle.version} + + org.pgpainless + pgpainless-core + ${pgpainless.version} + org.slf4j slf4j-api From 38ecfe7eb6f708785c34a48fc9d12dcaf5314ff3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 3 Apr 2024 22:08:55 +0200 Subject: [PATCH 08/16] Remove unused stuff from pgpainless_signer.pom --- pgpainless_signer/pom.xml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/pgpainless_signer/pom.xml b/pgpainless_signer/pom.xml index 4c447cf..2d9ec8a 100644 --- a/pgpainless_signer/pom.xml +++ b/pgpainless_signer/pom.xml @@ -29,26 +29,6 @@ org.slf4j slf4j-api - - org.apache.commons - commons-compress - - - com.google.guava - guava - - - org.tukaani - xz - - From e7936b3fe9f79ecc8ad6e2ac956d19deba8cc95a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Apr 2024 14:14:53 +0200 Subject: [PATCH 09/16] Rename module to packager-pgpainless --- {pgpainless_signer => pgpainless}/pom.xml | 2 +- .../pgpainless/PGPainlessHeaderSignatureProcessor.java | 0 .../rpm/signature/pgpainless/PGPainlessSignatureProcessor.java | 0 .../pgpainless/PGPainlessSignatureProcessorFactory.java | 0 .../security/pgp/pgpainless/PGPainlessSignerCreator.java | 0 .../security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java | 0 .../packager/rpm/signature/pgpainless/AbstractSigningTest.java | 0 .../rpm/signature/pgpainless/HeaderSignatureProcessorTest.java | 0 pom.xml | 2 +- 9 files changed, 2 insertions(+), 2 deletions(-) rename {pgpainless_signer => pgpainless}/pom.xml (97%) rename {pgpainless_signer => pgpainless}/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java (100%) rename {pgpainless_signer => pgpainless}/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java (100%) rename {pgpainless_signer => pgpainless}/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java (100%) rename {pgpainless_signer => pgpainless}/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java (100%) rename {pgpainless_signer => pgpainless}/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java (100%) rename {pgpainless_signer => pgpainless}/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java (100%) rename {pgpainless_signer => pgpainless}/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java (100%) diff --git a/pgpainless_signer/pom.xml b/pgpainless/pom.xml similarity index 97% rename from pgpainless_signer/pom.xml rename to pgpainless/pom.xml index 2d9ec8a..68289d1 100644 --- a/pgpainless_signer/pom.xml +++ b/pgpainless/pom.xml @@ -7,7 +7,7 @@ 0.20.1-SNAPSHOT - packager-pgpainless_signer + packager-pgpainless Eclipse Packager :: PGPainless Signer diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java similarity index 100% rename from pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java rename to pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java similarity index 100% rename from pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java rename to pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java similarity index 100% rename from pgpainless_signer/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java rename to pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java b/pgpainless/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java similarity index 100% rename from pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java rename to pgpainless/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java diff --git a/pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java b/pgpainless/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java similarity index 100% rename from pgpainless_signer/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java rename to pgpainless/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreatorFactory.java diff --git a/pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java similarity index 100% rename from pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java rename to pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java diff --git a/pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java similarity index 100% rename from pgpainless_signer/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java rename to pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java diff --git a/pom.xml b/pom.xml index 36ede81..c455b4d 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ core deb rpm - pgpainless_signer + pgpainless From 0d0dd3e9d3469d6feb22729af7323ea954ea14ff Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Apr 2024 14:18:31 +0200 Subject: [PATCH 10/16] Document 'EcDSA' typo in manual --- .../pgpainless/PGPainlessHeaderSignatureProcessor.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java index 3553081..5b57c39 100644 --- a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java +++ b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java @@ -59,6 +59,8 @@ public void finish(Header signature) { break; // DSA + // https://rpm-software-management.github.io/rpm/manual/format_v4.html is talking about "EcDSA", + // which is probably a typo. case PublicKeyAlgorithmTags.DSA: // 17 case PublicKeyAlgorithmTags.EDDSA_LEGACY: // 22 getLogger().info("DSA HEADER: {}", value); From a433610e539721a036f960f74f1ac917a03ba332 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Apr 2024 14:24:41 +0200 Subject: [PATCH 11/16] Add documentation to PGPainlessHeaderSignatureProcessor --- .../pgpainless/PGPainlessHeaderSignatureProcessor.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java index 5b57c39..d9c1efd 100644 --- a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java +++ b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java @@ -26,6 +26,13 @@ import java.io.IOException; import java.nio.ByteBuffer; +/** + * A {@link org.eclipse.packager.rpm.signature.SignatureProcessor} for signing headers, which uses PGPainless as + * backend for the signing operation. + * This processor can be used with RSA or EdDSA signing (sub-)keys and will produce RPMv4 header signatures + * which are emitted with either the {@link RpmSignatureTag#RSAHEADER} or {@link RpmSignatureTag#DSAHEADER} + * header tag. + */ public class PGPainlessHeaderSignatureProcessor extends PGPainlessSignatureProcessor { private final Logger logger = LoggerFactory.getLogger(PGPainlessHeaderSignatureProcessor.class); From fac4c6568e1c70b7d7e2f13b8974dde21025cde8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Apr 2024 14:39:01 +0200 Subject: [PATCH 12/16] Allow signing-backend to choose hash algorithm when 0 is passed --- .../packager/security/pgp/BcPgpSignerCreator.java | 7 ++++++- .../pgpainless/PGPainlessHeaderSignatureProcessor.java | 4 ++++ .../pgpainless/PGPainlessSignatureProcessor.java | 8 +++++--- .../PGPainlessSignatureProcessorFactory.java | 10 +++++++++- .../pgp/pgpainless/PGPainlessSignerCreator.java | 4 +++- .../rpm/signature/BcPgpSignatureProcessorFactory.java | 5 +++++ 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreator.java b/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreator.java index 616def2..4eea770 100644 --- a/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreator.java +++ b/core/src/main/java/org/eclipse/packager/security/pgp/BcPgpSignerCreator.java @@ -13,6 +13,7 @@ package org.eclipse.packager.security.pgp; +import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.PGPPrivateKey; import java.io.OutputStream; @@ -39,7 +40,11 @@ public class BcPgpSignerCreator extends PgpSignerCreator { */ public BcPgpSignerCreator(PGPPrivateKey privateKey, int hashAlgorithmId, boolean inlineSigned) { super(inlineSigned); - this.hashAlgorithm = hashAlgorithmId; + if (hashAlgorithmId != 0) { + this.hashAlgorithm = hashAlgorithmId; + } else { + this.hashAlgorithm = HashAlgorithmTags.SHA256; + } this.privateKey = privateKey; } diff --git a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java index d9c1efd..bdb236c 100644 --- a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java +++ b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java @@ -37,6 +37,10 @@ public class PGPainlessHeaderSignatureProcessor extends PGPainlessSignatureProce private final Logger logger = LoggerFactory.getLogger(PGPainlessHeaderSignatureProcessor.class); + public PGPainlessHeaderSignatureProcessor(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector) { + this(key, keyProtector, 0); + } + public PGPainlessHeaderSignatureProcessor(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector, int hashAlgorithm) { super(key, keyProtector, hashAlgorithm); } diff --git a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java index 70a38e1..4812971 100644 --- a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java +++ b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java @@ -47,14 +47,16 @@ public void write(int i) { // Discard plaintext } }; + SigningOptions signingOptions = SigningOptions.get(); + if (hashAlgorithm != 0) { + signingOptions.overrideHashAlgorithm(HashAlgorithm.requireFromId(hashAlgorithm)); + } try { signingStream = PGPainless.encryptAndOrSign() .onOutputStream(sink) .withOptions( ProducerOptions.sign( - SigningOptions.get() - .overrideHashAlgorithm(HashAlgorithm.requireFromId(hashAlgorithm)) - .addDetachedSignature(keyProtector, key) + signingOptions.addDetachedSignature(keyProtector, key) ).setAsciiArmor(false) .overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED) ); diff --git a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java index 4b6e3b9..7f434de 100644 --- a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java +++ b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java @@ -25,6 +25,10 @@ public class PGPainlessSignatureProcessorFactory extends PgpSignatureProcessorFa private final SecretKeyRingProtector keyProtector; private final int hashAlgorithm; + public PGPainlessSignatureProcessorFactory(PGPSecretKeyRing key, char[] passphrase) { + this(key, passphrase, 0); // Leave hash algorithm up to the signing backend to choose. + } + public PGPainlessSignatureProcessorFactory(PGPSecretKeyRing key, char[] passphrase, int hashAlgorithm) { this.key = key; this.keyProtector = SecretKeyRingProtector.unlockAnyKeyWith(new Passphrase(passphrase)); @@ -33,7 +37,11 @@ public PGPainlessSignatureProcessorFactory(PGPSecretKeyRing key, char[] passphra @Override public SignatureProcessor createHeaderSignatureProcessor() { - return new PGPainlessHeaderSignatureProcessor(key, keyProtector, hashAlgorithm); + if (hashAlgorithm != 0) { + return new PGPainlessHeaderSignatureProcessor(key, keyProtector, hashAlgorithm); + } else { + return new PGPainlessHeaderSignatureProcessor(key, keyProtector); + } } @Override diff --git a/pgpainless/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java b/pgpainless/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java index 9b4253c..77869fd 100644 --- a/pgpainless/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java +++ b/pgpainless/src/main/java/org/eclipse/packager/security/pgp/pgpainless/PGPainlessSignerCreator.java @@ -53,7 +53,9 @@ public class PGPainlessSignerCreator extends PgpSignerCreator { */ public PGPainlessSignerCreator(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector, int hashAlgorithm, boolean inlineSigned) { super(inlineSigned); - signing.overrideHashAlgorithm(HashAlgorithm.requireFromId(hashAlgorithm)); + if (hashAlgorithm != 0) { + signing.overrideHashAlgorithm(HashAlgorithm.requireFromId(hashAlgorithm)); + } try { if (inlineSigned) { signing.addInlineSignature(keyProtector, key, DocumentSignatureType.BINARY_DOCUMENT); diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java index 8b98450..01da486 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java @@ -13,6 +13,7 @@ package org.eclipse.packager.rpm.signature; +import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.PGPPrivateKey; /** @@ -23,6 +24,10 @@ public class BcPgpSignatureProcessorFactory extends PgpSignatureProcessorFactory private final PGPPrivateKey privateKey; private final int hashAlgorithm; + public BcPgpSignatureProcessorFactory(PGPPrivateKey privateKey) { + this(privateKey, HashAlgorithmTags.SHA256); + } + /** * Create a new factory. * From c8e290b015e1619c9c12814d6eb79f23347a589a Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Apr 2024 14:39:25 +0200 Subject: [PATCH 13/16] Tests: Read secret key from rpm/test/resources --- .../pgpainless/AbstractSigningTest.java | 135 ++---------------- 1 file changed, 11 insertions(+), 124 deletions(-) diff --git a/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java index 268631c..8bf0584 100644 --- a/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java +++ b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java @@ -6,136 +6,23 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; -import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; public class AbstractSigningTest { - public static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + - "Version: OpenPGP.js v4.10.10\n" + - "Comment: https://openpgpjs.org\n" + - "\n" + - "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\n" + - "25uiY4mR\n" + - "=iZMt\n" + - "-----END PGP PRIVATE KEY BLOCK-----"; - public static final char[] PASSPHRASE = "testkey".toCharArray(); public PGPSecretKeyRing readSecretKey() throws IOException { - ByteArrayInputStream bIn = new ByteArrayInputStream(KEY.getBytes(StandardCharsets.UTF_8)); - ArmoredInputStream armorIn = new ArmoredInputStream(bIn); - BCPGInputStream bcIn = new BCPGInputStream(armorIn); - PGPObjectFactory objectFactory = new BcPGPObjectFactory(bcIn); - return (PGPSecretKeyRing) objectFactory.nextObject(); + File keyFile = new File("../rpm/src/test/resources/key/private_key.txt"); + try ( + FileInputStream fileIn = new FileInputStream(keyFile); + ArmoredInputStream armorIn = new ArmoredInputStream(fileIn); + BCPGInputStream bcIn = new BCPGInputStream(armorIn) + ) { + PGPObjectFactory objectFactory = new BcPGPObjectFactory(bcIn); + return (PGPSecretKeyRing) objectFactory.nextObject(); + } } } From c9393ff15bb9e5947ef4c47e5a8979fe11283deb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Apr 2024 23:14:40 +0200 Subject: [PATCH 14/16] Do not port over RPMv3 style signature processors --- .../PGPainlessHeaderSignatureProcessor.java | 56 +++++++- .../PGPainlessSignatureProcessor.java | 124 ------------------ .../PGPainlessSignatureProcessorFactory.java | 5 - .../BcPgpSignatureProcessorFactory.java | 5 - .../PgpSignatureProcessorFactory.java | 7 - 5 files changed, 53 insertions(+), 144 deletions(-) delete mode 100644 pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java diff --git a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java index bdb236c..5d5c8be 100644 --- a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java +++ b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessHeaderSignatureProcessor.java @@ -14,16 +14,25 @@ package org.eclipse.packager.rpm.signature.pgpainless; import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; +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.eclipse.packager.rpm.signature.SignatureProcessor; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.CompressionAlgorithm; +import org.pgpainless.algorithm.HashAlgorithm; 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; import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; /** @@ -33,28 +42,69 @@ * which are emitted with either the {@link RpmSignatureTag#RSAHEADER} or {@link RpmSignatureTag#DSAHEADER} * header tag. */ -public class PGPainlessHeaderSignatureProcessor extends PGPainlessSignatureProcessor { +public class PGPainlessHeaderSignatureProcessor implements SignatureProcessor { private final Logger logger = LoggerFactory.getLogger(PGPainlessHeaderSignatureProcessor.class); + private final EncryptionStream signingStream; + public PGPainlessHeaderSignatureProcessor(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector) { this(key, keyProtector, 0); } public PGPainlessHeaderSignatureProcessor(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector, int hashAlgorithm) { - super(key, keyProtector, hashAlgorithm); + OutputStream sink = new OutputStream() { + @Override + public void write(int i) { + // Discard plaintext + } + }; + SigningOptions signingOptions = SigningOptions.get(); + if (hashAlgorithm != 0) { + signingOptions.overrideHashAlgorithm(HashAlgorithm.requireFromId(hashAlgorithm)); + } + try { + signingStream = PGPainless.encryptAndOrSign() + .onOutputStream(sink) + .withOptions( + ProducerOptions.sign( + signingOptions.addDetachedSignature(keyProtector, key) + ).setAsciiArmor(false) + .overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED) + ); + } catch (PGPException | IOException e) { + throw new RuntimeException(e); + } } - @Override public Logger getLogger() { return logger; } + @Override + public void feedHeader(ByteBuffer header) { + feedData(header); + } + @Override public void feedPayloadData(ByteBuffer data) { // We only work on header 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 { diff --git a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java deleted file mode 100644 index 4812971..0000000 --- a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessor.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2024 Paul Schaub - * - * 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.pgpainless; - -import org.bouncycastle.bcpg.PublicKeyAlgorithmTags; -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.eclipse.packager.rpm.signature.SignatureProcessor; -import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.CompressionAlgorithm; -import org.pgpainless.algorithm.HashAlgorithm; -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; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -public class PGPainlessSignatureProcessor implements SignatureProcessor { - - protected final EncryptionStream signingStream; - private final Logger logger = LoggerFactory.getLogger(PGPainlessSignatureProcessor.class); - - public PGPainlessSignatureProcessor(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector, int hashAlgorithm) { - OutputStream sink = new OutputStream() { - @Override - public void write(int i) { - // Discard plaintext - } - }; - SigningOptions signingOptions = SigningOptions.get(); - if (hashAlgorithm != 0) { - signingOptions.overrideHashAlgorithm(HashAlgorithm.requireFromId(hashAlgorithm)); - } - try { - signingStream = PGPainless.encryptAndOrSign() - .onOutputStream(sink) - .withOptions( - ProducerOptions.sign( - signingOptions.addDetachedSignature(keyProtector, key) - ).setAsciiArmor(false) - .overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED) - ); - } 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); - } - } - - protected Logger getLogger() { - return logger; - } - - @Override - public void finish(Header signature) { - try { - signingStream.close(); - EncryptionResult result = signingStream.getResult(); - PGPSignature pgpSignature = result.getDetachedSignatures().flatten().iterator().next(); - byte[] value = pgpSignature.getEncoded(); - switch (pgpSignature.getKeyAlgorithm()) { - // RSA - case PublicKeyAlgorithmTags.RSA_GENERAL: // 1 - getLogger().info("RSA: {}", value); - signature.putBlob(RpmSignatureTag.PGP, value); - break; - - // DSA - case PublicKeyAlgorithmTags.DSA: // 17 - case PublicKeyAlgorithmTags.EDDSA_LEGACY: // 22 - getLogger().info("DSA: {}", value); - signature.putBlob(RpmSignatureTag.GPG, value); - break; - - default: - throw new RuntimeException("Unsupported public key algorithm id: " + pgpSignature.getKeyAlgorithm()); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java index 7f434de..35f568d 100644 --- a/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java +++ b/pgpainless/src/main/java/org/eclipse/packager/rpm/signature/pgpainless/PGPainlessSignatureProcessorFactory.java @@ -43,9 +43,4 @@ public SignatureProcessor createHeaderSignatureProcessor() { return new PGPainlessHeaderSignatureProcessor(key, keyProtector); } } - - @Override - public SignatureProcessor createSignatureProcessor() { - return new PGPainlessSignatureProcessor(key, keyProtector, hashAlgorithm); - } } diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java index 01da486..7fe1915 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/BcPgpSignatureProcessorFactory.java @@ -43,9 +43,4 @@ public BcPgpSignatureProcessorFactory(PGPPrivateKey privateKey, int hashAlgorith public SignatureProcessor createHeaderSignatureProcessor() { return new RsaHeaderSignatureProcessor(privateKey, hashAlgorithm); } - - @Override - public SignatureProcessor createSignatureProcessor() { - return new RsaSignatureProcessor(privateKey, hashAlgorithm); - } } diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessorFactory.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessorFactory.java index 895bbd9..11f59f4 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessorFactory.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpSignatureProcessorFactory.java @@ -26,11 +26,4 @@ public abstract class PgpSignatureProcessorFactory { * @return header signature processor */ public abstract SignatureProcessor createHeaderSignatureProcessor(); - - /** - * Create a {@link SignatureProcessor} for signing both header and data. - * - * @return signature processor - */ - public abstract SignatureProcessor createSignatureProcessor(); } From d80e57bf964e03354520055721df29b0374792c9 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Apr 2024 23:15:36 +0200 Subject: [PATCH 15/16] Remove deprecated GPG RpmSignatureTag --- rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java b/rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java index 35dd645..a3e708e 100644 --- a/rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java +++ b/rpm/src/main/java/org/eclipse/packager/rpm/RpmSignatureTag.java @@ -27,7 +27,6 @@ public enum RpmSignatureTag implements RpmBaseTag { SIZE(1000), PGP(1002), // RSA MD5(1004), - GPG(1005), // EcDSA and DSA, see https://rpm-software-management.github.io/rpm/manual/format_v4.html PAYLOAD_SIZE(1007), LONGSIZE(5009); From ddf7ad7db85fd62c823f85260e8a889ab7907ebb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 8 Apr 2024 13:54:59 +0200 Subject: [PATCH 16/16] Add tests --- .../pgpainless/AbstractSigningTest.java | 28 -- .../HeaderSignatureProcessorTest.java | 153 +++++++++-- .../rpm/signature/pgpainless/TestKey.java | 240 ++++++++++++++++++ 3 files changed, 367 insertions(+), 54 deletions(-) delete mode 100644 pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java create mode 100644 pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/TestKey.java diff --git a/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java deleted file mode 100644 index 8bf0584..0000000 --- a/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/AbstractSigningTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.eclipse.packager.rpm.signature.pgpainless; - -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.bcpg.BCPGInputStream; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -public class AbstractSigningTest { - - public static final char[] PASSPHRASE = "testkey".toCharArray(); - - public PGPSecretKeyRing readSecretKey() throws IOException { - File keyFile = new File("../rpm/src/test/resources/key/private_key.txt"); - try ( - FileInputStream fileIn = new FileInputStream(keyFile); - ArmoredInputStream armorIn = new ArmoredInputStream(fileIn); - BCPGInputStream bcIn = new BCPGInputStream(armorIn) - ) { - PGPObjectFactory objectFactory = new BcPGPObjectFactory(bcIn); - return (PGPSecretKeyRing) objectFactory.nextObject(); - } - } -} diff --git a/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java index 171b36b..0f495e1 100644 --- a/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java +++ b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/HeaderSignatureProcessorTest.java @@ -1,55 +1,156 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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.pgpainless; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPSecretKey; -import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.util.io.Streams; import org.eclipse.packager.rpm.RpmSignatureTag; import org.eclipse.packager.rpm.header.Header; import org.eclipse.packager.rpm.signature.BcPgpSignatureProcessorFactory; import org.eclipse.packager.rpm.signature.PgpSignatureProcessorFactory; import org.eclipse.packager.rpm.signature.SignatureProcessor; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Named; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.pgpainless.PGPainless; +import org.pgpainless.decryption_verification.ConsumerOptions; +import org.pgpainless.decryption_verification.DecryptionStream; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -public class HeaderSignatureProcessorTest extends AbstractSigningTest { +public class HeaderSignatureProcessorTest { - private static final List instances = new ArrayList<>(); + public static Stream pgpSignatureProcessorFactoriesForKey(TestKey key, int hashAlgorithm) throws PGPException { + PGPPrivateKey signingKey = key.getKey().getSecretKey(key.getSigningKeyId()).extractPrivateKey( + new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) + .build(key.getPassphrase()) + ); + return Stream.of( + Arguments.of( + Named.of("Bouncy Castle", new BcPgpSignatureProcessorFactory(signingKey, hashAlgorithm))), + Arguments.of( + Named.of("PGPainless", new PGPainlessSignatureProcessorFactory(key.getKey(), key.getPassphrase(), hashAlgorithm))) + ); + } - @BeforeEach - public void setup() throws IOException, PGPException { - PGPSecretKeyRing secretKeys = readSecretKey(); + public void verifySignature(boolean expectSuccess, TestKey key, byte[] data, Header header, RpmSignatureTag tag) throws PGPException, IOException { + if (expectSuccess) { + ByteBuffer sig = (ByteBuffer) header.get(tag); + assertNotNull(sig, "Implementation did not populate " + tag + " header"); + + DecryptionStream verificationStream = PGPainless.decryptAndOrVerify() + .onInputStream(new ByteArrayInputStream(data)) + .withOptions(ConsumerOptions.get() + .addVerificationCert(PGPainless.extractCertificate(key.getKey())) + .addVerificationOfDetachedSignatures(new ByteArrayInputStream(sig.array())) + ); + + Streams.drain(verificationStream); // Process all plaintext + verificationStream.close(); // finalize verification + + assertTrue(verificationStream.getMetadata().isVerifiedSigned()); + } else { + assertNull(header.get(tag)); + } + } + + + public static Stream factoriesForComplexRsaKeyWithPrimaryKeyId() throws PGPException { + return pgpSignatureProcessorFactoriesForKey(TestKey.COMPLEX_RSA_KEY_MISMATCHED_KEYID, HashAlgorithmTags.SHA256); + } + + @ParameterizedTest + @MethodSource("factoriesForComplexRsaKeyWithPrimaryKeyId") + public void testWithComplexRsaKeyWithProvidedPrimaryKeyId(PgpSignatureProcessorFactory factory) throws PGPException, IOException { + byte[] data = "Hello, World!".getBytes(StandardCharsets.UTF_8); + + SignatureProcessor processor = factory.createHeaderSignatureProcessor(); + processor.feedHeader(ByteBuffer.wrap(data)); + Header header = new Header<>(); + processor.finish(header); + + verifySignature(true, TestKey.COMPLEX_RSA_KEY_MISMATCHED_KEYID, data, header, RpmSignatureTag.RSAHEADER); + } - // BC - PGPSecretKey signingKey = secretKeys.getSecretKey(); - PGPPrivateKey privateKey = signingKey.extractPrivateKey( - new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) - .build(PASSPHRASE)); - instances.add(new BcPgpSignatureProcessorFactory(privateKey, HashAlgorithmTags.SHA256)); - // PGPainless - instances.add(new PGPainlessSignatureProcessorFactory(secretKeys, PASSPHRASE, HashAlgorithmTags.SHA256)); + public static Stream factoriesForECDSAKey() throws PGPException { + return pgpSignatureProcessorFactoriesForKey(TestKey.FRESH_ECDSA_KEY, HashAlgorithmTags.SHA256); } - @Test - public void test() { - for (PgpSignatureProcessorFactory instance : instances) { - SignatureProcessor processor = instance.createHeaderSignatureProcessor(); - processor.feedHeader(ByteBuffer.wrap("Hello World!".getBytes(StandardCharsets.UTF_8))); - Header header = new Header<>(); + @ParameterizedTest + @MethodSource("factoriesForECDSAKey") + public void testWithECDSAKey(PgpSignatureProcessorFactory factory) throws PGPException, IOException { + byte[] data = "Hello, World!".getBytes(StandardCharsets.UTF_8); + + SignatureProcessor processor = factory.createHeaderSignatureProcessor(); + processor.feedHeader(ByteBuffer.wrap(data)); + Header header = new Header<>(); + try { processor.finish(header); - assertNotNull(header.get(RpmSignatureTag.RSAHEADER), instance.getClass().getName()); + } catch (RuntimeException e) { + // expected + return; } + verifySignature(false, TestKey.FRESH_ECDSA_KEY, data, header, RpmSignatureTag.DSAHEADER); + } + + + public static Stream factoriesForEdDSAKey() throws PGPException { + return pgpSignatureProcessorFactoriesForKey(TestKey.FRESH_EDDSA_KEY, HashAlgorithmTags.SHA256); + } + + @ParameterizedTest + @MethodSource("factoriesForEdDSAKey") + public void testWithEdDSAKey(PgpSignatureProcessorFactory factory) throws PGPException, IOException { + byte[] data = "Hello, World!".getBytes(StandardCharsets.UTF_8); + + SignatureProcessor processor = factory.createHeaderSignatureProcessor(); + processor.feedHeader(ByteBuffer.wrap(data)); + Header header = new Header<>(); + processor.finish(header); + + verifySignature(true, TestKey.FRESH_EDDSA_KEY, data, header, RpmSignatureTag.DSAHEADER); + } + + + public static Stream factoriesForRsaKey() throws PGPException { + return pgpSignatureProcessorFactoriesForKey(TestKey.MAT, HashAlgorithmTags.SHA256); + } + + @ParameterizedTest + @MethodSource("factoriesForRsaKey") + public void testWithRsaKey(PgpSignatureProcessorFactory factory) throws PGPException, IOException { + byte[] data = "Hello, World!".getBytes(StandardCharsets.UTF_8); + + SignatureProcessor processor = factory.createHeaderSignatureProcessor(); + processor.feedHeader(ByteBuffer.wrap(data)); + Header header = new Header<>(); + processor.finish(header); + + verifySignature(true, TestKey.MAT, data, header, RpmSignatureTag.RSAHEADER); } } diff --git a/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/TestKey.java b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/TestKey.java new file mode 100644 index 0000000..47d039b --- /dev/null +++ b/pgpainless/src/test/java/org/eclipse/packager/rpm/signature/pgpainless/TestKey.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2024 Paul Schaub + * + * 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.pgpainless; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; +import org.opentest4j.TestAbortedException; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.key.generation.KeySpec; +import org.pgpainless.key.generation.type.KeyType; +import org.pgpainless.key.generation.type.ecc.EllipticCurve; +import org.pgpainless.key.generation.type.rsa.RsaLength; +import org.pgpainless.key.info.KeyRingInfo; +import org.pgpainless.util.Passphrase; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; + +public abstract class TestKey { + + @Nonnull + public abstract PGPSecretKeyRing getKey(); + + @Nullable + public abstract char[] getPassphrase(); + + public abstract long getPrimaryKeyId(); + + public abstract long getSigningKeyId(); + + public static TestKey MAT = new TestKey() { + @Nonnull + @Override + public PGPSecretKeyRing getKey() { + // The MAT test key is from the test resources directory of the rpm module + File keyFile = new File("../rpm/src/test/resources/key/private_key.txt"); + try ( + FileInputStream fileIn = new FileInputStream(keyFile); + ArmoredInputStream armorIn = new ArmoredInputStream(fileIn); + BCPGInputStream bcIn = new BCPGInputStream(armorIn) + ) { + PGPObjectFactory objectFactory = new BcPGPObjectFactory(bcIn); + return (PGPSecretKeyRing) objectFactory.nextObject(); + } catch (IOException e) { + throw new TestAbortedException("Could not read test key", e); + } + } + + @Nullable + @Override + public char[] getPassphrase() { + return "testkey".toCharArray(); + } + + @Override + public long getPrimaryKeyId() { + return 1757664734257043235L; + } + + @Override + public long getSigningKeyId() { + return getPrimaryKeyId(); + } + }; + + /** + * An OpenPGP key that gets generated on the fly. + * It consists of an EdDSA primary key capable of certification, along with + *
    + *
  • an EdDSA subkey for generating signatures, and
  • + *
  • an XDH subkey for encryption.
  • + *
+ */ + public static TestKey FRESH_EDDSA_KEY = new TestKey() { + + private final PGPSecretKeyRing key; + private final long signingKeyId; + + { + try { + key = PGPainless.generateKeyRing() + .modernKeyRing("Random EdDSA "); + KeyRingInfo info = PGPainless.inspectKeyRing(key); + signingKeyId = info.getSigningSubkeys().get(0).getKeyID(); + } catch (PGPException | InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + throw new RuntimeException("Could not generate fresh EdDSA test key", e); + } + } + + @Nonnull + @Override + public PGPSecretKeyRing getKey() { + return key; + } + + @Nullable + @Override + public char[] getPassphrase() { + return null; + } + + @Override + public long getPrimaryKeyId() { + return key.getPublicKey().getKeyID(); + } + + @Override + public long getSigningKeyId() { + return signingKeyId; + } + }; + + public static TestKey FRESH_ECDSA_KEY = new TestKey() { + + private final PGPSecretKeyRing key; + private final long signingKeyId; + + { + try { + key = PGPainless.buildKeyRing() + .setPrimaryKey(KeySpec.getBuilder(KeyType.ECDSA(EllipticCurve._SECP256K1), KeyFlag.CERTIFY_OTHER)) + .addSubkey(KeySpec.getBuilder(KeyType.ECDSA(EllipticCurve._SECP256K1), KeyFlag.SIGN_DATA)) + .addSubkey(KeySpec.getBuilder(KeyType.ECDH(EllipticCurve._SECP256K1), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) + .addUserId("Random ECDSA ") + .build(); + KeyRingInfo info = PGPainless.inspectKeyRing(key); + signingKeyId = info.getSigningSubkeys().get(0).getKeyID(); + } catch (PGPException | InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + throw new RuntimeException("Could not generate fresh ECDSA test key", e); + } + } + + @Nonnull + @Override + public PGPSecretKeyRing getKey() { + return key; + } + + @Nullable + @Override + public char[] getPassphrase() { + return null; + } + + @Override + public long getPrimaryKeyId() { + return key.getPublicKey().getKeyID(); + } + + @Override + public long getSigningKeyId() { + return signingKeyId; + } + }; + + public static TestKey COMPLEX_RSA_KEY = new TestKey() { + + private final PGPSecretKeyRing key; + private final long signingKeyId; + + { + try { + // RSA key with dedicated certifying primary key, signing subkey and encryption subkey + key = PGPainless.generateKeyRing() + .rsaKeyRing("Complex RSA ", RsaLength._4096, Passphrase.emptyPassphrase()); + KeyRingInfo info = PGPainless.inspectKeyRing(key); + signingKeyId = info.getSigningSubkeys().get(0).getKeyID(); + } catch (PGPException | InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { + throw new RuntimeException("Could not generate complex RSA key", e); + } + } + + @Nonnull + @Override + public PGPSecretKeyRing getKey() { + return key; + } + + @Nullable + @Override + public char[] getPassphrase() { + return null; + } + + @Override + public long getPrimaryKeyId() { + return key.getPublicKey().getKeyID(); + } + + @Override + public long getSigningKeyId() { + return signingKeyId; + } + }; + + public static TestKey COMPLEX_RSA_KEY_MISMATCHED_KEYID = new TestKey() { + @Nonnull + @Override + public PGPSecretKeyRing getKey() { + return COMPLEX_RSA_KEY.getKey(); + } + + @Nullable + @Override + public char[] getPassphrase() { + return COMPLEX_RSA_KEY.getPassphrase(); + } + + @Override + public long getPrimaryKeyId() { + return COMPLEX_RSA_KEY.getPrimaryKeyId(); + } + + @Override + public long getSigningKeyId() { + return COMPLEX_RSA_KEY.getPrimaryKeyId(); // Emulate user-error, primary key is not signing capable + } + }; +}