/** * Deterministic test keys for the webauthn fixture suite. * * ES256 (P-256), ES384, ES512 use hardcoded scalars; ECDSA via * `@noble/curves` uses RFC 6979 deterministic-k so signatures round-trip * byte-stably given the same message. Ed25519 is deterministic by spec. * Public keys are memoised across the suite. * * `@noble/curves` is added as a devDependency on `@suaveplan/webauthn` * specifically for these fixtures (it is the same transitive dep that * `@suaveplan/crypto-curve` already depends on; fixture-only). */ import { p256 as nobleP256 } from "@noble/curves/p256"; import { p384 as nobleP384 } from "@noble/curves/p384"; import { p521 as nobleP521 } from "@noble/curves/p521"; import type { Ec2Key, OkpKey } from "@suaveplan/cbor/cose"; import { ed25519 } from "@suaveplan/crypto-curve"; const P256_PRIV = Uint8Array.from([ 0x70, 0x83, 0x95, 0xab, 0x2c, 0x46, 0x3d, 0x68, 0xa1, 0x10, 0x4f, 0x37, 0x29, 0xb5, 0xd5, 0x42, 0x0c, 0x18, 0x21, 0x9f, 0xa0, 0x60, 0xff, 0xff, 0x5c, 0x4d, 0x16, 0x97, 0x21, 0x83, 0x9b, 0x55, ]); const P384_PRIV = Uint8Array.from( Array.from({ length: 48 }, (_v, i) => (i * 7 + 3) & 0xff) ); const P521_PRIV = Uint8Array.from([ 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x77, ]); const ED25519_PRIV = new Uint8Array(32).fill(0xa5); export interface EcTestKey { readonly priv: Uint8Array; readonly pubUncompressed: Uint8Array; readonly x: Uint8Array; readonly y: Uint8Array; readonly coseKey: Ec2Key; } export interface EddsaTestKey { readonly priv: Uint8Array; readonly pub: Uint8Array; readonly coseKey: OkpKey; } let p256Cache: EcTestKey | undefined; let p384Cache: EcTestKey | undefined; let p521Cache: EcTestKey | undefined; let edCache: EddsaTestKey | undefined; function ecBuild( priv: Uint8Array, pubUncompressed: Uint8Array, coordLen: number, crv: "P-256" | "P-384" | "P-521", alg: "ES256" | "ES384" | "ES512" ): EcTestKey { const x = pubUncompressed.slice(1, 1 + coordLen); const y = pubUncompressed.slice(1 + coordLen, 1 + 2 * coordLen); return { priv, pubUncompressed, x, y, coseKey: { kty: "EC2", crv, alg, x, y, }, }; } /** Deterministic P-256 keypair for ES256 webauthn tests. */ export function getEcdsaP256TestKey(): EcTestKey { if (p256Cache !== undefined) { return p256Cache; } const pub = nobleP256.getPublicKey(P256_PRIV, false); p256Cache = ecBuild(P256_PRIV, pub, 32, "P-256", "ES256"); return p256Cache; } /** Deterministic P-384 keypair for ES384 webauthn tests. */ export function getEcdsaP384TestKey(): EcTestKey { if (p384Cache !== undefined) { return p384Cache; } const pub = nobleP384.getPublicKey(P384_PRIV, false); p384Cache = ecBuild(P384_PRIV, pub, 48, "P-384", "ES384"); return p384Cache; } /** Deterministic P-521 keypair for ES512 webauthn tests. */ export function getEcdsaP521TestKey(): EcTestKey { if (p521Cache !== undefined) { return p521Cache; } const pub = nobleP521.getPublicKey(P521_PRIV, false); p521Cache = ecBuild(P521_PRIV, pub, 66, "P-521", "ES512"); return p521Cache; } /** Deterministic Ed25519 keypair for EdDSA webauthn tests. */ export function getEd25519TestKey(): EddsaTestKey { if (edCache !== undefined) { return edCache; } const pub = ed25519.getPublicKey(ED25519_PRIV); edCache = { priv: ED25519_PRIV, pub, coseKey: { kty: "OKP", crv: "Ed25519", alg: "EdDSA", x: pub, }, }; return edCache; } /** ECDSA-sign a digest with P-256. Returns DER signature. */ export function signEcdsaP256Der( digest: Uint8Array, priv: Uint8Array ): Uint8Array { return nobleP256.sign(digest, priv).toBytes("der"); } export function signEcdsaP384Der( digest: Uint8Array, priv: Uint8Array ): Uint8Array { return nobleP384.sign(digest, priv).toBytes("der"); } export function signEcdsaP521Der( digest: Uint8Array, priv: Uint8Array ): Uint8Array { return nobleP521.sign(digest, priv).toBytes("der"); }