diff --git a/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts b/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts index 4dbf5f44cc..144946d238 100644 --- a/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts +++ b/libs/common/src/webauthn/services/fido2-authenticator.service.spec.ts @@ -34,19 +34,6 @@ describe("FidoAuthenticatorService", () => { cipherService = mock(); userInterface = mock(); authenticator = new Fido2AuthenticatorService(cipherService, userInterface); - - // crypto.subtle.importKey doesn't work properly in jest, so we need to mock it with new keys. - const privateKey = ( - await crypto.subtle.generateKey( - { - name: "ECDSA", - namedCurve: "P-256", - }, - true, - ["sign"] - ) - ).privateKey; - crypto.subtle.importKey = jest.fn().mockResolvedValue(privateKey); }); describe("makeCredential", () => { @@ -648,16 +635,24 @@ describe("FidoAuthenticatorService", () => { describe(`assertion of ${ residentKey ? "discoverable" : "non-discoverable" } credential`, () => { + let keyPair: CryptoKeyPair; let credentialIds: string[]; let selectedCredentialId: string; let ciphers: CipherView[]; let params: Fido2AuthenticatorGetAssertionParams; beforeEach(async () => { + keyPair = await createKeyPair(); credentialIds = [Utils.newGuid(), Utils.newGuid()]; + const keyValue = Fido2Utils.bufferToString( + await crypto.subtle.exportKey("pkcs8", keyPair.privateKey) + ); if (residentKey) { ciphers = credentialIds.map((id) => - createCipherView({ type: CipherType.Fido2Key }, { rpId: RpId, counter: 9000 }) + createCipherView( + { type: CipherType.Fido2Key }, + { rpId: RpId, counter: 9000, keyValue } + ) ); selectedCredentialId = ciphers[0].id; params = await createParams({ @@ -723,8 +718,20 @@ describe("FidoAuthenticatorService", () => { ); expect(flags).toEqual(new Uint8Array([0b00000001])); // UP = true expect(counter).toEqual(new Uint8Array([0, 0, 0x23, 0x29])); // 9001 in hex - // Signatures are non-deterministic, and webcrypto can't verify DER signature format - // expect(result.signature).toMatchSnapshot(); + + // Verify signature + // TODO: Cannot verify signature because it has been converted into DER format + // const sigBase = new Uint8Array([ + // ...result.authenticatorData, + // ...Fido2Utils.bufferSourceToUint8Array(params.hash), + // ]); + // const isValidSignature = await crypto.subtle.verify( + // { name: "ECDSA", hash: { name: "SHA-256" } }, + // keyPair.publicKey, + // result.signature, + // sigBase + // ); + // expect(isValidSignature).toBe(true); }); /** Spec: If any error occurred while generating the assertion signature, return an error code equivalent to "UnknownError" and terminate the operation. */ @@ -804,3 +811,14 @@ async function createClientDataHash() { function randomBytes(length: number) { return new Uint8Array(Array.from({ length }, (_, k) => k % 255)); } + +async function createKeyPair() { + return await crypto.subtle.generateKey( + { + name: "ECDSA", + namedCurve: "P-256", + }, + true, + ["sign", "verify"] + ); +} diff --git a/libs/common/src/webauthn/services/fido2-authenticator.service.ts b/libs/common/src/webauthn/services/fido2-authenticator.service.ts index 3b89b8e9e8..166dfc4517 100644 --- a/libs/common/src/webauthn/services/fido2-authenticator.service.ts +++ b/libs/common/src/webauthn/services/fido2-authenticator.service.ts @@ -123,7 +123,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr const credentialId = params.requireResidentKey ? cipher.id : cipher.fido2Key.nonDiscoverableId; const authData = await generateAuthData({ rpId: params.rpEntity.id, - credentialId, + credentialId: Utils.guidToRawFormat(credentialId), counter: cipher.fido2Key.counter, userPresence: true, userVerification: false, @@ -201,7 +201,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr const authenticatorData = await generateAuthData({ rpId: selectedCipher.fido2Key.rpId, - credentialId: selectedCredentialId, + credentialId: Utils.guidToRawFormat(selectedCredentialId), counter: selectedCipher.fido2Key.counter, userPresence: true, userVerification: false, @@ -209,7 +209,7 @@ export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstr const signature = await generateSignature({ authData: authenticatorData, - clientData: params.hash, + clientDataHash: params.hash, privateKey: await getPrivateKeyFromCipher(selectedCipher), }); @@ -340,7 +340,7 @@ async function getPrivateKeyFromCipher(cipher: CipherView): Promise { interface AuthDataParams { rpId: string; - credentialId: string; + credentialId: BufferSource; userPresence: boolean; userVerification: boolean; counter: number; @@ -380,7 +380,7 @@ async function generateAuthData(params: AuthDataParams) { attestedCredentialData.push(...AAGUID); // credentialIdLength (2 bytes) and credential Id - const rawId = Utils.guidToRawFormat(params.credentialId); + const rawId = Fido2Utils.bufferSourceToUint8Array(params.credentialId); const credentialIdLength = [(rawId.length - (rawId.length & 0xff)) / 256, rawId.length & 0xff]; attestedCredentialData.push(...credentialIdLength); attestedCredentialData.push(...rawId); @@ -408,14 +408,15 @@ async function generateAuthData(params: AuthDataParams) { interface SignatureParams { authData: Uint8Array; - clientData: BufferSource; + clientDataHash: BufferSource; privateKey: CryptoKey; } async function generateSignature(params: SignatureParams) { - const clientData = Fido2Utils.bufferSourceToUint8Array(params.clientData); - const clientDataHash = await crypto.subtle.digest({ name: "SHA-256" }, clientData); - const sigBase = new Uint8Array([...params.authData, ...new Uint8Array(clientDataHash)]); + const sigBase = new Uint8Array([ + ...params.authData, + ...Fido2Utils.bufferSourceToUint8Array(params.clientDataHash), + ]); const p1336_signature = new Uint8Array( await crypto.subtle.sign( { diff --git a/libs/common/src/webauthn/services/fido2-client.service.ts b/libs/common/src/webauthn/services/fido2-client.service.ts index 8d4eae9c06..2ed13a05f7 100644 --- a/libs/common/src/webauthn/services/fido2-client.service.ts +++ b/libs/common/src/webauthn/services/fido2-client.service.ts @@ -159,7 +159,7 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { } const collectedClientData = { - type: "webauthn.create", + type: "webauthn.get", challenge: params.challenge, origin: params.origin, crossOrigin: !params.sameOriginWithAncestors,