diff --git a/js/MLS_JS.ml b/js/MLS_JS.ml index 95d5ded..5ba8d1c 100644 --- a/js/MLS_JS.ml +++ b/js/MLS_JS.ml @@ -56,38 +56,55 @@ let option_bytes_of_uint8array o = 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; + 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 + let proposals = Array.to_list (Js.to_array 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 + val group_info_ = uint8array_of_bytes group_info end let js_of_create_key_package_result { key_package; keystore_key; keystore_value } = object%js - val key_package = uint8array_of_bytes key_package + val key_package_ = uint8array_of_bytes key_package val keystore_key_ = uint8array_of_bytes keystore_key val keystore_value_ = uint8array_of_bytes keystore_value end +let js_of_processed_message_content = function + | ApplicationMessage bytes -> + Obj.magic object%js + val kind = Js.string "ApplicationMessage" + val message = uint8array_of_bytes bytes + end + | Proposal uv -> + Obj.magic object%js + val kind = Js.string "Proposal" + val unvalidated_proposal_ = uv + end + | Commit uc -> + Obj.magic object%js + val kind = Js.string "Commit" + val unvalidated_commit_ = uc + end + let js_of_processed_message { group_id; epoch; sender; authenticated_data1; content } = object%js - val group_id = uint8array_of_bytes group_id + val group_id_ = uint8array_of_bytes group_id val epoch = 0 (* TODO *) val sender = sender (* TODO *) - val authenticated_data = uint8array_of_bytes authenticated_data1 - val content = content + val authenticated_data_ = uint8array_of_bytes authenticated_data1 + val content = js_of_processed_message_content content end let _ = @@ -131,7 +148,7 @@ let _ = Js.some cp method mkX509Credential kp chain = - let chain = List.map bytes_of_uint8array (Array.to_list chain) in + let chain = List.map bytes_of_uint8array (Array.to_list (Js.to_array chain)) in let* cp = call_c mk_x509_credential kp chain in Js.some cp @@ -141,7 +158,7 @@ let _ = method createKeyPackage cp = let$ ckpr = call_ce create_key_package cp in - Js.some ckpr + Js.some (js_of_create_key_package_result ckpr) method createGroup cp = let$ g = call_ce create_group cp in @@ -185,7 +202,7 @@ let _ = method getNewCredentials uv = let* r = call_c get_new_credentials uv in - Js.some (Array.of_list r) + Js.some (Js.array (Array.of_list r)) method getNewCredential uv = let* r = call_c get_new_credential uv in @@ -195,19 +212,19 @@ let _ = let bytes = bytes_of_uint8array bytes in let* pm, g = call_c process_message g bytes in Js.some (object%js - val processed_message = js_of_processed_message pm + val processed_message_ = js_of_processed_message pm val group = g end) method iHerebyDeclareThatIHaveCheckedTheNewCredentialsAndValidateTheCommit uv = - Js.some (i_hereby_declare_that_i_have_checked_the_new_credentials_and_validate_the_commit uv) + Js.some (call_c i_hereby_declare_that_i_have_checked_the_new_credentials_and_validate_the_commit uv) method mergeCommit g vc = let* g = call_c merge_commit g vc in Js.some g method iHerebyDeclareThatIHaveCheckedTheNewCredentialsAndValidateTheProposal up = - Js.some (i_hereby_declare_that_i_have_checked_the_new_credentials_and_validate_the_proposal up) + Js.some (call_c i_hereby_declare_that_i_have_checked_the_new_credentials_and_validate_the_proposal up) method queueNewProposal g vp = let* g = call_c queue_new_proposal g vp in @@ -216,7 +233,7 @@ let _ = method sendApplicationMessage g fp m = let fp = framing_params_of_js fp in let m = bytes_of_uint8array m in - let$ message, g = call_ce g fp m in + let$ message, g = call_ce send_application_message g fp m in Js.some (object%js val message = uint8array_of_bytes message val group = g @@ -253,8 +270,8 @@ let _ = let commit_params = commit_params_of_js commit_params in let$ create_commit_result, mls_group = call_ce create_commit mls_group framing_params commit_params in Js.some object%js - val create_commit_result = js_of_create_commit_result create_commit_result - val mls_group = mls_group + val create_commit_result_ = js_of_create_commit_result create_commit_result + val group_ = mls_group end method createAddProposal kp = diff --git a/js/index.js b/js/index.js index 0b52881..21df5d0 100644 --- a/js/index.js +++ b/js/index.js @@ -177,6 +177,31 @@ if (typeof module !== undefined) // NEW MLS API setEntropy: MLS.setEntropy, setCiphersuite: MLS.setCiphersuite, + generateSignatureKeyPair: MLS.generateSignatureKeyPair, + getSignaturePublicKey: MLS.getSignaturePublicKey, + mkBasicCredential: MLS.mkBasicCredential, + mkX509Credential: MLS.mkX509Credential, + getPublicCredential: MLS.getPublicCredential, + createKeyPackage: MLS.createKeyPackage, + createGroup: MLS.createGroup, + startJoinGroup: MLS.startJoinGroup, + continueJoinGroup: MLS.continueJoinGroup, + finalizeJoinGroup: MLS.finalizeJoinGroup, + exportSecret: MLS.exportSecret, + epochAuthenticator: MLS.epochAuthenticator, + epoch: MLS.epoch, + groupId: MLS.groupId, + getNewCredentials: MLS.getNewCredentials, + getNewCredential: MLS.getNewCredential, + processMessage: MLS.processMessage, + iHerebyDeclareThatIHaveCheckedTheNewCredentialsAndValidateTheCommit: MLS.iHerebyDeclareThatIHaveCheckedTheNewCredentialsAndValidateTheCommit, + mergeCommit: MLS.mergeCommit, + iHerebyDeclareThatIHaveCheckedTheNewCredentialsAndValidateTheProposal: MLS.iHerebyDeclareThatIHaveCheckedTheNewCredentialsAndValidateTheProposal, + queueNewProposal: MLS.queueNewProposal, + sendApplicationMessage: MLS.sendApplicationMessage, + proposeAddMember: MLS.proposeAddMember, + proposeRemoveMember: MLS.proposeRemoveMember, + proposeRemoveMyself: MLS.proposeRemoveMyself, createCommit: MLS.createCommit, createAddProposal: MLS.createAddProposal, createRemoveProposal: MLS.createRemoveProposal, diff --git a/js/test.js b/js/test.js index d926860..d4a70fd 100644 --- a/js/test.js +++ b/js/test.js @@ -84,6 +84,85 @@ var test_main = () => { console.log(`JS-driven test took ${t2 - t1} milliseconds.`); }; +var test_new = () => { + // A test for the new, more general API. We start with some warm-up. + + // This is the only supported one for now, we plan to expose AES-GCM soo. + MLS.setCiphersuite("mls_128_dhkemx25519_chacha20poly1305_sha256_ed25519"); + // The source of entropy is customizable. + MLS.setEntropy((n) => crypto.getRandomValues(new Uint8Array(n))); + + // Key pairs are opaque. + let signKeyPair_A = MLS.generateSignatureKeyPair(); + console.log("Generated keyPair for A, public = ", MLS.getSignaturePublicKey(signKeyPair_A)); + + // We now distinguish a credential from a key package. Note that the identity + // is a Uint8Array as well, for uniformity with the rest of the API. + let credPair_A = MLS.mkBasicCredential(signKeyPair_A, Buffer.from("Alice", "ascii")); + let completeKeyPackage_A = MLS.createKeyPackage(credPair_A); + let store_A = {}; + console.log(completeKeyPackage_A); + store_A[completeKeyPackage_A.keystore_key] = completeKeyPackage_A.keystore_value; + let keyPackage_A = completeKeyPackage_A.key_package; + + let group_A = MLS.createGroup(credPair_A); + console.log("Updated the store =", store_A, "and created a group with just A, epoch =", MLS.epoch(group_A)); + + // Same thing for B. + let signKeyPair_B = MLS.generateSignatureKeyPair(); + let credPair_B = MLS.mkBasicCredential(signKeyPair_B, Buffer.from("Bob", "ascii")); + let completeKeyPackage_B = MLS.createKeyPackage(credPair_B); + let store_B = {}; + console.log(completeKeyPackage_B); + store_B[completeKeyPackage_B.keystore_key] = completeKeyPackage_B.keystore_value; + let keyPackage_B = completeKeyPackage_B.key_package; + + // TODO: perhaps we would want to allow authenticated_data being null? + let framingParams = { encrypt: true, padding_size: 0, authenticated_data: new Uint8Array([]) }; + let addProposal = MLS.createAddProposal(keyPackage_B); + let commitParams = { + // Extra proposals to include in the commit + proposals: [ addProposal ], + // Should we inline the ratchet tree in the Welcome messages? + inline_tree: true, + // Should we force the UpdatePath even if we could do an add-only commit? + force_update: true, + // Options for the generation of the new leaf node + leaf_node_params: {} + }; + ({ create_commit_result: { welcome, commit, group_info }, group: group_A } = MLS.createCommit(group_A, framingParams, commitParams)); + console.log("welcome message for B", welcome); + console.log("commit message", commit); + + // A processes the echo of the add + ({ group: group_A, processed_message: { content } } = MLS.processMessage(group_A, commit)); + // Note that beyond content, there are also fields for: epoch, sender, authenticated data, + // and group_id. + console.assert(content.kind == "Commit", "Processed message is not a commit!!"); + let validated_commit = MLS.iHerebyDeclareThatIHaveCheckedTheNewCredentialsAndValidateTheCommit(content.unvalidated_commit); + group_A = MLS.mergeCommit(group_A, validated_commit); + + // B creates its fresh state via the welcome message (try returning null + // always and you'll see the error). + let out = MLS.startJoinGroup(welcome, (k) => (k in store_B) ? store_B[k] : null); + out = MLS.continueJoinGroup(out, null); + group_B = MLS.finalizeJoinGroup(out); + console.log("B joined the group", groupId); + + // B says hello + ({ message, group: group_B } = MLS.sendApplicationMessage(group_B, framingParams, Buffer.from("hello", "ascii"))); + + // B processes the echo of the message + ({ group: group_B, processed_message: { content } } = MLS.processMessage(group_B, message)); + console.assert(content.kind == "ApplicationMessage", "Processed message is not an application message!!"); + console.log((new TextDecoder()).decode(content.message, "ascii")); + + // A receives the message + ({ message, group: group_A } = MLS.processMessage(group_A, message)); + console.assert(content.kind == "ApplicationMessage", "Processed message is not an application message!!"); + console.log((new TextDecoder()).decode(content.message, "ascii")); +}; + if (typeof module !== "undefined") { (async () => { // Load the WASM modules, and instruct the MLS node module to use NodeCrypto @@ -101,6 +180,7 @@ if (typeof module !== "undefined") { console.log("Test starting"); await test_main(); + await test_new(); console.log("done\n") })(); }