1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-06-17 00:54:50 +02:00

Experimental support for inline-sign, inline-verify

This commit is contained in:
Paul Schaub 2022-06-09 00:44:09 +02:00
parent dd26b5230d
commit 0b69e18715
11 changed files with 222 additions and 87 deletions

View file

@ -43,7 +43,7 @@ public class ArmorTest {
@FailOnSystemExit @FailOnSystemExit
public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { public void armorSecretKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("alice@pgpainless.org", null); .modernKeyRing("alice@pgpainless.org");
byte[] bytes = secretKey.getEncoded(); byte[] bytes = secretKey.getEncoded();
System.setIn(new ByteArrayInputStream(bytes)); System.setIn(new ByteArrayInputStream(bytes));
@ -59,7 +59,7 @@ public class ArmorTest {
@FailOnSystemExit @FailOnSystemExit
public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { public void armorPublicKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("alice@pgpainless.org", null); .modernKeyRing("alice@pgpainless.org");
PGPPublicKeyRing publicKey = PGPainless.extractCertificate(secretKey); PGPPublicKeyRing publicKey = PGPainless.extractCertificate(secretKey);
byte[] bytes = publicKey.getEncoded(); byte[] bytes = publicKey.getEncoded();

View file

@ -43,7 +43,7 @@ public class DearmorTest {
@FailOnSystemExit @FailOnSystemExit
public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { public void dearmorSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("alice@pgpainless.org", null); .modernKeyRing("alice@pgpainless.org");
String armored = PGPainless.asciiArmor(secretKey); String armored = PGPainless.asciiArmor(secretKey);
System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8))); System.setIn(new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)));
@ -59,7 +59,7 @@ public class DearmorTest {
@FailOnSystemExit @FailOnSystemExit
public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { public void dearmorCertificate() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() PGPSecretKeyRing secretKey = PGPainless.generateKeyRing()
.modernKeyRing("alice@pgpainless.org", null); .modernKeyRing("alice@pgpainless.org");
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey); PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
String armored = PGPainless.asciiArmor(certificate); String armored = PGPainless.asciiArmor(certificate);

View file

@ -115,7 +115,7 @@ public class DetachInbandSignatureAndMessageTest {
// Detach // Detach
File tempSigFile = new File(tempDir, "sig.out"); 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 // Test equality with expected values
assertEquals(CLEAR_SIGNED_BODY, msgOut.toString()); assertEquals(CLEAR_SIGNED_BODY, msgOut.toString());
@ -150,7 +150,7 @@ public class DetachInbandSignatureAndMessageTest {
// Detach // Detach
File tempSigFile = new File(tempDir, "sig.asc"); 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 // Test equality with expected values
assertEquals(CLEAR_SIGNED_BODY, msgOut.toString()); assertEquals(CLEAR_SIGNED_BODY, msgOut.toString());
@ -187,7 +187,7 @@ public class DetachInbandSignatureAndMessageTest {
// Detach // Detach
File existingSigFile = new File(tempDir, "sig.existing"); File existingSigFile = new File(tempDir, "sig.existing");
assertTrue(existingSigFile.createNewFile()); 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()});
} }
} }

View file

@ -59,7 +59,7 @@ public class SignVerifyTest {
File aliceKeyFile = new File(tempDir, "alice.key"); File aliceKeyFile = new File(tempDir, "alice.key");
assertTrue(aliceKeyFile.createNewFile()); assertTrue(aliceKeyFile.createNewFile());
PGPSecretKeyRing aliceKeys = PGPainless.generateKeyRing() PGPSecretKeyRing aliceKeys = PGPainless.generateKeyRing()
.modernKeyRing("alice", null); .modernKeyRing("alice");
OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile); OutputStream aliceKeyOut = new FileOutputStream(aliceKeyFile);
Streams.pipeAll(new ByteArrayInputStream(aliceKeys.getEncoded()), aliceKeyOut); Streams.pipeAll(new ByteArrayInputStream(aliceKeys.getEncoded()), aliceKeyOut);
aliceKeyOut.close(); aliceKeyOut.close();
@ -108,7 +108,7 @@ public class SignVerifyTest {
String[] split = verification.split(" "); String[] split = verification.split(" ");
OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys); OpenPgpV4Fingerprint primaryKeyFingerprint = new OpenPgpV4Fingerprint(aliceKeys);
OpenPgpV4Fingerprint signingKeyFingerprint = new OpenPgpV4Fingerprint(new KeyRingInfo(alicePub, new Date()).getSigningSubkeys().get(0)); 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()); assertEquals(primaryKeyFingerprint.toString(), split[2].trim());
// Test micalg output // Test micalg output

View file

@ -134,7 +134,7 @@ public class SignUsingPublicKeyBehaviorTest {
assertTrue(sigFile.createNewFile()); assertTrue(sigFile.createNewFile());
FileOutputStream sigOut = new FileOutputStream(sigFile); FileOutputStream sigOut = new FileOutputStream(sigFile);
System.setOut(new PrintStream(sigOut)); System.setOut(new PrintStream(sigOut));
PGPainlessCLI.execute("sign", "--armor", aliceKeyFile.getAbsolutePath()); PGPainlessCLI.main(new String[] {"sign", "--armor", aliceKeyFile.getAbsolutePath()});
System.setIn(originalIn); System.setIn(originalIn);
} }

View file

@ -10,9 +10,7 @@ import java.io.OutputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
@ -39,7 +37,6 @@ import sop.operation.Decrypt;
public class DecryptImpl implements Decrypt { public class DecryptImpl implements Decrypt {
private final ConsumerOptions consumerOptions = new ConsumerOptions(); private final ConsumerOptions consumerOptions = new ConsumerOptions();
private final Set<PGPSecretKeyRing> keys = new HashSet<>();
private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector(); private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector();
@Override @Override
@ -105,7 +102,8 @@ public class DecryptImpl implements Decrypt {
PGPSecretKeyRingCollection secretKeyCollection = PGPainless.readKeyRing() PGPSecretKeyRingCollection secretKeyCollection = PGPainless.readKeyRing()
.secretKeyRingCollection(keyIn); .secretKeyRingCollection(keyIn);
for (PGPSecretKeyRing key : secretKeyCollection) { for (PGPSecretKeyRing key : secretKeyCollection) {
keys.add(key); protector.addSecretKey(key);
consumerOptions.addDecryptionKey(key, protector);
} }
} catch (IOException | PGPException e) { } catch (IOException | PGPException e) {
throw new SOPGPException.BadData(e); throw new SOPGPException.BadData(e);
@ -124,10 +122,6 @@ public class DecryptImpl implements Decrypt {
public ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext) public ReadyWithResult<DecryptionResult> ciphertext(InputStream ciphertext)
throws SOPGPException.BadData, throws SOPGPException.BadData,
SOPGPException.MissingArg { SOPGPException.MissingArg {
for (PGPSecretKeyRing key : keys) {
protector.addSecretKey(key);
consumerOptions.addDecryptionKey(key, protector);
}
if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty() && consumerOptions.getSessionKey() == null) { if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty() && consumerOptions.getSessionKey() == null) {
throw new SOPGPException.MissingArg("Missing decryption key, passphrase or session key."); throw new SOPGPException.MissingArg("Missing decryption key, passphrase or session key.");
@ -145,6 +139,7 @@ public class DecryptImpl implements Decrypt {
} catch (PGPException | IOException e) { } catch (PGPException | IOException e) {
throw new SOPGPException.BadData(e); throw new SOPGPException.BadData(e);
} finally { } finally {
// Forget passphrases after decryption
protector.clear(); protector.clear();
} }

View file

@ -8,6 +8,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -24,9 +25,8 @@ import org.pgpainless.encryption_signing.ProducerOptions;
import org.pgpainless.encryption_signing.SigningOptions; import org.pgpainless.encryption_signing.SigningOptions;
import org.pgpainless.exception.KeyException; import org.pgpainless.exception.KeyException;
import org.pgpainless.key.SubkeyIdentifier; 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.ArmoredOutputStreamFactory;
import org.pgpainless.util.Passphrase;
import sop.MicAlg; import sop.MicAlg;
import sop.ReadyWithResult; import sop.ReadyWithResult;
import sop.SigningResult; import sop.SigningResult;
@ -39,6 +39,7 @@ public class DetachedSignImpl implements DetachedSign {
private boolean armor = true; private boolean armor = true;
private SignAs mode = SignAs.Binary; private SignAs mode = SignAs.Binary;
private final SigningOptions signingOptions = new SigningOptions(); private final SigningOptions signingOptions = new SigningOptions();
private final MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector();
@Override @Override
public DetachedSign noArmor() { public DetachedSign noArmor() {
@ -58,11 +59,8 @@ public class DetachedSignImpl implements DetachedSign {
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn); PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
for (PGPSecretKeyRing key : keys) { for (PGPSecretKeyRing key : keys) {
KeyRingInfo info = new KeyRingInfo(key); protector.addSecretKey(key);
if (!info.isFullyDecrypted()) { signingOptions.addDetachedSignature(protector, key, modeToSigType(mode));
throw new SOPGPException.KeyIsProtected();
}
signingOptions.addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), key, modeToSigType(mode));
} }
} catch (PGPException | KeyException e) { } catch (PGPException | KeyException e) {
throw new SOPGPException.BadData(e); throw new SOPGPException.BadData(e);
@ -72,7 +70,9 @@ public class DetachedSignImpl implements DetachedSign {
@Override @Override
public DetachedSign withKeyPassword(byte[] password) { public DetachedSign withKeyPassword(byte[] password) {
return null; String string = new String(password, Charset.forName("UTF8"));
protector.addPassphrase(Passphrase.fromPassword(string));
return this;
} }
@Override @Override
@ -96,6 +96,9 @@ public class DetachedSignImpl implements DetachedSign {
signingStream.close(); signingStream.close();
EncryptionResult encryptionResult = signingStream.getResult(); EncryptionResult encryptionResult = signingStream.getResult();
// forget passphrases
protector.clear();
List<PGPSignature> signatures = new ArrayList<>(); List<PGPSignature> signatures = new ArrayList<>();
for (SubkeyIdentifier key : encryptionResult.getDetachedSignatures().keySet()) { for (SubkeyIdentifier key : encryptionResult.getDetachedSignatures().keySet()) {
signatures.addAll(encryptionResult.getDetachedSignatures().get(key)); signatures.addAll(encryptionResult.getDetachedSignatures().get(key));

View file

@ -8,8 +8,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
@ -25,18 +23,16 @@ import org.pgpainless.encryption_signing.ProducerOptions;
import org.pgpainless.encryption_signing.SigningOptions; import org.pgpainless.encryption_signing.SigningOptions;
import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.exception.WrongPassphraseException;
import org.pgpainless.util.Passphrase; import org.pgpainless.util.Passphrase;
import sop.util.ProxyOutputStream;
import sop.Ready; import sop.Ready;
import sop.enums.EncryptAs; import sop.enums.EncryptAs;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
import sop.operation.Encrypt; import sop.operation.Encrypt;
import sop.util.ProxyOutputStream;
public class EncryptImpl implements Encrypt { public class EncryptImpl implements Encrypt {
EncryptionOptions encryptionOptions = new EncryptionOptions(); EncryptionOptions encryptionOptions = new EncryptionOptions();
SigningOptions signingOptions = null; SigningOptions signingOptions = null;
Set<PGPSecretKeyRing> signingKeys = new HashSet<>();
MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector(); MatchMakingSecretKeyRingProtector protector = new MatchMakingSecretKeyRingProtector();
private EncryptAs encryptAs = EncryptAs.Binary; private EncryptAs encryptAs = EncryptAs.Binary;
@ -55,17 +51,34 @@ public class EncryptImpl implements Encrypt {
} }
@Override @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 { try {
PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn); PGPSecretKeyRingCollection keys = PGPainless.readKeyRing().secretKeyRingCollection(keyIn);
if (keys.size() != 1) { if (keys.size() != 1) {
throw new SOPGPException.BadData(new AssertionError("Exactly one secret key at a time expected. Got " + keys.size())); throw new SOPGPException.BadData(new AssertionError("Exactly one secret key at a time expected. Got " + keys.size()));
} }
if (signingOptions == null) { PGPSecretKeyRing signingKey = keys.iterator().next();
signingOptions = SigningOptions.get(); 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) { } catch (IOException | PGPException e) {
throw new SOPGPException.BadData(e); throw new SOPGPException.BadData(e);
} }
@ -100,26 +113,6 @@ public class EncryptImpl implements Encrypt {
@Override @Override
public Ready plaintext(InputStream plaintext) throws IOException { 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 producerOptions = signingOptions != null ?
ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions) : ProducerOptions.signAndEncrypt(encryptionOptions, signingOptions) :
ProducerOptions.encrypt(encryptionOptions); ProducerOptions.encrypt(encryptionOptions);

View file

@ -4,39 +4,139 @@
package org.pgpainless.sop; 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.ReadyWithResult;
import sop.SigningResult; import sop.SigningResult;
import sop.enums.InlineSignAs; import sop.enums.InlineSignAs;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
import sop.operation.DetachedSign;
import sop.operation.InlineSign; import sop.operation.InlineSign;
import java.io.IOException;
import java.io.InputStream;
public class InlineSignImpl implements InlineSign { 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 @Override
public DetachedSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption { public InlineSign mode(InlineSignAs mode) throws SOPGPException.UnsupportedOption {
return null; this.mode = mode;
return this;
} }
@Override @Override
public DetachedSign noArmor() { public InlineSign noArmor() {
return null; this.armor = false;
return this;
} }
@Override @Override
public InlineSign key(InputStream key) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException { public InlineSign key(InputStream keyIn) throws SOPGPException.KeyIsProtected, SOPGPException.BadData, IOException {
return null; 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 @Override
public InlineSign withKeyPassword(byte[] password) { public InlineSign withKeyPassword(byte[] password) {
return null; String string = new String(password, Charset.forName("UTF8"));
protector.addPassphrase(Passphrase.fromPassword(string));
return this;
} }
@Override @Override
public ReadyWithResult<SigningResult> data(InputStream data) throws IOException, SOPGPException.ExpectedText { public ReadyWithResult<SigningResult> 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<SigningResult>() {
@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<PGPSignature> 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<PGPSignature> 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;
} }
} }

View file

@ -4,6 +4,15 @@
package org.pgpainless.sop; 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.ReadyWithResult;
import sop.Verification; import sop.Verification;
import sop.exception.SOPGPException; import sop.exception.SOPGPException;
@ -11,27 +20,75 @@ import sop.operation.InlineVerify;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
public class InlineVerifyImpl implements InlineVerify { public class InlineVerifyImpl implements InlineVerify {
@Override
public ReadyWithResult<List<Verification>> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData { private final ConsumerOptions options = new ConsumerOptions();
return null;
}
@Override @Override
public InlineVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption { public InlineVerify notBefore(Date timestamp) throws SOPGPException.UnsupportedOption {
return null; options.verifyNotBefore(timestamp);
return this;
} }
@Override @Override
public InlineVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption { public InlineVerify notAfter(Date timestamp) throws SOPGPException.UnsupportedOption {
return null; options.verifyNotAfter(timestamp);
return this;
} }
@Override @Override
public InlineVerify cert(InputStream cert) throws SOPGPException.BadData { 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<List<Verification>> data(InputStream data) throws IOException, SOPGPException.NoSignature, SOPGPException.BadData {
return new ReadyWithResult<List<Verification>>() {
@Override
public List<Verification> 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<Verification> 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);
}
}
};
} }
} }

View file

@ -11,17 +11,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.signature.SignatureUtils; import org.pgpainless.signature.SignatureUtils;
import sop.SOP; import sop.SOP;
@ -128,13 +124,4 @@ public class SignTest {
assertEquals(SignatureType.CANONICAL_TEXT_DOCUMENT.getCode(), sig.getSignatureType()); 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));
}
} }