Skip to content

Commit a4be378

Browse files
authored
Add support for the ML-DSA Context (#31)
Signed-off-by: John Gray <[email protected]>
1 parent 8b8cc11 commit a4be378

File tree

6 files changed

+235
-26
lines changed

6 files changed

+235
-26
lines changed

RELEASE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
liboqs-java version 0.1.0
1+
liboqs-java version 0.2.0
22
=========================
33

44
About
@@ -14,3 +14,5 @@ Release notes
1414
=============
1515

1616
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.
17+
18+
Release 0.2.0 from January 2025 added support for Signature and Verify API's which accept a Context String.

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<groupId>org.openquantumsafe</groupId>
77
<artifactId>liboqs-java</artifactId>
88
<packaging>jar</packaging>
9-
<version>1.0</version>
9+
<version>2.0</version>
1010
<name>liboqs-java: Java wrapper for liboqs</name>
1111
<description>liboqs-java offers a Java wrapper providing quantum-resistant cryptographic algorithms via liboqs.</description>
1212

src/main/c/Signature.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ JNIEXPORT jint JNICALL Java_org_openquantumsafe_Signature_sign
122122

123123
OQS_SIG *sig = (OQS_SIG *) getHandle(env, obj, "native_sig_handle_");
124124
size_t len_sig;
125+
125126
OQS_STATUS rv_ = OQS_SIG_sign(sig, (uint8_t*)signature_native, &len_sig,
126127
(uint8_t*)message_native, message_len,
127128
(uint8_t*)secret_key_native);
@@ -173,3 +174,81 @@ JNIEXPORT jboolean JNICALL Java_org_openquantumsafe_Signature_verify
173174

174175
return (rv_ == OQS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
175176
}
177+
178+
/*
179+
* Class: org_openquantumsafe_Signature
180+
* Method: sign_with_ctx_str
181+
* Signature: ([BLjava/lang/Long;[BJ[B)I
182+
*/
183+
JNIEXPORT jint JNICALL Java_org_openquantumsafe_Signature_sign_1with_1ctx_1str
184+
(JNIEnv * env, jobject obj, jbyteArray jsignature, jobject sig_len_obj,
185+
jbyteArray jmessage, jlong message_len, jbyteArray jctx, jlong ctx_len,
186+
jbyteArray jsecret_key)
187+
{
188+
// Convert to jbyte arrays
189+
jbyte *signature_native = (*env)->GetByteArrayElements(env, jsignature, 0);
190+
jbyte *message_native = (*env)->GetByteArrayElements(env, jmessage, 0);
191+
jbyte *ctx_native = (*env)->GetByteArrayElements(env, jctx, 0);
192+
jbyte *secret_key_native = (*env)->GetByteArrayElements(env, jsecret_key, 0);
193+
194+
OQS_SIG *sig = (OQS_SIG *) getHandle(env, obj, "native_sig_handle_");
195+
size_t len_sig;
196+
OQS_STATUS rv_ = OQS_SIG_sign_with_ctx_str(sig, (uint8_t*)signature_native, &len_sig,
197+
(uint8_t*)message_native, message_len,
198+
(uint8_t*)ctx_native, ctx_len,
199+
(uint8_t*)secret_key_native);
200+
201+
// fill java signature bytes
202+
(*env)->SetByteArrayRegion(env, jsignature, 0, len_sig, (jbyte*) signature_native);
203+
204+
// fill java object signature length
205+
jfieldID value_fid = (*env)->GetFieldID(env,
206+
(*env)->GetObjectClass(env, sig_len_obj),
207+
"value", "Ljava/lang/Object;");
208+
jclass cls = (*env)->FindClass(env, "java/lang/Long");
209+
jobject jlong_obj = (*env)->NewObject(env, cls,
210+
(*env)->GetMethodID(env, cls, "<init>", "(J)V"),
211+
(jlong) len_sig);
212+
(*env)->SetObjectField(env, sig_len_obj, value_fid, jlong_obj);
213+
214+
// Release C memory
215+
(*env)->ReleaseByteArrayElements(env, jsignature, signature_native, 0);
216+
(*env)->ReleaseByteArrayElements(env, jmessage, message_native, JNI_ABORT);
217+
(*env)->ReleaseByteArrayElements(env, jctx, ctx_native, JNI_ABORT);
218+
(*env)->ReleaseByteArrayElements(env, jsecret_key, secret_key_native, JNI_ABORT);
219+
220+
return (rv_ == OQS_SUCCESS) ? 0 : -1;
221+
}
222+
223+
/*
224+
* Class: org_openquantumsafe_Signature
225+
* Method: verify_with_ctx_str
226+
* Signature: ([BJ[BJ[B)Z
227+
*/
228+
JNIEXPORT jboolean JNICALL Java_org_openquantumsafe_Signature_verify_1with_1ctx_1str
229+
(JNIEnv *env, jobject obj, jbyteArray jmessage, jlong message_len,
230+
jbyteArray jsignature, jlong signature_len, jbyteArray jctx, jlong ctx_len,
231+
jbyteArray jpublic_key)
232+
{
233+
// Convert to jbyte arrays
234+
jbyte *message_native = (*env)->GetByteArrayElements(env, jmessage, 0);
235+
jbyte *signature_native = (*env)->GetByteArrayElements(env, jsignature, 0);
236+
jbyte *ctx_native = (*env)->GetByteArrayElements(env, jctx, 0);
237+
jbyte *public_key_native = (*env)->GetByteArrayElements(env, jpublic_key, 0);
238+
239+
OQS_SIG *sig = (OQS_SIG *) getHandle(env, obj, "native_sig_handle_");
240+
OQS_STATUS rv_ = OQS_SIG_verify_with_ctx_str(sig, (uint8_t*) message_native, message_len,
241+
(uint8_t*) signature_native, signature_len,
242+
(uint8_t*) ctx_native, ctx_len,
243+
(uint8_t*) public_key_native);
244+
245+
// Release C memory
246+
(*env)->ReleaseByteArrayElements(env, jsignature, signature_native, JNI_ABORT);
247+
(*env)->ReleaseByteArrayElements(env, jmessage, message_native, JNI_ABORT);
248+
(*env)->ReleaseByteArrayElements(env, jctx, ctx_native, JNI_ABORT);
249+
(*env)->ReleaseByteArrayElements(env, jpublic_key, public_key_native, JNI_ABORT);
250+
251+
return (rv_ == OQS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
252+
}
253+
254+

src/main/c/Signature.h

Lines changed: 16 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/java/org/openquantumsafe/Signature.java

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,51 @@ private native int sign(byte[] signature, Mutable<Long> signature_len_ret,
146146
private native boolean verify(byte[] message, long message_len,
147147
byte[] signature, long signature_len,
148148
byte[] public_key);
149+
150+
/**
151+
* \brief Wrapper for OQS_API OQS_STATUS OQS_SIG_sign_with_ctx_str(const OQS_SIG *sig,
152+
* uint8_t *signature,
153+
* size_t *signature_len,
154+
* const uint8_t *message,
155+
* size_t message_len,
156+
* const uint8_t *ctx,
157+
* size_t ctx_len,
158+
* const uint8_t *secret_key);
159+
* \param signature
160+
* \param signature_len_ret
161+
* \param message
162+
* \param message_len
163+
* \param ctx
164+
* \param ctx_len
165+
* \param secret_key
166+
* \return Status
167+
*/
168+
private native int sign_with_ctx_str(byte[] signature, Mutable<Long> signature_len_ret,
169+
byte[] message, long message_len, byte[] ctx, long ctx_len,
170+
byte[] secret_key);
171+
172+
/**
173+
* \brief Wrapper for OQS_API OQS_STATUS OQS_SIG_verify_with_ctx_str(const OQS_SIG *sig,
174+
* const uint8_t *message,
175+
* size_t message_len,
176+
* const uint8_t *signature,
177+
* size_t signature_len,
178+
* const uint8_t *ctx,
179+
* size_t ctx_len,
180+
* const uint8_t *public_key);
181+
* \param message
182+
* \param message_len
183+
* \param signature
184+
* \param signature_len
185+
* \param ctx
186+
* \param ctx_len
187+
* \param public_key
188+
* \return True if the signature is valid, false otherwise
189+
*/
190+
private native boolean verify_with_ctx_str(byte[] message, long message_len,
191+
byte[] signature, long signature_len,
192+
byte[] ctx, long ctx_len,
193+
byte[] public_key);
149194

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

221266
return verify(message, message.length, signature, signature.length, public_key);
222267
}
268+
269+
/**
270+
* \brief Invoke native sign method
271+
* \param message
272+
* \param ctx
273+
* \return signature
274+
*/
275+
public byte[] sign(byte[] message, byte[] ctx) throws RuntimeException {
276+
if (this.secret_key_.length != alg_details_.length_secret_key) {
277+
throw new RuntimeException("Incorrect secret key length, " +
278+
"make sure you specify one in the " +
279+
"constructor or run generate_keypair()");
280+
}
281+
byte[] signature = new byte[(int) alg_details_.max_length_signature];
282+
Mutable<Long> signature_len_ret = new Mutable<>();
283+
int ctx_len = (ctx == null) ? 0 : ctx.length;
284+
int rv_= sign_with_ctx_str(signature, signature_len_ret,
285+
message, message.length,
286+
ctx, ctx_len,
287+
this.secret_key_);
288+
long actual_signature_len = signature_len_ret.value;
289+
byte[] actual_signature = new byte[(int) actual_signature_len];
290+
System.arraycopy(signature, 0,
291+
actual_signature, 0, (int) actual_signature_len);
292+
if (rv_ != 0) throw new RuntimeException("Cannot sign message");
293+
return actual_signature;
294+
}
295+
296+
/**
297+
* \brief Invoke native verify method
298+
* \param message
299+
* \param signature
300+
* \param ctx
301+
* \param public_key
302+
* \return True if the signature is valid, false otherwise
303+
*/
304+
public boolean verify(byte[] message, byte[] signature, byte[] ctx, byte[] public_key)
305+
throws RuntimeException {
306+
if (public_key.length != alg_details_.length_public_key) {
307+
throw new RuntimeException("Incorrect public key length");
308+
}
309+
if (signature.length > alg_details_.max_length_signature) {
310+
throw new RuntimeException("Incorrect signature length");
311+
}
312+
313+
int ctx_len = (ctx == null) ? 0 : ctx.length;
314+
315+
return verify_with_ctx_str(message, message.length, signature, signature.length, ctx, ctx_len, public_key);
316+
}
223317

224318
/**
225319
* \brief Print Signature. If a SignatureDetails object is not

src/test/java/org/openquantumsafe/SigTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
import org.junit.jupiter.api.Test;
66
import org.junit.jupiter.params.ParameterizedTest;
77
import org.junit.jupiter.params.provider.MethodSource;
8+
9+
// import static org.junit.Assert.fail;
810
import static org.junit.jupiter.api.Assertions.assertTrue;
911

1012
import java.util.ArrayList;
13+
import java.util.Arrays;
1114
import java.util.stream.Stream;
1215

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

2731
/**
@@ -53,6 +57,36 @@ public void testAllSigs(String sig_name) {
5357
sb.append("\033[0;32m").append("PASSED").append("\033[0m");
5458
System.out.println(sb.toString());
5559
}
60+
61+
/**
62+
* Test Sigs with context.
63+
*/
64+
@ParameterizedTest(name = "Testing {arguments}")
65+
@MethodSource("getContextSupportedAlgsAsStream")
66+
public void testSigsWithContext(String sig_name) {
67+
byte[] context = "01234567890".getBytes();
68+
StringBuilder sb = new StringBuilder();
69+
sb.append(sig_name);
70+
sb.append(String.format("%1$" + (40 - sig_name.length()) + "s", ""));
71+
72+
// Create signer and verifier
73+
Signature signer = new Signature(sig_name);
74+
Signature verifier = new Signature(sig_name);
75+
76+
// Generate signer key pair
77+
byte[] signer_public_key = signer.generate_keypair();
78+
79+
// Sign the message
80+
byte[] signature = signer.sign(message, context);
81+
82+
// Verify the signature
83+
boolean is_valid = verifier.verify(message, signature, context, signer_public_key);
84+
assertTrue(is_valid, sig_name);
85+
86+
// If successful print Sig name, otherwise an exception will be thrown
87+
sb.append("\033[0;32m").append("PASSED").append("\033[0m");
88+
System.out.println(sb.toString());
89+
}
5690

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

106+
/**
107+
* Method to convert the list of ML-DSA Sigs to a stream for input to testAllSigs
108+
*/
109+
private static Stream<String> getContextSupportedAlgsAsStream() {
110+
return Arrays.asList(
111+
"ML-DSA-44", "ML-DSA-65", "ML-DSA-87"
112+
).parallelStream();
113+
}
72114
}

0 commit comments

Comments
 (0)