diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java index 11b5d9cc..81213085 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/EncryptDecryptTest.java @@ -15,26 +15,18 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.util.Set; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSignature; -import org.bouncycastle.openpgp.api.MessageEncryptionMechanism; import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.util.io.Streams; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.pgpainless.PGPainless; -import org.pgpainless.algorithm.AEADAlgorithm; -import org.pgpainless.algorithm.AEADCipherMode; -import org.pgpainless.algorithm.AlgorithmSuite; 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.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.DecryptionStream; @@ -42,11 +34,7 @@ import org.pgpainless.decryption_verification.MessageMetadata; import org.pgpainless.exception.KeyException; import org.pgpainless.key.SubkeyIdentifier; 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.xdh_legacy.XDHLegacySpec; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.util.ArmoredOutputStreamFactory; @@ -319,78 +307,4 @@ public class EncryptDecryptTest { .addRecipient(publicKeys)); } - @TestTemplate - @ExtendWith(TestAllImplementations.class) - public void testEncryptToOnlyV4CertWithOnlySEIPD1Feature() throws PGPException, IOException { - PGPainless api = PGPainless.getInstance(); - OpenPGPKey v4Key = api.buildKey(OpenPGPKeyVersion.v4) - .withPreferences(AlgorithmSuite.emptyBuilder() - .overrideSymmetricKeyAlgorithms(SymmetricKeyAlgorithm.AES_192) - .overrideFeatures(Feature.MODIFICATION_DETECTION) - .build()) - .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) - .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) - .withPreferences(AlgorithmSuite.emptyBuilder() - .overrideFeatures(Feature.MODIFICATION_DETECTION_2) - .overrideAeadAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_192)) - .build()) - .setPrimaryKey(KeySpec.getBuilder(KeyType.Ed25519(), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) - .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_192.getAlgorithmId(), AEADAlgorithm.OCB.getAlgorithmId()), - encryptionMechanism); - } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/MechanismNegotiationTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/MechanismNegotiationTest.java new file mode 100644 index 00000000..44a10a65 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/MechanismNegotiationTest.java @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2025 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.api.MessageEncryptionMechanism; +import org.bouncycastle.openpgp.api.OpenPGPKey; +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.AEADAlgorithm; +import org.pgpainless.algorithm.AEADCipherMode; +import org.pgpainless.algorithm.AlgorithmSuite; +import org.pgpainless.algorithm.Feature; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.algorithm.OpenPGPKeyVersion; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.decryption_verification.ConsumerOptions; +import org.pgpainless.decryption_verification.DecryptionStream; +import org.pgpainless.decryption_verification.MessageMetadata; +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.xdh_legacy.XDHLegacySpec; +import org.pgpainless.util.TestAllImplementations; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class MechanismNegotiationTest { + + private static final String testMessage = + "Ah, Juliet, if the measure of thy joy\n" + + "Be heaped like mine, and that thy skill be more\n" + + "To blazon it, then sweeten with thy breath\n" + + "This neighbor air, and let rich music’s tongue\n" + + "Unfold the imagined happiness that both\n" + + "Receive in either by this dear encounter."; + + @TestTemplate + @ExtendWith(TestAllImplementations.class) + public void testEncryptToOnlyV4CertWithOnlySEIPD1Feature() throws PGPException, IOException { + testEncryptDecryptAndCheckExpectations( + MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_192.getAlgorithmId()), + new KeySpecification(OpenPGPKeyVersion.v4, AlgorithmSuite.emptyBuilder() + .overrideSymmetricKeyAlgorithms(SymmetricKeyAlgorithm.AES_192) + .overrideFeatures(Feature.MODIFICATION_DETECTION) + .build())); + } + + @TestTemplate + @ExtendWith(TestAllImplementations.class) + public void testEncryptToOnlyV4CertWithOnlySEIPD2Feature() throws PGPException, IOException { + testEncryptDecryptAndCheckExpectations( + MessageEncryptionMechanism.aead(SymmetricKeyAlgorithm.AES_256.getAlgorithmId(), AEADAlgorithm.OCB.getAlgorithmId()), + new KeySpecification(OpenPGPKeyVersion.v4, AlgorithmSuite.emptyBuilder() + .overrideAeadAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_256)) + .overrideFeatures(Feature.MODIFICATION_DETECTION_2) + .build())); + } + + @TestTemplate + @ExtendWith(TestAllImplementations.class) + public void testEncryptToOnlyV6CertWithOnlySEIPD2Features() throws IOException, PGPException { + testEncryptDecryptAndCheckExpectations( + MessageEncryptionMechanism.aead(SymmetricKeyAlgorithm.AES_256.getAlgorithmId(), AEADAlgorithm.OCB.getAlgorithmId()), + new KeySpecification(OpenPGPKeyVersion.v6, AlgorithmSuite.emptyBuilder() + .overrideAeadAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_256)) + .overrideFeatures(Feature.MODIFICATION_DETECTION_2) + .build())); + } + + @TestTemplate + @ExtendWith(TestAllImplementations.class) + public void testEncryptToV6SEIPD1CertAndV6SEIPD2Cert() throws IOException, PGPException { + testEncryptDecryptAndCheckExpectations( + MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_192.getAlgorithmId()), + + new KeySpecification(OpenPGPKeyVersion.v6, AlgorithmSuite.emptyBuilder() + .overrideAeadAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_256)) + .overrideFeatures(Feature.MODIFICATION_DETECTION_2) + .build()), + new KeySpecification(OpenPGPKeyVersion.v6, AlgorithmSuite.emptyBuilder() + .overrideSymmetricKeyAlgorithms(SymmetricKeyAlgorithm.AES_192) + .overrideFeatures(Feature.MODIFICATION_DETECTION) + .build())); + } + + + private void testEncryptDecryptAndCheckExpectations(MessageEncryptionMechanism expectation, KeySpecification... keys) + throws PGPException, IOException { + PGPainless api = PGPainless.getInstance(); + List keyList = new ArrayList<>(); + for (KeySpecification spec : keys) { + if (spec.version == OpenPGPKeyVersion.v4) { + keyList.add(api.buildKey(spec.version) + .withPreferences(spec.preferences) + .setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) + .addSubkey(KeySpec.getBuilder(KeyType.XDH_LEGACY(XDHLegacySpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)) + .build()); + } else { + keyList.add(api.buildKey(spec.version) + .withPreferences(spec.preferences) + .setPrimaryKey(KeySpec.getBuilder(KeyType.Ed25519(), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)) + .addSubkey(KeySpec.getBuilder(KeyType.X25519(), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE)) + .build()); + } + } + + EncryptionOptions encOpts = EncryptionOptions.encryptCommunications(); + for (OpenPGPKey k : keyList) { + encOpts.addRecipient(k.toCertificate()); + } + + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + EncryptionStream eOut = api.generateMessage().onOutputStream(bOut) + .withOptions(ProducerOptions.encrypt(encOpts)); + 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(keyList.get(0))); + + bOut = new ByteArrayOutputStream(); + Streams.pipeAll(dIn, bOut); + dIn.close(); + + assertEquals(testMessage, bOut.toString()); + MessageMetadata metadata = dIn.getMetadata(); + MessageEncryptionMechanism encryptionMechanism = metadata.getEncryptionMechanism(); + assertEquals(expectation, encryptionMechanism); + } + + private static class KeySpecification { + private final OpenPGPKeyVersion version; + private final AlgorithmSuite preferences; + + public KeySpecification(OpenPGPKeyVersion version, + AlgorithmSuite preferences) { + this.version = version; + this.preferences = preferences; + } + } + +}