Skip to content

Commit

Permalink
checks to prevent repeated delegations to public keys
Browse files Browse the repository at this point in the history
  • Loading branch information
krpeacock committed Sep 6, 2023
1 parent ef6c173 commit 3a89fc0
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 18 deletions.
47 changes: 39 additions & 8 deletions packages/identity/src/identity/delegation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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');
});
Expand All @@ -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, {
Expand All @@ -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.');
});
38 changes: 29 additions & 9 deletions packages/identity/src/identity/delegation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -195,20 +194,47 @@ export class DelegationChain {
targets?: Principal[];
} = {},
): Promise<DelegationChain> {
/**
* 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<ArrayBuffer>();
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.');
}
usedPublicKeys.add(currentPublicKey);
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,
});
}
}

Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion packages/identity/src/identity/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

0 comments on commit 3a89fc0

Please sign in to comment.