From 3cbd2a931731ea7e7381812010464db438a3f782 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 29 Sep 2025 14:32:07 +0200 Subject: [PATCH] Bump PGPainless to 2.0.0, cert-d-java to 0.2.3 --- .../java/pgp/cert_d/cli/commands/Get.java | 12 +- .../java/pgp/cert_d/cli/commands/Import.java | 22 ++- .../java/pgp/cert_d/cli/commands/Setup.java | 15 +- .../pgp/cert_d/cli/commands/SetupTest.java | 33 ++--- .../certificate_store/CertificateFactory.java | 33 +++++ .../certificate_store/KeyFactory.java | 27 ++++ .../certificate_store/KeyMaterialReader.java | 43 +++--- .../certificate_store/MergeCallbacks.java | 139 ++++++++++-------- .../SharedPGPCertificateDirectoryTest.java | 41 +++--- .../KeyMaterialReaderTest.java | 6 +- version.gradle | 4 +- 11 files changed, 223 insertions(+), 152 deletions(-) diff --git a/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Get.java b/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Get.java index 6e35ecc..fa71e12 100644 --- a/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Get.java +++ b/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Get.java @@ -4,11 +4,9 @@ package pgp.cert_d.cli.commands; -import org.bouncycastle.bcpg.ArmoredOutputStream; -import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.util.io.Streams; import org.pgpainless.PGPainless; -import org.pgpainless.util.ArmorUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pgp.cert_d.SpecialNames; @@ -54,10 +52,10 @@ public class Get implements Runnable { } if (armor) { - PGPKeyRing keyRing = PGPainless.readKeyRing().keyRing(record.getInputStream()); - ArmoredOutputStream armorOut = ArmorUtils.toAsciiArmoredStream(keyRing, System.out); - Streams.pipeAll(record.getInputStream(), armorOut); - armorOut.close(); + OpenPGPCertificate certOrKey = PGPainless.getInstance().readKey().parseCertificateOrKey(record.getInputStream()); + // CHECKSTYLE:OFF + System.out.println(certOrKey.toAsciiArmoredString()); + // CHECKSTYLE:ON } else { Streams.pipeAll(record.getInputStream(), System.out); } diff --git a/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Import.java b/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Import.java index 16c7850..3fabe18 100644 --- a/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Import.java +++ b/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Import.java @@ -4,10 +4,8 @@ package pgp.cert_d.cli.commands; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.pgpainless.PGPainless; -import org.pgpainless.key.OpenPgpFingerprint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.pgpainless.certificate_store.MergeCallbacks; @@ -28,19 +26,19 @@ public class Import implements Runnable { @Override public void run() { try { - PGPPublicKeyRingCollection certificates = PGPainless.readKeyRing().publicKeyRingCollection(System.in); - for (PGPPublicKeyRing cert : certificates) { - ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded()); + java.util.List certsOrKeys = PGPainless.getInstance().readKey().parseKeysOrCertificates(System.in); + for (OpenPGPCertificate toInsert : certsOrKeys) { try { - Certificate certificate = PGPCertDCli.getCertificateDirectory() - .insert(certIn, MergeCallbacks.mergeWithExisting()); - LOGGER.info(certificate.getFingerprint()); + Certificate inserted = PGPCertDCli.getCertificateDirectory().insert( + new ByteArrayInputStream(toInsert.getEncoded()), + MergeCallbacks.mergeWithExisting()); + LOGGER.info(inserted.getFingerprint()); } catch (BadDataException e) { - LOGGER.error("Certificate " + OpenPgpFingerprint.of(cert) + " contains bad data.", e); + LOGGER.error("Certificate " + toInsert.getKeyIdentifier() + " contains bad data.", e); } catch (IOException e) { - LOGGER.error("IO error importing certificate " + OpenPgpFingerprint.of(cert), e); + LOGGER.error("IO error importing certificate " + toInsert.getKeyIdentifier(), e); } catch (InterruptedException e) { - LOGGER.error("Thread interrupted while importing certificate " + OpenPgpFingerprint.of(cert), e); + LOGGER.error("Thread interrupted while importing certificate " + toInsert.getKeyIdentifier(), e); System.exit(1); } } diff --git a/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Setup.java b/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Setup.java index 7c762f1..48418dd 100644 --- a/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Setup.java +++ b/pgpainless-cert-d-cli/src/main/java/pgp/cert_d/cli/commands/Setup.java @@ -4,9 +4,10 @@ package pgp.cert_d.cli.commands; -import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.algorithm.OpenPGPKeyVersion; import org.pgpainless.key.generation.KeyRingBuilder; import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.generation.type.KeyType; @@ -46,7 +47,7 @@ public class Setup implements Runnable { @Override public void run() { - PGPSecretKeyRing trustRoot; + OpenPGPKey trustRoot; if (exclusive == null) { trustRoot = generateTrustRoot(Passphrase.emptyPassphrase()); } else { @@ -76,9 +77,9 @@ public class Setup implements Runnable { } } - private PGPSecretKeyRing generateTrustRoot(Passphrase passphrase) { - PGPSecretKeyRing trustRoot; - KeyRingBuilder builder = PGPainless.buildKeyRing() + private OpenPGPKey generateTrustRoot(Passphrase passphrase) { + OpenPGPKey trustRoot; + KeyRingBuilder builder = PGPainless.getInstance().buildKey(OpenPGPKeyVersion.v4) .addUserId("trust-root"); if (passphrase != null) { builder.setPassphrase(passphrase); @@ -88,9 +89,9 @@ public class Setup implements Runnable { return trustRoot; } - private PGPSecretKeyRing readTrustRoot(InputStream inputStream) { + private OpenPGPKey readTrustRoot(InputStream inputStream) { try { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(inputStream); + OpenPGPKey secretKeys = PGPainless.getInstance().readKey().parseKey(inputStream); if (secretKeys == null) { throw new BadDataException(); } diff --git a/pgpainless-cert-d-cli/src/test/java/pgp/cert_d/cli/commands/SetupTest.java b/pgpainless-cert-d-cli/src/test/java/pgp/cert_d/cli/commands/SetupTest.java index d3a505a..992e9f2 100644 --- a/pgpainless-cert-d-cli/src/test/java/pgp/cert_d/cli/commands/SetupTest.java +++ b/pgpainless-cert-d-cli/src/test/java/pgp/cert_d/cli/commands/SetupTest.java @@ -5,16 +5,13 @@ package pgp.cert_d.cli.commands; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.certificate_store.PGPainlessCertD; import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.info.KeyInfo; -import org.pgpainless.key.protection.UnlockSecretKey; -import org.pgpainless.util.Passphrase; import pgp.cert_d.cli.InstantiateCLI; import pgp.cert_d.cli.PGPCertDCli; import pgp.certificate_store.certificate.Key; @@ -24,12 +21,14 @@ import pgp.certificate_store.exception.BadDataException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.util.NoSuchElementException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -59,29 +58,29 @@ public class SetupTest { PGPCertDCli.main(new String[] {"setup"}); KeyMaterial trustRoot = store.getTrustRoot(); assertNotNull(trustRoot); - assertTrue(trustRoot instanceof Key); + assertInstanceOf(Key.class, trustRoot); // Check that key has no password - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(trustRoot.getInputStream()); - assertTrue(KeyInfo.isDecrypted(secretKeys.getSecretKey())); + OpenPGPKey key = PGPainless.getInstance().readKey().parseKey(trustRoot.getInputStream()); + assertFalse(key.getPrimarySecretKey().isLocked(), "trust-root MUST NOT be passphrase protected here"); } @Test public void testSetupWithPassword() - throws BadDataException, IOException, PGPException { + throws BadDataException, IOException { assertThrows(NoSuchElementException.class, () -> store.getTrustRoot()); PGPCertDCli.main(new String[] {"setup", "--with-password", "sw0rdf1sh"}); KeyMaterial trustRoot = store.getTrustRoot(); assertNotNull(trustRoot); - assertTrue(trustRoot instanceof Key); + assertInstanceOf(Key.class, trustRoot); // Check that key is encrypted - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(trustRoot.getInputStream()); - assertTrue(KeyInfo.isEncrypted(secretKeys.getSecretKey())); + OpenPGPKey key = PGPainless.getInstance().readKey().parseKey(trustRoot.getInputStream()); + assertTrue(key.getPrimarySecretKey().isLocked()); // Check that password matches - assertNotNull(UnlockSecretKey.unlockSecretKey( - secretKeys.getSecretKey(), Passphrase.fromPassword("sw0rdf1sh"))); + assertTrue(key.getPrimarySecretKey().isPassphraseCorrect("sw0rdf1sh".toCharArray()), + "Key MUST be able to be unlocked using passphrase"); } @Test @@ -90,12 +89,12 @@ public class SetupTest { BadDataException, IOException { assertThrows(NoSuchElementException.class, () -> store.getTrustRoot()); - PGPSecretKeyRing trustRoot = PGPainless.generateKeyRing() + OpenPGPKey trustRoot = PGPainless.getInstance().generateKey() .modernKeyRing("trust-root"); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(trustRoot); - String armored = PGPainless.asciiArmor(trustRoot); + String armored = trustRoot.toAsciiArmoredString(); ByteArrayInputStream trustRootIn = new ByteArrayInputStream( - armored.getBytes(Charset.forName("UTF8"))); + armored.getBytes(StandardCharsets.UTF_8)); InputStream originalStdin = System.in; System.setIn(trustRootIn); diff --git a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/CertificateFactory.java b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/CertificateFactory.java index ceb1719..57708f9 100644 --- a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/CertificateFactory.java +++ b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/CertificateFactory.java @@ -4,8 +4,10 @@ package org.pgpainless.certificate_store; +import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.pgpainless.key.OpenPgpFingerprint; import pgp.certificate_store.certificate.Certificate; @@ -13,9 +15,21 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; public class CertificateFactory { + /** + * Create a {@link Certificate} from the given {@link PGPPublicKeyRing} and tag. + * + * @param publicKeyRing PGPPublicKeyRing + * @param tag tag + * @return certificate + * @throws IOException if the certificate cannot be encoded + * + * @deprecated use {@link #certificateFromOpenPGPCertificate(OpenPGPCertificate, Long)} instead. + */ + @Deprecated public static Certificate certificateFromPublicKeyRing(PGPPublicKeyRing publicKeyRing, Long tag) throws IOException { byte[] bytes = publicKeyRing.getEncoded(); @@ -28,4 +42,23 @@ public class CertificateFactory { return new Certificate(bytes, fingerprint, subkeyIds, tag); } + + /** + * Create a {@link Certificate} from the given {@link OpenPGPCertificate} and tag. + * + * @param openPGPCertificate OpenPGPCertificate + * @param tag tag + * @return certificate + * @throws IOException if the certificate cannot be encoded + */ + public static Certificate certificateFromOpenPGPCertificate(OpenPGPCertificate openPGPCertificate, Long tag) + throws IOException { + byte[] bytes = openPGPCertificate.getEncoded(PacketFormat.ROUNDTRIP); + String fingerprint = OpenPgpFingerprint.of(openPGPCertificate).getFingerprint().toLowerCase(); + List subkeyIds = openPGPCertificate.getValidKeys() + .stream() + .map(it -> it.getKeyIdentifier().getKeyId()) + .collect(Collectors.toList()); + return new Certificate(bytes, fingerprint, subkeyIds, tag); + } } diff --git a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/KeyFactory.java b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/KeyFactory.java index a43f76f..510353b 100644 --- a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/KeyFactory.java +++ b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/KeyFactory.java @@ -4,8 +4,10 @@ package org.pgpainless.certificate_store; +import org.bouncycastle.bcpg.PacketFormat; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.pgpainless.PGPainless; import pgp.certificate_store.certificate.Certificate; import pgp.certificate_store.certificate.Key; @@ -14,10 +16,35 @@ import java.io.IOException; public class KeyFactory { + /** + * Create a {@link Key} from the given {@link PGPSecretKeyRing} and tag. + * + * @param secretKeyRing PGPSecretKeyRing + * @param tag tag + * @return key + * @throws IOException if the key cannot be encoded + * + * @deprecated use {@link #keyFromOpenPGPKey(OpenPGPKey, Long)} instead. + */ + @Deprecated public static Key keyFromSecretKeyRing(PGPSecretKeyRing secretKeyRing, Long tag) throws IOException { byte[] bytes = secretKeyRing.getEncoded(); PGPPublicKeyRing publicKeyRing = PGPainless.extractCertificate(secretKeyRing); Certificate certificate = CertificateFactory.certificateFromPublicKeyRing(publicKeyRing, tag); return new Key(bytes, certificate, tag); } + + /** + * Create a {@link Key} from the given {@link OpenPGPKey} and tag. + * + * @param key OpenPGP key + * @param tag tag + * @return key + * @throws IOException if the key cannot be encoded + */ + public static Key keyFromOpenPGPKey(OpenPGPKey key, Long tag) throws IOException { + byte[] bytes = key.getEncoded(PacketFormat.ROUNDTRIP); + Certificate certificate = CertificateFactory.certificateFromOpenPGPCertificate(key.toCertificate(), tag); + return new Key(bytes, certificate, tag); + } } diff --git a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/KeyMaterialReader.java b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/KeyMaterialReader.java index 8ba3b9d..9a2ab9f 100644 --- a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/KeyMaterialReader.java +++ b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/KeyMaterialReader.java @@ -4,14 +4,14 @@ package org.pgpainless.certificate_store; -import org.bouncycastle.openpgp.PGPKeyRing; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.pgpainless.PGPainless; import pgp.certificate_store.certificate.KeyMaterial; import pgp.certificate_store.certificate.KeyMaterialReaderBackend; import pgp.certificate_store.exception.BadDataException; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -19,28 +19,25 @@ public class KeyMaterialReader implements KeyMaterialReaderBackend { @Override public KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException { - PGPKeyRing keyMaterial; + OpenPGPCertificate keyOrCertificate; try { - keyMaterial = PGPainless.readKeyRing().keyRing(data); - } catch (IOException e) { - String msg = e.getMessage(); - if (msg == null) { - throw e; - } - if (msg.contains("unknown object in stream") || - msg.contains("unexpected end of file in armored stream.") || - msg.contains("invalid header encountered")) { - throw new BadDataException(); - } else { - throw e; - } - } - if (keyMaterial instanceof PGPSecretKeyRing) { - return KeyFactory.keyFromSecretKeyRing((PGPSecretKeyRing) keyMaterial, tag); - } else if (keyMaterial instanceof PGPPublicKeyRing) { - return CertificateFactory.certificateFromPublicKeyRing((PGPPublicKeyRing) keyMaterial, tag); - } else { + keyOrCertificate = PGPainless.getInstance() + .readKey() + .parseCertificateOrKey(data); + } catch (EOFException e) { + // TODO: Pass 'e' once cert-d-java is bumped to 0.2.4 throw new BadDataException(); + } catch (IOException e) { + if (e.getMessage().contains("Neither a certificate, nor secret key.")) { + throw new BadDataException(); + } + throw e; + } + + if (keyOrCertificate.isSecretKey()) { + return KeyFactory.keyFromOpenPGPKey((OpenPGPKey) keyOrCertificate, tag); + } else { + return CertificateFactory.certificateFromOpenPGPCertificate(keyOrCertificate, tag); } } } diff --git a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/MergeCallbacks.java b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/MergeCallbacks.java index 000e935..034cbce 100644 --- a/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/MergeCallbacks.java +++ b/pgpainless-cert-d/src/main/java/org/pgpainless/certificate_store/MergeCallbacks.java @@ -5,19 +5,21 @@ package org.pgpainless.certificate_store; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPPublicKey; -import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.pgpainless.PGPainless; import org.pgpainless.key.OpenPgpFingerprint; import pgp.certificate_store.certificate.KeyMaterial; import pgp.certificate_store.certificate.KeyMaterialMerger; -import pgp.certificate_store.exception.BadDataException; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import java.util.List; public class MergeCallbacks { @@ -41,79 +43,95 @@ public class MergeCallbacks { return data; } + PGPainless api = PGPainless.getInstance(); + + OpenPGPCertificate existingCert = api.readKey().parseCertificateOrKey(existing.getInputStream()); + OpenPGPCertificate updatedCert = api.readKey().parseCertificateOrKey(data.getInputStream()); + + OpenPGPCertificate mergedCert = mergeCertificates(updatedCert, existingCert); + + printOutDifferences(existingCert, mergedCert); + return toKeyMaterial(mergedCert); + } + + private OpenPGPCertificate mergeCertificates(OpenPGPCertificate updatedCertOrKey, + OpenPGPCertificate existingCertOrKey) { + if (!existingCertOrKey.getKeyIdentifier().matchesExplicit(updatedCertOrKey.getKeyIdentifier())) { + throw new IllegalArgumentException("Not the same OpenPGP key/certificate: Mismatched primary key."); + } + + OpenPGPCertificate merged; + try { - PGPKeyRing existingKeyRing = PGPainless.readKeyRing().keyRing(existing.getInputStream()); - PGPKeyRing updatedKeyRing = PGPainless.readKeyRing().keyRing(data.getInputStream()); + if (existingCertOrKey.isSecretKey()) { + OpenPGPKey existingKey = (OpenPGPKey) existingCertOrKey; - PGPKeyRing mergedKeyRing; + if (updatedCertOrKey.isSecretKey()) { + // Merge key with key + OpenPGPKey updatedKey = (OpenPGPKey) updatedCertOrKey; + OpenPGPCertificate mergedCertPart = OpenPGPCertificate.join( + existingKey.toCertificate(), + updatedKey.toCertificate()); - if (existingKeyRing instanceof PGPPublicKeyRing) { - mergedKeyRing = mergeWithCert((PGPPublicKeyRing) existingKeyRing, updatedKeyRing); - } else if (existingKeyRing instanceof PGPSecretKeyRing) { - mergedKeyRing = mergeWithKey(existingKeyRing, updatedKeyRing); + List mergedSecretKeys = new ArrayList<>(); + Iterator existingKeysIterator = existingKey.getPGPSecretKeyRing().getSecretKeys(); + while (existingKeysIterator.hasNext()) { + mergedSecretKeys.add(existingKeysIterator.next()); + } + + Iterator updatedKeysIterator = updatedKey.getPGPSecretKeyRing().getSecretKeys(); + while (updatedKeysIterator.hasNext()) { + PGPSecretKey next = updatedKeysIterator.next(); + if (existingKey.getPGPSecretKeyRing().getSecretKey(next.getKeyIdentifier()) == null) { + mergedSecretKeys.add(next); + } + } + PGPSecretKeyRing mergedSecretKeyRing = new PGPSecretKeyRing(mergedSecretKeys); + merged = new OpenPGPKey( + PGPSecretKeyRing.replacePublicKeys( + mergedSecretKeyRing, + mergedCertPart.getPGPPublicKeyRing())); + } else { + // Merge key with cert + OpenPGPCertificate mergedCertPart = OpenPGPCertificate.join( + existingKey.toCertificate(), + updatedCertOrKey); + merged = new OpenPGPKey( + PGPSecretKeyRing.replacePublicKeys( + existingKey.getPGPSecretKeyRing(), + mergedCertPart.getPGPPublicKeyRing())); + } } else { - throw new IOException(new BadDataException()); + if (updatedCertOrKey.isSecretKey()) { + // Swap update and existing cert + return mergeCertificates(existingCertOrKey, updatedCertOrKey); + } + + // Merge cert with cert + return OpenPGPCertificate.join(existingCertOrKey, updatedCertOrKey); } - printOutDifferences(existingKeyRing, mergedKeyRing); - - return toKeyMaterial(mergedKeyRing); - + return merged; } catch (PGPException e) { throw new RuntimeException(e); } } - private PGPKeyRing mergeWithCert(PGPPublicKeyRing existingKeyRing, PGPKeyRing updatedKeyRing) - throws PGPException, IOException { - PGPKeyRing mergedKeyRing; - PGPPublicKeyRing existingCert = existingKeyRing; - if (updatedKeyRing instanceof PGPPublicKeyRing) { - mergedKeyRing = PGPPublicKeyRing.join(existingCert, (PGPPublicKeyRing) updatedKeyRing); - } else if (updatedKeyRing instanceof PGPSecretKeyRing) { - PGPPublicKeyRing updatedPublicKeys = PGPainless.extractCertificate((PGPSecretKeyRing) updatedKeyRing); - PGPPublicKeyRing mergedPublicKeys = PGPPublicKeyRing.join(existingCert, updatedPublicKeys); - updatedKeyRing = PGPSecretKeyRing.replacePublicKeys((PGPSecretKeyRing) updatedKeyRing, mergedPublicKeys); - mergedKeyRing = updatedKeyRing; - } else { - throw new IOException(new BadDataException()); - } - return mergedKeyRing; - } - - private PGPKeyRing mergeWithKey(PGPKeyRing existingKeyRing, PGPKeyRing updatedKeyRing) - throws PGPException, IOException { - PGPKeyRing mergedKeyRing; - PGPSecretKeyRing existingKey = (PGPSecretKeyRing) existingKeyRing; - PGPPublicKeyRing existingCert = PGPainless.extractCertificate(existingKey); - if (updatedKeyRing instanceof PGPPublicKeyRing) { - PGPPublicKeyRing updatedCert = (PGPPublicKeyRing) updatedKeyRing; - PGPPublicKeyRing mergedCert = PGPPublicKeyRing.join(existingCert, updatedCert); - mergedKeyRing = PGPSecretKeyRing.replacePublicKeys(existingKey, mergedCert); - } else if (updatedKeyRing instanceof PGPSecretKeyRing) { - // Merging keys is not supported - mergedKeyRing = existingKeyRing; - } else { - throw new IOException(new BadDataException()); - } - return mergedKeyRing; - } - - private KeyMaterial toKeyMaterial(PGPKeyRing mergedKeyRing) + private KeyMaterial toKeyMaterial(OpenPGPCertificate mergedCertificate) throws IOException { - if (mergedKeyRing instanceof PGPPublicKeyRing) { - return CertificateFactory.certificateFromPublicKeyRing((PGPPublicKeyRing) mergedKeyRing, null); + if (mergedCertificate.isSecretKey()) { + return KeyFactory.keyFromOpenPGPKey((OpenPGPKey) mergedCertificate, null); } else { - return KeyFactory.keyFromSecretKeyRing((PGPSecretKeyRing) mergedKeyRing, null); + return CertificateFactory.certificateFromOpenPGPCertificate(mergedCertificate, null); } } - private void printOutDifferences(PGPKeyRing existingCert, PGPKeyRing mergedCert) throws IOException { + private void printOutDifferences(OpenPGPCertificate existingCert, OpenPGPCertificate mergedCert) throws IOException { int numSigsBefore = countSigs(existingCert); int numSigsAfter = countSigs(mergedCert); int newSigs = numSigsAfter - numSigsBefore; - int numUidsBefore = count(existingCert.getPublicKey().getUserIDs()); - int numUidsAfter = count(mergedCert.getPublicKey().getUserIDs()); + int numUidsBefore = count(existingCert.getAllUserIds().iterator()); + int numUidsAfter = count(mergedCert.getAllUserIds().iterator()); int newUids = numUidsAfter - numUidsBefore; if (!Arrays.equals(existingCert.getEncoded(), mergedCert.getEncoded())) { @@ -140,11 +158,10 @@ public class MergeCallbacks { } } - private int countSigs(PGPKeyRing keys) { + private int countSigs(OpenPGPCertificate keys) { int numSigs = 0; - Iterator iterator = keys.getPublicKeys(); - while (iterator.hasNext()) { - PGPPublicKey key = iterator.next(); + for (OpenPGPCertificate.OpenPGPComponentKey componentKey : keys.getKeys()) { + PGPPublicKey key = componentKey.getPGPPublicKey(); numSigs += count(key.getSignatures()); } return numSigs; diff --git a/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryTest.java b/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryTest.java index 38fd992..b16b948 100644 --- a/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryTest.java +++ b/pgpainless-cert-d/src/test/java/org/pgpainless/cert_d/SharedPGPCertificateDirectoryTest.java @@ -14,8 +14,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -24,14 +22,14 @@ import java.util.NoSuchElementException; import java.util.Set; import java.util.stream.Stream; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.api.OpenPGPCertificate; +import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.util.io.Streams; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.algorithm.OpenPGPKeyVersion; import org.pgpainless.certificate_store.PGPainlessCertD; import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.generation.KeySpec; @@ -68,10 +66,11 @@ public class SharedPGPCertificateDirectoryTest { @ParameterizedTest @MethodSource("provideTestSubjects") public void simpleInsertGet(PGPainlessCertD directory) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, - BadDataException, InterruptedException, BadNameException { - PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice"); - PGPPublicKeyRing cert = PGPainless.extractCertificate(key); + throws IOException, BadDataException, InterruptedException { + OpenPGPKey key = PGPainless.getInstance() + .generateKey(OpenPGPKeyVersion.v4) + .modernKeyRing("Alice"); + OpenPGPCertificate cert = key.toCertificate(); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert); ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded()); @@ -90,13 +89,14 @@ public class SharedPGPCertificateDirectoryTest { @ParameterizedTest @MethodSource("provideTestSubjects") public void simpleInsertGetBySpecialName(PGPainlessCertD directory) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, - BadDataException, InterruptedException, BadNameException { - PGPSecretKeyRing key = PGPainless.buildKeyRing() + throws IOException, BadDataException, InterruptedException, BadNameException { + OpenPGPKey key = PGPainless.getInstance() + .buildKey(OpenPGPKeyVersion.v4) .addUserId("trust-root") .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER)) .build(); - PGPPublicKeyRing trustRoot = PGPainless.extractCertificate(key); + + OpenPGPCertificate trustRoot = key.toCertificate(); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(trustRoot); ByteArrayInputStream certIn = new ByteArrayInputStream(trustRoot.getEncoded()); @@ -115,20 +115,21 @@ public class SharedPGPCertificateDirectoryTest { @ParameterizedTest @MethodSource("provideTestSubjects") public void testGetItemsAndFingerprints(PGPainlessCertD directory) - throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, - BadDataException, InterruptedException, BadNameException { + throws IOException, BadDataException, InterruptedException, BadNameException { - PGPSecretKeyRing trustRootKey = PGPainless.generateKeyRing().modernKeyRing("Alice"); - PGPPublicKeyRing trustRootCert = PGPainless.extractCertificate(trustRootKey); + OpenPGPKey trustRootKey = PGPainless.getInstance().generateKey(OpenPGPKeyVersion.v4) + .modernKeyRing("Alice"); + OpenPGPCertificate trustRootCert = trustRootKey.toCertificate(); OpenPgpFingerprint trustRootFingerprint = OpenPgpFingerprint.of(trustRootCert); ByteArrayInputStream trustRootCertIn = new ByteArrayInputStream(trustRootCert.getEncoded()); directory.insertWithSpecialName("trust-root", trustRootCertIn, dummyMerge); final int certificateCount = 3; - Map certificateMap = new HashMap<>(); + Map certificateMap = new HashMap<>(); for (int i = 0; i < certificateCount; i++) { - PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice"); - PGPPublicKeyRing cert = PGPainless.extractCertificate(key); + OpenPGPKey key = PGPainless.getInstance().generateKey(OpenPGPKeyVersion.v4) + .modernKeyRing("Alice"); + OpenPGPCertificate cert = key.toCertificate(); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert); certificateMap.put(fingerprint.toString().toLowerCase(), cert); diff --git a/pgpainless-cert-d/src/test/java/org/pgpainless/certificate_store/KeyMaterialReaderTest.java b/pgpainless-cert-d/src/test/java/org/pgpainless/certificate_store/KeyMaterialReaderTest.java index 3899a00..35ffad0 100644 --- a/pgpainless-cert-d/src/test/java/org/pgpainless/certificate_store/KeyMaterialReaderTest.java +++ b/pgpainless-cert-d/src/test/java/org/pgpainless/certificate_store/KeyMaterialReaderTest.java @@ -16,11 +16,11 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; public class KeyMaterialReaderTest { @@ -111,7 +111,7 @@ public class KeyMaterialReaderTest { public void readKeyTest() throws BadDataException, IOException { KeyMaterial keyMaterial = reader.read(new ByteArrayInputStream(KEY.getBytes(UTF8)), 12L); assertNotNull(keyMaterial); - assertTrue(keyMaterial instanceof Key); + assertInstanceOf(Key.class, keyMaterial); Key key = (Key) keyMaterial; assertEquals("b21aabbf15df0fda37424de9ad008384ad0a064c", key.getFingerprint()); assertEquals(12L, key.getTag()); @@ -125,7 +125,7 @@ public class KeyMaterialReaderTest { public void readCertTest() throws BadDataException, IOException { KeyMaterial keyMaterial = reader.read(new ByteArrayInputStream(CERT.getBytes(UTF8)), null); assertNotNull(keyMaterial); - assertTrue(keyMaterial instanceof Certificate); + assertInstanceOf(Certificate.class, keyMaterial); Certificate certificate = (Certificate) keyMaterial; assertEquals("b21aabbf15df0fda37424de9ad008384ad0a064c", certificate.getFingerprint()); assertNull(certificate.getTag()); diff --git a/version.gradle b/version.gradle index cb1010b..608c055 100644 --- a/version.gradle +++ b/version.gradle @@ -12,8 +12,8 @@ allprojects { junitVersion = '5.8.2' mockitoVersion = '4.5.1' bouncyCastleVersion = '1.82' - pgpainlessVersion = '1.7.7' - pgpCertDJavaVersion = '0.2.2' + pgpainlessVersion = '2.0.0' + pgpCertDJavaVersion = '0.2.3' picocliVersion = '4.6.3' } }