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

  1. Inspect the wrapped TPM return code; TPM_RC_LOCKOUT means wait out the dictionary-attack lockout period.
  2. Retry — transient object exhaustion frees up as other clients flush handles.
  3. Reboot or reset the TPM if the owner hierarchy is in a bad state (note: clearing the TPM destroys existing sealed keys).
  4. Confirm no non-empty owner auth was set on the TPM; pulumi authenticates with an empty password.
  5. 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

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


AI-assisted analysis of pulumi/pulumi@793f7b2e16 (2026-08-31). Data as JSON: /api/errors/dac6e37cf3f5ef5c. Report an issue: GitHub.