OpenNHP/opennhp · error

JWT signing key is not initialized

Error message

JWT signing key is not initialized

What it means

generateJWT signs the KBS token with a package-level jwtSigningKey that must be initialized first (typically during startup, e.g. from ParseTeePubkey/generateJWT setup). If it is still nil when Attest() asks for a token, JWT creation is refused instead of panicking on a nil signing method.

Solutions

  1. Ensure the JWT signing key is generated/loaded during service init before Attest() is reachable
  2. Check init logs for a failed key-load step and fix its root cause
  3. Add the signing key setup to test fixtures when testing generateJWT
  4. Guard startup so the KBS HTTP endpoints only register after key init succeeds

Example fix

// before (test)
token, _ := generateJWT()
// after
jwtSigningKey = mustGenerateSigningKey()
token, _ := generateJWT()
Defensive patterns

Strategy: validation

Validate before calling

if jwtSigningKey == nil {
    return errors.New("generateJWT called before signing key init")
}
token, err := generateJWT()

Type guard

func jwtReady() bool { return jwtSigningKey != nil }

Try / catch

token, err := generateJWT()
if err != nil {
    if err.Error() == "JWT signing key is not initialized" {
        return fmt.Errorf("KBS not ready: %w", err) // surface as 503 to callers
    }
    return err
}

Prevention

When it happens

Trigger: Attest() -> generateJWT runs before the key initialization step executed, or initialization failed silently earlier (e.g. key load error swallowed, wrong init order in tests or partial server startup).

Common situations: Unit tests calling generateJWT directly without setting jwtSigningKey; server started with a broken attestation config so init skipped; race where attestation request arrives during startup.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/2b83fe6eea768ea5. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/server/kbs/attest/attest.go:129

	eBytes, err := base64.RawURLEncoding.DecodeString(pubkey.E)
	if err != nil {
		return nil, fmt.Errorf("invalid e: %w", err)
	}

	e := 0
	for _, b := range eBytes {
		e = e<<8 | int(b)
	}

	return &rsa.PublicKey{
		N: new(big.Int).SetBytes(nBytes),
		E: e,
	}, nil
}

func generateJWT() (string, error) {
	if jwtSigningKey == nil {
		return "", errors.New("JWT signing key is not initialized")
	}

	claims := CustomClaims{
		CosignAuthorized: true,
		RegisteredClaims: jwt.RegisteredClaims{
			IssuedAt:  jwt.NewNumericDate(time.Now()),
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Minute)),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)

	publicKey := jwtSigningKey.PublicKey
	token.Header["jwk"] = map[string]any{
		"alg": "ES256",
		"crv": "P-256",
		"kty": "EC",
		"x":   base64.RawURLEncoding.EncodeToString(publicKey.X.Bytes()),

View on GitHub (pinned to 6e04ca5ff0)