pgpainless/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java

370 lines
19 KiB
Java
Raw Normal View History

/*
* 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;
2018-06-06 18:46:41 +02:00
import java.io.IOException;
import java.io.InputStream;
2021-02-17 21:04:05 +01:00
import java.util.ArrayList;
2018-06-06 18:46:41 +02:00
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
2020-08-24 14:55:06 +02:00
import java.util.List;
2018-06-06 18:46:41 +02:00
import java.util.Map;
import java.util.Set;
2018-06-19 17:14:37 +02:00
import java.util.logging.Level;
import java.util.logging.Logger;
2020-12-27 01:56:18 +01:00
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
2018-06-06 18:46:41 +02:00
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedData;
2018-06-06 18:46:41 +02:00
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPOnePassSignature;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
2018-06-06 18:46:41 +02:00
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
2018-06-06 18:46:41 +02:00
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
2020-08-24 14:55:06 +02:00
import org.bouncycastle.openpgp.PGPSignature;
2018-06-06 18:46:41 +02:00
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
2018-06-06 18:46:41 +02:00
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
2020-12-27 01:56:18 +01:00
import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.protection.SecretKeyRingProtector;
2021-02-17 21:04:05 +01:00
import org.pgpainless.util.IntegrityProtectedInputStream;
import org.pgpainless.util.Passphrase;
2018-06-06 18:46:41 +02:00
2018-07-02 21:40:59 +02:00
public final class DecryptionStreamFactory {
2018-06-06 18:46:41 +02:00
2018-06-19 17:14:37 +02:00
private static final Logger LOGGER = Logger.getLogger(DecryptionStreamFactory.class.getName());
2018-06-21 15:19:07 +02:00
private static final Level LEVEL = Level.FINE;
private static final int MAX_RECURSION_DEPTH = 16;
2018-06-19 17:14:37 +02:00
2018-06-06 18:46:41 +02:00
private final PGPSecretKeyRingCollection decryptionKeys;
2018-06-07 18:12:13 +02:00
private final SecretKeyRingProtector decryptionKeyDecryptor;
private final Passphrase decryptionPassphrase;
2018-06-06 18:46:41 +02:00
private final Set<PGPPublicKeyRing> verificationKeys = new HashSet<>();
private final MissingPublicKeyCallback missingPublicKeyCallback;
private final OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
2020-12-27 01:56:18 +01:00
private static final PGPContentVerifierBuilderProvider verifierBuilderProvider = ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider();
private static final KeyFingerPrintCalculator keyFingerprintCalculator = ImplementationFactory.getInstance().getKeyFingerprintCalculator();
2020-08-24 14:55:06 +02:00
private final Map<OpenPgpV4Fingerprint, OnePassSignature> verifiableOnePassSignatures = new HashMap<>();
2021-02-17 21:04:05 +01:00
private final List<IntegrityProtectedInputStream> integrityProtectedStreams = new ArrayList<>();
2018-06-06 18:46:41 +02:00
private DecryptionStreamFactory(@Nullable PGPSecretKeyRingCollection decryptionKeys,
@Nullable SecretKeyRingProtector decryptor,
@Nullable Passphrase decryptionPassphrase,
@Nullable Set<PGPPublicKeyRing> verificationKeys,
@Nullable MissingPublicKeyCallback missingPublicKeyCallback) {
2018-06-06 18:46:41 +02:00
this.decryptionKeys = decryptionKeys;
this.decryptionKeyDecryptor = decryptor;
this.decryptionPassphrase = decryptionPassphrase;
2018-06-06 18:46:41 +02:00
this.verificationKeys.addAll(verificationKeys != null ? verificationKeys : Collections.emptyList());
this.missingPublicKeyCallback = missingPublicKeyCallback;
}
public static DecryptionStream create(@Nonnull InputStream inputStream,
@Nullable PGPSecretKeyRingCollection decryptionKeys,
@Nullable SecretKeyRingProtector decryptor,
@Nullable Passphrase decryptionPassphrase,
2020-08-24 14:55:06 +02:00
@Nullable List<PGPSignature> detachedSignatures,
@Nullable Set<PGPPublicKeyRing> verificationKeys,
@Nullable MissingPublicKeyCallback missingPublicKeyCallback)
2018-06-06 18:46:41 +02:00
throws IOException, PGPException {
2020-08-24 14:55:06 +02:00
InputStream pgpInputStream;
DecryptionStreamFactory factory = new DecryptionStreamFactory(decryptionKeys, decryptor,
decryptionPassphrase, verificationKeys, missingPublicKeyCallback);
2018-06-06 18:46:41 +02:00
2020-08-24 14:55:06 +02:00
if (detachedSignatures != null) {
pgpInputStream = inputStream;
for (PGPSignature signature : detachedSignatures) {
PGPPublicKey signingKey = factory.findSignatureVerificationKey(signature.getKeyID());
if (signingKey == null) {
continue;
}
2020-12-27 01:56:18 +01:00
signature.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), signingKey);
2020-08-24 14:55:06 +02:00
factory.resultBuilder.addDetachedSignature(
new DetachedSignature(signature, new OpenPgpV4Fingerprint(signingKey)));
}
} else {
PGPObjectFactory objectFactory = new PGPObjectFactory(
2020-12-27 01:56:18 +01:00
PGPUtil.getDecoderStream(inputStream), keyFingerprintCalculator);
pgpInputStream = factory.processPGPPackets(objectFactory, 1);
2020-08-24 14:55:06 +02:00
}
2021-02-17 21:04:05 +01:00
return new DecryptionStream(pgpInputStream, factory.resultBuilder, factory.integrityProtectedStreams);
2018-06-06 18:46:41 +02:00
}
private InputStream processPGPPackets(@Nonnull PGPObjectFactory objectFactory, int depth) throws IOException, PGPException {
if (depth >= MAX_RECURSION_DEPTH) {
throw new PGPException("Maximum recursion depth of packages exceeded.");
}
Object nextPgpObject;
while ((nextPgpObject = objectFactory.nextObject()) != null) {
if (nextPgpObject instanceof PGPEncryptedDataList) {
return processPGPEncryptedDataList((PGPEncryptedDataList) nextPgpObject, depth);
2018-06-06 18:46:41 +02:00
}
if (nextPgpObject instanceof PGPCompressedData) {
return processPGPCompressedData((PGPCompressedData) nextPgpObject, depth);
2018-06-06 18:46:41 +02:00
}
if (nextPgpObject instanceof PGPOnePassSignatureList) {
return processOnePassSignatureList(objectFactory, (PGPOnePassSignatureList) nextPgpObject, depth);
2018-06-06 18:46:41 +02:00
}
if (nextPgpObject instanceof PGPLiteralData) {
return processPGPLiteralData(objectFactory, (PGPLiteralData) nextPgpObject);
}
}
2018-06-06 18:46:41 +02:00
throw new PGPException("No Literal Data Packet found");
}
2018-06-06 18:46:41 +02:00
private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
throws PGPException, IOException {
LOGGER.log(LEVEL, "Encountered PGPEncryptedDataList");
InputStream decryptedDataStream = decrypt(pgpEncryptedDataList);
return processPGPPackets(new PGPObjectFactory(PGPUtil.getDecoderStream(decryptedDataStream), keyFingerprintCalculator), ++depth);
}
2018-06-06 18:46:41 +02:00
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
throws PGPException, IOException {
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.fromId(pgpCompressedData.getAlgorithm());
LOGGER.log(LEVEL, "Encountered PGPCompressedData: " + compressionAlgorithm);
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
InputStream dataStream = pgpCompressedData.getDataStream();
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(dataStream), keyFingerprintCalculator);
return processPGPPackets(objectFactory, ++depth);
}
private InputStream processOnePassSignatureList(@Nonnull PGPObjectFactory objectFactory, PGPOnePassSignatureList onePassSignatures, int depth)
throws PGPException, IOException {
LOGGER.log(LEVEL, "Encountered PGPOnePassSignatureList of size " + onePassSignatures.size());
initOnePassSignatures(onePassSignatures);
return processPGPPackets(objectFactory, ++depth);
}
private InputStream processPGPLiteralData(@Nonnull PGPObjectFactory objectFactory, PGPLiteralData pgpLiteralData) {
LOGGER.log(LEVEL, "Found PGPLiteralData");
InputStream literalDataInputStream = pgpLiteralData.getInputStream();
OpenPgpMetadata.FileInfo fileInfo = new OpenPgpMetadata.FileInfo(
pgpLiteralData.getFileName(),
pgpLiteralData.getModificationTime(),
StreamEncoding.fromCode(pgpLiteralData.getFormat()));
resultBuilder.setFileInfo(fileInfo);
if (verifiableOnePassSignatures.isEmpty()) {
LOGGER.log(LEVEL, "No OnePassSignatures found -> We are done");
return literalDataInputStream;
2018-06-06 18:46:41 +02:00
}
return new SignatureVerifyingInputStream(literalDataInputStream,
objectFactory, verifiableOnePassSignatures, resultBuilder);
2018-06-06 18:46:41 +02:00
}
private InputStream decrypt(@Nonnull PGPEncryptedDataList encryptedDataList)
2018-06-06 18:46:41 +02:00
throws PGPException {
Iterator<PGPEncryptedData> encryptedDataIterator = encryptedDataList.getEncryptedDataObjects();
if (!encryptedDataIterator.hasNext()) {
2018-06-19 17:14:37 +02:00
throw new PGPException("Decryption failed - EncryptedDataList has no items");
2018-06-06 18:46:41 +02:00
}
PGPPrivateKey decryptionKey = null;
PGPPublicKeyEncryptedData encryptedSessionKey = null;
while (encryptedDataIterator.hasNext()) {
PGPEncryptedData encryptedData = encryptedDataIterator.next();
if (encryptedData instanceof PGPPBEEncryptedData) {
PGPPBEEncryptedData pbeEncryptedData = (PGPPBEEncryptedData) encryptedData;
if (decryptionPassphrase != null) {
2020-12-27 01:56:18 +01:00
PBEDataDecryptorFactory passphraseDecryptor = ImplementationFactory.getInstance()
.getPBEDataDecryptorFactory(decryptionPassphrase);
SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.fromId(
pbeEncryptedData.getSymmetricAlgorithm(passphraseDecryptor));
if (symmetricKeyAlgorithm == SymmetricKeyAlgorithm.NULL) {
throw new PGPException("Data is not encrypted.");
}
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
resultBuilder.setIntegrityProtected(pbeEncryptedData.isIntegrityProtected());
try {
return pbeEncryptedData.getDataStream(passphraseDecryptor);
} catch (PGPException e) {
LOGGER.log(LEVEL, "Probable passphrase mismatch, skip PBE encrypted data block", e);
}
}
} else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData;
long keyId = publicKeyEncryptedData.getKeyID();
if (decryptionKeys != null) {
// Known key id
if (keyId != 0) {
LOGGER.log(LEVEL, "PGPEncryptedData is encrypted for key " + Long.toHexString(keyId));
resultBuilder.addRecipientKeyId(keyId);
PGPSecretKey secretKey = decryptionKeys.getSecretKey(keyId);
if (secretKey != null) {
LOGGER.log(LEVEL, "Found respective secret key " + Long.toHexString(keyId));
// Watch out! This assignment is possibly done multiple times.
encryptedSessionKey = publicKeyEncryptedData;
decryptionKey = secretKey.extractPrivateKey(decryptionKeyDecryptor.getDecryptor(keyId));
resultBuilder.setDecryptionFingerprint(new OpenPgpV4Fingerprint(secretKey));
}
} else {
// Hidden recipient
LOGGER.log(LEVEL, "Hidden recipient detected. Try to decrypt with all available secret keys.");
outerloop: for (PGPSecretKeyRing ring : decryptionKeys) {
for (PGPSecretKey key : ring) {
PGPPrivateKey privateKey = key.extractPrivateKey(decryptionKeyDecryptor.getDecryptor(key.getKeyID()));
PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey);
try {
publicKeyEncryptedData.getSymmetricAlgorithm(decryptorFactory); // will only succeed if we have the right secret key
LOGGER.log(LEVEL, "Found correct key " + Long.toHexString(key.getKeyID()) + " for hidden recipient decryption.");
decryptionKey = privateKey;
resultBuilder.setDecryptionFingerprint(new OpenPgpV4Fingerprint(key));
encryptedSessionKey = publicKeyEncryptedData;
break outerloop;
} catch (PGPException | ClassCastException e) {
LOGGER.log(LEVEL, "Skipping wrong key " + Long.toHexString(key.getKeyID()) + " for hidden recipient decryption.", e);
}
}
}
}
}
2018-06-06 18:46:41 +02:00
}
}
if (decryptionKey == null) {
throw new PGPException("Decryption failed - No suitable decryption key or passphrase found");
2018-06-06 18:46:41 +02:00
}
2021-02-17 21:04:05 +01:00
PublicKeyDataDecryptorFactory dataDecryptor = ImplementationFactory.getInstance()
2020-12-27 01:56:18 +01:00
.getPublicKeyDataDecryptorFactory(decryptionKey);
2018-06-19 17:14:37 +02:00
SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm
2021-02-17 21:04:05 +01:00
.fromId(encryptedSessionKey.getSymmetricAlgorithm(dataDecryptor));
if (symmetricKeyAlgorithm == SymmetricKeyAlgorithm.NULL) {
throw new PGPException("Data is not encrypted.");
}
2018-06-19 17:14:37 +02:00
LOGGER.log(LEVEL, "Message is encrypted using " + symmetricKeyAlgorithm);
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
resultBuilder.setIntegrityProtected(encryptedSessionKey.isIntegrityProtected());
2018-06-06 18:46:41 +02:00
2021-02-17 21:04:05 +01:00
if (encryptedSessionKey.isIntegrityProtected()) {
IntegrityProtectedInputStream integrityProtected =
new IntegrityProtectedInputStream(encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey);
integrityProtectedStreams.add(integrityProtected);
return integrityProtected;
}
return encryptedSessionKey.getDataStream(dataDecryptor);
2018-06-06 18:46:41 +02:00
}
private void initOnePassSignatures(@Nonnull PGPOnePassSignatureList onePassSignatureList) throws PGPException {
2018-06-06 18:46:41 +02:00
Iterator<PGPOnePassSignature> iterator = onePassSignatureList.iterator();
if (!iterator.hasNext()) {
2018-06-19 17:14:37 +02:00
throw new PGPException("Verification failed - No OnePassSignatures found");
2018-06-06 18:46:41 +02:00
}
processOnePassSignatures(iterator);
}
private void processOnePassSignatures(Iterator<PGPOnePassSignature> signatures) throws PGPException {
while (signatures.hasNext()) {
PGPOnePassSignature signature = signatures.next();
processOnePassSignature(signature);
}
}
2018-06-06 18:46:41 +02:00
private void processOnePassSignature(PGPOnePassSignature signature) throws PGPException {
final long keyId = signature.getKeyID();
2018-07-02 20:46:27 +02:00
LOGGER.log(LEVEL, "Message contains OnePassSignature from " + Long.toHexString(keyId));
2018-07-02 20:46:27 +02:00
// Find public key
PGPPublicKey verificationKey = findSignatureVerificationKey(keyId);
if (verificationKey == null) {
2020-08-24 14:55:06 +02:00
LOGGER.log(LEVEL, "Missing verification key from " + Long.toHexString(keyId));
return;
}
2018-07-02 20:46:27 +02:00
signature.init(verifierBuilderProvider, verificationKey);
2020-12-27 01:56:18 +01:00
OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(verificationKey);
OnePassSignature onePassSignature = new OnePassSignature(signature, fingerprint);
2020-08-24 14:55:06 +02:00
resultBuilder.addOnePassSignature(onePassSignature);
2020-12-27 01:56:18 +01:00
verifiableOnePassSignatures.put(fingerprint, onePassSignature);
}
2018-07-02 20:46:27 +02:00
private PGPPublicKey findSignatureVerificationKey(long keyId) {
PGPPublicKey verificationKey = null;
for (PGPPublicKeyRing publicKeyRing : verificationKeys) {
verificationKey = publicKeyRing.getPublicKey(keyId);
if (verificationKey != null) {
LOGGER.log(LEVEL, "Found public key " + Long.toHexString(keyId) + " for signature verification");
break;
2018-06-06 18:46:41 +02:00
}
}
2018-07-02 20:46:27 +02:00
if (verificationKey == null) {
verificationKey = handleMissingVerificationKey(keyId);
2018-06-06 18:46:41 +02:00
}
return verificationKey;
}
private PGPPublicKey handleMissingVerificationKey(long keyId) {
LOGGER.log(Level.FINER, "No public key found for signature of " + Long.toHexString(keyId));
if (missingPublicKeyCallback == null) {
LOGGER.log(Level.FINER, "No MissingPublicKeyCallback registered. " +
"Skip signature of " + Long.toHexString(keyId));
return null;
}
PGPPublicKey missingPublicKey = missingPublicKeyCallback.onMissingPublicKeyEncountered(keyId);
if (missingPublicKey == null) {
LOGGER.log(Level.FINER, "MissingPublicKeyCallback did not provider key. " +
"Skip signature of " + Long.toHexString(keyId));
return null;
}
if (missingPublicKey.getKeyID() != keyId) {
throw new IllegalArgumentException("KeyID of the provided public key differs from the signatures keyId. " +
"The signature was created from " + Long.toHexString(keyId) + " while the provided key has ID " +
Long.toHexString(missingPublicKey.getKeyID()));
}
return missingPublicKey;
2018-06-06 18:46:41 +02:00
}
2020-08-24 14:55:06 +02:00
2018-06-06 18:46:41 +02:00
}