From e934d3c8937a0e19b95594baad2c0c1bb17e85d1 Mon Sep 17 00:00:00 2001 From: Philippe Auriach <920265+philippeauriach@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:34:21 +0200 Subject: [PATCH] feat: crypto_box_seal (#3) --- README.md | 23 ++++ android/src/main/cpp/sodium-jni.c | 102 ++++++++++++++++++ .../SodiumReactNativeModule.java | 102 ++++++++++++++++-- example/src/App.tsx | 36 +++++++ ios/SodiumReactNative.mm | 46 ++++++++ src/crypto_box.ts | 56 ++++++++++ src/index.ts | 4 +- src/types.ts | 6 ++ 8 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 src/crypto_box.ts diff --git a/README.md b/README.md index 85dbfb5..b5f32c6 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,29 @@ Clamping involves clearing the lowest 3 bits of the result, ensuring the result crypto_secretstream_xchacha20poly1305_TAG_FINAL ``` +#### `crypto_box` + +[Sealed box encryption](https://sodium-friends.github.io/docs/docs/sealedboxencryption) + +#### Methods + +``` + crypto_box_keypair + crypto_box_seal + crypto_box_seal_open +``` + +#### Constants + +``` + crypto_box_SEALBYTES + crypto_box_PUBLICKEYBYTES + crypto_box_SECRETKEYBYTES + crypto_box_SEEDBYTES + crypto_box_NONCEBYTES + crypto_box_MACBYTES +``` + #### `crypto_secretbox` [Secret key box encryption](https://sodium-friends.github.io/docs/docs/secretkeyboxencryption) diff --git a/android/src/main/cpp/sodium-jni.c b/android/src/main/cpp/sodium-jni.c index 7f7f6ba..f52bea4 100644 --- a/android/src/main/cpp/sodium-jni.c +++ b/android/src/main/cpp/sodium-jni.c @@ -1130,6 +1130,108 @@ Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1stream_1noncebytes( return (jint)crypto_stream_NONCEBYTES; } +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1sealbytes( + JNIEnv *env, + jclass clazz +) { + return (jint)crypto_box_SEALBYTES; +} + +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1publickeybytes( + JNIEnv *env, + jclass clazz +) { + return (jint)crypto_box_PUBLICKEYBYTES; +} + +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1secretkeybytes( + JNIEnv *env, + jclass clazz +) { + return (jint)crypto_box_SECRETKEYBYTES; +} + +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1seedbytes( + JNIEnv *env, + jclass clazz +) { + return (jint)crypto_box_SEEDBYTES; +} + +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1noncebytes( + JNIEnv *env, + jclass clazz +) { + return (jint)crypto_box_NONCEBYTES; +} + +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1macbytes( + JNIEnv *env, + jclass clazz +) { + return (jint)crypto_box_MACBYTES; +} + +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1keypair( + JNIEnv *jenv, + jclass clazz, + jbyteArray j_pk, + jbyteArray j_sk +) { + unsigned char *pk = (unsigned char *) (*jenv)->GetByteArrayElements(jenv, j_pk, 0); + unsigned char *sk = (unsigned char *) (*jenv)->GetByteArrayElements(jenv, j_sk, 0); + + int result = crypto_box_keypair(pk, sk); + (*jenv)->ReleaseByteArrayElements(jenv, j_pk, (jbyte *) pk, 0); + (*jenv)->ReleaseByteArrayElements(jenv, j_sk, (jbyte *) sk, 0); + return (jint)result; +} + +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1seal( + JNIEnv *jenv, + jclass clazz, + jbyteArray j_c, + jbyteArray j_m, + jint j_mlen, + jbyteArray j_pk +) { + unsigned char *c = (unsigned char *) (*jenv)->GetByteArrayElements(jenv, j_c, 0); + unsigned char *m = as_unsigned_char_array(jenv, j_m); + unsigned char *pk = as_unsigned_char_array(jenv, j_pk); + + int result = crypto_box_seal(c, m, j_mlen, pk); + (*jenv)->ReleaseByteArrayElements(jenv, j_c, (jbyte *) c, 0); + return (jint)result; +} + +JNIEXPORT jint JNICALL +Java_com_sodiumreactnative_jni_SodiumReactNativeJNI_crypto_1box_1seal_1open( + JNIEnv *jenv, + jclass clazz, + jbyteArray j_m, + jbyteArray j_c, + jint j_clen, + jbyteArray j_pk, + jbyteArray j_sk +) { + unsigned char *m = (unsigned char *) (*jenv)->GetByteArrayElements(jenv, j_m, 0); + unsigned char *c = as_unsigned_char_array(jenv, j_c); + unsigned char *pk = as_unsigned_char_array(jenv, j_pk); + unsigned char *sk = as_unsigned_char_array(jenv, j_sk); + + int result = crypto_box_seal_open(m, c, j_clen, pk, sk); + (*jenv)->ReleaseByteArrayElements(jenv, j_m, (jbyte *) m, 0); + return (jint)result; +} + /* ***************************************************************************** * Key exchange * ***************************************************************************** diff --git a/android/src/main/java/com/sodiumreactnative/SodiumReactNativeModule.java b/android/src/main/java/com/sodiumreactnative/SodiumReactNativeModule.java index 4b9dfab..b88f3d3 100644 --- a/android/src/main/java/com/sodiumreactnative/SodiumReactNativeModule.java +++ b/android/src/main/java/com/sodiumreactnative/SodiumReactNativeModule.java @@ -119,22 +119,22 @@ public Map getConstants() { constants.put("crypto_sign_SECRETKEYBYTES", SodiumReactNative.crypto_sign_secretkeybytes()); constants.put("crypto_stream_KEYBYTES", SodiumReactNative.crypto_stream_keybytes()); constants.put("crypto_stream_NONCEBYTES", SodiumReactNative.crypto_stream_noncebytes()); + constants.put("crypto_box_SEALBYTES", SodiumReactNative.crypto_box_sealbytes()); + constants.put("crypto_box_SEEDBYTES", SodiumReactNative.crypto_box_seedbytes()); + constants.put("crypto_box_PUBLICKEYBYTES", SodiumReactNative.crypto_box_publickeybytes()); + constants.put("crypto_box_SECRETKEYBYTES", SodiumReactNative.crypto_box_secretkeybytes()); + constants.put("crypto_box_NONCEBYTES", SodiumReactNative.crypto_box_noncebytes()); + constants.put("crypto_box_MACBYTES", SodiumReactNative.crypto_box_macbytes()); // These may be useful for future extensions // constants.put("crypto_auth_BYTES", SodiumReactNative.crypto_auth_bytes()); // constants.put("crypto_auth_KEYBYTES", SodiumReactNative.crypto_auth_keybytes()); // constants.put("crypto_hash_BYTES", SodiumReactNative.crypto_hash_bytes()); - // constants.put("crypto_box_SEEDBYTES", SodiumReactNative.crypto_box_seedbytes()); - // constants.put("crypto_box_PUBLICKEYBYTES", SodiumReactNative.crypto_box_publickeybytes()); - // constants.put("crypto_box_SECRETKEYBYTES", SodiumReactNative.crypto_box_secretkeybytes()); - // constants.put("crypto_box_NONCEBYTES", SodiumReactNative.crypto_box_noncebytes()); - // constants.put("crypto_box_MACBYTES", SodiumReactNative.crypto_box_macbytes()); // constants.put("crypto_hash_sha256_STATEBYTES", SodiumReactNative.crypto_hash_sha256_statebytes()); // constants.put("crypto_hash_sha512_STATEBYTES", SodiumReactNative.crypto_hash_sha512_statebytes()); // constants.put("crypto_stream_xor_STATEBYTES", SodiumReactNative.crypto_stream_xor_statebytes()); // constants.put("crypto_stream_chacha20_xor_STATEBYTES", SodiumReactNative.crypto_stream_chacha20_xor_statebytes()); - // constants.put("crypto_box_SEALBYTES", SodiumReactNative.crypto_box_sealbytes()); // constants.put("crypto_hash_sha256_BYTES", SodiumReactNative.crypto_hash_sha256_bytes()); // constants.put("crypto_pwhash_scryptsalsa208sha256_BYTES_MIN", SodiumReactNative.crypto_pwhash_scryptsalsa208sha256_bytes_min()); // constants.put("crypto_pwhash_scryptsalsa208sha256_BYTES_MAX", SodiumReactNative.crypto_pwhash_scryptsalsa208sha256_bytes_max()); @@ -1211,4 +1211,94 @@ public WritableArray sodium_unpad (ReadableArray buf, int padded_buflen, int blo return ArrayUtil.toWritableArray( Arrays.copyOfRange(_buf, 0, unpadded_buflen[0] ) ); } + + @ReactMethod(isBlockingSynchronousMethod = true) + public WritableArray crypto_box_keypair ( + ReadableArray pk, + ReadableArray sk + ) throws Exception { + byte[] _pk = ArgumentsEx.toByteArray(pk); + byte[] _sk = ArgumentsEx.toByteArray(sk); + + try { + ArgumentsEx.check(_pk, SodiumReactNative.crypto_box_publickeybytes(), "ERR_BAD_KEY"); + ArgumentsEx.check(_sk, SodiumReactNative.crypto_box_secretkeybytes(), "ERR_BAD_KEY"); + } catch (Exception e) { + throw e; + } + + int success = SodiumReactNative.crypto_box_keypair(_pk, _sk); + if (success != 0) { + Exception e = new Exception("crypto_box_keypair execution failed"); + throw e; + } + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream( ); + + try { + outputStream.write( _pk ); + outputStream.write( _sk ); + } catch (IOException e) { + throw e; + } + + byte ret[] = outputStream.toByteArray( ); + + return ArrayUtil.toWritableArray(ret); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public WritableArray crypto_box_seal ( + ReadableArray c, + ReadableArray m, + ReadableArray pk + ) throws Exception { + byte[] _c = ArgumentsEx.toByteArray(c); + byte[] _m = ArgumentsEx.toByteArray(m); + byte[] _pk = ArgumentsEx.toByteArray(pk); + + try { + ArgumentsEx.check(_pk, SodiumReactNative.crypto_box_publickeybytes(), "ERR_BAD_KEY"); + ArgumentsEx.check(_c, _m.length + SodiumReactNative.crypto_box_sealbytes(), "ERR_BAD_CIPHERTEXT_LENGTH"); + } catch (Exception e) { + throw e; + } + + int success = SodiumReactNative.crypto_box_seal(_c, _m, _m.length, _pk); + if (success != 0) { + Exception e = new Exception("crypto_box_seal execution failed"); + throw e; + } + + return ArrayUtil.toWritableArray(_c); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public WritableArray crypto_box_seal_open ( + ReadableArray m, + ReadableArray c, + ReadableArray pk, + ReadableArray sk + ) throws Exception { + byte[] _m = ArgumentsEx.toByteArray(m); + byte[] _c = ArgumentsEx.toByteArray(c); + byte[] _pk = ArgumentsEx.toByteArray(pk); + byte[] _sk = ArgumentsEx.toByteArray(sk); + + try { + ArgumentsEx.check(_pk, SodiumReactNative.crypto_box_publickeybytes(), "ERR_BAD_KEY"); + ArgumentsEx.check(_sk, SodiumReactNative.crypto_box_secretkeybytes(), "ERR_BAD_KEY"); + ArgumentsEx.check(_m, _c.length - SodiumReactNative.crypto_box_sealbytes(), "ERR_BAD_PLAINTEXT_LENGTH"); + } catch (Exception e) { + throw e; + } + + int success = SodiumReactNative.crypto_box_seal_open(_m, _c, _c.length, _pk, _sk); + if (success != 0) { + Exception e = new Exception("crypto_box_seal_open execution failed"); + throw e; + } + + return ArrayUtil.toWritableArray(_m); + } } diff --git a/example/src/App.tsx b/example/src/App.tsx index 5540525..99a8d3e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -267,6 +267,37 @@ export default function App() { setResult(decoded); }; + const onCryptoBoxSeal = async () => { + const message = b4a.from('Hello, World!'); + const cipherText = new Uint8Array( + message.byteLength + sodium.crypto_box_SEALBYTES + ); + const pk = new Uint8Array(sodium.crypto_box_PUBLICKEYBYTES); + const sk = new Uint8Array(sodium.crypto_box_SECRETKEYBYTES); + + try { + sodium.crypto_box_keypair(pk, sk); + console.log({ pk: b4a.toString(pk, 'hex') }); + console.log({ sk: b4a.toString(sk, 'hex') }); + sodium.crypto_box_seal(cipherText, message, pk); + const encrypted = b4a.toString(cipherText, 'hex'); + console.log({ encrypted }); + setResult(`Encrypted: ${encrypted}\nDecrypting in 2 seconds...`); + await sleep(2000); + + const decrypted = new Uint8Array( + cipherText.byteLength - sodium.crypto_box_SEALBYTES + ); + sodium.crypto_box_seal_open(decrypted, cipherText, pk, sk); + const decoded = b4a.toString(decrypted); + console.log({ decoded }); + setResult(`Decrypted: ${decoded}`); + } catch (error: any) { + console.log(error); + setResult(error.message); + } + }; + const openTests = () => { setShowTests(true); }; @@ -338,6 +369,11 @@ export default function App() { title="RandomBytesBuf" onPress={onRandomBytesBuf} /> +