mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-29 15:52:08 +01:00
Move signature verification to dedicated streams
This commit is contained in:
parent
ba0e5eb3fe
commit
90a00e0541
12 changed files with 374 additions and 325 deletions
|
@ -15,21 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.decryption_verification;
|
package org.pgpainless.decryption_verification;
|
||||||
|
|
||||||
import static org.pgpainless.signature.SignatureValidator.signatureWasCreatedInBounds;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.pgpainless.PGPainless;
|
|
||||||
import org.pgpainless.exception.SignatureValidationException;
|
|
||||||
import org.pgpainless.signature.CertificateValidator;
|
|
||||||
import org.pgpainless.signature.DetachedSignature;
|
|
||||||
import org.pgpainless.util.IntegrityProtectedInputStream;
|
import org.pgpainless.util.IntegrityProtectedInputStream;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decryption Stream that handles updating and verification of detached signatures,
|
* Decryption Stream that handles updating and verification of detached signatures,
|
||||||
|
@ -37,10 +28,7 @@ import org.slf4j.LoggerFactory;
|
||||||
*/
|
*/
|
||||||
public class DecryptionStream extends InputStream {
|
public class DecryptionStream extends InputStream {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(DecryptionStream.class);
|
|
||||||
|
|
||||||
private final InputStream inputStream;
|
private final InputStream inputStream;
|
||||||
private final ConsumerOptions options;
|
|
||||||
private final OpenPgpMetadata.Builder resultBuilder;
|
private final OpenPgpMetadata.Builder resultBuilder;
|
||||||
private boolean isClosed = false;
|
private boolean isClosed = false;
|
||||||
private final IntegrityProtectedInputStream integrityProtectedInputStream;
|
private final IntegrityProtectedInputStream integrityProtectedInputStream;
|
||||||
|
@ -50,17 +38,15 @@ public class DecryptionStream extends InputStream {
|
||||||
* Create an input stream that handles decryption and - if necessary - integrity protection verification.
|
* Create an input stream that handles decryption and - if necessary - integrity protection verification.
|
||||||
*
|
*
|
||||||
* @param wrapped underlying input stream
|
* @param wrapped underlying input stream
|
||||||
* @param options options for consuming, eg. decryption key...
|
|
||||||
* @param resultBuilder builder for decryption metadata like algorithms, recipients etc.
|
* @param resultBuilder builder for decryption metadata like algorithms, recipients etc.
|
||||||
* @param integrityProtectedInputStream in case of data encrypted using SEIP packet close this stream to check integrity
|
* @param integrityProtectedInputStream in case of data encrypted using SEIP packet close this stream to check integrity
|
||||||
* @param armorStream armor stream to verify CRC checksums
|
* @param armorStream armor stream to verify CRC checksums
|
||||||
*/
|
*/
|
||||||
DecryptionStream(@Nonnull InputStream wrapped, @Nonnull ConsumerOptions options,
|
DecryptionStream(@Nonnull InputStream wrapped,
|
||||||
@Nonnull OpenPgpMetadata.Builder resultBuilder,
|
@Nonnull OpenPgpMetadata.Builder resultBuilder,
|
||||||
IntegrityProtectedInputStream integrityProtectedInputStream,
|
IntegrityProtectedInputStream integrityProtectedInputStream,
|
||||||
InputStream armorStream) {
|
InputStream armorStream) {
|
||||||
this.inputStream = wrapped;
|
this.inputStream = wrapped;
|
||||||
this.options = options;
|
|
||||||
this.resultBuilder = resultBuilder;
|
this.resultBuilder = resultBuilder;
|
||||||
this.integrityProtectedInputStream = integrityProtectedInputStream;
|
this.integrityProtectedInputStream = integrityProtectedInputStream;
|
||||||
this.armorStream = armorStream;
|
this.armorStream = armorStream;
|
||||||
|
@ -69,58 +55,27 @@ public class DecryptionStream extends InputStream {
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
int r = inputStream.read();
|
int r = inputStream.read();
|
||||||
maybeUpdateDetachedSignatures(r);
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(@Nonnull byte[] bytes, int offset, int length) throws IOException {
|
public int read(@Nonnull byte[] bytes, int offset, int length) throws IOException {
|
||||||
int read = inputStream.read(bytes, offset, length);
|
int read = inputStream.read(bytes, offset, length);
|
||||||
if (read != -1) {
|
|
||||||
maybeUpdateDetachedSignatures(bytes, offset, read);
|
|
||||||
}
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeUpdateDetachedSignatures(byte[] bytes, int offset, int length) {
|
|
||||||
for (DetachedSignature s : resultBuilder.getDetachedSignatures()) {
|
|
||||||
s.getSignature().update(bytes, offset, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void maybeUpdateDetachedSignatures(int rByte) {
|
|
||||||
for (DetachedSignature s : resultBuilder.getDetachedSignatures()) {
|
|
||||||
if (rByte != -1) {
|
|
||||||
s.getSignature().update((byte) rByte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
if (armorStream != null) {
|
if (armorStream != null) {
|
||||||
Streams.drain(armorStream);
|
Streams.drain(armorStream);
|
||||||
}
|
}
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
maybeVerifyDetachedSignatures();
|
|
||||||
if (integrityProtectedInputStream != null) {
|
if (integrityProtectedInputStream != null) {
|
||||||
integrityProtectedInputStream.close();
|
integrityProtectedInputStream.close();
|
||||||
}
|
}
|
||||||
this.isClosed = true;
|
this.isClosed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeVerifyDetachedSignatures() {
|
|
||||||
for (DetachedSignature s : resultBuilder.getDetachedSignatures()) {
|
|
||||||
try {
|
|
||||||
signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()).verify(s.getSignature());
|
|
||||||
boolean verified = CertificateValidator.validateCertificateAndVerifyInitializedSignature(s.getSignature(), (PGPPublicKeyRing) s.getSigningKeyRing(), PGPainless.getPolicy());
|
|
||||||
s.setVerified(verified);
|
|
||||||
} catch (SignatureValidationException e) {
|
|
||||||
LOGGER.warn("Could not verify signature of key {}", s.getSigningKeyIdentifier(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the result of the decryption.
|
* Return the result of the decryption.
|
||||||
* The result contains metadata about the decryption, such as signatures, used keys and algorithms, as well as information
|
* The result contains metadata about the decryption, such as signatures, used keys and algorithms, as well as information
|
||||||
|
|
|
@ -19,10 +19,9 @@ import java.io.BufferedInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashMap;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
@ -43,6 +42,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
import org.bouncycastle.openpgp.PGPUtil;
|
import org.bouncycastle.openpgp.PGPUtil;
|
||||||
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
||||||
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||||
|
@ -59,12 +59,11 @@ import org.pgpainless.exception.MissingLiteralDataException;
|
||||||
import org.pgpainless.exception.UnacceptableAlgorithmException;
|
import org.pgpainless.exception.UnacceptableAlgorithmException;
|
||||||
import org.pgpainless.exception.WrongConsumingMethodException;
|
import org.pgpainless.exception.WrongConsumingMethodException;
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
import org.pgpainless.implementation.ImplementationFactory;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.key.info.KeyRingInfo;
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||||
import org.pgpainless.signature.DetachedSignature;
|
import org.pgpainless.signature.DetachedSignature;
|
||||||
import org.pgpainless.signature.OnePassSignature;
|
import org.pgpainless.signature.OnePassSignatureCheck;
|
||||||
import org.pgpainless.signature.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
import org.pgpainless.util.CRCingArmoredInputStreamWrapper;
|
import org.pgpainless.util.CRCingArmoredInputStreamWrapper;
|
||||||
import org.pgpainless.util.IntegrityProtectedInputStream;
|
import org.pgpainless.util.IntegrityProtectedInputStream;
|
||||||
|
@ -78,15 +77,17 @@ public final class DecryptionStreamFactory {
|
||||||
private static final int MAX_RECURSION_DEPTH = 16;
|
private static final int MAX_RECURSION_DEPTH = 16;
|
||||||
|
|
||||||
private final ConsumerOptions options;
|
private final ConsumerOptions options;
|
||||||
|
|
||||||
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||||
|
private final List<OnePassSignatureCheck> onePassSignatureChecks = new ArrayList<>();
|
||||||
|
private final List<DetachedSignature> detachedSignatureChecks = new ArrayList<>();
|
||||||
|
|
||||||
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider =
|
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider =
|
||||||
ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
|
ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
|
||||||
private static final KeyFingerPrintCalculator keyFingerprintCalculator =
|
private static final KeyFingerPrintCalculator keyFingerprintCalculator =
|
||||||
ImplementationFactory.getInstance().getKeyFingerprintCalculator();
|
ImplementationFactory.getInstance().getKeyFingerprintCalculator();
|
||||||
private final Map<OpenPgpV4Fingerprint, OnePassSignature> verifiableOnePassSignatures = new HashMap<>();
|
|
||||||
private IntegrityProtectedInputStream integrityProtectedEncryptedInputStream;
|
private IntegrityProtectedInputStream integrityProtectedEncryptedInputStream;
|
||||||
|
|
||||||
|
|
||||||
public static DecryptionStream create(@Nonnull InputStream inputStream,
|
public static DecryptionStream create(@Nonnull InputStream inputStream,
|
||||||
@Nonnull ConsumerOptions options)
|
@Nonnull ConsumerOptions options)
|
||||||
throws PGPException, IOException {
|
throws PGPException, IOException {
|
||||||
|
@ -109,9 +110,9 @@ public final class DecryptionStreamFactory {
|
||||||
PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId);
|
PGPPublicKey signingKey = signingKeyRing.getPublicKey(issuerKeyId);
|
||||||
SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(signingKeyRing, signingKey.getKeyID());
|
SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(signingKeyRing, signingKey.getKeyID());
|
||||||
try {
|
try {
|
||||||
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
|
signature.init(verifierBuilderProvider, signingKey);
|
||||||
resultBuilder.addDetachedSignature(
|
DetachedSignature detachedSignature = new DetachedSignature(signature, signingKeyRing, signingKeyIdentifier);
|
||||||
new DetachedSignature(signature, signingKeyRing, signingKeyIdentifier));
|
detachedSignatureChecks.add(detachedSignature);
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
LOGGER.warn("Cannot verify detached signature made by {}. Reason: {}", signingKeyIdentifier, e.getMessage(), e);
|
LOGGER.warn("Cannot verify detached signature made by {}. Reason: {}", signingKeyIdentifier, e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
@ -142,29 +143,35 @@ public final class DecryptionStreamFactory {
|
||||||
inputStream = processPGPPackets(objectFactory, 1);
|
inputStream = processPGPPackets(objectFactory, 1);
|
||||||
} catch (EOFException e) {
|
} catch (EOFException e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
} catch (MissingLiteralDataException e) {
|
||||||
catch (MissingLiteralDataException e) {
|
|
||||||
// Not an OpenPGP message.
|
// Not an OpenPGP message.
|
||||||
// Reset the buffered stream to parse the message as arbitrary binary data
|
// Reset the buffered stream to parse the message as arbitrary binary data
|
||||||
// to allow for detached signature verification.
|
// to allow for detached signature verification.
|
||||||
LOGGER.debug("The message appears to not be an OpenPGP message. This is probably data signed with detached signatures?");
|
LOGGER.debug("The message appears to not be an OpenPGP message. This is probably data signed with detached signatures?");
|
||||||
bufferedIn.reset();
|
bufferedIn.reset();
|
||||||
inputStream = bufferedIn;
|
inputStream = wrapInVerifySignatureStream(bufferedIn);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (e.getMessage().contains("invalid armor")) {
|
if (e.getMessage().contains("invalid armor")) {
|
||||||
// We falsely assumed the data to be armored.
|
// We falsely assumed the data to be armored.
|
||||||
LOGGER.debug("The message is apparently not armored.");
|
LOGGER.debug("The message is apparently not armored.");
|
||||||
bufferedIn.reset();
|
bufferedIn.reset();
|
||||||
inputStream = bufferedIn;
|
inputStream = wrapInVerifySignatureStream(bufferedIn);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DecryptionStream(inputStream, options, resultBuilder, integrityProtectedEncryptedInputStream,
|
return new DecryptionStream(inputStream, resultBuilder, integrityProtectedEncryptedInputStream,
|
||||||
(decoderStream instanceof ArmoredInputStream) ? decoderStream : null);
|
(decoderStream instanceof ArmoredInputStream) ? decoderStream : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InputStream wrapInVerifySignatureStream(InputStream bufferedIn) {
|
||||||
|
return new SignatureInputStream.VerifySignatures(
|
||||||
|
bufferedIn, onePassSignatureChecks,
|
||||||
|
detachedSignatureChecks, options,
|
||||||
|
resultBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory, int depth) throws IOException, PGPException {
|
private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory, int depth) throws IOException, PGPException {
|
||||||
if (depth >= MAX_RECURSION_DEPTH) {
|
if (depth >= MAX_RECURSION_DEPTH) {
|
||||||
throw new PGPException("Maximum recursion depth of packages exceeded.");
|
throw new PGPException("Maximum recursion depth of packages exceeded.");
|
||||||
|
@ -217,7 +224,7 @@ public final class DecryptionStreamFactory {
|
||||||
return processPGPPackets(objectFactory, ++depth);
|
return processPGPPackets(objectFactory, ++depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream processPGPLiteralData(@Nonnull PGPObjectFactory objectFactory, PGPLiteralData pgpLiteralData, int depth) {
|
private InputStream processPGPLiteralData(@Nonnull PGPObjectFactory objectFactory, PGPLiteralData pgpLiteralData, int depth) throws IOException {
|
||||||
LOGGER.debug("Depth {}: Found PGPLiteralData", depth);
|
LOGGER.debug("Depth {}: Found PGPLiteralData", depth);
|
||||||
InputStream literalDataInputStream = pgpLiteralData.getInputStream();
|
InputStream literalDataInputStream = pgpLiteralData.getInputStream();
|
||||||
|
|
||||||
|
@ -225,13 +232,39 @@ public final class DecryptionStreamFactory {
|
||||||
.setModificationDate(pgpLiteralData.getModificationTime())
|
.setModificationDate(pgpLiteralData.getModificationTime())
|
||||||
.setFileEncoding(StreamEncoding.fromCode(pgpLiteralData.getFormat()));
|
.setFileEncoding(StreamEncoding.fromCode(pgpLiteralData.getFormat()));
|
||||||
|
|
||||||
if (verifiableOnePassSignatures.isEmpty()) {
|
if (onePassSignatureChecks.isEmpty()) {
|
||||||
LOGGER.debug("No OnePassSignatures found -> We are done");
|
LOGGER.debug("No OnePassSignatures found -> We are done");
|
||||||
return literalDataInputStream;
|
return literalDataInputStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SignatureVerifyingInputStream(literalDataInputStream,
|
PGPSignatureList signatures = parseSignatures(objectFactory);
|
||||||
objectFactory, verifiableOnePassSignatures, options, resultBuilder);
|
List<PGPSignature> signatureList = SignatureUtils.toList(signatures);
|
||||||
|
|
||||||
|
for (int i = 0; i < onePassSignatureChecks.size(); i++) {
|
||||||
|
onePassSignatureChecks.get(i).setSignature(signatureList.get(onePassSignatureChecks.size() - i - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SignatureInputStream.VerifySignatures(literalDataInputStream,
|
||||||
|
onePassSignatureChecks, detachedSignatureChecks, options, resultBuilder) {
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private PGPSignatureList parseSignatures(PGPObjectFactory objectFactory) throws IOException {
|
||||||
|
PGPSignatureList signatureList = null;
|
||||||
|
Object pgpObject = objectFactory.nextObject();
|
||||||
|
while (pgpObject != null && signatureList == null) {
|
||||||
|
if (pgpObject instanceof PGPSignatureList) {
|
||||||
|
signatureList = (PGPSignatureList) pgpObject;
|
||||||
|
} else {
|
||||||
|
pgpObject = objectFactory.nextObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signatureList == null || signatureList.isEmpty()) {
|
||||||
|
throw new IOException("Verification failed - No Signatures found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return signatureList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream decryptSessionKey(@Nonnull PGPEncryptedDataList encryptedDataList)
|
private InputStream decryptSessionKey(@Nonnull PGPEncryptedDataList encryptedDataList)
|
||||||
|
@ -377,7 +410,7 @@ public final class DecryptionStreamFactory {
|
||||||
private void processOnePassSignature(PGPOnePassSignature signature) throws PGPException {
|
private void processOnePassSignature(PGPOnePassSignature signature) throws PGPException {
|
||||||
final long keyId = signature.getKeyID();
|
final long keyId = signature.getKeyID();
|
||||||
|
|
||||||
LOGGER.debug("Message contains OnePassSignature from {}", Long.toHexString(keyId));
|
LOGGER.debug("Encountered OnePassSignature from {}", Long.toHexString(keyId));
|
||||||
|
|
||||||
// Find public key
|
// Find public key
|
||||||
PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId);
|
PGPPublicKeyRing verificationKeyRing = findSignatureVerificationKeyRing(keyId);
|
||||||
|
@ -388,10 +421,8 @@ public final class DecryptionStreamFactory {
|
||||||
PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId);
|
PGPPublicKey verificationKey = verificationKeyRing.getPublicKey(keyId);
|
||||||
|
|
||||||
signature.init(verifierBuilderProvider, verificationKey);
|
signature.init(verifierBuilderProvider, verificationKey);
|
||||||
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(verificationKey);
|
OnePassSignatureCheck onePassSignature = new OnePassSignatureCheck(signature, verificationKeyRing);
|
||||||
OnePassSignature onePassSignature = new OnePassSignature(signature, verificationKeyRing);
|
onePassSignatureChecks.add(onePassSignature);
|
||||||
resultBuilder.addOnePassSignature(onePassSignature);
|
|
||||||
verifiableOnePassSignatures.put(fingerprint, onePassSignature);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PGPSecretKeyRing findDecryptionKeyRing(long keyId) {
|
private PGPSecretKeyRing findDecryptionKeyRing(long keyId) {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -34,17 +33,19 @@ import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.StreamEncoding;
|
import org.pgpainless.algorithm.StreamEncoding;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
|
import org.pgpainless.exception.SignatureValidationException;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.signature.DetachedSignature;
|
import org.pgpainless.signature.cleartext_signatures.SignatureVerification;
|
||||||
import org.pgpainless.signature.OnePassSignature;
|
|
||||||
|
|
||||||
public class OpenPgpMetadata {
|
public class OpenPgpMetadata {
|
||||||
|
|
||||||
private final Set<Long> recipientKeyIds;
|
private final Set<Long> recipientKeyIds;
|
||||||
private final SubkeyIdentifier decryptionKey;
|
private final SubkeyIdentifier decryptionKey;
|
||||||
private final List<OnePassSignature> onePassSignatures;
|
private final List<SignatureVerification> verifiedInbandSignatures;
|
||||||
private final List<DetachedSignature> detachedSignatures;
|
private final List<SignatureVerification.Failure> invalidInbandSignatures;
|
||||||
|
private final List<SignatureVerification> verifiedDetachedSignatures;
|
||||||
|
private final List<SignatureVerification.Failure> invalidDetachedSignatures;
|
||||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
||||||
private final CompressionAlgorithm compressionAlgorithm;
|
private final CompressionAlgorithm compressionAlgorithm;
|
||||||
private final String fileName;
|
private final String fileName;
|
||||||
|
@ -55,8 +56,10 @@ public class OpenPgpMetadata {
|
||||||
SubkeyIdentifier decryptionKey,
|
SubkeyIdentifier decryptionKey,
|
||||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
||||||
CompressionAlgorithm algorithm,
|
CompressionAlgorithm algorithm,
|
||||||
List<OnePassSignature> onePassSignatures,
|
List<SignatureVerification> verifiedInbandSignatures,
|
||||||
List<DetachedSignature> detachedSignatures,
|
List<SignatureVerification.Failure> invalidInbandSignatures,
|
||||||
|
List<SignatureVerification> verifiedDetachedSignatures,
|
||||||
|
List<SignatureVerification.Failure> invalidDetachedSignatures,
|
||||||
String fileName,
|
String fileName,
|
||||||
Date modificationDate,
|
Date modificationDate,
|
||||||
StreamEncoding fileEncoding) {
|
StreamEncoding fileEncoding) {
|
||||||
|
@ -65,8 +68,10 @@ public class OpenPgpMetadata {
|
||||||
this.decryptionKey = decryptionKey;
|
this.decryptionKey = decryptionKey;
|
||||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||||
this.compressionAlgorithm = algorithm;
|
this.compressionAlgorithm = algorithm;
|
||||||
this.detachedSignatures = Collections.unmodifiableList(detachedSignatures);
|
this.verifiedInbandSignatures = Collections.unmodifiableList(verifiedInbandSignatures);
|
||||||
this.onePassSignatures = Collections.unmodifiableList(onePassSignatures);
|
this.invalidInbandSignatures = Collections.unmodifiableList(invalidInbandSignatures);
|
||||||
|
this.verifiedDetachedSignatures = Collections.unmodifiableList(verifiedDetachedSignatures);
|
||||||
|
this.invalidDetachedSignatures = Collections.unmodifiableList(invalidDetachedSignatures);
|
||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
this.modificationDate = modificationDate;
|
this.modificationDate = modificationDate;
|
||||||
this.fileEncoding = fileEncoding;
|
this.fileEncoding = fileEncoding;
|
||||||
|
@ -128,11 +133,17 @@ public class OpenPgpMetadata {
|
||||||
*/
|
*/
|
||||||
public @Nonnull Set<PGPSignature> getSignatures() {
|
public @Nonnull Set<PGPSignature> getSignatures() {
|
||||||
Set<PGPSignature> signatures = new HashSet<>();
|
Set<PGPSignature> signatures = new HashSet<>();
|
||||||
for (DetachedSignature detachedSignature : detachedSignatures) {
|
for (SignatureVerification v : getVerifiedDetachedSignatures()) {
|
||||||
signatures.add(detachedSignature.getSignature());
|
signatures.add(v.getSignature());
|
||||||
}
|
}
|
||||||
for (OnePassSignature onePassSignature : onePassSignatures) {
|
for (SignatureVerification v : getVerifiedInbandSignatures()) {
|
||||||
signatures.add(onePassSignature.getSignature());
|
signatures.add(v.getSignature());
|
||||||
|
}
|
||||||
|
for (SignatureVerification.Failure f : getInvalidDetachedSignatures()) {
|
||||||
|
signatures.add(f.getSignatureVerification().getSignature());
|
||||||
|
}
|
||||||
|
for (SignatureVerification.Failure f : getInvalidInbandSignatures()) {
|
||||||
|
signatures.add(f.getSignatureVerification().getSignature());
|
||||||
}
|
}
|
||||||
return signatures;
|
return signatures;
|
||||||
}
|
}
|
||||||
|
@ -158,20 +169,32 @@ public class OpenPgpMetadata {
|
||||||
*/
|
*/
|
||||||
public Map<SubkeyIdentifier, PGPSignature> getVerifiedSignatures() {
|
public Map<SubkeyIdentifier, PGPSignature> getVerifiedSignatures() {
|
||||||
Map<SubkeyIdentifier, PGPSignature> verifiedSignatures = new ConcurrentHashMap<>();
|
Map<SubkeyIdentifier, PGPSignature> verifiedSignatures = new ConcurrentHashMap<>();
|
||||||
for (DetachedSignature detachedSignature : detachedSignatures) {
|
for (SignatureVerification detachedSignature : getVerifiedDetachedSignatures()) {
|
||||||
if (detachedSignature.isVerified()) {
|
verifiedSignatures.put(detachedSignature.getSigningKey(), detachedSignature.getSignature());
|
||||||
verifiedSignatures.put(detachedSignature.getSigningKeyIdentifier(), detachedSignature.getSignature());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (OnePassSignature onePassSignature : onePassSignatures) {
|
|
||||||
if (onePassSignature.isVerified()) {
|
|
||||||
verifiedSignatures.put(onePassSignature.getSigningKey(), onePassSignature.getSignature());
|
|
||||||
}
|
}
|
||||||
|
for (SignatureVerification inbandSignatures : verifiedInbandSignatures) {
|
||||||
|
verifiedSignatures.put(inbandSignatures.getSigningKey(), inbandSignatures.getSignature());
|
||||||
}
|
}
|
||||||
|
|
||||||
return verifiedSignatures;
|
return verifiedSignatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<SignatureVerification> getVerifiedInbandSignatures() {
|
||||||
|
return verifiedInbandSignatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SignatureVerification> getVerifiedDetachedSignatures() {
|
||||||
|
return verifiedDetachedSignatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SignatureVerification.Failure> getInvalidInbandSignatures() {
|
||||||
|
return invalidInbandSignatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SignatureVerification.Failure> getInvalidDetachedSignatures() {
|
||||||
|
return invalidDetachedSignatures;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true, if the message is signed and at least one signature on the message was verified successfully.
|
* Return true, if the message is signed and at least one signature on the message was verified successfully.
|
||||||
*
|
*
|
||||||
|
@ -261,14 +284,18 @@ public class OpenPgpMetadata {
|
||||||
|
|
||||||
private final Set<Long> recipientFingerprints = new HashSet<>();
|
private final Set<Long> recipientFingerprints = new HashSet<>();
|
||||||
private SubkeyIdentifier decryptionKey;
|
private SubkeyIdentifier decryptionKey;
|
||||||
private final List<DetachedSignature> detachedSignatures = new ArrayList<>();
|
|
||||||
private final List<OnePassSignature> onePassSignatures = new ArrayList<>();
|
|
||||||
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
|
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
|
||||||
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
|
||||||
private String fileName;
|
private String fileName;
|
||||||
private StreamEncoding fileEncoding;
|
private StreamEncoding fileEncoding;
|
||||||
private Date modificationDate;
|
private Date modificationDate;
|
||||||
|
|
||||||
|
private final List<SignatureVerification> verifiedInbandSignatures = new ArrayList<>();
|
||||||
|
private final List<SignatureVerification> verifiedDetachedSignatures = new ArrayList<>();
|
||||||
|
private final List<SignatureVerification.Failure> invalidInbandSignatures = new ArrayList<>();
|
||||||
|
private final List<SignatureVerification.Failure> invalidDetachedSignatures = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
public Builder addRecipientKeyId(Long keyId) {
|
public Builder addRecipientKeyId(Long keyId) {
|
||||||
this.recipientFingerprints.add(keyId);
|
this.recipientFingerprints.add(keyId);
|
||||||
return this;
|
return this;
|
||||||
|
@ -284,10 +311,6 @@ public class OpenPgpMetadata {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<DetachedSignature> getDetachedSignatures() {
|
|
||||||
return detachedSignatures;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
|
public Builder setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
|
||||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
||||||
return this;
|
return this;
|
||||||
|
@ -308,18 +331,29 @@ public class OpenPgpMetadata {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDetachedSignature(DetachedSignature signature) {
|
|
||||||
this.detachedSignatures.add(signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addOnePassSignature(OnePassSignature onePassSignature) {
|
|
||||||
this.onePassSignatures.add(onePassSignature);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenPgpMetadata build() {
|
public OpenPgpMetadata build() {
|
||||||
return new OpenPgpMetadata(recipientFingerprints, decryptionKey,
|
return new OpenPgpMetadata(
|
||||||
|
recipientFingerprints, decryptionKey,
|
||||||
symmetricKeyAlgorithm, compressionAlgorithm,
|
symmetricKeyAlgorithm, compressionAlgorithm,
|
||||||
onePassSignatures, detachedSignatures, fileName, modificationDate, fileEncoding);
|
verifiedInbandSignatures, invalidInbandSignatures,
|
||||||
|
verifiedDetachedSignatures, invalidDetachedSignatures,
|
||||||
|
fileName, modificationDate, fileEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addVerifiedInbandSignature(SignatureVerification signatureVerification) {
|
||||||
|
this.verifiedInbandSignatures.add(signatureVerification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addVerifiedDetachedSignature(SignatureVerification signatureVerification) {
|
||||||
|
this.verifiedDetachedSignatures.add(signatureVerification);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInvalidInbandSignature(SignatureVerification signatureVerification, SignatureValidationException e) {
|
||||||
|
this.invalidInbandSignatures.add(new SignatureVerification.Failure(signatureVerification, e));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInvalidDetachedSignature(SignatureVerification signatureVerification, SignatureValidationException e) {
|
||||||
|
this.invalidDetachedSignatures.add(new SignatureVerification.Failure(signatureVerification, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.decryption_verification;
|
||||||
|
|
||||||
|
import static org.pgpainless.signature.SignatureValidator.signatureWasCreatedInBounds;
|
||||||
|
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.exception.SignatureValidationException;
|
||||||
|
import org.pgpainless.policy.Policy;
|
||||||
|
import org.pgpainless.signature.CertificateValidator;
|
||||||
|
import org.pgpainless.signature.DetachedSignature;
|
||||||
|
import org.pgpainless.signature.OnePassSignatureCheck;
|
||||||
|
import org.pgpainless.signature.cleartext_signatures.SignatureVerification;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public abstract class SignatureInputStream extends FilterInputStream {
|
||||||
|
|
||||||
|
protected SignatureInputStream(InputStream inputStream) {
|
||||||
|
super(inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class VerifySignatures extends SignatureInputStream {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(VerifySignatures.class);
|
||||||
|
|
||||||
|
private final List<OnePassSignatureCheck> opSignatures;
|
||||||
|
private final List<DetachedSignature> detachedSignatures;
|
||||||
|
private final ConsumerOptions options;
|
||||||
|
private final OpenPgpMetadata.Builder resultBuilder;
|
||||||
|
|
||||||
|
public VerifySignatures(
|
||||||
|
InputStream literalDataStream,
|
||||||
|
List<OnePassSignatureCheck> opSignatures,
|
||||||
|
List<DetachedSignature> detachedSignatures,
|
||||||
|
ConsumerOptions options,
|
||||||
|
OpenPgpMetadata.Builder resultBuilder) {
|
||||||
|
super(literalDataStream);
|
||||||
|
this.opSignatures = opSignatures;
|
||||||
|
this.detachedSignatures = detachedSignatures;
|
||||||
|
this.options = options;
|
||||||
|
this.resultBuilder = resultBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
final int data = super.read();
|
||||||
|
final boolean endOfStream = data == -1;
|
||||||
|
if (endOfStream) {
|
||||||
|
verifyOnePassSignatures();
|
||||||
|
verifyDetachedSignatures();
|
||||||
|
} else {
|
||||||
|
byte b = (byte) data;
|
||||||
|
updateOnePassSignatures(b);
|
||||||
|
updateDetachedSignatures(b);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(@Nonnull byte[] b, int off, int len) throws IOException {
|
||||||
|
int read = super.read(b, off, len);
|
||||||
|
|
||||||
|
final boolean endOfStream = read == -1;
|
||||||
|
if (endOfStream) {
|
||||||
|
verifyOnePassSignatures();
|
||||||
|
verifyDetachedSignatures();
|
||||||
|
} else {
|
||||||
|
updateOnePassSignatures(b, off, read);
|
||||||
|
updateDetachedSignatures(b, off, read);
|
||||||
|
}
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void verifyOnePassSignatures() {
|
||||||
|
Policy policy = PGPainless.getPolicy();
|
||||||
|
for (OnePassSignatureCheck opSignature : opSignatures) {
|
||||||
|
if (opSignature.getSignature() == null) {
|
||||||
|
LOGGER.warn("Found OnePassSignature without respective signature packet -> skip");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()).verify(opSignature.getSignature());
|
||||||
|
CertificateValidator.validateCertificateAndVerifyOnePassSignature(opSignature, policy);
|
||||||
|
resultBuilder.addVerifiedInbandSignature(new SignatureVerification(opSignature.getSignature(), opSignature.getSigningKey()));
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
LOGGER.warn("One-pass-signature verification failed for signature made by key {}: {}",
|
||||||
|
opSignature.getSigningKey(), e.getMessage(), e);
|
||||||
|
resultBuilder.addInvalidInbandSignature(new SignatureVerification(opSignature.getSignature(), opSignature.getSigningKey()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyDetachedSignatures() {
|
||||||
|
Policy policy = PGPainless.getPolicy();
|
||||||
|
for (DetachedSignature s : detachedSignatures) {
|
||||||
|
try {
|
||||||
|
signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()).verify(s.getSignature());
|
||||||
|
CertificateValidator.validateCertificateAndVerifyInitializedSignature(s.getSignature(), (PGPPublicKeyRing) s.getSigningKeyRing(), policy);
|
||||||
|
resultBuilder.addVerifiedDetachedSignature(new SignatureVerification(s.getSignature(), s.getSigningKeyIdentifier()));
|
||||||
|
} catch (SignatureValidationException e) {
|
||||||
|
LOGGER.warn("One-pass-signature verification failed for signature made by key {}: {}",
|
||||||
|
s.getSigningKeyIdentifier(), e.getMessage(), e);
|
||||||
|
resultBuilder.addInvalidDetachedSignature(new SignatureVerification(s.getSignature(), s.getSigningKeyIdentifier()), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOnePassSignatures(byte data) {
|
||||||
|
for (OnePassSignatureCheck opSignature : opSignatures) {
|
||||||
|
opSignature.getOnePassSignature().update(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateOnePassSignatures(byte[] bytes, int offset, int length) {
|
||||||
|
for (OnePassSignatureCheck opSignature : opSignatures) {
|
||||||
|
opSignature.getOnePassSignature().update(bytes, offset, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDetachedSignatures(byte b) {
|
||||||
|
for (DetachedSignature detachedSignature : detachedSignatures) {
|
||||||
|
detachedSignature.getSignature().update(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDetachedSignatures(byte[] b, int off, int read) {
|
||||||
|
for (DetachedSignature detachedSignature : detachedSignatures) {
|
||||||
|
detachedSignature.getSignature().update(b, off, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CleartextSignatures extends SignatureInputStream {
|
||||||
|
public CleartextSignatures(InputStream inputStream, List<PGPSignature> signatures) {
|
||||||
|
super(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,203 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2018 Paul Schaub.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.pgpainless.decryption_verification;
|
|
||||||
|
|
||||||
import static org.pgpainless.signature.SignatureValidator.signatureWasCreatedInBounds;
|
|
||||||
|
|
||||||
import java.io.FilterInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPObjectFactory;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
|
||||||
import org.bouncycastle.openpgp.PGPSignatureList;
|
|
||||||
import org.pgpainless.PGPainless;
|
|
||||||
import org.pgpainless.exception.SignatureValidationException;
|
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
|
||||||
import org.pgpainless.policy.Policy;
|
|
||||||
import org.pgpainless.signature.OnePassSignature;
|
|
||||||
import org.pgpainless.signature.CertificateValidator;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
public class SignatureVerifyingInputStream extends FilterInputStream {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(SignatureVerifyingInputStream.class);
|
|
||||||
|
|
||||||
private final PGPObjectFactory objectFactory;
|
|
||||||
private final Map<OpenPgpV4Fingerprint, OnePassSignature> onePassSignatures;
|
|
||||||
private final ConsumerOptions options;
|
|
||||||
private final OpenPgpMetadata.Builder resultBuilder;
|
|
||||||
|
|
||||||
private boolean validated = false;
|
|
||||||
|
|
||||||
protected SignatureVerifyingInputStream(@Nonnull InputStream inputStream,
|
|
||||||
@Nonnull PGPObjectFactory objectFactory,
|
|
||||||
@Nonnull Map<OpenPgpV4Fingerprint, OnePassSignature> onePassSignatures,
|
|
||||||
@Nonnull ConsumerOptions options,
|
|
||||||
@Nonnull OpenPgpMetadata.Builder resultBuilder) {
|
|
||||||
super(inputStream);
|
|
||||||
this.objectFactory = objectFactory;
|
|
||||||
this.options = options;
|
|
||||||
this.resultBuilder = resultBuilder;
|
|
||||||
this.onePassSignatures = onePassSignatures;
|
|
||||||
|
|
||||||
LOGGER.debug("Begin verifying OnePassSignatures");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateOnePassSignatures(byte data) {
|
|
||||||
for (OnePassSignature signature : onePassSignatures.values()) {
|
|
||||||
signature.getOnePassSignature().update(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateOnePassSignatures(byte[] b, int off, int len) {
|
|
||||||
for (OnePassSignature signature : onePassSignatures.values()) {
|
|
||||||
signature.getOnePassSignature().update(b, off, len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateOnePassSignaturesIfNeeded() throws IOException {
|
|
||||||
if (validated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
validated = true;
|
|
||||||
validateOnePassSignaturesIfAny();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateOnePassSignaturesIfAny() throws IOException {
|
|
||||||
if (onePassSignatures.isEmpty()) {
|
|
||||||
LOGGER.debug("No One-Pass-Signatures found -> No validation");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
validateOnePassSignatures();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateOnePassSignatures() throws IOException {
|
|
||||||
PGPSignatureList signatureList = findPgpSignatureList();
|
|
||||||
|
|
||||||
for (PGPSignature signature : signatureList) {
|
|
||||||
try {
|
|
||||||
OpenPgpV4Fingerprint fingerprint = findFingerprintForSignature(signature);
|
|
||||||
OnePassSignature onePassSignature = findOnePassSignature(fingerprint);
|
|
||||||
if (onePassSignature == null) {
|
|
||||||
LOGGER.warn("Found Signature without respective OnePassSignature packet -> skip");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
verifySignatureOrThrowSignatureException(signature, onePassSignature);
|
|
||||||
} catch (SignatureValidationException e) {
|
|
||||||
LOGGER.warn("One-pass-signature verification failed for signature made by key {}: {}",
|
|
||||||
Long.toHexString(signature.getKeyID()), e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifySignatureOrThrowSignatureException(PGPSignature signature, OnePassSignature onePassSignature)
|
|
||||||
throws SignatureValidationException {
|
|
||||||
Policy policy = PGPainless.getPolicy();
|
|
||||||
signatureWasCreatedInBounds(options.getVerifyNotBefore(), options.getVerifyNotAfter()).verify(signature);
|
|
||||||
CertificateValidator.validateCertificateAndVerifyOnePassSignature(signature, onePassSignature, policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
private OnePassSignature findOnePassSignature(OpenPgpV4Fingerprint fingerprint) {
|
|
||||||
if (fingerprint != null) {
|
|
||||||
return onePassSignatures.get(fingerprint);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PGPSignatureList findPgpSignatureList() throws IOException {
|
|
||||||
PGPSignatureList signatureList = null;
|
|
||||||
Object pgpObject = objectFactory.nextObject();
|
|
||||||
while (pgpObject != null && signatureList == null) {
|
|
||||||
if (pgpObject instanceof PGPSignatureList) {
|
|
||||||
signatureList = (PGPSignatureList) pgpObject;
|
|
||||||
} else {
|
|
||||||
pgpObject = objectFactory.nextObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signatureList == null || signatureList.isEmpty()) {
|
|
||||||
throw new IOException("Verification failed - No Signatures found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return signatureList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private OpenPgpV4Fingerprint findFingerprintForSignature(PGPSignature signature) {
|
|
||||||
OpenPgpV4Fingerprint fingerprint = null;
|
|
||||||
for (OpenPgpV4Fingerprint f : onePassSignatures.keySet()) {
|
|
||||||
if (f.getKeyId() == signature.getKeyID()) {
|
|
||||||
fingerprint = f;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
final int data = super.read();
|
|
||||||
final boolean endOfStream = data == -1;
|
|
||||||
if (endOfStream) {
|
|
||||||
validateOnePassSignaturesIfNeeded();
|
|
||||||
} else {
|
|
||||||
updateOnePassSignatures((byte) data);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(@Nonnull byte[] b) throws IOException {
|
|
||||||
return read(b, 0, b.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read(@Nonnull byte[] b, int off, int len) throws IOException {
|
|
||||||
int read = super.read(b, off, len);
|
|
||||||
|
|
||||||
final boolean endOfStream = read == -1;
|
|
||||||
if (endOfStream) {
|
|
||||||
validateOnePassSignaturesIfNeeded();
|
|
||||||
} else {
|
|
||||||
updateOnePassSignatures(b, off, read);
|
|
||||||
}
|
|
||||||
return read;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long skip(long n) {
|
|
||||||
throw new UnsupportedOperationException("skip() is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void mark(int readlimit) {
|
|
||||||
throw new UnsupportedOperationException("mark() not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void reset() {
|
|
||||||
throw new UnsupportedOperationException("reset() is not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean markSupported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -265,16 +265,16 @@ public final class CertificateValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the signing key certificate and the given {@link OnePassSignature}.
|
* Validate the signing key certificate and the given {@link OnePassSignatureCheck}.
|
||||||
*
|
*
|
||||||
* @param signature OpenPGP signature from the signed message
|
|
||||||
* @param onePassSignature corresponding one-pass-signature
|
* @param onePassSignature corresponding one-pass-signature
|
||||||
* @param policy policy
|
* @param policy policy
|
||||||
* @return true if the certificate is valid and the signature is correct, false otherwise.
|
* @return true if the certificate is valid and the signature is correct, false otherwise.
|
||||||
* @throws SignatureValidationException in case of a validation error
|
* @throws SignatureValidationException in case of a validation error
|
||||||
*/
|
*/
|
||||||
public static boolean validateCertificateAndVerifyOnePassSignature(PGPSignature signature, OnePassSignature onePassSignature, Policy policy)
|
public static boolean validateCertificateAndVerifyOnePassSignature(OnePassSignatureCheck onePassSignature, Policy policy)
|
||||||
throws SignatureValidationException {
|
throws SignatureValidationException {
|
||||||
|
PGPSignature signature = onePassSignature.getSignature();
|
||||||
validateCertificate(signature, onePassSignature.getVerificationKeys(), policy);
|
validateCertificate(signature, onePassSignature.getVerificationKeys(), policy);
|
||||||
PGPPublicKey signingKey = onePassSignature.getVerificationKeys().getPublicKey(signature.getKeyID());
|
PGPPublicKey signingKey = onePassSignature.getVerificationKeys().getPublicKey(signature.getKeyID());
|
||||||
verifyOnePassSignature(signature, signingKey, onePassSignature, policy);
|
verifyOnePassSignature(signature, signingKey, onePassSignature, policy);
|
||||||
|
|
|
@ -19,31 +19,36 @@ import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
import org.bouncycastle.openpgp.PGPOnePassSignature;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.decryption_verification.SignatureInputStream;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tuple-class that bundles together a {@link PGPOnePassSignature} object, a {@link PGPPublicKeyRing}
|
* Tuple-class that bundles together a {@link PGPOnePassSignature} object, a {@link PGPPublicKeyRing}
|
||||||
* destined to verify the signature, the {@link PGPSignature} itself and a record of whether or not the signature
|
* destined to verify the signature, the {@link PGPSignature} itself and a record of whether the signature
|
||||||
* was verified.
|
* was verified.
|
||||||
*/
|
*/
|
||||||
public class OnePassSignature {
|
public class OnePassSignatureCheck {
|
||||||
private final PGPOnePassSignature onePassSignature;
|
private final PGPOnePassSignature onePassSignature;
|
||||||
private final PGPPublicKeyRing verificationKeys;
|
private final PGPPublicKeyRing verificationKeys;
|
||||||
private PGPSignature signature;
|
private PGPSignature signature;
|
||||||
private boolean verified;
|
private boolean verified;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link OnePassSignature}.
|
* Create a new {@link OnePassSignatureCheck}.
|
||||||
*
|
*
|
||||||
* @param onePassSignature one-pass signature packet used to initialize the signature verifier.
|
* @param onePassSignature one-pass signature packet used to initialize the signature verifier.
|
||||||
* @param verificationKeys verification keys
|
* @param verificationKeys verification keys
|
||||||
*/
|
*/
|
||||||
public OnePassSignature(PGPOnePassSignature onePassSignature, PGPPublicKeyRing verificationKeys) {
|
public OnePassSignatureCheck(PGPOnePassSignature onePassSignature, PGPPublicKeyRing verificationKeys) {
|
||||||
this.onePassSignature = onePassSignature;
|
this.onePassSignature = onePassSignature;
|
||||||
this.verificationKeys = verificationKeys;
|
this.verificationKeys = verificationKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSignature(PGPSignature signature) {
|
||||||
|
this.signature = signature;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if the signature is verified.
|
* Return true if the signature is verified.
|
||||||
*
|
*
|
||||||
|
@ -75,17 +80,16 @@ public class OnePassSignature {
|
||||||
* Verify the one-pass signature.
|
* Verify the one-pass signature.
|
||||||
* Note: This method only checks if the signature itself is correct.
|
* Note: This method only checks if the signature itself is correct.
|
||||||
* It does not check if the signing key was eligible to create the signature, or if the signature is expired etc.
|
* It does not check if the signing key was eligible to create the signature, or if the signature is expired etc.
|
||||||
* Those checks are being done by {@link org.pgpainless.decryption_verification.SignatureVerifyingInputStream}.
|
* Those checks are being done by {@link SignatureInputStream.VerifySignatures}.
|
||||||
*
|
*
|
||||||
* @param signature parsed-out signature
|
|
||||||
* @return true if the signature was verified, false otherwise
|
* @return true if the signature was verified, false otherwise
|
||||||
* @throws PGPException if signature verification fails with an exception.
|
* @throws PGPException if signature verification fails with an exception.
|
||||||
*/
|
*/
|
||||||
public boolean verify(PGPSignature signature) throws PGPException {
|
public boolean verify() throws PGPException {
|
||||||
this.verified = getOnePassSignature().verify(signature);
|
if (signature == null) {
|
||||||
if (verified) {
|
throw new IllegalStateException("No comparison signature provided.");
|
||||||
this.signature = signature;
|
|
||||||
}
|
}
|
||||||
|
this.verified = getOnePassSignature().verify(signature);
|
||||||
return verified;
|
return verified;
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,4 +337,12 @@ public final class SignatureUtils {
|
||||||
public static String getSignatureDigestPrefix(PGPSignature signature) {
|
public static String getSignatureDigestPrefix(PGPSignature signature) {
|
||||||
return Hex.toHexString(signature.getDigestPrefix());
|
return Hex.toHexString(signature.getDigestPrefix());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<PGPSignature> toList(PGPSignatureList signatures) {
|
||||||
|
List<PGPSignature> list = new ArrayList<>();
|
||||||
|
for (PGPSignature signature : signatures) {
|
||||||
|
list.add(signature);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -424,7 +424,7 @@ public final class SignatureVerifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean verifyOnePassSignature(PGPSignature signature, PGPPublicKey signingKey, OnePassSignature onePassSignature, Policy policy)
|
public static boolean verifyOnePassSignature(PGPSignature signature, PGPPublicKey signingKey, OnePassSignatureCheck onePassSignature, Policy policy)
|
||||||
throws SignatureValidationException {
|
throws SignatureValidationException {
|
||||||
try {
|
try {
|
||||||
SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature);
|
SignatureValidator.wasPossiblyMadeByKey(signingKey).verify(signature);
|
||||||
|
@ -435,7 +435,7 @@ public final class SignatureVerifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!onePassSignature.verify(signature)) {
|
if (!onePassSignature.verify()) {
|
||||||
throw new SignatureValidationException("Bad signature of key " + Long.toHexString(signingKey.getKeyID()));
|
throw new SignatureValidationException("Bad signature of key " + Long.toHexString(signingKey.getKeyID()));
|
||||||
}
|
}
|
||||||
} catch (PGPException e) {
|
} catch (PGPException e) {
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Paul Schaub.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.pgpainless.signature.cleartext_signatures;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.pgpainless.exception.SignatureValidationException;
|
||||||
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
|
|
||||||
|
public class SignatureVerification {
|
||||||
|
|
||||||
|
private final PGPSignature signature;
|
||||||
|
private final SubkeyIdentifier signingKey;
|
||||||
|
|
||||||
|
public SignatureVerification(PGPSignature signature, SubkeyIdentifier signingKey) {
|
||||||
|
this.signature = signature;
|
||||||
|
this.signingKey = signingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PGPSignature getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubkeyIdentifier getSigningKey() {
|
||||||
|
return signingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Failure {
|
||||||
|
private final SignatureVerification signatureVerification;
|
||||||
|
private final SignatureValidationException validationException;
|
||||||
|
|
||||||
|
public Failure(SignatureVerification verification, SignatureValidationException validationException) {
|
||||||
|
this.signatureVerification = verification;
|
||||||
|
this.validationException = validationException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureVerification getSignatureVerification() {
|
||||||
|
return signatureVerification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignatureValidationException getValidationException() {
|
||||||
|
return validationException;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -93,7 +93,7 @@ public class VerifyNotBeforeNotAfterTest {
|
||||||
.withOptions(options);
|
.withOptions(options);
|
||||||
|
|
||||||
OpenPgpMetadata metadata = processSignedData(verifier);
|
OpenPgpMetadata metadata = processSignedData(verifier);
|
||||||
assertTrue(metadata.getVerifiedSignatures().containsKey(new SubkeyIdentifier(certificate)));
|
assertTrue(metadata.containsVerifiedSignatureFrom(certificate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -30,6 +30,7 @@ public class DearmorImpl implements Dearmor {
|
||||||
public Ready data(InputStream data) throws IOException {
|
public Ready data(InputStream data) throws IOException {
|
||||||
InputStream decoder = PGPUtil.getDecoderStream(data);
|
InputStream decoder = PGPUtil.getDecoderStream(data);
|
||||||
return new Ready() {
|
return new Ready() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeTo(OutputStream outputStream) throws IOException {
|
public void writeTo(OutputStream outputStream) throws IOException {
|
||||||
Streams.pipeAll(decoder, outputStream);
|
Streams.pipeAll(decoder, outputStream);
|
||||||
|
|
Loading…
Reference in a new issue