Skip to content

Commit

Permalink
Add MLDSA65 to NativeCrypto. (#1303)
Browse files Browse the repository at this point in the history
  • Loading branch information
juergw authored Feb 4, 2025
1 parent 5473d34 commit 9b5482b
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 0 deletions.
136 changes: 136 additions & 0 deletions common/src/jni/main/cpp/conscrypt/native_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/hpke.h>
#include <openssl/mldsa.h>
#include <openssl/pkcs7.h>
#include <openssl/pkcs8.h>
#include <openssl/rand.h>
Expand Down Expand Up @@ -2504,6 +2505,138 @@ static jint NativeCrypto_ECDSA_verify(JNIEnv* env, jclass, jbyteArray data, jbyt
return static_cast<jint>(result);
}

static jbyteArray NativeCrypto_MLDSA65_public_key_from_seed(JNIEnv* env, jclass,
jbyteArray privateKeySeed) {
CHECK_ERROR_QUEUE_ON_RETURN;

ScopedByteArrayRO seedArray(env, privateKeySeed);
if (seedArray.get() == nullptr) {
JNI_TRACE("NativeCrypto_MLDSA65_sign => privateKeySeed == null");
return nullptr;
}

MLDSA65_private_key privateKey;
if (!MLDSA65_private_key_from_seed(
&privateKey, reinterpret_cast<const uint8_t*>(seedArray.get()), seedArray.size())) {
JNI_TRACE("MLDSA65_private_key_from_seed failed");
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_private_key_from_seed");
return nullptr;
}

MLDSA65_public_key publicKey;
if (!MLDSA65_public_from_private(&publicKey, &privateKey)) {
JNI_TRACE("MLDSA65_public_from_private failed");
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_public_from_private");
return nullptr;
}

CBB cbb;
size_t size;
uint8_t public_key_bytes[MLDSA65_SIGNATURE_BYTES];
if (!CBB_init_fixed(&cbb, public_key_bytes, MLDSA65_PUBLIC_KEY_BYTES) ||
!MLDSA65_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) ||
size != MLDSA65_PUBLIC_KEY_BYTES) {
JNI_TRACE("Failed to serialize ML-DSA public key.");
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_marshal_public_key");
return nullptr;
}

ScopedLocalRef<jbyteArray> publicKeyRef(
env, env->NewByteArray(static_cast<jsize>(MLDSA65_PUBLIC_KEY_BYTES)));
if (publicKeyRef.get() == nullptr) {
return nullptr;
}

ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get());
if (publicKeyArray.get() == nullptr) {
return nullptr;
}
memcpy(publicKeyArray.get(), public_key_bytes, MLDSA65_PUBLIC_KEY_BYTES);
return publicKeyRef.release();
}

static jbyteArray NativeCrypto_MLDSA65_sign(JNIEnv* env, jclass, jbyteArray data,
jbyteArray privateKeySeed) {
CHECK_ERROR_QUEUE_ON_RETURN;

ScopedByteArrayRO seedArray(env, privateKeySeed);
if (seedArray.get() == nullptr) {
JNI_TRACE("NativeCrypto_MLDSA65_sign => privateKeySeed == null");
return nullptr;
}

MLDSA65_private_key privateKey;
if (!MLDSA65_private_key_from_seed(
&privateKey, reinterpret_cast<const uint8_t*>(seedArray.get()), seedArray.size())) {
JNI_TRACE("MLDSA65_private_key_from_seed failed");
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_private_key_from_seed");
return nullptr;
}

ScopedByteArrayRO dataArray(env, data);
if (dataArray.get() == nullptr) {
return nullptr;
}

uint8_t result[MLDSA65_SIGNATURE_BYTES];
if (!MLDSA65_sign(result, &privateKey, reinterpret_cast<const unsigned char*>(dataArray.get()),
dataArray.size(), /* context */ NULL, /* context_len */ 0)) {
JNI_TRACE("MLDSA65_sign failed");
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_sign");
return nullptr;
}

ScopedLocalRef<jbyteArray> resultRef(
env, env->NewByteArray(static_cast<jsize>(MLDSA65_SIGNATURE_BYTES)));
if (resultRef.get() == nullptr) {
return nullptr;
}

ScopedByteArrayRW resultArray(env, resultRef.get());
if (resultArray.get() == nullptr) {
return nullptr;
}
memcpy(resultArray.get(), result, MLDSA65_SIGNATURE_BYTES);
return resultRef.release();
}

static jint NativeCrypto_MLDSA65_verify(JNIEnv* env, jclass, jbyteArray data, jbyteArray sig,
jbyteArray publicKey) {
CHECK_ERROR_QUEUE_ON_RETURN;

ScopedByteArrayRO publicKeyArray(env, publicKey);
if (publicKeyArray.get() == nullptr) {
JNI_TRACE("NativeCrypto_MLDSA65_verify => publicKey == null");
return -1;
}

CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(publicKeyArray.get()), publicKeyArray.size());
MLDSA65_public_key pubkey;
if (!MLDSA65_parse_public_key(&pubkey, &cbs)) {
JNI_TRACE("MLDSA65_parse_public_key failed");
return -1;
}

ScopedByteArrayRO dataArray(env, data);
if (dataArray.get() == nullptr) {
return -1;
}

ScopedByteArrayRO sigArray(env, sig);
if (sigArray.get() == nullptr) {
return -1;
}

int result =
MLDSA65_verify(&pubkey, reinterpret_cast<const unsigned char*>(sigArray.get()),
sigArray.size(), reinterpret_cast<const unsigned char*>(dataArray.get()),
dataArray.size(), /*context=*/NULL, /*context_len=*/0);

JNI_TRACE("MLDSA65_verify(%p, %p, %p) => %d", publicKey, sig, data, result);
return static_cast<jint>(result);
}

static jboolean NativeCrypto_X25519(JNIEnv* env, jclass, jbyteArray outArray,
jbyteArray privkeyArray, jbyteArray pubkeyArray) {
CHECK_ERROR_QUEUE_ON_RETURN;
Expand Down Expand Up @@ -11289,6 +11422,9 @@ static JNINativeMethod sNativeCryptoMethods[] = {
CONSCRYPT_NATIVE_METHOD(ECDSA_size, "(" REF_EVP_PKEY ")I"),
CONSCRYPT_NATIVE_METHOD(ECDSA_sign, "([B[B" REF_EVP_PKEY ")I"),
CONSCRYPT_NATIVE_METHOD(ECDSA_verify, "([B[B" REF_EVP_PKEY ")I"),
CONSCRYPT_NATIVE_METHOD(MLDSA65_public_key_from_seed, "([B)[B"),
CONSCRYPT_NATIVE_METHOD(MLDSA65_sign, "([B[B)[B"),
CONSCRYPT_NATIVE_METHOD(MLDSA65_verify, "([B[B[B)I"),
CONSCRYPT_NATIVE_METHOD(X25519, "([B[B[B)Z"),
CONSCRYPT_NATIVE_METHOD(X25519_keypair, "([B[B)V"),
CONSCRYPT_NATIVE_METHOD(ED25519_keypair, "([B[B)V"),
Expand Down
8 changes: 8 additions & 0 deletions common/src/main/java/org/conscrypt/NativeCrypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ static native int ECDH_compute_key(byte[] out, int outOffset, NativeRef.EVP_PKEY

static native int ECDSA_verify(byte[] data, byte[] sig, NativeRef.EVP_PKEY pkey);

// --- MLDSA65 --------------------------------------------------------------

static native byte[] MLDSA65_public_key_from_seed(byte[] privateKeySeed);

static native byte[] MLDSA65_sign(byte[] data, byte[] privateKeySeed);

static native int MLDSA65_verify(byte[] data, byte[] sig, byte[] publicKey);

// --- Curve25519 --------------

static native boolean X25519(byte[] out, byte[] privateKey, byte[] publicKey) throws InvalidKeyException;
Expand Down
49 changes: 49 additions & 0 deletions openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3181,4 +3181,53 @@ private static void assertContains(String actualValue, String expectedSubstring)
private static ServerSocket newServerSocket() throws IOException {
return new ServerSocket(0, 50, TestUtils.getLoopbackAddress());
}

@Test
public void test_mldsa65_works() throws Exception {
byte[] privateKeySeed =
decodeHex("7C9935A0B07694AA0C6D10E4DB6B1ADD2FD81A25CCB148032DCD739936737F2D");
byte[] data =
decodeHex("D81C4D8D734FCBFBEADE3D3F8A039FAA2A2C9957E835AD55B22E75BF57BB556AC8");

byte[] publicKey = NativeCrypto.MLDSA65_public_key_from_seed(privateKeySeed);
assertEquals(1952, publicKey.length);

byte[] signature = NativeCrypto.MLDSA65_sign(data, privateKeySeed);
assertEquals(3309, signature.length);

int result = NativeCrypto.MLDSA65_verify(data, signature, publicKey);
assertEquals(1, result);

byte[] signatureTooShort = Arrays.copyOf(signature, signature.length - 1);
assertEquals(0, NativeCrypto.MLDSA65_verify(data, signatureTooShort, publicKey));

byte[] signatureTooLong = Arrays.copyOf(signature, signature.length + 1);
assertEquals(0, NativeCrypto.MLDSA65_verify(data, signatureTooLong, publicKey));

byte[] modifiedSignature = signature.clone();
modifiedSignature[0] = (byte) (modifiedSignature[0] ^ 0x01);
assertEquals(0, NativeCrypto.MLDSA65_verify(data, modifiedSignature, publicKey));

byte[] modifiedData = data.clone();
modifiedData[0] = (byte) (modifiedData[0] ^ 0x01);
assertEquals(0, NativeCrypto.MLDSA65_verify(modifiedData, signature, publicKey));

byte[] privateKeySeedTooShort = Arrays.copyOf(privateKeySeed, privateKeySeed.length - 1);
assertThrows(RuntimeException.class,
() -> NativeCrypto.MLDSA65_public_key_from_seed(privateKeySeedTooShort));
assertThrows(RuntimeException.class,
() -> NativeCrypto.MLDSA65_sign(data, privateKeySeedTooShort));

byte[] privateKeySeedTooLong = Arrays.copyOf(privateKeySeed, privateKeySeed.length + 1);
assertThrows(RuntimeException.class,
() -> NativeCrypto.MLDSA65_public_key_from_seed(privateKeySeedTooLong));
assertThrows(RuntimeException.class,
() -> NativeCrypto.MLDSA65_sign(data, privateKeySeedTooLong));

byte[] publicKeyTooShort = Arrays.copyOf(publicKey, publicKey.length - 1);
assertEquals(-1, NativeCrypto.MLDSA65_verify(data, signature, publicKeyTooShort));

byte[] publicKeyTooLong = Arrays.copyOf(publicKey, publicKey.length + 1);
assertEquals(-1, NativeCrypto.MLDSA65_verify(data, signature, publicKeyTooLong));
}
}

0 comments on commit 9b5482b

Please sign in to comment.