Skip to content

tpmmgr: device certificate is stored in TPM NV as PEM and written unchunked, leaving ~108 bytes of headroom #6345

Description

@eriknordmark

Summary

writeDeviceCert() stores the device certificate in TPM NV as PEM, and
writes it with a single un-chunked TPM2_NV_Write. On an Infineon SLB9665 the
per-write ceiling (TPM_PT_NV_BUFFER_MAX) is 768 bytes and the stored
certificate is 660, so there are 108 bytes of headroom before the write
starts failing — and that failure is currently silent and permanent (#6344).

Storing DER instead of PEM recovers ~33% immediately: the same certificate is 446
bytes in DER.

Detail

pkg/pillar/cmd/tpmmgr/tpmmgr.go:

deviceCertBytes, err := os.ReadFile(types.DeviceCertName)   // PEM
...
tpm2.NVDefineSpace(rw, tpm2.HandleOwner, etpm.TpmDeviceCertHdl,
	etpm.EmptyPassword, etpm.EmptyPassword, nil,
	tpm2.AttrOwnerWrite|tpm2.AttrOwnerRead,
	uint16(len(deviceCertBytes)))
...
tpm2.NVWrite(rw, tpm2.HandleOwner, etpm.TpmDeviceCertHdl,
	etpm.EmptyPassword, deviceCertBytes, 0)

go-tpm's NVWrite issues one TPM2_NV_Write with the whole payload; it does
not chunk. (Its NVReadEx counterpart does chunk, querying
TPM_PT_NV_BUFFER_MAX first — so reads are already correct.)

Measured on an EVE device with an Infineon SLB9665:

Value
TPM_PT_NV_BUFFER_MAX (per-write ceiling) 0x300 = 768
TPM_PT_NV_INDEX_MAX (per-index ceiling) 0x680 = 1664
NV index 0x1500000 actual size 660
Same certificate encoded as DER 446

The TCG PC Client Platform TPM Profile sets the minimum NV_BUFFER_MAX at 512,
so a conforming part could be tighter than the 660 bytes EVE writes today. Worth
checking tpm2 getcap properties-fixed across the qualified platform list.

Why the headroom matters

Any addition to the certificate template consumes it. Concretely, adding a
device-specific serialNumber attribute to the subject — a change worth making
for other reasons, and only ~90 bytes of DER — takes the stored PEM to roughly
786 bytes. That is over the 768-byte ceiling, so NVDefineSpace succeeds (786 <
1664) and NVWrite then fails, triggering #6344.

Suggested fix

  1. Store DER, not PEM. TCG's "TPM 2.0 Keys for Device Identity and
    Attestation" §7.3.3 recommends exactly this for LDevID/LAK certificates in NV:
    "A certificate chain SHOULD be stored as X.509 DER-encoded certificates, as
    this is the smallest form and requires no conversion for use by networked
    applications." (It is a SHOULD, inside a section that is RECOMMENDED as a
    whole; the MUST form is §7.3.2, which governs only OEM-provisioned IDevID/IAK
    chains. The binding constraint for EVE is the measured TPM_PT_NV_BUFFER_MAX,
    not the spec.) Accept either encoding on read so existing devices keep
    working.
  2. Chunk the write against TPM_PT_NV_BUFFER_MAX, mirroring what NVReadEx
    already does for reads. Without this, nothing larger than one buffer can ever
    be stored even in DER — which also blocks storing a certificate chain, where
    a leaf plus one issuing CA runs about 1150 bytes in DER (under the 1664-byte
    index limit, over the 768-byte write limit).

Two adjacent observations from the same NV dump, worth folding in if these
indices are ever revised:

  • All five EVE NV indices use a SHA-1 nameAlg, where the TPM vendor's own
    indices use SHA-256. EVE never chooses this — NVDefineSpace is called with
    nil for the policy and the default applies — so it is an artifact rather than
    a decision.
  • The indices are ownerwrite|ownerread with an empty auth value and no policy,
    so anything with owner access can rewrite the device-certificate backup. That
    is consistent with its documented role as a backup rather than an anchor, but
    it is worth being explicit about if that role ever changes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions