mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-26 06:12:06 +01:00
Implement decryption with - and access of session keys
This commit is contained in:
parent
03f13ee4a7
commit
c55fd2e552
12 changed files with 334 additions and 37 deletions
|
@ -24,10 +24,10 @@ import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy;
|
import org.pgpainless.decryption_verification.cleartext_signatures.InMemoryMultiPassStrategy;
|
||||||
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
|
import org.pgpainless.decryption_verification.cleartext_signatures.MultiPassStrategy;
|
||||||
import org.pgpainless.exception.NotYetImplementedException;
|
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.signature.SignatureUtils;
|
import org.pgpainless.signature.SignatureUtils;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
import org.pgpainless.util.SessionKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for decryption and signature verification.
|
* Options for decryption and signature verification.
|
||||||
|
@ -46,7 +46,7 @@ public class ConsumerOptions {
|
||||||
private MissingPublicKeyCallback missingCertificateCallback = null;
|
private MissingPublicKeyCallback missingCertificateCallback = null;
|
||||||
|
|
||||||
// Session key for decryption without passphrase/key
|
// 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 Map<PGPSecretKeyRing, SecretKeyRingProtector> decryptionKeys = new HashMap<>();
|
||||||
private final Set<Passphrase> decryptionPassphrases = new HashSet<>();
|
private final Set<Passphrase> decryptionPassphrases = new HashSet<>();
|
||||||
|
@ -162,16 +162,15 @@ public class ConsumerOptions {
|
||||||
* Attempt decryption using a session key.
|
* Attempt decryption using a session key.
|
||||||
*
|
*
|
||||||
* Note: PGPainless does not yet support decryption with session keys.
|
* 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>
|
* @see <a href="https://datatracker.ietf.org/doc/html/rfc4880#section-2.1">RFC4880 on Session Keys</a>
|
||||||
*
|
*
|
||||||
* @param sessionKey session key
|
* @param sessionKey session key
|
||||||
* @return options
|
* @return options
|
||||||
*/
|
*/
|
||||||
public ConsumerOptions setSessionKey(@Nonnull byte[] sessionKey) {
|
public ConsumerOptions setSessionKey(@Nonnull SessionKey sessionKey) {
|
||||||
this.sessionKey = sessionKey;
|
this.sessionKey = sessionKey;
|
||||||
throw new NotYetImplementedException();
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -179,14 +178,8 @@ public class ConsumerOptions {
|
||||||
*
|
*
|
||||||
* @return session key or null
|
* @return session key or null
|
||||||
*/
|
*/
|
||||||
public @Nullable byte[] getSessionKey() {
|
public @Nullable SessionKey getSessionKey() {
|
||||||
if (sessionKey == null) {
|
return sessionKey;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] sk = new byte[sessionKey.length];
|
|
||||||
System.arraycopy(sessionKey, 0, sk, 0, sessionKey.length);
|
|
||||||
return sk;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -34,12 +34,14 @@ import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
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.PGPSessionKey;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
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;
|
||||||
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
|
import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider;
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||||
|
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
import org.pgpainless.algorithm.EncryptionPurpose;
|
import org.pgpainless.algorithm.EncryptionPurpose;
|
||||||
|
@ -63,6 +65,7 @@ import org.pgpainless.signature.SignatureUtils;
|
||||||
import org.pgpainless.util.CRCingArmoredInputStreamWrapper;
|
import org.pgpainless.util.CRCingArmoredInputStreamWrapper;
|
||||||
import org.pgpainless.util.PGPUtilWrapper;
|
import org.pgpainless.util.PGPUtilWrapper;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
import org.pgpainless.util.SessionKey;
|
||||||
import org.pgpainless.util.Tuple;
|
import org.pgpainless.util.Tuple;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -210,12 +213,53 @@ public final class DecryptionStreamFactory {
|
||||||
private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
|
private InputStream processPGPEncryptedDataList(PGPEncryptedDataList pgpEncryptedDataList, int depth)
|
||||||
throws PGPException, IOException {
|
throws PGPException, IOException {
|
||||||
LOGGER.debug("Depth {}: Encountered PGPEncryptedDataList", depth);
|
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 decryptedDataStream = decryptSessionKey(pgpEncryptedDataList);
|
||||||
InputStream decodedDataStream = PGPUtil.getDecoderStream(decryptedDataStream);
|
InputStream decodedDataStream = PGPUtil.getDecoderStream(decryptedDataStream);
|
||||||
PGPObjectFactory factory = new PGPObjectFactory(decodedDataStream, keyFingerprintCalculator);
|
PGPObjectFactory factory = new PGPObjectFactory(decodedDataStream, keyFingerprintCalculator);
|
||||||
return processPGPPackets(factory, ++depth);
|
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)
|
private InputStream processPGPCompressedData(PGPCompressedData pgpCompressedData, int depth)
|
||||||
throws PGPException, IOException {
|
throws PGPException, IOException {
|
||||||
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.fromId(pgpCompressedData.getAlgorithm());
|
CompressionAlgorithm compressionAlgorithm = CompressionAlgorithm.fromId(pgpCompressedData.getAlgorithm());
|
||||||
|
@ -294,10 +338,11 @@ public final class DecryptionStreamFactory {
|
||||||
try {
|
try {
|
||||||
InputStream decryptedDataStream = pbeEncryptedData.getDataStream(passphraseDecryptor);
|
InputStream decryptedDataStream = pbeEncryptedData.getDataStream(passphraseDecryptor);
|
||||||
|
|
||||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm.fromId(
|
PGPSessionKey pgpSessionKey = pbeEncryptedData.getSessionKey(passphraseDecryptor);
|
||||||
pbeEncryptedData.getSymmetricAlgorithm(passphraseDecryptor));
|
SessionKey sessionKey = new SessionKey(pgpSessionKey);
|
||||||
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
|
resultBuilder.setSessionKey(sessionKey);
|
||||||
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
|
|
||||||
|
throwIfAlgorithmIsRejected(sessionKey.getAlgorithm());
|
||||||
|
|
||||||
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData, options);
|
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(decryptedDataStream, pbeEncryptedData, options);
|
||||||
|
|
||||||
|
@ -454,15 +499,17 @@ public final class DecryptionStreamFactory {
|
||||||
PublicKeyDataDecryptorFactory dataDecryptor = ImplementationFactory.getInstance()
|
PublicKeyDataDecryptorFactory dataDecryptor = ImplementationFactory.getInstance()
|
||||||
.getPublicKeyDataDecryptorFactory(decryptionKey);
|
.getPublicKeyDataDecryptorFactory(decryptionKey);
|
||||||
|
|
||||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm = SymmetricKeyAlgorithm
|
PGPSessionKey pgpSessionKey = encryptedSessionKey.getSessionKey(dataDecryptor);
|
||||||
.fromId(encryptedSessionKey.getSymmetricAlgorithm(dataDecryptor));
|
SessionKey sessionKey = new SessionKey(pgpSessionKey);
|
||||||
|
resultBuilder.setSessionKey(sessionKey);
|
||||||
|
|
||||||
|
SymmetricKeyAlgorithm symmetricKeyAlgorithm = sessionKey.getAlgorithm();
|
||||||
if (symmetricKeyAlgorithm == SymmetricKeyAlgorithm.NULL) {
|
if (symmetricKeyAlgorithm == SymmetricKeyAlgorithm.NULL) {
|
||||||
LOGGER.debug("Message is unencrypted");
|
LOGGER.debug("Message is unencrypted");
|
||||||
} else {
|
} else {
|
||||||
LOGGER.debug("Message is encrypted using {}", symmetricKeyAlgorithm);
|
LOGGER.debug("Message is encrypted using {}", symmetricKeyAlgorithm);
|
||||||
}
|
}
|
||||||
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
|
throwIfAlgorithmIsRejected(symmetricKeyAlgorithm);
|
||||||
resultBuilder.setSymmetricKeyAlgorithm(symmetricKeyAlgorithm);
|
|
||||||
|
|
||||||
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey, options);
|
integrityProtectedEncryptedInputStream = new IntegrityProtectedInputStream(encryptedSessionKey.getDataStream(dataDecryptor), encryptedSessionKey, options);
|
||||||
return integrityProtectedEncryptedInputStream;
|
return integrityProtectedEncryptedInputStream;
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.exception.SignatureValidationException;
|
import org.pgpainless.exception.SignatureValidationException;
|
||||||
import org.pgpainless.key.OpenPgpFingerprint;
|
import org.pgpainless.key.OpenPgpFingerprint;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
|
import org.pgpainless.util.SessionKey;
|
||||||
|
|
||||||
public class OpenPgpMetadata {
|
public class OpenPgpMetadata {
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ public class OpenPgpMetadata {
|
||||||
private final List<SignatureVerification.Failure> invalidInbandSignatures;
|
private final List<SignatureVerification.Failure> invalidInbandSignatures;
|
||||||
private final List<SignatureVerification> verifiedDetachedSignatures;
|
private final List<SignatureVerification> verifiedDetachedSignatures;
|
||||||
private final List<SignatureVerification.Failure> invalidDetachedSignatures;
|
private final List<SignatureVerification.Failure> invalidDetachedSignatures;
|
||||||
private final SymmetricKeyAlgorithm symmetricKeyAlgorithm;
|
private final SessionKey sessionKey;
|
||||||
private final CompressionAlgorithm compressionAlgorithm;
|
private final CompressionAlgorithm compressionAlgorithm;
|
||||||
private final String fileName;
|
private final String fileName;
|
||||||
private final Date modificationDate;
|
private final Date modificationDate;
|
||||||
|
@ -42,7 +43,7 @@ public class OpenPgpMetadata {
|
||||||
|
|
||||||
public OpenPgpMetadata(Set<Long> recipientKeyIds,
|
public OpenPgpMetadata(Set<Long> recipientKeyIds,
|
||||||
SubkeyIdentifier decryptionKey,
|
SubkeyIdentifier decryptionKey,
|
||||||
SymmetricKeyAlgorithm symmetricKeyAlgorithm,
|
SessionKey sessionKey,
|
||||||
CompressionAlgorithm algorithm,
|
CompressionAlgorithm algorithm,
|
||||||
List<SignatureVerification> verifiedInbandSignatures,
|
List<SignatureVerification> verifiedInbandSignatures,
|
||||||
List<SignatureVerification.Failure> invalidInbandSignatures,
|
List<SignatureVerification.Failure> invalidInbandSignatures,
|
||||||
|
@ -54,7 +55,7 @@ public class OpenPgpMetadata {
|
||||||
|
|
||||||
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
|
this.recipientKeyIds = Collections.unmodifiableSet(recipientKeyIds);
|
||||||
this.decryptionKey = decryptionKey;
|
this.decryptionKey = decryptionKey;
|
||||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
this.sessionKey = sessionKey;
|
||||||
this.compressionAlgorithm = algorithm;
|
this.compressionAlgorithm = algorithm;
|
||||||
this.verifiedInbandSignatures = Collections.unmodifiableList(verifiedInbandSignatures);
|
this.verifiedInbandSignatures = Collections.unmodifiableList(verifiedInbandSignatures);
|
||||||
this.invalidInbandSignatures = Collections.unmodifiableList(invalidInbandSignatures);
|
this.invalidInbandSignatures = Collections.unmodifiableList(invalidInbandSignatures);
|
||||||
|
@ -80,7 +81,7 @@ public class OpenPgpMetadata {
|
||||||
* @return true if encrypted, false otherwise
|
* @return true if encrypted, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isEncrypted() {
|
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
|
* @return encryption algorithm
|
||||||
*/
|
*/
|
||||||
public @Nullable SymmetricKeyAlgorithm getSymmetricKeyAlgorithm() {
|
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 {
|
public static class Builder {
|
||||||
|
|
||||||
private final Set<Long> recipientFingerprints = new HashSet<>();
|
private final Set<Long> recipientFingerprints = new HashSet<>();
|
||||||
|
private SessionKey sessionKey;
|
||||||
private SubkeyIdentifier decryptionKey;
|
private SubkeyIdentifier decryptionKey;
|
||||||
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;
|
||||||
|
@ -294,13 +299,13 @@ public class OpenPgpMetadata {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) {
|
public Builder setSessionKey(SessionKey sessionKey) {
|
||||||
this.compressionAlgorithm = algorithm;
|
this.sessionKey = sessionKey;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm symmetricKeyAlgorithm) {
|
public Builder setCompressionAlgorithm(CompressionAlgorithm algorithm) {
|
||||||
this.symmetricKeyAlgorithm = symmetricKeyAlgorithm;
|
this.compressionAlgorithm = algorithm;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,7 +327,7 @@ public class OpenPgpMetadata {
|
||||||
public OpenPgpMetadata build() {
|
public OpenPgpMetadata build() {
|
||||||
return new OpenPgpMetadata(
|
return new OpenPgpMetadata(
|
||||||
recipientFingerprints, decryptionKey,
|
recipientFingerprints, decryptionKey,
|
||||||
symmetricKeyAlgorithm, compressionAlgorithm,
|
sessionKey, compressionAlgorithm,
|
||||||
verifiedInbandSignatures, invalidInbandSignatures,
|
verifiedInbandSignatures, invalidInbandSignatures,
|
||||||
verifiedDetachedSignatures, invalidDetachedSignatures,
|
verifiedDetachedSignatures, invalidDetachedSignatures,
|
||||||
fileName, modificationDate, fileEncoding);
|
fileName, modificationDate, fileEncoding);
|
||||||
|
|
|
@ -14,7 +14,6 @@ import org.bouncycastle.openpgp.PGPSignatureList;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
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.decryption_verification.ConsumerOptions;
|
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
|
@ -58,7 +57,6 @@ public class CleartextSignatureProcessor {
|
||||||
public DecryptionStream getVerificationStream() throws IOException, PGPException {
|
public DecryptionStream getVerificationStream() throws IOException, PGPException {
|
||||||
OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
OpenPgpMetadata.Builder resultBuilder = OpenPgpMetadata.getBuilder();
|
||||||
resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
|
resultBuilder.setCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)
|
||||||
.setSymmetricKeyAlgorithm(SymmetricKeyAlgorithm.NULL)
|
|
||||||
.setFileEncoding(StreamEncoding.TEXT);
|
.setFileEncoding(StreamEncoding.TEXT);
|
||||||
|
|
||||||
MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy();
|
MultiPassStrategy multiPassStrategy = options.getMultiPassStrategy();
|
||||||
|
|
|
@ -14,6 +14,7 @@ import org.bouncycastle.openpgp.PGPKeyPair;
|
||||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSessionKey;
|
||||||
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
||||||
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
|
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.PGPDigestCalculator;
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
|
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.BcKeyFingerprintCalculator;
|
||||||
import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.bc.BcPBEKeyEncryptionMethodGenerator;
|
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.BcPGPKeyPair;
|
||||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
|
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
|
||||||
|
import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
|
@ -137,6 +140,11 @@ public class BcImplementationFactory extends ImplementationFactory {
|
||||||
.build(passphrase.getChars());
|
.build(passphrase.getChars());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SessionKeyDataDecryptorFactory provideSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) {
|
||||||
|
return new BcSessionKeyDataDecryptorFactory(sessionKey);
|
||||||
|
}
|
||||||
|
|
||||||
private AsymmetricCipherKeyPair jceToBcKeyPair(PublicKeyAlgorithm algorithm,
|
private AsymmetricCipherKeyPair jceToBcKeyPair(PublicKeyAlgorithm algorithm,
|
||||||
KeyPair keyPair,
|
KeyPair keyPair,
|
||||||
Date creationDate) throws PGPException {
|
Date creationDate) throws PGPException {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.bouncycastle.openpgp.PGPKeyPair;
|
||||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSessionKey;
|
||||||
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
||||||
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
|
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.PGPDigestCalculatorProvider;
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
|
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
|
||||||
|
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
|
@ -103,6 +105,8 @@ public abstract class ImplementationFactory {
|
||||||
HashAlgorithm hashAlgorithm, int s2kCount,
|
HashAlgorithm hashAlgorithm, int s2kCount,
|
||||||
Passphrase passphrase) throws PGPException;
|
Passphrase passphrase) throws PGPException;
|
||||||
|
|
||||||
|
public abstract SessionKeyDataDecryptorFactory provideSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getClass().getSimpleName();
|
return getClass().getSimpleName();
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.bouncycastle.openpgp.PGPKeyPair;
|
||||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
|
import org.bouncycastle.openpgp.PGPSessionKey;
|
||||||
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
|
||||||
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.PBEKeyEncryptionMethodGenerator;
|
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.PGPDigestCalculatorProvider;
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
|
||||||
import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
|
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.JcaKeyFingerprintCalculator;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
|
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.JcePGPDataEncryptorBuilder;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
|
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
|
||||||
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
|
||||||
|
import org.bouncycastle.openpgp.operator.jcajce.JceSessionKeyDataDecryptorFactoryBuilder;
|
||||||
import org.pgpainless.algorithm.HashAlgorithm;
|
import org.pgpainless.algorithm.HashAlgorithm;
|
||||||
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
|
@ -124,4 +127,9 @@ public class JceImplementationFactory extends ImplementationFactory {
|
||||||
.setProvider(ProviderFactory.getProvider())
|
.setProvider(ProviderFactory.getProvider())
|
||||||
.build(passphrase.getChars());
|
.build(passphrase.getChars());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SessionKeyDataDecryptorFactory provideSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey) {
|
||||||
|
return new JceSessionKeyDataDecryptorFactoryBuilder().build(sessionKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||||
import org.pgpainless.decryption_verification.ConsumerOptions;
|
import org.pgpainless.decryption_verification.ConsumerOptions;
|
||||||
import org.pgpainless.decryption_verification.DecryptionStream;
|
import org.pgpainless.decryption_verification.DecryptionStream;
|
||||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||||
|
@ -67,7 +68,11 @@ public class DecryptImpl implements Decrypt {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DecryptImpl withSessionKey(SessionKey sessionKey) throws SOPGPException.UnsupportedOption {
|
public DecryptImpl withSessionKey(SessionKey sessionKey) throws SOPGPException.UnsupportedOption {
|
||||||
throw new SOPGPException.UnsupportedOption("Setting custom session key not supported.");
|
consumerOptions.setSessionKey(
|
||||||
|
new org.pgpainless.util.SessionKey(
|
||||||
|
SymmetricKeyAlgorithm.fromId(sessionKey.getAlgorithm()),
|
||||||
|
sessionKey.getKey()));
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -118,8 +123,8 @@ public class DecryptImpl implements Decrypt {
|
||||||
throws SOPGPException.BadData,
|
throws SOPGPException.BadData,
|
||||||
SOPGPException.MissingArg {
|
SOPGPException.MissingArg {
|
||||||
|
|
||||||
if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty()) {
|
if (consumerOptions.getDecryptionKeys().isEmpty() && consumerOptions.getDecryptionPassphrases().isEmpty() && consumerOptions.getSessionKey() == null) {
|
||||||
throw new SOPGPException.MissingArg("Missing decryption key or passphrase.");
|
throw new SOPGPException.MissingArg("Missing decryption key, passphrase or session key.");
|
||||||
}
|
}
|
||||||
|
|
||||||
DecryptionStream decryptionStream;
|
DecryptionStream decryptionStream;
|
||||||
|
@ -153,7 +158,16 @@ public class DecryptImpl implements Decrypt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DecryptionResult(null, verificationList);
|
SessionKey sessionKey = null;
|
||||||
|
if (metadata.getSessionKey() != null) {
|
||||||
|
org.pgpainless.util.SessionKey sk = metadata.getSessionKey();
|
||||||
|
sessionKey = new SessionKey(
|
||||||
|
(byte) sk.getAlgorithm().getAlgorithmId(),
|
||||||
|
sk.getKey()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DecryptionResult(sessionKey, verificationList);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ package org.pgpainless.sop;
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -18,6 +19,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import sop.ByteArrayAndResult;
|
import sop.ByteArrayAndResult;
|
||||||
import sop.DecryptionResult;
|
import sop.DecryptionResult;
|
||||||
import sop.SOP;
|
import sop.SOP;
|
||||||
|
import sop.SessionKey;
|
||||||
import sop.exception.SOPGPException;
|
import sop.exception.SOPGPException;
|
||||||
|
|
||||||
public class EncryptDecryptRoundTripTest {
|
public class EncryptDecryptRoundTripTest {
|
||||||
|
@ -235,4 +237,165 @@ public class EncryptDecryptRoundTripTest {
|
||||||
assertThrows(SOPGPException.BadData.class, () -> sop.decrypt()
|
assertThrows(SOPGPException.BadData.class, () -> sop.decrypt()
|
||||||
.verifyWithCert(new byte[0]));
|
.verifyWithCert(new byte[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPassphraseDecryptionYieldsSessionKey() throws IOException {
|
||||||
|
byte[] message = "Hello\nWorld\n".getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] ciphertext = ("-----BEGIN PGP MESSAGE-----\n" +
|
||||||
|
"Version: PGPainless\n" +
|
||||||
|
"\n" +
|
||||||
|
"jA0ECQMCdFswArqHpj1g0j4BLDTkZhCC1crZf0EFq1xPIMUtnyRmfJJ7IzsdMJ5Y\n" +
|
||||||
|
"EhKbBc2h6wIX7B/GxUbyNj1xh5JRzt2ZX8KL2d6HAQ==\n" +
|
||||||
|
"=zZ0/\n" +
|
||||||
|
"-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8);
|
||||||
|
String passphrase = "sw0rdf1sh";
|
||||||
|
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt().withPassword(passphrase).ciphertext(ciphertext).toByteArrayAndResult();
|
||||||
|
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||||
|
assertTrue(bytesAndResult.getResult().getSessionKey().isPresent());
|
||||||
|
assertEquals("9:7BCB7383D23E20D4BA8980B26D6C0813769056546C45B7E55F4612BFAD5B4B1C", bytesAndResult.getResult().getSessionKey().get().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPublicKeyDecryptionYieldsSessionKey() throws IOException {
|
||||||
|
byte[] key = ("-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||||
|
"Version: PGPainless\n" +
|
||||||
|
"Comment: D94A AA9C 5F73 48B2 5D81 72E7 F20C F71E 93FE 897F\n" +
|
||||||
|
"Comment: Alice\n" +
|
||||||
|
"\n" +
|
||||||
|
"lFgEYWlyaRYJKwYBBAHaRw8BAQdAJsJfjByLE+8HNVGbEKiIbSGXBYwR6L61bT5E\n" +
|
||||||
|
"Hhu642kAAP49D4TOaI+Z3G5ko4C4D1bOzLajLRpIuPLuwYHpF1xD0RHmtAVBbGlj\n" +
|
||||||
|
"ZYh4BBMWCgAgBQJhaXJpAhsBBRYCAwEABRUKCQgLBAsJCAcCHgECGQEACgkQ8gz3\n" +
|
||||||
|
"HpP+iX/c8AD9Hx0PUu97n8ZlrpuA6YuJL3rONPQnaXMz9eE+KHxJS6sBAM06X8Wm\n" +
|
||||||
|
"XRGUVURsoerwYTbUnXcUnqH/U/JhwlUerJAInF0EYWlyaRIKKwYBBAGXVQEFAQEH\n" +
|
||||||
|
"QJOHyxI5K8ZqX+v/AmTLHAIjWd8wHO8eGld4KHniCFx9AwEIBwAA/0zVZYYWsr3w\n" +
|
||||||
|
"GKkmqfIZlB+wIeJlWrho87kvXiNAe0LIEIGIdQQYFgoAHQUCYWlyaQIbDAUWAgMB\n" +
|
||||||
|
"AAUVCgkICwQLCQgHAh4BAAoJEPIM9x6T/ol/vggA/ilxi5UTjDYDR7sGrYyaGPRK\n" +
|
||||||
|
"Sg0KNn2SV4c5M5ZmZR7sAP4kKz6kQ4UtYmSmUmMBu+A3mMTN8VQY+6LSTdekvU0N\n" +
|
||||||
|
"ApxYBGFpcmkWCSsGAQQB2kcPAQEHQJiiZENQ52jyt8wBwX7fD1vQkvgTg5T3v1S1\n" +
|
||||||
|
"yzr1yI0RAAD+KOTcMdv8rz3U6K42PNE4b983KoMfyQ/hgjIWOi2BYBwP94jVBBgW\n" +
|
||||||
|
"CgB9BQJhaXJpAhsCBRYCAwEABRUKCQgLBAsJCAcCHgFfIAQZFgoABgUCYWlyaQAK\n" +
|
||||||
|
"CRDP7lemqmadIYLuAP9oAm+OFzyMNrmWRcvdHqH/DAfJTM2+ZmANSm44geZDEAD9\n" +
|
||||||
|
"HfeCHev1H1H1wOd0S3tW9gZwonrYFoqOBW/YTmf5XwYACgkQ8gz3HpP+iX+veQEA\n" +
|
||||||
|
"sWC+xDo+lc6oJr4q0mTJkxzYfgUBtQ0VjUWNcGyOdegBAL8hMzb9+e4wbP2F0tMb\n" +
|
||||||
|
"ZFA2MgHsvqGhXyAXi50arZYF\n" +
|
||||||
|
"=k66N\n" +
|
||||||
|
"-----END PGP PRIVATE KEY BLOCK-----\n").getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] message = "Hello\nWorld\n".getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] ciphertext = ("-----BEGIN PGP MESSAGE-----\n" +
|
||||||
|
"Version: PGPainless\n" +
|
||||||
|
"\n" +
|
||||||
|
"hF4DrJ3c2YF1IKUSAQdA9VL6OwIwOwB4GnE4yR5JJ5OjcC76WTpdm85I6WHvhD4w\n" +
|
||||||
|
"hqHpf6UGaDDQ7xAcSd7YnEGVMBOOBnJfD1PRuNWE5hwgqqsqpMDrvvMHjUsg3HNH\n" +
|
||||||
|
"0j4BriMU8XQ6MLdvCaFmeQqFwBD4mlI/x32wj0I9VyBIKysopA8HNV4ES2rOhGuW\n" +
|
||||||
|
"T/zFmI9Tm9eWvNwv0LUNhQ==\n" +
|
||||||
|
"=4Z+m\n" +
|
||||||
|
"-----END PGP MESSAGE-----\n").getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt().withKey(key).ciphertext(ciphertext).toByteArrayAndResult();
|
||||||
|
DecryptionResult result = bytesAndResult.getResult();
|
||||||
|
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||||
|
assertTrue(result.getSessionKey().isPresent());
|
||||||
|
assertEquals("9:63F741E7FB60247BE59C64158573308F727236482DB7653908C95839E4166AAE", result.getSessionKey().get().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecryptionWithSessionKey() throws IOException {
|
||||||
|
byte[] message = "Hello\nWorld\n".getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] ciphertext = ("-----BEGIN PGP MESSAGE-----\n" +
|
||||||
|
"Version: PGPainless\n" +
|
||||||
|
"\n" +
|
||||||
|
"hF4DrJ3c2YF1IKUSAQdA9VL6OwIwOwB4GnE4yR5JJ5OjcC76WTpdm85I6WHvhD4w\n" +
|
||||||
|
"hqHpf6UGaDDQ7xAcSd7YnEGVMBOOBnJfD1PRuNWE5hwgqqsqpMDrvvMHjUsg3HNH\n" +
|
||||||
|
"0j4BriMU8XQ6MLdvCaFmeQqFwBD4mlI/x32wj0I9VyBIKysopA8HNV4ES2rOhGuW\n" +
|
||||||
|
"T/zFmI9Tm9eWvNwv0LUNhQ==\n" +
|
||||||
|
"=4Z+m\n" +
|
||||||
|
"-----END PGP MESSAGE-----\n").getBytes(StandardCharsets.UTF_8);
|
||||||
|
SessionKey sessionKey = SessionKey.fromString("9:63F741E7FB60247BE59C64158573308F727236482DB7653908C95839E4166AAE");
|
||||||
|
|
||||||
|
ByteArrayAndResult<DecryptionResult> bytesAndResult = sop.decrypt().withSessionKey(sessionKey)
|
||||||
|
.ciphertext(ciphertext)
|
||||||
|
.toByteArrayAndResult();
|
||||||
|
|
||||||
|
DecryptionResult result = bytesAndResult.getResult();
|
||||||
|
assertTrue(result.getSessionKey().isPresent());
|
||||||
|
assertEquals(sessionKey, result.getSessionKey().get());
|
||||||
|
|
||||||
|
assertArrayEquals(message, bytesAndResult.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecryptionWithSessionKey_VerificationWithCert() throws IOException {
|
||||||
|
byte[] plaintext = "This is a test message.\nSit back and relax.\n".getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] key = ("-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
||||||
|
"Version: PGPainless\n" +
|
||||||
|
"Comment: 9C26 EFAB 1C65 00A2 28E8 A9C2 658E E420 C824 D191\n" +
|
||||||
|
"Comment: Alice\n" +
|
||||||
|
"\n" +
|
||||||
|
"lFgEYWl4ixYJKwYBBAHaRw8BAQdAv6+cd8R/ICS/z9hlT99g++wyquxVsO0FCb8F\n" +
|
||||||
|
"MSkTplUAAP9gPoBi8fxdfLaEyt6GWeIBTeYsVxsogbKzXXnjp3MbiRE/tAVBbGlj\n" +
|
||||||
|
"ZYh4BBMWCgAgBQJhaXiLAhsBBRYCAwEABRUKCQgLBAsJCAcCHgECGQEACgkQZY7k\n" +
|
||||||
|
"IMgk0ZEZuAEA3hWzfqCXGUjlv+miWey1AyWRu9eQvTdE9YqbIMuxIk4BAMtGlo6l\n" +
|
||||||
|
"d3E868q0zLOOktmsBxnzaE7knbd9nAlK3FUJnF0EYWl4ixIKKwYBBAGXVQEFAQEH\n" +
|
||||||
|
"QK8vS3T3Yf3Gpy9iWOTR0jdhV4XgtchcvKCpFMgc5uwFAwEIBwAA/1tNle5cT9kS\n" +
|
||||||
|
"8yzNxL16ElEREtEX+5kpkt6JZyTx0xfAEPGIdQQYFgoAHQUCYWl4iwIbDAUWAgMB\n" +
|
||||||
|
"AAUVCgkICwQLCQgHAh4BAAoJEGWO5CDIJNGRM80BANJ6EGKIkVNxYj7wOaEqyRh1\n" +
|
||||||
|
"Rtv3tLAnEzLl/b0mZx3WAQDADAPNCl5xnjTt5InyfrwV90kM4vDGcl4mQE8FD7dD\n" +
|
||||||
|
"B5xYBGFpeIsWCSsGAQQB2kcPAQEHQFuEaBKUllw+MfdkkSNE0CncJCeFGCbHvmsc\n" +
|
||||||
|
"Ma/DPgrpAAEAlsoxcTyTFfHxV2CayDCFvBSHYXOSOg6fyMdh0SxzjC0PVIjVBBgW\n" +
|
||||||
|
"CgB9BQJhaXiLAhsCBRYCAwEABRUKCQgLBAsJCAcCHgFfIAQZFgoABgUCYWl4iwAK\n" +
|
||||||
|
"CRBGMq3j1oKUXenjAP974AvBOAVIdNUkVAishoDL7ee7/eAU3Ni7V2Kn47cusQD/\n" +
|
||||||
|
"c8c9phtf2NIL23K4bvBdvsU3opV2DIVJwRutV4v6jgAACgkQZY7kIMgk0ZG1dwEA\n" +
|
||||||
|
"sFp1AuPcn3dGF05D6ohlqunoBwBWEcwZLjx+v5X27R8A/17V5nzC+eny3XjCF8Ib\n" +
|
||||||
|
"qw1VTfR84stki65Xhm2lxFAN\n" +
|
||||||
|
"=TQO7\n" +
|
||||||
|
"-----END PGP PRIVATE KEY BLOCK-----\n").getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] cert = sop.extractCert().key(key).getBytes();
|
||||||
|
byte[] ciphertext = ("-----BEGIN PGP MESSAGE-----\n" +
|
||||||
|
"Version: PGPainless\n" +
|
||||||
|
"\n" +
|
||||||
|
"hF4DSjXDMRql2RASAQdAkhJyA9GX5ios8PNlti7v7BieggiiR9trqrKFQwomU2Aw\n" +
|
||||||
|
"elEFuDA3ugJO2rNiyQQH1riFFJuod6BiQuxFhdf/mmsFFDzHmJeUOx9pQeNemzST\n" +
|
||||||
|
"0sAdAQQYC+iXUNn2y15kTqbFQFgfOWObgsqspGY04V17fZdVI7bEORLM+YT6KoZA\n" +
|
||||||
|
"uq2WO49ze9jp2jdvTsjjNNseZDhmxtgOCfi1/Fi3IHPnBJW7M3UWaJCSLozWkO95\n" +
|
||||||
|
"FztCSWL22jDGPGIjgQ589hYW+WuJMvMv6ltTOo+l70S5dHSObijbcOqfNSmrxlpw\n" +
|
||||||
|
"hqZfkU0BA01I9Pf3lBPCNyMbCPZP0oaIiWACnm6svWp4oH5u5ClhS9BVJTptzwXv\n" +
|
||||||
|
"mMj+lTi5ahGQJ3Nr8krloTSsjpkssz6D2+FDnvjwu6E=\n" +
|
||||||
|
"=BYOB\n" +
|
||||||
|
"-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8);
|
||||||
|
String sessionKey = "9:87C0870598AD908ABEECCAE265DCEEA146CF557AAF698D097024404A00EBD072";
|
||||||
|
|
||||||
|
// Decrypt with public key
|
||||||
|
ByteArrayAndResult<DecryptionResult> bytesAndResult =
|
||||||
|
sop.decrypt().withKey(key).verifyWithCert(cert).ciphertext(ciphertext).toByteArrayAndResult();
|
||||||
|
assertEquals(sessionKey, bytesAndResult.getResult().getSessionKey().get().toString());
|
||||||
|
assertArrayEquals(plaintext, bytesAndResult.getBytes());
|
||||||
|
assertEquals(1, bytesAndResult.getResult().getVerifications().size());
|
||||||
|
|
||||||
|
// Decrypt with session key
|
||||||
|
bytesAndResult = sop.decrypt().withSessionKey(SessionKey.fromString(sessionKey))
|
||||||
|
.verifyWithCert(cert).ciphertext(ciphertext).toByteArrayAndResult();
|
||||||
|
assertEquals(sessionKey, bytesAndResult.getResult().getSessionKey().get().toString());
|
||||||
|
assertArrayEquals(plaintext, bytesAndResult.getBytes());
|
||||||
|
assertEquals(1, bytesAndResult.getResult().getVerifications().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decryptWithWrongSessionKey() {
|
||||||
|
byte[] ciphertext = ("-----BEGIN PGP MESSAGE-----\n" +
|
||||||
|
"Version: PGPainless\n" +
|
||||||
|
"\n" +
|
||||||
|
"hF4DSjXDMRql2RASAQdAkhJyA9GX5ios8PNlti7v7BieggiiR9trqrKFQwomU2Aw\n" +
|
||||||
|
"elEFuDA3ugJO2rNiyQQH1riFFJuod6BiQuxFhdf/mmsFFDzHmJeUOx9pQeNemzST\n" +
|
||||||
|
"0sAdAQQYC+iXUNn2y15kTqbFQFgfOWObgsqspGY04V17fZdVI7bEORLM+YT6KoZA\n" +
|
||||||
|
"uq2WO49ze9jp2jdvTsjjNNseZDhmxtgOCfi1/Fi3IHPnBJW7M3UWaJCSLozWkO95\n" +
|
||||||
|
"FztCSWL22jDGPGIjgQ589hYW+WuJMvMv6ltTOo+l70S5dHSObijbcOqfNSmrxlpw\n" +
|
||||||
|
"hqZfkU0BA01I9Pf3lBPCNyMbCPZP0oaIiWACnm6svWp4oH5u5ClhS9BVJTptzwXv\n" +
|
||||||
|
"mMj+lTi5ahGQJ3Nr8krloTSsjpkssz6D2+FDnvjwu6E=\n" +
|
||||||
|
"=BYOB\n" +
|
||||||
|
"-----END PGP MESSAGE-----").getBytes(StandardCharsets.UTF_8);
|
||||||
|
SessionKey wrongSessionKey = SessionKey.fromString("9:63F741E7FB60247BE59C64158573308F727236482DB7653908C95839E4166AAE");
|
||||||
|
|
||||||
|
assertThrows(SOPGPException.BadData.class, () ->
|
||||||
|
sop.decrypt().withSessionKey(wrongSessionKey).ciphertext(ciphertext));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,15 @@
|
||||||
package sop;
|
package sop;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import sop.util.HexUtil;
|
import sop.util.HexUtil;
|
||||||
|
|
||||||
public class SessionKey {
|
public class SessionKey {
|
||||||
|
|
||||||
|
private static final Pattern PATTERN = Pattern.compile("^(\\d):([0-9a-fA-F]+)$");
|
||||||
|
|
||||||
private final byte algorithm;
|
private final byte algorithm;
|
||||||
private final byte[] sessionKey;
|
private final byte[] sessionKey;
|
||||||
|
|
||||||
|
@ -57,6 +61,17 @@ public class SessionKey {
|
||||||
return getAlgorithm() == otherKey.getAlgorithm() && Arrays.equals(getKey(), otherKey.getKey());
|
return getAlgorithm() == otherKey.getAlgorithm() && Arrays.equals(getKey(), otherKey.getKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SessionKey fromString(String string) {
|
||||||
|
Matcher matcher = PATTERN.matcher(string);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
throw new IllegalArgumentException("Provided session key does not match expected format.");
|
||||||
|
}
|
||||||
|
byte algorithm = Byte.parseByte(matcher.group(1));
|
||||||
|
String key = matcher.group(2);
|
||||||
|
|
||||||
|
return new SessionKey(algorithm, HexUtil.hexToBytes(key));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "" + (int) getAlgorithm() + ':' + HexUtil.bytesToHex(sessionKey);
|
return "" + (int) getAlgorithm() + ':' + HexUtil.bytesToHex(sessionKey);
|
||||||
|
|
|
@ -12,6 +12,13 @@ import sop.SessionKey;
|
||||||
|
|
||||||
public class SessionKeyTest {
|
public class SessionKeyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fromStringTest() {
|
||||||
|
String string = "9:FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD";
|
||||||
|
SessionKey sessionKey = SessionKey.fromString(string);
|
||||||
|
assertEquals(string, sessionKey.toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void toStringTest() {
|
public void toStringTest() {
|
||||||
SessionKey sessionKey = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
|
SessionKey sessionKey = new SessionKey((byte) 9, HexUtil.hexToBytes("FCA4BEAF687F48059CACC14FB019125CD57392BAB7037C707835925CBF9F7BCD"));
|
||||||
|
|
Loading…
Reference in a new issue