nats-io/nats-server · error

unable to write key file: %v

Error message

unable to write key file: %v

What it means

After a successful tpm2.Seal, the returned private and public blobs could not be persisted to the JetStream key file on disk. The wrapping writeTPMKeysToFile error (permissions, path, disk space) is embedded in %v.

Source

Thrown at server/tpm/js_ek_tpm_windows.go:171

		return "", fmt.Errorf("unable to flush session: %v", err)
	}
	// Seal the data to the parent key and the policy
	user, err := nkeys.CreateUser()
	if err != nil {
		return "", fmt.Errorf("unable to create seed: %v", err)
	}
	// We'll use the seed to represent the encryption key.
	jsStoreKey, err := user.Seed()
	if err != nil {
		return "", fmt.Errorf("unable to get seed: %v", err)
	}
	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)

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Read the embedded %v cause to identify the OS-level file error.
  2. Verify the directory containing jsKeyFile exists and the process user has write permission.
  3. Create the parent directory before calling LoadJetStreamEncryptionKeyFromTPM.
  4. Check disk space and that the path isn't on a read-only mount.

Example fix

// before: directory may not exist
err = writeTPMKeysToFile(jsKeyFile, privateArea, publicArea)
// after: ensure directory exists first
if err := os.MkdirAll(filepath.Dir(jsKeyFile), 0o700); err != nil {
	return "", fmt.Errorf("unable to create key dir: %v", err)
}
err = writeTPMKeysToFile(jsKeyFile, privateArea, publicArea)
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Dir(jsKeyFile)
if info, err := os.Stat(dir); err != nil || !info.IsDir() {
	return fmt.Errorf("key file directory %q missing", dir)
}
test, err := os.CreateTemp(dir, ".writecheck")
if err != nil {
	return fmt.Errorf("directory %q not writable: %w", dir, err)
}
test.Close()
os.Remove(test.Name())

Try / catch

if err != nil {
	if os.IsPermission(err) {
		log.Printf("permission denied writing key file %s: %v", jsKeyFile, err)
	}
	return err
}

Prevention

When it happens

Trigger: writeTPMKeysToFile(jsKeyFile, privateArea, publicArea) fails — bad jsKeyFile path, unwritable directory, disk full, or the parent directory doesn't exist.

Common situations: Running under a service account lacking write permission to the configured key file directory; jsKeyFile path misconfigured; read-only filesystem or container volume.

Understand the failure class

Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.

Related errors


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