Repository navigation
Security hardening: authenticated AES, salted TPM sessions, owner-hierarchy SRK - #4
Merged
Merged
Conversation
AES Encrypt/Decrypt used AES-CFB in the TPM with no integrity protection, so ciphertexts were malleable: flipping bits in the ciphertext flipped the same bits in the decrypted plaintext. Each message now gets a random single-use data key, wrapped by the TPM AES key. The message is sealed with AES-GCM in software, using a key derived from the data key with HKDF. The version, IV, and wrapped key are authenticated as additional data. This also removes the TPM's max-buffer size limit on messages. This is a breaking change to the AES ciphertext format. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decrypt ignored its opts and always used OAEP-SHA256 with an empty label. Callers relying on OAEP labels for domain separation got none, and callers asking for PKCS#1 v1.5 silently got OAEP. The OAEP hash and label from *rsa.OAEPOptions are now passed to the TPM. Other option types are rejected. The TPM requires labels to be null-terminated, so labels without a trailing 0x00 are rejected rather than silently changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SRK was created under the endorsement hierarchy, which is meant for identity keys and not for general storage. Keys now use the standard SRK location in the owner hierarchy, and the owner hierarchy's seed is replaced by TPM2_Clear. Clearing the TPM is therefore guaranteed to invalidate all keys created by this library. WithEndorsementAuth is replaced by WithOwnerAuth. This is a breaking change: keys created by earlier versions can no longer be loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All commands used plain password sessions. The object and owner auth values were sent in cleartext on every command, decrypted plaintexts and HMAC values came back in cleartext, and nothing checked that the TPM on the other end was the one the keys were created with. Anyone who could observe the TPM bus (or interpose on it) could recover secrets. - The SRK is created once per TPM instance and stays loaded until Close. Its name is checked against its public area. - Key commands use one-time HMAC sessions salted with the SRK. Auth values never cross the bus, responses are authenticated, and secret parameters are encrypted with AES-128-CFB parameter encryption. - Creating the SRK uses an unsalted HMAC session, so the owner auth is not sent in cleartext either. - The serialized key format now includes the SRK name, and UnmarshalKey returns ErrWrongTPM if the TPM's SRK doesn't match. Together with salted sessions, this stops a different device from impersonating the TPM. - Object names are computed from the public area and checked after TPM2_Load, and AuthHandle.Name is now the real object name (it was the hash of the serialized key). - The key's public parameters come from its verified public area, not from an unauthenticated TPM2_ReadPublic. - RSA encryption is done in software with the public key. This is a breaking change to the serialized key format. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UnmarshalKey accepted any object that loaded under the SRK. Anyone with access to the TPM can create or import objects under the SRK, including objects whose sensitive data they choose (SensitiveDataOrigin clear) or that can be duplicated (FixedTPM/FixedParent clear). If such a blob replaced a stored key, the application would silently use a key known to the attacker. UnmarshalKey now requires FixedTPM, FixedParent, SensitiveDataOrigin, and UserWithAuth, and a supported key type. Otherwise it returns ErrInvalidKey. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RSA and ECC keys are created with both the sign and decrypt attributes. The TPM requires such keys to have no fixed scheme, so anyone who can use the key with the TPM directly can also use schemes this package never uses, e.g. raw RSA or PKCS#1 v1.5 decryption. New opt-in key options: - WithSigningOnly (RSA, ECC): the decrypt attribute is cleared. - WithDecryptionOnly (RSA): the sign attribute is cleared and the scheme is fixed to RSA-OAEP with SHA-256. The default is unchanged (dual-use) and is now documented. Sign, Encrypt, and Decrypt return ErrKeyUsage when the key doesn't allow the operation, and the TPM enforces the same restrictions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SHA-1 is not collision resistant and shouldn't be used for new signatures. Sign now also checks that the digest length matches the hash function instead of relying on the TPM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New flushed every transient object visible on the TPM connection. With /dev/tpmrm0 there's nothing to flush, and a TPM passed in with WithTPM may be shared, so this could destroy other users' objects. Leftover objects are now flushed only when New opens /dev/tpm0 itself, since that device can only be opened by one process at a time. New also closes the device if this step fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Marshal returned the key's internal buffer, so a caller modifying the result would corrupt the key's stored blob. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Go 1.24 no longer gets security fixes. Build with go1.27.1, require go 1.26, and update golang.org/x/crypto, golang.org/x/sys, and go-tpm-tools. govulncheck reports no reachable vulnerabilities. One advisory in golang.org/x/crypto (GO-2026-5932) has no fix yet and isn't reachable from this package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Accept PSS salt lengths that match the hash (Auto, EqualsHash, or the hash size) for SHA-256/384/512, and verify the TPM's salt length. - Reuse salted HMAC sessions for key operations instead of starting a new one for every command. - Recover when the TPM loses the SRK, sessions, or loaded key: flush the cached state and retry once. - Also flush leftover sessions when opening /dev/tpm0. - Make ECC keys signing-only. - Return ErrInvalidCurve instead of panicking with WithECC(nil). - Don't alias the AES-GCM output with the additional data. - Don't reload a key that is already loaded in UnmarshalKey. - Drop the meaningless SRK name check and document that the SRK is trusted on first use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBPwKpCGCUikziy99vn1jz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security hardening: authenticated AES, salted TPM sessions, owner-hierarchy SRK
This PR fixes security issues found during an internal review, along with follow-up fixes from code review. It contains breaking changes (see below).
Changes
Authenticated AES encryption. AES keys no longer encrypt data directly with unauthenticated AES-CFB. Each message gets a random data key, wrapped by the TPM key, and the message is sealed in software with AES-GCM. The header is authenticated as additional data. This removes ciphertext malleability and the TPM's ~1KB message size limit.
DecrypterOpts enforcement. RSA
Decryptpasses the OAEP hash and label to the TPM instead of ignoring them. Options other than OAEP, such as PKCS#1 v1.5, are rejected. The MGF hash must match the label hash, and the TPM requires non-empty labels to end with a null byte.Salted HMAC sessions. Commands previously used plain password sessions. Commands that use keys now run in HMAC sessions salted with the SRK: auth values never cross the bus in cleartext, secret parameters are encrypted, and responses are authenticated. Key operations reuse two long-lived sessions, one encrypting command and response and one encrypting only the command (for
TPM2_Sign), instead of starting a salted session for every command.TPM2_Loaduses the SRK's empty auth value. The private area is already encrypted by the SRK, the loaded object's name is checked, and every later command on the key is covered by an SRK-salted session.Owner hierarchy. Keys are created under an SRK in the owner (storage) hierarchy instead of the endorsement hierarchy, so
TPM2_Clearinvalidates them.WithOwnerAuthsets the owner passphrase. Serialized keys record the name of the SRK they were created under, and loading them on a TPM with a different SRK fails withErrWrongTPM.Import rejection. Keys must have
FixedTPM,FixedParent,SensitiveDataOriginandUserWithAuthset. Keys imported or created with known sensitive data are rejected withErrInvalidKey.Single-purpose keys. New
WithSigningOnly()andWithDecryptionOnly()options restrict RSA keys to one use, enforced by both the library (ErrKeyUsage) and the TPM. Decryption-only keys are fixed to RSA-OAEP with SHA-256. ECC keys are always signing-only.Signing.
Signrejects SHA-1 and digests whose length doesn't match the hash. RSA-PSS supports SHA-256, SHA-384 and SHA-512 withPSSSaltLengthEqualsHash,PSSSaltLengthAutoor a salt as long as the hash, whichcrypto/tlsneeds. The signature is checked after signing so that a TPM using a different salt length can't produce signatures that fail to verify.Robustness.
TPM. If the TPM loses them (for example after a TPM restart, or because another user of a shared TPM flushed handles), they're recreated and the operation is retried once. If a command or response is lost, or a response fails its HMAC check, the sessions are reset so they can't drift out of step with the TPM./dev/tpm0(no resource manager), objects and sessions left behind by a previous process are flushed at startup. TPMs shared throughWithTPMare left alone.WithECC(nil)returnsErrInvalidCurveinstead of panicking.Hygiene.
Marshalreturns a copy of the serialized key. Go 1.26+ is required and dependencies are updated. The README and package doc describe the security properties.Known limitation
The SRK is trusted on first use: it isn't checked against the endorsement key certificate. A device impersonating the TPM when a key is created could capture that key's auth value and parameters. After a key is created, a different device can't use it or complete the sessions that protect it.
Breaking changes
WithEndorsementAuthwas removed; useWithOwnerAuth.Signrejects SHA-1.Testing
Tests run against the go-tpm-tools simulator. They cover bus confidentiality (no auth values or payloads visible on the bus), rejection of known-sensitive keys, key usage restrictions, keys bound to the right TPM and SRK, keys invalidated by
TPM2_Clear, RSA-PSS with each hash and salt length, session reuse, and recovery after all handles are flushed outside the library.🤖 Generated with Claude Code