From b3eb18bb42c3c9423c8e9206965e2f7c9cef58f3 Mon Sep 17 00:00:00 2001 From: Miguel Aranda Date: Tue, 12 May 2026 15:40:31 +0000 Subject: [PATCH] Project import generated by Copybara. PiperOrigin-RevId: 914302160 --- .../jni/main/cpp/conscrypt/native_crypto.cc | 36 ++++++++---- .../org/conscrypt/OpenSslXwingPrivateKey.java | 3 +- .../javax/net/ssl/KeyManagerFactoryTest.java | 16 +---- .../javax/net/ssl/SSLSocketTest.java | 11 +++- .../org/conscrypt/ConscryptSocketTest.java | 9 ++- .../java/org/conscrypt/NativeCryptoTest.java | 58 +++++++++++++++++++ .../java/org/conscrypt/AndroidHpkeSpi.java | 54 +++++++++++++++++ .../org/conscrypt/AndroidHpkeSpiTest.java | 11 +++- .../java/security/StandardNames.java | 7 ++- .../tlswire/handshake/EllipticCurve.java | 3 + 10 files changed, 174 insertions(+), 34 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 0674a9abb..188bcb38c 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -3096,16 +3096,6 @@ static jbyteArray NativeCrypto_SLHDSA_SHA2_128S_sign(JNIEnv* env, jclass, jbyteA return nullptr; } - uint8_t result[SLHDSA_SHA2_128S_SIGNATURE_BYTES]; - if (!SLHDSA_SHA2_128S_sign(result, - reinterpret_cast(privateKeyArray.get()), - reinterpret_cast(dataArray.get()), dataLen, - /* context */ NULL, /* context_len */ 0)) { - JNI_TRACE("SLHDSA_SHA2_128S_sign failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SLHDSA_SHA2_128S_sign"); - return nullptr; - } - ScopedLocalRef resultRef( env, env->NewByteArray(static_cast(SLHDSA_SHA2_128S_SIGNATURE_BYTES))); if (resultRef.get() == nullptr) { @@ -3116,7 +3106,16 @@ static jbyteArray NativeCrypto_SLHDSA_SHA2_128S_sign(JNIEnv* env, jclass, jbyteA if (resultArray.get() == nullptr) { return nullptr; } - memcpy(resultArray.get(), result, SLHDSA_SHA2_128S_SIGNATURE_BYTES); + + if (!SLHDSA_SHA2_128S_sign(reinterpret_cast(resultArray.get()), + reinterpret_cast(privateKeyArray.get()), + reinterpret_cast(dataArray.get()), dataLen, + /* context */ NULL, /* context_len */ 0)) { + JNI_TRACE("SLHDSA_SHA2_128S_sign failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SLHDSA_SHA2_128S_sign"); + return nullptr; + } + return resultRef.release(); } @@ -3174,18 +3173,33 @@ static jboolean NativeCrypto_X25519(JNIEnv* env, jclass, jbyteArray outArray, pubkeyArray); return JNI_FALSE; } + if (out.size() != 32) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Output array length != 32"); + return JNI_FALSE; + } ScopedByteArrayRO privkey(env, privkeyArray); if (privkey.get() == nullptr) { JNI_TRACE("X25519(%p) => privkey == null", outArray); return JNI_FALSE; } + if (privkey.size() != 32) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Private key array length != 32"); + return JNI_FALSE; + } ScopedByteArrayRO pubkey(env, pubkeyArray); if (pubkey.get() == nullptr) { JNI_TRACE("X25519(%p) => pubkey == null", outArray); return JNI_FALSE; } + if (pubkey.size() != 32) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Public key array length != 32"); + return JNI_FALSE; + } if (X25519(reinterpret_cast(out.get()), reinterpret_cast(privkey.get()), diff --git a/common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java index 1b52c7b71..c2d76cd62 100644 --- a/common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java @@ -43,8 +43,7 @@ public class OpenSslXwingPrivateKey implements PrivateKey { public OpenSslXwingPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { byte[] encoded = keySpec.getEncoded(); if (keySpec.getFormat().equals("PKCS#8")) { - byte[] preamble = Arrays.copyOf(encoded, pkcs8Preamble.length); - if (!Arrays.equals(preamble, pkcs8Preamble)) { + if (!ArrayUtils.startsWith(encoded, pkcs8Preamble)) { throw new InvalidKeySpecException("Invalid X-Wing PKCS8 key preamble"); } raw = Arrays.copyOfRange(encoded, pkcs8Preamble.length, encoded.length); diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java index 5ab143c96..11d3d13c2 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java @@ -85,18 +85,6 @@ private TestKeyStore getTestKeyStore() throws Exception { return testKeyStore; } - // Remove legacy EC_EC type, which Jdk now considers invalid. (may or may not be a bug: - // https://bugs.openjdk.org/browse/JDK-8379191) - private static String[] removeLegacyEcTypes(String[] input) { - List list = new ArrayList<>(); - for (String s : input) { - if (s != null && !s.equals("EC_EC")) { - list.add(s); - } - } - return list.toArray(new String[0]); - } - @Test public void test_KeyManagerFactory_getDefaultAlgorithm() throws Exception { String algorithm = KeyManagerFactory.getDefaultAlgorithm(); @@ -207,7 +195,7 @@ private void test_KeyManagerFactory_getKeyManagers(KeyManagerFactory kmf, boolea private void test_X509KeyManager(X509KeyManager km, boolean empty, String algorithm) throws Exception { - String[] keyTypes = removeLegacyEcTypes(keyTypes(algorithm)); + String[] keyTypes = keyTypes(algorithm); for (String keyType : keyTypes) { String[] aliases = km.getClientAliases(keyType, null); if (empty || keyType == null || keyType.isEmpty()) { @@ -253,7 +241,7 @@ private void test_X509KeyManager(X509KeyManager km, boolean empty, String algori private void test_X509ExtendedKeyManager(X509ExtendedKeyManager km, boolean empty, String algorithm) throws Exception { - String[] keyTypes = removeLegacyEcTypes(keyTypes(algorithm)); + String[] keyTypes = keyTypes(algorithm); String[][] rotatedTypes = rotate(nonEmpty(keyTypes)); for (String[] keyList : rotatedTypes) { String alias = km.chooseEngineClientAlias(keyList, null, null); diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java index 8aeaa94df..acf021424 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java @@ -409,7 +409,7 @@ public void test_SSLSocket_setEnabledProtocols() throws Exception { ssl.setEnabledProtocols(ssl.getSupportedProtocols()); // Check that setEnabledProtocols affects getEnabledProtocols for (String protocol : ssl.getSupportedProtocols()) { - if ("SSLv2Hello".equals(protocol)) { + if (protocol.equals("SSLv2Hello")) { // Should fail when SSLv2Hello is set by itself assertThrows(IllegalArgumentException.class, () -> ssl.setEnabledProtocols(new String[] {protocol})); @@ -977,8 +977,13 @@ public void handshake_noNamedGroupsProperty_usesDefaultGroups() throws Exception c.get(); // By default, BoringSSL uses X25519, P-256, and P-384, in this order. // So X25519 gets priority. - assertEquals("X25519", getCurveName(client)); - assertEquals("X25519", getCurveName(server)); + // We also allow for X25519MLKEM768, as that may be the default in the future. + String clientCurve = getCurveName(client); + String serverCurve = getCurveName(server); + assertTrue("Unexpected client curve: " + clientCurve, + clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); + assertTrue("Unexpected server curve: " + serverCurve, + serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); client.close(); server.close(); context.close(); diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java index d538c9bb3..942bacc5f 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java @@ -474,8 +474,13 @@ public void test_handshake() throws Exception { // By default, BoringSSL supports curves "X25519", "P-256" and "P-384". // X25519 gets priority, so that curve will be used in the handshake here. - assertEquals("X25519", connection.server.getCurveNameForTesting()); - assertEquals("X25519", connection.client.getCurveNameForTesting()); + // We also allow for X25519MLKEM768, as that may be the default in the future. + String serverCurve = connection.server.getCurveNameForTesting(); + String clientCurve = connection.client.getCurveNameForTesting(); + assertTrue("Unexpected server curve: " + serverCurve, + serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); + assertTrue("Unexpected client curve: " + clientCurve, + clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); } @Test diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index 3bc32bbc4..1e1802c44 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -4595,4 +4595,62 @@ public void test_slhdsa_sha2_128s_works() throws Exception { RuntimeException.class, () -> NativeCrypto.SLHDSA_SHA2_128S_generate_key(publicKeyTooLong, privateKey)); } + + @Test + public void x25519_testVector1FromRfc7748_works() throws Exception { + // Test vector from RFC 7748, Section 6.1 (Alice's side) + byte[] privateKey = + decodeHex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"); + byte[] expectedOutput = + decodeHex("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"); + byte[] base = new byte[32]; + base[0] = 9; + + byte[] out = new byte[32]; + boolean success = NativeCrypto.X25519(out, privateKey, base); + + assertTrue(success); + assertArrayEquals(expectedOutput, out); + } + + @Test + public void x25519_testVector2FromRfc7748_works() throws Exception { + // Test vector from RFC 7748, Section 6.1 (Bob's side) + byte[] privateKey = + decodeHex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"); + byte[] expectedOutput = + decodeHex("de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"); + byte[] base = new byte[32]; + base[0] = 9; + + byte[] out = new byte[32]; + boolean success = NativeCrypto.X25519(out, privateKey, base); + + assertTrue(success); + assertArrayEquals(expectedOutput, out); + } + + @Test + public void x25519_invalidInputSize_throwsIllegalArgumentException() throws Exception { + assertThrows(IllegalArgumentException.class, + () -> NativeCrypto.X25519(new byte[31], new byte[32], new byte[32])); + + assertThrows(IllegalArgumentException.class, + () -> NativeCrypto.X25519(new byte[32], new byte[31], new byte[32])); + + assertThrows(IllegalArgumentException.class, + () -> NativeCrypto.X25519(new byte[32], new byte[32], new byte[31])); + } + + @Test + public void x25519_nullInput_throwsIllegalArgumentException() throws Exception { + assertThrows(NullPointerException.class, + () -> NativeCrypto.X25519(null, new byte[32], new byte[32])); + + assertThrows(NullPointerException.class, + () -> NativeCrypto.X25519(new byte[32], null, new byte[32])); + + assertThrows(NullPointerException.class, + () -> NativeCrypto.X25519(new byte[32], new byte[32], null)); + } } diff --git a/platform/src/main/java/org/conscrypt/AndroidHpkeSpi.java b/platform/src/main/java/org/conscrypt/AndroidHpkeSpi.java index 24b4bfa6b..e6e0daab8 100644 --- a/platform/src/main/java/org/conscrypt/AndroidHpkeSpi.java +++ b/platform/src/main/java/org/conscrypt/AndroidHpkeSpi.java @@ -104,4 +104,58 @@ public X25519_CHACHA20() { super(new HpkeImpl.X25519_CHACHA20()); } } + + public static class MlKem768HkdfSha256Aes128Gcm extends AndroidHpkeSpi { + public MlKem768HkdfSha256Aes128Gcm() { + super(new HpkeImpl.MlKem768HkdfSha256Aes128Gcm()); + } + } + + public static class MlKem768HkdfSha256Aes256Gcm extends AndroidHpkeSpi { + public MlKem768HkdfSha256Aes256Gcm() { + super(new HpkeImpl.MlKem768HkdfSha256Aes256Gcm()); + } + } + + public static class MlKem768HkdfSha256ChaCha20Poly1305 extends AndroidHpkeSpi { + public MlKem768HkdfSha256ChaCha20Poly1305() { + super(new HpkeImpl.MlKem768HkdfSha256ChaCha20Poly1305()); + } + } + + public static class MlKem1024HkdfSha256Aes128Gcm extends AndroidHpkeSpi { + public MlKem1024HkdfSha256Aes128Gcm() { + super(new HpkeImpl.MlKem1024HkdfSha256Aes128Gcm()); + } + } + + public static class MlKem1024HkdfSha256Aes256Gcm extends AndroidHpkeSpi { + public MlKem1024HkdfSha256Aes256Gcm() { + super(new HpkeImpl.MlKem1024HkdfSha256Aes256Gcm()); + } + } + + public static class MlKem1024HkdfSha256ChaCha20Poly1305 extends AndroidHpkeSpi { + public MlKem1024HkdfSha256ChaCha20Poly1305() { + super(new HpkeImpl.MlKem1024HkdfSha256ChaCha20Poly1305()); + } + } + + public static class XwingHkdfSha256Aes128Gcm extends AndroidHpkeSpi { + public XwingHkdfSha256Aes128Gcm() { + super(new HpkeImpl.XwingHkdfSha256Aes128Gcm()); + } + } + + public static class XwingHkdfSha256Aes256Gcm extends AndroidHpkeSpi { + public XwingHkdfSha256Aes256Gcm() { + super(new HpkeImpl.XwingHkdfSha256Aes256Gcm()); + } + } + + public static class XwingHkdfSha256ChaCha20Poly1305 extends AndroidHpkeSpi { + public XwingHkdfSha256ChaCha20Poly1305() { + super(new HpkeImpl.XwingHkdfSha256ChaCha20Poly1305()); + } + } } diff --git a/platform/src/test/java/org/conscrypt/AndroidHpkeSpiTest.java b/platform/src/test/java/org/conscrypt/AndroidHpkeSpiTest.java index 4348b472e..575f0e072 100644 --- a/platform/src/test/java/org/conscrypt/AndroidHpkeSpiTest.java +++ b/platform/src/test/java/org/conscrypt/AndroidHpkeSpiTest.java @@ -30,7 +30,16 @@ public class AndroidHpkeSpiTest { private static final String[] HPKE_NAMES = new String[] {"DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_128_GCM", "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_256_GCM", - "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305"}; + "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305", + "MLKEM_768/HKDF_SHA256/AES_128_GCM", + "MLKEM_768/HKDF_SHA256/AES_256_GCM", + "MLKEM_768/HKDF_SHA256/CHACHA20POLY1305", + "MLKEM_1024/HKDF_SHA256/AES_128_GCM", + "MLKEM_1024/HKDF_SHA256/AES_256_GCM", + "MLKEM_1024/HKDF_SHA256/CHACHA20POLY1305", + "XWING/HKDF_SHA256/AES_128_GCM", + "XWING/HKDF_SHA256/AES_256_GCM", + "XWING/HKDF_SHA256/CHACHA20POLY1305"}; // This only needs to test the wrapper functionality as the implementation and client // APIs are tested elsewhere. What we're looking for is that HPKE SPI instances returned diff --git a/testing/src/main/java/org/conscrypt/java/security/StandardNames.java b/testing/src/main/java/org/conscrypt/java/security/StandardNames.java index e9bf0437c..9e5d69221 100644 --- a/testing/src/main/java/org/conscrypt/java/security/StandardNames.java +++ b/testing/src/main/java/org/conscrypt/java/security/StandardNames.java @@ -333,6 +333,8 @@ private static void addOpenSsl(String cipherSuite) { // https://boringssl.googlesource.com/boringssl/+/main/ssl/extensions.cc#215 private static final List ELLIPTIC_CURVES_DEFAULT = Arrays.asList("x25519 (29)", "secp256r1 (23)", "secp384r1 (24)"); + private static final List ELLIPTIC_CURVES_DEFAULT_PQC = Arrays.asList( + "x25519mlkem768 (4588)", "x25519 (29)", "secp256r1 (23)", "secp384r1 (24)"); /** * Asserts that the cipher suites array is non-null and that it @@ -441,7 +443,10 @@ public static void assertDefaultCipherSuites(String[] cipherSuites) { } public static void assertDefaultEllipticCurves(String[] curves) { - assertEquals(ELLIPTIC_CURVES_DEFAULT, Arrays.asList(curves)); + List actualList = Arrays.asList(curves); + assertTrue("Unexpected default curves list: " + actualList, + actualList.equals(ELLIPTIC_CURVES_DEFAULT) + || actualList.equals(ELLIPTIC_CURVES_DEFAULT_PQC)); } public static void assertSSLContextEnabledProtocols(String version, String[] protocols) { diff --git a/testing/src/main/java/org/conscrypt/tlswire/handshake/EllipticCurve.java b/testing/src/main/java/org/conscrypt/tlswire/handshake/EllipticCurve.java index ea31dea8f..a49df0eb8 100644 --- a/testing/src/main/java/org/conscrypt/tlswire/handshake/EllipticCurve.java +++ b/testing/src/main/java/org/conscrypt/tlswire/handshake/EllipticCurve.java @@ -52,6 +52,9 @@ public enum EllipticCurve { BRAINPOOLP521R1(28, "brainpoolP521r1"), X25519(29, "x25519"), X448(30, "x448"), + MLKEM1024(514, "mlkem1024"), + X25519MLKEM768(4588, "x25519mlkem768"), + X25519KYBER768DRAFT00(25055, "x25519kyber768draft00"), ARBITRARY_PRIME(0xFF01, "arbitrary_explicit_prime_curves"), ARBITRARY_CHAR2(0xFF02, "arbitrary_explicit_char2_curves"); public final int identifier;