Files
2026-05-27 23:47:53 +01:00

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_ATTEST variants.
  • Re-emits the 32-bit magic prefix 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 clockInfo substructure 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 construct att through parseTpmsAttest (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.