import * as cbor from './cbor'; import { decodeAuthenticatorExtensions, AuthenticationExtensionsAuthenticatorOutputs, } from './decodeAuthenticatorExtensions'; import * as uint8Array from './uint8Array'; import { COSEPublicKey } from './convertCOSEtoPKCS'; /** * Make sense of the authData buffer contained in an Attestation */ export function parseAuthenticatorData(authData: Uint8Array): ParsedAuthenticatorData { if (authData.byteLength < 37) { throw new Error( `Authenticator data was ${authData.byteLength} bytes, expected at least 37 bytes`, ); } let pointer = 0; const dataView = uint8Array.toDataView(authData); const rpIdHash = authData.slice(pointer, (pointer += 32)); const flagsBuf = authData.slice(pointer, (pointer += 1)); const flagsInt = flagsBuf[0]; // Bit positions can be referenced here: // https://www.w3.org/TR/webauthn-2/#flags const flags = { up: !!(flagsInt & (1 << 0)), // User Presence uv: !!(flagsInt & (1 << 2)), // User Verified be: !!(flagsInt & (1 << 3)), // Backup Eligibility bs: !!(flagsInt & (1 << 4)), // Backup State at: !!(flagsInt & (1 << 6)), // Attested Credential Data Present ed: !!(flagsInt & (1 << 7)), // Extension Data Present flagsInt, }; const counterBuf = authData.slice(pointer, pointer + 4); const counter = dataView.getUint32(pointer, false); pointer += 4; let aaguid: Uint8Array | undefined = undefined; let credentialID: Uint8Array | undefined = undefined; let credentialPublicKey: Uint8Array | undefined = undefined; if (flags.at) { aaguid = authData.slice(pointer, (pointer += 16)); const credIDLen = dataView.getUint16(pointer); pointer += 2; credentialID = authData.slice(pointer, (pointer += credIDLen)); // Decode the next CBOR item in the buffer, then re-encode it back to a Buffer const firstDecoded = cbor.decodeFirst(authData.slice(pointer)); const firstEncoded = Uint8Array.from(cbor.encode(firstDecoded)); credentialPublicKey = firstEncoded; pointer += firstEncoded.byteLength; } let extensionsData: AuthenticationExtensionsAuthenticatorOutputs | undefined = undefined; let extensionsDataBuffer: Uint8Array | undefined = undefined; if (flags.ed) { const firstDecoded = cbor.decodeFirst(authData.slice(pointer)); extensionsDataBuffer = Uint8Array.from(cbor.encode(firstDecoded)); extensionsData = decodeAuthenticatorExtensions(extensionsDataBuffer); pointer += extensionsDataBuffer.byteLength; } // Pointer should be at the end of the authenticator data, otherwise too much data was sent if (authData.byteLength > pointer) { throw new Error('Leftover bytes detected while parsing authenticator data'); } return { rpIdHash, flagsBuf, flags, counter, counterBuf, aaguid, credentialID, credentialPublicKey, extensionsData, extensionsDataBuffer, }; } export type ParsedAuthenticatorData = { rpIdHash: Uint8Array; flagsBuf: Uint8Array; flags: { up: boolean; uv: boolean; be: boolean; bs: boolean; at: boolean; ed: boolean; flagsInt: number; }; counter: number; counterBuf: Uint8Array; aaguid?: Uint8Array; credentialID?: Uint8Array; credentialPublicKey?: Uint8Array; extensionsData?: AuthenticationExtensionsAuthenticatorOutputs; extensionsDataBuffer?: Uint8Array; };