Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.w3c.dom.Document;
Expand Down Expand Up @@ -220,15 +219,21 @@ void testMLKEMEncryptDecrypt(String keyEncapsulationUri, String jcaAlgorithm) th
assertEquals("CardNumber:4019111111111111", decryptedRoot.getTextContent());
}

@Test
void testMLKEMWrongRecipientPrivateKeyFailsCleanly() throws Exception {
@ParameterizedTest
@CsvSource({
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_512 + ",ML-KEM-512",
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_768 + ",ML-KEM-768",
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_1024 + ",ML-KEM-1024",
})
void testMLKEMWrongRecipientPrivateKeyFailsCleanly(String keyEncapsulationUri, String jcaAlgorithm)
throws Exception {
Assumptions.assumeTrue(mlKemAvailable, "ML-KEM requires BouncyCastle 1.84+ and Java 21+ (javax.crypto.KEM)");

PublicKey recipientAPub = keyPairs.get("ML-KEM-768").getPublic();
byte[] encryptedXml = encryptToRecipient(recipientAPub);
PublicKey recipientAPub = keyPairs.get(jcaAlgorithm).getPublic();
byte[] encryptedXml = encryptToRecipient(recipientAPub, keyEncapsulationUri);

// A second, independent recipient - not the one the message was encrypted to.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM-768", "BC");
KeyPairGenerator kpg = KeyPairGenerator.getInstance(jcaAlgorithm, "BC");
PrivateKey wrongPrivateKey = kpg.generateKeyPair().getPrivate();

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Expand All @@ -254,13 +259,18 @@ void testMLKEMWrongRecipientPrivateKeyFailsCleanly() throws Exception {
() -> unwrapCipher.decryptKey(ek, encData.getEncryptionMethod().getAlgorithm()));
}

@Test
void testMLKEMTruncatedEncapsulationRejected() throws Exception {
@ParameterizedTest
@CsvSource({
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_512 + ",ML-KEM-512",
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_768 + ",ML-KEM-768",
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_1024 + ",ML-KEM-1024",
})
void testMLKEMTruncatedEncapsulationRejected(String keyEncapsulationUri, String jcaAlgorithm) throws Exception {
Assumptions.assumeTrue(mlKemAvailable, "ML-KEM requires BouncyCastle 1.84+ and Java 21+ (javax.crypto.KEM)");

PrivateKey privKey = keyPairs.get("ML-KEM-768").getPrivate();
PublicKey pubKey = keyPairs.get("ML-KEM-768").getPublic();
byte[] encryptedXml = encryptToRecipient(pubKey);
PrivateKey privKey = keyPairs.get(jcaAlgorithm).getPrivate();
PublicKey pubKey = keyPairs.get(jcaAlgorithm).getPublic();
byte[] encryptedXml = encryptToRecipient(pubKey, keyEncapsulationUri);

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Expand Down Expand Up @@ -296,13 +306,11 @@ void testMLKEMTruncatedEncapsulationRejected() throws Exception {
}

/**
* Runs the encrypt half of the ML-KEM-768 round trip (same structure as
* {@link #testMLKEMEncryptDecrypt}) and returns the serialised encrypted XML, for tests that
* want to corrupt or otherwise interfere with the decrypt half.
* Runs the encrypt half of the round trip for the given key encapsulation algorithm (same
* structure as {@link #testMLKEMEncryptDecrypt}) and returns the serialised encrypted XML,
* for tests that want to corrupt or otherwise interfere with the decrypt half.
*/
private byte[] encryptToRecipient(PublicKey pubKey) throws Exception {
String keyEncapsulationUri = EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_768;

private byte[] encryptToRecipient(PublicKey pubKey, String keyEncapsulationUri) throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document doc = dbf.newDocumentBuilder().newDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.w3c.dom.Document;
Expand Down Expand Up @@ -278,28 +277,40 @@ private Document decryptUsingDOM(Document document, Key privateKey) throws Excep
return cipher.doFinal(document, ee);
}

@Test
void testMLKEMStaxWrongRecipientPrivateKeyFailsCleanly() throws Exception {
@ParameterizedTest
@CsvSource({
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_512 + ",ML-KEM-512",
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_768 + ",ML-KEM-768",
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_1024 + ",ML-KEM-1024"
})
void testMLKEMStaxWrongRecipientPrivateKeyFailsCleanly(String keyEncapsulationUri, String jcaAlgorithm)
throws Exception {
Assumptions.assumeTrue(mlKemAvailable, "ML-KEM requires BouncyCastle 1.84+ and Java 21+ (javax.crypto.KEM)");

KeyPair kp = keyPairs.get("ML-KEM-768");
Document document = encryptToRecipient(kp.getPublic());
KeyPair kp = keyPairs.get(jcaAlgorithm);
Document document = encryptToRecipient(kp.getPublic(), keyEncapsulationUri);

// A second, independent recipient - not the one the message was encrypted to.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-KEM-768", "BC");
KeyPairGenerator kpg = KeyPairGenerator.getInstance(jcaAlgorithm, "BC");
PrivateKey wrongPrivateKey = kpg.generateKeyPair().getPrivate();

// See the equivalent DOM-path test (XMLEncryptionMLKEMTest) for why this must throw
// rather than return a Key built from the wrong shared secret.
assertThrows(XMLEncryptionException.class, () -> decryptUsingDOM(document, wrongPrivateKey));
}

@Test
void testMLKEMStaxTruncatedEncapsulationRejected() throws Exception {
@ParameterizedTest
@CsvSource({
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_512 + ",ML-KEM-512",
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_768 + ",ML-KEM-768",
EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_1024 + ",ML-KEM-1024"
})
void testMLKEMStaxTruncatedEncapsulationRejected(String keyEncapsulationUri, String jcaAlgorithm)
throws Exception {
Assumptions.assumeTrue(mlKemAvailable, "ML-KEM requires BouncyCastle 1.84+ and Java 21+ (javax.crypto.KEM)");

KeyPair kp = keyPairs.get("ML-KEM-768");
Document document = encryptToRecipient(kp.getPublic());
KeyPair kp = keyPairs.get(jcaAlgorithm);
Document document = encryptToRecipient(kp.getPublic(), keyEncapsulationUri);

// Truncate the EncryptedKey's CipherValue to well under half its length - shorter than
// any ML-KEM variant's encapsulationSize() - before it is parsed into an EncryptedKey
Expand All @@ -324,11 +335,12 @@ void testMLKEMStaxTruncatedEncapsulationRejected() throws Exception {
}

/**
* Runs the encrypt half of the ML-KEM-768 round trip (same properties as
* {@link #testMLKEMEncryptDecrypt}) and returns the parsed resulting document, for tests
* that want to corrupt or otherwise interfere with the decrypt half.
* Runs the encrypt half of the round trip for the given key encapsulation algorithm (same
* properties as {@link #testMLKEMEncryptDecrypt}) and returns the parsed resulting document,
* for tests that want to corrupt or otherwise interfere with the decrypt half.
*/
private Document encryptToRecipient(java.security.PublicKey pubKey) throws Exception {
private Document encryptToRecipient(java.security.PublicKey pubKey, String keyEncapsulationUri)
throws Exception {
XMLSecurityProperties properties = new XMLSecurityProperties();
List<XMLSecurityConstants.Action> actions = new ArrayList<>();
actions.add(XMLSecurityConstants.ENCRYPTION);
Expand All @@ -341,7 +353,7 @@ private Document encryptToRecipient(java.security.PublicKey pubKey) throws Excep
properties.setEncryptionSymAlgorithm("http://www.w3.org/2009/xmlenc11#aes256-gcm");

properties.setEncryptionKeyTransportAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_GENERIC_HYBRID);
properties.setEncryptionKeyEncapsulationAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_MLKEM_768);
properties.setEncryptionKeyEncapsulationAlgorithm(keyEncapsulationUri);
properties.setEncryptionDataEncapsulationAlgorithm(EncryptionConstants.ALGO_ID_KEYWRAP_AES256);
properties.setEncryptionTransportKey(pubKey);

Expand Down