1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-10 18:59:39 +02:00

Implement decryption with - and access of session keys

This commit is contained in:
Paul Schaub 2021-10-15 14:58:17 +02:00
parent 03f13ee4a7
commit c55fd2e552
12 changed files with 334 additions and 37 deletions

View file

@ -24,10 +24,10 @@ import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy;
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
import org.pgpainless.exception.NotYetImplementedException;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.signature.SignatureUtils;
import org.pgpainless.util.Passphrase;
import org.pgpainless.util.SessionKey;
/**
* Options for decryption and signature verification.
@ -46,7 +46,7 @@ public class ConsumerOptions {
private MissingPublicKeyCallback missingCertificateCallback = null;
// Session key for decryption without passphrase/key
private byte[] sessionKey = null;
private SessionKey sessionKey = null;
private final Map<PGPSecretKeyRing, SecretKeyRingProtector> decryptionKeys = new HashMap<>();
private final Set<Passphrase> decryptionPassphrases = new HashSet<>();
@ -162,16 +162,15 @@ public class ConsumerOptions {
* Attempt decryption using a session key.
*
* Note: PGPainless does not yet support decryption with session keys.
* TODO: Add support for decryption using session key.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-2.1">RFC4880 on Session Keys</a>
*
* @param sessionKey session key
* @return options
*/
public ConsumerOptions setSessionKey(@Nonnull byte[] sessionKey) {
public ConsumerOptions setSessionKey(@Nonnull SessionKey sessionKey) {
this.sessionKey = sessionKey;
throw new NotYetImplementedException();
return this;
}
/**
@ -179,14 +178,8 @@ public class ConsumerOptions {
*
* @return session key or null
*/
public @Nullable byte[] getSessionKey() {
if (sessionKey == null) {
return null;
}
byte[] sk = new byte[sessionKey.length];
System.arraycopy(sessionKey, 0, sk, 0, sessionKey.length);
return sk;
public @Nullable SessionKey getSessionKey() {
return sessionKey;
}
/**

View file

@ -34,12 +34,14 @@ import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.EncryptionPurpose;
@ -63,6 +65,7 @@ import org.pgpainless.signature.SignatureUtils;
import org.pgpainless.util.CRCingArmoredInputStreamWrapper;
import org.pgpainless.util.PGPUtilWrapper;
import org.pgpainless.util.Passphrase;
import org.pgpainless.util.SessionKey;
import org.pgpainless.util.Tuple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -210,12 +213,53 @@ public final class DecryptionStreamFactory {
private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
throws PGPException, IOException {
LOGGER.debug("Depth {}: Encountered PGPEncryptedDataList", depth);
SessionKey sessionKey = options.getSessionKey();
if (sessionKey != null) {
integrityProtectedEncryptedInputStream = decryptWithProvidedSessionKey(pgpEncryptedDataList, sessionKey);
InputStream decodedDataStream = PGPUtil.getDecoderStream(integrityProtectedEncryptedInputStream);
PGPObjectFactory factory = new PGPObjectFactory(decodedDataStream, keyFingerprintCalculator);
return processPGPPackets(factory, ++depth);
}
InputStream decryptedDataStream = decryptSessionKey(pgpEncryptedDataList);
InputStream decodedDataStream = PGPUtil.getDecoderStream(decryptedDataStream);
PGPObjectFactory factory = new PGPObjectFactory(decodedDataStream, keyFingerprintCalculator);
return processPGPPackets(factory, ++depth);
}
private IntegrityProtectedInputStream decryptWithProvidedSessionKey(PGPEncryptedDataList pgpEncryptedDataList, SessionKey sessionKey) throws PGPException {
PGPSessionKey pgpSessionKey = new PGPSessionKey(sessionKey.getAlgorithm().getAlgorithmId(), sessionKey.getKey());
SessionKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance().provideSessionKeyDataDecryptorFactory(pgpSessionKey);
InputStream decryptedDataStream = null;
PGPEncryptedData encryptedData = null;
for (PGPEncryptedData pgpEncryptedData : pgpEncryptedDataList) {
encryptedData = pgpEncryptedData;
if (!options.isIgnoreMDCErrors() && !encryptedData.isIntegrityProtected()) {
throw new MessageNotIntegrityProtectedException();
}
if (encryptedData instanceof PGPPBEEncryptedData) {
PGPPBEEncryptedData pbeEncrypted = (PGPPBEEncryptedData) encryptedData;
decryptedDataStream = pbeEncrypted.getDataStream(decryptorFactory);
break;
} else if (encryptedData instanceof PGPPublicKeyEncryptedData) {
PGPPublicKeyEncryptedData pkEncrypted = (PGPPublicKeyEncryptedData) encryptedData;
decryptedDataStream = pkEncrypted.getDataStream(decryptorFactory);
break;
}
}
if (decryptedDataStream == null) {
throw new PGPException("No valid PGP data encountered.");
}
resultBuilder.setSessionKey(sessionKey);
throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(decryptedDataStream, encryptedData, options);
return integrityProtectedEncryptedInputStream;
}
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
throws PGPException, IOException {
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.fromId(pgpCompressedData.getAlgorithm());
@ -294,10 +338,11 @@ public final class DecryptionStreamFactory {
try {
InputStream decryptedDataStream = pbeEncryptedData.getDataStream(passphraseDecryptor);
SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.fromId(
pbeEncryptedData.getSymmetricAlgorithm(passphraseDecryptor));
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
PGPSessionKey pgpSessionKey = pbeEncryptedData.getSessionKey(passphraseDecryptor);
SessionKey sessionKey = new SessionKey(pgpSessionKey);
resultBuilder.setSessionKey(sessionKey);
throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData, options);
@ -454,15 +499,17 @@ public final class DecryptionStreamFactory {
PublicKeyDataDecryptorFactory dataDecryptor = ImplementationFactory.getInstance()
.getPublicKeyDataDecryptorFactory(decryptionKey);
SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm
.fromId(encryptedSessionKey.getSymmetricAlgorithm(dataDecryptor));
PGPSessionKey pgpSessionKey = encryptedSessionKey.getSessionKey(dataDecryptor);
SessionKey sessionKey = new SessionKey(pgpSessionKey);
resultBuilder.setSessionKey(sessionKey);
SymmetricKeyAlgorithm symmetricKeyAlgorithm = sessionKey.getAlgorithm();
if (symmetricKeyAlgorithm == SymmetricKeyAlgorithm.NULL) {
LOGGER.debug("Message is unencrypted");
} else {
LOGGER.debug("Message is encrypted using {}", symmetricKeyAlgorithm);
}
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey, options);
return integrityProtectedEncryptedInputStream;

View file

@ -25,6 +25,7 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.exception.SignatureValidationException;
import org.pgpainless.key.OpenPgpFingerprint;
import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.util.SessionKey;
public class OpenPgpMetadata {
@ -34,7 +35,7 @@ public class OpenPgpMetadata {
private final List<SignatureVerification.Failure> invalidInbandSignatures;
private final List<SignatureVerification> verifiedDetachedSignatures;
private final List<SignatureVerification.Failure> invalidDetachedSignatures;
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
private final SessionKey sessionKey;
private final CompressionAlgorithm compressionAlgorithm;
private final String fileName;
private final Date modificationDate;
@ -42,7 +43,7 @@ public class OpenPgpMetadata {
public OpenPgpMetadata(Set<Long> recipientKeyIds,
SubkeyIdentifier decryptionKey,
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
SessionKey sessionKey,
CompressionAlgorithm algorithm,
List<SignatureVerification> verifiedInbandSignatures,
List<SignatureVerification.Failure> invalidInbandSignatures,
@ -54,7 +55,7 @@ public class OpenPgpMetadata {
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
this.decryptionKey = decryptionKey;
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
this.sessionKey = sessionKey;
this.compressionAlgorithm = algorithm;
this.verifiedInbandSignatures = Collections.unmodifiableList(verifiedInbandSignatures);
this.invalidInbandSignatures = Collections.unmodifiableList(invalidInbandSignatures);
@ -80,7 +81,7 @@ public class OpenPgpMetadata {
* @return true if encrypted, false otherwise
*/
public boolean isEncrypted() {
return symmetricKeyAlgorithm != SymmetricKeyAlgorithm.NULL && !getRecipientKeyIds().isEmpty();
return sessionKey != null && sessionKey.getAlgorithm() != SymmetricKeyAlgorithm.NULL && !getRecipientKeyIds().isEmpty();
}
/**
@ -100,7 +101,11 @@ public class OpenPgpMetadata {
* @return encryption algorithm
*/
public @Nullable SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() {
return symmetricKeyAlgorithm;
return sessionKey == null ? null : sessionKey.getAlgorithm();
}
public @Nullable SessionKey getSessionKey() {
return sessionKey;
}
/**
@ -271,8 +276,8 @@ public class OpenPgpMetadata {
public static class Builder {
private final Set<Long> recipientFingerprints = new HashSet<>();
private SessionKey sessionKey;
private SubkeyIdentifier decryptionKey;
private SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.NULL;
private CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.UNCOMPRESSED;
private String fileName;
private StreamEncoding fileEncoding;
@ -294,13 +299,13 @@ public class OpenPgpMetadata {
return this;
}
public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) {
this.compressionAlgorithm = algorithm;
public Builder setSessionKey(SessionKey sessionKey) {
this.sessionKey = sessionKey;
return this;
}
public Builder setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) {
this.compressionAlgorithm = algorithm;
return this;
}
@ -322,7 +327,7 @@ public class OpenPgpMetadata {
public OpenPgpMetadata build() {
return new OpenPgpMetadata(
recipientFingerprints, decryptionKey,
symmetricKeyAlgorithm, compressionAlgorithm,
sessionKey, compressionAlgorithm,
verifiedInbandSignatures, invalidInbandSignatures,
verifiedDetachedSignatures, invalidDetachedSignatures,
fileName, modificationDate, fileEncoding);

View file

@ -14,7 +14,6 @@ import org.bouncycastle.openpgp.PGPSignatureList;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.StreamEncoding;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.ConsumerOptions;
import org.pgpainless.decryption_verification.DecryptionStream;
import org.pgpainless.decryption_verification.OpenPgpMetadata;
@ -58,7 +57,6 @@ public class CleartextSignatureProcessor {
public DecryptionStream getVerificationStream() throws IOException, PGPException {
OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
.setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm.NULL)
.setFileEncoding(StreamEncoding.TEXT);
MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy();

View file

@ -14,6 +14,7 @@ import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
@ -25,6 +26,7 @@ import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator;
@ -38,6 +40,7 @@ import org.bouncycastle.openpgp.operator.bc.BcPGPKeyConverter;
import org.bouncycastle.openpgp.operator.bc.BcPGPKeyPair;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
@ -137,6 +140,11 @@ public class BcImplementationFactory extends ImplementationFactory {
.build(passphrase.getChars());
}
@Override
public SessionKeyDataDecryptorFactory provideSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) {
return new BcSessionKeyDataDecryptorFactory(sessionKey);
}
private AsymmetricCipherKeyPair jceToBcKeyPair(PublicKeyAlgorithm algorithm,
KeyPair keyPair,
Date creationDate) throws PGPException {

View file

@ -12,6 +12,7 @@ import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
@ -24,6 +25,7 @@ import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
@ -103,6 +105,8 @@ public abstract class ImplementationFactory {
HashAlgorithm hashAlgorithm, int s2kCount,
Passphrase passphrase) throws PGPException;
public abstract SessionKeyDataDecryptorFactory provideSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey);
@Override
public String toString() {
return getClass().getSimpleName();

View file

@ -12,6 +12,7 @@ import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
@ -24,6 +25,7 @@ import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
@ -36,6 +38,7 @@ import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.jcajce.JceSessionKeyDataDecryptorFactoryBuilder;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
@ -124,4 +127,9 @@ public class JceImplementationFactory extends ImplementationFactory {
.setProvider(ProviderFactory.getProvider())
.build(passphrase.getChars());
}
@Override
public SessionKeyDataDecryptorFactory provideSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) {
return new JceSessionKeyDataDecryptorFactoryBuilder().build(sessionKey);
}
}

View file

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.util;
import javax.annotation.Nonnull;
import org.bouncycastle.openpgp.PGPSessionKey;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
public class SessionKey {
private SymmetricKeyAlgorithm algorithm;
private byte[] key;
public SessionKey(@Nonnull PGPSessionKey sessionKey) {
this(SymmetricKeyAlgorithm.fromId(sessionKey.getAlgorithm()), sessionKey.getKey());
}
public SessionKey(@Nonnull SymmetricKeyAlgorithm algorithm, @Nonnull byte[] key) {
this.algorithm = algorithm;
this.key = key;
}
public SymmetricKeyAlgorithm getAlgorithm() {
return algorithm;
}
public byte[] getKey() {
byte[] copy = new byte[key.length];
System.arraycopy(key, 0, copy, 0, copy.length);
return copy;
}
}