From 7a324611f8fc03240a73e3a50c0bfd06eb12f97c Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Mon, 21 Aug 2023 16:48:30 -0700 Subject: [PATCH 01/21] feat: DelegationChain audits for delegation targets and maximum delegations during initialization --- .../__snapshots__/delegation.test.ts.snap | 12 ++++ .../identity/src/identity/delegation.test.ts | 69 ++++++++++++++++++- packages/identity/src/identity/delegation.ts | 36 ++++++++-- packages/identity/src/identity/errors.ts | 13 ++++ 4 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 packages/identity/src/identity/__snapshots__/delegation.test.ts.snap create mode 100644 packages/identity/src/identity/errors.ts diff --git a/packages/identity/src/identity/__snapshots__/delegation.test.ts.snap b/packages/identity/src/identity/__snapshots__/delegation.test.ts.snap new file mode 100644 index 000000000..2fa987dc9 --- /dev/null +++ b/packages/identity/src/identity/__snapshots__/delegation.test.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Delegation can include multiple targets 1`] = ` +Object { + "expiration": "176bb3e7000", + "pubkey": "010203", + "targets": Array [ + "00000000002000030101", + "000000000020008E0101", + ], +} +`; diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index 620b620a1..8500778a4 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -1,6 +1,6 @@ import { SignIdentity } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; -import { DelegationChain } from './delegation'; +import { Delegation, DelegationChain } from './delegation'; import { Ed25519KeyIdentity } from './ed25519'; function createIdentity(seed: number): SignIdentity { @@ -8,6 +8,12 @@ function createIdentity(seed: number): SignIdentity { return Ed25519KeyIdentity.generate(s); } +function randomPrincipal(): Principal { + const bytes = new Uint8Array(29); + crypto.getRandomValues(bytes); + return Principal.fromUint8Array(bytes); +} + test('delegation signs with proper keys (3)', async () => { const root = createIdentity(2); const middle = createIdentity(1); @@ -97,3 +103,64 @@ test('DelegationChain can be serialized to and from JSON', async () => { const middleToBottomActual = DelegationChain.fromJSON(middleToBottomJson); expect(middleToBottomActual).toEqual(middleToBottom); }); + +test('DelegationChain has a maximum length of 20 delegations', async () => { + const identities = new Array(21).fill(0).map((_, i) => createIdentity(i)); + const signedDelegations: DelegationChain[] = []; + + for (let i = 0; i < identities.length - 1; i++) { + const identity = identities[i]; + const nextIdentity = identities[i + 1]; + let signedDelegation: DelegationChain; + if (i === 0) { + signedDelegation = await DelegationChain.create( + identity, + nextIdentity.getPublicKey(), + new Date(1609459200000), + ); + } else { + signedDelegation = await DelegationChain.create( + identity, + nextIdentity.getPublicKey(), + new Date(1609459200000), + { + previous: signedDelegations[i - 1], + }, + ); + } + signedDelegations.push(signedDelegation); + } + expect(signedDelegations.length).toEqual(20); + + const secondLastIdentity = identities[identities.length - 2]; + const lastIdentity = identities[identities.length - 1]; + expect( + DelegationChain.create( + secondLastIdentity, + lastIdentity.getPublicKey(), + new Date(1609459200000), + { + previous: signedDelegations[signedDelegations.length - 1], + }, + ), + ).rejects.toThrow('Delegation chain cannot exceed 20'); +}); + +test('Delegation can include multiple targets', async () => { + const pubkey = new Uint8Array([1, 2, 3]); + const expiration = BigInt(Number(new Date(1609459200000))); + const targets = [ + Principal.fromText('jyi7r-7aaaa-aaaab-aaabq-cai'), + Principal.fromText('u76ha-lyaaa-aaaab-aacha-cai'), + ]; + const delegation = new Delegation(pubkey, expiration, targets); + expect(delegation.targets).toEqual(targets); + expect(delegation.toJSON()).toMatchSnapshot(); +}); + +test('Delegation targets cannot exceed 1_000', () => { + const targets = new Array(1_001).fill(randomPrincipal()); + expect(() => new Delegation(new Uint8Array([1, 2, 3]), BigInt(0), targets)).toThrow( + 'Delegation targets cannot exceed 1000', + ); +}); diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 055a97887..98db784bc 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -9,13 +9,16 @@ import { import { Principal } from '@dfinity/principal'; import * as cbor from 'simple-cbor'; import { fromHexString, toHexString } from '../buffer'; +import { DelegationError } from './errors'; const domainSeparator = new TextEncoder().encode('\x1Aic-request-auth-delegation'); const requestDomainSeparator = new TextEncoder().encode('\x0Aic-request'); +const MAXIMUM_NUMBER_OF_TARGETS = 1_000; +const MAXIMUM_DELEGATION_CHAIN_LENGTH = 20; function _parseBlob(value: unknown): ArrayBuffer { if (typeof value !== 'string' || value.length < 64) { - throw new Error('Invalid public key.'); + throw new DelegationError('Invalid public key.'); } return fromHexString(value); @@ -32,7 +35,11 @@ export class Delegation { public readonly pubkey: ArrayBuffer, public readonly expiration: bigint, public readonly targets?: Principal[], - ) {} + ) { + if (targets && targets?.length > MAXIMUM_NUMBER_OF_TARGETS) { + throw new DelegationError(`Delegation targets cannot exceed ${MAXIMUM_NUMBER_OF_TARGETS}`); + } + } public toCBOR(): cbor.CborValue { // Expiration field needs to be encoded as a u64 specifically. @@ -97,6 +104,17 @@ async function _createSingleDelegation( expiration: Date, targets?: Principal[], ): Promise { + // Validate inputs + if (targets && targets?.length > MAXIMUM_NUMBER_OF_TARGETS) { + throw new DelegationError(`Delegation targets cannot exceed ${MAXIMUM_NUMBER_OF_TARGETS}`); + } + if (!from.sign) { + throw new DelegationError('The from identity does not have a sign method.'); + } + 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. @@ -192,14 +210,14 @@ export class DelegationChain { public static fromJSON(json: string | JsonnableDelegationChain): DelegationChain { const { publicKey, delegations } = typeof json === 'string' ? JSON.parse(json) : json; if (!Array.isArray(delegations)) { - throw new Error('Invalid delegations.'); + throw new DelegationError('Invalid delegations.'); } const parsedDelegations: SignedDelegation[] = delegations.map(signedDelegation => { const { delegation, signature } = signedDelegation; const { pubkey, expiration, targets } = delegation; if (targets !== undefined && !Array.isArray(targets)) { - throw new Error('Invalid targets.'); + throw new DelegationError('Invalid targets.'); } return { @@ -209,7 +227,7 @@ export class DelegationChain { targets && targets.map((t: unknown) => { if (typeof t !== 'string') { - throw new Error('Invalid target.'); + throw new DelegationError('Invalid target.'); } return Principal.fromHex(t); }), @@ -237,7 +255,13 @@ export class DelegationChain { protected constructor( public readonly delegations: SignedDelegation[], public readonly publicKey: DerEncodedPublicKey, - ) {} + ) { + 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 new file mode 100644 index 000000000..84b7a609e --- /dev/null +++ b/packages/identity/src/identity/errors.ts @@ -0,0 +1,13 @@ +export class IdentityError extends Error { + constructor(public readonly message: string) { + super(message); + Object.setPrototypeOf(this, IdentityError.prototype); + } +} + +export class DelegationError extends IdentityError { + constructor(public readonly message: string) { + super(message); + Object.setPrototypeOf(this, DelegationError.prototype); + } +} From 96e90b95db36577c62db1264d26f896bd4cf09e7 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Mon, 21 Aug 2023 16:48:54 -0700 Subject: [PATCH 02/21] changelog --- docs/generated/changelog.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index 3082cfcf9..507881e04 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -12,7 +12,8 @@

Agent-JS Changelog

Version x.x.x

    -
  • +
  • feat: DelegationChain audits for delegation targets and maximum delegations during + initialization

Version 0.19.2

    From 298d37350e4116962d29e05ad3a051cd36b425c5 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Fri, 25 Aug 2023 09:25:14 -0700 Subject: [PATCH 03/21] wip --- .../identity/src/identity/delegation.test.ts | 26 +++++++++++++++++++ packages/identity/src/identity/delegation.ts | 13 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index 8500778a4..f5bfd27a8 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -164,3 +164,29 @@ test('Delegation targets cannot exceed 1_000', () => { 'Delegation targets cannot exceed 1000', ); }); + +test('Delegation chains cannot repeat public keys', async () => { + const root = createIdentity(0); + const middle = createIdentity(1); + const bottom = createIdentity(2); + + const rootToMiddle = await DelegationChain.create( + root, + middle.getPublicKey(), + new Date(1609459200000), + ); + const middleToBottom = await DelegationChain.create( + middle, + bottom.getPublicKey(), + new Date(1609459200000), + { + previous: rootToMiddle, + }, + ); + + expect( + DelegationChain.create(bottom, root.getPublicKey(), new Date(1609459200000), { + previous: middleToBottom, + }), + ).rejects.toThrow('Delegation chain cannot repeat public keys'); +}); diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 98db784bc..4339dc27c 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -195,6 +195,18 @@ export class DelegationChain { targets?: Principal[]; } = {}, ): Promise { + if (options.previous) { + const delegatedKeys: PublicKey[] = [ + ...options.previous.delegations.map(signedDelegation => signedDelegation.delegation.pubkey), + from.getPublicKey(), + to, + ]; + const delegatedKeysSet = new Set(delegatedKeys.map(key => toHexString(key.derKey))); + if (delegatedKeys.length !== delegatedKeysSet.size) { + throw new DelegationError('Delegation chain cannot repeat public keys'); + } + } + const delegation = await _createSingleDelegation(from, to, expiration, options.targets); return new DelegationChain( [...(options.previous?.delegations || []), delegation], @@ -255,6 +267,7 @@ export class DelegationChain { protected constructor( public readonly delegations: SignedDelegation[], public readonly publicKey: DerEncodedPublicKey, + public readonly previous?: DelegationChain, ) { if (delegations.length > MAXIMUM_DELEGATION_CHAIN_LENGTH) { throw new DelegationError( From ef6c17381687fe57ae2b67ad9fd60c028c9cb5a0 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 5 Sep 2023 12:03:00 -0700 Subject: [PATCH 04/21] continuing work on not repeating delegations --- .../identity/src/identity/delegation.test.ts | 96 ++++++++----------- packages/identity/src/identity/delegation.ts | 21 ++-- 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index f5bfd27a8..015547941 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -2,6 +2,7 @@ import { SignIdentity } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; import { Delegation, DelegationChain } from './delegation'; import { Ed25519KeyIdentity } from './ed25519'; +import { toHexString } from '../buffer'; function createIdentity(seed: number): SignIdentity { const s = new Uint8Array([seed, ...new Array(31).fill(0)]); @@ -14,24 +15,21 @@ function randomPrincipal(): Principal { return Principal.fromUint8Array(bytes); } +const expiry_date = new Date(1609459200000); +const current_date = new Date(1609459200000 - 10000); + +jest.useFakeTimers(); +jest.setSystemTime(current_date); + test('delegation signs with proper keys (3)', async () => { const root = createIdentity(2); const middle = createIdentity(1); const bottom = createIdentity(0); - const rootToMiddle = await DelegationChain.create( - root, - middle.getPublicKey(), - new Date(1609459200000), - ); - const middleToBottom = await DelegationChain.create( - middle, - bottom.getPublicKey(), - new Date(1609459200000), - { - previous: rootToMiddle, - }, - ); + const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date); + const middleToBottom = await DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { + previous: rootToMiddle, + }); const golden = { delegations: [ @@ -66,23 +64,13 @@ test('DelegationChain can be serialized to and from JSON', async () => { const middle = createIdentity(1); const bottom = createIdentity(0); - const rootToMiddle = await DelegationChain.create( - root, - middle.getPublicKey(), - new Date(1609459200000), - { - targets: [Principal.fromText('jyi7r-7aaaa-aaaab-aaabq-cai')], - }, - ); - const middleToBottom = await DelegationChain.create( - middle, - bottom.getPublicKey(), - new Date(1609459200000), - { - previous: rootToMiddle, - targets: [Principal.fromText('u76ha-lyaaa-aaaab-aacha-cai')], - }, - ); + const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date, { + targets: [Principal.fromText('jyi7r-7aaaa-aaaab-aaabq-cai')], + }); + const middleToBottom = await DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { + previous: rootToMiddle, + targets: [Principal.fromText('u76ha-lyaaa-aaaab-aacha-cai')], + }); const rootToMiddleJson = JSON.stringify(rootToMiddle); // All strings in the JSON should be hex so it is clear how to decode this as different versions @@ -104,7 +92,7 @@ test('DelegationChain can be serialized to and from JSON', async () => { expect(middleToBottomActual).toEqual(middleToBottom); }); -test('DelegationChain has a maximum length of 20 delegations', async () => { +test.skip('DelegationChain has a maximum length of 20 delegations', async () => { const identities = new Array(21).fill(0).map((_, i) => createIdentity(i)); const signedDelegations: DelegationChain[] = []; @@ -116,13 +104,13 @@ test('DelegationChain has a maximum length of 20 delegations', async () => { signedDelegation = await DelegationChain.create( identity, nextIdentity.getPublicKey(), - new Date(1609459200000), + expiry_date, ); } else { signedDelegation = await DelegationChain.create( identity, nextIdentity.getPublicKey(), - new Date(1609459200000), + expiry_date, { previous: signedDelegations[i - 1], }, @@ -135,20 +123,15 @@ test('DelegationChain has a maximum length of 20 delegations', async () => { const secondLastIdentity = identities[identities.length - 2]; const lastIdentity = identities[identities.length - 1]; expect( - DelegationChain.create( - secondLastIdentity, - lastIdentity.getPublicKey(), - new Date(1609459200000), - { - previous: signedDelegations[signedDelegations.length - 1], - }, - ), + DelegationChain.create(secondLastIdentity, lastIdentity.getPublicKey(), expiry_date, { + previous: signedDelegations[signedDelegations.length - 1], + }), ).rejects.toThrow('Delegation chain cannot exceed 20'); }); test('Delegation can include multiple targets', async () => { const pubkey = new Uint8Array([1, 2, 3]); - const expiration = BigInt(Number(new Date(1609459200000))); + const expiration = BigInt(Number(expiry_date)); const targets = [ Principal.fromText('jyi7r-7aaaa-aaaab-aaabq-cai'), Principal.fromText('u76ha-lyaaa-aaaab-aacha-cai'), @@ -170,23 +153,22 @@ test('Delegation chains cannot repeat public keys', async () => { const middle = createIdentity(1); const bottom = createIdentity(2); - const rootToMiddle = await DelegationChain.create( - root, - middle.getPublicKey(), - new Date(1609459200000), - ); - const middleToBottom = await DelegationChain.create( - middle, - bottom.getPublicKey(), - new Date(1609459200000), - { - previous: rootToMiddle, - }, - ); + const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date); + const middleToBottom = await DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { + previous: rootToMiddle, + }); + + // Repeating middle's public key in the delegation chain should throw an error. + expect( + DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { + previous: middleToBottom, + }), + ).rejects.toThrow('Cannot repeat public keys in a delegation chain.'); + // Repeating root's public key in the delegation chain should throw an error. expect( - DelegationChain.create(bottom, root.getPublicKey(), new Date(1609459200000), { + DelegationChain.create(root, bottom.getPublicKey(), expiry_date, { previous: middleToBottom, }), - ).rejects.toThrow('Delegation chain cannot repeat public keys'); + ).rejects.toThrow('Cannot repeat public keys in a delegation chain.'); }); diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 4339dc27c..869a9d7b2 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -196,14 +196,19 @@ export class DelegationChain { } = {}, ): Promise { if (options.previous) { - const delegatedKeys: PublicKey[] = [ - ...options.previous.delegations.map(signedDelegation => signedDelegation.delegation.pubkey), - from.getPublicKey(), - to, - ]; - const delegatedKeysSet = new Set(delegatedKeys.map(key => toHexString(key.derKey))); - if (delegatedKeys.length !== delegatedKeysSet.size) { - throw new DelegationError('Delegation chain cannot repeat public keys'); + const usedPublicKeys = new Set(); + let currentPublicKey = to.toDer(); + + for (const delegation of options.previous.delegations) { + if (usedPublicKeys.has(currentPublicKey)) { + throw new DelegationError('Delegation target cannot be repeated in the chain.'); + } + usedPublicKeys.add(currentPublicKey); + currentPublicKey = delegation.delegation.pubkey; + } + + if (usedPublicKeys.has(currentPublicKey)) { + throw new DelegationError('Error: Cannot repeat public keys in a delegation chain.'); } } From 3a89fc019d83a1621a985cd743393725a5ad0104 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 6 Sep 2023 10:25:14 -0700 Subject: [PATCH 05/21] 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); + } } } From 78e6e56b45ef753eb69ed23fbf02fed4be46e6f6 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 6 Sep 2023 11:19:04 -0700 Subject: [PATCH 06/21] chore: lock npm version for CI (#762) * chore: limit npm version to 9 in ci for compatibility with node 16 * changelog --- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/mitm.yml | 2 +- .github/workflows/prettier.yml | 2 +- .github/workflows/size-limit.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- docs/generated/changelog.html | 1 + 7 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 6d15d72cc..21338101f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -28,7 +28,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm install diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d3fdef1ef..0a684e283 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,6 +24,6 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm install - run: npm run lint --workspaces --if-present diff --git a/.github/workflows/mitm.yml b/.github/workflows/mitm.yml index 33150f1cf..d2fa2d52b 100644 --- a/.github/workflows/mitm.yml +++ b/.github/workflows/mitm.yml @@ -28,7 +28,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm install diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 83b45dd59..9f0ae436a 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm install prettier pretty-quick - run: npm run prettier:check diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml index 9b5143fc0..394b41097 100644 --- a/.github/workflows/size-limit.yml +++ b/.github/workflows/size-limit.yml @@ -7,7 +7,7 @@ jobs: CI_JOB_NUMBER: 1 steps: - uses: actions/checkout@v1 - - run: npm install -g npm + - run: npm install -g npm@9 # - run: npm run size-limit --workspaces --if-present # commented out until the job can be configured diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index a35d85a6b..4a1255650 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -29,7 +29,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm ci diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index 6d974f42c..b24d7f30a 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -12,6 +12,7 @@

    Agent-JS Changelog

    Version x.x.x

      +
    • chore: limit npm version to 9 in ci for compatibility with node 16
    • Adds more helpful error message for when principal is undefined during actor creation
    • From 139356c3035207020362ff5b318f9bc32a877711 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Mon, 21 Aug 2023 16:48:30 -0700 Subject: [PATCH 07/21] feat: DelegationChain audits for delegation targets and maximum delegations during initialization --- .../__snapshots__/delegation.test.ts.snap | 12 ++++ .../identity/src/identity/delegation.test.ts | 69 ++++++++++++++++++- packages/identity/src/identity/delegation.ts | 36 ++++++++-- packages/identity/src/identity/errors.ts | 13 ++++ 4 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 packages/identity/src/identity/__snapshots__/delegation.test.ts.snap create mode 100644 packages/identity/src/identity/errors.ts diff --git a/packages/identity/src/identity/__snapshots__/delegation.test.ts.snap b/packages/identity/src/identity/__snapshots__/delegation.test.ts.snap new file mode 100644 index 000000000..2fa987dc9 --- /dev/null +++ b/packages/identity/src/identity/__snapshots__/delegation.test.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Delegation can include multiple targets 1`] = ` +Object { + "expiration": "176bb3e7000", + "pubkey": "010203", + "targets": Array [ + "00000000002000030101", + "000000000020008E0101", + ], +} +`; diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index 620b620a1..8500778a4 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -1,6 +1,6 @@ import { SignIdentity } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; -import { DelegationChain } from './delegation'; +import { Delegation, DelegationChain } from './delegation'; import { Ed25519KeyIdentity } from './ed25519'; function createIdentity(seed: number): SignIdentity { @@ -8,6 +8,12 @@ function createIdentity(seed: number): SignIdentity { return Ed25519KeyIdentity.generate(s); } +function randomPrincipal(): Principal { + const bytes = new Uint8Array(29); + crypto.getRandomValues(bytes); + return Principal.fromUint8Array(bytes); +} + test('delegation signs with proper keys (3)', async () => { const root = createIdentity(2); const middle = createIdentity(1); @@ -97,3 +103,64 @@ test('DelegationChain can be serialized to and from JSON', async () => { const middleToBottomActual = DelegationChain.fromJSON(middleToBottomJson); expect(middleToBottomActual).toEqual(middleToBottom); }); + +test('DelegationChain has a maximum length of 20 delegations', async () => { + const identities = new Array(21).fill(0).map((_, i) => createIdentity(i)); + const signedDelegations: DelegationChain[] = []; + + for (let i = 0; i < identities.length - 1; i++) { + const identity = identities[i]; + const nextIdentity = identities[i + 1]; + let signedDelegation: DelegationChain; + if (i === 0) { + signedDelegation = await DelegationChain.create( + identity, + nextIdentity.getPublicKey(), + new Date(1609459200000), + ); + } else { + signedDelegation = await DelegationChain.create( + identity, + nextIdentity.getPublicKey(), + new Date(1609459200000), + { + previous: signedDelegations[i - 1], + }, + ); + } + signedDelegations.push(signedDelegation); + } + expect(signedDelegations.length).toEqual(20); + + const secondLastIdentity = identities[identities.length - 2]; + const lastIdentity = identities[identities.length - 1]; + expect( + DelegationChain.create( + secondLastIdentity, + lastIdentity.getPublicKey(), + new Date(1609459200000), + { + previous: signedDelegations[signedDelegations.length - 1], + }, + ), + ).rejects.toThrow('Delegation chain cannot exceed 20'); +}); + +test('Delegation can include multiple targets', async () => { + const pubkey = new Uint8Array([1, 2, 3]); + const expiration = BigInt(Number(new Date(1609459200000))); + const targets = [ + Principal.fromText('jyi7r-7aaaa-aaaab-aaabq-cai'), + Principal.fromText('u76ha-lyaaa-aaaab-aacha-cai'), + ]; + const delegation = new Delegation(pubkey, expiration, targets); + expect(delegation.targets).toEqual(targets); + expect(delegation.toJSON()).toMatchSnapshot(); +}); + +test('Delegation targets cannot exceed 1_000', () => { + const targets = new Array(1_001).fill(randomPrincipal()); + expect(() => new Delegation(new Uint8Array([1, 2, 3]), BigInt(0), targets)).toThrow( + 'Delegation targets cannot exceed 1000', + ); +}); diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 055a97887..98db784bc 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -9,13 +9,16 @@ import { import { Principal } from '@dfinity/principal'; import * as cbor from 'simple-cbor'; import { fromHexString, toHexString } from '../buffer'; +import { DelegationError } from './errors'; const domainSeparator = new TextEncoder().encode('\x1Aic-request-auth-delegation'); const requestDomainSeparator = new TextEncoder().encode('\x0Aic-request'); +const MAXIMUM_NUMBER_OF_TARGETS = 1_000; +const MAXIMUM_DELEGATION_CHAIN_LENGTH = 20; function _parseBlob(value: unknown): ArrayBuffer { if (typeof value !== 'string' || value.length < 64) { - throw new Error('Invalid public key.'); + throw new DelegationError('Invalid public key.'); } return fromHexString(value); @@ -32,7 +35,11 @@ export class Delegation { public readonly pubkey: ArrayBuffer, public readonly expiration: bigint, public readonly targets?: Principal[], - ) {} + ) { + if (targets && targets?.length > MAXIMUM_NUMBER_OF_TARGETS) { + throw new DelegationError(`Delegation targets cannot exceed ${MAXIMUM_NUMBER_OF_TARGETS}`); + } + } public toCBOR(): cbor.CborValue { // Expiration field needs to be encoded as a u64 specifically. @@ -97,6 +104,17 @@ async function _createSingleDelegation( expiration: Date, targets?: Principal[], ): Promise { + // Validate inputs + if (targets && targets?.length > MAXIMUM_NUMBER_OF_TARGETS) { + throw new DelegationError(`Delegation targets cannot exceed ${MAXIMUM_NUMBER_OF_TARGETS}`); + } + if (!from.sign) { + throw new DelegationError('The from identity does not have a sign method.'); + } + 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. @@ -192,14 +210,14 @@ export class DelegationChain { public static fromJSON(json: string | JsonnableDelegationChain): DelegationChain { const { publicKey, delegations } = typeof json === 'string' ? JSON.parse(json) : json; if (!Array.isArray(delegations)) { - throw new Error('Invalid delegations.'); + throw new DelegationError('Invalid delegations.'); } const parsedDelegations: SignedDelegation[] = delegations.map(signedDelegation => { const { delegation, signature } = signedDelegation; const { pubkey, expiration, targets } = delegation; if (targets !== undefined && !Array.isArray(targets)) { - throw new Error('Invalid targets.'); + throw new DelegationError('Invalid targets.'); } return { @@ -209,7 +227,7 @@ export class DelegationChain { targets && targets.map((t: unknown) => { if (typeof t !== 'string') { - throw new Error('Invalid target.'); + throw new DelegationError('Invalid target.'); } return Principal.fromHex(t); }), @@ -237,7 +255,13 @@ export class DelegationChain { protected constructor( public readonly delegations: SignedDelegation[], public readonly publicKey: DerEncodedPublicKey, - ) {} + ) { + 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 new file mode 100644 index 000000000..84b7a609e --- /dev/null +++ b/packages/identity/src/identity/errors.ts @@ -0,0 +1,13 @@ +export class IdentityError extends Error { + constructor(public readonly message: string) { + super(message); + Object.setPrototypeOf(this, IdentityError.prototype); + } +} + +export class DelegationError extends IdentityError { + constructor(public readonly message: string) { + super(message); + Object.setPrototypeOf(this, DelegationError.prototype); + } +} From 4f87fe1e94075f01686940e09fb3665678b6bd7b Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Mon, 21 Aug 2023 16:48:54 -0700 Subject: [PATCH 08/21] changelog --- docs/generated/changelog.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/generated/changelog.html b/docs/generated/changelog.html index b24d7f30a..b62ab0c8b 100644 --- a/docs/generated/changelog.html +++ b/docs/generated/changelog.html @@ -16,6 +16,8 @@

      Version x.x.x

    • Adds more helpful error message for when principal is undefined during actor creation
    • +
    • feat: DelegationChain audits for delegation targets and maximum delegations during + initialization

    Version 0.19.2

      From 95588ea85b1a0c5bab8d581b53cbc30f5b173010 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Fri, 25 Aug 2023 09:25:14 -0700 Subject: [PATCH 09/21] wip --- .../identity/src/identity/delegation.test.ts | 26 +++++++++++++++++++ packages/identity/src/identity/delegation.ts | 13 ++++++++++ 2 files changed, 39 insertions(+) diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index 8500778a4..f5bfd27a8 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -164,3 +164,29 @@ test('Delegation targets cannot exceed 1_000', () => { 'Delegation targets cannot exceed 1000', ); }); + +test('Delegation chains cannot repeat public keys', async () => { + const root = createIdentity(0); + const middle = createIdentity(1); + const bottom = createIdentity(2); + + const rootToMiddle = await DelegationChain.create( + root, + middle.getPublicKey(), + new Date(1609459200000), + ); + const middleToBottom = await DelegationChain.create( + middle, + bottom.getPublicKey(), + new Date(1609459200000), + { + previous: rootToMiddle, + }, + ); + + expect( + DelegationChain.create(bottom, root.getPublicKey(), new Date(1609459200000), { + previous: middleToBottom, + }), + ).rejects.toThrow('Delegation chain cannot repeat public keys'); +}); diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 98db784bc..4339dc27c 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -195,6 +195,18 @@ export class DelegationChain { targets?: Principal[]; } = {}, ): Promise { + if (options.previous) { + const delegatedKeys: PublicKey[] = [ + ...options.previous.delegations.map(signedDelegation => signedDelegation.delegation.pubkey), + from.getPublicKey(), + to, + ]; + const delegatedKeysSet = new Set(delegatedKeys.map(key => toHexString(key.derKey))); + if (delegatedKeys.length !== delegatedKeysSet.size) { + throw new DelegationError('Delegation chain cannot repeat public keys'); + } + } + const delegation = await _createSingleDelegation(from, to, expiration, options.targets); return new DelegationChain( [...(options.previous?.delegations || []), delegation], @@ -255,6 +267,7 @@ export class DelegationChain { protected constructor( public readonly delegations: SignedDelegation[], public readonly publicKey: DerEncodedPublicKey, + public readonly previous?: DelegationChain, ) { if (delegations.length > MAXIMUM_DELEGATION_CHAIN_LENGTH) { throw new DelegationError( From c0dab1b0d8f6fe68fe67b7600cdcd6fa8464daae Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Tue, 5 Sep 2023 12:03:00 -0700 Subject: [PATCH 10/21] continuing work on not repeating delegations --- .../identity/src/identity/delegation.test.ts | 96 ++++++++----------- packages/identity/src/identity/delegation.ts | 21 ++-- 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index f5bfd27a8..015547941 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -2,6 +2,7 @@ import { SignIdentity } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; import { Delegation, DelegationChain } from './delegation'; import { Ed25519KeyIdentity } from './ed25519'; +import { toHexString } from '../buffer'; function createIdentity(seed: number): SignIdentity { const s = new Uint8Array([seed, ...new Array(31).fill(0)]); @@ -14,24 +15,21 @@ function randomPrincipal(): Principal { return Principal.fromUint8Array(bytes); } +const expiry_date = new Date(1609459200000); +const current_date = new Date(1609459200000 - 10000); + +jest.useFakeTimers(); +jest.setSystemTime(current_date); + test('delegation signs with proper keys (3)', async () => { const root = createIdentity(2); const middle = createIdentity(1); const bottom = createIdentity(0); - const rootToMiddle = await DelegationChain.create( - root, - middle.getPublicKey(), - new Date(1609459200000), - ); - const middleToBottom = await DelegationChain.create( - middle, - bottom.getPublicKey(), - new Date(1609459200000), - { - previous: rootToMiddle, - }, - ); + const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date); + const middleToBottom = await DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { + previous: rootToMiddle, + }); const golden = { delegations: [ @@ -66,23 +64,13 @@ test('DelegationChain can be serialized to and from JSON', async () => { const middle = createIdentity(1); const bottom = createIdentity(0); - const rootToMiddle = await DelegationChain.create( - root, - middle.getPublicKey(), - new Date(1609459200000), - { - targets: [Principal.fromText('jyi7r-7aaaa-aaaab-aaabq-cai')], - }, - ); - const middleToBottom = await DelegationChain.create( - middle, - bottom.getPublicKey(), - new Date(1609459200000), - { - previous: rootToMiddle, - targets: [Principal.fromText('u76ha-lyaaa-aaaab-aacha-cai')], - }, - ); + const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date, { + targets: [Principal.fromText('jyi7r-7aaaa-aaaab-aaabq-cai')], + }); + const middleToBottom = await DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { + previous: rootToMiddle, + targets: [Principal.fromText('u76ha-lyaaa-aaaab-aacha-cai')], + }); const rootToMiddleJson = JSON.stringify(rootToMiddle); // All strings in the JSON should be hex so it is clear how to decode this as different versions @@ -104,7 +92,7 @@ test('DelegationChain can be serialized to and from JSON', async () => { expect(middleToBottomActual).toEqual(middleToBottom); }); -test('DelegationChain has a maximum length of 20 delegations', async () => { +test.skip('DelegationChain has a maximum length of 20 delegations', async () => { const identities = new Array(21).fill(0).map((_, i) => createIdentity(i)); const signedDelegations: DelegationChain[] = []; @@ -116,13 +104,13 @@ test('DelegationChain has a maximum length of 20 delegations', async () => { signedDelegation = await DelegationChain.create( identity, nextIdentity.getPublicKey(), - new Date(1609459200000), + expiry_date, ); } else { signedDelegation = await DelegationChain.create( identity, nextIdentity.getPublicKey(), - new Date(1609459200000), + expiry_date, { previous: signedDelegations[i - 1], }, @@ -135,20 +123,15 @@ test('DelegationChain has a maximum length of 20 delegations', async () => { const secondLastIdentity = identities[identities.length - 2]; const lastIdentity = identities[identities.length - 1]; expect( - DelegationChain.create( - secondLastIdentity, - lastIdentity.getPublicKey(), - new Date(1609459200000), - { - previous: signedDelegations[signedDelegations.length - 1], - }, - ), + DelegationChain.create(secondLastIdentity, lastIdentity.getPublicKey(), expiry_date, { + previous: signedDelegations[signedDelegations.length - 1], + }), ).rejects.toThrow('Delegation chain cannot exceed 20'); }); test('Delegation can include multiple targets', async () => { const pubkey = new Uint8Array([1, 2, 3]); - const expiration = BigInt(Number(new Date(1609459200000))); + const expiration = BigInt(Number(expiry_date)); const targets = [ Principal.fromText('jyi7r-7aaaa-aaaab-aaabq-cai'), Principal.fromText('u76ha-lyaaa-aaaab-aacha-cai'), @@ -170,23 +153,22 @@ test('Delegation chains cannot repeat public keys', async () => { const middle = createIdentity(1); const bottom = createIdentity(2); - const rootToMiddle = await DelegationChain.create( - root, - middle.getPublicKey(), - new Date(1609459200000), - ); - const middleToBottom = await DelegationChain.create( - middle, - bottom.getPublicKey(), - new Date(1609459200000), - { - previous: rootToMiddle, - }, - ); + const rootToMiddle = await DelegationChain.create(root, middle.getPublicKey(), expiry_date); + const middleToBottom = await DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { + previous: rootToMiddle, + }); + + // Repeating middle's public key in the delegation chain should throw an error. + expect( + DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { + previous: middleToBottom, + }), + ).rejects.toThrow('Cannot repeat public keys in a delegation chain.'); + // Repeating root's public key in the delegation chain should throw an error. expect( - DelegationChain.create(bottom, root.getPublicKey(), new Date(1609459200000), { + DelegationChain.create(root, bottom.getPublicKey(), expiry_date, { previous: middleToBottom, }), - ).rejects.toThrow('Delegation chain cannot repeat public keys'); + ).rejects.toThrow('Cannot repeat public keys in a delegation chain.'); }); diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 4339dc27c..869a9d7b2 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -196,14 +196,19 @@ export class DelegationChain { } = {}, ): Promise { if (options.previous) { - const delegatedKeys: PublicKey[] = [ - ...options.previous.delegations.map(signedDelegation => signedDelegation.delegation.pubkey), - from.getPublicKey(), - to, - ]; - const delegatedKeysSet = new Set(delegatedKeys.map(key => toHexString(key.derKey))); - if (delegatedKeys.length !== delegatedKeysSet.size) { - throw new DelegationError('Delegation chain cannot repeat public keys'); + const usedPublicKeys = new Set(); + let currentPublicKey = to.toDer(); + + for (const delegation of options.previous.delegations) { + if (usedPublicKeys.has(currentPublicKey)) { + throw new DelegationError('Delegation target cannot be repeated in the chain.'); + } + usedPublicKeys.add(currentPublicKey); + currentPublicKey = delegation.delegation.pubkey; + } + + if (usedPublicKeys.has(currentPublicKey)) { + throw new DelegationError('Error: Cannot repeat public keys in a delegation chain.'); } } From d6d5a3cab0a59a7131a4a121e998e901e60d2c25 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 6 Sep 2023 10:25:14 -0700 Subject: [PATCH 11/21] 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); + } } } From 8b805630e3f4d7f5ca7e810e66831cd32ebbbe5e Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 6 Sep 2023 12:08:54 -0700 Subject: [PATCH 12/21] fixing test case --- packages/identity/src/identity/delegation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index 33e8b6c4e..e22e9eaa3 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -198,7 +198,7 @@ test('Cannot create a delegation chain with an outdated delegation', async () => (rootToMiddle.delegations[0].delegation as any).expiration = BigInt(0); expect( - DelegationChain.create(middle, bottom.getPublicKey(), new Date(0), { + DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { previous: rootToMiddle, }), ).rejects.toThrow('Previous delegation in the chain has expired.'); From 32aad221ee78acc7c77a246f8a297873a7ffb7bc Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 6 Sep 2023 12:30:34 -0700 Subject: [PATCH 13/21] fixing auth-client test around expired delegations --- packages/auth-client/src/index.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/auth-client/src/index.test.ts b/packages/auth-client/src/index.test.ts index 4e249d1ba..eeb1e12ba 100644 --- a/packages/auth-client/src/index.test.ts +++ b/packages/auth-client/src/index.test.ts @@ -704,10 +704,14 @@ describe('Migration from Ed25519Key', () => { jest.setSystemTime(new Date('2020-01-01T00:00:00.000Z')); // two days ago - const expiration = new Date('2019-12-30T00:00:00.000Z'); + const properExpiration = new Date('2020-01-03T00:00:00.000Z'); + const expiredExpiration = new Date('2019-12-30T00:00:00.000Z'); const key = await Ed25519KeyIdentity.fromJSON(JSON.stringify(testSecrets)); - const chain = DelegationChain.create(key, key.getPublicKey(), expiration); + const chain = await DelegationChain.create(key, key.getPublicKey(), properExpiration); + // Override the expiration to be expired before storing + (chain.delegations[0].delegation as any).expiration = + BigInt(Number(expiredExpiration)) * BigInt(10000); const fakeStore: Record = {}; fakeStore[KEY_STORAGE_DELEGATION] = JSON.stringify((await chain).toJSON()); fakeStore[KEY_STORAGE_KEY] = JSON.stringify(testSecrets); From 9e38f30c45bda41b942c0ceb5bdb3c56a8a56e5e Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Sep 2023 14:12:39 -0700 Subject: [PATCH 14/21] npm version pinning was accomplished in a separate PR --- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/mitm.yml | 2 +- .github/workflows/prettier.yml | 2 +- .github/workflows/size-limit.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 21338101f..6d15d72cc 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -28,7 +28,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm@9 + - run: npm install -g npm - run: npm install diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0a684e283..d3fdef1ef 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,6 +24,6 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - run: npm install -g npm@9 + - run: npm install -g npm - run: npm install - run: npm run lint --workspaces --if-present diff --git a/.github/workflows/mitm.yml b/.github/workflows/mitm.yml index d2fa2d52b..33150f1cf 100644 --- a/.github/workflows/mitm.yml +++ b/.github/workflows/mitm.yml @@ -28,7 +28,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm@9 + - run: npm install -g npm - run: npm install diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 9f0ae436a..83b45dd59 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - run: npm install -g npm@9 + - run: npm install -g npm - run: npm install prettier pretty-quick - run: npm run prettier:check diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml index 394b41097..9b5143fc0 100644 --- a/.github/workflows/size-limit.yml +++ b/.github/workflows/size-limit.yml @@ -7,7 +7,7 @@ jobs: CI_JOB_NUMBER: 1 steps: - uses: actions/checkout@v1 - - run: npm install -g npm@9 + - run: npm install -g npm # - run: npm run size-limit --workspaces --if-present # commented out until the job can be configured diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 4a1255650..a35d85a6b 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -29,7 +29,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm@9 + - run: npm install -g npm - run: npm ci From ed64572093a985a28457be731ca7d90e43cd35a8 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Sep 2023 14:45:51 -0700 Subject: [PATCH 15/21] Update packages/identity/src/identity/delegation.ts Co-authored-by: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> --- packages/identity/src/identity/delegation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 163d898c2..f450bb5d5 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -212,7 +212,7 @@ export class DelegationChain { // Ensure that public keys are not repeated in the chain. const usedPublicKeys = new Set(); - let currentPublicKey = to.toDer(); + usedPublicKeys.add(to.toDer()); for (const delegation of options.previous.delegations) { // Ensure that previous delegations have not expired. From 194007f32d6a78a6b6cab6c17f1351734f2e2435 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Sep 2023 14:46:50 -0700 Subject: [PATCH 16/21] Update packages/identity/src/identity/delegation.ts Co-authored-by: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> --- packages/identity/src/identity/delegation.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index f450bb5d5..a488a7e17 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -225,8 +225,7 @@ export class DelegationChain { if (usedPublicKeys.has(currentPublicKey)) { throw new DelegationError('Delegation target cannot be repeated in the chain.'); } - usedPublicKeys.add(currentPublicKey); - currentPublicKey = delegation.delegation.pubkey; + usedPublicKeys.add(delegation.delegation.pubkey); } // Ensure that the last public key in the chain not repeated. From b60c7367a538d8a9c7aa7a4e985da81f04f832b3 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Sep 2023 14:47:00 -0700 Subject: [PATCH 17/21] Update packages/identity/src/identity/delegation.ts Co-authored-by: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> --- packages/identity/src/identity/delegation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index a488a7e17..974de0408 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -222,7 +222,7 @@ export class DelegationChain { delegation.delegation.expiration, ); } - if (usedPublicKeys.has(currentPublicKey)) { + if (usedPublicKeys.has(delegation.delegation.pubkey)) { throw new DelegationError('Delegation target cannot be repeated in the chain.'); } usedPublicKeys.add(delegation.delegation.pubkey); From f5a798eec4737d3b0d456bcc689bcd9e46238e53 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Sep 2023 14:47:13 -0700 Subject: [PATCH 18/21] Update packages/identity/src/identity/delegation.ts Co-authored-by: Eric Swanson <64809312+ericswanson-dfinity@users.noreply.github.com> --- packages/identity/src/identity/delegation.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 974de0408..21c18ffa4 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -228,13 +228,6 @@ export class DelegationChain { usedPublicKeys.add(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.', { - currentPublicKey, - usedPublicKeys, - }); - } } const delegation = await _createSingleDelegation(from, to, expiration, options.targets); From bab42fbbe29cc02b92700d6ddf672b2199122b63 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Sep 2023 14:57:24 -0700 Subject: [PATCH 19/21] refactoring to use reduce function --- .../identity/src/identity/delegation.test.ts | 5 ++--- packages/identity/src/identity/delegation.ts | 18 +++++++----------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/identity/src/identity/delegation.test.ts b/packages/identity/src/identity/delegation.test.ts index e22e9eaa3..309ff2e4b 100644 --- a/packages/identity/src/identity/delegation.test.ts +++ b/packages/identity/src/identity/delegation.test.ts @@ -2,7 +2,6 @@ import { SignIdentity } from '@dfinity/agent'; import { Principal } from '@dfinity/principal'; import { Delegation, DelegationChain } from './delegation'; import { Ed25519KeyIdentity } from './ed25519'; -import { toHexString } from '../buffer'; function createIdentity(seed: number): SignIdentity { const s = new Uint8Array([seed, ...new Array(31).fill(0)]); @@ -163,14 +162,14 @@ test('Delegation chains cannot repeat public keys', async () => { DelegationChain.create(middle, bottom.getPublicKey(), expiry_date, { previous: middleToBottom, }), - ).rejects.toThrow('Cannot repeat public keys in a delegation chain.'); + ).rejects.toThrow('Delegation target cannot be repeated in the chain.'); // Repeating root's public key in the delegation chain should throw an error. expect( DelegationChain.create(root, bottom.getPublicKey(), expiry_date, { previous: middleToBottom, }), - ).rejects.toThrow('Cannot repeat public keys in a delegation chain.'); + ).rejects.toThrow('Delegation target cannot be repeated in the chain.'); }); test('Cannot create a delegation chain with an outdated expiry', async () => { diff --git a/packages/identity/src/identity/delegation.ts b/packages/identity/src/identity/delegation.ts index 21c18ffa4..efdf59c91 100644 --- a/packages/identity/src/identity/delegation.ts +++ b/packages/identity/src/identity/delegation.ts @@ -211,23 +211,19 @@ export class DelegationChain { } // Ensure that public keys are not repeated in the chain. - const usedPublicKeys = new Set(); - usedPublicKeys.add(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)) { + options.previous.delegations.reduce((previous, current) => { + if (current.delegation.expiration < BigInt(Date.now()) * BigInt(1000000)) { throw new DelegationError( 'Previous delegation in the chain has expired.', - delegation.delegation.expiration, + current.delegation.expiration, ); } - if (usedPublicKeys.has(delegation.delegation.pubkey)) { + if (previous.has(current.delegation.pubkey)) { throw new DelegationError('Delegation target cannot be repeated in the chain.'); } - usedPublicKeys.add(delegation.delegation.pubkey); - } - + previous.add(current.delegation.pubkey); + return previous; + }, new Set([to.toDer()])); } const delegation = await _createSingleDelegation(from, to, expiration, options.targets); From f5b1c97a1a1aa8a182e6ea1d19fe5f2d78062310 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Sep 2023 15:08:35 -0700 Subject: [PATCH 20/21] upgrading node version to 16 as a baseline for CI --- .github/workflows/lint.yml | 2 +- .github/workflows/prettier.yml | 2 +- .github/workflows/unit-tests.yml | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d3fdef1ef..54470ff40 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: spec: - release-0.16 # https://github.com/dfinity-lab/ic-ref/tree/release-0.16 node: - - 14 + - 16 steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node }} diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 83b45dd59..09a72643f 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -17,7 +17,7 @@ jobs: spec: - release-0.16 # https://github.com/dfinity-lab/ic-ref/tree/release-0.16 node: - - 14 + - 16 steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node }} diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index a35d85a6b..5af29fe4d 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -19,7 +19,6 @@ jobs: spec: - '0.16.1' node: - - 14 - 16 steps: From ba9025d3d7808cb873d9bdabd9a2a7b1700fd459 Mon Sep 17 00:00:00 2001 From: Kyle Peacock Date: Wed, 20 Sep 2023 15:30:04 -0700 Subject: [PATCH 21/21] pinning node 9 again --- .github/workflows/e2e-tests.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/mitm.yml | 2 +- .github/workflows/prettier.yml | 2 +- .github/workflows/size-limit.yml | 2 +- .github/workflows/unit-tests.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 6d15d72cc..21338101f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -28,7 +28,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm install diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 54470ff40..00e281edf 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,6 +24,6 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm install - run: npm run lint --workspaces --if-present diff --git a/.github/workflows/mitm.yml b/.github/workflows/mitm.yml index 33150f1cf..d2fa2d52b 100644 --- a/.github/workflows/mitm.yml +++ b/.github/workflows/mitm.yml @@ -28,7 +28,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm install diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 09a72643f..0e78c4cd5 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-node@v1 with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm install prettier pretty-quick - run: npm run prettier:check diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml index 9b5143fc0..394b41097 100644 --- a/.github/workflows/size-limit.yml +++ b/.github/workflows/size-limit.yml @@ -7,7 +7,7 @@ jobs: CI_JOB_NUMBER: 1 steps: - uses: actions/checkout@v1 - - run: npm install -g npm + - run: npm install -g npm@9 # - run: npm run size-limit --workspaces --if-present # commented out until the job can be configured diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 5af29fe4d..198cfa9c6 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -28,7 +28,7 @@ jobs: with: node-version: ${{ matrix.node }} - - run: npm install -g npm + - run: npm install -g npm@9 - run: npm ci