ts-mls
Version:
[](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml) [](https://badge.fury.io/js/ts-mls) [) {
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