diff --git a/RELEASE.md b/RELEASE.md
index 4858b5b..98f791b 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,4 +1,4 @@
-liboqs-java version 0.1.0
+liboqs-java version 0.2.0
=========================
About
@@ -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.
diff --git a/pom.xml b/pom.xml
index b80ea94..bad7e78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.openquantumsafe
liboqs-java
jar
- 1.0
+ 2.0
liboqs-java: Java wrapper for liboqs
liboqs-java offers a Java wrapper providing quantum-resistant cryptographic algorithms via liboqs.
diff --git a/src/main/c/Signature.c b/src/main/c/Signature.c
index e2203c1..00efd07 100644
--- a/src/main/c/Signature.c
+++ b/src/main/c/Signature.c
@@ -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);
@@ -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, "", "(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;
+}
+
+
diff --git a/src/main/c/Signature.h b/src/main/c/Signature.h
index 611c4f8..9a36969 100644
--- a/src/main/c/Signature.h
+++ b/src/main/c/Signature.h
@@ -41,43 +41,35 @@ JNIEXPORT jint JNICALL Java_org_openquantumsafe_Signature_generate_1keypair
/*
* Class: org_openquantumsafe_Signature
- * Method: import_secret_key
- * Signature: ([B)V
- */
-JNIEXPORT void JNICALL Java_org_openquantumsafe_Signature_import_1secret_1key
- (JNIEnv *, jobject, jbyteArray);
-
-/*
- * Class: org_openquantumsafe_Signature
- * Method: export_public_key
- * Signature: ([B)V
+ * Method: sign
+ * Signature: ([BLorg/openquantumsafe/Signature/Mutable;[BJ[B)I
*/
-JNIEXPORT void JNICALL Java_org_openquantumsafe_Signature_export_1public_1key
- (JNIEnv *, jobject, jbyteArray);
+JNIEXPORT jint JNICALL Java_org_openquantumsafe_Signature_sign
+ (JNIEnv *, jobject, jbyteArray, jobject, jbyteArray, jlong, jbyteArray);
/*
* Class: org_openquantumsafe_Signature
- * Method: export_secret_key
- * Signature: ([B)V
+ * Method: verify
+ * Signature: ([BJ[BJ[B)Z
*/
-JNIEXPORT void JNICALL Java_org_openquantumsafe_Signature_export_1secret_1key
- (JNIEnv *, jobject, jbyteArray);
+JNIEXPORT jboolean JNICALL Java_org_openquantumsafe_Signature_verify
+ (JNIEnv *, jobject, jbyteArray, jlong, jbyteArray, jlong, jbyteArray);
/*
* Class: org_openquantumsafe_Signature
- * Method: sign
- * Signature: ([BLjava/lang/Long;[BJ[B)I
+ * Method: sign_with_ctx_str
+ * Signature: ([BLorg/openquantumsafe/Signature/Mutable;[BJ[BJ[B)I
*/
-JNIEXPORT jint JNICALL Java_org_openquantumsafe_Signature_sign
- (JNIEnv *, jobject, jbyteArray, jobject, jbyteArray, jlong, jbyteArray);
+JNIEXPORT jint JNICALL Java_org_openquantumsafe_Signature_sign_1with_1ctx_1str
+ (JNIEnv *, jobject, jbyteArray, jobject, jbyteArray, jlong, jbyteArray, jlong, jbyteArray);
/*
* Class: org_openquantumsafe_Signature
- * Method: verify
- * Signature: ([BJ[BJ[B)Z
+ * Method: verify_with_ctx_str
+ * Signature: ([BJ[BJ[BJ[B)Z
*/
-JNIEXPORT jboolean JNICALL Java_org_openquantumsafe_Signature_verify
- (JNIEnv *, jobject, jbyteArray, jlong, jbyteArray, jlong, jbyteArray);
+JNIEXPORT jboolean JNICALL Java_org_openquantumsafe_Signature_verify_1with_1ctx_1str
+ (JNIEnv *, jobject, jbyteArray, jlong, jbyteArray, jlong, jbyteArray, jlong, jbyteArray);
#ifdef __cplusplus
}
diff --git a/src/main/java/org/openquantumsafe/Signature.java b/src/main/java/org/openquantumsafe/Signature.java
index e4128e8..2055877 100644
--- a/src/main/java/org/openquantumsafe/Signature.java
+++ b/src/main/java/org/openquantumsafe/Signature.java
@@ -146,6 +146,51 @@ private native int sign(byte[] signature, Mutable 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 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
@@ -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 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
diff --git a/src/test/java/org/openquantumsafe/SigTest.java b/src/test/java/org/openquantumsafe/SigTest.java
index 07fa4fa..d1f283f 100644
--- a/src/test/java/org/openquantumsafe/SigTest.java
+++ b/src/test/java/org/openquantumsafe/SigTest.java
@@ -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 {
@@ -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 + "]" );
}
/**
@@ -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
@@ -69,4 +103,12 @@ private static Stream getEnabledSigsAsStream() {
return enabled_sigs.parallelStream();
}
+ /**
+ * Method to convert the list of ML-DSA Sigs to a stream for input to testAllSigs
+ */
+ private static Stream getContextSupportedAlgsAsStream() {
+ return Arrays.asList(
+ "ML-DSA-44", "ML-DSA-65", "ML-DSA-87"
+ ).parallelStream();
+ }
}