mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-09-09 18:29:39 +02:00
Expose encryption mechanism during decryption
This commit is contained in:
parent
3cef99d256
commit
94febc33df
4 changed files with 167 additions and 24 deletions
|
@ -9,6 +9,7 @@ import javax.annotation.Nonnull
|
||||||
import org.bouncycastle.bcpg.KeyIdentifier
|
import org.bouncycastle.bcpg.KeyIdentifier
|
||||||
import org.bouncycastle.openpgp.PGPKeyRing
|
import org.bouncycastle.openpgp.PGPKeyRing
|
||||||
import org.bouncycastle.openpgp.PGPLiteralData
|
import org.bouncycastle.openpgp.PGPLiteralData
|
||||||
|
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
|
||||||
import org.bouncycastle.openpgp.api.OpenPGPCertificate
|
import org.bouncycastle.openpgp.api.OpenPGPCertificate
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm
|
import org.pgpainless.algorithm.CompressionAlgorithm
|
||||||
import org.pgpainless.algorithm.StreamEncoding
|
import org.pgpainless.algorithm.StreamEncoding
|
||||||
|
@ -32,22 +33,34 @@ class MessageMetadata(val message: Message) {
|
||||||
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is
|
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is
|
||||||
* unencrypted.
|
* unencrypted.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Deprecated in favor of encryptionMechanism",
|
||||||
|
replaceWith = ReplaceWith("encryptionMechanism")
|
||||||
|
)
|
||||||
val encryptionAlgorithm: SymmetricKeyAlgorithm?
|
val encryptionAlgorithm: SymmetricKeyAlgorithm?
|
||||||
get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null }
|
get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null }
|
||||||
|
|
||||||
|
val encryptionMechanism: MessageEncryptionMechanism?
|
||||||
|
get() = encryptionMechanisms.let { if (it.hasNext()) it.next() else null }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. The first item
|
* [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. The first item
|
||||||
* returned by the iterator is the algorithm of the outermost encrypted data packet, the next
|
* returned by the iterator is the algorithm of the outermost encrypted data packet, the next
|
||||||
* item that of the next nested encrypted data packet and so on. The iterator might also be
|
* item that of the next nested encrypted data packet and so on. The iterator might also be
|
||||||
* empty, in case of an unencrypted message.
|
* empty, in case of an unencrypted message.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Deprecated in favor of encryptionMechanisms",
|
||||||
|
replaceWith = ReplaceWith("encryptionMechanisms")
|
||||||
|
)
|
||||||
val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm>
|
val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm>
|
||||||
get() = encryptionLayers.asSequence().map { it.algorithm }.iterator()
|
get() = encryptionLayers.asSequence().map { it.algorithm }.iterator()
|
||||||
|
|
||||||
|
val encryptionMechanisms: Iterator<MessageEncryptionMechanism>
|
||||||
|
get() = encryptionLayers.asSequence().map { it.mechanism }.iterator()
|
||||||
|
|
||||||
val isEncrypted: Boolean
|
val isEncrypted: Boolean
|
||||||
get() =
|
get() =
|
||||||
if (encryptionAlgorithm == null) false
|
if (encryptionMechanism == null) false
|
||||||
else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL
|
else encryptionMechanism!!.symmetricKeyAlgorithm != SymmetricKeyAlgorithm.NULL.algorithmId
|
||||||
|
|
||||||
fun isEncryptedFor(cert: OpenPGPCertificate): Boolean {
|
fun isEncryptedFor(cert: OpenPGPCertificate): Boolean {
|
||||||
return encryptionLayers.asSequence().any {
|
return encryptionLayers.asSequence().any {
|
||||||
|
@ -472,7 +485,8 @@ class MessageMetadata(val message: Message) {
|
||||||
* @param algorithm symmetric key algorithm used to encrypt the packet.
|
* @param algorithm symmetric key algorithm used to encrypt the packet.
|
||||||
* @param depth nesting depth at which this packet was encountered.
|
* @param depth nesting depth at which this packet was encountered.
|
||||||
*/
|
*/
|
||||||
class EncryptedData(val algorithm: SymmetricKeyAlgorithm, depth: Int) : Layer(depth), Nested {
|
class EncryptedData(val mechanism: MessageEncryptionMechanism, depth: Int) :
|
||||||
|
Layer(depth), Nested {
|
||||||
|
|
||||||
/** [SessionKey] used to decrypt the packet. */
|
/** [SessionKey] used to decrypt the packet. */
|
||||||
var sessionKey: SessionKey? = null
|
var sessionKey: SessionKey? = null
|
||||||
|
@ -480,6 +494,9 @@ class MessageMetadata(val message: Message) {
|
||||||
/** List of all recipient key ids to which the packet was encrypted for. */
|
/** List of all recipient key ids to which the packet was encrypted for. */
|
||||||
val recipients: List<KeyIdentifier> = mutableListOf()
|
val recipients: List<KeyIdentifier> = mutableListOf()
|
||||||
|
|
||||||
|
val algorithm: SymmetricKeyAlgorithm =
|
||||||
|
SymmetricKeyAlgorithm.requireFromId(mechanism.symmetricKeyAlgorithm)
|
||||||
|
|
||||||
fun addRecipients(keyIds: List<KeyIdentifier>) = apply {
|
fun addRecipients(keyIds: List<KeyIdentifier>) = apply {
|
||||||
(recipients as MutableList).addAll(keyIds)
|
(recipients as MutableList).addAll(keyIds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,11 @@ import java.io.OutputStream
|
||||||
import java.util.zip.Inflater
|
import java.util.zip.Inflater
|
||||||
import java.util.zip.InflaterInputStream
|
import java.util.zip.InflaterInputStream
|
||||||
import openpgp.openPgpKeyId
|
import openpgp.openPgpKeyId
|
||||||
|
import org.bouncycastle.bcpg.AEADEncDataPacket
|
||||||
import org.bouncycastle.bcpg.BCPGInputStream
|
import org.bouncycastle.bcpg.BCPGInputStream
|
||||||
import org.bouncycastle.bcpg.CompressionAlgorithmTags
|
import org.bouncycastle.bcpg.CompressionAlgorithmTags
|
||||||
import org.bouncycastle.bcpg.KeyIdentifier
|
import org.bouncycastle.bcpg.KeyIdentifier
|
||||||
|
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
|
||||||
import org.bouncycastle.bcpg.UnsupportedPacketVersionException
|
import org.bouncycastle.bcpg.UnsupportedPacketVersionException
|
||||||
import org.bouncycastle.openpgp.PGPCompressedData
|
import org.bouncycastle.openpgp.PGPCompressedData
|
||||||
import org.bouncycastle.openpgp.PGPEncryptedData
|
import org.bouncycastle.openpgp.PGPEncryptedData
|
||||||
|
@ -27,6 +29,8 @@ import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData
|
||||||
import org.bouncycastle.openpgp.PGPSessionKey
|
import org.bouncycastle.openpgp.PGPSessionKey
|
||||||
import org.bouncycastle.openpgp.PGPSignature
|
import org.bouncycastle.openpgp.PGPSignature
|
||||||
import org.bouncycastle.openpgp.PGPSignatureException
|
import org.bouncycastle.openpgp.PGPSignatureException
|
||||||
|
import org.bouncycastle.openpgp.api.EncryptedDataPacketType
|
||||||
|
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
|
||||||
import org.bouncycastle.openpgp.api.OpenPGPCertificate
|
import org.bouncycastle.openpgp.api.OpenPGPCertificate
|
||||||
import org.bouncycastle.openpgp.api.OpenPGPKey
|
import org.bouncycastle.openpgp.api.OpenPGPKey
|
||||||
import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey
|
import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey
|
||||||
|
@ -318,20 +322,38 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processEncryptedData(): Boolean {
|
private fun processEncryptedData(): Boolean {
|
||||||
LOGGER.debug(
|
// TODO: Replace by dedicated encryption packet type input symbols
|
||||||
"Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.")
|
|
||||||
syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA)
|
syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA)
|
||||||
|
|
||||||
val encDataList = packetInputStream!!.readEncryptedDataList()
|
val encDataList = packetInputStream!!.readEncryptedDataList()
|
||||||
if (!encDataList.isIntegrityProtected && !encDataList.get(0).isAEAD) {
|
val esks = ESKsAndData(encDataList)
|
||||||
LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.")
|
|
||||||
if (!options.isIgnoreMDCErrors()) {
|
when (EncryptedDataPacketType.of(encDataList)!!) {
|
||||||
throw MessageNotIntegrityProtectedException()
|
EncryptedDataPacketType.SEIPDv2 ->
|
||||||
|
LOGGER.debug(
|
||||||
|
"Symmetrically Encrypted Integrity Protected Data Packet version 2 at depth " +
|
||||||
|
"${layerMetadata.depth} encountered.")
|
||||||
|
EncryptedDataPacketType.SEIPDv1 ->
|
||||||
|
LOGGER.debug(
|
||||||
|
"Symmetrically Encrypted Integrity Protected Data Packet version 1 at depth " +
|
||||||
|
"${layerMetadata.depth} encountered.")
|
||||||
|
EncryptedDataPacketType.LIBREPGP_OED ->
|
||||||
|
LOGGER.debug(
|
||||||
|
"LibrePGP OCB-Encrypted Data Packet at depth " +
|
||||||
|
"${layerMetadata.depth} encountered.")
|
||||||
|
EncryptedDataPacketType.SED -> {
|
||||||
|
LOGGER.debug(
|
||||||
|
"(Deprecated) Symmetrically Encrypted Data Packet at depth " +
|
||||||
|
"${layerMetadata.depth} encountered.")
|
||||||
|
LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.")
|
||||||
|
if (!options.isIgnoreMDCErrors()) {
|
||||||
|
throw MessageNotIntegrityProtectedException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val esks = SortedESKs(encDataList)
|
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
"Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" +
|
"Encrypted Data has ${esks.skesks.size} SKESK(s) and" +
|
||||||
" ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" +
|
" ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" +
|
||||||
" have an anonymous recipient.")
|
" have an anonymous recipient.")
|
||||||
|
|
||||||
|
@ -359,7 +381,7 @@ class OpenPgpMessageInputStream(
|
||||||
|
|
||||||
val pgpSk = PGPSessionKey(sk.algorithm.algorithmId, sk.key)
|
val pgpSk = PGPSessionKey(sk.algorithm.algorithmId, sk.key)
|
||||||
val decryptorFactory = api.implementation.sessionKeyDataDecryptorFactory(pgpSk)
|
val decryptorFactory = api.implementation.sessionKeyDataDecryptorFactory(pgpSk)
|
||||||
val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1)
|
val layer = esks.toEncryptedData(sk, layerMetadata.depth + 1)
|
||||||
val skEncData = encDataList.extractSessionKeyEncryptedData()
|
val skEncData = encDataList.extractSessionKeyEncryptedData()
|
||||||
try {
|
try {
|
||||||
val decrypted = skEncData.getDataStream(decryptorFactory)
|
val decrypted = skEncData.getDataStream(decryptorFactory)
|
||||||
|
@ -506,7 +528,7 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptWithPrivateKey(
|
private fun decryptWithPrivateKey(
|
||||||
esks: SortedESKs,
|
esks: ESKsAndData,
|
||||||
privateKey: PGPKeyPair,
|
privateKey: PGPKeyPair,
|
||||||
decryptionKeyId: SubkeyIdentifier,
|
decryptionKeyId: SubkeyIdentifier,
|
||||||
pkesk: PGPPublicKeyEncryptedData
|
pkesk: PGPPublicKeyEncryptedData
|
||||||
|
@ -529,7 +551,7 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptSKESKAndStream(
|
private fun decryptSKESKAndStream(
|
||||||
esks: SortedESKs,
|
esks: ESKsAndData,
|
||||||
skesk: PGPPBEEncryptedData,
|
skesk: PGPPBEEncryptedData,
|
||||||
decryptorFactory: PBEDataDecryptorFactory
|
decryptorFactory: PBEDataDecryptorFactory
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
@ -537,7 +559,7 @@ class OpenPgpMessageInputStream(
|
||||||
val decrypted = skesk.getDataStream(decryptorFactory)
|
val decrypted = skesk.getDataStream(decryptorFactory)
|
||||||
val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory))
|
val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory))
|
||||||
throwIfUnacceptable(sessionKey.algorithm)
|
throwIfUnacceptable(sessionKey.algorithm)
|
||||||
val encryptedData = EncryptedData(sessionKey.algorithm, layerMetadata.depth + 1)
|
val encryptedData = esks.toEncryptedData(sessionKey, layerMetadata.depth + 1)
|
||||||
encryptedData.sessionKey = sessionKey
|
encryptedData.sessionKey = sessionKey
|
||||||
encryptedData.addRecipients(esks.pkesks.map { it.keyIdentifier })
|
encryptedData.addRecipients(esks.pkesks.map { it.keyIdentifier })
|
||||||
LOGGER.debug("Successfully decrypted data with passphrase")
|
LOGGER.debug("Successfully decrypted data with passphrase")
|
||||||
|
@ -555,7 +577,7 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptPKESKAndStream(
|
private fun decryptPKESKAndStream(
|
||||||
esks: SortedESKs,
|
esks: ESKsAndData,
|
||||||
decryptionKeyId: SubkeyIdentifier,
|
decryptionKeyId: SubkeyIdentifier,
|
||||||
decryptorFactory: PublicKeyDataDecryptorFactory,
|
decryptorFactory: PublicKeyDataDecryptorFactory,
|
||||||
pkesk: PGPPublicKeyEncryptedData
|
pkesk: PGPPublicKeyEncryptedData
|
||||||
|
@ -565,11 +587,7 @@ class OpenPgpMessageInputStream(
|
||||||
val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory))
|
val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory))
|
||||||
throwIfUnacceptable(sessionKey.algorithm)
|
throwIfUnacceptable(sessionKey.algorithm)
|
||||||
|
|
||||||
val encryptedData =
|
val encryptedData = esks.toEncryptedData(sessionKey, layerMetadata.depth)
|
||||||
EncryptedData(
|
|
||||||
SymmetricKeyAlgorithm.requireFromId(
|
|
||||||
pkesk.getSymmetricAlgorithm(decryptorFactory)),
|
|
||||||
layerMetadata.depth + 1)
|
|
||||||
encryptedData.decryptionKey = decryptionKeyId
|
encryptedData.decryptionKey = decryptionKeyId
|
||||||
encryptedData.sessionKey = sessionKey
|
encryptedData.sessionKey = sessionKey
|
||||||
encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyIdentifier })
|
encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyIdentifier })
|
||||||
|
@ -730,7 +748,32 @@ class OpenPgpMessageInputStream(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SortedESKs(esks: PGPEncryptedDataList) {
|
private class ESKsAndData(private val esks: PGPEncryptedDataList) {
|
||||||
|
fun toEncryptedData(sk: SessionKey, depth: Int): EncryptedData {
|
||||||
|
return when (EncryptedDataPacketType.of(esks)!!) {
|
||||||
|
EncryptedDataPacketType.SED ->
|
||||||
|
EncryptedData(
|
||||||
|
MessageEncryptionMechanism.legacyEncryptedNonIntegrityProtected(
|
||||||
|
sk.algorithm.algorithmId),
|
||||||
|
depth)
|
||||||
|
EncryptedDataPacketType.SEIPDv1 ->
|
||||||
|
EncryptedData(
|
||||||
|
MessageEncryptionMechanism.integrityProtected(sk.algorithm.algorithmId),
|
||||||
|
depth)
|
||||||
|
EncryptedDataPacketType.SEIPDv2 -> {
|
||||||
|
val seipd2 = esks.encryptedData as SymmetricEncIntegrityPacket
|
||||||
|
EncryptedData(
|
||||||
|
MessageEncryptionMechanism.aead(
|
||||||
|
seipd2.cipherAlgorithm, seipd2.aeadAlgorithm),
|
||||||
|
depth)
|
||||||
|
}
|
||||||
|
EncryptedDataPacketType.LIBREPGP_OED -> {
|
||||||
|
val oed = esks.encryptedData as AEADEncDataPacket
|
||||||
|
EncryptedData(MessageEncryptionMechanism.librePgp(oed.algorithm.toInt()), depth)
|
||||||
|
}
|
||||||
|
}.also { it.sessionKey = sk }
|
||||||
|
}
|
||||||
|
|
||||||
val skesks: List<PGPPBEEncryptedData>
|
val skesks: List<PGPPBEEncryptedData>
|
||||||
val pkesks: List<PGPPublicKeyEncryptedData>
|
val pkesks: List<PGPPublicKeyEncryptedData>
|
||||||
val anonPkesks: List<PGPPublicKeyEncryptedData>
|
val anonPkesks: List<PGPPublicKeyEncryptedData>
|
||||||
|
@ -739,6 +782,7 @@ class OpenPgpMessageInputStream(
|
||||||
skesks = mutableListOf()
|
skesks = mutableListOf()
|
||||||
pkesks = mutableListOf()
|
pkesks = mutableListOf()
|
||||||
anonPkesks = mutableListOf()
|
anonPkesks = mutableListOf()
|
||||||
|
|
||||||
for (esk in esks) {
|
for (esk in esks) {
|
||||||
if (esk is PGPPBEEncryptedData) {
|
if (esk is PGPPBEEncryptedData) {
|
||||||
skesks.add(esk)
|
skesks.add(esk)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
package org.pgpainless.decryption_verification;
|
package org.pgpainless.decryption_verification;
|
||||||
|
|
||||||
|
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism;
|
||||||
import org.junit.JUtils;
|
import org.junit.JUtils;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||||
|
@ -29,8 +30,8 @@ public class MessageMetadataTest {
|
||||||
MessageMetadata.Message message = new MessageMetadata.Message();
|
MessageMetadata.Message message = new MessageMetadata.Message();
|
||||||
|
|
||||||
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.getDepth() + 1);
|
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.getDepth() + 1);
|
||||||
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128, compressedData.getDepth() + 1);
|
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_128.getAlgorithmId()), compressedData.getDepth() + 1);
|
||||||
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256, encryptedData.getDepth() + 1);
|
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_256.getAlgorithmId()), encryptedData.getDepth() + 1);
|
||||||
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
|
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
|
||||||
|
|
||||||
message.setChild(compressedData);
|
message.setChild(compressedData);
|
||||||
|
|
|
@ -15,18 +15,25 @@ import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
import org.bouncycastle.bcpg.ArmoredOutputStream;
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
import org.bouncycastle.openpgp.PGPException;
|
||||||
import org.bouncycastle.openpgp.PGPSignature;
|
import org.bouncycastle.openpgp.PGPSignature;
|
||||||
|
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism;
|
||||||
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
|
import org.bouncycastle.openpgp.api.OpenPGPCertificate;
|
||||||
import org.bouncycastle.openpgp.api.OpenPGPKey;
|
import org.bouncycastle.openpgp.api.OpenPGPKey;
|
||||||
import org.bouncycastle.util.io.Streams;
|
import org.bouncycastle.util.io.Streams;
|
||||||
import org.junit.jupiter.api.TestTemplate;
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.pgpainless.PGPainless;
|
import org.pgpainless.PGPainless;
|
||||||
|
import org.pgpainless.algorithm.AEADAlgorithm;
|
||||||
|
import org.pgpainless.algorithm.AEADCipherMode;
|
||||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||||
|
import org.pgpainless.algorithm.Feature;
|
||||||
|
import org.pgpainless.algorithm.KeyFlag;
|
||||||
|
import org.pgpainless.algorithm.OpenPGPKeyVersion;
|
||||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
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;
|
||||||
|
@ -34,7 +41,11 @@ import org.pgpainless.decryption_verification.MessageMetadata;
|
||||||
import org.pgpainless.exception.KeyException;
|
import org.pgpainless.exception.KeyException;
|
||||||
import org.pgpainless.key.SubkeyIdentifier;
|
import org.pgpainless.key.SubkeyIdentifier;
|
||||||
import org.pgpainless.key.TestKeys;
|
import org.pgpainless.key.TestKeys;
|
||||||
|
import org.pgpainless.key.generation.KeySpec;
|
||||||
|
import org.pgpainless.key.generation.type.KeyType;
|
||||||
|
import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve;
|
||||||
import org.pgpainless.key.generation.type.rsa.RsaLength;
|
import org.pgpainless.key.generation.type.rsa.RsaLength;
|
||||||
|
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
import org.pgpainless.key.protection.UnprotectedKeysProtector;
|
||||||
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
import org.pgpainless.util.ArmoredOutputStreamFactory;
|
||||||
|
@ -306,4 +317,74 @@ public class EncryptDecryptTest {
|
||||||
EncryptionOptions.encryptCommunications(api)
|
EncryptionOptions.encryptCommunications(api)
|
||||||
.addRecipient(publicKeys));
|
.addRecipient(publicKeys));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestTemplate
|
||||||
|
@ExtendWith(TestAllImplementations.class)
|
||||||
|
public void testEncryptToOnlyV4CertWithOnlySEIPD1Feature() throws PGPException, IOException {
|
||||||
|
PGPainless api = PGPainless.getInstance();
|
||||||
|
OpenPGPKey v4Key = api.buildKey(OpenPGPKeyVersion.v4)
|
||||||
|
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)
|
||||||
|
.overridePreferredSymmetricKeyAlgorithms(SymmetricKeyAlgorithm.AES_192)
|
||||||
|
.overrideFeatures(Feature.MODIFICATION_DETECTION)) // the key only supports SEIPD1
|
||||||
|
.addSubkey(KeySpec.getBuilder(KeyType.XDH_LEGACY(XDHLegacySpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||||
|
EncryptionStream eOut = api.generateMessage().onOutputStream(bOut)
|
||||||
|
.withOptions(ProducerOptions.encrypt(EncryptionOptions.encryptCommunications()
|
||||||
|
.addRecipient(v4Key.toCertificate())));
|
||||||
|
eOut.write(testMessage.getBytes(StandardCharsets.UTF_8));
|
||||||
|
eOut.close();
|
||||||
|
|
||||||
|
ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
|
||||||
|
DecryptionStream dIn = PGPainless.decryptAndOrVerify()
|
||||||
|
.onInputStream(bIn)
|
||||||
|
.withOptions(ConsumerOptions.get().addDecryptionKey(v4Key));
|
||||||
|
|
||||||
|
bOut = new ByteArrayOutputStream();
|
||||||
|
Streams.pipeAll(dIn, bOut);
|
||||||
|
dIn.close();
|
||||||
|
|
||||||
|
assertEquals(testMessage, bOut.toString());
|
||||||
|
MessageMetadata metadata = dIn.getMetadata();
|
||||||
|
MessageEncryptionMechanism encryptionMechanism = metadata.getEncryptionMechanism();
|
||||||
|
assertEquals(
|
||||||
|
MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_192.getAlgorithmId()),
|
||||||
|
encryptionMechanism);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestTemplate
|
||||||
|
@ExtendWith(TestAllImplementations.class)
|
||||||
|
public void testEncryptToOnlyV6CertWithOnlySEIPD2Features() throws IOException, PGPException {
|
||||||
|
PGPainless api = PGPainless.getInstance();
|
||||||
|
OpenPGPKey v6Key = api.buildKey(OpenPGPKeyVersion.v6)
|
||||||
|
.setPrimaryKey(KeySpec.getBuilder(KeyType.Ed25519(), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)
|
||||||
|
.overridePreferredAEADAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_128))
|
||||||
|
.overrideFeatures(Feature.MODIFICATION_DETECTION_2)) // the key only supports SEIPD2
|
||||||
|
.addSubkey(KeySpec.getBuilder(KeyType.X25519(), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
|
||||||
|
EncryptionStream eOut = api.generateMessage().onOutputStream(bOut)
|
||||||
|
.withOptions(ProducerOptions.encrypt(EncryptionOptions.encryptCommunications()
|
||||||
|
.addRecipient(v6Key.toCertificate())));
|
||||||
|
eOut.write(testMessage.getBytes(StandardCharsets.UTF_8));
|
||||||
|
eOut.close();
|
||||||
|
|
||||||
|
ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
|
||||||
|
DecryptionStream dIn = PGPainless.decryptAndOrVerify()
|
||||||
|
.onInputStream(bIn)
|
||||||
|
.withOptions(ConsumerOptions.get().addDecryptionKey(v6Key));
|
||||||
|
|
||||||
|
bOut = new ByteArrayOutputStream();
|
||||||
|
Streams.pipeAll(dIn, bOut);
|
||||||
|
dIn.close();
|
||||||
|
|
||||||
|
assertEquals(testMessage, bOut.toString());
|
||||||
|
MessageMetadata metadata = dIn.getMetadata();
|
||||||
|
MessageEncryptionMechanism encryptionMechanism = metadata.getEncryptionMechanism();
|
||||||
|
assertEquals(
|
||||||
|
MessageEncryptionMechanism.aead(SymmetricKeyAlgorithm.AES_128.getAlgorithmId(), AEADAlgorithm.OCB.getAlgorithmId()),
|
||||||
|
encryptionMechanism);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue