Bump PGPainless to 2.0.0, cert-d-java to 0.2.3

This commit is contained in:
Paul Schaub 2025-09-29 14:32:07 +02:00
parent 7a0b79254d
commit 3cbd2a9317
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
11 changed files with 223 additions and 152 deletions

View file

@ -4,11 +4,9 @@
package pgp.cert_d.cli.commands; package pgp.cert_d.cli.commands;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.util.io.Streams; import org.bouncycastle.util.io.Streams;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.util.ArmorUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import pgp.cert_d.SpecialNames; import pgp.cert_d.SpecialNames;
@ -54,10 +52,10 @@ public class Get implements Runnable {
} }
if (armor) { if (armor) {
PGPKeyRing keyRing = PGPainless.readKeyRing().keyRing(record.getInputStream()); OpenPGPCertificate certOrKey = PGPainless.getInstance().readKey().parseCertificateOrKey(record.getInputStream());
ArmoredOutputStream armorOut = ArmorUtils.toAsciiArmoredStream(keyRing, System.out); // CHECKSTYLE:OFF
Streams.pipeAll(record.getInputStream(), armorOut); System.out.println(certOrKey.toAsciiArmoredString());
armorOut.close(); // CHECKSTYLE:ON
} else { } else {
Streams.pipeAll(record.getInputStream(), System.out); Streams.pipeAll(record.getInputStream(), System.out);
} }

View file

@ -4,10 +4,8 @@
package pgp.cert_d.cli.commands; package pgp.cert_d.cli.commands;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.key.OpenPgpFingerprint;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.pgpainless.certificate_store.MergeCallbacks; import org.pgpainless.certificate_store.MergeCallbacks;
@ -28,19 +26,19 @@ public class Import implements Runnable {
@Override @Override
public void run() { public void run() {
try { try {
PGPPublicKeyRingCollection certificates = PGPainless.readKeyRing().publicKeyRingCollection(System.in); java.util.List<OpenPGPCertificate> certsOrKeys = PGPainless.getInstance().readKey().parseKeysOrCertificates(System.in);
for (PGPPublicKeyRing cert : certificates) { for (OpenPGPCertificate toInsert : certsOrKeys) {
ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded());
try { try {
Certificate certificate = PGPCertDCli.getCertificateDirectory() Certificate inserted = PGPCertDCli.getCertificateDirectory().insert(
.insert(certIn, MergeCallbacks.mergeWithExisting()); new ByteArrayInputStream(toInsert.getEncoded()),
LOGGER.info(certificate.getFingerprint()); MergeCallbacks.mergeWithExisting());
LOGGER.info(inserted.getFingerprint());
} catch (BadDataException e) { } 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) { } 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) { } 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); System.exit(1);
} }
} }

View file

@ -4,9 +4,10 @@
package pgp.cert_d.cli.commands; package pgp.cert_d.cli.commands;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.OpenPGPKeyVersion;
import org.pgpainless.key.generation.KeyRingBuilder; import org.pgpainless.key.generation.KeyRingBuilder;
import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.type.KeyType; import org.pgpainless.key.generation.type.KeyType;
@ -46,7 +47,7 @@ public class Setup implements Runnable {
@Override @Override
public void run() { public void run() {
PGPSecretKeyRing trustRoot; OpenPGPKey trustRoot;
if (exclusive == null) { if (exclusive == null) {
trustRoot = generateTrustRoot(Passphrase.emptyPassphrase()); trustRoot = generateTrustRoot(Passphrase.emptyPassphrase());
} else { } else {
@ -76,9 +77,9 @@ public class Setup implements Runnable {
} }
} }
private PGPSecretKeyRing generateTrustRoot(Passphrase passphrase) { private OpenPGPKey generateTrustRoot(Passphrase passphrase) {
PGPSecretKeyRing trustRoot; OpenPGPKey trustRoot;
KeyRingBuilder builder = PGPainless.buildKeyRing() KeyRingBuilder builder = PGPainless.getInstance().buildKey(OpenPGPKeyVersion.v4)
.addUserId("trust-root"); .addUserId("trust-root");
if (passphrase != null) { if (passphrase != null) {
builder.setPassphrase(passphrase); builder.setPassphrase(passphrase);
@ -88,9 +89,9 @@ public class Setup implements Runnable {
return trustRoot; return trustRoot;
} }
private PGPSecretKeyRing readTrustRoot(InputStream inputStream) { private OpenPGPKey readTrustRoot(InputStream inputStream) {
try { try {
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(inputStream); OpenPGPKey secretKeys = PGPainless.getInstance().readKey().parseKey(inputStream);
if (secretKeys == null) { if (secretKeys == null) {
throw new BadDataException(); throw new BadDataException();
} }

View file

@ -5,16 +5,13 @@
package pgp.cert_d.cli.commands; package pgp.cert_d.cli.commands;
import org.bouncycastle.openpgp.PGPException; 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.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.certificate_store.PGPainlessCertD; import org.pgpainless.certificate_store.PGPainlessCertD;
import org.pgpainless.key.OpenPgpFingerprint; 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.InstantiateCLI;
import pgp.cert_d.cli.PGPCertDCli; import pgp.cert_d.cli.PGPCertDCli;
import pgp.certificate_store.certificate.Key; import pgp.certificate_store.certificate.Key;
@ -24,12 +21,14 @@ import pgp.certificate_store.exception.BadDataException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@ -59,29 +58,29 @@ public class SetupTest {
PGPCertDCli.main(new String[] {"setup"}); PGPCertDCli.main(new String[] {"setup"});
KeyMaterial trustRoot = store.getTrustRoot(); KeyMaterial trustRoot = store.getTrustRoot();
assertNotNull(trustRoot); assertNotNull(trustRoot);
assertTrue(trustRoot instanceof Key); assertInstanceOf(Key.class, trustRoot);
// Check that key has no password // Check that key has no password
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(trustRoot.getInputStream()); OpenPGPKey key = PGPainless.getInstance().readKey().parseKey(trustRoot.getInputStream());
assertTrue(KeyInfo.isDecrypted(secretKeys.getSecretKey())); assertFalse(key.getPrimarySecretKey().isLocked(), "trust-root MUST NOT be passphrase protected here");
} }
@Test @Test
public void testSetupWithPassword() public void testSetupWithPassword()
throws BadDataException, IOException, PGPException { throws BadDataException, IOException {
assertThrows(NoSuchElementException.class, () -> store.getTrustRoot()); assertThrows(NoSuchElementException.class, () -> store.getTrustRoot());
PGPCertDCli.main(new String[] {"setup", "--with-password", "sw0rdf1sh"}); PGPCertDCli.main(new String[] {"setup", "--with-password", "sw0rdf1sh"});
KeyMaterial trustRoot = store.getTrustRoot(); KeyMaterial trustRoot = store.getTrustRoot();
assertNotNull(trustRoot); assertNotNull(trustRoot);
assertTrue(trustRoot instanceof Key); assertInstanceOf(Key.class, trustRoot);
// Check that key is encrypted // Check that key is encrypted
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(trustRoot.getInputStream()); OpenPGPKey key = PGPainless.getInstance().readKey().parseKey(trustRoot.getInputStream());
assertTrue(KeyInfo.isEncrypted(secretKeys.getSecretKey())); assertTrue(key.getPrimarySecretKey().isLocked());
// Check that password matches // Check that password matches
assertNotNull(UnlockSecretKey.unlockSecretKey( assertTrue(key.getPrimarySecretKey().isPassphraseCorrect("sw0rdf1sh".toCharArray()),
secretKeys.getSecretKey(), Passphrase.fromPassword("sw0rdf1sh"))); "Key MUST be able to be unlocked using passphrase");
} }
@Test @Test
@ -90,12 +89,12 @@ public class SetupTest {
BadDataException, IOException { BadDataException, IOException {
assertThrows(NoSuchElementException.class, () -> store.getTrustRoot()); assertThrows(NoSuchElementException.class, () -> store.getTrustRoot());
PGPSecretKeyRing trustRoot = PGPainless.generateKeyRing() OpenPGPKey trustRoot = PGPainless.getInstance().generateKey()
.modernKeyRing("trust-root"); .modernKeyRing("trust-root");
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(trustRoot); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(trustRoot);
String armored = PGPainless.asciiArmor(trustRoot); String armored = trustRoot.toAsciiArmoredString();
ByteArrayInputStream trustRootIn = new ByteArrayInputStream( ByteArrayInputStream trustRootIn = new ByteArrayInputStream(
armored.getBytes(Charset.forName("UTF8"))); armored.getBytes(StandardCharsets.UTF_8));
InputStream originalStdin = System.in; InputStream originalStdin = System.in;
System.setIn(trustRootIn); System.setIn(trustRootIn);

View file

@ -4,8 +4,10 @@
package org.pgpainless.certificate_store; package org.pgpainless.certificate_store;
import org.bouncycastle.bcpg.PacketFormat;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.OpenPgpFingerprint;
import pgp.certificate_store.certificate.Certificate; import pgp.certificate_store.certificate.Certificate;
@ -13,9 +15,21 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class CertificateFactory { 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) public static Certificate certificateFromPublicKeyRing(PGPPublicKeyRing publicKeyRing, Long tag)
throws IOException { throws IOException {
byte[] bytes = publicKeyRing.getEncoded(); byte[] bytes = publicKeyRing.getEncoded();
@ -28,4 +42,23 @@ public class CertificateFactory {
return new Certificate(bytes, fingerprint, subkeyIds, tag); 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<Long> subkeyIds = openPGPCertificate.getValidKeys()
.stream()
.map(it -> it.getKeyIdentifier().getKeyId())
.collect(Collectors.toList());
return new Certificate(bytes, fingerprint, subkeyIds, tag);
}
} }

View file

@ -4,8 +4,10 @@
package org.pgpainless.certificate_store; package org.pgpainless.certificate_store;
import org.bouncycastle.bcpg.PacketFormat;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import pgp.certificate_store.certificate.Certificate; import pgp.certificate_store.certificate.Certificate;
import pgp.certificate_store.certificate.Key; import pgp.certificate_store.certificate.Key;
@ -14,10 +16,35 @@ import java.io.IOException;
public class KeyFactory { 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 { public static Key keyFromSecretKeyRing(PGPSecretKeyRing secretKeyRing, Long tag) throws IOException {
byte[] bytes = secretKeyRing.getEncoded(); byte[] bytes = secretKeyRing.getEncoded();
PGPPublicKeyRing publicKeyRing = PGPainless.extractCertificate(secretKeyRing); PGPPublicKeyRing publicKeyRing = PGPainless.extractCertificate(secretKeyRing);
Certificate certificate = CertificateFactory.certificateFromPublicKeyRing(publicKeyRing, tag); Certificate certificate = CertificateFactory.certificateFromPublicKeyRing(publicKeyRing, tag);
return new Key(bytes, certificate, 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);
}
} }

View file

@ -4,14 +4,14 @@
package org.pgpainless.certificate_store; package org.pgpainless.certificate_store;
import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import pgp.certificate_store.certificate.KeyMaterial; import pgp.certificate_store.certificate.KeyMaterial;
import pgp.certificate_store.certificate.KeyMaterialReaderBackend; import pgp.certificate_store.certificate.KeyMaterialReaderBackend;
import pgp.certificate_store.exception.BadDataException; import pgp.certificate_store.exception.BadDataException;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -19,28 +19,25 @@ public class KeyMaterialReader implements KeyMaterialReaderBackend {
@Override @Override
public KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException { public KeyMaterial read(InputStream data, Long tag) throws IOException, BadDataException {
PGPKeyRing keyMaterial; OpenPGPCertificate keyOrCertificate;
try { try {
keyMaterial = PGPainless.readKeyRing().keyRing(data); 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) { } catch (IOException e) {
String msg = e.getMessage(); if (e.getMessage().contains("Neither a certificate, nor secret key.")) {
if (msg == null) { throw new BadDataException();
}
throw e; throw e;
} }
if (msg.contains("unknown object in stream") ||
msg.contains("unexpected end of file in armored stream.") || if (keyOrCertificate.isSecretKey()) {
msg.contains("invalid header encountered")) { return KeyFactory.keyFromOpenPGPKey((OpenPGPKey) keyOrCertificate, tag);
throw new BadDataException();
} else { } else {
throw e; return CertificateFactory.certificateFromOpenPGPCertificate(keyOrCertificate, tag);
}
}
if (keyMaterial instanceof PGPSecretKeyRing) {
return KeyFactory.keyFromSecretKeyRing((PGPSecretKeyRing) keyMaterial, tag);
} else if (keyMaterial instanceof PGPPublicKeyRing) {
return CertificateFactory.certificateFromPublicKeyRing((PGPPublicKeyRing) keyMaterial, tag);
} else {
throw new BadDataException();
} }
} }
} }

View file

@ -5,19 +5,21 @@
package org.pgpainless.certificate_store; package org.pgpainless.certificate_store;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyRing;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.OpenPgpFingerprint;
import pgp.certificate_store.certificate.KeyMaterial; import pgp.certificate_store.certificate.KeyMaterial;
import pgp.certificate_store.certificate.KeyMaterialMerger; import pgp.certificate_store.certificate.KeyMaterialMerger;
import pgp.certificate_store.exception.BadDataException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
public class MergeCallbacks { public class MergeCallbacks {
@ -41,79 +43,95 @@ public class MergeCallbacks {
return data; return data;
} }
try { PGPainless api = PGPainless.getInstance();
PGPKeyRing existingKeyRing = PGPainless.readKeyRing().keyRing(existing.getInputStream());
PGPKeyRing updatedKeyRing = PGPainless.readKeyRing().keyRing(data.getInputStream());
PGPKeyRing mergedKeyRing; OpenPGPCertificate existingCert = api.readKey().parseCertificateOrKey(existing.getInputStream());
OpenPGPCertificate updatedCert = api.readKey().parseCertificateOrKey(data.getInputStream());
if (existingKeyRing instanceof PGPPublicKeyRing) { OpenPGPCertificate mergedCert = mergeCertificates(updatedCert, existingCert);
mergedKeyRing = mergeWithCert((PGPPublicKeyRing) existingKeyRing, updatedKeyRing);
} else if (existingKeyRing instanceof PGPSecretKeyRing) { printOutDifferences(existingCert, mergedCert);
mergedKeyRing = mergeWithKey(existingKeyRing, updatedKeyRing); return toKeyMaterial(mergedCert);
} else {
throw new IOException(new BadDataException());
} }
printOutDifferences(existingKeyRing, mergedKeyRing); 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.");
}
return toKeyMaterial(mergedKeyRing); OpenPGPCertificate merged;
try {
if (existingCertOrKey.isSecretKey()) {
OpenPGPKey existingKey = (OpenPGPKey) existingCertOrKey;
if (updatedCertOrKey.isSecretKey()) {
// Merge key with key
OpenPGPKey updatedKey = (OpenPGPKey) updatedCertOrKey;
OpenPGPCertificate mergedCertPart = OpenPGPCertificate.join(
existingKey.toCertificate(),
updatedKey.toCertificate());
List<PGPSecretKey> mergedSecretKeys = new ArrayList<>();
Iterator<PGPSecretKey> existingKeysIterator = existingKey.getPGPSecretKeyRing().getSecretKeys();
while (existingKeysIterator.hasNext()) {
mergedSecretKeys.add(existingKeysIterator.next());
}
Iterator<PGPSecretKey> 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 {
if (updatedCertOrKey.isSecretKey()) {
// Swap update and existing cert
return mergeCertificates(existingCertOrKey, updatedCertOrKey);
}
// Merge cert with cert
return OpenPGPCertificate.join(existingCertOrKey, updatedCertOrKey);
}
return merged;
} catch (PGPException e) { } catch (PGPException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private PGPKeyRing mergeWithCert(PGPPublicKeyRing existingKeyRing, PGPKeyRing updatedKeyRing) private KeyMaterial toKeyMaterial(OpenPGPCertificate mergedCertificate)
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)
throws IOException { throws IOException {
if (mergedKeyRing instanceof PGPPublicKeyRing) { if (mergedCertificate.isSecretKey()) {
return CertificateFactory.certificateFromPublicKeyRing((PGPPublicKeyRing) mergedKeyRing, null); return KeyFactory.keyFromOpenPGPKey((OpenPGPKey) mergedCertificate, null);
} else { } 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 numSigsBefore = countSigs(existingCert);
int numSigsAfter = countSigs(mergedCert); int numSigsAfter = countSigs(mergedCert);
int newSigs = numSigsAfter - numSigsBefore; int newSigs = numSigsAfter - numSigsBefore;
int numUidsBefore = count(existingCert.getPublicKey().getUserIDs()); int numUidsBefore = count(existingCert.getAllUserIds().iterator());
int numUidsAfter = count(mergedCert.getPublicKey().getUserIDs()); int numUidsAfter = count(mergedCert.getAllUserIds().iterator());
int newUids = numUidsAfter - numUidsBefore; int newUids = numUidsAfter - numUidsBefore;
if (!Arrays.equals(existingCert.getEncoded(), mergedCert.getEncoded())) { 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; int numSigs = 0;
Iterator<PGPPublicKey> iterator = keys.getPublicKeys(); for (OpenPGPCertificate.OpenPGPComponentKey componentKey : keys.getKeys()) {
while (iterator.hasNext()) { PGPPublicKey key = componentKey.getPGPPublicKey();
PGPPublicKey key = iterator.next();
numSigs += count(key.getSignatures()); numSigs += count(key.getSignatures());
} }
return numSigs; return numSigs;

View file

@ -14,8 +14,6 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -24,14 +22,14 @@ import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.util.io.Streams; import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.OpenPGPKeyVersion;
import org.pgpainless.certificate_store.PGPainlessCertD; import org.pgpainless.certificate_store.PGPainlessCertD;
import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.generation.KeySpec;
@ -68,10 +66,11 @@ public class SharedPGPCertificateDirectoryTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideTestSubjects") @MethodSource("provideTestSubjects")
public void simpleInsertGet(PGPainlessCertD directory) public void simpleInsertGet(PGPainlessCertD directory)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, throws IOException, BadDataException, InterruptedException {
BadDataException, InterruptedException, BadNameException { OpenPGPKey key = PGPainless.getInstance()
PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice"); .generateKey(OpenPGPKeyVersion.v4)
PGPPublicKeyRing cert = PGPainless.extractCertificate(key); .modernKeyRing("Alice");
OpenPGPCertificate cert = key.toCertificate();
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert);
ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded()); ByteArrayInputStream certIn = new ByteArrayInputStream(cert.getEncoded());
@ -90,13 +89,14 @@ public class SharedPGPCertificateDirectoryTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideTestSubjects") @MethodSource("provideTestSubjects")
public void simpleInsertGetBySpecialName(PGPainlessCertD directory) public void simpleInsertGetBySpecialName(PGPainlessCertD directory)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, throws IOException, BadDataException, InterruptedException, BadNameException {
BadDataException, InterruptedException, BadNameException { OpenPGPKey key = PGPainless.getInstance()
PGPSecretKeyRing key = PGPainless.buildKeyRing() .buildKey(OpenPGPKeyVersion.v4)
.addUserId("trust-root") .addUserId("trust-root")
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER)) .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER))
.build(); .build();
PGPPublicKeyRing trustRoot = PGPainless.extractCertificate(key);
OpenPGPCertificate trustRoot = key.toCertificate();
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(trustRoot); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(trustRoot);
ByteArrayInputStream certIn = new ByteArrayInputStream(trustRoot.getEncoded()); ByteArrayInputStream certIn = new ByteArrayInputStream(trustRoot.getEncoded());
@ -115,20 +115,21 @@ public class SharedPGPCertificateDirectoryTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("provideTestSubjects") @MethodSource("provideTestSubjects")
public void testGetItemsAndFingerprints(PGPainlessCertD directory) public void testGetItemsAndFingerprints(PGPainlessCertD directory)
throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException, throws IOException, BadDataException, InterruptedException, BadNameException {
BadDataException, InterruptedException, BadNameException {
PGPSecretKeyRing trustRootKey = PGPainless.generateKeyRing().modernKeyRing("Alice"); OpenPGPKey trustRootKey = PGPainless.getInstance().generateKey(OpenPGPKeyVersion.v4)
PGPPublicKeyRing trustRootCert = PGPainless.extractCertificate(trustRootKey); .modernKeyRing("Alice");
OpenPGPCertificate trustRootCert = trustRootKey.toCertificate();
OpenPgpFingerprint trustRootFingerprint = OpenPgpFingerprint.of(trustRootCert); OpenPgpFingerprint trustRootFingerprint = OpenPgpFingerprint.of(trustRootCert);
ByteArrayInputStream trustRootCertIn = new ByteArrayInputStream(trustRootCert.getEncoded()); ByteArrayInputStream trustRootCertIn = new ByteArrayInputStream(trustRootCert.getEncoded());
directory.insertWithSpecialName("trust-root", trustRootCertIn, dummyMerge); directory.insertWithSpecialName("trust-root", trustRootCertIn, dummyMerge);
final int certificateCount = 3; final int certificateCount = 3;
Map<String, PGPPublicKeyRing> certificateMap = new HashMap<>(); Map<String, OpenPGPCertificate> certificateMap = new HashMap<>();
for (int i = 0; i < certificateCount; i++) { for (int i = 0; i < certificateCount; i++) {
PGPSecretKeyRing key = PGPainless.generateKeyRing().modernKeyRing("Alice"); OpenPGPKey key = PGPainless.getInstance().generateKey(OpenPGPKeyVersion.v4)
PGPPublicKeyRing cert = PGPainless.extractCertificate(key); .modernKeyRing("Alice");
OpenPGPCertificate cert = key.toCertificate();
OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert); OpenPgpFingerprint fingerprint = OpenPgpFingerprint.of(cert);
certificateMap.put(fingerprint.toString().toLowerCase(), cert); certificateMap.put(fingerprint.toString().toLowerCase(), cert);

View file

@ -16,11 +16,11 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class KeyMaterialReaderTest { public class KeyMaterialReaderTest {
@ -111,7 +111,7 @@ public class KeyMaterialReaderTest {
public void readKeyTest() throws BadDataException, IOException { public void readKeyTest() throws BadDataException, IOException {
KeyMaterial keyMaterial = reader.read(new ByteArrayInputStream(KEY.getBytes(UTF8)), 12L); KeyMaterial keyMaterial = reader.read(new ByteArrayInputStream(KEY.getBytes(UTF8)), 12L);
assertNotNull(keyMaterial); assertNotNull(keyMaterial);
assertTrue(keyMaterial instanceof Key); assertInstanceOf(Key.class, keyMaterial);
Key key = (Key) keyMaterial; Key key = (Key) keyMaterial;
assertEquals("b21aabbf15df0fda37424de9ad008384ad0a064c", key.getFingerprint()); assertEquals("b21aabbf15df0fda37424de9ad008384ad0a064c", key.getFingerprint());
assertEquals(12L, key.getTag()); assertEquals(12L, key.getTag());
@ -125,7 +125,7 @@ public class KeyMaterialReaderTest {
public void readCertTest() throws BadDataException, IOException { public void readCertTest() throws BadDataException, IOException {
KeyMaterial keyMaterial = reader.read(new ByteArrayInputStream(CERT.getBytes(UTF8)), null); KeyMaterial keyMaterial = reader.read(new ByteArrayInputStream(CERT.getBytes(UTF8)), null);
assertNotNull(keyMaterial); assertNotNull(keyMaterial);
assertTrue(keyMaterial instanceof Certificate); assertInstanceOf(Certificate.class, keyMaterial);
Certificate certificate = (Certificate) keyMaterial; Certificate certificate = (Certificate) keyMaterial;
assertEquals("b21aabbf15df0fda37424de9ad008384ad0a064c", certificate.getFingerprint()); assertEquals("b21aabbf15df0fda37424de9ad008384ad0a064c", certificate.getFingerprint());
assertNull(certificate.getTag()); assertNull(certificate.getTag());

View file

@ -12,8 +12,8 @@ allprojects {
junitVersion = '5.8.2' junitVersion = '5.8.2'
mockitoVersion = '4.5.1' mockitoVersion = '4.5.1'
bouncyCastleVersion = '1.82' bouncyCastleVersion = '1.82'
pgpainlessVersion = '1.7.7' pgpainlessVersion = '2.0.0'
pgpCertDJavaVersion = '0.2.2' pgpCertDJavaVersion = '0.2.3'
picocliVersion = '4.6.3' picocliVersion = '4.6.3'
} }
} }