153 lines
4.5 KiB
TypeScript
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");
|
|
}
|