import { arrayBufferEquals, abToPem, appendBuffer, coerceToArrayBuffer, coerceToBase64, tools } from "../utils.js";
import { Certificate, CertManager } from "../certUtils.js";
import { u2fRootCerts as rootCertList } from "./u2fRootCerts.js";
const algMap = new Map([ [-7, { algName: "ECDSA_w_SHA256", hashAlg: "SHA-256", }], [-35, { algName: "ECDSA_w_SHA384", hashAlg: "SHA-384", }], [-36, { algName: "ECDSA_w_SHA512", hashAlg: "SHA-512", }], [-257, { algName: "RSASSA-PKCS1-v1_5_w_SHA256", hashAlg: "SHA-256", }],]);
function packedParseFn(attStmt) { const ret = new Map();
const algEntry = algMap.get(attStmt.alg); if (algEntry === undefined) { throw new Error("packed attestation: unknown algorithm: " + attStmt.alg); } ret.set("alg", algEntry);
const x5c = attStmt.x5c; const newX5c = []; if (Array.isArray(x5c)) { for (let cert of x5c) { cert = coerceToArrayBuffer(cert, "packed x5c cert"); newX5c.push(cert); } ret.set("attCert", newX5c.shift()); ret.set("x5c", newX5c); } else { ret.set("x5c", x5c); }
let ecdaaKeyId = attStmt.ecdaaKeyId; if (ecdaaKeyId !== undefined) { ecdaaKeyId = coerceToArrayBuffer(ecdaaKeyId, "ecdaaKeyId"); ret.set("ecdaaKeyId", ecdaaKeyId); }
let sig = attStmt.sig; sig = coerceToArrayBuffer(sig, "packed signature"); ret.set("sig", sig);
return ret;}
function packedValidateFn() { const x5c = this.authnrData.get("x5c"); const ecdaaKeyId = this.authnrData.get("ecdaaKeyId");
if (x5c !== undefined && ecdaaKeyId !== undefined) { throw new Error("packed attestation: should be 'basic' or 'ecdaa', got both"); }
if (x5c) return packedValidateBasic.call(this); if (ecdaaKeyId) return packedValidateEcdaa.call(this); return packedValidateSurrogate.call(this);}
async function packedValidateBasic() { const { algName, hashAlg, } = this.authnrData.get("alg");
if (algName === undefined) { throw new Error("packed attestation: unknown algorithm " + algName); }
const res = await validateSignature( this.clientData.get("rawClientDataJson"), this.authnrData.get("rawAuthnrData"), this.authnrData.get("sig"), hashAlg, this.authnrData.get("attCert"), ); if (!res) { throw new Error("packed attestation signature verification failed"); } this.audit.journal.add("sig"); this.audit.journal.add("alg");
await validateCerts( this.authnrData.get("attCert"), this.authnrData.get("aaguid"), this.authnrData.get("x5c"), this.audit );
this.audit.info.set("attestation-type", "basic");
this.audit.journal.add("fmt");
return true;}
async function validateSignature( rawClientData, authenticatorData, sig, hashAlg, parsedAttCert,) { const hash = await tools.hashDigest(rawClientData); const clientDataHash = new Uint8Array(hash).buffer;
const attCertPem = abToPem("CERTIFICATE", parsedAttCert);
const cert = new Certificate(attCertPem); const publicKey = await cert.getPublicKey();
const verify = await tools.verifySignature( publicKey, sig, appendBuffer(authenticatorData, clientDataHash), hashAlg, ); return verify;}
async function validateCerts(parsedAttCert, aaguid, _x5c, audit) {
if (CertManager.getCerts().size === 0) { rootCertList.forEach((cert) => CertManager.addCert(cert)); }
const attCert = new Certificate(coerceToBase64(parsedAttCert, "parsedAttCert")); try { await attCert.verify(); } catch (e) { const err = e; if (err.message === "Please provide issuer certificate as a parameter") { audit.warning.set("attesation-not-validated", "could not validate attestation because the root attestation certification could not be found"); } else { throw err; } } audit.journal.add("x5c");
if (attCert.getVersion() !== 3) { throw new Error("expected packed attestation certificate to be x.509v3"); }
const exts = attCert.getExtensions(); exts.forEach((v, k) => audit.info.set(k, v)); attCert.info.forEach((v, k) => audit.info.set(k, v)); attCert.warning.forEach((v, k) => audit.warning.set(k, v)); audit.journal.add("attCert");
if (attCert.getVersion() !== 3) { throw new Error("expected packed attestation certificate to be x.509v3"); }
const subject = attCert.getSubject(); if (typeof subject.get("country-name") !== "string") { throw new Error("packed attestation: attestation certificate missing 'country name'"); }
if (typeof subject.get("organization-name") !== "string") { throw new Error("packed attestation: attestation certificate missing 'organization name'"); }
if (subject.get("organizational-unit-name") !== "Authenticator Attestation") { throw new Error("packed attestation: attestation certificate 'organizational unit name' must be 'Authenticator Attestation'"); }
if (typeof subject.get("common-name") !== "string") { throw new Error("packed attestation: attestation certificate missing 'common name'"); }
const basicConstraints = exts.get("basic-constraints"); if (basicConstraints.cA !== false) { throw new Error("packed attestation: basic constraints 'cA' must be 'false'"); }
const certAaguid = exts.get("fido-aaguid"); if (certAaguid !== undefined && !arrayBufferEquals(aaguid, certAaguid)) { throw new Error("packed attestation: authnrData AAGUID did not match AAGUID in attestation certificate"); }}
async function validateSelfSignature(rawClientData, authenticatorData, sig, hashAlg, publicKeyPem) { const clientDataHash = await tools.hashDigest(rawClientData, hashAlg);
const verify = await tools.verifySignature( publicKeyPem, sig, appendBuffer(authenticatorData, clientDataHash), hashAlg, ); return verify;}
function packedValidateSurrogate() { const { algName, hashAlg, } = this.authnrData.get("alg");
if (algName === undefined) { throw new Error("packed attestation: unknown algorithm " + algName); }
const res = validateSelfSignature( this.clientData.get("rawClientDataJson"), this.authnrData.get("rawAuthnrData"), this.authnrData.get("sig"), hashAlg, this.authnrData.get("credentialPublicKeyPem"), ); if (!res) { throw new Error("packed attestation signature verification failed"); } this.audit.journal.add("sig"); this.audit.journal.add("alg"); this.audit.journal.add("x5c");
this.audit.info.set("attestation-type", "self");
this.audit.journal.add("fmt");
return true;}
function packedValidateEcdaa() { throw new Error("packed attestation: ECDAA not implemented, please open a GitHub issue.");}
const packedAttestation = { name: "packed", parseFn: packedParseFn, validateFn: packedValidateFn,};
export { packedAttestation };