From 5466a0d8871ab5395f6d94722e5657876ec1dbb5 Mon Sep 17 00:00:00 2001 From: Jonathan Protzenko Date: Fri, 14 Jun 2024 15:07:25 -0700 Subject: [PATCH] WIP: start exposing the new api alongside the old one --- js/MLS_JS.ml | 103 ++++++++++++++++++++++++++++++++++++++++++++++++--- js/index.js | 53 ++++---------------------- js/test.js | 2 +- 3 files changed, 107 insertions(+), 51 deletions(-) diff --git a/js/MLS_JS.ml b/js/MLS_JS.ml index 3577dd6..c8be0f6 100644 --- a/js/MLS_JS.ml +++ b/js/MLS_JS.ml @@ -1,6 +1,10 @@ open Js_of_ocaml open JsHelpers +open MLS_API +(* HELPERS *) + +(* Effectful monadic operator with extra debugging for MLS_Result. *) let (let*) r f = match r with | MLS_Result.Success s -> @@ -12,14 +16,106 @@ let (let*) r f = print_endline ("ProtocolError: " ^ s); Js.null +let print_and_fail s = + (* Makes sure something shows up in the JS console *) + print_endline ("ERROR: " ^ s); + assert false + +(* GLOBAL STATE: entroy, bytes instance, etc. *) + +let entropy_state: Obj.t = Obj.repr () + +let extract_entropy: (MLS_Crypto_Builtins.hacl_star_bytes, Obj.t) MLS_API.entropy ref = + ref { extract_entropy = fun _ _ -> print_and_fail "Please call setEntropy first" } + +let crypto_bytes_ = ref None + +let crypto_bytes () = + match !crypto_bytes_ with + | Some cb -> cb + | None -> print_endline ("Must call setCiphersuite first"); assert false + +(* CONVERSIONS *) + +let framing_params_of_js o = { + encrypt = o##.encrypt; + padding_size = Z.of_int o##.padding_size; + authenticated_data = bytes_of_uint8array o##.authenticated_data; +} + +let leaf_node_params_of_js o = + { nothing_yet = () } + +let commit_params_of_js o = + let proposals = Array.to_list o##.proposals in + let inline_tree = o##.inline_tree in + let force_update = o##.force_update in + let leaf_node_params = leaf_node_params_of_js o##.leaf_node_params in + { proposals; inline_tree; force_update; leaf_node_params } + +let js_of_create_commit_result { commit; welcome; group_info } = object%js + val commit = uint8array_of_bytes commit + val welcome = Js.Opt.option (Option.map uint8array_of_bytes welcome) + val group_info = uint8array_of_bytes group_info +end + let _ = Js.export_all (object%js - method test: _ = - MLS_Test_Internal.test () + + (* NEW API: global state *) method setCrypto (arg: _ Js.t) = Js.Unsafe.global##._MyCrypto := arg + (* Expects a JS function that takes a Number and returns that many random + bytes as a UInt8Array. *) + method setEntropy (f: _ Js.t) = + extract_entropy := { extract_entropy = fun n state -> + let bytes = bytes_of_uint8array (Js.Unsafe.fun_call f [| Js.Unsafe.inject (Z.to_int n) |]) in + MLS_Result.Success bytes, state + } + + (* Expects a JS string that contains the expected ciphersuite *) + method setCiphersuite (cs: _ Js.t) = + match Js.to_string cs with + | _ -> + (* TODO: actually offer more ciphersuites *) + if !crypto_bytes_ <> None then + print_and_fail "Cannot dynamically change ciphersuites"; + crypto_bytes_ := Some MLS_Crypto_Builtins.(mk_concrete_crypto_bytes AC_mls_128_dhkemx25519_chacha20poly1305_sha256_ed25519) + + (* NEW API: binders for MLS.API.fst (via MLS_API.ml) *) + method createCommit mls_group framing_params commit_params = + (* mls_group is left "as is" and is not roundtripped via serialization *) + let framing_params = framing_params_of_js framing_params in + let commit_params = commit_params_of_js commit_params in + let res, _entropy_state = create_commit (crypto_bytes ()) () + (!extract_entropy) mls_group framing_params commit_params entropy_state + in + let* create_commit_result, mls_group = res in + Js.some object%js + val create_commit_result = js_of_create_commit_result create_commit_result + val mls_group = mls_group + end + + method createAddProposal kp = + let* p = create_add_proposal (crypto_bytes ()) (bytes_of_uint8array kp) in + Js.some p + + method createRemoveProposal group removed = + let* p = create_remove_proposal (crypto_bytes ()) group removed in + Js.some p + + (* INTERNAL SELF-TEST *) + + method test: _ = + MLS_Test_Internal.test () + + (* OLD API BASED ON MLS.fst / MLS.ml -- TODO: REMOVE *) + + method currentEpoch (s: MLS.state) = + Z.to_int (MLS.current_epoch s) + method freshKeyPair1 (e: Typed_array.uint8Array Js.t) = let e = bytes_of_uint8array e in let* pub_key, priv_key = MLS.fresh_key_pair e in @@ -39,9 +135,6 @@ let _ = val hash = uint8array_of_bytes hash end) - method currentEpoch (s: MLS.state) = - Z.to_int (MLS.current_epoch s) - method create1 (e: Typed_array.uint8Array Js.t) (credential: _ Js.t) (priv: MLS.signature_key) (group_id: Js.js_string Js.t) = diff --git a/js/index.js b/js/index.js index f4d53cf..0b52881 100644 --- a/js/index.js +++ b/js/index.js @@ -1,7 +1,7 @@ var debug = true; //////////////////////////////////////////////////////////////////////////////// -// MLS Lib // +// OLD MLS Lib // //////////////////////////////////////////////////////////////////////////////// let freshKeyPairDebug = 0; @@ -162,52 +162,9 @@ function HaclCrypto(Hacl) { }; } -// WIP, TODO: port all primitives to node-crypto -function NodeCrypto(Hacl) { - return { - sha2_256_hash: (b) => node_crypto.createHash('sha256').update(b).digest(), - - hkdf_sha2_256_extract: (salt, ikm) => Hacl.HKDF.extract_sha2_256(salt, ikm)[0], - - hkdf_sha2_256_expand: (prk, info, size) => Hacl.HKDF.expand_sha2_256(prk, info, size)[0], - - sha2_512_hash: (b) => node_crypto.createHash('sha512').update(b).digest(), - - // const ecdh = node_crypto.createECDH("X25519"); - // ecdh.setPrivateKey(sk); - // return new Uint8Array(ecdh.getPrivateKey().buffer); - ed25519_secret_to_public: (sk) => Hacl.Ed25519.secret_to_public(sk)[0], - - ed25519_sign: (sk, msg) => Hacl.Ed25519.sign(sk, msg)[0], - - ed25519_verify: (pk, msg, signature) => Hacl.Ed25519.verify(pk, msg, signature)[0], - - chacha20_poly1305_encrypt: (key, iv, ad, pt) => { - let cipher = node_crypto.createCipheriv('chacha20-poly1305', key, iv, { authTagLength: 16 }); - cipher.setAAD(ad); - let ct = cipher.update(pt); - cipher.final(); - return [ new Uint8Array(ct.buffer), new Uint8Array(cipher.getAuthTag().buffer) ]; - }, - - chacha20_poly1305_decrypt: (key, iv, ad, ct, tag) => { - let decipher = node_crypto.createDecipheriv("chacha20-poly1305", key, iv, { authTagLength: 16 }); - decipher.setAAD(ad); - let pt = decipher.update(ct); - decipher.setAuthTag(tag); - try { - decipher.final(); - return pt; - } catch (e) { - return null; - } - } - }; -} - if (typeof module !== undefined) module.exports = { - // MLS API + // OLD MLS API freshKeyPair, freshKeyPackage, create, @@ -217,6 +174,12 @@ if (typeof module !== undefined) processGroupMessage, processWelcomeMessage, currentEpoch: MLS.currentEpoch, + // NEW MLS API + setEntropy: MLS.setEntropy, + setCiphersuite: MLS.setCiphersuite, + createCommit: MLS.createCommit, + createAddProposal: MLS.createAddProposal, + createRemoveProposal: MLS.createRemoveProposal, // MLS Crypto API (the individual primitives) setCrypto: MLS.setCrypto, HaclCrypto, diff --git a/js/test.js b/js/test.js index f0c13e5..d926860 100644 --- a/js/test.js +++ b/js/test.js @@ -6,7 +6,7 @@ var HaclWasm = require("./wasm/api.js"); //////////////////////////////////////////////////////////////////////////////// var test_main = () => { - // A demo of how to drive the high-level API. + // A demo of how to drive the (OLD) high-level API. console.log("Page loaded"); // A self-test mostly for my own debugging.