1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-12-23 03:17:58 +01:00

Add support for detached signatures

This commit is contained in:
Paul Schaub 2020-08-24 14:55:06 +02:00
parent e8ccf78455
commit 65b670740e
14 changed files with 410 additions and 122 deletions

View file

@ -3,6 +3,7 @@ plugins {
}
dependencies {
implementation 'org.jetbrains:annotations:19.0.0'
testImplementation group: 'junit', name: 'junit', version: '4.12'
/*
implementation "org.bouncycastle:bcprov-debug-jdk15on:$bouncyCastleVersion"

View file

@ -17,15 +17,24 @@ package org.pgpainless.decryption_verification;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.protection.SecretKeyRingProtector;
@ -34,9 +43,12 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
private InputStream inputStream;
private PGPSecretKeyRingCollection decryptionKeys;
private SecretKeyRingProtector decryptionKeyDecryptor;
private List<PGPSignature> detachedSignatures;
private Set<PGPPublicKeyRing> verificationKeys = new HashSet<>();
private MissingPublicKeyCallback missingPublicKeyCallback = null;
private final KeyFingerPrintCalculator keyFingerPrintCalculator = new BcKeyFingerprintCalculator();
@Override
public DecryptWith onInputStream(InputStream inputStream) {
this.inputStream = inputStream;
@ -46,17 +58,76 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
class DecryptWithImpl implements DecryptWith {
@Override
public VerifyWith decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings) {
public Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings) {
DecryptionBuilder.this.decryptionKeys = secretKeyRings;
DecryptionBuilder.this.decryptionKeyDecryptor = decryptor;
return new VerifyImpl();
}
@Override
public Verify doNotDecrypt() {
DecryptionBuilder.this.decryptionKeys = null;
DecryptionBuilder.this.decryptionKeyDecryptor = null;
return new VerifyImpl();
}
}
class VerifyImpl implements Verify {
@Override
public VerifyWith verifyDetachedSignature(InputStream inputStream) throws IOException, PGPException {
List<PGPSignature> signatures = new ArrayList<>();
InputStream pgpIn = PGPUtil.getDecoderStream(inputStream);
PGPObjectFactory objectFactory = new PGPObjectFactory(
pgpIn, keyFingerPrintCalculator);
Object nextObject = objectFactory.nextObject();
while (nextObject != null) {
if (nextObject instanceof PGPCompressedData) {
PGPCompressedData compressedData = (PGPCompressedData) nextObject;
objectFactory = new PGPObjectFactory(compressedData.getDataStream(), keyFingerPrintCalculator);
nextObject = objectFactory.nextObject();
continue;
}
if (nextObject instanceof PGPSignatureList) {
PGPSignatureList signatureList = (PGPSignatureList) nextObject;
for (PGPSignature s : signatureList) {
signatures.add(s);
}
}
if (nextObject instanceof PGPSignature) {
signatures.add((PGPSignature) nextObject);
}
nextObject = objectFactory.nextObject();
}
pgpIn.close();
return verifyDetachedSignatures(signatures);
}
@Override
public VerifyWith verifyDetachedSignatures(List<PGPSignature> signatures) {
DecryptionBuilder.this.detachedSignatures = signatures;
return new VerifyWithImpl();
}
@Override
public VerifyWith doNotDecrypt() {
DecryptionBuilder.this.decryptionKeys = null;
DecryptionBuilder.this.decryptionKeyDecryptor = null;
return new VerifyWithImpl();
public HandleMissingPublicKeys verifyWith(@org.jetbrains.annotations.NotNull PGPPublicKeyRingCollection publicKeyRings) {
return new VerifyWithImpl().verifyWith(publicKeyRings);
}
@Override
public HandleMissingPublicKeys verifyWith(@org.jetbrains.annotations.NotNull Set<OpenPgpV4Fingerprint> trustedFingerprints, @org.jetbrains.annotations.NotNull PGPPublicKeyRingCollection publicKeyRings) {
return new VerifyWithImpl().verifyWith(trustedFingerprints, publicKeyRings);
}
@Override
public HandleMissingPublicKeys verifyWith(@org.jetbrains.annotations.NotNull Set<PGPPublicKeyRing> publicKeyRings) {
return new VerifyWithImpl().verifyWith(publicKeyRings);
}
@Override
public Build doNotVerify() {
DecryptionBuilder.this.verificationKeys = null;
return new BuildImpl();
}
}
@ -100,12 +171,6 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
DecryptionBuilder.this.verificationKeys = publicKeyRings;
return new HandleMissingPublicKeysImpl();
}
@Override
public Build doNotVerify() {
DecryptionBuilder.this.verificationKeys = null;
return new BuildImpl();
}
}
class HandleMissingPublicKeysImpl implements HandleMissingPublicKeys {
@ -128,7 +193,7 @@ public class DecryptionBuilder implements DecryptionBuilderInterface {
@Override
public DecryptionStream build() throws IOException, PGPException {
return DecryptionStreamFactory.create(inputStream,
decryptionKeys, decryptionKeyDecryptor, verificationKeys, missingPublicKeyCallback);
decryptionKeys, decryptionKeyDecryptor, detachedSignatures, verificationKeys, missingPublicKeyCallback);
}
}
}

View file

@ -18,12 +18,14 @@ package org.pgpainless.decryption_verification;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.protection.SecretKeyRingProtector;
@ -33,12 +35,21 @@ public interface DecryptionBuilderInterface {
interface DecryptWith {
VerifyWith decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings);
Verify decryptWith(@Nonnull SecretKeyRingProtector decryptor, @Nonnull PGPSecretKeyRingCollection secretKeyRings);
VerifyWith doNotDecrypt();
Verify doNotDecrypt();
}
interface Verify extends VerifyWith {
VerifyWith verifyDetachedSignature(InputStream inputStream) throws IOException, PGPException;
VerifyWith verifyDetachedSignatures(List<PGPSignature> signatures);
Build doNotVerify();
}
interface VerifyWith {
HandleMissingPublicKeys verifyWith(@Nonnull PGPPublicKeyRingCollection publicKeyRings);
@ -47,7 +58,6 @@ public interface DecryptionBuilderInterface {
HandleMissingPublicKeys verifyWith(@Nonnull Set<PGPPublicKeyRing> publicKeyRings);
Build doNotVerify();
}

View file

@ -19,6 +19,8 @@ import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import org.bouncycastle.openpgp.PGPException;
public class DecryptionStream extends InputStream {
private final InputStream inputStream;
@ -32,15 +34,36 @@ public class DecryptionStream extends InputStream {
@Override
public int read() throws IOException {
return inputStream.read();
int r = inputStream.read();
maybeUpdateDetachedSignatures(r);
return r;
}
private void maybeUpdateDetachedSignatures(int rByte) {
for (DetachedSignature s : resultBuilder.getDetachedSignatures()) {
if (rByte != -1) {
s.getSignature().update((byte) rByte);
}
}
}
@Override
public void close() throws IOException {
inputStream.close();
maybeVerifyDetachedSignatures();
this.isClosed = true;
}
void maybeVerifyDetachedSignatures() {
for (DetachedSignature s : resultBuilder.getDetachedSignatures()) {
try {
s.setVerified(s.getSignature().verify());
} catch (PGPException e) {
e.printStackTrace();
}
}
}
public OpenPgpMetadata getResult() {
if (!isClosed) {
throw new IllegalStateException("DecryptionStream MUST be closed before the result can be accessed.");

View file

@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
@ -41,6 +42,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
@ -66,7 +68,7 @@ public final class DecryptionStreamFactory {
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
private final PGPContentVerifierBuilderProvider verifierBuilderProvider = new BcPGPContentVerifierBuilderProvider();
private final KeyFingerPrintCalculator keyFingerprintCalculator = new BcKeyFingerprintCalculator();
private final Map<OpenPgpV4Fingerprint, PGPOnePassSignature> verifiableOnePassSignatures = new HashMap<>();
private final Map<OpenPgpV4Fingerprint, OnePassSignature> verifiableOnePassSignatures = new HashMap<>();
private DecryptionStreamFactory(@Nullable PGPSecretKeyRingCollection decryptionKeys,
@Nullable SecretKeyRingProtector decryptor,
@ -81,21 +83,31 @@ public final class DecryptionStreamFactory {
public static DecryptionStream create(@Nonnull InputStream inputStream,
@Nullable PGPSecretKeyRingCollection decryptionKeys,
@Nullable SecretKeyRingProtector decryptor,
@Nullable List<PGPSignature> detachedSignatures,
@Nullable Set<PGPPublicKeyRing> verificationKeys,
@Nullable MissingPublicKeyCallback missingPublicKeyCallback)
throws IOException, PGPException {
InputStream pgpInputStream;
DecryptionStreamFactory factory = new DecryptionStreamFactory(decryptionKeys, decryptor, verificationKeys,
missingPublicKeyCallback);
if (detachedSignatures != null) {
pgpInputStream = inputStream;
for (PGPSignature signature : detachedSignatures) {
PGPPublicKey signingKey = factory.findSignatureVerificationKey(signature.getKeyID());
signature.init(new BcPGPContentVerifierBuilderProvider(), signingKey);
factory.resultBuilder.addDetachedSignature(
new DetachedSignature(signature, new OpenPgpV4Fingerprint(signingKey)));
}
} else {
PGPObjectFactory objectFactory = new PGPObjectFactory(
PGPUtil.getDecoderStream(inputStream), new BcKeyFingerprintCalculator());
return new DecryptionStream(factory.processPGPPackets(objectFactory), factory.resultBuilder);
pgpInputStream = factory.processPGPPackets(objectFactory);
}
return new DecryptionStream(pgpInputStream, factory.resultBuilder);
}
private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory) throws IOException, PGPException {
Object nextPgpObject;
while ((nextPgpObject = objectFactory.nextObject()) != null) {
if (nextPgpObject instanceof PGPEncryptedDataList) {
@ -214,18 +226,20 @@ public final class DecryptionStreamFactory {
private void processOnePassSignature(PGPOnePassSignature signature) throws PGPException {
final long keyId = signature.getKeyID();
resultBuilder.addUnverifiedSignatureKeyId(keyId);
LOGGER.log(LEVEL, "Message contains OnePassSignature from " + Long.toHexString(keyId));
// Find public key
PGPPublicKey verificationKey = findSignatureVerificationKey(keyId);
if (verificationKey == null) {
LOGGER.log(LEVEL, "Missing verification key from " + Long.toHexString(keyId));
return;
}
signature.init(verifierBuilderProvider, verificationKey);
verifiableOnePassSignatures.put(new OpenPgpV4Fingerprint(verificationKey), signature);
OnePassSignature onePassSignature = new OnePassSignature(signature, new OpenPgpV4Fingerprint(verificationKey));
resultBuilder.addOnePassSignature(onePassSignature);
verifiableOnePassSignatures.put(new OpenPgpV4Fingerprint(verificationKey), onePassSignature);
}
private PGPPublicKey findSignatureVerificationKey(long keyId) {
@ -269,4 +283,5 @@ public final class DecryptionStreamFactory {
return missingPublicKey;
}
}

View file

@ -0,0 +1,31 @@
package org.pgpainless.decryption_verification;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.key.OpenPgpV4Fingerprint;
public class DetachedSignature {
private final PGPSignature signature;
private final OpenPgpV4Fingerprint fingerprint;
private boolean verified;
public DetachedSignature(PGPSignature signature, OpenPgpV4Fingerprint fingerprint) {
this.signature = signature;
this.fingerprint = fingerprint;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
public boolean isVerified() {
return verified;
}
public PGPSignature getSignature() {
return signature;
}
public OpenPgpV4Fingerprint getFingerprint() {
return fingerprint;
}
}

View file

@ -0,0 +1,42 @@
package org.pgpainless.decryption_verification;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.key.OpenPgpV4Fingerprint;
public class OnePassSignature {
private final PGPOnePassSignature onePassSignature;
private final OpenPgpV4Fingerprint fingerprint;
private PGPSignature signature;
private boolean verified;
public OnePassSignature(PGPOnePassSignature onePassSignature, OpenPgpV4Fingerprint fingerprint) {
this.onePassSignature = onePassSignature;
this.fingerprint = fingerprint;
}
public boolean isVerified() {
return verified;
}
public PGPOnePassSignature getOnePassSignature() {
return onePassSignature;
}
public OpenPgpV4Fingerprint getFingerprint() {
return fingerprint;
}
public boolean verify(PGPSignature signature) throws PGPException {
this.verified = getOnePassSignature().verify(signature);
if (verified) {
this.signature = signature;
}
return verified;
}
public PGPSignature getSignature() {
return signature;
}
}

View file

@ -15,8 +15,10 @@
*/
package org.pgpainless.decryption_verification;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ -32,11 +34,8 @@ public class OpenPgpMetadata {
private final Set<Long> recipientKeyIds;
private final OpenPgpV4Fingerprint decryptionFingerprint;
private final Set<PGPSignature> signatures;
private final Set<Long> signatureKeyIds;
private final Map<OpenPgpV4Fingerprint, PGPSignature> verifiedSignatures;
private final Set<OpenPgpV4Fingerprint> verifiedSignaturesFingerprints;
private final List<OnePassSignature> onePassSignatures;
private final List<DetachedSignature> detachedSignatures;
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
private final CompressionAlgorithm compressionAlgorithm;
private final boolean integrityProtected;
@ -46,20 +45,16 @@ public class OpenPgpMetadata {
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
CompressionAlgorithm algorithm,
boolean integrityProtected,
Set<PGPSignature> signatures,
Set<Long> signatureKeyIds,
Map<OpenPgpV4Fingerprint, PGPSignature> verifiedSignatures,
Set<OpenPgpV4Fingerprint> verifiedSignaturesFingerprints) {
List<OnePassSignature> onePassSignatures,
List<DetachedSignature> detachedSignatures) {
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
this.decryptionFingerprint = decryptionFingerprint;
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
this.compressionAlgorithm = algorithm;
this.integrityProtected = integrityProtected;
this.signatures = Collections.unmodifiableSet(signatures);
this.signatureKeyIds = Collections.unmodifiableSet(signatureKeyIds);
this.verifiedSignatures = Collections.unmodifiableMap(verifiedSignatures);
this.verifiedSignaturesFingerprints = Collections.unmodifiableSet(verifiedSignaturesFingerprints);
this.detachedSignatures = Collections.unmodifiableList(detachedSignatures);
this.onePassSignatures = Collections.unmodifiableList(onePassSignatures);
}
public Set<Long> getRecipientKeyIds() {
@ -87,27 +82,42 @@ public class OpenPgpMetadata {
}
public Set<PGPSignature> getSignatures() {
Set<PGPSignature> signatures = new HashSet<>();
for (DetachedSignature detachedSignature : detachedSignatures) {
signatures.add(detachedSignature.getSignature());
}
for (OnePassSignature onePassSignature : onePassSignatures) {
signatures.add(onePassSignature.getSignature());
}
return signatures;
}
public Set<Long> getSignatureKeyIDs() {
return signatureKeyIds;
}
public boolean isSigned() {
return !signatureKeyIds.isEmpty();
return !getSignatures().isEmpty();
}
public Map<OpenPgpV4Fingerprint, PGPSignature> getVerifiedSignatures() {
Map<OpenPgpV4Fingerprint, PGPSignature> verifiedSignatures = new ConcurrentHashMap<>();
for (DetachedSignature detachedSignature : detachedSignatures) {
if (detachedSignature.isVerified()) {
verifiedSignatures.put(detachedSignature.getFingerprint(), detachedSignature.getSignature());
}
}
for (OnePassSignature onePassSignature : onePassSignatures) {
if (onePassSignature.isVerified()) {
verifiedSignatures.put(onePassSignature.getFingerprint(), onePassSignature.getSignature());
}
}
return verifiedSignatures;
}
public Set<OpenPgpV4Fingerprint> getVerifiedSignatureKeyFingerprints() {
return verifiedSignaturesFingerprints;
return getVerifiedSignatures().keySet();
}
public boolean isVerified() {
return !verifiedSignaturesFingerprints.isEmpty();
return !getVerifiedSignatures().isEmpty();
}
public boolean containsVerifiedSignatureFrom(PGPPublicKeyRing publicKeys) {
@ -121,7 +131,17 @@ public class OpenPgpMetadata {
}
public boolean containsVerifiedSignatureFrom(OpenPgpV4Fingerprint fingerprint) {
return verifiedSignaturesFingerprints.contains(fingerprint);
return getVerifiedSignatureKeyFingerprints().contains(fingerprint);
}
public static class Signature {
protected final PGPSignature signature;
protected final OpenPgpV4Fingerprint fingerprint;
public Signature(PGPSignature signature, OpenPgpV4Fingerprint fingerprint) {
this.signature = signature;
this.fingerprint = fingerprint;
}
}
public static Builder getBuilder() {
@ -132,10 +152,8 @@ public class OpenPgpMetadata {
private final Set<Long> recipientFingerprints = new HashSet<>();
private OpenPgpV4Fingerprint decryptionFingerprint;
private final Set<PGPSignature> signatures = new HashSet<>();
private final Set<Long> signatureKeyIds = new HashSet<>();
private final Map<OpenPgpV4Fingerprint, PGPSignature> verifiedSignatures = new ConcurrentHashMap<>();
private final Set<OpenPgpV4Fingerprint> verifiedSignatureKeyFingerprints = new HashSet<>();
private final List<DetachedSignature> detachedSignatures = new ArrayList<>();
private final List<OnePassSignature> onePassSignatures = new ArrayList<>();
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
private boolean integrityProtected = false;
@ -155,24 +173,8 @@ public class OpenPgpMetadata {
return this;
}
public Builder addSignature(PGPSignature signature) {
signatures.add(signature);
return this;
}
public Builder addUnverifiedSignatureKeyId(Long keyId) {
this.signatureKeyIds.add(keyId);
return this;
}
public Builder putVerifiedSignature(OpenPgpV4Fingerprint fingerprint, PGPSignature verifiedSignature) {
verifiedSignatures.put(fingerprint, verifiedSignature);
return this;
}
public Builder addVerifiedSignatureFingerprint(OpenPgpV4Fingerprint fingerprint) {
this.verifiedSignatureKeyFingerprints.add(fingerprint);
return this;
public List<DetachedSignature> getDetachedSignatures() {
return detachedSignatures;
}
public Builder setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
@ -185,11 +187,18 @@ public class OpenPgpMetadata {
return this;
}
public void addDetachedSignature(DetachedSignature signature) {
this.detachedSignatures.add(signature);
}
public void addOnePassSignature(OnePassSignature onePassSignature) {
this.onePassSignatures.add(onePassSignature);
}
public OpenPgpMetadata build() {
return new OpenPgpMetadata(recipientFingerprints, decryptionFingerprint,
symmetricKeyAlgorithm, compressionAlgorithm, integrityProtected,
signatures, signatureKeyIds,
verifiedSignatures, verifiedSignatureKeyFingerprints);
onePassSignatures, detachedSignatures);
}
}
}

View file

@ -26,7 +26,6 @@ import java.util.logging.Logger;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.pgpainless.key.OpenPgpV4Fingerprint;
@ -37,14 +36,14 @@ public class SignatureVerifyingInputStream extends FilterInputStream {
private static final Level LEVEL = Level.FINE;
private final PGPObjectFactory objectFactory;
private final Map<OpenPgpV4Fingerprint, PGPOnePassSignature> onePassSignatures;
private final Map<OpenPgpV4Fingerprint, OnePassSignature> onePassSignatures;
private final OpenPgpMetadata.Builder resultBuilder;
private boolean validated = false;
protected SignatureVerifyingInputStream(@Nonnull InputStream inputStream,
@Nonnull PGPObjectFactory objectFactory,
@Nonnull Map<OpenPgpV4Fingerprint, PGPOnePassSignature> onePassSignatures,
@Nonnull Map<OpenPgpV4Fingerprint, OnePassSignature> onePassSignatures,
@Nonnull OpenPgpMetadata.Builder resultBuilder) {
super(inputStream);
this.objectFactory = objectFactory;
@ -55,14 +54,14 @@ public class SignatureVerifyingInputStream extends FilterInputStream {
}
private void updateOnePassSignatures(byte data) {
for (PGPOnePassSignature signature : onePassSignatures.values()) {
signature.update(data);
for (OnePassSignature signature : onePassSignatures.values()) {
signature.getOnePassSignature().update(data);
}
}
private void updateOnePassSignatures(byte[] b, int off, int len) {
for (PGPOnePassSignature signature : onePassSignatures.values()) {
signature.update(b, off, len);
for (OnePassSignature signature : onePassSignatures.values()) {
signature.getOnePassSignature().update(b, off, len);
}
}
@ -87,10 +86,8 @@ public class SignatureVerifyingInputStream extends FilterInputStream {
try {
for (PGPSignature signature : signatureList) {
resultBuilder.addSignature(signature);
OpenPgpV4Fingerprint fingerprint = findFingerprintForSignature(signature);
PGPOnePassSignature onePassSignature = findOnePassSignature(fingerprint);
OnePassSignature onePassSignature = findOnePassSignature(fingerprint);
if (onePassSignature == null) {
LOGGER.log(LEVEL, "Found Signature without respective OnePassSignature packet -> skip");
continue;
@ -103,17 +100,17 @@ public class SignatureVerifyingInputStream extends FilterInputStream {
}
}
private void verifySignatureOrThrowSignatureException(PGPSignature signature, OpenPgpV4Fingerprint fingerprint, PGPOnePassSignature onePassSignature) throws PGPException, SignatureException {
private void verifySignatureOrThrowSignatureException(PGPSignature signature, OpenPgpV4Fingerprint fingerprint,
OnePassSignature onePassSignature)
throws PGPException, SignatureException {
if (onePassSignature.verify(signature)) {
LOGGER.log(LEVEL, "Verified signature of key " + Long.toHexString(signature.getKeyID()));
resultBuilder.putVerifiedSignature(fingerprint, signature);
resultBuilder.addVerifiedSignatureFingerprint(fingerprint);
} else {
throw new SignatureException("Bad Signature of key " + signature.getKeyID());
}
}
private PGPOnePassSignature findOnePassSignature(OpenPgpV4Fingerprint fingerprint) {
private OnePassSignature findOnePassSignature(OpenPgpV4Fingerprint fingerprint) {
if (fingerprint != null) {
return onePassSignatures.get(fingerprint);
}

View file

@ -20,7 +20,9 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
@ -30,9 +32,12 @@ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.jetbrains.annotations.NotNull;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.exception.SecretKeyNotFoundException;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.selection.key.PublicKeySelectionStrategy;
import org.pgpainless.key.selection.key.SecretKeySelectionStrategy;
@ -48,6 +53,7 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
private OutputStream outputStream;
private final Set<PGPPublicKey> encryptionKeys = new HashSet<>();
private boolean detachedSignature = false;
private final Set<PGPSecretKey> signingKeys = new HashSet<>();
private SecretKeyRingProtector signingKeysDecryptor;
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_128;
@ -142,8 +148,8 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
}
@Override
public SignWith doNotEncrypt() {
return new SignWithImpl();
public DetachedSign doNotEncrypt() {
return new DetachedSignImpl();
}
}
@ -216,7 +222,7 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
}
@Override
public SignWith usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
public DetachedSign usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
@Nonnull HashAlgorithm hashAlgorithm,
@Nonnull CompressionAlgorithm compressionAlgorithm) {
@ -224,17 +230,46 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
EncryptionBuilder.this.hashAlgorithm = hashAlgorithm;
EncryptionBuilder.this.compressionAlgorithm = compressionAlgorithm;
return new SignWithImpl();
return new DetachedSignImpl();
}
@Override
public SignWith usingSecureAlgorithms() {
public DetachedSign usingSecureAlgorithms() {
EncryptionBuilder.this.symmetricKeyAlgorithm = SymmetricKeyAlgorithm.AES_256;
EncryptionBuilder.this.hashAlgorithm = HashAlgorithm.SHA512;
EncryptionBuilder.this.compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
return new DetachedSignImpl();
}
}
class DetachedSignImpl implements DetachedSign {
@Override
public SignWith createDetachedSignature() {
EncryptionBuilder.this.detachedSignature = true;
return new SignWithImpl();
}
@Override
public Armor doNotSign() {
return new ArmorImpl();
}
@Override
public <O> Armor signWith(@org.jetbrains.annotations.NotNull SecretKeyRingProtector decryptor, @org.jetbrains.annotations.NotNull PGPSecretKey... keys) {
return new SignWithImpl().signWith(decryptor, keys);
}
@Override
public <O> Armor signWith(@org.jetbrains.annotations.NotNull SecretKeyRingProtector decryptor, @org.jetbrains.annotations.NotNull PGPSecretKeyRing... keyRings) {
return new SignWithImpl().signWith(decryptor, keyRings);
}
@Override
public <O> Armor signWith(@org.jetbrains.annotations.NotNull SecretKeyRingSelectionStrategy<O> selectionStrategy, @org.jetbrains.annotations.NotNull SecretKeyRingProtector decryptor, @org.jetbrains.annotations.NotNull MultiMap<O, PGPSecretKeyRingCollection> keys) throws SecretKeyNotFoundException {
return new SignWithImpl().signWith(selectionStrategy, decryptor, keys);
}
}
class SignWithImpl implements SignWith {
@ -296,11 +331,6 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
}
return new ArmorImpl();
}
@Override
public Armor doNotSign() {
return new ArmorImpl();
}
}
class ArmorImpl implements Armor {
@ -319,14 +349,16 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
private EncryptionStream build() throws IOException, PGPException {
Set<PGPPrivateKey> privateKeys = new HashSet<>();
Map<OpenPgpV4Fingerprint, PGPPrivateKey> privateKeys = new ConcurrentHashMap<>();
for (PGPSecretKey secretKey : signingKeys) {
privateKeys.add(secretKey.extractPrivateKey(signingKeysDecryptor.getDecryptor(secretKey.getKeyID())));
privateKeys.put(new OpenPgpV4Fingerprint(secretKey),
secretKey.extractPrivateKey(signingKeysDecryptor.getDecryptor(secretKey.getKeyID())));
}
return new EncryptionStream(
EncryptionBuilder.this.outputStream,
EncryptionBuilder.this.encryptionKeys,
EncryptionBuilder.this.detachedSignature,
privateKeys,
EncryptionBuilder.this.symmetricKeyAlgorithm,
EncryptionBuilder.this.hashAlgorithm,

View file

@ -50,7 +50,7 @@ public interface EncryptionBuilderInterface {
<O> WithAlgorithms toRecipients(@Nonnull PublicKeyRingSelectionStrategy<O> selectionStrategy,
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys);
SignWith doNotEncrypt();
DetachedSign doNotEncrypt();
}
@ -65,11 +65,18 @@ public interface EncryptionBuilderInterface {
<O> WithAlgorithms andToSelf(@Nonnull PublicKeyRingSelectionStrategy<O> selectionStrategy,
@Nonnull MultiMap<O, PGPPublicKeyRingCollection> keys);
SignWith usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
DetachedSign usingAlgorithms(@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
@Nonnull HashAlgorithm hashAlgorithm,
@Nonnull CompressionAlgorithm compressionAlgorithm);
SignWith usingSecureAlgorithms();
DetachedSign usingSecureAlgorithms();
}
interface DetachedSign extends SignWith {
SignWith createDetachedSignature();
Armor doNotSign();
}
@ -84,8 +91,6 @@ public interface EncryptionBuilderInterface {
@Nonnull MultiMap<O, PGPSecretKeyRingCollection> keys)
throws SecretKeyNotFoundException;
Armor doNotSign();
}
interface Armor {

View file

@ -21,8 +21,11 @@ import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -35,15 +38,19 @@ import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.DetachedSignature;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.key.OpenPgpV4Fingerprint;
/**
* This class is based upon Jens Neuhalfen's Bouncy-GPG PGPEncryptingStream.
@ -60,12 +67,13 @@ public final class EncryptionStream extends OutputStream {
private final HashAlgorithm hashAlgorithm;
private final CompressionAlgorithm compressionAlgorithm;
private final Set<PGPPublicKey> encryptionKeys;
private final Set<PGPPrivateKey> signingKeys;
private final boolean detachedSignature;
private final Map<OpenPgpV4Fingerprint, PGPPrivateKey> signingKeys;
private final boolean asciiArmor;
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
private List<PGPSignatureGenerator> signatureGenerators = new ArrayList<>();
private Map<OpenPgpV4Fingerprint, PGPSignatureGenerator> signatureGenerators = new ConcurrentHashMap<>();
private boolean closed = false;
OutputStream outermostStream = null;
@ -81,7 +89,8 @@ public final class EncryptionStream extends OutputStream {
EncryptionStream(@Nonnull OutputStream targetOutputStream,
@Nonnull Set<PGPPublicKey> encryptionKeys,
@Nonnull Set<PGPPrivateKey> signingKeys,
boolean detachedSignature,
@Nonnull Map<OpenPgpV4Fingerprint, PGPPrivateKey> signingKeys,
@Nonnull SymmetricKeyAlgorithm symmetricKeyAlgorithm,
@Nonnull HashAlgorithm hashAlgorithm,
@Nonnull CompressionAlgorithm compressionAlgorithm,
@ -92,7 +101,8 @@ public final class EncryptionStream extends OutputStream {
this.hashAlgorithm = hashAlgorithm;
this.compressionAlgorithm = compressionAlgorithm;
this.encryptionKeys = Collections.unmodifiableSet(encryptionKeys);
this.signingKeys = Collections.unmodifiableSet(signingKeys);
this.detachedSignature = detachedSignature;
this.signingKeys = Collections.unmodifiableMap(signingKeys);
this.asciiArmor = asciiArmor;
outermostStream = targetOutputStream;
@ -144,14 +154,15 @@ public final class EncryptionStream extends OutputStream {
}
LOGGER.log(LEVEL, "At least one signing key is available -> sign " + hashAlgorithm + " hash of message");
for (PGPPrivateKey privateKey : signingKeys) {
LOGGER.log(LEVEL, "Sign using key " + Long.toHexString(privateKey.getKeyID()));
for (OpenPgpV4Fingerprint fingerprint : signingKeys.keySet()) {
PGPPrivateKey privateKey = signingKeys.get(fingerprint);
LOGGER.log(LEVEL, "Sign using key " + fingerprint);
BcPGPContentSignerBuilder contentSignerBuilder = new BcPGPContentSignerBuilder(
privateKey.getPublicKeyPacket().getAlgorithm(), hashAlgorithm.getAlgorithmId());
PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(contentSignerBuilder);
signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
signatureGenerators.add(signatureGenerator);
signatureGenerators.put(fingerprint, signatureGenerator);
}
}
@ -163,7 +174,7 @@ public final class EncryptionStream extends OutputStream {
}
private void prepareOnePassSignatures() throws IOException, PGPException {
for (PGPSignatureGenerator signatureGenerator : signatureGenerators) {
for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) {
signatureGenerator.generateOnePassVersion(false).encode(basicCompressionStream);
}
}
@ -186,7 +197,7 @@ public final class EncryptionStream extends OutputStream {
public void write(int data) throws IOException {
literalDataStream.write(data);
for (PGPSignatureGenerator signatureGenerator : signatureGenerators) {
for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) {
byte asByte = (byte) (data & 0xff);
signatureGenerator.update(asByte);
}
@ -201,7 +212,7 @@ public final class EncryptionStream extends OutputStream {
@Override
public void write(byte[] buffer, int off, int len) throws IOException {
literalDataStream.write(buffer, 0, len);
for (PGPSignatureGenerator signatureGenerator : signatureGenerators) {
for (PGPSignatureGenerator signatureGenerator : signatureGenerators.values()) {
signatureGenerator.update(buffer, 0, len);
}
}
@ -242,12 +253,14 @@ public final class EncryptionStream extends OutputStream {
}
private void writeSignatures() throws IOException {
for (PGPSignatureGenerator signatureGenerator : signatureGenerators) {
for (OpenPgpV4Fingerprint fingerprint : signatureGenerators.keySet()) {
PGPSignatureGenerator signatureGenerator = signatureGenerators.get(fingerprint);
try {
PGPSignature signature = signatureGenerator.generate();
if (!detachedSignature) {
signature.encode(basicCompressionStream);
resultBuilder.addSignature(signature);
resultBuilder.addUnverifiedSignatureKeyId(signature.getKeyID());
}
resultBuilder.addDetachedSignature(new DetachedSignature(signature, fingerprint));
} catch (PGPException e) {
throw new IOException(e);
}

View file

@ -80,7 +80,7 @@ public class DecryptAndVerifyMessageTest {
assertTrue(metadata.isVerified());
assertEquals(CompressionAlgorithm.ZLIB, metadata.getCompressionAlgorithm());
assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getSymmetricKeyAlgorithm());
assertEquals(1, metadata.getSignatureKeyIDs().size());
//assertEquals(1, metadata.getSignatureKeyIDs().size());
assertEquals(1, metadata.getVerifiedSignatureKeyFingerprints().size());
assertTrue(metadata.containsVerifiedSignatureFrom(TestKeys.JULIET_FINGERPRINT));
assertEquals(TestKeys.JULIET_FINGERPRINT, metadata.getDecryptionFingerprint());

View file

@ -26,11 +26,15 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Set;
import java.util.logging.Logger;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.util.io.Streams;
import org.junit.Test;
import org.pgpainless.PGPainless;
@ -38,6 +42,7 @@ import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.TestKeys;
import org.pgpainless.key.collection.PGPKeyRing;
import org.pgpainless.key.generation.KeySpec;
@ -145,9 +150,9 @@ public class EncryptDecryptTest {
OpenPgpMetadata encryptionResult = encryptor.getResult();
assertFalse(encryptionResult.getSignatureKeyIDs().isEmpty());
for (long keyId : encryptionResult.getSignatureKeyIDs()) {
assertTrue(BCUtil.keyRingContainsKeyWithId(senderPub, keyId));
assertFalse(encryptionResult.getSignatures().isEmpty());
for (OpenPgpV4Fingerprint fingerprint : encryptionResult.getVerifiedSignatures().keySet()) {
assertTrue(BCUtil.keyRingContainsKeyWithId(senderPub, fingerprint.getKeyId()));
}
assertFalse(encryptionResult.getRecipientKeyIds().isEmpty());
@ -180,4 +185,44 @@ public class EncryptDecryptTest {
assertTrue(result.isEncrypted());
assertTrue(result.isVerified());
}
@Test
public void testDetachedSignatureCreationAndVerification() throws IOException, PGPException {
PGPKeyRing signingKeys = new PGPKeyRing(TestKeys.getJulietPublicKeyRing(), TestKeys.getJulietSecretKeyRing());
SecretKeyRingProtector keyRingProtector = new UnprotectedKeysProtector();
byte[] data = testMessage.getBytes();
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
ByteArrayOutputStream dummyOut = new ByteArrayOutputStream();
EncryptionStream signer = PGPainless.createEncryptor().onOutputStream(dummyOut)
.doNotEncrypt()
.createDetachedSignature()
.signWith(keyRingProtector, signingKeys.getSecretKeys())
.noArmor();
Streams.pipeAll(inputStream, signer);
signer.close();
OpenPgpMetadata metadata = signer.getResult();
Set<PGPSignature> signatureSet = metadata.getSignatures();
ByteArrayOutputStream sigOut = new ByteArrayOutputStream();
ArmoredOutputStream armorOut = new ArmoredOutputStream(sigOut);
signatureSet.iterator().next().encode(armorOut);
armorOut.close();
String armorSig = sigOut.toString();
System.out.println(armorSig);
inputStream = new ByteArrayInputStream(testMessage.getBytes());
DecryptionStream verifier = PGPainless.createDecryptor().onInputStream(inputStream)
.doNotDecrypt()
.verifyDetachedSignature(new ByteArrayInputStream(armorSig.getBytes()))
.verifyWith(Collections.singleton(signingKeys.getPublicKeys()))
.ignoreMissingPublicKeys()
.build();
dummyOut = new ByteArrayOutputStream();
Streams.pipeAll(verifier, dummyOut);
verifier.close();
metadata = verifier.getResult();
assertFalse(metadata.getVerifiedSignatures().isEmpty());
}
}