fix: Ed25519/X25519 JNI bugs and test vectors

- jni_ed25519.c: fix HAVE_ED25519_KEY_GEN guard (not defined in
  wolfSSL 5.8.4); write make_public output to key->p so sign_msg
  can use it (was writing to local stack buffer)
- jni_curve25519.c: switch all 6 Curve25519 ops to _ex variants
  with EC25519_LITTLE_ENDIAN (RFC 7748 / JCA wire format); mask
  bit 255 of public key before import per RFC 7748 §5
- WolfCryptEdDSASignature.java: call ed.makePublic() after
  importPrivateOnly so pubKeySet=1 before signing
- Ed25519.java: add makePublic() wrapper for wc_ed25519_make_public
- Fix wrong test vectors: TV1_SEED last 2 bytes (3d55->7f60);
  X25519 Alice/Bob private keys and Bob public key corrected to
  RFC 8037 §A.6 (verified via Wycheproof tcId 102)
ed25519-xdh-jce
Mark Atwood 2026-05-29 19:54:09 -07:00
parent 56c0d9c25a
commit c40a4fd911
6 changed files with 80 additions and 20 deletions

View File

@ -174,8 +174,9 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Curve25519_wc_1curve25519_1imp
ret = BAD_FUNC_ARG;
} else {
/* detect, and later skip, leading zero byte */
ret = wc_curve25519_import_private_raw(priv, privSz, pub,
pubSz, curve25519);
ret = wc_curve25519_import_private_raw_ex(priv, privSz, pub,
pubSz, curve25519,
EC25519_LITTLE_ENDIAN);
}
if (ret != 0)
@ -212,7 +213,8 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Curve25519_wc_1curve25519_1imp
ret = BAD_FUNC_ARG;
} else {
/* detect, and later skip, leading zero byte */
ret = wc_curve25519_import_private(priv, privSz, curve25519);
ret = wc_curve25519_import_private_ex(priv, privSz, curve25519,
EC25519_LITTLE_ENDIAN);
}
if (ret != 0)
@ -246,8 +248,13 @@ JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Curve25519_wc_1curve25519_1imp
if (!curve25519 || !pub) {
ret = BAD_FUNC_ARG;
} else {
/* detect, and later skip, leading zero byte */
ret = wc_curve25519_import_public(pub, pubSz, curve25519);
/* RFC 7748 §5: mask bit 255 (MSB of last byte) of u-coordinate.
* wolfSSL fingerprinting check rejects keys with this bit set.
* This masking is safe and mandated by the RFC. */
if (pubSz > 0)
pub[pubSz - 1] &= 0x7f;
ret = wc_curve25519_import_public_ex(pub, pubSz, curve25519,
EC25519_LITTLE_ENDIAN);
}
if (ret != 0)
@ -295,7 +302,8 @@ Java_com_wolfssl_wolfcrypt_Curve25519_wc_1curve25519_1export_1private(
}
XMEMSET(output, 0, outputSz);
ret = wc_curve25519_export_private_raw(curve25519, output, &outputSz);
ret = wc_curve25519_export_private_raw_ex(curve25519, output, &outputSz,
EC25519_LITTLE_ENDIAN);
if (ret == 0) {
result = (*env)->NewByteArray(env, outputSz);
@ -360,7 +368,8 @@ Java_com_wolfssl_wolfcrypt_Curve25519_wc_1curve25519_1export_1public (
}
XMEMSET(output, 0, outputSz);
ret = wc_curve25519_export_public(curve25519, output, &outputSz);
ret = wc_curve25519_export_public_ex(curve25519, output, &outputSz,
EC25519_LITTLE_ENDIAN);
if (ret == 0) {
result = (*env)->NewByteArray(env, outputSz);
@ -429,7 +438,8 @@ Java_com_wolfssl_wolfcrypt_Curve25519_wc_1curve25519_1make_1shared_1secret(
}
XMEMSET(output, 0, outputSz);
ret = wc_curve25519_shared_secret(curve25519, pub, output, &outputSz);
ret = wc_curve25519_shared_secret_ex(curve25519, pub, output, &outputSz,
EC25519_LITTLE_ENDIAN);
if (ret == 0) {
result = (*env)->NewByteArray(env, outputSz);

View File

@ -173,6 +173,34 @@ Java_com_wolfssl_wolfcrypt_Ed25519_wc_1ed25519_1check_1key(
#endif
}
JNIEXPORT void JNICALL
Java_com_wolfssl_wolfcrypt_Ed25519_wc_1ed25519_1make_1public(
JNIEnv* env, jobject this)
{
#ifdef HAVE_ED25519
int ret = 0;
ed25519_key* ed25519 = (ed25519_key*) getNativeStruct(env, this);
if ((*env)->ExceptionOccurred(env)) {
return;
}
/* Write the derived public key directly into key->p so that sign_msg
* can access it (sign_msg reads from key->p at hash step). This
* matches the internal call in wc_ed25519_make_key:
* wc_ed25519_make_public(key, key->p, ED25519_PUB_KEY_SIZE). */
ret = (!ed25519)
? BAD_FUNC_ARG
: wc_ed25519_make_public(ed25519, ed25519->p, ED25519_PUB_KEY_SIZE);
if (ret != 0)
throwWolfCryptExceptionFromError(env, ret);
LogStr("wc_ed25519_make_public(ed25519=%p) = %d\n", ed25519, ret);
#else
throwNotCompiledInException(env);
#endif
}
JNIEXPORT void JNICALL Java_com_wolfssl_wolfcrypt_Ed25519_wc_1ed25519_1import_1private
(JNIEnv* env, jobject this, jbyteArray priv_object, jbyteArray pub_object)
{

View File

@ -84,6 +84,7 @@ public class WolfCryptEdDSASignature extends SignatureSpi {
try {
ed = new Ed25519();
ed.importPrivateOnly(seed);
ed.makePublic();
} catch (WolfCryptException e) {
releaseKey();
throw new InvalidKeyException(

View File

@ -83,6 +83,7 @@ public class Ed25519 extends NativeStruct {
private native void wc_ed25519_free();
private native void wc_ed25519_make_key(Rng rng, int size);
private native void wc_ed25519_check_key();
private native void wc_ed25519_make_public();
private native void wc_ed25519_import_private(byte[] privKey, byte[] key);
private native void wc_ed25519_import_private_only(byte[] privKey);
private native void wc_ed25519_import_public(byte[] privKey);
@ -201,6 +202,24 @@ public class Ed25519 extends NativeStruct {
}
}
/**
* Derive and store the Ed25519 public key from the loaded private key.
* Must be called after importPrivateOnly() before signing.
*
* @throws WolfCryptException if native operation fails
* @throws IllegalStateException if object fails to initialize or has been
* released.
*/
public void makePublic()
throws WolfCryptException, IllegalStateException {
checkStateAndInitialize();
synchronized (pointerLock) {
wc_ed25519_make_public();
}
}
/**
* Import private and public Ed25519 key.
*

View File

@ -117,11 +117,12 @@ public class WolfCryptEdDSASignatureTest {
}
/*
* RFC 8032 Section 6 Test Vector 1 (empty message)
* draft-josefsson-eddsa-ed25519-02 §6 Test Vector 1 (empty message)
* (also cited as RFC 8032 §6; vectors from IETF draft)
*/
private static final byte[] TV1_SEED = hex(
"9d61b19deffd5a60ba844af492ec2cc4" +
"4449c5697b326919703bac031cae3d55");
"4449c5697b326919703bac031cae7f60");
private static final byte[] TV1_PUB = hex(
"d75a980182b10ab7d54bfed3c964073a" +
"0ee172f3daa62325af021a68f707511a");

View File

@ -47,7 +47,7 @@ import com.wolfssl.wolfcrypt.test.TimedTestWatcher;
/**
* JUnit4 tests for WolfCryptKeyAgreement X25519 (XDH).
*
* RFC 7748 Section 6.1 test vector used for correctness verification.
* RFC 8037, Section A.6 test vector used for correctness verification.
*/
public class WolfCryptX25519KeyAgreementTest {
@ -95,27 +95,28 @@ public class WolfCryptX25519KeyAgreementTest {
}
/*
* RFC 7748 Section 6.1 X25519 Diffie-Hellman test vector.
* RFC 8037, Section A.6 X25519 Diffie-Hellman test vector.
* (Also cited in RFC 7748; canonical vectors per Wycheproof tcId 102.)
* All values are 32-byte little-endian (raw X25519 wire format).
*/
private static final byte[] RFC_ALICE_PRIV = hex(
"77076d0a7318a57d3c16c17251b26645" +
"df91ef6f5eacc0aee9eefb22e65fc54e");
"df4c2f87ebc0992ab177fba51db92c2a");
private static final byte[] RFC_ALICE_PUB = hex(
"8520f0098930a754748b7ddcb43ef75a" +
"0dbf3a0d26381af4eba4a98eaa9b4e6a");
private static final byte[] RFC_BOB_PRIV = hex(
"5dab087e624a8a4b79e17f8b83800ee6" +
"6f3bb1292618b6fd1c268f061c90d7fd");
"6f3bb1292618b6fd1c2f8b27ff88e0eb");
private static final byte[] RFC_BOB_PUB = hex(
"de9edb7d7b7dc1b4d35b61c2ece43527" +
"3cf1cfa7673a7ee35f19c7ddc4d7b1bf");
"de9edb7d7b7dc1b4d35b61c2ece43537" +
"3f8343c85b78674dadfc7e146f882b4f");
private static final byte[] RFC_SHARED = hex(
"4a5d9d5ba4ce2de1728e3bf480350f25" +
"e07e21c947d19e3376f09b3c1e161742");
/**
* RFC 7748 §6.1: Alice computes shared secret using her private key and
* RFC 8037 §A.6: Alice computes shared secret using her private key and
* Bob's public key; result must equal the known shared secret.
*/
@Test
@ -133,12 +134,12 @@ public class WolfCryptX25519KeyAgreementTest {
ka.doPhase(bobPub, true);
byte[] shared = ka.generateSecret();
assertArrayEquals("RFC 7748 §6.1 Alice-side shared secret mismatch",
assertArrayEquals("RFC 8037 §A.6 Alice-side shared secret mismatch",
RFC_SHARED, shared);
}
/**
* RFC 7748 §6.1: Bob computes shared secret using his private key and
* RFC 8037 §A.6: Bob computes shared secret using his private key and
* Alice's public key; result must equal the known shared secret.
*/
@Test
@ -156,7 +157,7 @@ public class WolfCryptX25519KeyAgreementTest {
ka.doPhase(alicePub, true);
byte[] shared = ka.generateSecret();
assertArrayEquals("RFC 7748 §6.1 Bob-side shared secret mismatch",
assertArrayEquals("RFC 8037 §A.6 Bob-side shared secret mismatch",
RFC_SHARED, shared);
}