nats-io/nats-server · critical

unable to load data: %v

Error message

unable to load data: %v

What it means

unsealJsEncrpytionKey could not load the previously sealed key's public/private blobs back into the TPM. tpm2.Load re-imports the object under the SRK; failure typically means the blobs don't match the current SRK or are corrupted.

Source

Thrown at server/tpm/js_ek_tpm_windows.go:182

	}
	privateArea, publicArea, err := tpm2.Seal(rwc, srkHandle, srkPassword, jsKeyPassword, policy, jsStoreKey)
	if err != nil {
		return "", fmt.Errorf("unable to seal data: %v", err)
	}
	err = writeTPMKeysToFile(jsKeyFile, privateArea, publicArea)
	if err != nil {
		return "", fmt.Errorf("unable to write key file: %v", err)
	}
	return string(jsStoreKey), nil
}

// Unseals the JetStream encryption key from the TPM with the provided keys.
// The key is returned as a string.
func unsealJsEncrpytionKey(rwc io.ReadWriteCloser, pcr int, srkHandle tpmutil.Handle, srkPassword, objectPassword string, publicBlob, privateBlob []byte) (string, error) {
	// 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
}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Delete the stale key file and let LoadJetStreamEncryptionKeyFromTPM recreate the key.
  2. Verify the key file was created on the SAME TPM (sealed blobs are not portable).
  3. Confirm srkHandle/srkPassword match those used at seal time.
  4. Check the blobs were not swapped or truncated when written/read.

Example fix

// before: blindly loading stale blobs
objectHandle, _, err := tpm2.Load(rwc, srkHandle, srkPassword, publicBlob, privateBlob)
if err != nil {
	return "", fmt.Errorf("unable to load data: %v", err)
}
// after: caller fallback — remove key file and re-create on mismatch
if _, err := tpm2.Load(rwc, srkHandle, srkPassword, publicBlob, privateBlob); err != nil {
	os.Remove(jsKeyFile) // force re-seal path
	return LoadJetStreamEncryptionKeyFromTPM(...)
}
Defensive patterns

Strategy: fallback

Validate before calling

info, err := os.Stat(jsKeyFile)
if err != nil || info.Size() == 0 {
	// force re-seal path
	os.Remove(jsKeyFile)
}

Type guard

func isTPMLoadError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "unable to load data")
}

Try / catch

key, err := LoadJetStreamEncryptionKeyFromTPM(rwc)
if err != nil && isTPMLoadError(err) {
	os.Remove(jsKeyFile) // blobs unusable on this TPM; regenerate
	key, err = LoadJetStreamEncryptionKeyFromTPM(rwc)
}

Prevention

When it happens

Trigger: tpm2.Load(rwc, srkHandle, srkPassword, publicBlob, privateBlob) errors — blobs corrupted/truncated, key file from a different machine/TPM, wrong srkHandle, or wrong srkPassword.

Common situations: Copying the key file between machines (TPM-sealed data is TPM-specific); key file truncated by a bad write; TPM was re-provisioned so the SRK changed; swapping private and public blobs.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/ce269a3da3b11326. Report an issue: GitHub.