Files
suaveplan/packages/identity/auth/webauthn/src/__fixtures__/deterministic-keys.ts
2026-05-27 23:47:53 +01:00

153 lines
4.5 KiB
TypeScript

/**
* 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");
}