From 2309aeb65241c4586769064f47bbf66edeac1ebb Mon Sep 17 00:00:00 2001 From: "dmitrii.tychinin" Date: Fri, 8 Mar 2024 17:33:41 +0000 Subject: [PATCH 1/4] * moved all signature input generation from authing to httping * added optional authority parameter for authing methods. * removed Signer calls from httping. moved them to the upper level (authing) * refactor siginput and desiginput method to make them reflect each other * desiginput return value changed to map by signature label. According to the RFC the labels must be unique * added sigbase method to httping. It is now used in both sign and verify authing functions * authing.test.ts: mock httping methods. * added RFC test cases --- src/keri/core/authing.ts | 103 +++++++---------- src/keri/core/httping.ts | 153 +++++++++++-------------- test/core/authing.test.ts | 176 +++++++++++++++++++++++++++-- test/core/httping.test.ts | 232 ++++++++++++++++++++++++++++---------- test/core/signer.test.ts | 27 +++++ 5 files changed, 472 insertions(+), 219 deletions(-) diff --git a/src/keri/core/authing.ts b/src/keri/core/authing.ts index 5f816f0e..aa1c167b 100644 --- a/src/keri/core/authing.ts +++ b/src/keri/core/authing.ts @@ -1,11 +1,14 @@ import { Signer } from './signer'; import { Verfer } from './verfer'; -import { desiginput, normalize, siginput } from './httping'; -import { Signage, signature, designature } from '../end/ending'; +import { desiginput, sigbase, siginput } from './httping'; +import { designature, Signage, signature } from '../end/ending'; import { Cigar } from './cigar'; import { Siger } from './siger'; +import { nowUTC } from './utils'; +import { b } from './core.ts'; + export class Authenticater { - static DefaultFields = [ + static readonly DefaultFields = [ '@method', '@path', 'signify-resource', @@ -19,64 +22,38 @@ export class Authenticater { this._verfer = verfer; } - verify(headers: Headers, method: string, path: string): boolean { - const siginput = headers.get('Signature-Input'); - if (siginput == null) { + verify( + headers: Headers, + method: string, + path: string, + authority?: string, + ): boolean { + const siginputHeader = headers.get('Signature-Input'); + if (siginputHeader == null) { return false; } const signature = headers.get('Signature'); if (signature == null) { return false; } - let inputs = desiginput(siginput); - inputs = inputs.filter((input) => input.name == 'signify'); - if (inputs.length == 0) { + const inputs = desiginput(siginputHeader); + const input = inputs.get('signify'); + if (!input) { return false; } - inputs.forEach((input) => { - const items = new Array(); - input.fields!.forEach((field: string) => { - if (field.startsWith('@')) { - if (field == '@method') { - items.push(`"${field}": ${method}`); - } else if (field == '@path') { - items.push(`"${field}": ${path}`); - } - } else { - if (headers.has(field)) { - const value = normalize(headers.get(field) as string); - items.push(`"${field}": ${value}`); - } - } - }); - const values = new Array(); - values.push(`(${input.fields!.join(' ')})`); - values.push(`created=${input.created}`); - if (input.expires != undefined) { - values.push(`expires=${input.expires}`); - } - if (input.nonce != undefined) { - values.push(`nonce=${input.nonce}`); - } - if (input.keyid != undefined) { - values.push(`keyid=${input.keyid}`); - } - if (input.context != undefined) { - values.push(`context=${input.context}`); - } - if (input.alg != undefined) { - values.push(`alg=${input.alg}`); - } - const params = values.join(';'); - items.push(`"@signature-params: ${params}"`); - const ser = items.join('\n'); - const signage = designature(signature!); - const cig = signage[0].markers.get(input.name); - if (!this._verfer.verify(cig.raw, ser)) { - throw new Error(`Signature for ${input.keyid} invalid.`); - } - }); - + const ser = sigbase( + input.fields, + siginput(input), + headers, + method, + path, + authority, + ); + const signage = designature(signature); + const cig = signage[0].markers.get('signify'); + if (!this._verfer.verify(cig.raw, ser)) { + throw new Error(`Signature for ${input.keyid} invalid.`); + } return true; } @@ -84,26 +61,24 @@ export class Authenticater { headers: Headers, method: string, path: string, + authority?: string, fields?: Array ): Headers { if (fields == undefined) { fields = Authenticater.DefaultFields; } - - const [header, sig] = siginput(this._csig, { - name: 'signify', - method, - path, - headers, + const input = { fields, + created: Math.floor(nowUTC().getTime() / 1000), alg: 'ed25519', keyid: this._csig.verfer.qb64, - }); - - header.forEach((value, key) => { - headers.append(key, value); - }); + }; + const signatureParams = siginput(input); + const signatureBase = sigbase(fields, signatureParams, headers, method, path, authority); + const sid = `signify=${ signatureParams }`; + headers.append('Signature-Input', sid); + const sig = this._csig.sign(b(signatureBase)); const markers = new Map(); markers.set('signify', sig); const signage = new Signage(markers, false); diff --git a/src/keri/core/httping.ts b/src/keri/core/httping.ts index 70b59dd5..e6fe513a 100644 --- a/src/keri/core/httping.ts +++ b/src/keri/core/httping.ts @@ -1,15 +1,5 @@ -import { - serializeDictionary, - Dictionary, - parseDictionary, - Item, - Parameters, -} from 'structured-headers'; -import { Signer } from './signer'; +import { Item, Parameters, parseDictionary, serializeInnerList } from 'structured-headers'; import { b } from './core'; -import { Cigar } from './cigar'; -import { nowUTC } from './utils'; -import { Siger } from './siger'; import Base64 from 'urlsafe-base64'; import { Buffer } from 'buffer'; @@ -18,99 +8,80 @@ export function normalize(header: string) { } export interface SiginputArgs { - name: string; method: string; path: string; + authority?: string; headers: Headers; - fields: Array; - expires?: number; - nonce?: string; - alg?: string; - keyid?: string; - context?: string; } -export function siginput( - signer: Signer, - { - name, - method, - path, - headers, - fields, - expires, - nonce, - alg, - keyid, - context, - }: SiginputArgs -): [Map, Siger | Cigar] { +/** + * Prepare signature-parameters (https://datatracker.ietf.org/doc/html/rfc9421#section-2.3) + * and signature-base (https://datatracker.ietf.org/doc/html/rfc9421#section-2.5) strings based on the following input + * @param fields - signature fields names in a signature order + * @param signatureParams - signature params string + * @param headers - request headers to derive signature input components from + * @param method - request method + * @param path - request path + * @param authority - request authority + */ +export function sigbase( + fields: Array, + signatureParams: string, + headers: Headers, + method: string, + path: string, + authority?: string, +): string { const items = new Array(); - const ifields = new Array<[string, Map]>(); - - fields.forEach((field) => { + fields.forEach((field: string) => { if (field.startsWith('@')) { switch (field) { case '@method': items.push(`"${field}": ${method}`); - ifields.push([field, new Map()]); break; case '@path': items.push(`"${field}": ${path}`); - ifields.push([field, new Map()]); + break; + case '@authority': + items.push(`"${ field }": ${ authority }`); break; } - } else { - if (!headers.has(field)) return; - - ifields.push([field, new Map()]); - const value = normalize(headers.get(field)!); + } else if (headers.has(field)) { + const value = normalize(headers.get(field) as string); items.push(`"${field}": ${value}`); } }); + items.push(`"@signature-params": ${ signatureParams }`); + return items.join('\n'); +} +/** + * Build a signature-params string based on the {@link Inputage} values + * @param input - the input values for signature-params + */ +export function siginput(input: Inputage): string { + const ifields = new Array<[string, Map]>(); + input.fields.forEach((field: string) => { + ifields.push([field, new Map()]); + }); const nameParams = new Map(); - const now = Math.floor(nowUTC().getTime() / 1000); - nameParams.set('created', now); - - const values = [ - `(${ifields.map((field) => field[0]).join(' ')})`, - `created=${now}`, - ]; - if (expires != undefined) { - values.push(`expires=${expires}`); - nameParams.set('expires', expires); + nameParams.set('created', input.created); + if (input.expires != undefined) { + nameParams.set('expires', input.expires); } - if (nonce != undefined) { - values.push(`nonce=${nonce}`); - nameParams.set('nonce', nonce); + if (input.nonce != undefined) { + nameParams.set('nonce', input.nonce); } - if (keyid != undefined) { - values.push(`keyid=${keyid}`); - nameParams.set('keyid', keyid); + if (input.keyid != undefined) { + nameParams.set('keyid', input.keyid); } - if (context != undefined) { - values.push(`context=${context}`); - nameParams.set('context', context); + if (input.context != undefined) { + nameParams.set('context', input.context); } - if (alg != undefined) { - values.push(`alg=${alg}`); - nameParams.set('alg', alg); + if (input.alg != undefined) { + nameParams.set('alg', input.alg); } - const sid = new Map([[name, [ifields, nameParams]]]); - - const params = values.join(';'); - items.push(`"@signature-params: ${params}"`); - - const ser = items.join('\n'); - const sig = signer.sign(b(ser)); - - return [ - new Map([ - ['Signature-Input', `${serializeDictionary(sid as Dictionary)}`], - ]), - sig, - ]; + return serializeInnerList([ifields, nameParams]); } export class Unqualified { @@ -130,23 +101,26 @@ export class Unqualified { } export class Inputage { - public name: any; public fields: any; public created: any; - public expires: any; - public nonce: any; - public alg: any; - public keyid: any; - public context: any; + public expires?: any; + public nonce?: any; + public alg?: any; + public keyid?: any; + public context?: any; } -export function desiginput(value: string): Array { + +/** + * Parse a Signature-Input value into an {@link Inputage} by label map + * @param value - Signature-Input string + */ +export function desiginput(value: string): Map { const sid = parseDictionary(value); - const siginputs = new Array(); + const siginputs = new Map(); sid.forEach((value, key) => { const siginput = new Inputage(); - siginput.name = key; let list: Item[]; let params; [list, params] = value as [Item[], Parameters]; @@ -179,11 +153,12 @@ export function desiginput(value: string): Array { siginput.context = params.get('context'); } - siginputs.push(siginput); + siginputs.set(key, siginput); }); return siginputs; } + /** Parse start, end and total from HTTP Content-Range header value * @param {string|null} header - HTTP Range header value * @param {string} typ - type of range, e.g. "aids" diff --git a/test/core/authing.test.ts b/test/core/authing.test.ts index 27466437..1694bbd6 100644 --- a/test/core/authing.test.ts +++ b/test/core/authing.test.ts @@ -1,10 +1,10 @@ import { strict as assert } from 'assert'; import libsodium from 'libsodium-wrappers-sumo'; -import { Salter } from '../../src/keri/core/salter'; -import { b } from '../../src/keri/core/core'; -import { Authenticater } from '../../src/keri/core/authing'; +import { Authenticater, b, Inputage, Matter, MtrDex, Salter, Signer, Verfer } from '../../src'; import * as utilApi from '../../src/keri/core/utils'; -import { Verfer } from '../../src/keri/core/verfer'; +import * as httping from '../../src/keri/core/httping'; +import { mock } from 'ts-mockito'; +import Base64 from 'urlsafe-base64'; describe('Authenticater.verify', () => { it('verify signature on Response', async () => { @@ -12,7 +12,7 @@ describe('Authenticater.verify', () => { const salt = '0123456789abcdef'; const salter = new Salter({ raw: b(salt) }); const signer = salter.signer(); - const aaid = 'DMZh_y-H5C3cSbZZST-fqnsmdNTReZxIh0t2xSTOJQ8a'; + const aaid = 'DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt'; const verfer = new Verfer({ qb64: aaid }); const headers = new Headers([ @@ -20,11 +20,11 @@ describe('Authenticater.verify', () => { ['Content-Type', 'application/json'], [ 'Signature', - 'indexed="?0";signify="0BDLh8QCytVBx1YMam4Vt8s4b9HAW1dwfE4yU5H_w1V6gUvPBoVGWQlIMdC16T3WFWHDHCbMcuceQzrr6n9OULsK"', + 'indexed="?0";signify="0BBRr2GjhqbkjEUSYHLNyu0w4ORZw2IU9AOYikZfIBKESdrY_O1E_ePGYzzK_4I7LLkqZOiulq7P527t2zU5vKoH"', ], [ 'Signature-Input', - 'signify=("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei";alg="ed25519"', + 'signify=("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', ], [ 'Signify-Resource', @@ -32,14 +32,137 @@ describe('Authenticater.verify', () => { ], ['Signify-Timestamp', '2023-05-22T00:37:00.248708+00:00'], ]); + const desiginputMock = jest.fn(); + const input = { + fields: [ + 'signify-resource', + '@method', + '@path', + 'signify-timestamp', + ], + created: 1684715820, + keyid: 'DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt', + alg: 'ed25519', + } as Inputage; + desiginputMock.mockReturnValue(new Map([['signify', input]])); + const sigbaseMock = jest.fn(); + sigbaseMock.mockReturnValue( + '"signify-resource": EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei\n' + + '"@method": GET\n' + + '"@path": /identifiers/aid1\n' + + '"signify-timestamp": 2023-05-22T00:37:00.248708+00:00\n' + + '"@signature-params": ("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', + ); + const siginputMock = jest.fn(); + siginputMock.mockReturnValue('("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'); + jest.spyOn(httping, 'sigbase').mockImplementation(sigbaseMock); + jest.spyOn(httping, 'siginput').mockImplementation(siginputMock); + jest.spyOn(httping, 'desiginput').mockImplementation(desiginputMock); const authn = new Authenticater(signer, verfer); assert.notEqual(authn, undefined); + assert.equal(authn.verify(headers, 'GET', '/identifiers/aid1'), true); + expect(desiginputMock).toHaveBeenCalledTimes(1); + expect(desiginputMock).toHaveBeenCalledWith('signify=("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'); + expect(siginputMock).toHaveBeenCalledTimes(1); + expect(siginputMock).toHaveBeenCalledWith(input); + expect(sigbaseMock).toHaveBeenCalledTimes(1); + expect(sigbaseMock).toHaveBeenCalledWith( + input.fields, + '("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', + headers, + 'GET', + '/identifiers/aid1', + undefined, + ); + }); + it('verify test request https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { + await libsodium.ready; + + const publicKey = new Uint8Array([ + 38, 180, 11, 143, 147, 255, 243, 216, 151, 17, 47, 126, 188, 88, 43, + 35, 45, 189, 114, 81, 125, 8, 47, 232, 60, 251, 48, 221, 206, 67, + 209, 187, + ]); + const signerMock = mock(Signer); + const verfer = new Verfer({ raw: publicKey }); + const expectedSignatureB64 = + 'wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw=='; + const expectedSignatureRaw = new Uint8Array( + Base64.decode(expectedSignatureB64), + ); + const expectedSignatureCESR = new Matter({ + raw: expectedSignatureRaw, + code: MtrDex.Ed25519_Sig, + }); + + const headers = new Headers([ + [ + 'content-digest', + 'sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:', + ], + ['content-length', '18'], + ['content-type', 'application/json'], + ['date', 'Tue, 20 Apr 2021 02:07:55 GMT'], + [ + 'Signature', + `indexed="?0";signify="${ expectedSignatureCESR.qb64 }"`, + ], + [ + 'Signature-Input', + 'signify=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"', + ], + ]); + const desiginputMock = jest.fn(); + const input = { + fields: [ + 'date', + '@method', + '@path', + '@authority', + 'content-type', + 'content-length', + ], + created: 1618884473, + keyid: 'test-key-ed25519', + } as Inputage; + desiginputMock.mockReturnValue(new Map([['signify', input]])); + const sigbaseMock = jest.fn(); + sigbaseMock.mockReturnValue( + '"date": Tue, 20 Apr 2021 02:07:55 GMT\n' + + '"@method": POST\n' + + '"@path": /foo\n' + + '"@authority": example.com\n' + + '"content-type": application/json\n' + + '"content-length": 18\n' + + '"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"', + ); + const siginputMock = jest.fn(); + siginputMock.mockReturnValue('("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'); + jest.spyOn(httping, 'sigbase').mockImplementation(sigbaseMock); + jest.spyOn(httping, 'siginput').mockImplementation(siginputMock); + jest.spyOn(httping, 'desiginput').mockImplementation(desiginputMock); + + const authn = new Authenticater(signerMock, verfer); + assert.notEqual(authn, undefined); assert.equal( - authn.verify(new Headers(headers), 'GET', '/identifiers/aid1'), + authn.verify(new Headers(headers), 'POST', '/foo', 'example.com'), true ); + expect(desiginputMock).toHaveBeenCalledTimes(1); + expect(desiginputMock).toHaveBeenCalledWith('signify=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'); + expect(siginputMock).toHaveBeenCalledTimes(1); + expect(siginputMock).toHaveBeenCalledWith(input); + expect(sigbaseMock).toHaveBeenCalledTimes(1); + expect(sigbaseMock).toHaveBeenCalledWith( + input.fields, + '("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"', + headers, + 'POST', + '/foo', + 'example.com', + ); }); }); @@ -66,6 +189,19 @@ describe('Authenticater.sign', () => { new Date('2021-01-01T00:00:00.000000+00:00') ); + const sigbaseMock = jest.fn(); + sigbaseMock.mockReturnValue( + '"@method": POST\n' + + '"@path": /boot\n' + + '"signify-resource": EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs\n' + + '"signify-timestamp": 2022-09-24T00:05:48.196795+00:00\n' + + '"@signature-params": ("@method" "@path" "signify-resource" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', + ); + const siginputMock = jest.fn(); + siginputMock.mockReturnValue('("@method" "@path" "signify-resource" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'); + jest.spyOn(httping, 'sigbase').mockImplementation(sigbaseMock); + jest.spyOn(httping, 'siginput').mockImplementation(siginputMock); + const authn = new Authenticater(signer, verfer); headers = authn.sign(headers, 'POST', '/boot'); @@ -77,7 +213,29 @@ describe('Authenticater.sign', () => { ); assert.equal( headers.get('Signature'), - 'indexed="?0";signify="0BChvN_BWAf-mgEuTnWfNnktgHdWOuOh9cWc4o0GFWuZOwra3DyJT5dJ_6BX7AANDOTnIlAKh5Sg_9qGQXHjj5oJ"' + 'indexed="?0";signify="0BDcjKTbpvmcF9oIeCI-95enQRd3_PfAgzOWi9vVf811lWGlTTOsKtFzpdkwr90ksvpvB_GhvsbV2l29wFN_QW0K"', + ); + + const expectedInput = { + fields: [ + '@method', + '@path', + 'signify-resource', + 'signify-timestamp', + ], + created: 1609459200, + alg: 'ed25519', + keyid: 'DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt', + } as Inputage; + expect(siginputMock).toHaveBeenCalledTimes(1); + expect(siginputMock).toHaveBeenCalledWith(expectedInput); + expect(sigbaseMock).toHaveBeenCalledTimes(1); + expect(sigbaseMock).toHaveBeenCalledWith( + expectedInput.fields, + '("@method" "@path" "signify-resource" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', + headers, + 'POST', + '/boot', ); }); }); diff --git a/test/core/httping.test.ts b/test/core/httping.test.ts index c482fc25..6b2c5fb8 100644 --- a/test/core/httping.test.ts +++ b/test/core/httping.test.ts @@ -1,87 +1,205 @@ import { strict as assert } from 'assert'; -import libsodium from 'libsodium-wrappers-sumo'; -import { Salter } from '../../src/keri/core/salter'; -import { b } from '../../src/keri/core/core'; -import { - siginput, - desiginput, - SiginputArgs, -} from '../../src/keri/core/httping'; -import * as utilApi from '../../src/keri/core/utils'; +import { desiginput, Inputage, sigbase, siginput } from '../../src'; describe('siginput', () => { - it('create valid Signature-Input header with signature', async () => { - await libsodium.ready; - const salt = '0123456789abcdef'; - const salter = new Salter({ raw: b(salt) }); - const signer = salter.signer(); - - const headers: Headers = new Headers([ - ['Content-Type', 'application/json'], - ['Content-Length', '256'], - ['Connection', 'close'], - [ - 'Signify-Resource', - 'EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs', - ], - ['Signify-Timestamp', '2022-09-24T00:05:48.196795+00:00'], - ]); - jest.spyOn(utilApi, 'nowUTC').mockReturnValue( - new Date('2021-01-01T00:00:00.000000+00:00') - ); - - const [header, sig] = siginput(signer, { - name: 'sig0', - method: 'POST', - path: '/signify', - headers, + it('create valid signature parameters string from Inputage', async () => { + const input = { fields: [ - 'Signify-Resource', + 'signify-resource', '@method', '@path', - 'Signify-Timestamp', + 'signify-timestamp', ], + created: 1609459200, alg: 'ed25519', - keyid: signer.verfer.qb64, - } as SiginputArgs); - - assert.equal(header.size, 1); - assert.equal(header.has('Signature-Input'), true); - const sigipt = header.get('Signature-Input'); - assert.equal( - sigipt, - 'sig0=("Signify-Resource" "@method" "@path" "Signify-Timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"' - ); + keyid: 'DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt', + } as Inputage; + const signiputString = siginput(input); assert.equal( - sig.qb64, - '0BAJWoDvZXYKnq_9rFTy_mucctxk3rVK6szopNi1rq5WQcJSNIw-_PocSQNoQGD1Ow_s2mDI5-Qqm34Y56gUKQcF' + signiputString, + '("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', ); }); + it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#section-2.5', async () => { + const expectedParameters = '("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"'; + const input = { + fields: [ + '@method', + '@authority', + '@path', + 'content-digest', + 'content-length', + 'content-type', + ], + created: 1618884473, + keyid: 'test-key-rsa-pss', + } as Inputage; + const result = siginput(input); + assert.equal(result, expectedParameters); + }); + it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { + const expectedParameters = '("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; + const input = { + fields: [ + 'date', + '@method', + '@path', + '@authority', + 'content-type', + 'content-length', + ], + created: 1618884473, + keyid: 'test-key-ed25519', + } as Inputage; + const result = siginput(input); + assert.equal(result, expectedParameters); + }); }); describe('desiginput', () => { - it('create valid Signature-Input header with signature', async () => { - await libsodium.ready; + it('parse signature input to a map of Inputage object by label', async () => { const siginput = - 'sig0=("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3";alg="ed25519"'; + 'sig0=("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3";alg="ed25519", ' + + 'sig1=("@method" "@path" "signify-resource" "signify-timestamp");created=1609459201;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";expires=1609459210;alg="ed25519"'; const inputs = desiginput(siginput); - assert.equal(inputs.length, 1); - const input = inputs[0]; - assert.deepStrictEqual(input.fields, [ + assert.equal(inputs.size, 2); + const sig0Input = inputs.get('sig0')!; + assert.deepStrictEqual(sig0Input.fields, [ 'signify-resource', '@method', '@path', 'signify-timestamp', ]); - assert.equal(input.created, 1609459200); - assert.equal(input.alg, 'ed25519'); + assert.equal(sig0Input.created, 1609459200); + assert.equal(sig0Input.alg, 'ed25519'); assert.equal( - input.keyid, + sig0Input.keyid, 'EIaGMMWJFPmtXznY1IIiKDIrg-vIyge6mBl2QV8dDjI3' ); + assert.equal(sig0Input.expires, undefined); + assert.equal(sig0Input.nonce, undefined); + assert.equal(sig0Input.context, undefined); + const sig1Input = inputs.get('sig1')!; + assert.deepStrictEqual(sig1Input.fields, [ + '@method', + '@path', + 'signify-resource', + 'signify-timestamp', + ]); + assert.equal(sig1Input.created, 1609459201); + assert.equal(sig1Input.alg, 'ed25519'); + assert.equal( + sig1Input.keyid, + 'DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt', + ); + assert.equal(sig1Input.expires, 1609459210); + assert.equal(sig1Input.nonce, undefined); + assert.equal(sig1Input.context, undefined); + }); + it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { + const siginputString = 'sig-b26=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; + const inputs = desiginput(siginputString); + assert.equal(inputs.size, 1); + const input = inputs.get('sig-b26')!; + assert.deepStrictEqual(input.fields, [ + 'date', + '@method', + '@path', + '@authority', + 'content-type', + 'content-length', + ]); + assert.equal(input.created, 1618884473); + assert.equal(input.alg, undefined); + assert.equal(input.keyid, 'test-key-ed25519'); assert.equal(input.expires, undefined); assert.equal(input.nonce, undefined); assert.equal(input.context, undefined); }); }); + +describe('sigbase', () => { + it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#section-2.5', async () => { + const expectedSigbase = + '"@method": POST\n' + + '"@authority": example.com\n' + + '"@path": /foo\n' + + '"content-digest": sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:\n' + + '"content-length": 18\n' + + '"content-type": application/json\n' + + '"@signature-params": ("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"'; + const signatureParams = '("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"'; + const fields = [ + '@method', + '@authority', + '@path', + 'content-digest', + 'content-length', + 'content-type', + ]; + const inputHeaders = new Headers([ + [ + 'content-digest', + 'sha-512=:WZDPaVn/7XgHaAy8pmojAkGWoRx2UFChF41A2svX+TaPm+AbwAgBWnrIiYllu7BNNyealdVLvRwEmTHWXvJwew==:', + ], + ['content-length', '18'], + ['content-type', 'application/json'], + ]); + const result = sigbase(fields, signatureParams, inputHeaders, 'POST', '/foo', 'example.com'); + assert.equal(result, expectedSigbase); + }); + it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { + const expectedSigbase = + '"date": Tue, 20 Apr 2021 02:07:55 GMT\n' + + '"@method": POST\n' + + '"@path": /foo\n' + + '"@authority": example.com\n' + + '"content-type": application/json\n' + + '"content-length": 18\n' + + '"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; + const signatureParams = '("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; + const fields = [ + 'date', + '@method', + '@path', + '@authority', + 'content-type', + 'content-length', + ]; + const inputHeaders = new Headers([ + ['content-length', '18'], + ['date', 'Tue, 20 Apr 2021 02:07:55 GMT'], + ['content-type', 'application/json'], + ]); + const result = sigbase(fields, signatureParams, inputHeaders, 'POST', '/foo', 'example.com'); + assert.equal(result, expectedSigbase); + }); + it('signify valid', async () => { + const expectedSigbase = + '"signify-resource": EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs\n' + + '"@method": POST\n' + + '"@path": /signify\n' + + '"signify-timestamp": 2022-09-24T00:05:48.196795+00:00\n' + + '"@signature-params": ("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'; + const signatureParams = '("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'; + const fields = [ + 'signify-resource', + '@method', + '@path', + 'signify-timestamp', + ]; + const inputHeaders: Headers = new Headers([ + ['Content-Type', 'application/json'], + ['Content-Length', '256'], + ['Connection', 'close'], + [ + 'Signify-Resource', + 'EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs', + ], + ['Signify-Timestamp', '2022-09-24T00:05:48.196795+00:00'], + ]); + const result = sigbase(fields, signatureParams, inputHeaders, 'POST', '/signify'); + assert.equal(result, expectedSigbase); + }); +}); diff --git a/test/core/signer.test.ts b/test/core/signer.test.ts index 439a08b7..f0b931c3 100644 --- a/test/core/signer.test.ts +++ b/test/core/signer.test.ts @@ -4,6 +4,7 @@ import libsodium from 'libsodium-wrappers-sumo'; import { Signer } from '../../src'; import { Matter, MtrDex } from '../../src'; import { b } from '../../src'; +import Base64 from 'urlsafe-base64'; describe('Signer', () => { it('should sign things', async () => { @@ -26,4 +27,30 @@ describe('Signer', () => { const result = signer.verfer.verify(cigar.raw, ser); assert.equal(result, true); }); + it('signs following RFC https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { + await libsodium.ready; + + const content = + '"date": Tue, 20 Apr 2021 02:07:55 GMT\n' + + '"@method": POST\n' + + '"@path": /foo\n' + + '"@authority": example.com\n' + + '"content-type": application/json\n' + + '"content-length": 18\n' + + '"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; + const privateKey = new Uint8Array([ + 159, 131, 98, 248, 122, 72, 74, 149, 78, 110, 116, 12, 91, 76, 14, + 132, 34, 145, 57, 162, 10, 168, 171, 86, 255, 102, 88, 111, 106, + 125, 41, 197, + ]); + const expectedSignature = + 'wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw=='; + + const signer = new Signer({ raw: privateKey }); + const result = signer.sign(b(content)); + const expectedSignatureBytes = new Uint8Array( + Base64.decode(expectedSignature), + ); + assert.deepStrictEqual(result.raw, expectedSignatureBytes); + }); }); From 48b837f70e72fe9aa8ab4d056f494750ed8fb331 Mon Sep 17 00:00:00 2001 From: "dmitrii.tychinin" Date: Fri, 8 Mar 2024 17:46:32 +0000 Subject: [PATCH 2/4] * unit test fix: missing function argument --- test/core/authing.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/core/authing.test.ts b/test/core/authing.test.ts index 1694bbd6..74fc2b8e 100644 --- a/test/core/authing.test.ts +++ b/test/core/authing.test.ts @@ -236,6 +236,7 @@ describe('Authenticater.sign', () => { headers, 'POST', '/boot', + undefined ); }); }); From 342f6c3ed550ad1b26f3a8ca939b9d12beb58eea Mon Sep 17 00:00:00 2001 From: "dmitrii.tychinin" Date: Fri, 8 Mar 2024 18:41:25 +0000 Subject: [PATCH 3/4] * TS5097 import path fix --- src/keri/core/authing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/keri/core/authing.ts b/src/keri/core/authing.ts index aa1c167b..70f9b941 100644 --- a/src/keri/core/authing.ts +++ b/src/keri/core/authing.ts @@ -5,7 +5,7 @@ import { designature, Signage, signature } from '../end/ending'; import { Cigar } from './cigar'; import { Siger } from './siger'; import { nowUTC } from './utils'; -import { b } from './core.ts'; +import { b } from './core'; export class Authenticater { static readonly DefaultFields = [ From 4bd7b7d8c2413536cb2856d3ce8f6bfe87111a2d Mon Sep 17 00:00:00 2001 From: "dmitrii.tychinin" Date: Mon, 11 Mar 2024 10:39:07 +0000 Subject: [PATCH 4/4] * run prettier --- src/keri/core/authing.ts | 15 ++++++--- src/keri/core/httping.ts | 14 +++++--- test/core/authing.test.ts | 69 +++++++++++++++++++++++++-------------- test/core/httping.test.ts | 48 ++++++++++++++++++++------- test/core/signer.test.ts | 2 +- 5 files changed, 102 insertions(+), 46 deletions(-) diff --git a/src/keri/core/authing.ts b/src/keri/core/authing.ts index 70f9b941..a54c49d6 100644 --- a/src/keri/core/authing.ts +++ b/src/keri/core/authing.ts @@ -26,7 +26,7 @@ export class Authenticater { headers: Headers, method: string, path: string, - authority?: string, + authority?: string ): boolean { const siginputHeader = headers.get('Signature-Input'); if (siginputHeader == null) { @@ -47,7 +47,7 @@ export class Authenticater { headers, method, path, - authority, + authority ); const signage = designature(signature); const cig = signage[0].markers.get('signify'); @@ -74,8 +74,15 @@ export class Authenticater { keyid: this._csig.verfer.qb64, }; const signatureParams = siginput(input); - const signatureBase = sigbase(fields, signatureParams, headers, method, path, authority); - const sid = `signify=${ signatureParams }`; + const signatureBase = sigbase( + fields, + signatureParams, + headers, + method, + path, + authority + ); + const sid = `signify=${signatureParams}`; headers.append('Signature-Input', sid); const sig = this._csig.sign(b(signatureBase)); diff --git a/src/keri/core/httping.ts b/src/keri/core/httping.ts index e6fe513a..b9d9a606 100644 --- a/src/keri/core/httping.ts +++ b/src/keri/core/httping.ts @@ -1,4 +1,9 @@ -import { Item, Parameters, parseDictionary, serializeInnerList } from 'structured-headers'; +import { + Item, + Parameters, + parseDictionary, + serializeInnerList, +} from 'structured-headers'; import { b } from './core'; import Base64 from 'urlsafe-base64'; import { Buffer } from 'buffer'; @@ -30,7 +35,7 @@ export function sigbase( headers: Headers, method: string, path: string, - authority?: string, + authority?: string ): string { const items = new Array(); fields.forEach((field: string) => { @@ -43,7 +48,7 @@ export function sigbase( items.push(`"${field}": ${path}`); break; case '@authority': - items.push(`"${ field }": ${ authority }`); + items.push(`"${field}": ${authority}`); break; } } else if (headers.has(field)) { @@ -51,7 +56,7 @@ export function sigbase( items.push(`"${field}": ${value}`); } }); - items.push(`"@signature-params": ${ signatureParams }`); + items.push(`"@signature-params": ${signatureParams}`); return items.join('\n'); } @@ -110,7 +115,6 @@ export class Inputage { public context?: any; } - /** * Parse a Signature-Input value into an {@link Inputage} by label map * @param value - Signature-Input string diff --git a/test/core/authing.test.ts b/test/core/authing.test.ts index 74fc2b8e..1926c623 100644 --- a/test/core/authing.test.ts +++ b/test/core/authing.test.ts @@ -1,6 +1,15 @@ import { strict as assert } from 'assert'; import libsodium from 'libsodium-wrappers-sumo'; -import { Authenticater, b, Inputage, Matter, MtrDex, Salter, Signer, Verfer } from '../../src'; +import { + Authenticater, + b, + Inputage, + Matter, + MtrDex, + Salter, + Signer, + Verfer, +} from '../../src'; import * as utilApi from '../../src/keri/core/utils'; import * as httping from '../../src/keri/core/httping'; import { mock } from 'ts-mockito'; @@ -48,13 +57,15 @@ describe('Authenticater.verify', () => { const sigbaseMock = jest.fn(); sigbaseMock.mockReturnValue( '"signify-resource": EEXekkGu9IAzav6pZVJhkLnjtjM5v3AcyA-pdKUcaGei\n' + - '"@method": GET\n' + - '"@path": /identifiers/aid1\n' + - '"signify-timestamp": 2023-05-22T00:37:00.248708+00:00\n' + - '"@signature-params": ("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', + '"@method": GET\n' + + '"@path": /identifiers/aid1\n' + + '"signify-timestamp": 2023-05-22T00:37:00.248708+00:00\n' + + '"@signature-params": ("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"' ); const siginputMock = jest.fn(); - siginputMock.mockReturnValue('("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'); + siginputMock.mockReturnValue( + '("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"' + ); jest.spyOn(httping, 'sigbase').mockImplementation(sigbaseMock); jest.spyOn(httping, 'siginput').mockImplementation(siginputMock); jest.spyOn(httping, 'desiginput').mockImplementation(desiginputMock); @@ -63,7 +74,9 @@ describe('Authenticater.verify', () => { assert.notEqual(authn, undefined); assert.equal(authn.verify(headers, 'GET', '/identifiers/aid1'), true); expect(desiginputMock).toHaveBeenCalledTimes(1); - expect(desiginputMock).toHaveBeenCalledWith('signify=("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'); + expect(desiginputMock).toHaveBeenCalledWith( + 'signify=("signify-resource" "@method" "@path" "signify-timestamp");created=1684715820;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"' + ); expect(siginputMock).toHaveBeenCalledTimes(1); expect(siginputMock).toHaveBeenCalledWith(input); expect(sigbaseMock).toHaveBeenCalledTimes(1); @@ -73,7 +86,7 @@ describe('Authenticater.verify', () => { headers, 'GET', '/identifiers/aid1', - undefined, + undefined ); }); it('verify test request https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { @@ -89,7 +102,7 @@ describe('Authenticater.verify', () => { const expectedSignatureB64 = 'wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw=='; const expectedSignatureRaw = new Uint8Array( - Base64.decode(expectedSignatureB64), + Base64.decode(expectedSignatureB64) ); const expectedSignatureCESR = new Matter({ raw: expectedSignatureRaw, @@ -106,7 +119,7 @@ describe('Authenticater.verify', () => { ['date', 'Tue, 20 Apr 2021 02:07:55 GMT'], [ 'Signature', - `indexed="?0";signify="${ expectedSignatureCESR.qb64 }"`, + `indexed="?0";signify="${expectedSignatureCESR.qb64}"`, ], [ 'Signature-Input', @@ -130,15 +143,17 @@ describe('Authenticater.verify', () => { const sigbaseMock = jest.fn(); sigbaseMock.mockReturnValue( '"date": Tue, 20 Apr 2021 02:07:55 GMT\n' + - '"@method": POST\n' + - '"@path": /foo\n' + - '"@authority": example.com\n' + - '"content-type": application/json\n' + - '"content-length": 18\n' + - '"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"', + '"@method": POST\n' + + '"@path": /foo\n' + + '"@authority": example.com\n' + + '"content-type": application/json\n' + + '"content-length": 18\n' + + '"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"' ); const siginputMock = jest.fn(); - siginputMock.mockReturnValue('("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'); + siginputMock.mockReturnValue( + '("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"' + ); jest.spyOn(httping, 'sigbase').mockImplementation(sigbaseMock); jest.spyOn(httping, 'siginput').mockImplementation(siginputMock); jest.spyOn(httping, 'desiginput').mockImplementation(desiginputMock); @@ -151,7 +166,9 @@ describe('Authenticater.verify', () => { true ); expect(desiginputMock).toHaveBeenCalledTimes(1); - expect(desiginputMock).toHaveBeenCalledWith('signify=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'); + expect(desiginputMock).toHaveBeenCalledWith( + 'signify=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"' + ); expect(siginputMock).toHaveBeenCalledTimes(1); expect(siginputMock).toHaveBeenCalledWith(input); expect(sigbaseMock).toHaveBeenCalledTimes(1); @@ -161,7 +178,7 @@ describe('Authenticater.verify', () => { headers, 'POST', '/foo', - 'example.com', + 'example.com' ); }); }); @@ -192,13 +209,15 @@ describe('Authenticater.sign', () => { const sigbaseMock = jest.fn(); sigbaseMock.mockReturnValue( '"@method": POST\n' + - '"@path": /boot\n' + - '"signify-resource": EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs\n' + - '"signify-timestamp": 2022-09-24T00:05:48.196795+00:00\n' + - '"@signature-params": ("@method" "@path" "signify-resource" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', + '"@path": /boot\n' + + '"signify-resource": EWJkQCFvKuyxZi582yJPb0wcwuW3VXmFNuvbQuBpgmIs\n' + + '"signify-timestamp": 2022-09-24T00:05:48.196795+00:00\n' + + '"@signature-params": ("@method" "@path" "signify-resource" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"' ); const siginputMock = jest.fn(); - siginputMock.mockReturnValue('("@method" "@path" "signify-resource" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'); + siginputMock.mockReturnValue( + '("@method" "@path" "signify-resource" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"' + ); jest.spyOn(httping, 'sigbase').mockImplementation(sigbaseMock); jest.spyOn(httping, 'siginput').mockImplementation(siginputMock); @@ -213,7 +232,7 @@ describe('Authenticater.sign', () => { ); assert.equal( headers.get('Signature'), - 'indexed="?0";signify="0BDcjKTbpvmcF9oIeCI-95enQRd3_PfAgzOWi9vVf811lWGlTTOsKtFzpdkwr90ksvpvB_GhvsbV2l29wFN_QW0K"', + 'indexed="?0";signify="0BDcjKTbpvmcF9oIeCI-95enQRd3_PfAgzOWi9vVf811lWGlTTOsKtFzpdkwr90ksvpvB_GhvsbV2l29wFN_QW0K"' ); const expectedInput = { diff --git a/test/core/httping.test.ts b/test/core/httping.test.ts index 6b2c5fb8..cfaa24b2 100644 --- a/test/core/httping.test.ts +++ b/test/core/httping.test.ts @@ -17,11 +17,12 @@ describe('siginput', () => { const signiputString = siginput(input); assert.equal( signiputString, - '("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"', + '("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"' ); }); it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#section-2.5', async () => { - const expectedParameters = '("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"'; + const expectedParameters = + '("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"'; const input = { fields: [ '@method', @@ -38,7 +39,8 @@ describe('siginput', () => { assert.equal(result, expectedParameters); }); it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { - const expectedParameters = '("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; + const expectedParameters = + '("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; const input = { fields: [ 'date', @@ -91,14 +93,15 @@ describe('desiginput', () => { assert.equal(sig1Input.alg, 'ed25519'); assert.equal( sig1Input.keyid, - 'DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt', + 'DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt' ); assert.equal(sig1Input.expires, 1609459210); assert.equal(sig1Input.nonce, undefined); assert.equal(sig1Input.context, undefined); }); it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { - const siginputString = 'sig-b26=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; + const siginputString = + 'sig-b26=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; const inputs = desiginput(siginputString); assert.equal(inputs.size, 1); const input = inputs.get('sig-b26')!; @@ -129,7 +132,8 @@ describe('sigbase', () => { '"content-length": 18\n' + '"content-type": application/json\n' + '"@signature-params": ("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"'; - const signatureParams = '("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"'; + const signatureParams = + '("@method" "@authority" "@path" "content-digest" "content-length" "content-type");created=1618884473;keyid="test-key-rsa-pss"'; const fields = [ '@method', '@authority', @@ -146,7 +150,14 @@ describe('sigbase', () => { ['content-length', '18'], ['content-type', 'application/json'], ]); - const result = sigbase(fields, signatureParams, inputHeaders, 'POST', '/foo', 'example.com'); + const result = sigbase( + fields, + signatureParams, + inputHeaders, + 'POST', + '/foo', + 'example.com' + ); assert.equal(result, expectedSigbase); }); it('RFC Test https://datatracker.ietf.org/doc/html/rfc9421#appendix-B.2.6', async () => { @@ -158,7 +169,8 @@ describe('sigbase', () => { '"content-type": application/json\n' + '"content-length": 18\n' + '"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; - const signatureParams = '("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; + const signatureParams = + '("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"'; const fields = [ 'date', '@method', @@ -172,7 +184,14 @@ describe('sigbase', () => { ['date', 'Tue, 20 Apr 2021 02:07:55 GMT'], ['content-type', 'application/json'], ]); - const result = sigbase(fields, signatureParams, inputHeaders, 'POST', '/foo', 'example.com'); + const result = sigbase( + fields, + signatureParams, + inputHeaders, + 'POST', + '/foo', + 'example.com' + ); assert.equal(result, expectedSigbase); }); it('signify valid', async () => { @@ -182,7 +201,8 @@ describe('sigbase', () => { '"@path": /signify\n' + '"signify-timestamp": 2022-09-24T00:05:48.196795+00:00\n' + '"@signature-params": ("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'; - const signatureParams = '("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'; + const signatureParams = + '("signify-resource" "@method" "@path" "signify-timestamp");created=1609459200;keyid="DN54yRad_BTqgZYUSi_NthRBQrxSnqQdJXWI5UHcGOQt";alg="ed25519"'; const fields = [ 'signify-resource', '@method', @@ -199,7 +219,13 @@ describe('sigbase', () => { ], ['Signify-Timestamp', '2022-09-24T00:05:48.196795+00:00'], ]); - const result = sigbase(fields, signatureParams, inputHeaders, 'POST', '/signify'); + const result = sigbase( + fields, + signatureParams, + inputHeaders, + 'POST', + '/signify' + ); assert.equal(result, expectedSigbase); }); }); diff --git a/test/core/signer.test.ts b/test/core/signer.test.ts index f0b931c3..74a227df 100644 --- a/test/core/signer.test.ts +++ b/test/core/signer.test.ts @@ -49,7 +49,7 @@ describe('Signer', () => { const signer = new Signer({ raw: privateKey }); const result = signer.sign(b(content)); const expectedSignatureBytes = new Uint8Array( - Base64.decode(expectedSignature), + Base64.decode(expectedSignature) ); assert.deepStrictEqual(result.raw, expectedSignatureBytes); });