From debee9839ef3a16e1532cd7caeb8f8829cf997d2 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 11 Jun 2018 01:33:49 +0200 Subject: [PATCH] Fix signing and include strategies --- .../vanitasvitae/crypto/pgpainless/Main.java | 166 ------------------ .../DecryptionBuilder.java | 3 +- .../DecryptionBuilderInterface.java | 3 +- .../DecryptionStream.java | 39 ++++ .../DecryptionStreamFactory.java | 17 +- .../PainlessResult.java | 27 +-- .../SignatureVerifyingInputStream.java | 2 - .../VerificationFeedbackCallback.java | 6 - .../encryption_signing/EncryptionBuilder.java | 137 ++++++++++----- .../EncryptionBuilderInterface.java | 28 +-- .../key/selection/key/impl/And.java | 44 +++++ .../crypto/pgpainless/util/BCUtil.java | 30 ++-- .../crypto/pgpainless/BCUtilTest.java | 43 +++++ .../crypto/pgpainless/EncryptDecryptTest.java | 12 +- .../crypto/pgpainless/TestKeysTest.java | 5 +- 15 files changed, 278 insertions(+), 284 deletions(-) delete mode 100644 src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java create mode 100644 src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionStream.java rename src/main/java/de/vanitasvitae/crypto/pgpainless/{ => decryption_verification}/PainlessResult.java (84%) delete mode 100644 src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/VerificationFeedbackCallback.java create mode 100644 src/main/java/de/vanitasvitae/crypto/pgpainless/key/selection/key/impl/And.java create mode 100644 src/test/java/de/vanitasvitae/crypto/pgpainless/BCUtilTest.java diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java deleted file mode 100644 index ef4b8e2c..00000000 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/Main.java +++ /dev/null @@ -1,166 +0,0 @@ -package de.vanitasvitae.crypto.pgpainless; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.Security; -import java.util.Collections; - -import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm; -import de.vanitasvitae.crypto.pgpainless.algorithm.HashAlgorithm; -import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm; -import de.vanitasvitae.crypto.pgpainless.key.SecretKeyRingProtector; -import de.vanitasvitae.crypto.pgpainless.key.generation.type.length.RsaLength; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPublicKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; -import org.bouncycastle.util.io.Streams; - -public class Main { - - public static void main(String[] args) - throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, - IOException { - - Security.addProvider(new BouncyCastleProvider()); - - PGPSecretKeyRing a = PGPainless.generateKeyRing().simpleRsaKeyRing("a@b.c", RsaLength._2048); - PGPSecretKeyRing b = PGPainless.generateKeyRing().simpleRsaKeyRing("b@c.d", RsaLength._2048); - - SecretKeyRingProtector secretKeyRingDecryptor = new SecretKeyRingProtector() { - @Override - public PBESecretKeyDecryptor getDecryptor(Long keyId) { - return null; - } - - @Override - public PBESecretKeyEncryptor getEncryptor(Long keyId) { - return null; - } - }; - - byte[] m = "Dies ist ein verschlüsselter Text.".getBytes(); - ByteArrayInputStream fromPlain = new ByteArrayInputStream(m); - ByteArrayOutputStream toEncrypted = new ByteArrayOutputStream(); - - OutputStream encryptor = PGPainless.createEncryptor().onOutputStream(toEncrypted) - .toRecipient(b.getPublicKey()) - .usingAlgorithms(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA512, CompressionAlgorithm.UNCOMPRESSED) - .signWith(a, secretKeyRingDecryptor) - .asciiArmor(); - - Streams.pipeAll(fromPlain, encryptor); - fromPlain.close(); - encryptor.close(); - - System.out.println(new String(toEncrypted.toByteArray())); - - ByteArrayInputStream fromEncrypted = new ByteArrayInputStream(toEncrypted.toByteArray()); - ByteArrayOutputStream toPlain = new ByteArrayOutputStream(); - - PainlessResult.ResultAndInputStream resultAndInputStream = PGPainless.createDecryptor() - .onInputStream(fromEncrypted) - .decryptWith(new PGPSecretKeyRingCollection(Collections.singleton(b)), secretKeyRingDecryptor) - .verifyWith(Collections.singleton(a.getPublicKey().getKeyID()), - Collections.singleton(new PGPPublicKeyRing(a.getPublicKey().getEncoded(), new BcKeyFingerprintCalculator()))) - .ignoreMissingPublicKeys() - .build(); - - InputStream decryptor = resultAndInputStream.getInputStream(); - - Streams.pipeAll(decryptor, toPlain); - decryptor.close(); - toPlain.close(); - - PainlessResult result = resultAndInputStream.getResult(); - - System.out.println(b.getPublicKey().getKeyID() + " " + result.getDecryptionKeyId()); - System.out.println(new String(toPlain.toByteArray())); - } - - private static void gpg(PGPSecretKeyRing a, PGPSecretKeyRing b, SecretKeyRingProtector secretKeyRingDecryptor) - throws IOException, PGPException { - String gpg = "-----BEGIN PGP MESSAGE-----\n" + - "\n" + - "hQGMAwAAAAAAAAAAAQv+JyovfiPxDiLe9XlgQAG6zD+YdRtZRuUJD+A+ZX4Sn0w0\n" + - "2Dl9Ehf7lKjIo0cIfVOUrgITnWIRWAyfrk5KiXdXcZ6dXxz/YJFnLSlgqUwq7GWi\n" + - "NYf3Uqg+/8f3Ucl0x6sr1oddwB9OI7zRJwDqEzTORjLBu1vtDlFPMPWwAeqDtZgz\n" + - "ikT6vSFfhVjVbgx4mztw7hatWNjXzNkl9+lojzo9IyiA+3SBsRe/2My3ZBjPx97f\n" + - "3YGMCvbggdX3C/MRV2iek2pFX7YTKasFeEy5Y1c09upqaEIpaJq8vi1Fu44dv0Rt\n" + - "gv4sdljaJXsFn9aoVFrp/xU4SyPiC1Z/KjqE3Zfyh+OMWKoWmtYH07/g8IGkkBCh\n" + - "xuDiyy813WS3xtCyX405Vd+rxYC3y1h1FtthdO/AIrYSWj6qI6hyK2tyYmwsg+oY\n" + - "1oaXhcTbWXsBO89v0YtmVK1bPVXq8ao/DQvrs84JsYsKzXA17gKyBLBUoTNn8h8A\n" + - "AXg455AN8iHi2u5pAKr7hQIMAwAAAAAAAAAAAQ/7Bg8SGEfMPmtDy/BazrYWaXvX\n" + - "1+WWRNM630ULPEq7LG4BKJJOOrhk0kNkjIsaXhgqn9bt3YxxuE1eOQksjn0sNOD7\n" + - "3NAOicHzQ7xOarvN9OUSGirc+EIn4ETRGKYF1TXHBSYnBeb+DLCbRZkBZhRrA5Lc\n" + - "z08kWrGRfq5Bz6eMatBTO1L8XTIxHPgc9/LNv7OqcIfT0udjOQMkA7oxCz5mLl2b\n" + - "dApsDEFNKNaGgRzSf2rDqw0SGkDxYsXI6IYrVSEm6uDt+ScybS0KkcEgg+I9l91n\n" + - "XgqQQaXpYnHgETqKYfcUOk5iEND5Lvik/XhHNViaL3CdOkxFLTa0wfy0y0IsV2Y3\n" + - "xGkMOWdDjXlY8UWRgoK61M91phgZ48zfSoVvXNDrjOJzm1jn8CFFFov4Gse7CtlM\n" + - "A+3ntVdjL94jkp+2mU3e9kzCOG+ChylLuqlGTvavbHt/rzuZooi/6g1VHy1r+v9I\n" + - "rWKX6q55H8JzZXZOrfED39QocK9b1BjtEca/Qnqw82+IVY/CufBmnmbOWUkHq1zP\n" + - "6nj840HxH1zV5vHf8vlXxV7/iBesAF94dLT/Hp0E7+Ilyp/pQaQjMS2RLycMUJQJ\n" + - "pQey81gpuOWD6YIbvgnrMBMrJLyJSk3r3sMdJ3DCPxHC+OyvHxddA5TdL2e4aP3L\n" + - "OzKql59v1w+9Doe3LEPSPgEVAdUUg0nEl5lg9LLqaepaYp8NfsEC1Rnk/MLxciJu\n" + - "9oNjqPqQxKTv4aQO/Qb8gHFb3O34OnNKz+CzrX5Q\n" + - "=sVVl\n" + - "-----END PGP MESSAGE-----"; - - ByteArrayInputStream inputStream = new ByteArrayInputStream(gpg.getBytes()); - - InputStream decryptor = PGPainless.createDecryptor().onInputStream(inputStream) - .decryptWith(new PGPSecretKeyRingCollection(Collections.singleton(b)), secretKeyRingDecryptor) - .doNotVerify() - .build() - .getInputStream(); - - } - - public static void symm(PGPSecretKeyRing a, PGPSecretKeyRing b, SecretKeyRingProtector secretKeyRingDecryptor) - throws IOException, PGPException { - byte[] bytes = "Diese Nachricht ist streng geheim!!!".getBytes(Charset.forName("UTF-8")); - ByteArrayInputStream fromPlain = new ByteArrayInputStream(bytes); - ByteArrayOutputStream toEncrypted = new ByteArrayOutputStream(); - - OutputStream encryptor = PGPainless.createEncryptor() - .onOutputStream(toEncrypted) - .toRecipient(b.getPublicKey()) - .usingAlgorithms(SymmetricKeyAlgorithm.AES_256, HashAlgorithm.SHA512, CompressionAlgorithm.UNCOMPRESSED) - .signWith(a, secretKeyRingDecryptor) - .noArmor(); - - Streams.pipeAll(fromPlain, encryptor); - encryptor.close(); - - System.out.println(new String(toEncrypted.toByteArray(), Charset.forName("UTF-8"))); - - ByteArrayInputStream fromEncrypted = new ByteArrayInputStream(toEncrypted.toByteArray()); - ByteArrayOutputStream toPlain = new ByteArrayOutputStream(); - - PainlessResult.ResultAndInputStream resultAndInputStream = PGPainless.createDecryptor() - .onInputStream(fromEncrypted) - .decryptWith(new PGPSecretKeyRingCollection(Collections.singleton(b)), secretKeyRingDecryptor) - .verifyWith(Collections.singleton(a.getPublicKey().getKeyID()), - Collections.singleton(new PGPPublicKeyRing(a.getPublicKey().getEncoded(), new BcKeyFingerprintCalculator()))) - .ignoreMissingPublicKeys() - .build(); - - InputStream decryptor = resultAndInputStream.getInputStream(); - - Streams.pipeAll(decryptor, toPlain); - decryptor.close(); - - PainlessResult result = resultAndInputStream.getResult(); - } -} diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionBuilder.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionBuilder.java index 86223e89..c4acbfa6 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionBuilder.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionBuilder.java @@ -6,7 +6,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import de.vanitasvitae.crypto.pgpainless.PainlessResult; import de.vanitasvitae.crypto.pgpainless.key.SecretKeyRingProtector; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -89,7 +88,7 @@ public class DecryptionBuilder implements DecryptionBuilderInterface { class BuildImpl implements Build { @Override - public PainlessResult.ResultAndInputStream build() throws IOException, PGPException { + public DecryptionStream build() throws IOException, PGPException { return DecryptionStreamFactory.create(inputStream, decryptionKeys, decryptionKeyDecryptor, verificationKeys, trustedKeyIds, missingPublicKeyCallback); } diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionBuilderInterface.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionBuilderInterface.java index b9ef0939..b820bbae 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionBuilderInterface.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionBuilderInterface.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.io.InputStream; import java.util.Set; -import de.vanitasvitae.crypto.pgpainless.PainlessResult; import de.vanitasvitae.crypto.pgpainless.key.SecretKeyRingProtector; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -42,7 +41,7 @@ public interface DecryptionBuilderInterface { interface Build { - PainlessResult.ResultAndInputStream build() throws IOException, PGPException; + DecryptionStream build() throws IOException, PGPException; } diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionStream.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionStream.java new file mode 100644 index 00000000..3efb3bdb --- /dev/null +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionStream.java @@ -0,0 +1,39 @@ +package de.vanitasvitae.crypto.pgpainless.decryption_verification; + +import java.io.IOException; +import java.io.InputStream; + +public class DecryptionStream extends InputStream { + + private final InputStream inputStream; + private final PainlessResult.Builder resultBuilder; + private boolean isClosed = false; + + DecryptionStream(InputStream wrapped, PainlessResult.Builder resultBuilder) { + + if (wrapped == null) { + throw new NullPointerException("Wrapped InputStream MUST NOT be null!"); + } + + this.inputStream = wrapped; + this.resultBuilder = resultBuilder; + } + + @Override + public int read() throws IOException { + return inputStream.read(); + } + + @Override + public void close() throws IOException { + inputStream.close(); + this.isClosed = true; + } + + public PainlessResult getResult() { + if (!isClosed) { + throw new IllegalStateException("DecryptionStream MUST be closed before the result can be accessed."); + } + return resultBuilder.build(); + } +} diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionStreamFactory.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionStreamFactory.java index 1cfd8ba7..84673a2d 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionStreamFactory.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/DecryptionStreamFactory.java @@ -9,7 +9,6 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; -import de.vanitasvitae.crypto.pgpainless.PainlessResult; import de.vanitasvitae.crypto.pgpainless.PainlessStream; import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm; @@ -60,12 +59,12 @@ public class DecryptionStreamFactory { this.missingPublicKeyCallback = missingPublicKeyCallback; } - public static PainlessResult.ResultAndInputStream create(InputStream inputStream, - PGPSecretKeyRingCollection decryptionKeys, - SecretKeyRingProtector decryptor, - Set verificationKeys, - Set trustedKeyIds, - MissingPublicKeyCallback missingPublicKeyCallback) + public static DecryptionStream create(InputStream inputStream, + PGPSecretKeyRingCollection decryptionKeys, + SecretKeyRingProtector decryptor, + Set verificationKeys, + Set trustedKeyIds, + MissingPublicKeyCallback missingPublicKeyCallback) throws IOException, PGPException { DecryptionStreamFactory factory = new DecryptionStreamFactory(decryptionKeys, @@ -77,9 +76,7 @@ public class DecryptionStreamFactory { PGPObjectFactory objectFactory = new PGPObjectFactory( PGPUtil.getDecoderStream(inputStream), new BcKeyFingerprintCalculator()); - return new PainlessResult.ResultAndInputStream( - factory.resultBuilder, - new PainlessStream.In(factory.wrap(objectFactory))); + return new DecryptionStream(factory.wrap(objectFactory), factory.resultBuilder); } private InputStream wrap(PGPObjectFactory objectFactory) throws IOException, PGPException { diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/PainlessResult.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/PainlessResult.java similarity index 84% rename from src/main/java/de/vanitasvitae/crypto/pgpainless/PainlessResult.java rename to src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/PainlessResult.java index 570fc8b9..56534239 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/PainlessResult.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/PainlessResult.java @@ -1,4 +1,4 @@ -package de.vanitasvitae.crypto.pgpainless; +package de.vanitasvitae.crypto.pgpainless.decryption_verification; import java.util.Collections; import java.util.HashSet; @@ -86,11 +86,11 @@ public class PainlessResult { return false; } - public static Builder getBuilder() { + static Builder getBuilder() { return new Builder(); } - public static class Builder { + static class Builder { private final Set recipientKeyIds = new HashSet<>(); private Long decryptionKeyId; @@ -139,25 +139,4 @@ public class PainlessResult { return new PainlessResult(recipientKeyIds, decryptionKeyId, symmetricKeyAlgorithm, compressionAlgorithm, integrityProtected, signatureKeyIds, verifiedSignatureKeyIds); } } - - public static class ResultAndInputStream { - private final PainlessResult.Builder resultBuilder; - private final PainlessStream.In inputStream; - - public ResultAndInputStream(PainlessResult.Builder resultBuilder, PainlessStream.In inputStream) { - this.resultBuilder = resultBuilder; - this.inputStream = inputStream; - } - - public PainlessResult getResult() { - if (!inputStream.isClosed()) { - throw new IllegalStateException("InputStream must be closed before the PainlessResult can be accessed."); - } - return resultBuilder.build(); - } - - public PainlessStream.In getInputStream() { - return inputStream; - } - } } diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/SignatureVerifyingInputStream.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/SignatureVerifyingInputStream.java index ac01d6a9..975b5a44 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/SignatureVerifyingInputStream.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/SignatureVerifyingInputStream.java @@ -4,12 +4,10 @@ import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.security.SignatureException; -import java.util.Iterator; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; -import de.vanitasvitae.crypto.pgpainless.PainlessResult; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPOnePassSignature; diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/VerificationFeedbackCallback.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/VerificationFeedbackCallback.java deleted file mode 100644 index 9f3ab6d0..00000000 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/decryption_verification/VerificationFeedbackCallback.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.vanitasvitae.crypto.pgpainless.decryption_verification; - -public class VerificationFeedbackCallback { - - -} diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilder.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilder.java index 7d5549c0..552756d8 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilder.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilder.java @@ -3,16 +3,25 @@ package de.vanitasvitae.crypto.pgpainless.encryption_signing; import java.io.IOException; import java.io.OutputStream; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import de.vanitasvitae.crypto.pgpainless.PublicKeyNotFoundException; import de.vanitasvitae.crypto.pgpainless.SecretKeyNotFoundException; import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.HashAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm; import de.vanitasvitae.crypto.pgpainless.key.SecretKeyRingProtector; +import de.vanitasvitae.crypto.pgpainless.key.selection.key.PublicKeySelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.key.selection.key.SecretKeySelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.key.selection.key.impl.And; +import de.vanitasvitae.crypto.pgpainless.key.selection.key.impl.EncryptionKeySelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.key.selection.key.impl.NoRevocation; +import de.vanitasvitae.crypto.pgpainless.key.selection.key.impl.SignatureKeySelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.util.MultiMap; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; @@ -42,19 +51,22 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { class ToRecipientsImpl implements ToRecipients { @Override - public WithAlgorithms toRecipient(PGPPublicKey key) { - if (!key.isEncryptionKey()) { - throw new IllegalStateException("Public Key " + Long.toHexString(key.getKeyID()) + " is not capable of encryption."); + public WithAlgorithms toRecipients(PGPPublicKey... keys) { + for (PGPPublicKey k : keys) { + if (encryptionKeySelector().accept(null, k)) { + EncryptionBuilder.this.encryptionKeys.add(k); + } else { + throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key."); + } } - EncryptionBuilder.this.encryptionKeys.add(key); return new WithAlgorithmsImpl(); } @Override - public WithAlgorithms toRecipients(Set keys) { + public WithAlgorithms toRecipients(PGPPublicKeyRing... keys) { for (PGPPublicKeyRing ring : keys) { for (PGPPublicKey k : ring) { - if (k.isEncryptionKey()) { + if (encryptionKeySelector().accept(null, k)) { EncryptionBuilder.this.encryptionKeys.add(k); } } @@ -63,25 +75,20 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { } @Override - public WithAlgorithms toRecipients(Set keyIds, Set keys) - throws PublicKeyNotFoundException { - Set rings = new HashSet<>(); - - for (PGPPublicKeyRingCollection collection : keys) { - for (long keyId : keyIds) { - try { - PGPPublicKeyRing ring = collection.getPublicKeyRing(keyId); - if (ring != null) { - rings.add(ring); - keyIds.remove(keyId); + public WithAlgorithms toRecipients(PublicKeyRingSelectionStrategy ringSelectionStrategy, + MultiMap keys) { + MultiMap acceptedKeyRings = ringSelectionStrategy.selectKeyRingsFromCollections(keys); + for (O identifier : acceptedKeyRings.keySet()) { + Set acceptedSet = acceptedKeyRings.get(identifier); + for (PGPPublicKeyRing ring : acceptedSet) { + for (PGPPublicKey k : ring) { + if (encryptionKeySelector().accept(null, k)) { + EncryptionBuilder.this.encryptionKeys.add(k); } - } catch (PGPException e) { - throw new PublicKeyNotFoundException(e); } } } - - return toRecipients(rings); + return new WithAlgorithmsImpl(); } @Override @@ -93,17 +100,23 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { class WithAlgorithmsImpl implements WithAlgorithms { @Override - public WithAlgorithms andToSelf(PGPPublicKey key) { - EncryptionBuilder.this.encryptionKeys.add(key); + public WithAlgorithms andToSelf(PGPPublicKey... keys) { + for (PGPPublicKey k : keys) { + if (encryptionKeySelector().accept(null, k)) { + EncryptionBuilder.this.encryptionKeys.add(k); + } else { + throw new IllegalArgumentException("Key " + k.getKeyID() + " is not a valid encryption key."); + } + } return this; } @Override - public WithAlgorithms andToSelf(Set keyRings) { + public WithAlgorithms andToSelf(PGPPublicKeyRing... keyRings) { for (PGPPublicKeyRing ring : keyRings) { for (Iterator i = ring.getPublicKeys(); i.hasNext(); ) { PGPPublicKey key = i.next(); - if (key.isEncryptionKey()) { + if (encryptionKeySelector().accept(null, key)) { EncryptionBuilder.this.encryptionKeys.add(key); } } @@ -111,6 +124,24 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { return this; } + public WithAlgorithms andToSelf(PublicKeyRingSelectionStrategy ringSelectionStrategy, + MultiMap keyRingCollections) { + MultiMap acceptedKeyRings = + ringSelectionStrategy.selectKeyRingsFromCollections(keyRingCollections); + for (O identifier : acceptedKeyRings.keySet()) { + Set acceptedSet = acceptedKeyRings.get(identifier); + for (PGPPublicKeyRing k : acceptedSet) { + for (Iterator i = k.getPublicKeys(); i.hasNext(); ) { + PGPPublicKey key = i.next(); + if (encryptionKeySelector().accept(null, key)) { + EncryptionBuilder.this.encryptionKeys.add(key); + } + } + } + } + return this; + } + @Override public SignWith usingAlgorithms(SymmetricKeyAlgorithm symmetricKeyAlgorithm, HashAlgorithm hashAlgorithm, @@ -136,16 +167,24 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { class SignWithImpl implements SignWith { @Override - public Armor signWith(PGPSecretKeyRing key, SecretKeyRingProtector decryptor) { - return signWith(Collections.singleton(key), decryptor); + public Armor signWith(SecretKeyRingProtector decryptor, PGPSecretKey... keys) { + for (PGPSecretKey s : keys) { + if (EncryptionBuilder.this.signingKeySelector().accept(null, s)) { + signingKeys.add(s); + } else { + throw new IllegalArgumentException("Key " + s.getKeyID() + " is not a valid signing key."); + } + } + EncryptionBuilder.this.signingKeysDecryptor = decryptor; + return new ArmorImpl(); } @Override - public Armor signWith(Set keys, SecretKeyRingProtector decryptor) { + public Armor signWith(SecretKeyRingProtector decryptor, PGPSecretKeyRing... keys) { for (PGPSecretKeyRing key : keys) { for (Iterator i = key.getSecretKeys(); i.hasNext(); ) { PGPSecretKey s = i.next(); - if (s.isSigningKey()) { + if (EncryptionBuilder.this.signingKeySelector().accept(null, s)) { EncryptionBuilder.this.signingKeys.add(s); } } @@ -155,23 +194,23 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { } @Override - public Armor signWith(Set keyIds, Set keyRings, SecretKeyRingProtector decryptor) - throws SecretKeyNotFoundException { - Set rings = new HashSet<>(); - for (PGPSecretKeyRingCollection collection : keyRings) { - for (long keyId : keyIds) { - try { - PGPSecretKeyRing ring = collection.getSecretKeyRing(keyId); - if (ring != null) { - rings.add(ring); - keyIds.remove(keyId); + public Armor signWith(SecretKeyRingSelectionStrategy ringSelectionStrategy, + SecretKeyRingProtector decryptor, + MultiMap keyRingCollections) { + MultiMap acceptedKeyRings = + ringSelectionStrategy.selectKeyRingsFromCollections(keyRingCollections); + for (O identifier : acceptedKeyRings.keySet()) { + Set acceptedSet = acceptedKeyRings.get(identifier); + for (PGPSecretKeyRing k : acceptedSet) { + for (Iterator i = k.getSecretKeys(); i.hasNext(); ) { + PGPSecretKey s = i.next(); + if (EncryptionBuilder.this.signingKeySelector().accept(null, s)) { + EncryptionBuilder.this.signingKeys.add(s); } - } catch (PGPException e) { - throw new SecretKeyNotFoundException(keyId); } } } - return signWith(rings, decryptor); + return new ArmorImpl(); } @Override @@ -211,4 +250,16 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { EncryptionBuilder.this.asciiArmor); } } + + PublicKeySelectionStrategy encryptionKeySelector() { + return new And.PubKeySelectionStrategy<>( + new NoRevocation.PubKeySelectionStrategy<>(), + new EncryptionKeySelectionStrategy<>()); + } + + SecretKeySelectionStrategy signingKeySelector() { + return new And.SecKeySelectionStrategy<>( + new NoRevocation.SecKeySelectionStrategy<>(), + new SignatureKeySelectionStrategy<>()); + } } diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilderInterface.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilderInterface.java index 8cb75685..1c32f106 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilderInterface.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/encryption_signing/EncryptionBuilderInterface.java @@ -10,10 +10,14 @@ import de.vanitasvitae.crypto.pgpainless.algorithm.CompressionAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.HashAlgorithm; import de.vanitasvitae.crypto.pgpainless.algorithm.SymmetricKeyAlgorithm; import de.vanitasvitae.crypto.pgpainless.key.SecretKeyRingProtector; +import de.vanitasvitae.crypto.pgpainless.key.selection.keyring.PublicKeyRingSelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.key.selection.keyring.SecretKeyRingSelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.util.MultiMap; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; @@ -23,12 +27,12 @@ public interface EncryptionBuilderInterface { interface ToRecipients { - WithAlgorithms toRecipient(PGPPublicKey key); + WithAlgorithms toRecipients(PGPPublicKey... keys); - WithAlgorithms toRecipients(Set keys); + WithAlgorithms toRecipients(PGPPublicKeyRing... keys); - WithAlgorithms toRecipients(Set keyIds, Set keys) - throws PublicKeyNotFoundException; + WithAlgorithms toRecipients(PublicKeyRingSelectionStrategy selectionStrategy, + MultiMap keys); SignWith doNotEncrypt(); @@ -36,9 +40,12 @@ public interface EncryptionBuilderInterface { interface WithAlgorithms { - WithAlgorithms andToSelf(PGPPublicKey key); + WithAlgorithms andToSelf(PGPPublicKey... keys); - WithAlgorithms andToSelf(Set keys); + WithAlgorithms andToSelf(PGPPublicKeyRing... keys); + + WithAlgorithms andToSelf(PublicKeyRingSelectionStrategy selectionStrategy, + MultiMap keys); SignWith usingAlgorithms(SymmetricKeyAlgorithm symmetricKeyAlgorithm, HashAlgorithm hashAlgorithm, @@ -50,12 +57,13 @@ public interface EncryptionBuilderInterface { interface SignWith { - Armor signWith(PGPSecretKeyRing key, SecretKeyRingProtector decryptor); + Armor signWith(SecretKeyRingProtector decryptor, PGPSecretKey... keys); - Armor signWith(Set keyRings, SecretKeyRingProtector decryptor) - throws SecretKeyNotFoundException; + Armor signWith(SecretKeyRingProtector decryptor, PGPSecretKeyRing... keyRings); - Armor signWith(Set keyIds, Set keys, SecretKeyRingProtector decryptor) + Armor signWith(SecretKeyRingSelectionStrategy selectionStrategy, + SecretKeyRingProtector decryptor, + MultiMap keys) throws SecretKeyNotFoundException; Armor doNotSign(); diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/key/selection/key/impl/And.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/key/selection/key/impl/And.java new file mode 100644 index 00000000..50f91338 --- /dev/null +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/key/selection/key/impl/And.java @@ -0,0 +1,44 @@ +package de.vanitasvitae.crypto.pgpainless.key.selection.key.impl; + +import de.vanitasvitae.crypto.pgpainless.key.selection.key.PublicKeySelectionStrategy; +import de.vanitasvitae.crypto.pgpainless.key.selection.key.SecretKeySelectionStrategy; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; + +public class And { + + public static class PubKeySelectionStrategy extends PublicKeySelectionStrategy { + + private final PublicKeySelectionStrategy left; + private final PublicKeySelectionStrategy right; + + public PubKeySelectionStrategy(PublicKeySelectionStrategy left, + PublicKeySelectionStrategy right) { + this.left = left; + this.right = right; + } + + @Override + public boolean accept(O identifier, PGPPublicKey key) { + return left.accept(identifier, key) && right.accept(identifier, key); + } + } + + public static class SecKeySelectionStrategy extends SecretKeySelectionStrategy { + + private final SecretKeySelectionStrategy left; + private final SecretKeySelectionStrategy right; + + public SecKeySelectionStrategy(SecretKeySelectionStrategy left, + SecretKeySelectionStrategy right) { + this.left = left; + this.right = right; + } + + @Override + public boolean accept(O identifier, PGPSecretKey key) { + return left.accept(identifier, key) && right.accept(identifier, key); + } + } + +} diff --git a/src/main/java/de/vanitasvitae/crypto/pgpainless/util/BCUtil.java b/src/main/java/de/vanitasvitae/crypto/pgpainless/util/BCUtil.java index 9ef57dab..327813c3 100644 --- a/src/main/java/de/vanitasvitae/crypto/pgpainless/util/BCUtil.java +++ b/src/main/java/de/vanitasvitae/crypto/pgpainless/util/BCUtil.java @@ -1,10 +1,12 @@ package de.vanitasvitae.crypto.pgpainless.util; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; +import java.util.List; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKey; @@ -12,7 +14,6 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; public class BCUtil { @@ -26,14 +27,23 @@ public class BCUtil { return new PGPSecretKeyRingCollection(Arrays.asList(rings)); } - public static PGPPublicKeyRing publicKeyRingFromSecretKeyRing(PGPSecretKeyRing ring) throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - for (Iterator i = ring.getPublicKeys(); i.hasNext(); ) { + public static PGPPublicKeyRing publicKeyRingFromSecretKeyRing(PGPSecretKeyRing secring) { + List list = new ArrayList<>(); + for (Iterator i = secring.getPublicKeys(); i.hasNext(); ) { PGPPublicKey k = i.next(); - k.encode(buffer); + list.add(k); + } + + // TODO: Change to simply using the List constructor once BC 1.60 gets released. + try { + Constructor constructor; + constructor = PGPPublicKeyRing.class.getDeclaredConstructor(List.class); + constructor.setAccessible(true); + PGPPublicKeyRing pubring = constructor.newInstance(list); + return pubring; + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + e.printStackTrace(); + return null; } - buffer.close(); - ByteArrayInputStream in = new ByteArrayInputStream(buffer.toByteArray()); - return new PGPPublicKeyRing(in, new BcKeyFingerprintCalculator()); } } \ No newline at end of file diff --git a/src/test/java/de/vanitasvitae/crypto/pgpainless/BCUtilTest.java b/src/test/java/de/vanitasvitae/crypto/pgpainless/BCUtilTest.java new file mode 100644 index 00000000..f81dea02 --- /dev/null +++ b/src/test/java/de/vanitasvitae/crypto/pgpainless/BCUtilTest.java @@ -0,0 +1,43 @@ +package de.vanitasvitae.crypto.pgpainless; + +import static junit.framework.TestCase.assertEquals; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Iterator; + +import de.vanitasvitae.crypto.pgpainless.util.BCUtil; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.Test; + +public class BCUtilTest extends AbstractPGPainlessTest { + + @Test + public void test() + throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, + IOException { + PGPSecretKeyRing sec = PGPainless.generateKeyRing().simpleEcKeyRing("Hallo Welt"); + PGPPublicKeyRing pub = BCUtil.publicKeyRingFromSecretKeyRing(sec); + + int secSize = 0; + Iterator secPubIt = sec.getPublicKeys(); + while (secPubIt.hasNext()) { + secPubIt.next(); + secSize++; + } + + int pubSize = 0; + Iterator pubPubIt = pub.getPublicKeys(); + while (pubPubIt.hasNext()) { + pubPubIt.next(); + pubSize++; + } + + assertEquals(secSize, pubSize); + } +} diff --git a/src/test/java/de/vanitasvitae/crypto/pgpainless/EncryptDecryptTest.java b/src/test/java/de/vanitasvitae/crypto/pgpainless/EncryptDecryptTest.java index 6a5e2d80..ac47cf3e 100644 --- a/src/test/java/de/vanitasvitae/crypto/pgpainless/EncryptDecryptTest.java +++ b/src/test/java/de/vanitasvitae/crypto/pgpainless/EncryptDecryptTest.java @@ -5,7 +5,6 @@ import static junit.framework.TestCase.assertTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; import java.security.InvalidAlgorithmParameterException; @@ -16,6 +15,8 @@ import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; +import de.vanitasvitae.crypto.pgpainless.decryption_verification.DecryptionStream; +import de.vanitasvitae.crypto.pgpainless.decryption_verification.PainlessResult; import de.vanitasvitae.crypto.pgpainless.key.SecretKeyRingProtector; import de.vanitasvitae.crypto.pgpainless.key.UnprotectedKeysProtector; import de.vanitasvitae.crypto.pgpainless.key.generation.type.length.RsaLength; @@ -90,9 +91,9 @@ public class EncryptDecryptTest extends AbstractPGPainlessTest { OutputStream encryptor = PGPainless.createEncryptor() .onOutputStream(envelope) - .toRecipients(Collections.singleton(recipientPub)) + .toRecipients(recipientPub) .usingSecureAlgorithms() - .signWith(sender, keyDecryptor) + .signWith(keyDecryptor, sender) .noArmor(); Streams.pipeAll(new ByteArrayInputStream(secretMessage), encryptor); @@ -102,21 +103,20 @@ public class EncryptDecryptTest extends AbstractPGPainlessTest { // Juliet trieth to comprehend Romeos words ByteArrayInputStream envelopeIn = new ByteArrayInputStream(encryptedSecretMessage); - PainlessResult.ResultAndInputStream resultAndInputStream = PGPainless.createDecryptor() + DecryptionStream decryptor = PGPainless.createDecryptor() .onInputStream(envelopeIn) .decryptWith(BCUtil.keyRingsToKeyRingCollection(recipient), keyDecryptor) .verifyWith(Collections.singleton(TestKeys.ROMEO_KEY_ID), BCUtil.keyRingsToKeyRingCollection(senderPub)) .ignoreMissingPublicKeys() .build(); - InputStream decryptor = resultAndInputStream.getInputStream(); OutputStream decryptedSecretMessage = new ByteArrayOutputStream(); Streams.pipeAll(decryptor, decryptedSecretMessage); decryptor.close(); assertTrue(Arrays.equals(secretMessage, ((ByteArrayOutputStream) decryptedSecretMessage).toByteArray())); - PainlessResult result = resultAndInputStream.getResult(); + PainlessResult result = decryptor.getResult(); assertTrue(result.containsVerifiedSignatureFrom(senderPub)); assertTrue(result.isIntegrityProtected()); assertTrue(result.isSigned()); diff --git a/src/test/java/de/vanitasvitae/crypto/pgpainless/TestKeysTest.java b/src/test/java/de/vanitasvitae/crypto/pgpainless/TestKeysTest.java index 53800c2e..01ac619f 100644 --- a/src/test/java/de/vanitasvitae/crypto/pgpainless/TestKeysTest.java +++ b/src/test/java/de/vanitasvitae/crypto/pgpainless/TestKeysTest.java @@ -6,11 +6,11 @@ import static junit.framework.TestCase.assertTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; +import de.vanitasvitae.crypto.pgpainless.decryption_verification.DecryptionStream; import de.vanitasvitae.crypto.pgpainless.key.UnprotectedKeysProtector; import de.vanitasvitae.crypto.pgpainless.util.BCUtil; import org.bouncycastle.openpgp.PGPException; @@ -39,7 +39,7 @@ public class TestKeysTest extends AbstractPGPainlessTest { public void decryptVerifyTest() throws Exception { String encryptedMessage = TestKeys.TEST_MESSAGE_01; - PainlessResult.ResultAndInputStream resultAndInputStream = PGPainless.createDecryptor() + DecryptionStream decryptor = PGPainless.createDecryptor() .onInputStream(new ByteArrayInputStream(encryptedMessage.getBytes())) .decryptWith(new PGPSecretKeyRingCollection(Collections.singleton(juliet)), new UnprotectedKeysProtector()) .verifyWith( @@ -48,7 +48,6 @@ public class TestKeysTest extends AbstractPGPainlessTest { .ignoreMissingPublicKeys() .build(); - InputStream decryptor = resultAndInputStream.getInputStream(); ByteArrayOutputStream toPlain = new ByteArrayOutputStream(); Streams.pipeAll(decryptor, toPlain); decryptor.close();