diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java index f8f59d36..f7b3c020 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java @@ -6,10 +6,12 @@ package org.pgpainless.decryption_verification; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -23,6 +25,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.pgpainless.PGPainless; import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy; import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy; import org.pgpainless.key.SubkeyIdentifier; @@ -30,6 +33,9 @@ import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.signature.SignatureUtils; import org.pgpainless.util.Passphrase; import org.pgpainless.util.SessionKey; +import pgp.certificate_store.PGPCertificateStore; +import pgp.certificate_store.certificate.Certificate; +import pgp.certificate_store.exception.BadDataException; /** * Options for decryption and signature verification. @@ -43,8 +49,7 @@ public class ConsumerOptions { private Date verifyNotBefore = null; private Date verifyNotAfter = new Date(); - // Set of verification keys - private final Set certificates = new HashSet<>(); + private final CertificateSource certificates = new CertificateSource(); private final Set detachedSignatures = new HashSet<>(); private MissingPublicKeyCallback missingCertificateCallback = null; @@ -113,7 +118,7 @@ public class ConsumerOptions { * @return options */ public ConsumerOptions addVerificationCert(PGPPublicKeyRing verificationCert) { - this.certificates.add(verificationCert); + this.certificates.addCertificate(verificationCert); return this; } @@ -130,6 +135,11 @@ public class ConsumerOptions { return this; } + public ConsumerOptions addVerificationCerts(PGPCertificateStore certificateStore) { + this.certificates.addStore(certificateStore); + return this; + } + public ConsumerOptions addVerificationOfDetachedSignatures(InputStream signatureInputStream) throws IOException, PGPException { List signatures = SignatureUtils.readSignatures(signatureInputStream); return addVerificationOfDetachedSignatures(signatures); @@ -266,8 +276,19 @@ public class ConsumerOptions { return Collections.unmodifiableSet(decryptionPassphrases); } + /** + * Return the explicitly set verification certificates. + * + * @deprecated use {@link #getCertificateSource()} instead. + * @return verification certs + */ + @Deprecated public @Nonnull Set getCertificates() { - return Collections.unmodifiableSet(certificates); + return certificates.getExplicitCertificates(); + } + + public @Nonnull CertificateSource getCertificateSource() { + return certificates; } public @Nullable MissingPublicKeyCallback getMissingCertificateCallback() { @@ -385,4 +406,68 @@ public class ConsumerOptions { public MultiPassStrategy getMultiPassStrategy() { return multiPassStrategy; } + + public static class CertificateSource { + + private List stores = new ArrayList<>(); + private Set explicitCertificates = new HashSet<>(); + + /** + * Add a certificate store as source for verification certificates. + * + * @param certificateStore cert store + */ + public void addStore(PGPCertificateStore certificateStore) { + this.stores.add(certificateStore); + } + + /** + * Add a certificate as verification cert explicitly. + * + * @param certificate certificate + */ + public void addCertificate(PGPPublicKeyRing certificate) { + this.explicitCertificates.add(certificate); + } + + /** + * Return the set of explicitly set verification certificates. + * @return explicitly set verification certs + */ + public Set getExplicitCertificates() { + return Collections.unmodifiableSet(explicitCertificates); + } + + /** + * Return a certificate which contains a subkey with the given keyId. + * This method first checks all explicitly set verification certs and if no cert is found it consults + * the certificate stores. + * + * @param keyId key id + * @return certificate + */ + public PGPPublicKeyRing getCertificate(long keyId) { + + for (PGPPublicKeyRing cert : explicitCertificates) { + if (cert.getPublicKey(keyId) != null) { + return cert; + } + } + + for (PGPCertificateStore store : stores) { + try { + Iterator certs = store.getCertificatesBySubkeyId(keyId); + if (!certs.hasNext()) { + continue; + } + Certificate cert = certs.next(); + PGPPublicKeyRing publicKey = PGPainless.readKeyRing().publicKeyRing(cert.getInputStream()); + return publicKey; + } catch (IOException | BadDataException e) { + continue; + } + } + return null; + } + } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptWithKeyFromKeyStoreTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptWithKeyFromKeyStoreTest.java index 9291d703..48f1bbd7 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptWithKeyFromKeyStoreTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptWithKeyFromKeyStoreTest.java @@ -7,12 +7,18 @@ package org.pgpainless.encryption_signing; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.bouncycastle.util.io.Streams; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; import org.pgpainless.certificate_store.MergeCallbacks; import org.pgpainless.certificate_store.PGPainlessCertD; +import org.pgpainless.decryption_verification.ConsumerOptions; +import org.pgpainless.decryption_verification.DecryptionStream; +import org.pgpainless.decryption_verification.OpenPgpMetadata; import org.pgpainless.key.OpenPgpFingerprint; +import org.pgpainless.key.protection.SecretKeyRingProtector; import pgp.cert_d.PGPCertificateStoreAdapter; import pgp.certificate_store.certificate.Certificate; import pgp.certificate_store.exception.BadDataException; @@ -26,60 +32,116 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class EncryptWithKeyFromKeyStoreTest { + // Collection of 3 keys (fingerprints below) + private static final String KEY_COLLECTION = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "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\n" + + "jzJc4ViKmwiNJAPttDQCpYjzJT7LUKAA\n" + + "=EAvh\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + // Collection of 3 certificates (fingerprints below) private static final String CERT_COLLECTION = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + "Version: BCPG v1.71\n" + "\n" + - "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\n" + - "Pr2snaTp6/NukIbg3D/YMXm+mM6119v7HJkO\n" + - "=KVYs\n" + + "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\n" + + "Mwj4jzJc4ViKmwiNJAPttDQCpYjzJT7LUKAA\n" + + "=WaRm\n" + "-----END PGP PUBLIC KEY BLOCK-----"; - private static final OpenPgpFingerprint cert1fp = OpenPgpFingerprint.parse("76324A41CC19C0AA33B3C1F6070730D6CC6AAE26"); - private static final OpenPgpFingerprint cert2fp = OpenPgpFingerprint.parse("C7CD4DE07F1C972E04D338600E64F63484F50F82"); - private static final OpenPgpFingerprint cert3fp = OpenPgpFingerprint.parse("9165E83E17F979BD2631FEFD545DC02306859824"); + private static final OpenPgpFingerprint cert1fp = OpenPgpFingerprint.parse("DA8AF595FC41F71BC4F26608688F656CA3A169B3"); + private static final OpenPgpFingerprint cert2fp = OpenPgpFingerprint.parse("DBF2F91C16DAE881431EEF1C09EC13A1534C7365"); + private static final OpenPgpFingerprint cert3fp = OpenPgpFingerprint.parse("29F8CAF0F3A115D66C948142A269971D8C7597DE"); @Test - public void encryptWithKeyFromStore() throws PGPException, IOException, BadDataException, InterruptedException, BadNameException { + public void encryptWithCertFromCertificateStore() throws PGPException, IOException, BadDataException, InterruptedException, BadNameException { // In-Memory certificate store PGPainlessCertD certificateDirectory = PGPainlessCertD.inMemory(); PGPCertificateStoreAdapter adapter = new PGPCertificateStoreAdapter(certificateDirectory); @@ -108,4 +170,61 @@ public class EncryptWithKeyFromKeyStoreTest { // check if message was encrypted for cert assertTrue(encryptionStream.getResult().isEncryptedFor(publicKeys)); } + + @Test + public void verifyWithCertFromCertificateStore() + throws PGPException, IOException, BadDataException, InterruptedException, BadNameException { + // In-Memory certificate store + PGPainlessCertD certificateDirectory = PGPainlessCertD.inMemory(); + PGPCertificateStoreAdapter adapter = new PGPCertificateStoreAdapter(certificateDirectory); + + // Populate store + PGPPublicKeyRingCollection certificates = PGPainless.readKeyRing().publicKeyRingCollection(CERT_COLLECTION); + for (PGPPublicKeyRing cert : certificates) { + certificateDirectory.insert(new ByteArrayInputStream(cert.getEncoded()), MergeCallbacks.mergeWithExisting()); + } + + // Prepare keys + OpenPgpFingerprint cryptFp = cert3fp; + OpenPgpFingerprint signFp = cert1fp; + PGPSecretKeyRingCollection secretKeys = PGPainless.readKeyRing().secretKeyRingCollection(KEY_COLLECTION); + PGPSecretKeyRing signingKey = secretKeys.getSecretKeyRing(signFp.getKeyId()); + PGPSecretKeyRing decryptionKey = secretKeys.getSecretKeyRing(cryptFp.getKeyId()); + SecretKeyRingProtector protector = SecretKeyRingProtector.unprotectedKeys(); + + // Encrypt and sign message + ByteArrayInputStream plaintextIn = new ByteArrayInputStream( + "This message was encrypted with a cert from a store and gets verified with a cert from a store as well".getBytes()); + ByteArrayOutputStream ciphertext = new ByteArrayOutputStream(); + EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() + .onOutputStream(ciphertext) + .withOptions( + ProducerOptions.signAndEncrypt( + EncryptionOptions.encryptCommunications() + .addRecipient(adapter, cryptFp), + SigningOptions.get() + .addSignature(protector, signingKey) + )); + Streams.pipeAll(plaintextIn, encryptionStream); + encryptionStream.close(); + + // Prepare ciphertext for decryption + ByteArrayInputStream ciphertextIn = new ByteArrayInputStream(ciphertext.toByteArray()); + ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream(); + // Decrypt and verify + DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(ciphertextIn) + .withOptions( + new ConsumerOptions() + .addDecryptionKey(decryptionKey, protector) + .addVerificationCerts(adapter)); + Streams.pipeAll(decryptionStream, plaintextOut); + decryptionStream.close(); + + // Check that message can be decrypted and is verified + OpenPgpMetadata result = decryptionStream.getResult(); + assertTrue(result.isEncrypted()); + assertTrue(result.isVerified()); + assertTrue(result.containsVerifiedSignatureFrom(signFp)); + } } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java index b563e704..3a5b3b6a 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedVerifyImpl.java @@ -76,7 +76,7 @@ public class DetachedVerifyImpl implements DetachedVerify { verificationList.add(map(signatureVerification)); } - if (!options.getCertificates().isEmpty()) { + if (!options.getCertificateSource().getExplicitCertificates().isEmpty()) { if (verificationList.isEmpty()) { throw new SOPGPException.NoSignature(); } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java index 4948712c..82ed4282 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java @@ -74,7 +74,7 @@ public class InlineVerifyImpl implements InlineVerify { verificationList.add(map(signatureVerification)); } - if (!options.getCertificates().isEmpty()) { + if (!options.getCertificateSource().getExplicitCertificates().isEmpty()) { if (verificationList.isEmpty()) { throw new SOPGPException.NoSignature(); }