UNPKG

ts-mls

Version:

[![CI](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml/badge.svg)](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml) [![npm version](https://badge.fury.io/js/ts-mls.svg)](https://badge.fury.io/js/ts-mls) [![Coverage Status](https://co

215 lines 13.9 kB
import json from "../../test_vectors/message-protection.json"; import { hexToBytes } from "@noble/ciphers/utils"; import { getCiphersuiteFromId, getCiphersuiteImpl, getCiphersuiteNameFromId, } from "../../src/crypto/ciphersuite"; import { decodeMlsMessage } from "../../src/message"; import { protect, unprotectPrivateMessage } from "../../src/messageProtection"; import { createContentCommitSignature } from "../../src/framedContent"; import { decodeProposal, encodeProposal } from "../../src/proposal"; import { decodeCommit, encodeCommit } from "../../src/commit"; import { createSecretTree } from "../../src/secretTree"; import { protectApplicationData, protectProposal } from "../../src/messageProtection"; import { protectProposalPublic, protectPublicMessage, unprotectPublicMessage } from "../../src/messageProtectionPublic"; import { defaultKeyRetentionConfig } from "../../src/keyRetentionConfig"; import { defaultCapabilities } from "../../src/defaultCapabilities"; import { UsageError } from "../../src/mlsError"; import { defaultPaddingConfig } from "../../src/paddingConfig"; for (const [index, x] of json.entries()) { test(`message-protection test vectors ${index}`, async () => { const impl = await getCiphersuiteImpl(getCiphersuiteFromId(x.cipher_suite)); await testMessageProtection(x, impl); }); } async function testMessageProtection(data, impl) { const gc = { version: "mls10", cipherSuite: getCiphersuiteNameFromId(data.cipher_suite), groupId: hexToBytes(data.group_id), epoch: BigInt(data.epoch), treeHash: hexToBytes(data.tree_hash), confirmedTranscriptHash: hexToBytes(data.confirmed_transcript_hash), extensions: [], }; await publicProposal(data, gc, impl); await protectThenUnprotectProposalPublic(data, gc, impl); await publicCommit(data, gc, impl); await protectThenUnprotectCommitPublic(data, gc, impl); await proposal(data, gc, impl); await protectThenUnprotectProposal(data, gc, impl); await application(data, gc, impl); await protectThenUnprotectApplication(data, gc, impl); await commit(data, gc, impl); await protectThenUnprotectCommit(data, gc, impl); await publicApplicationFails(data, gc, impl); } // need to provide a ratchet tree with non blank leaf node so senderData validation doesn't fail const treeForLeafIndex1 = [ undefined, undefined, { nodeType: "leaf", leaf: { leafNodeSource: "commit", hpkePublicKey: new Uint8Array(), signaturePublicKey: new Uint8Array(), capabilities: defaultCapabilities(), parentHash: new Uint8Array(), extensions: [], signature: new Uint8Array(), credential: { credentialType: "basic", identity: new Uint8Array() }, }, }, ]; async function protectThenUnprotectProposalPublic(data, gc, impl) { const p = decodeProposal(hexToBytes(data.proposal), 0); if (p === undefined) throw new Error("could not decode proposal"); const prot = await protectProposalPublic(hexToBytes(data.signature_priv), hexToBytes(data.membership_key), gc, new Uint8Array(), p[0], 1, impl); const unprotected = await unprotectPublicMessage(hexToBytes(data.membership_key), gc, [], prot.publicMessage, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.contentType !== "proposal") throw new Error("could not unprotect mls public message"); expect(encodeProposal(unprotected.content.proposal)).toStrictEqual(hexToBytes(data.proposal)); } async function protectThenUnprotectCommitPublic(data, gc, impl) { const c = decodeCommit(hexToBytes(data.commit), 0); if (c === undefined) throw new Error("could not decode commit"); const confirmationTag = crypto.getRandomValues(new Uint8Array(impl.hpke.keyLength)); // should I be getting this elsewhere? const { framedContent, signature } = await createContentCommitSignature(gc, "mls_public_message", c[0], { leafIndex: 1, senderType: "member" }, new Uint8Array(), hexToBytes(data.signature_priv), impl.signature); const authenticatedContent = { wireformat: "mls_public_message", content: framedContent, auth: { contentType: "commit", signature: signature, confirmationTag }, }; const prot = await protectPublicMessage(hexToBytes(data.membership_key), gc, authenticatedContent, impl); const unprotected = await unprotectPublicMessage(hexToBytes(data.membership_key), gc, [], prot, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.contentType !== "commit") throw new Error("could not unprotect mls public message"); expect(encodeCommit(unprotected.content.commit)).toStrictEqual(hexToBytes(data.commit)); } async function publicProposal(data, gc, impl) { const prop = decodeMlsMessage(hexToBytes(data.proposal_pub), 0); if (prop === undefined || prop[0].wireformat !== "mls_public_message") throw new Error("could not decode mls public message"); const unprotected = await unprotectPublicMessage(hexToBytes(data.membership_key), gc, [], prop[0].publicMessage, impl, hexToBytes(data.signature_pub)); if (unprotected.content.contentType !== "proposal") throw new Error("Could not decode as proposal"); expect(encodeProposal(unprotected.content.proposal)).toStrictEqual(hexToBytes(data.proposal)); } async function publicCommit(data, gc, impl) { const c = decodeMlsMessage(hexToBytes(data.commit_pub), 0); if (c === undefined || c[0].wireformat !== "mls_public_message") throw new Error("could not decode mls public message"); const unprotected = await unprotectPublicMessage(hexToBytes(data.membership_key), gc, [], c[0].publicMessage, impl, hexToBytes(data.signature_pub)); if (unprotected.content.contentType !== "commit") throw new Error("Could not decode as commit"); expect(encodeCommit(unprotected.content.commit)).toStrictEqual(hexToBytes(data.commit)); } async function publicApplicationFails(data, gc, impl) { const privateApplication = decodeMlsMessage(hexToBytes(data.application_priv), 0); if (privateApplication === undefined || privateApplication[0].wireformat !== "mls_private_message") throw new Error("could not decode mls private message"); const secretTree = await createSecretTree(2, hexToBytes(data.encryption_secret), impl.kdf); const unprotected = await unprotectPrivateMessage(hexToBytes(data.sender_data_secret), privateApplication[0].privateMessage, secretTree, treeForLeafIndex1, gc, defaultKeyRetentionConfig, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.content.contentType !== "application") throw new Error("could not unprotect mls private message"); const content = { content: { ...unprotected.content.content, contentType: "application", groupId: gc.groupId, sender: { leafIndex: 0, senderType: "member" }, epoch: gc.epoch, authenticatedData: new Uint8Array(), }, auth: unprotected.content.auth, wireformat: "mls_public_message", }; await expect(protectPublicMessage(hexToBytes(data.membership_key), gc, content, impl)).rejects.toThrow(UsageError); } async function commit(data, gc, impl) { const privateCommit = decodeMlsMessage(hexToBytes(data.commit_priv), 0); if (privateCommit === undefined || privateCommit[0].wireformat !== "mls_private_message") throw new Error("could not decode mls private message"); const secretTree = await createSecretTree(2, hexToBytes(data.encryption_secret), impl.kdf); const unprotected = await unprotectPrivateMessage(hexToBytes(data.sender_data_secret), privateCommit[0].privateMessage, secretTree, treeForLeafIndex1, gc, defaultKeyRetentionConfig, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.content.contentType !== "commit") throw new Error("could not unprotect mls private message"); expect(encodeCommit(unprotected.content.content.commit)).toStrictEqual(hexToBytes(data.commit)); } async function application(data, gc, impl) { const privateApplication = decodeMlsMessage(hexToBytes(data.application_priv), 0); if (privateApplication === undefined || privateApplication[0].wireformat !== "mls_private_message") throw new Error("could not decode mls private message"); const secretTree = await createSecretTree(2, hexToBytes(data.encryption_secret), impl.kdf); const unprotected = await unprotectPrivateMessage(hexToBytes(data.sender_data_secret), privateApplication[0].privateMessage, secretTree, treeForLeafIndex1, gc, defaultKeyRetentionConfig, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.content.contentType !== "application") throw new Error("could not unprotect mls private message"); expect(unprotected.content.content.applicationData).toStrictEqual(hexToBytes(data.application)); } async function protectThenUnprotectProposal(data, gc, impl) { const p = decodeProposal(hexToBytes(data.proposal), 0); if (p === undefined) throw new Error("could not decode proposal"); const secretTree = await createSecretTree(2, hexToBytes(data.encryption_secret), impl.kdf); const pro = await protectProposal(hexToBytes(data.signature_priv), hexToBytes(data.sender_data_secret), p[0], new Uint8Array(), gc, secretTree, 1, defaultPaddingConfig, impl); const unprotected = await unprotectPrivateMessage(hexToBytes(data.sender_data_secret), pro.privateMessage, secretTree, treeForLeafIndex1, gc, defaultKeyRetentionConfig, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.content.contentType !== "proposal") throw new Error("could not unprotect mls private message"); expect(encodeProposal(unprotected.content.content.proposal)).toStrictEqual(hexToBytes(data.proposal)); } async function protectThenUnprotectApplication(data, gc, impl) { const secretTree = await createSecretTree(2, hexToBytes(data.encryption_secret), impl.kdf); const pro = await protectApplicationData(hexToBytes(data.signature_priv), hexToBytes(data.sender_data_secret), hexToBytes(data.application), new Uint8Array(), gc, secretTree, 1, defaultPaddingConfig, impl); const unprotected = await unprotectPrivateMessage(hexToBytes(data.sender_data_secret), pro.privateMessage, secretTree, treeForLeafIndex1, gc, defaultKeyRetentionConfig, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.content.contentType !== "application") throw new Error("could not unprotect mls private message"); expect(unprotected.content.content.applicationData).toStrictEqual(hexToBytes(data.application)); } async function protectThenUnprotectCommit(data, gc, impl) { const c = decodeCommit(hexToBytes(data.commit), 0); if (c === undefined) throw new Error("could not decode commit"); const secretTree = await createSecretTree(2, hexToBytes(data.encryption_secret), impl.kdf); const confirmationTag = crypto.getRandomValues(new Uint8Array(impl.hpke.keyLength)); // should I be getting this elsewhere? const { framedContent, signature } = await createContentCommitSignature(gc, "mls_private_message", c[0], { leafIndex: 1, senderType: "member" }, new Uint8Array(), hexToBytes(data.signature_priv), impl.signature); const content = { ...framedContent, auth: { contentType: framedContent.contentType, signature, confirmationTag, }, }; const pro = await protect(hexToBytes(data.sender_data_secret), new Uint8Array(), gc, secretTree, content, 1, defaultPaddingConfig, impl); const unprotected = await unprotectPrivateMessage(hexToBytes(data.sender_data_secret), pro.privateMessage, secretTree, treeForLeafIndex1, gc, defaultKeyRetentionConfig, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.content.contentType !== "commit") throw new Error("could not unprotect mls private message"); expect(encodeCommit(unprotected.content.content.commit)).toStrictEqual(hexToBytes(data.commit)); } async function proposal(data, gc, impl) { const privateProposal = decodeMlsMessage(hexToBytes(data.proposal_priv), 0); if (privateProposal === undefined || privateProposal[0].wireformat !== "mls_private_message") throw new Error("could not decode mls private message"); const secretTree = await createSecretTree(2, hexToBytes(data.encryption_secret), impl.kdf); const unprotected = await unprotectPrivateMessage(hexToBytes(data.sender_data_secret), privateProposal[0].privateMessage, secretTree, [ undefined, undefined, { nodeType: "leaf", leaf: { leafNodeSource: "commit", hpkePublicKey: new Uint8Array(), signaturePublicKey: new Uint8Array(), capabilities: defaultCapabilities(), parentHash: new Uint8Array(), extensions: [], signature: new Uint8Array(), credential: { credentialType: "basic", identity: new Uint8Array() }, }, }, ], gc, defaultKeyRetentionConfig, impl, hexToBytes(data.signature_pub)); if (unprotected === undefined || unprotected.content.content.contentType !== "proposal") throw new Error("could not unprotect mls private message"); expect(encodeProposal(unprotected.content.content.proposal)).toStrictEqual(hexToBytes(data.proposal)); } //# sourceMappingURL=messageProtection.test.js.map