import type { TpmsAttest } from "../attest/attest.types.js"; import type { TpmtPublic } from "../public/public.types.js"; /** * Render a parsed `TpmsAttest` or `TpmtPublic` as a human-readable multi-line * dump for debugging. * * Telemetry is NEVER emitted from this function — the diagnostic dumper is * for human consumption only and SHOULD NOT be invoked from production hot * paths. Symbolic `TPM_ALG_*` and `TPM_ECC_*` names appear inline so the * output is grep-friendly. */ export function toDiagnostic(value: TpmsAttest | TpmtPublic): string { if (isAttest(value)) { return renderAttest(value); } return renderPublic(value); } function isAttest(v: TpmsAttest | TpmtPublic): v is TpmsAttest { return ( typeof (v as { type?: string }).type === "string" && (v as { type: string }).type.startsWith("TPM_ST_ATTEST_") ); } function renderAttest(a: TpmsAttest): string { const lines: string[] = []; lines.push("TPMS_ATTEST {"); lines.push(` magic: 0x${a.magic.toString(16)}`); lines.push(` type: ${a.type}`); lines.push(` qualifiedSigner: ${hex(a.qualifiedSigner)}`); lines.push(` extraData: ${hex(a.extraData)}`); lines.push( ` clockInfo: { clock: ${a.clockInfo.clock}n, resetCount: ${a.clockInfo.resetCount}, restartCount: ${a.clockInfo.restartCount}, safe: ${a.clockInfo.safe} }` ); lines.push(` firmwareVersion: ${a.firmwareVersion}n`); lines.push(` attested: ${renderAttested(a)}`); lines.push("}"); return lines.join("\n"); } function renderAttested(a: TpmsAttest): string { switch (a.type) { case "TPM_ST_ATTEST_CERTIFY": return `TPMS_CERTIFY_INFO { name: ${hex(a.attested.name)}, qualifiedName: ${hex(a.attested.qualifiedName)} }`; case "TPM_ST_ATTEST_QUOTE": return `TPMS_QUOTE_INFO { banks: ${a.attested.pcrSelect.length}, pcrDigest: ${hex(a.attested.pcrDigest)} }`; case "TPM_ST_ATTEST_SESSION_AUDIT": return `TPMS_SESSION_AUDIT_INFO { exclusive: ${a.attested.exclusiveSession}, sessionDigest: ${hex(a.attested.sessionDigest)} }`; case "TPM_ST_ATTEST_COMMAND_AUDIT": return `TPMS_COMMAND_AUDIT_INFO { counter: ${a.attested.auditCounter}n, digestAlg: ${a.attested.digestAlg}, auditDigest: ${hex(a.attested.auditDigest)} }`; case "TPM_ST_ATTEST_TIME": return `TPMS_TIME_ATTEST_INFO { time: ${a.attested.time.time}n, firmwareVersion: ${a.attested.firmwareVersion}n }`; case "TPM_ST_ATTEST_CREATION": return `TPMS_CREATION_INFO { objectName: ${hex(a.attested.objectName)}, creationHash: ${hex(a.attested.creationHash)} }`; case "TPM_ST_ATTEST_NV": return `TPMS_NV_CERTIFY_INFO { indexName: ${hex(a.attested.indexName)}, offset: ${a.attested.offset}, contents: ${hex(a.attested.nvContents)} }`; case "TPM_ST_ATTEST_NV_DIGEST": return `TPMS_NV_DIGEST_CERTIFY_INFO { indexName: ${hex(a.attested.indexName)}, nvDigest: ${hex(a.attested.nvDigest)} }`; } } function renderPublic(p: TpmtPublic): string { const lines: string[] = []; lines.push("TPMT_PUBLIC {"); lines.push(` type: ${p.type}`); lines.push(` nameAlg: ${p.nameAlg}`); lines.push( ` objectAttributes: { raw: 0x${p.objectAttributes.raw.toString(16).padStart(8, "0")}, flags: ${flagSummary(p.objectAttributes.flags)} }` ); lines.push(` authPolicy: ${hex(p.authPolicy)}`); if (p.type === "TPM_ALG_RSA") { lines.push( ` parameters: TPMS_RSA_PARMS { keyBits: ${p.parameters.keyBits}, exponent: ${p.parameters.exponent}, scheme: ${p.parameters.scheme.scheme} }` ); lines.push( ` unique: TPM2B_PUBLIC_KEY_RSA(${p.unique.modulus.length} bytes)` ); } else if (p.type === "TPM_ALG_ECC") { lines.push( ` parameters: TPMS_ECC_PARMS { curve: ${p.parameters.curveID}, scheme: ${p.parameters.scheme.scheme}, kdf: ${p.parameters.kdf.scheme} }` ); lines.push( ` unique: TPMS_ECC_POINT { x: ${p.unique.x.length} bytes, y: ${p.unique.y.length} bytes }` ); } else if (p.type === "TPM_ALG_KEYEDHASH") { lines.push( ` parameters: TPMS_KEYEDHASH_PARMS { scheme: ${p.parameters.scheme} }` ); lines.push(` unique: TPM2B_DIGEST(${p.unique.length} bytes)`); } else { lines.push( ` parameters: TPMS_SYMCIPHER_PARMS { algorithm: ${p.parameters.sym.algorithm} }` ); lines.push(` unique: TPM2B_DIGEST(${p.unique.length} bytes)`); } lines.push("}"); return lines.join("\n"); } function flagSummary(flags: object): string { const enabled = Object.entries(flags) .filter(([, v]) => v === true) .map(([k]) => k); return enabled.length === 0 ? "{}" : `{${enabled.join(", ")}}`; } function hex(b: Uint8Array): string { if (b.length === 0) return "0x"; let s = "0x"; for (let i = 0; i < b.length; i++) { s += (b[i] as number).toString(16).padStart(2, "0"); } return s; }