From 0b69e18715ae14220e054bebc30a635eef5f5e50 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 9 Jun 2022 00:44:09 +0200 Subject: [PATCH] Experimental support for inline-sign, inline-verify --- .../pgpainless/cli/commands/ArmorTest.java | 4 +- .../pgpainless/cli/commands/DearmorTest.java | 4 +- .../DetachInbandSignatureAndMessageTest.java | 6 +- .../cli/commands/SignVerifyTest.java | 4 +- .../misc/SignUsingPublicKeyBehaviorTest.java | 2 +- .../java/org/pgpainless/sop/DecryptImpl.java | 11 +- .../org/pgpainless/sop/DetachedSignImpl.java | 19 +-- .../java/org/pgpainless/sop/EncryptImpl.java | 51 ++++--- .../org/pgpainless/sop/InlineSignImpl.java | 124 ++++++++++++++++-- .../org/pgpainless/sop/InlineVerifyImpl.java | 71 +++++++++- .../java/org/pgpainless/sop/SignTest.java | 13 -- 11 files changed, 222 insertions(+), 87 deletions(-) diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ArmorTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ArmorTest.java index 409d4040..c4644319 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ArmorTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/ArmorTest.java @@ -43,7 +43,7 @@ public class ArmorTest { @FailOnSystemExit public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() - .modernKeyRing("alice@pgpainless.org", null); + .modernKeyRing("alice@pgpainless.org"); byte[] bytes = secretKey.getEncoded(); System.setIn(new ByteArrayInputStream(bytes)); @@ -59,7 +59,7 @@ public class ArmorTest { @FailOnSystemExit public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() - .modernKeyRing("alice@pgpainless.org", null); + .modernKeyRing("alice@pgpainless.org"); PGPPublicKeyRing publicKey = PGPainless.extractCertificate(secretKey); byte[] bytes = publicKey.getEncoded(); diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DearmorTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DearmorTest.java index ab5e2c7a..4dc43822 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DearmorTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DearmorTest.java @@ -43,7 +43,7 @@ public class DearmorTest { @FailOnSystemExit public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() - .modernKeyRing("alice@pgpainless.org", null); + .modernKeyRing("alice@pgpainless.org"); String armored = PGPainless.asciiArmor(secretKey); System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8))); @@ -59,7 +59,7 @@ public class DearmorTest { @FailOnSystemExit public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() - .modernKeyRing("alice@pgpainless.org", null); + .modernKeyRing("alice@pgpainless.org"); PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey); String armored = PGPainless.asciiArmor(certificate); diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DetachInbandSignatureAndMessageTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DetachInbandSignatureAndMessageTest.java index 15cf2546..dd7a77d2 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DetachInbandSignatureAndMessageTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/DetachInbandSignatureAndMessageTest.java @@ -115,7 +115,7 @@ public class DetachInbandSignatureAndMessageTest { // Detach File tempSigFile = new File(tempDir, "sig.out"); - PGPainlessCLI.main(new String[] {"detach-inband-signature-and-message", "--signatures-out=" + tempSigFile.getAbsolutePath()}); + PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + tempSigFile.getAbsolutePath()}); // Test equality with expected values assertEquals(CLEAR_SIGNED_BODY, msgOut.toString()); @@ -150,7 +150,7 @@ public class DetachInbandSignatureAndMessageTest { // Detach File tempSigFile = new File(tempDir, "sig.asc"); - PGPainlessCLI.main(new String[] {"detach-inband-signature-and-message", "--signatures-out=" + tempSigFile.getAbsolutePath(), "--no-armor"}); + PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + tempSigFile.getAbsolutePath(), "--no-armor"}); // Test equality with expected values assertEquals(CLEAR_SIGNED_BODY, msgOut.toString()); @@ -187,7 +187,7 @@ public class DetachInbandSignatureAndMessageTest { // Detach File existingSigFile = new File(tempDir, "sig.existing"); assertTrue(existingSigFile.createNewFile()); - PGPainlessCLI.main(new String[] {"detach-inband-signature-and-message", "--signatures-out=" + existingSigFile.getAbsolutePath()}); + PGPainlessCLI.main(new String[] {"inline-detach", "--signatures-out=" + existingSigFile.getAbsolutePath()}); } } diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/SignVerifyTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/SignVerifyTest.java index 525e3e1d..c207db4e 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/SignVerifyTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/commands/SignVerifyTest.java @@ -59,7 +59,7 @@ public class SignVerifyTest { File aliceKeyFile = new File(tempDir, "alice.key"); assertTrue(aliceKeyFile.createNewFile()); PGPSecretKeyRing aliceKeys = PGPainless.generateKeyRing() - .modernKeyRing("alice", null); + .modernKeyRing("alice"); OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile); Streams.pipeAll(new ByteArrayInputStream(aliceKeys.getEncoded()), aliceKeyOut); aliceKeyOut.close(); @@ -108,7 +108,7 @@ public class SignVerifyTest { String[] split = verification.split(" "); OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys); OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0)); - assertEquals(signingKeyFingerprint.toString(), split[1].trim()); + assertEquals(signingKeyFingerprint.toString(), split[1].trim(), verification); assertEquals(primaryKeyFingerprint.toString(), split[2].trim()); // Test micalg output diff --git a/pgpainless-cli/src/test/java/org/pgpainless/cli/misc/SignUsingPublicKeyBehaviorTest.java b/pgpainless-cli/src/test/java/org/pgpainless/cli/misc/SignUsingPublicKeyBehaviorTest.java index 0c6eac2b..81562d28 100644 --- a/pgpainless-cli/src/test/java/org/pgpainless/cli/misc/SignUsingPublicKeyBehaviorTest.java +++ b/pgpainless-cli/src/test/java/org/pgpainless/cli/misc/SignUsingPublicKeyBehaviorTest.java @@ -134,7 +134,7 @@ public class SignUsingPublicKeyBehaviorTest { assertTrue(sigFile.createNewFile()); FileOutputStream sigOut = new FileOutputStream(sigFile); System.setOut(new PrintStream(sigOut)); - PGPainlessCLI.execute("sign", "--armor", aliceKeyFile.getAbsolutePath()); + PGPainlessCLI.main(new String[] {"sign", "--armor", aliceKeyFile.getAbsolutePath()}); System.setIn(originalIn); } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java index 89eb29db..4c813efe 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/DecryptImpl.java @@ -10,9 +10,7 @@ import java.io.OutputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; @@ -39,7 +37,6 @@ import sop.operation.Decrypt; public class DecryptImpl implements Decrypt { private final ConsumerOptions consumerOptions = new ConsumerOptions(); - private final Set keys = new HashSet<>(); private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector(); @Override @@ -105,7 +102,8 @@ public class DecryptImpl implements Decrypt { PGPSecretKeyRingCollection secretKeyCollection = PGPainless.readKeyRing() .secretKeyRingCollection(keyIn); for (PGPSecretKeyRing key : secretKeyCollection) { - keys.add(key); + protector.addSecretKey(key); + consumerOptions.addDecryptionKey(key, protector); } } catch (IOException | PGPException e) { throw new SOPGPException.BadData(e); @@ -124,10 +122,6 @@ public class DecryptImpl implements Decrypt { public ReadyWithResult ciphertext(InputStream ciphertext) throws SOPGPException.BadData, SOPGPException.MissingArg { - for (PGPSecretKeyRing key : keys) { - protector.addSecretKey(key); - consumerOptions.addDecryptionKey(key, protector); - } if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty() && consumerOptions.getSessionKey() == null) { throw new SOPGPException.MissingArg("Missing decryption key, passphrase or session key."); @@ -145,6 +139,7 @@ public class DecryptImpl implements Decrypt { } catch (PGPException | IOException e) { throw new SOPGPException.BadData(e); } finally { + // Forget passphrases after decryption protector.clear(); } diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedSignImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedSignImpl.java index 3341712a..3b985de6 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedSignImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/DetachedSignImpl.java @@ -8,6 +8,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -24,9 +25,8 @@ import org.pgpainless.encryption_signing.ProducerOptions; import org.pgpainless.encryption_signing.SigningOptions; import org.pgpainless.exception.KeyException; import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.util.ArmoredOutputStreamFactory; +import org.pgpainless.util.Passphrase; import sop.MicAlg; import sop.ReadyWithResult; import sop.SigningResult; @@ -39,6 +39,7 @@ public class DetachedSignImpl implements DetachedSign { private boolean armor = true; private SignAs mode = SignAs.Binary; private final SigningOptions signingOptions = new SigningOptions(); + private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector(); @Override public DetachedSign noArmor() { @@ -58,11 +59,8 @@ public class DetachedSignImpl implements DetachedSign { PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn); for (PGPSecretKeyRing key : keys) { - KeyRingInfo info = new KeyRingInfo(key); - if (!info.isFullyDecrypted()) { - throw new SOPGPException.KeyIsProtected(); - } - signingOptions.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key, modeToSigType(mode)); + protector.addSecretKey(key); + signingOptions.addDetachedSignature(protector, key, modeToSigType(mode)); } } catch (PGPException | KeyException e) { throw new SOPGPException.BadData(e); @@ -72,7 +70,9 @@ public class DetachedSignImpl implements DetachedSign { @Override public DetachedSign withKeyPassword(byte[] password) { - return null; + String string = new String(password, Charset.forName("UTF8")); + protector.addPassphrase(Passphrase.fromPassword(string)); + return this; } @Override @@ -96,6 +96,9 @@ public class DetachedSignImpl implements DetachedSign { signingStream.close(); EncryptionResult encryptionResult = signingStream.getResult(); + // forget passphrases + protector.clear(); + List signatures = new ArrayList<>(); for (SubkeyIdentifier key : encryptionResult.getDetachedSignatures().keySet()) { signatures.addAll(encryptionResult.getDetachedSignatures().get(key)); diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java index bfa80f47..635ab82f 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/EncryptImpl.java @@ -8,8 +8,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; -import java.util.HashSet; -import java.util.Set; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; @@ -25,18 +23,16 @@ import org.pgpainless.encryption_signing.ProducerOptions; import org.pgpainless.encryption_signing.SigningOptions; import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.util.Passphrase; -import sop.util.ProxyOutputStream; import sop.Ready; import sop.enums.EncryptAs; import sop.exception.SOPGPException; import sop.operation.Encrypt; +import sop.util.ProxyOutputStream; public class EncryptImpl implements Encrypt { EncryptionOptions encryptionOptions = new EncryptionOptions(); SigningOptions signingOptions = null; - - Set signingKeys = new HashSet<>(); MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector(); private EncryptAs encryptAs = EncryptAs.Binary; @@ -55,17 +51,34 @@ public class EncryptImpl implements Encrypt { } @Override - public Encrypt signWith(InputStream keyIn) throws SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData { + public Encrypt signWith(InputStream keyIn) + throws SOPGPException.KeyCannotSign, SOPGPException.UnsupportedAsymmetricAlgo, SOPGPException.BadData { + if (signingOptions == null) { + signingOptions = SigningOptions.get(); + } + try { PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn); if (keys.size() != 1) { throw new SOPGPException.BadData(new AssertionError("Exactly one secret key at a time expected. Got " + keys.size())); } - if (signingOptions == null) { - signingOptions = SigningOptions.get(); + PGPSecretKeyRing signingKey = keys.iterator().next(); + protector.addSecretKey(signingKey); + + try { + signingOptions.addInlineSignature( + protector, + signingKey, + (encryptAs == EncryptAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT : DocumentSignatureType.CANONICAL_TEXT_DOCUMENT) + ); + } catch (IllegalArgumentException e) { + throw new SOPGPException.KeyCannotSign(); + } catch (WrongPassphraseException e) { + throw new SOPGPException.KeyIsProtected(); + } catch (PGPException e) { + throw new SOPGPException.BadData(e); } - signingKeys.add(keys.getKeyRings().next()); } catch (IOException | PGPException e) { throw new SOPGPException.BadData(e); } @@ -100,26 +113,6 @@ public class EncryptImpl implements Encrypt { @Override public Ready plaintext(InputStream plaintext) throws IOException { - for (PGPSecretKeyRing signingKey : signingKeys) { - protector.addSecretKey(signingKey); - } - - if (signingOptions != null) { - try { - signingOptions.addInlineSignatures( - protector, - signingKeys, - (encryptAs == EncryptAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT : DocumentSignatureType.CANONICAL_TEXT_DOCUMENT) - ); - } catch (IllegalArgumentException e) { - throw new SOPGPException.KeyCannotSign(); - } catch (WrongPassphraseException e) { - throw new SOPGPException.KeyIsProtected(); - } catch (PGPException e) { - throw new SOPGPException.BadData(e); - } - } - ProducerOptions producerOptions = signingOptions != null ? ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions) : ProducerOptions.encrypt(encryptionOptions); diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineSignImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineSignImpl.java index 7eaeaa6f..d7b0c161 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineSignImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineSignImpl.java @@ -4,39 +4,139 @@ package org.pgpainless.sop; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.util.io.Streams; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.DocumentSignatureType; +import org.pgpainless.encryption_signing.EncryptionResult; +import org.pgpainless.encryption_signing.EncryptionStream; +import org.pgpainless.encryption_signing.ProducerOptions; +import org.pgpainless.encryption_signing.SigningOptions; +import org.pgpainless.exception.KeyException; +import org.pgpainless.key.SubkeyIdentifier; +import org.pgpainless.util.Passphrase; +import sop.MicAlg; import sop.ReadyWithResult; import sop.SigningResult; import sop.enums.InlineSignAs; import sop.exception.SOPGPException; -import sop.operation.DetachedSign; import sop.operation.InlineSign; -import java.io.IOException; -import java.io.InputStream; - public class InlineSignImpl implements InlineSign { + + private boolean armor = true; + private InlineSignAs mode = InlineSignAs.Binary; + private final SigningOptions signingOptions = new SigningOptions(); + private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector(); + @Override - public DetachedSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption { - return null; + public InlineSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption { + this.mode = mode; + return this; } @Override - public DetachedSign noArmor() { - return null; + public InlineSign noArmor() { + this.armor = false; + return this; } @Override - public InlineSign key(InputStream key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException { - return null; + public InlineSign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException { + try { + PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn); + + for (PGPSecretKeyRing key : keys) { + protector.addSecretKey(key); + if (mode == InlineSignAs.CleartextSigned) { + signingOptions.addDetachedSignature(protector, key, DocumentSignatureType.BINARY_DOCUMENT); + } else { + signingOptions.addInlineSignature(protector, key, modeToSigType(mode)); + } + } + } catch (PGPException | KeyException e) { + throw new SOPGPException.BadData(e); + } + return this; } @Override public InlineSign withKeyPassword(byte[] password) { - return null; + String string = new String(password, Charset.forName("UTF8")); + protector.addPassphrase(Passphrase.fromPassword(string)); + return this; } @Override public ReadyWithResult data(InputStream data) throws IOException, SOPGPException.ExpectedText { - return null; + + ProducerOptions producerOptions = ProducerOptions.sign(signingOptions); + if (mode == InlineSignAs.CleartextSigned) { + producerOptions.setCleartextSigned(); + producerOptions.setAsciiArmor(true); + } else { + producerOptions.setAsciiArmor(armor); + } + + return new ReadyWithResult() { + @Override + public SigningResult writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature { + try { + EncryptionStream signingStream = PGPainless.encryptAndOrSign() + .onOutputStream(outputStream) + .withOptions(producerOptions); + + if (signingStream.isClosed()) { + throw new IllegalStateException("EncryptionStream is already closed."); + } + + Streams.pipeAll(data, signingStream); + signingStream.close(); + EncryptionResult encryptionResult = signingStream.getResult(); + + // forget passphrases + protector.clear(); + + List signatures = new ArrayList<>(); + for (SubkeyIdentifier key : encryptionResult.getDetachedSignatures().keySet()) { + signatures.addAll(encryptionResult.getDetachedSignatures().get(key)); + } + + return SigningResult.builder() + .setMicAlg(micAlgFromSignatures(signatures)) + .build(); + } catch (PGPException e) { + throw new RuntimeException(e); + } + } + }; + } + + private MicAlg micAlgFromSignatures(Iterable signatures) { + int algorithmId = 0; + for (PGPSignature signature : signatures) { + int sigAlg = signature.getHashAlgorithm(); + if (algorithmId == 0 || algorithmId == sigAlg) { + algorithmId = sigAlg; + } else { + return MicAlg.empty(); + } + } + return algorithmId == 0 ? MicAlg.empty() : MicAlg.fromHashAlgorithmId(algorithmId); + } + + private static DocumentSignatureType modeToSigType(InlineSignAs mode) { + return mode == InlineSignAs.Binary ? DocumentSignatureType.BINARY_DOCUMENT + : DocumentSignatureType.CANONICAL_TEXT_DOCUMENT; } } 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 a230e0fd..7c1f7ce2 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineVerifyImpl.java @@ -4,6 +4,15 @@ package org.pgpainless.sop; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.util.io.Streams; +import org.pgpainless.PGPainless; +import org.pgpainless.decryption_verification.ConsumerOptions; +import org.pgpainless.decryption_verification.DecryptionStream; +import org.pgpainless.decryption_verification.OpenPgpMetadata; +import org.pgpainless.key.SubkeyIdentifier; import sop.ReadyWithResult; import sop.Verification; import sop.exception.SOPGPException; @@ -11,27 +20,75 @@ import sop.operation.InlineVerify; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; import java.util.Date; import java.util.List; public class InlineVerifyImpl implements InlineVerify { - @Override - public ReadyWithResult> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { - return null; - } + + private final ConsumerOptions options = new ConsumerOptions(); @Override public InlineVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { - return null; + options.verifyNotBefore(timestamp); + return this; } @Override public InlineVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { - return null; + options.verifyNotAfter(timestamp); + return this; } @Override public InlineVerify cert(InputStream cert) throws SOPGPException.BadData { - return null; + PGPPublicKeyRingCollection certificates; + try { + certificates = PGPainless.readKeyRing().publicKeyRingCollection(cert); + } catch (IOException | PGPException e) { + throw new SOPGPException.BadData(e); + } + options.addVerificationCerts(certificates); + return this; + } + + @Override + public ReadyWithResult> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { + return new ReadyWithResult>() { + @Override + public List writeTo(OutputStream outputStream) throws IOException, SOPGPException.NoSignature { + DecryptionStream decryptionStream; + try { + decryptionStream = PGPainless.decryptAndOrVerify() + .onInputStream(data) + .withOptions(options); + + Streams.pipeAll(decryptionStream, outputStream); + decryptionStream.close(); + + OpenPgpMetadata metadata = decryptionStream.getResult(); + List verificationList = new ArrayList<>(); + + for (SubkeyIdentifier verifiedSigningKey : metadata.getVerifiedSignatures().keySet()) { + PGPSignature signature = metadata.getVerifiedSignatures().get(verifiedSigningKey); + verificationList.add(new Verification( + signature.getCreationTime(), + verifiedSigningKey.getSubkeyFingerprint().toString(), + verifiedSigningKey.getPrimaryKeyFingerprint().toString())); + } + + if (!options.getCertificates().isEmpty()) { + if (verificationList.isEmpty()) { + throw new SOPGPException.NoSignature(); + } + } + + return verificationList; + } catch (PGPException e) { + throw new SOPGPException.BadData(e); + } + } + }; } } diff --git a/pgpainless-sop/src/test/java/org/pgpainless/sop/SignTest.java b/pgpainless-sop/src/test/java/org/pgpainless/sop/SignTest.java index 4736002f..7e2d55dd 100644 --- a/pgpainless-sop/src/test/java/org/pgpainless/sop/SignTest.java +++ b/pgpainless-sop/src/test/java/org/pgpainless/sop/SignTest.java @@ -11,17 +11,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.NoSuchAlgorithmException; import java.util.Date; import java.util.List; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.pgpainless.PGPainless; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.signature.SignatureUtils; import sop.SOP; @@ -128,13 +124,4 @@ public class SignTest { assertEquals(SignatureType.CANONICAL_TEXT_DOCUMENT.getCode(), sig.getSignatureType()); } - @Test - public void rejectEncryptedKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { - PGPSecretKeyRing key = PGPainless.generateKeyRing() - .modernKeyRing("Alice", "passphrase"); - byte[] bytes = key.getEncoded(); - - assertThrows(SOPGPException.KeyIsProtected.class, () -> sop.sign().key(bytes)); - } - }