ts-mls
Version:
[](https://github.com/LukaJCB/ts-mls/actions/workflows/ci.yml) [](https://badge.fury.io/js/ts-mls) [) {
test(`Proposal Validation ${cs}`, async () => {
await remove(cs);
});
}
async function remove(cipherSuite) {
const impl = await getCiphersuiteImpl(getCiphersuiteFromName(cipherSuite));
const aliceCredential = { credentialType: "basic", identity: new TextEncoder().encode("alice") };
const alice = await generateKeyPackage(aliceCredential, defaultCapabilities(), defaultLifetime, [], impl);
const groupId = new TextEncoder().encode("group1");
let aliceGroup = await createGroup(groupId, alice.publicPackage, alice.privatePackage, [], impl);
const bobCredential = { credentialType: "basic", identity: new TextEncoder().encode("bob") };
const bob = await generateKeyPackage(bobCredential, defaultCapabilities(), defaultLifetime, [], impl);
const charlieCredential = { credentialType: "basic", identity: new TextEncoder().encode("charlie") };
const charlie = await generateKeyPackage(charlieCredential, defaultCapabilities(), defaultLifetime, [], impl);
const addBobProposal = {
proposalType: "add",
add: {
keyPackage: bob.publicPackage,
},
};
const addCharlieProposal = {
proposalType: "add",
add: {
keyPackage: charlie.publicPackage,
},
};
const addBobAndCharlieCommitResult = await createCommit(aliceGroup, emptyPskIndex, false, [addBobProposal, addCharlieProposal], impl);
aliceGroup = addBobAndCharlieCommitResult.newState;
let bobGroup = await joinGroup(addBobAndCharlieCommitResult.welcome, bob.publicPackage, bob.privatePackage, emptyPskIndex, impl, aliceGroup.ratchetTree);
expect(bobGroup.keySchedule.epochAuthenticator).toStrictEqual(aliceGroup.keySchedule.epochAuthenticator);
let charlieGroup = await joinGroup(addBobAndCharlieCommitResult.welcome, charlie.publicPackage, charlie.privatePackage, emptyPskIndex, impl, aliceGroup.ratchetTree);
expect(charlieGroup.keySchedule.epochAuthenticator).toStrictEqual(aliceGroup.keySchedule.epochAuthenticator);
const removeBobProposal = {
proposalType: "remove",
remove: {
removed: bobGroup.privatePath.leafIndex,
},
};
const removeBobProposal2 = {
proposalType: "remove",
remove: {
removed: bobGroup.privatePath.leafIndex,
},
};
// can't remove same leaf node twice
await expect(createCommit(aliceGroup, emptyPskIndex, false, [removeBobProposal, removeBobProposal2], impl)).rejects.toThrow(ValidationError);
// can't add someone already in the group
await expect(createCommit(aliceGroup, emptyPskIndex, false, [addBobProposal], impl)).rejects.toThrow(ValidationError);
const proposalInvalidRequiredCapabilities = {
proposalType: "group_context_extensions",
groupContextExtensions: {
extensions: [{ extensionType: "required_capabilities", extensionData: new Uint8Array([1, 2]) }],
},
};
//can't add groupContextExtensions with invalid requiredCapabilities
await expect(createCommit(aliceGroup, emptyPskIndex, false, [proposalInvalidRequiredCapabilities], impl)).rejects.toThrow(CodecError);
const proposalRequiredCapabilities = {
proposalType: "group_context_extensions",
groupContextExtensions: {
extensions: [
{
extensionType: "required_capabilities",
extensionData: encodeRequiredCapabilities({ extensionTypes: [], proposalTypes: [99], credentialTypes: [] }),
},
],
},
};
//can't add groupContextExtensions with requiredCapabilities that members don't support
await expect(createCommit(aliceGroup, emptyPskIndex, false, [proposalRequiredCapabilities], impl)).rejects.toThrow(ValidationError);
const dianaCredential = { credentialType: "basic", identity: new TextEncoder().encode("diana") };
const diana = await generateKeyPackage(dianaCredential, { ...defaultCapabilities(), credentials: ["basic"] }, defaultLifetime, [], impl);
const addDiana = {
proposalType: "add",
add: {
keyPackage: diana.publicPackage,
},
};
const proposalRequiredCapabilitiesX509 = {
proposalType: "group_context_extensions",
groupContextExtensions: {
extensions: [
{
extensionType: "required_capabilities",
extensionData: encodeRequiredCapabilities({
extensionTypes: [],
proposalTypes: [],
credentialTypes: ["x509"],
}),
},
],
},
};
//can't add groupContextExtensions with requiredCapabilities that newly added member doesn't support
await expect(createCommit(aliceGroup, emptyPskIndex, false, [addDiana, proposalRequiredCapabilitiesX509], impl)).rejects.toThrow(ValidationError);
const proposalInvalidExternalSenders = {
proposalType: "group_context_extensions",
groupContextExtensions: {
extensions: [{ extensionType: "external_senders", extensionData: new Uint8Array([1, 2]) }],
},
};
//can't add groupContextExtensions with invalid requiredCapabilities
await expect(createCommit(aliceGroup, emptyPskIndex, false, [proposalInvalidExternalSenders], impl)).rejects.toThrow(CodecError);
const badCredential = { credentialType: "basic", identity: new TextEncoder().encode("NOT GOOD") };
const proposalUnauthenticatedExternalSenders = {
proposalType: "group_context_extensions",
groupContextExtensions: {
extensions: [
{
extensionType: "external_senders",
extensionData: encodeExternalSender({ credential: badCredential, signaturePublicKey: new Uint8Array() }),
},
],
},
};
const authService = {
async validateCredential(c, _pk) {
if (c.credentialType === "basic" && constantTimeEqual(c.identity, badCredential.identity))
return false;
return true;
},
};
//can't add groupContextExtensions with external senders that can't be auth'd
await expect(createCommit(withAuthService(aliceGroup, authService), emptyPskIndex, false, [proposalUnauthenticatedExternalSenders], impl)).rejects.toThrow(ValidationError);
const edwardCredential = { credentialType: "basic", identity: new TextEncoder().encode("edward") };
const edward = await generateKeyPackage(edwardCredential, { ...defaultCapabilities(), credentials: ["basic"] }, defaultLifetime, [], impl);
const addEdward = {
proposalType: "add",
add: {
keyPackage: edward.publicPackage,
},
};
const authServiceEdward = {
async validateCredential(c, _pk) {
if (c.credentialType === "basic" && constantTimeEqual(c.identity, edwardCredential.identity))
return false;
return true;
},
};
//can't add a member with invalid credentials
await expect(createCommit(withAuthService(aliceGroup, authServiceEdward), emptyPskIndex, false, [addEdward], impl)).rejects.toThrow(ValidationError);
const frankCredential = createCustomCredential(5, new Uint8Array([1, 2]));
const frank = await generateKeyPackage(frankCredential, defaultCapabilities(), defaultLifetime, [], impl);
const addFrank = {
proposalType: "add",
add: { keyPackage: frank.publicPackage },
};
//can't add leafNode with an unsupported credentialType
await expect(createCommit(aliceGroup, emptyPskIndex, false, [addFrank], impl)).rejects.toThrow(ValidationError);
const georgeCredential = { credentialType: "basic", identity: new TextEncoder().encode("george") };
const georgeExtension = { extensionType: 8545, extensionData: new Uint8Array() };
const george = await generateKeyPackage(georgeCredential, defaultCapabilities(), defaultLifetime, [georgeExtension], impl);
const addGeorge = {
proposalType: "add",
add: { keyPackage: george.publicPackage },
};
//can't add leafNode with an unsupported extension
await expect(createCommit(aliceGroup, emptyPskIndex, false, [addGeorge], impl)).rejects.toThrow(ValidationError);
const updateLeafNode = {
leafNodeSource: "update",
signaturePublicKey: alice.publicPackage.leafNode.signaturePublicKey,
hpkePublicKey: alice.publicPackage.leafNode.hpkePublicKey,
credential: alice.publicPackage.leafNode.credential,
capabilities: alice.publicPackage.leafNode.capabilities,
extensions: alice.publicPackage.leafNode.extensions,
signature: new Uint8Array(),
};
const updateProposal = {
proposalType: "update",
update: {
leafNode: updateLeafNode,
},
};
// commiter can't update themselves
await expect(createCommit(aliceGroup, emptyPskIndex, false, [updateProposal], impl)).rejects.toThrow(ValidationError);
const removeProposal = {
proposalType: "remove",
remove: {
removed: 0,
},
};
// committer can't remove themselves
await expect(createCommit(aliceGroup, emptyPskIndex, false, [removeProposal], impl)).rejects.toThrow(ValidationError);
const hannahCredential = { credentialType: "basic", identity: new TextEncoder().encode("bob") };
const hannah = await generateKeyPackage(hannahCredential, defaultCapabilities(), defaultLifetime, [], impl);
const addHannahProposal = {
proposalType: "add",
add: {
keyPackage: hannah.publicPackage,
},
};
// can't add the same keypackage twice
await expect(createCommit(aliceGroup, emptyPskIndex, false, [addHannahProposal, addHannahProposal], impl)).rejects.toThrow(ValidationError);
const pskId = new Uint8Array([1, 2, 3, 4]);
const pskProposal = {
proposalType: "psk",
psk: {
preSharedKeyId: {
psktype: "external",
pskId,
pskNonce: new Uint8Array([5, 6, 7, 8]),
},
},
};
// can't reference the same psk in multiple proposals
await expect(createCommit(aliceGroup, emptyPskIndex, false, [pskProposal, pskProposal], impl)).rejects.toThrow(ValidationError);
const groupContextExtensionsProposal = {
proposalType: "group_context_extensions",
groupContextExtensions: {
extensions: [],
},
};
// can't use multiple group_context_extensions proposals
await expect(createCommit(aliceGroup, emptyPskIndex, false, [groupContextExtensionsProposal, groupContextExtensionsProposal], impl)).rejects.toThrow(ValidationError);
}
function withAuthService(state, authService) {
return { ...state, clientConfig: { ...state.clientConfig, authService: authService } };
}
//# sourceMappingURL=proposalValidation.test.js.map