pulumi/pulumi · error
sealing key in TPM: %w
Error message
sealing key in TPM: %w
What it means
tpmWrapper.wrap executes tpm2.Create to seal the key as a keyedhash SEALED-DATA object under the storage primary. If the TPM rejects the Create command, the error is wrapped as 'sealing key in TPM'. This is distinct from openTPM failures: the TPM is reachable but refuses the seal operation.
Source
Thrown at sdk/go/common/util/securestore/tpm.go:149
return nil, err
}
defer flushHandle(tpm, primary.ObjectHandle)
rsp, err := tpm2.Create{
ParentHandle: tpm2.AuthHandle{
Handle: primary.ObjectHandle,
Name: primary.Name,
Auth: tpm2.PasswordAuth(nil),
},
InSensitive: tpm2.TPM2BSensitiveCreate{
Sensitive: &tpm2.TPMSSensitiveCreate{
Data: tpm2.NewTPMUSensitiveCreate(&tpm2.TPM2BSensitiveData{Buffer: key}),
},
},
InPublic: tpm2.New2B(sealedDataTemplate()),
}.Execute(tpm)
if err != nil {
return nil, fmt.Errorf("sealing key in TPM: %w", err)
}
return encodeSealedBlob(tpm2.Marshal(rsp.OutPrivate), tpm2.Marshal(rsp.OutPublic))
})
}
// unwrap recovers the key from a blob produced by wrap: it regenerates the
// same storage primary, loads the sealed object under it, and unseals it.
func (tpmWrapper) unwrap(blob []byte) ([]byte, error) {
privBytes, pubBytes, err := decodeSealedBlob(blob)
if err != nil {
return nil, err
}
priv, err := tpm2.Unmarshal[tpm2.TPM2BPrivate](privBytes)
if err != nil {
return nil, fmt.Errorf("stored key is corrupt (bad TPM private blob): %w", err)
}
pub, err := tpm2.Unmarshal[tpm2.TPM2BPublic](pubBytes)
if err != nil {View on GitHub (pinned to 793f7b2e16)
Solutions
- Inspect the wrapped TPM return code; TPM_RC_LOCKOUT means wait out the dictionary-attack lockout period.
- Retry — transient object exhaustion frees up as other clients flush handles.
- Reboot or reset the TPM if the owner hierarchy is in a bad state (note: clearing the TPM destroys existing sealed keys).
- Confirm no non-empty owner auth was set on the TPM; pulumi authenticates with an empty password.
- Fall back to PULUMI_CREDENTIAL_STORE=plaintext or an OS-store backend if the TPM cannot seal.
Example fix
# probe TPM health sudo tpm2_getcap properties-fixed sudo tpm2_getcap handles-transient # check for object-slot exhaustion # if locked out, wait; clearing the TPM destroys sealed keys sudo tpm2_clear -a p
Defensive patterns
Strategy: retry
Validate before calling
// Ensure the TPM can create the primary first (proves owner hierarchy usable)
if err := (tpmWrapper{}).available(); err != nil { /* pick another backend before sealing */ } Type guard
func isSealFailure(err error) bool { return strings.Contains(err.Error(), "sealing key in TPM") } Try / catch
blob, err := wrapper.wrap(key)
if err != nil {
if strings.Contains(err.Error(), "TPM_RC_LOCKOUT") {
// wait out dictionary-attack lockout, then retry once
}
return fmt.Errorf("TPM seal failed; falling back: %w", err)
} Prevention
- Wait out dictionary-attack lockout periods instead of hammering the TPM
- Watch for transient-object exhaustion on TPMs shared with other applications
- Keep TPM firmware updated; reboot to reset a wedged owner hierarchy
- Keep an OS-store or plaintext fallback path for machines where sealing fails repeatedly
When it happens
Trigger: tpm2.Create{ParentHandle: owner primary, InSensitive: key}.Execute returning a TPM error: owner hierarchy locked (dictionary-attack lockout), primary handle lost mid-session, out of transient object slots, or transport error during the command.
Common situations: TPM dictionary-attack lockout state; TPM transient memory exhausted by other applications' loaded objects; firmware quirks with the ECC SRK parent; device error mid-operation on a shared TPM.
Related errors
- %w: operation timed out after %s
- %w: no usable TPM: %v
- creating TPM storage primary: %w
- stored key was protected with %q but this backend uses %q: %
- protecting new key: %w
AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31).
Data as JSON: /api/errors/dac6e37cf3f5ef5c.
Report an issue: GitHub.