nats-io/nats-server · critical
unable to unseal data: %v
Error message
unable to unseal data: %v
What it means
The key object was loaded and the policy session created, but tpm2.UnsealWithSession failed to unseal the data. Because the policy binds both PCR state and password, this most often means the PCR value changed since sealing or the object password is wrong.
Source
Thrown at server/tpm/js_ek_tpm_windows.go:197
// Load the public/private blobs into the TPM for decryption.
objectHandle, _, err := tpm2.Load(rwc, srkHandle, srkPassword, publicBlob, privateBlob)
if err != nil {
return "", fmt.Errorf("unable to load data: %v", err)
}
defer tpm2.FlushContext(rwc, objectHandle)
// Create the authorization session with TPM.
sessHandle, _, err := policyPCRPasswordSession(rwc, pcr)
if err != nil {
return "", fmt.Errorf("unable to get auth session: %v", err)
}
defer func() {
tpm2.FlushContext(rwc, sessHandle)
}()
// Unseal the data we've loaded into the TPM with the object (js key) password.
unsealedData, err := tpm2.UnsealWithSession(rwc, sessHandle, objectHandle, objectPassword)
if err != nil {
return "", fmt.Errorf("unable to unseal data: %v", err)
}
return string(unsealedData), nil
}
// Returns session handle and policy digest.
func policyPCRPasswordSession(rwc io.ReadWriteCloser, pcr int) (sessHandle tpmutil.Handle, policy []byte, retErr error) {
sessHandle, _, err := tpm2.StartAuthSession(
rwc,
tpm2.HandleNull, /*tpmKey*/
tpm2.HandleNull, /*bindKey*/
make([]byte, 16), /*nonceCaller*/
nil, /*secret*/
tpm2.SessionPolicy,
tpm2.AlgNull,
tpm2.AlgSHA256)
if err != nil {
return tpm2.HandleNull, nil, fmt.Errorf("unable to start session: %v", err)
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Compare current PCR digest with the one at seal time (tpm2_pcrread); if changed, re-seal the key.
- Verify jsKeyPassword/objectPassword matches the one used at seal time.
- As a last resort, delete the key file and regenerate the encryption key (data encrypted with the old key becomes unrecoverable).
- Back up the key file path mapping to the PCR index used at seal time.
Example fix
// before: wrong password silently propagated
unsealedData, err := tpm2.UnsealWithSession(rwc, sessHandle, objectHandle, objectPassword)
// after: fail with actionable context
unsealedData, err := tpm2.UnsealWithSession(rwc, sessHandle, objectHandle, objectPassword)
if err != nil {
return "", fmt.Errorf("unable to unseal data (check password and PCR %d state): %v", pcr, err)
} Defensive patterns
Strategy: try-catch
Validate before calling
currentPCR, err := tpm2.ReadPCR(rwc, pcr, tpm2.AlgSHA256)
if err != nil {
return fmt.Errorf("cannot read PCR %d: %w", pcr, err)
}
// compare with PCR digest recorded at seal time before attempting unseal Type guard
func isUnsealError(err error) bool {
return err != nil && strings.Contains(err.Error(), "unable to unseal data")
} Try / catch
key, err := LoadJetStreamEncryptionKeyFromTPM(rwc)
if err != nil && isUnsealError(err) {
log.Printf("unseal failed (PCR %d state or password changed): %v", pcr, err)
// DO NOT delete key file automatically — encrypted data may be lost
return err
} Prevention
- Pin the sealed key to a PCR that changes rarely (not PCR 0/7 if firmware updates are frequent).
- Store the PCR digest at seal time and compare before unsealing.
- Manage jsKeyPassword centrally so seal/unseal always match.
- Never reboot into an updated OS/firmware before backing up the key dependency chain.
When it happens
Trigger: tpm2.UnsealWithSession(rwc, sessHandle, objectHandle, objectPassword) errors — jsKeyPassword mismatch, PCR state differs from seal time, or policy not satisfied.
Common situations: A firmware/OS update changed PCR values (e.g. PCR 0/7) after the key was sealed; wrong jsKeyPassword passed by caller; TPM HMAC/authorization failure.
Related errors
- unable to get auth session: %v
- unable to bind PCRs to auth policy: %v
- unable to seal data: %v
- unable to write key file: %v
- unable to load data: %v
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/66c214f35fd33b1d.
Report an issue: GitHub.