2.6 KiB
attest/serialize — TPMS_ATTEST canonical serialiser
Purpose
serializeTpmsAttest re-emits a parsed TpmsAttest value as the
TCG-canonical byte string per Library Part 2 §10.12. The serialiser is
the inverse of parseTpmsAttest: for any input the parser accepts,
serialize(parse(bytes)) returns the same bytes. This invariant is
load-bearing for two downstream uses: signature verification (the TPM
endorsement key signed these exact bytes — a re-serialisation that
differed by a single bit would silently fail every signature check) and
proxy / replay use cases that want to forward a verified attest blob
without trusting the originating wire to preserve the formatting.
Features
- Byte-identical round-trip across all eight
TPMU_ATTESTvariants. - Re-emits the 32-bit
magicprefix regardless of source — the serialiser is the authoritative encoder, not a passthrough. - Re-emits
TPM2B_*size prefixes from the decoded payload length so a mutated stored size is silently ignored. - Renders the 17-byte
clockInfosubstructure exactly per §10.10. - Per-variant attested encoder for
CERTIFY,QUOTE,SESSION_AUDIT,COMMAND_AUDIT,TIME,CREATION,NV,NV_DIGEST.
Usage: Basic Example
import {
parseTpmsAttest,
serializeTpmsAttest,
} from "@suaveplan/tpm-struct/attest";
const att = parseTpmsAttest(bytes);
const re = serializeTpmsAttest(att);
// `re` equals `bytes` byte-for-byte.
Usage: Advanced Example
import {
parseTpmsAttest,
serializeTpmsAttest,
} from "@suaveplan/tpm-struct/attest";
// Proxy / verifier-of-verifiers pattern: validate, normalise, forward.
function relayAttestation(raw: Uint8Array, verifySig: (b: Uint8Array) => boolean) {
const att = parseTpmsAttest(raw);
const canonical = serializeTpmsAttest(att);
if (!verifySig(canonical)) {
throw new Error("attestation signature did not verify");
}
return { att, canonical };
}
API Reference
serializeTpmsAttest(att: TpmsAttest): Uint8Array— returns a fresh byte string. Does not throw on any well-typed input — the caller must constructattthroughparseTpmsAttest(or a Zod- validated synthetic constructor) so the input is structurally valid.
Implementation Notes
The serialiser dispatches on att.type via a switch that the
TypeScript compiler verifies as exhaustive. Each per-variant encoder
walks the §10.11 / §10.12 field order using TpmBufferWriter so size
prefixes and big-endian conventions stay locked to the reader semantics.
The serialiser writes into a single growing buffer to avoid the cost of
multiple Uint8Array allocations on the encoder hot path.