Skip to content

Commit 465134a

Browse files
committed
Consumers need to provide a key lookup function when verifying messages
1 parent 3678dc5 commit 465134a

File tree

5 files changed

+80
-18
lines changed

5 files changed

+80
-18
lines changed

src/cavage/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ export async function verifyMessage(config: VerifyConfig, message: Request | Res
265265
}
266266
}
267267
// now look to verify the signature! Build the expected "signing base" and verify it!
268-
return config.verifier(Buffer.from(base), Buffer.from(parsedHeader.get('signature'), 'base64'), Array.from(parsedHeader.entries()).reduce((params, [key, value]) => {
268+
const params = Array.from(parsedHeader.entries()).reduce((params, [key, value]) => {
269269
let keyName = key;
270270
let val: Date | number | string;
271271
switch (key.toLowerCase()) {
@@ -284,7 +284,6 @@ export async function verifyMessage(config: VerifyConfig, message: Request | Res
284284
keyName = 'keyid';
285285
val = value;
286286
break;
287-
// no break
288287
default: {
289288
if (typeof value === 'string' || typeof value=== 'number') {
290289
val = value;
@@ -296,5 +295,7 @@ export async function verifyMessage(config: VerifyConfig, message: Request | Res
296295
return Object.assign(params, {
297296
[keyName]: val,
298297
});
299-
}, {}));
298+
}, {});
299+
const key = await config.keyLookup(params);
300+
return key?.verify(Buffer.from(base), Buffer.from(parsedHeader.get('signature'), 'base64'), params) ?? null;
300301
}

src/httpbis/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
defaultParams,
2323
isRequest,
2424
CommonConfig,
25+
VerifyingKey,
2526
} from '../types';
2627

2728
export function deriveComponent(component: string, params: Map<string, string | number | boolean>, res: Response, req?: Request): string[];
@@ -353,7 +354,28 @@ export async function verifyMessage(config: VerifyConfig, message: Request | Res
353354
const requiredParams = config.requiredParams ?? [];
354355
const requiredFields = config.requiredFields ?? [];
355356
return Array.from(signatureInputs.entries()).reduce<Promise<boolean | null>>(async (prev, [name, input]) => {
356-
const result: Error | boolean | null = await prev.catch((e) => e);
357+
const [result, key]: [Error | boolean | null, VerifyingKey] = await Promise.all([
358+
prev.catch((e) => e),
359+
config.keyLookup(Array.from(input[1].entries()).reduce((params, [key, value]) => {
360+
if (value instanceof ByteSequence) {
361+
Object.assign(params, {
362+
[key]: value.toBase64(),
363+
});
364+
} else if (value instanceof Token) {
365+
Object.assign(params, {
366+
[key]: value.toString(),
367+
});
368+
} else {
369+
Object.assign(params, {
370+
[key]: value,
371+
});
372+
}
373+
return params;
374+
}, {})),
375+
]);
376+
if (!config.all && !key) {
377+
return null;
378+
}
357379
if (!config.all && result === true) {
358380
return result;
359381
}
@@ -407,7 +429,7 @@ export async function verifyMessage(config: VerifyConfig, message: Request | Res
407429
if (!isByteSequence(signature[0] as BareItem)) {
408430
throw new Error('Malformed signature');
409431
}
410-
return config.verifier(Buffer.from(base), Buffer.from((signature[0] as ByteSequence).toBase64(), 'base64'), Array.from(input[1].entries()).reduce((params, [key, value]) => {
432+
return key.verify(Buffer.from(base), Buffer.from((signature[0] as ByteSequence).toBase64(), 'base64'), Array.from(input[1].entries()).reduce((params, [key, value]) => {
411433
let val: Date | number | string;
412434
switch (key.toLowerCase()) {
413435
case 'created':

src/types/index.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,40 @@ export interface Response {
1111

1212
export type Signer = (data: Buffer) => Promise<Buffer>;
1313
export type Verifier = (data: Buffer, signature: Buffer, parameters?: SignatureParameters) => Promise<boolean | null>;
14+
export type VerifierFinder = (parameters: SignatureParameters) => Promise<VerifyingKey>;
1415

1516
export type Algorithm = 'rsa-v1_5-sha256' | 'ecdsa-p256-sha256' | 'hmac-sha256' | 'rsa-pss-sha512' | string;
1617

1718
export interface SigningKey {
19+
/**
20+
* The ID of this key
21+
*/
1822
id?: string;
23+
/**
24+
* The algorithm to sign with
25+
*/
1926
alg?: Algorithm;
27+
/**
28+
* The Signer function
29+
*/
2030
sign: Signer;
2131
}
2232

33+
export interface VerifyingKey {
34+
/**
35+
* The ID of this key
36+
*/
37+
id?: string;
38+
/**
39+
* The supported algorithms for this key
40+
*/
41+
algs?: Algorithm[];
42+
/**
43+
* The Verify function
44+
*/
45+
verify: Verifier;
46+
}
47+
2348
/**
2449
* The signature parameters to include in signing
2550
*/
@@ -47,9 +72,9 @@ export interface SignatureParameters {
4772
*/
4873
keyid?: string;
4974
/**
50-
* A context parameter for the signature
75+
* A tag parameter for the signature
5176
*/
52-
context?: string;
77+
tag?: string;
5378
[param: string]: Date | number | string | null | undefined;
5479
}
5580

@@ -105,20 +130,25 @@ export interface SignConfig extends CommonConfig {
105130
* of adding creation time (by setting `created: null`)
106131
*/
107132
paramValues?: SignatureParameters,
133+
/**
134+
* A list of supported algorithms
135+
*/
136+
algs?: Algorithm[];
108137
}
109138

110139
/**
111140
* Options when verifying signatures
112141
*/
113142
export interface VerifyConfig extends CommonConfig {
114-
verifier: Verifier;
143+
keyLookup: VerifierFinder;
115144
/**
116-
* A maximum age for the signature
145+
* A date that the signature can't have been marked as `created` after
117146
* Default: Date.now() + tolerance
118147
*/
119148
notAfter?: Date | number;
120149
/**
121-
* The maximum age of the signature - this overrides the `expires` value for the signature
150+
* The maximum age of the signature - this effectively overrides the `expires` value for the
151+
* signature (unless the expires age is less than the maxAge specified)
122152
* if provided
123153
*/
124154
maxAge?: number;

test/cavage/new.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,12 @@ describe('cavage', () => {
413413
};
414414
it('verifies a request', async () => {
415415
const verifierStub = stub().resolves(true);
416+
const keyLookup = stub().callsFake(async ({ keyid }) => keyid === 'test-key-a' ? { verify: verifierStub } : null);
416417
const valid = await cavage.verifyMessage({
417-
verifier: verifierStub,
418+
keyLookup,
418419
}, request);
419420
expect(valid).to.equal(true);
421+
expect(keyLookup).to.have.callCount(1);
420422
expect(verifierStub).to.have.callCount(1);
421423
expect(verifierStub).to.have.been.calledOnceWithExactly(
422424
Buffer.from(
@@ -450,8 +452,9 @@ describe('cavage', () => {
450452
};
451453
it('verifies a response', async () => {
452454
const verifierStub = stub().resolves(true);
455+
const keyLookup = stub().callsFake(async ({ keyid }) => keyid === 'test-key-a' ? { verify: verifierStub } : null);
453456
const result = await cavage.verifyMessage({
454-
verifier: verifierStub,
457+
keyLookup,
455458
}, response);
456459
expect(result).to.equal(true);
457460
expect(verifierStub).to.have.callCount(1);

test/httpbis/new.spec.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,10 +1095,12 @@ describe('httpbis', () => {
10951095
};
10961096
it('verifies a request', async () => {
10971097
const verifierStub = stub().resolves(true);
1098+
const keyLookup = stub().callsFake(async ({ keyid }) => keyid === 'test-key-rsa-pss' ? { verify: verifierStub } : null);
10981099
const valid = await httpbis.verifyMessage({
1099-
verifier: verifierStub,
1100+
keyLookup,
11001101
}, request);
11011102
expect(valid).to.equal(true);
1103+
expect(keyLookup).to.have.callCount(1);
11021104
expect(verifierStub).to.have.callCount(1);
11031105
expect(verifierStub).to.have.been.calledOnceWithExactly(
11041106
Buffer.from('"@method": POST\n' +
@@ -1131,10 +1133,12 @@ describe('httpbis', () => {
11311133
};
11321134
it('verifies a response', async () => {
11331135
const verifierStub = stub().resolves(true);
1136+
const keyLookup = stub().callsFake(async ({ keyid }) => keyid === 'test-key-ecc-p256' ? { verify: verifierStub } : null);
11341137
const result = await httpbis.verifyMessage({
1135-
verifier: verifierStub,
1138+
keyLookup,
11361139
}, response);
11371140
expect(result).to.equal(true);
1141+
expect(keyLookup).to.have.callCount(1);
11381142
expect(verifierStub).to.have.callCount(1);
11391143
expect(verifierStub).to.have.been.calledOnceWithExactly(
11401144
Buffer.from('"@status": 200\n' +
@@ -1176,13 +1180,15 @@ describe('httpbis', () => {
11761180
},
11771181
};
11781182
it('verifies a response bound to a request', async () => {
1179-
const stubVerifier = stub().resolves(true);
1183+
const verifierStub = stub().resolves(true);
1184+
const keyLookup = stub().callsFake(async ({ keyid }) => keyid === 'test-key-ecc-p256' ? { verify: verifierStub } : null);
11801185
const result = await httpbis.verifyMessage({
1181-
verifier: stubVerifier,
1186+
keyLookup,
11821187
}, response, request);
11831188
expect(result).to.equal(true);
1184-
expect(stubVerifier).to.have.callCount(1);
1185-
expect(stubVerifier).to.have.been.calledOnceWithExactly(
1189+
expect(keyLookup).to.have.callCount(1);
1190+
expect(verifierStub).to.have.callCount(1);
1191+
expect(verifierStub).to.have.been.calledOnceWithExactly(
11861192
Buffer.from('"@status": 503\n' +
11871193
'"content-length": 62\n' +
11881194
'"content-type": application/json\n' +

0 commit comments

Comments
 (0)