Skip to content

Commit

Permalink
Add support for the ML-DSA Context (#31)
Browse files Browse the repository at this point in the history
Signed-off-by: John Gray <[email protected]>
  • Loading branch information
johngray-dev authored Jan 13, 2025
1 parent 8b8cc11 commit a4be378
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 26 deletions.
4 changes: 3 additions & 1 deletion RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
liboqs-java version 0.1.0
liboqs-java version 0.2.0
=========================

About
Expand All @@ -14,3 +14,5 @@ Release notes
=============

The initial release of liboqs-java was released on July 8, 2020. Its release page on GitHub is https://github.com/open-quantum-safe/liboqs-java/releases/tag/0.1.0.

Release 0.2.0 from January 2025 added support for Signature and Verify API's which accept a Context String.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<groupId>org.openquantumsafe</groupId>
<artifactId>liboqs-java</artifactId>
<packaging>jar</packaging>
<version>1.0</version>
<version>2.0</version>
<name>liboqs-java: Java wrapper for liboqs</name>
<description>liboqs-java offers a Java wrapper providing quantum-resistant cryptographic algorithms via liboqs.</description>

Expand Down
79 changes: 79 additions & 0 deletions src/main/c/Signature.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ JNIEXPORT jint JNICALL Java_org_openquantumsafe_Signature_sign

OQS_SIG *sig = (OQS_SIG *) getHandle(env, obj, "native_sig_handle_");
size_t len_sig;

OQS_STATUS rv_ = OQS_SIG_sign(sig, (uint8_t*)signature_native, &len_sig,
(uint8_t*)message_native, message_len,
(uint8_t*)secret_key_native);
Expand Down Expand Up @@ -173,3 +174,81 @@ JNIEXPORT jboolean JNICALL Java_org_openquantumsafe_Signature_verify

return (rv_ == OQS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

/*
* Class: org_openquantumsafe_Signature
* Method: sign_with_ctx_str
* Signature: ([BLjava/lang/Long;[BJ[B)I
*/
JNIEXPORT jint JNICALL Java_org_openquantumsafe_Signature_sign_1with_1ctx_1str
(JNIEnv * env, jobject obj, jbyteArray jsignature, jobject sig_len_obj,
jbyteArray jmessage, jlong message_len, jbyteArray jctx, jlong ctx_len,
jbyteArray jsecret_key)
{
// Convert to jbyte arrays
jbyte *signature_native = (*env)->GetByteArrayElements(env, jsignature, 0);
jbyte *message_native = (*env)->GetByteArrayElements(env, jmessage, 0);
jbyte *ctx_native = (*env)->GetByteArrayElements(env, jctx, 0);
jbyte *secret_key_native = (*env)->GetByteArrayElements(env, jsecret_key, 0);

OQS_SIG *sig = (OQS_SIG *) getHandle(env, obj, "native_sig_handle_");
size_t len_sig;
OQS_STATUS rv_ = OQS_SIG_sign_with_ctx_str(sig, (uint8_t*)signature_native, &len_sig,
(uint8_t*)message_native, message_len,
(uint8_t*)ctx_native, ctx_len,
(uint8_t*)secret_key_native);

// fill java signature bytes
(*env)->SetByteArrayRegion(env, jsignature, 0, len_sig, (jbyte*) signature_native);

// fill java object signature length
jfieldID value_fid = (*env)->GetFieldID(env,
(*env)->GetObjectClass(env, sig_len_obj),
"value", "Ljava/lang/Object;");
jclass cls = (*env)->FindClass(env, "java/lang/Long");
jobject jlong_obj = (*env)->NewObject(env, cls,
(*env)->GetMethodID(env, cls, "<init>", "(J)V"),
(jlong) len_sig);
(*env)->SetObjectField(env, sig_len_obj, value_fid, jlong_obj);

// Release C memory
(*env)->ReleaseByteArrayElements(env, jsignature, signature_native, 0);
(*env)->ReleaseByteArrayElements(env, jmessage, message_native, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, jctx, ctx_native, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, jsecret_key, secret_key_native, JNI_ABORT);

return (rv_ == OQS_SUCCESS) ? 0 : -1;
}

/*
* Class: org_openquantumsafe_Signature
* Method: verify_with_ctx_str
* Signature: ([BJ[BJ[B)Z
*/
JNIEXPORT jboolean JNICALL Java_org_openquantumsafe_Signature_verify_1with_1ctx_1str
(JNIEnv *env, jobject obj, jbyteArray jmessage, jlong message_len,
jbyteArray jsignature, jlong signature_len, jbyteArray jctx, jlong ctx_len,
jbyteArray jpublic_key)
{
// Convert to jbyte arrays
jbyte *message_native = (*env)->GetByteArrayElements(env, jmessage, 0);
jbyte *signature_native = (*env)->GetByteArrayElements(env, jsignature, 0);
jbyte *ctx_native = (*env)->GetByteArrayElements(env, jctx, 0);
jbyte *public_key_native = (*env)->GetByteArrayElements(env, jpublic_key, 0);

OQS_SIG *sig = (OQS_SIG *) getHandle(env, obj, "native_sig_handle_");
OQS_STATUS rv_ = OQS_SIG_verify_with_ctx_str(sig, (uint8_t*) message_native, message_len,
(uint8_t*) signature_native, signature_len,
(uint8_t*) ctx_native, ctx_len,
(uint8_t*) public_key_native);

// Release C memory
(*env)->ReleaseByteArrayElements(env, jsignature, signature_native, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, jmessage, message_native, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, jctx, ctx_native, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, jpublic_key, public_key_native, JNI_ABORT);

return (rv_ == OQS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}


40 changes: 16 additions & 24 deletions src/main/c/Signature.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 94 additions & 0 deletions src/main/java/org/openquantumsafe/Signature.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,51 @@ private native int sign(byte[] signature, Mutable<Long> signature_len_ret,
private native boolean verify(byte[] message, long message_len,
byte[] signature, long signature_len,
byte[] public_key);

/**
* \brief Wrapper for OQS_API OQS_STATUS OQS_SIG_sign_with_ctx_str(const OQS_SIG *sig,
* uint8_t *signature,
* size_t *signature_len,
* const uint8_t *message,
* size_t message_len,
* const uint8_t *ctx,
* size_t ctx_len,
* const uint8_t *secret_key);
* \param signature
* \param signature_len_ret
* \param message
* \param message_len
* \param ctx
* \param ctx_len
* \param secret_key
* \return Status
*/
private native int sign_with_ctx_str(byte[] signature, Mutable<Long> signature_len_ret,
byte[] message, long message_len, byte[] ctx, long ctx_len,
byte[] secret_key);

/**
* \brief Wrapper for OQS_API OQS_STATUS OQS_SIG_verify_with_ctx_str(const OQS_SIG *sig,
* const uint8_t *message,
* size_t message_len,
* const uint8_t *signature,
* size_t signature_len,
* const uint8_t *ctx,
* size_t ctx_len,
* const uint8_t *public_key);
* \param message
* \param message_len
* \param signature
* \param signature_len
* \param ctx
* \param ctx_len
* \param public_key
* \return True if the signature is valid, false otherwise
*/
private native boolean verify_with_ctx_str(byte[] message, long message_len,
byte[] signature, long signature_len,
byte[] ctx, long ctx_len,
byte[] public_key);

/**
* \brief Invoke native free_sig
Expand Down Expand Up @@ -220,6 +265,55 @@ public boolean verify(byte[] message, byte[] signature, byte[] public_key)

return verify(message, message.length, signature, signature.length, public_key);
}

/**
* \brief Invoke native sign method
* \param message
* \param ctx
* \return signature
*/
public byte[] sign(byte[] message, byte[] ctx) throws RuntimeException {
if (this.secret_key_.length != alg_details_.length_secret_key) {
throw new RuntimeException("Incorrect secret key length, " +
"make sure you specify one in the " +
"constructor or run generate_keypair()");
}
byte[] signature = new byte[(int) alg_details_.max_length_signature];
Mutable<Long> signature_len_ret = new Mutable<>();
int ctx_len = (ctx == null) ? 0 : ctx.length;
int rv_= sign_with_ctx_str(signature, signature_len_ret,
message, message.length,
ctx, ctx_len,
this.secret_key_);
long actual_signature_len = signature_len_ret.value;
byte[] actual_signature = new byte[(int) actual_signature_len];
System.arraycopy(signature, 0,
actual_signature, 0, (int) actual_signature_len);
if (rv_ != 0) throw new RuntimeException("Cannot sign message");
return actual_signature;
}

/**
* \brief Invoke native verify method
* \param message
* \param signature
* \param ctx
* \param public_key
* \return True if the signature is valid, false otherwise
*/
public boolean verify(byte[] message, byte[] signature, byte[] ctx, byte[] public_key)
throws RuntimeException {
if (public_key.length != alg_details_.length_public_key) {
throw new RuntimeException("Incorrect public key length");
}
if (signature.length > alg_details_.max_length_signature) {
throw new RuntimeException("Incorrect signature length");
}

int ctx_len = (ctx == null) ? 0 : ctx.length;

return verify_with_ctx_str(message, message.length, signature, signature.length, ctx, ctx_len, public_key);
}

/**
* \brief Print Signature. If a SignatureDetails object is not
Expand Down
42 changes: 42 additions & 0 deletions src/test/java/org/openquantumsafe/SigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

// import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;

public class SigTest {
Expand All @@ -22,6 +25,7 @@ public class SigTest {
public static void init(){
System.out.println("Initialize list of enabled Signatures");
enabled_sigs = Sigs.get_enabled_sigs();
System.out.println("Enabled signatures: [" + enabled_sigs + "]" );
}

/**
Expand Down Expand Up @@ -53,6 +57,36 @@ public void testAllSigs(String sig_name) {
sb.append("\033[0;32m").append("PASSED").append("\033[0m");
System.out.println(sb.toString());
}

/**
* Test Sigs with context.
*/
@ParameterizedTest(name = "Testing {arguments}")
@MethodSource("getContextSupportedAlgsAsStream")
public void testSigsWithContext(String sig_name) {
byte[] context = "01234567890".getBytes();
StringBuilder sb = new StringBuilder();
sb.append(sig_name);
sb.append(String.format("%1$" + (40 - sig_name.length()) + "s", ""));

// Create signer and verifier
Signature signer = new Signature(sig_name);
Signature verifier = new Signature(sig_name);

// Generate signer key pair
byte[] signer_public_key = signer.generate_keypair();

// Sign the message
byte[] signature = signer.sign(message, context);

// Verify the signature
boolean is_valid = verifier.verify(message, signature, context, signer_public_key);
assertTrue(is_valid, sig_name);

// If successful print Sig name, otherwise an exception will be thrown
sb.append("\033[0;32m").append("PASSED").append("\033[0m");
System.out.println(sb.toString());
}

/**
* Test the MechanismNotSupported Exception
Expand All @@ -69,4 +103,12 @@ private static Stream<String> getEnabledSigsAsStream() {
return enabled_sigs.parallelStream();
}

/**
* Method to convert the list of ML-DSA Sigs to a stream for input to testAllSigs
*/
private static Stream<String> getContextSupportedAlgsAsStream() {
return Arrays.asList(
"ML-DSA-44", "ML-DSA-65", "ML-DSA-87"
).parallelStream();
}
}

0 comments on commit a4be378

Please sign in to comment.