From 3a89fc019d83a1621a985cd743393725a5ad0104 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 6 Sep 2023 10:25:14 -0700 Subject: [PATCH] checks to prevent repeated delegations to public keys --- .../identity/src/identity/delegation.test.ts | 47 +++++++++++++++---- packages/identity/src/identity/delegation.ts | 38 +++++++++++---- packages/identity/src/identity/errors.ts | 5 +- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index 015547941..33e8b6c4e 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -92,9 +92,9 @@ test('DelegationChain can be serialized to and from JSON', async () => { expect(middleToBottomActual).toEqual(middleToBottom); }); -test.skip('DelegationChain has a maximum length of 20 delegations', async () => { +test('DelegationChain has a maximum length of 20 delegations', async () => { const identities = new Array(21).fill(0).map((_, i) => createIdentity(i)); - const signedDelegations: DelegationChain[] = []; + let prevDelegationChain: DelegationChain | undefined; for (let i = 0; i < identities.length - 1; i++) { const identity = identities[i]; @@ -112,19 +112,19 @@ test.skip('DelegationChain has a maximum length of 20 delegations', async () => nextIdentity.getPublicKey(), expiry_date, { - previous: signedDelegations[i - 1], + previous: prevDelegationChain, }, ); } - signedDelegations.push(signedDelegation); + prevDelegationChain = signedDelegation; } - expect(signedDelegations.length).toEqual(20); + expect(prevDelegationChain?.delegations.length).toEqual(20); const secondLastIdentity = identities[identities.length - 2]; const lastIdentity = identities[identities.length - 1]; expect( DelegationChain.create(secondLastIdentity, lastIdentity.getPublicKey(), expiry_date, { - previous: signedDelegations[signedDelegations.length - 1], + previous: prevDelegationChain, }), ).rejects.toThrow('Delegation chain cannot exceed 20'); }); @@ -149,9 +149,9 @@ test('Delegation targets cannot exceed 1_000', () => { }); test('Delegation chains cannot repeat public keys', async () => { - const root = createIdentity(0); + const root = createIdentity(2); const middle = createIdentity(1); - const bottom = createIdentity(2); + const bottom = createIdentity(0); const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date); const middleToBottom = await DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { @@ -172,3 +172,34 @@ test('Delegation chains cannot repeat public keys', async () => { }), ).rejects.toThrow('Cannot repeat public keys in a delegation chain.'); }); + +test('Cannot create a delegation chain with an outdated expiry', async () => { + const root = createIdentity(2); + const middle = createIdentity(1); + const bottom = createIdentity(0); + + const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date); + + expect( + DelegationChain.create(middle, bottom.getPublicKey(), new Date(0), { + previous: rootToMiddle, + }), + ).rejects.toThrow('Delegation expiration date cannot be in the past.'); +}); + +test('Cannot create a delegation chain with an outdated delegation', async () => { + const root = createIdentity(2); + const middle = createIdentity(1); + const bottom = createIdentity(0); + + const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date); + + // Modify the delegation to have an expiry of 0. + (rootToMiddle.delegations[0].delegation as any).expiration = BigInt(0); + + expect( + DelegationChain.create(middle, bottom.getPublicKey(), new Date(0), { + previous: rootToMiddle, + }), + ).rejects.toThrow('Previous delegation in the chain has expired.'); +}); diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 869a9d7b2..163d898c2 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -114,7 +114,6 @@ async function _createSingleDelegation( if (!to.toDer) { throw new DelegationError('The to public key does not have a toDer method.'); } - const delegation: Delegation = new Delegation( to.toDer(), BigInt(+expiration) * BigInt(1000000), // In nanoseconds. @@ -195,11 +194,34 @@ export class DelegationChain { targets?: Principal[]; } = {}, ): Promise { + /** + * Validations + */ + + // Validate expiration + if (expiration.getTime() < Date.now()) { + throw new DelegationError('Delegation expiration date cannot be in the past.'); + } if (options.previous) { + // Ensure we aren't extending beyond the maximum delegation chain length. + if (options.previous.delegations.length >= MAXIMUM_DELEGATION_CHAIN_LENGTH) { + throw new DelegationError( + `Delegation chain cannot exceed ${MAXIMUM_DELEGATION_CHAIN_LENGTH}`, + ); + } + + // Ensure that public keys are not repeated in the chain. const usedPublicKeys = new Set(); let currentPublicKey = to.toDer(); for (const delegation of options.previous.delegations) { + // Ensure that previous delegations have not expired. + if (delegation.delegation.expiration < BigInt(Date.now()) * BigInt(1000000)) { + throw new DelegationError( + 'Previous delegation in the chain has expired.', + delegation.delegation.expiration, + ); + } if (usedPublicKeys.has(currentPublicKey)) { throw new DelegationError('Delegation target cannot be repeated in the chain.'); } @@ -207,8 +229,12 @@ export class DelegationChain { currentPublicKey = delegation.delegation.pubkey; } + // Ensure that the last public key in the chain not repeated. if (usedPublicKeys.has(currentPublicKey)) { - throw new DelegationError('Error: Cannot repeat public keys in a delegation chain.'); + throw new DelegationError('Error: Cannot repeat public keys in a delegation chain.', { + currentPublicKey, + usedPublicKeys, + }); } } @@ -273,13 +299,7 @@ export class DelegationChain { public readonly delegations: SignedDelegation[], public readonly publicKey: DerEncodedPublicKey, public readonly previous?: DelegationChain, - ) { - if (delegations.length > MAXIMUM_DELEGATION_CHAIN_LENGTH) { - throw new DelegationError( - `Delegation chain cannot exceed ${MAXIMUM_DELEGATION_CHAIN_LENGTH}`, - ); - } - } + ) {} public toJSON(): JsonnableDelegationChain { return { diff --git a/packages/identity/src/identity/errors.ts b/packages/identity/src/identity/errors.ts index 84b7a609e..24da1d2e4 100644 --- a/packages/identity/src/identity/errors.ts +++ b/packages/identity/src/identity/errors.ts @@ -6,8 +6,11 @@ export class IdentityError extends Error { } export class DelegationError extends IdentityError { - constructor(public readonly message: string) { + constructor(public readonly message: string, loggedValue?: unknown) { super(message); Object.setPrototypeOf(this, DelegationError.prototype); + if (loggedValue) { + console.error(loggedValue); + } } }