diff --git a/__tests__/issuing/deviceResponseWithMac.tests.ts b/__tests__/issuing/deviceResponseWithMac.tests.ts index da6baa8..2c14093 100644 --- a/__tests__/issuing/deviceResponseWithMac.tests.ts +++ b/__tests__/issuing/deviceResponseWithMac.tests.ts @@ -9,250 +9,256 @@ import { DeviceResponse, DeviceSignedDocument, } from '../../src'; -import { DEVICE_JWK, ISSUER_CERTIFICATE, ISSUER_PRIVATE_KEY_JWK, PRESENTATION_DEFINITION_1 } from './config'; +import { ISSUER_CERTIFICATE, ISSUER_PRIVATE_KEY_JWK, PRESENTATION_DEFINITION_1 } from './config'; import { DataItem, cborEncode } from '../../src/cbor'; -import COSEKeyToRAW from '../../src/cose/coseKey'; -const { d, ...publicKeyJWK } = DEVICE_JWK as jose.JWK; +const curves = [ + { name: 'P-256', alg: 'ECDH-ES', opts: { crv: 'P-256' } }, + { name: 'P-384', alg: 'ECDH-ES', opts: { crv: 'P-384' } }, + { name: 'P-521', alg: 'ECDH-ES', opts: { crv: 'P-521' } }, +]; -describe('issuing a device response with MAC authentication', () => { - let encoded: Uint8Array; - let parsedDocument: DeviceSignedDocument; - let mdoc: MDoc; - let ephemeralPrivateKey: Uint8Array; - let ephemeralPublicKey: Uint8Array; +curves.forEach((c) => { + describe(`issuing a device response with MAC authentication (using ${c.name})`, () => { + let devicePrivateKey: jose.JWK; + let devicePublicJwk: jose.JWK; + let encoded: Uint8Array; + let parsedDocument: DeviceSignedDocument; + let mdoc: MDoc; + let readerPrivateKey: Uint8Array; + let readerPublicKey: Uint8Array; - const signed = new Date('2023-10-24T14:55:18Z'); - const validUntil = new Date(signed); - validUntil.setFullYear(signed.getFullYear() + 30); + const signed = new Date('2023-10-24T14:55:18Z'); + const validUntil = new Date(signed); + validUntil.setFullYear(signed.getFullYear() + 30); - beforeAll(async () => { - const issuerPrivateKey = ISSUER_PRIVATE_KEY_JWK; - - // this is the ISSUER side - { - const document = await new Document('org.iso.18013.5.1.mDL') - .addIssuerNameSpace('org.iso.18013.5.1', { - family_name: 'Jones', - given_name: 'Ava', - birth_date: '2007-03-25', - issue_date: '2023-09-01', - expiry_date: '2028-09-30', - issuing_country: 'US', - issuing_authority: 'NY DMV', - document_number: '01-856-5050', - portrait: 'bstr', - driving_privileges: [ - { - vehicle_category_code: 'C', - issue_date: '2022-09-01', - expiry_date: '2027-09-30', - }, - ], - un_distinguishing_sign: 'tbd-us.ny.dmv', + beforeAll(async () => { + // This is the device setup + { + const keypair = await jose.generateKeyPair(c.alg, c.opts); + devicePrivateKey = await jose.exportJWK(keypair.privateKey); + const { d, ...pk } = devicePrivateKey; + devicePublicJwk = pk; + } - sex: 'F', - height: '5\' 8"', - weight: '120lb', - eye_colour: 'brown', - hair_colour: 'brown', - resident_addres: '123 Street Rd', - resident_city: 'Brooklyn', - resident_state: 'NY', - resident_postal_code: '19001', - resident_country: 'US', - issuing_jurisdiction: 'New York', - }) - .useDigestAlgorithm('SHA-512') - .addValidityInfo({ - signed, - validUntil, - }) - .addDeviceKeyInfo({ deviceKey: publicKeyJWK }) - .sign({ - issuerPrivateKey, - issuerCertificate: ISSUER_CERTIFICATE, - alg: 'ES256', - }); + // this is the ISSUER side + { + const issuerPrivateKey = ISSUER_PRIVATE_KEY_JWK; + const document = await new Document('org.iso.18013.5.1.mDL') + .addIssuerNameSpace('org.iso.18013.5.1', { + family_name: 'Jones', + given_name: 'Ava', + birth_date: '2007-03-25', + issue_date: '2023-09-01', + expiry_date: '2028-09-30', + issuing_country: 'US', + issuing_authority: 'NY DMV', + document_number: '01-856-5050', + portrait: 'bstr', + driving_privileges: [ + { + vehicle_category_code: 'C', + issue_date: '2022-09-01', + expiry_date: '2027-09-30', + }, + ], + un_distinguishing_sign: 'tbd-us.ny.dmv', + sex: 'F', + height: '5\' 8"', + weight: '120lb', + eye_colour: 'brown', + hair_colour: 'brown', + resident_addres: '123 Street Rd', + resident_city: 'Brooklyn', + resident_state: 'NY', + resident_postal_code: '19001', + resident_country: 'US', + issuing_jurisdiction: 'New York', + }) + .useDigestAlgorithm('SHA-512') + .addValidityInfo({ + signed, + validUntil, + }) + .addDeviceKeyInfo({ deviceKey: devicePublicJwk }) + .sign({ + issuerPrivateKey, + issuerCertificate: ISSUER_CERTIFICATE, + alg: 'ES256', + }); - mdoc = new MDoc([document]); + mdoc = new MDoc([document]); + } // This is the verifier side before requesting the Device Response { - const ephemeralKey = await jose.exportJWK((await jose.generateKeyPair('ES256')).privateKey); - const { d: _1, ...ephemeralKeyPublic } = ephemeralKey; - ephemeralPrivateKey = COSEKeyToRAW(COSEKeyFromJWK(ephemeralKey)); - ephemeralPublicKey = COSEKeyToRAW(COSEKeyFromJWK(ephemeralKeyPublic)); + const readerKeypair = await jose.generateKeyPair(c.alg, c.opts); + const readerKey = await jose.exportJWK(readerKeypair.privateKey); + const { d: _1, ...pubKey } = readerKey; + readerPrivateKey = COSEKeyFromJWK(readerKey); + readerPublicKey = COSEKeyFromJWK(pubKey); } - } - }); + }); - describe('using OID4VP handover', () => { - const verifierGeneratedNonce = 'abcdefg'; - const mdocGeneratedNonce = '123456'; - const clientId = 'Cq1anPb8vZU5j5C0d7hcsbuJLBpIawUJIDQRi2Ebwb4'; - const responseUri = 'http://localhost:4000/api/presentation_request/dc8999df-d6ea-4c84-9985-37a8b81a82ec/callback'; + describe('using OID4VP handover', () => { + const verifierGeneratedNonce = 'abcdefg'; + const mdocGeneratedNonce = '123456'; + const clientId = 'Cq1anPb8vZU5j5C0d7hcsbuJLBpIawUJIDQRi2Ebwb4'; + const responseUri = 'http://localhost:4000/api/presentation_request/dc8999df-d6ea-4c84-9985-37a8b81a82ec/callback'; - const getSessionTranscriptBytes = (clId: string, respUri: string, nonce: string, mdocNonce: string) => cborEncode( - DataItem.fromData([ - null, // DeviceEngagementBytes - null, // EReaderKeyBytes - [mdocNonce, clId, respUri, nonce], // Handover = OID4VPHandover - ]), - ); + const getSessionTranscriptBytes = (clId: string, respUri: string, nonce: string, mdocNonce: string) => cborEncode( + DataItem.fromData([ + null, // DeviceEngagementBytes + null, // EReaderKeyBytes + [mdocNonce, clId, respUri, nonce], // Handover = OID4VPHandover + ]), + ); - beforeAll(async () => { - // This is the Device side - const deviceResponseMDoc = await DeviceResponse.from(mdoc) - .usingPresentationDefinition(PRESENTATION_DEFINITION_1) - .usingSessionTranscriptForOID4VP(mdocGeneratedNonce, clientId, responseUri, verifierGeneratedNonce) - .authenticateWithMAC(DEVICE_JWK, ephemeralPublicKey, 'HS256') - .addDeviceNameSpace('com.foobar-device', { test: 1234 }) - .sign(); + beforeAll(async () => { + // This is the Device side + const deviceResponseMDoc = await DeviceResponse.from(mdoc) + .usingPresentationDefinition(PRESENTATION_DEFINITION_1) + .usingSessionTranscriptForOID4VP(mdocGeneratedNonce, clientId, responseUri, verifierGeneratedNonce) + .authenticateWithMAC(devicePrivateKey, readerPublicKey, 'HS256') + .addDeviceNameSpace('com.foobar-device', { test: 1234 }) + .sign(); - encoded = deviceResponseMDoc.encode(); - const parsedMDOC = parse(encoded); - [parsedDocument] = parsedMDOC.documents as DeviceSignedDocument[]; - }); + encoded = deviceResponseMDoc.encode(); + const parsedMDOC = parse(encoded); + [parsedDocument] = parsedMDOC.documents as DeviceSignedDocument[]; + }); - it('should be verifiable', async () => { - const verifier = new Verifier([ISSUER_CERTIFICATE]); - await verifier.verify(encoded, { - ephemeralReaderKey: ephemeralPrivateKey, - encodedSessionTranscript: getSessionTranscriptBytes(clientId, responseUri, verifierGeneratedNonce, mdocGeneratedNonce), + it('should be verifiable', async () => { + const verifier = new Verifier([ISSUER_CERTIFICATE]); + await verifier.verify(encoded, { + ephemeralReaderKey: readerPrivateKey, + encodedSessionTranscript: getSessionTranscriptBytes(clientId, responseUri, verifierGeneratedNonce, mdocGeneratedNonce), + }); }); - }); - describe('should not be verifiable', () => { - [ - ['clientId', { clientId: 'wrong', responseUri, verifierGeneratedNonce, mdocGeneratedNonce }] as const, - ['responseUri', { clientId, responseUri: 'wrong', verifierGeneratedNonce, mdocGeneratedNonce }] as const, - ['verifierGeneratedNonce', { clientId, responseUri, verifierGeneratedNonce: 'wrong', mdocGeneratedNonce }] as const, - ['mdocGeneratedNonce', { clientId, responseUri, verifierGeneratedNonce, mdocGeneratedNonce: 'wrong' }] as const, - ].forEach(([name, values]) => { - it(`with a different ${name}`, async () => { - try { - const verifier = new Verifier([ISSUER_CERTIFICATE]); - await verifier.verify(encoded, { - ephemeralReaderKey: ephemeralPrivateKey, - encodedSessionTranscript: getSessionTranscriptBytes(values.clientId, values.responseUri, values.verifierGeneratedNonce, values.mdocGeneratedNonce), - }); - throw new Error('should not validate with different transcripts'); - } catch (error) { - expect(error.message).toMatch('Unable to verify deviceAuth MAC: Device MAC must be valid'); - } + describe('should not be verifiable', () => { + [ + ['clientId', { clientId: 'wrong', responseUri, verifierGeneratedNonce, mdocGeneratedNonce }] as const, + ['responseUri', { clientId, responseUri: 'wrong', verifierGeneratedNonce, mdocGeneratedNonce }] as const, + ['verifierGeneratedNonce', { clientId, responseUri, verifierGeneratedNonce: 'wrong', mdocGeneratedNonce }] as const, + ['mdocGeneratedNonce', { clientId, responseUri, verifierGeneratedNonce, mdocGeneratedNonce: 'wrong' }] as const, + ].forEach(([name, values]) => { + it(`with a different ${name}`, async () => { + try { + const verifier = new Verifier([ISSUER_CERTIFICATE]); + await verifier.verify(encoded, { + ephemeralReaderKey: readerPrivateKey, + encodedSessionTranscript: getSessionTranscriptBytes(values.clientId, values.responseUri, values.verifierGeneratedNonce, values.mdocGeneratedNonce), + }); + throw new Error('should not validate with different transcripts'); + } catch (error) { + expect(error.message).toMatch('Unable to verify deviceAuth MAC: Device MAC must be valid'); + } + }); }); }); - }); - it('should generate a device mac without payload', () => { - expect(parsedDocument.deviceSigned.deviceAuth.deviceMac?.payload).toBeNull(); - }); + it('should generate a device mac without payload', () => { + expect(parsedDocument.deviceSigned.deviceAuth.deviceMac?.payload).toBeNull(); + }); - it('should contain the validity info', () => { - const { validityInfo } = parsedDocument.issuerSigned.issuerAuth.decodedPayload; - expect(validityInfo).toBeDefined(); - expect(validityInfo.signed).toEqual(signed); - expect(validityInfo.validFrom).toEqual(signed); - expect(validityInfo.validUntil).toEqual(validUntil); - expect(validityInfo.expectedUpdate).toBeUndefined(); - }); + it('should contain the validity info', () => { + const { validityInfo } = parsedDocument.issuerSigned.issuerAuth.decodedPayload; + expect(validityInfo).toBeDefined(); + expect(validityInfo.signed).toEqual(signed); + expect(validityInfo.validFrom).toEqual(signed); + expect(validityInfo.validUntil).toEqual(validUntil); + expect(validityInfo.expectedUpdate).toBeUndefined(); + }); - it('should contain the device namespaces', () => { - expect(parsedDocument.getDeviceNameSpace('com.foobar-device')) - .toEqual({ test: 1234 }); + it('should contain the device namespaces', () => { + expect(parsedDocument.getDeviceNameSpace('com.foobar-device')) + .toEqual({ test: 1234 }); + }); }); - }); - describe('using WebAPI handover', () => { - // The actual value for the engagements & the key do not matter, - // as long as the device and the reader agree on what value to use. - const eReaderKeyBytes: Buffer = randomFillSync(Buffer.alloc(32)); - const readerEngagementBytes = randomFillSync(Buffer.alloc(32)); - const deviceEngagementBytes = randomFillSync(Buffer.alloc(32)); + describe('using WebAPI handover', () => { + // The actual value for the engagements & the key do not matter, + // as long as the device and the reader agree on what value to use. + const eReaderKeyBytes: Buffer = randomFillSync(Buffer.alloc(32)); + const readerEngagementBytes = randomFillSync(Buffer.alloc(32)); + const deviceEngagementBytes = randomFillSync(Buffer.alloc(32)); - const getSessionTranscriptBytes = ( - rdrEngtBytes: Buffer, - devEngtBytes: Buffer, - eRdrKeyBytes: Buffer, - ) => cborEncode( - DataItem.fromData([ - new DataItem({ buffer: devEngtBytes }), - new DataItem({ buffer: eRdrKeyBytes }), - rdrEngtBytes, - ]), - ); + const getSessionTranscriptBytes = ( + rdrEngtBytes: Buffer, + devEngtBytes: Buffer, + eRdrKeyBytes: Buffer, + ) => cborEncode( + DataItem.fromData([ + new DataItem({ buffer: devEngtBytes }), + new DataItem({ buffer: eRdrKeyBytes }), + rdrEngtBytes, + ]), + ); - beforeAll(async () => { - // This is the verifier side before requesting the Device Response - { - const ephemeralKey = await jose.exportJWK((await jose.generateKeyPair('ES256')).privateKey); - ephemeralPrivateKey = COSEKeyToRAW(COSEKeyFromJWK(ephemeralKey)); - const { d: _1, ...ephemeralKeyPublic } = ephemeralKey; - ephemeralPublicKey = COSEKeyToRAW(COSEKeyFromJWK(ephemeralKeyPublic)); - } + beforeAll(async () => { + // This is the Device side + { + const deviceResponseMDoc = await DeviceResponse.from(mdoc) + .usingPresentationDefinition(PRESENTATION_DEFINITION_1) + .usingSessionTranscriptForWebAPI(deviceEngagementBytes, readerEngagementBytes, eReaderKeyBytes) + .authenticateWithMAC(devicePrivateKey, readerPublicKey, 'HS256') + .addDeviceNameSpace('com.foobar-device', { test: 1234 }) + .sign(); + encoded = deviceResponseMDoc.encode(); + } - // This is the Device side - { - const deviceResponseMDoc = await DeviceResponse.from(mdoc) - .usingPresentationDefinition(PRESENTATION_DEFINITION_1) - .usingSessionTranscriptForWebAPI(deviceEngagementBytes, readerEngagementBytes, eReaderKeyBytes) - .authenticateWithMAC(DEVICE_JWK, ephemeralPublicKey, 'HS256') - .addDeviceNameSpace('com.foobar-device', { test: 1234 }) - .sign(); - encoded = deviceResponseMDoc.encode(); - } - - const parsedMDOC = parse(encoded); - [parsedDocument] = parsedMDOC.documents as DeviceSignedDocument[]; - }); + const parsedMDOC = parse(encoded); + [parsedDocument] = parsedMDOC.documents as DeviceSignedDocument[]; + }); - it('should be verifiable', async () => { - const verifier = new Verifier([ISSUER_CERTIFICATE]); - await verifier.verify(encoded, { - ephemeralReaderKey: ephemeralPrivateKey, - encodedSessionTranscript: getSessionTranscriptBytes(readerEngagementBytes, deviceEngagementBytes, eReaderKeyBytes), + it('should be verifiable', async () => { + const verifier = new Verifier([ISSUER_CERTIFICATE]); + await verifier.verify(encoded, { + ephemeralReaderKey: readerPrivateKey, + encodedSessionTranscript: getSessionTranscriptBytes(readerEngagementBytes, deviceEngagementBytes, eReaderKeyBytes), + }); }); - }); - describe('should not be verifiable', () => { - const wrong = randomFillSync(Buffer.alloc(32)); - [ - ['readerEngagementBytes', { readerEngagementBytes: wrong, deviceEngagementBytes, eReaderKeyBytes }] as const, - ['deviceEngagementBytes', { readerEngagementBytes, deviceEngagementBytes: wrong, eReaderKeyBytes }] as const, - ['eReaderKeyBytes', { readerEngagementBytes, deviceEngagementBytes, eReaderKeyBytes: wrong }] as const, - ].forEach(([name, values]) => { - it(`with a different ${name}`, async () => { - const verifier = new Verifier([ISSUER_CERTIFICATE]); - try { - await verifier.verify(encoded, { - ephemeralReaderKey: ephemeralPrivateKey, - encodedSessionTranscript: getSessionTranscriptBytes(values.readerEngagementBytes, values.deviceEngagementBytes, values.eReaderKeyBytes), - }); - throw new Error('should not validate with different transcripts'); - } catch (error) { - expect(error.message).toMatch('Unable to verify deviceAuth MAC: Device MAC must be valid'); - } + describe('should not be verifiable', () => { + const wrong = randomFillSync(Buffer.alloc(32)); + [ + ['readerEngagementBytes', { readerEngagementBytes: wrong, deviceEngagementBytes, eReaderKeyBytes }] as const, + ['deviceEngagementBytes', { readerEngagementBytes, deviceEngagementBytes: wrong, eReaderKeyBytes }] as const, + ['eReaderKeyBytes', { readerEngagementBytes, deviceEngagementBytes, eReaderKeyBytes: wrong }] as const, + ].forEach(([name, values]) => { + it(`with a different ${name}`, async () => { + const verifier = new Verifier([ISSUER_CERTIFICATE]); + try { + await verifier.verify(encoded, { + ephemeralReaderKey: readerPrivateKey, + encodedSessionTranscript: getSessionTranscriptBytes(values.readerEngagementBytes, values.deviceEngagementBytes, values.eReaderKeyBytes), + }); + throw new Error('should not validate with different transcripts'); + } catch (error) { + expect(error.message).toMatch('Unable to verify deviceAuth MAC: Device MAC must be valid'); + } + }); }); }); - }); - it('should generate a device mac without payload', () => { - expect(parsedDocument.deviceSigned.deviceAuth.deviceMac?.payload).toBeNull(); - }); + it('should generate a device mac without payload', () => { + expect(parsedDocument.deviceSigned.deviceAuth.deviceMac?.payload).toBeNull(); + }); - it('should contain the validity info', () => { - const { validityInfo } = parsedDocument.issuerSigned.issuerAuth.decodedPayload; - expect(validityInfo).toBeDefined(); - expect(validityInfo.signed).toEqual(signed); - expect(validityInfo.validFrom).toEqual(signed); - expect(validityInfo.validUntil).toEqual(validUntil); - expect(validityInfo.expectedUpdate).toBeUndefined(); - }); + it('should contain the validity info', () => { + const { validityInfo } = parsedDocument.issuerSigned.issuerAuth.decodedPayload; + expect(validityInfo).toBeDefined(); + expect(validityInfo.signed).toEqual(signed); + expect(validityInfo.validFrom).toEqual(signed); + expect(validityInfo.validUntil).toEqual(validUntil); + expect(validityInfo.expectedUpdate).toBeUndefined(); + }); - it('should contain the device namespaces', () => { - expect(parsedDocument.getDeviceNameSpace('com.foobar-device')) - .toEqual({ test: 1234 }); + it('should contain the device namespaces', () => { + expect(parsedDocument.getDeviceNameSpace('com.foobar-device')) + .toEqual({ test: 1234 }); + }); }); }); }); diff --git a/package-lock.json b/package-lock.json index fb34977..b78bfcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@auth0/mdl", - "version": "0.4.0", + "version": "1.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@auth0/mdl", - "version": "0.4.0", + "version": "1.5.0", "license": "Apache-2.0", "dependencies": { "@noble/curves": "^1.2.0", diff --git a/src/mdoc/Verifier.ts b/src/mdoc/Verifier.ts index c90f27e..462b39f 100644 --- a/src/mdoc/Verifier.ts +++ b/src/mdoc/Verifier.ts @@ -20,7 +20,6 @@ import { parse } from './parser'; import IssuerAuth from './model/IssuerAuth'; import { IssuerSignedDocument } from './model/IssuerSignedDocument'; import { DeviceSignedDocument } from './model/DeviceSignedDocument'; -import COSEKeyToRAW from '../cose/coseKey'; const MDL_NAMESPACE = 'org.iso.18013.5.1'; @@ -195,10 +194,9 @@ export class Verifier { if (!options.ephemeralPrivateKey) { return; } try { - const deviceKeyRaw = COSEKeyToRAW(deviceKeyCoseKey); const ephemeralMacKey = await calculateEphemeralMacKey( options.ephemeralPrivateKey, - deviceKeyRaw, + deviceKeyCoseKey, options.sessionTranscriptBytes, ); diff --git a/src/mdoc/model/DeviceResponse.ts b/src/mdoc/model/DeviceResponse.ts index dd28284..bb9287a 100644 --- a/src/mdoc/model/DeviceResponse.ts +++ b/src/mdoc/model/DeviceResponse.ts @@ -270,11 +270,10 @@ export class DeviceResponse { deviceAuthenticationBytes: Uint8Array, sessionTranscriptBytes: any, ): Promise { - const key = COSEKeyToRAW(this.devicePrivateKey); const { kid } = COSEKeyToJWK(this.devicePrivateKey); const ephemeralMacKey = await calculateEphemeralMacKey( - key, + this.devicePrivateKey, this.ephemeralPublicKey, sessionTranscriptBytes, ); diff --git a/src/mdoc/utils.ts b/src/mdoc/utils.ts index 8106c1b..cf395f0 100644 --- a/src/mdoc/utils.ts +++ b/src/mdoc/utils.ts @@ -1,11 +1,15 @@ import * as pkijs from 'pkijs'; import { p256 } from '@noble/curves/p256'; +import { p384 } from '@noble/curves/p384'; +import { p521 } from '@noble/curves/p521'; import * as webcrypto from 'uncrypto'; import { Buffer } from 'buffer'; import hkdf from '@panva/hkdf'; +import { COSEKeyToJWK } from 'cose-kit'; import { cborEncode, cborDecode } from '../cbor'; import { DataItem } from '../cbor/DataItem'; +import COSEKeyToRAW from '../cose/coseKey'; const { subtle } = webcrypto; @@ -35,21 +39,51 @@ export const hmacSHA256 = async ( * 1. SDeviceKey.Priv and EReaderKey.Pub for the mdoc * 2. EReaderKey.Priv and SDeviceKey.Pub for the mdoc reader * - * @param {Uint8Array} privateKey - The private key of the current party - * @param {Uint8Array} publicKey - The public key of the other party + * @param {Uint8Array} privateKey - The private key of the current party (COSE) + * @param {Uint8Array} publicKey - The public key of the other party, (COSE) * @param {Uint8Array} sessionTranscriptBytes - The session transcript bytes * @returns {Uint8Array} - The ephemeral mac key */ export const calculateEphemeralMacKey = async ( - privateKey: Uint8Array, - publicKey: Uint8Array, + privateKey: Uint8Array | Map, + publicKey: Uint8Array | Map, sessionTranscriptBytes: Uint8Array, ): Promise => { - const ikm = p256.getSharedSecret( - Buffer.from(privateKey).toString('hex'), - Buffer.from(publicKey).toString('hex'), - true, - ).slice(1); + const { kty, crv } = COSEKeyToJWK(privateKey); + const privkey = COSEKeyToRAW(privateKey); // only d + const pubkey = COSEKeyToRAW(publicKey); // 0x04 || x || y + let ikm; + if ((kty === 'EC')) { + if (crv === 'P-256') { + ikm = p256 + .getSharedSecret( + Buffer.from(privkey).toString('hex'), + Buffer.from(pubkey).toString('hex'), + true, + ) + .slice(1); + } else if (crv === 'P-384') { + ikm = p384 + .getSharedSecret( + Buffer.from(privkey).toString('hex'), + Buffer.from(pubkey).toString('hex'), + true, + ) + .slice(1); + } else if (crv === 'P-521') { + ikm = p521 + .getSharedSecret( + Buffer.from(privkey).toString('hex'), + Buffer.from(pubkey).toString('hex'), + true, + ) + .slice(1); + } else { + throw new Error(`unsupported EC curve: ${crv}`); + } + } else { + throw new Error(`unsupported key type: ${kty}`); + } const salt = new Uint8Array(await subtle.digest('SHA-256', sessionTranscriptBytes)); const info = Buffer.from('EMacKey', 'utf-8'); const result = await hkdf('sha256', ikm, salt, info, 32);