UNPKG

ox

Version:

Ethereum Standard Library

91 lines 3.74 kB
import { p256 } from '@noble/curves/nist.js'; import * as Registration from '../../webauthn/Registration.js'; import * as Hex from '../Hex.js'; import * as PublicKey from '../PublicKey.js'; /** * Parses an ASN.1 signature into a r and s value (32-byte padded hex). * * @internal */ export function parseAsn1Signature(bytes) { const sig = p256.Signature.fromBytes(bytes, 'der'); const n = p256.Point.CURVE().n; const s = sig.hasHighS() ? n - sig.s : sig.s; return { r: Hex.fromNumber(sig.r, { size: 32 }), s: Hex.fromNumber(s, { size: 32 }), }; } /** * SPKI prefix for an uncompressed P-256 public key (91-byte total SPKI): * `SEQUENCE { SEQUENCE { OID ecPublicKey, OID secp256r1 }, BIT STRING }`. * The 26-byte prefix is followed by `0x04 || X(32) || Y(32)`. * * @internal */ const spkiP256Prefix = /*#__PURE__*/ new Uint8Array([ 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, ]); /** * Parses a P-256 uncompressed public key from a SubjectPublicKeyInfo (SPKI) * DER blob. WebAuthn `getPublicKey()` returns SPKI bytes per the spec, and for * P-256 with `alg=-7` the structure is fixed 91 bytes. The trailing 65 bytes * are exactly the SEC1 uncompressed point `0x04 || X(32) || Y(32)`. * * @internal */ function spkiToRawP256(spki) { if (spki.length !== 91) throw new Registration.CreateFailedError(); for (let i = 0; i < spkiP256Prefix.length; i++) if (spki[i] !== spkiP256Prefix[i]) throw new Registration.CreateFailedError(); if (spki[spkiP256Prefix.length] !== 0x04) throw new Registration.CreateFailedError(); return spki.subarray(spkiP256Prefix.length); } /** * Parses a public key into x and y coordinates from the public key * defined on the credential. * * @internal */ export async function parseCredentialPublicKey(response, /** Pre-cloned attestationObject to use in the fallback path, avoiding * cross-origin access on the proxy response object. */ attestationObject) { try { const publicKeyBuffer = response.getPublicKey(); if (!publicKeyBuffer) throw new Registration.CreateFailedError(); // Converting `publicKeyBuffer` throws when credential is created by 1Password Firefox Add-on const publicKeyBytes = new Uint8Array(publicKeyBuffer); return PublicKey.from(spkiToRawP256(publicKeyBytes)); } catch (error) { // Fallback for 1Password Firefox Add-on restricts access to certain credential properties // so we need to use `attestationObject` to extract the public key. // https://github.com/passwordless-id/webauthn/issues/50#issuecomment-2072902094 if (error.message !== 'Permission denied to access object') throw error; const data = new Uint8Array(attestationObject ?? response.attestationObject); const coordinateLength = 0x20; const cborPrefix = 0x58; const findStart = (key) => { const coordinate = new Uint8Array([key, cborPrefix, coordinateLength]); for (let i = 0; i < data.length - coordinate.length; i++) if (coordinate.every((byte, j) => data[i + j] === byte)) return i + coordinate.length; throw new Registration.CreateFailedError(); }; const xStart = findStart(0x21); const yStart = findStart(0x22); return PublicKey.from(new Uint8Array([ 0x04, ...data.slice(xStart, xStart + coordinateLength), ...data.slice(yStart, yStart + coordinateLength), ])); } } //# sourceMappingURL=webauthn.js.map