ox
Version:
Ethereum Standard Library
91 lines • 3.74 kB
JavaScript
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