hashicorp/nomad · error

invalid signature: %w

Error message

invalid signature: %w

What it means

The JWT parsed but its signature failed verification against the typed public key looked up by KeyID (token.Claims(typedPubKey, &claims)). The token was not signed by the keyring key corresponding to its kid header, or the key is no longer available.

Source

Thrown at nomad/encrypter.go:386

		return nil, err
	}

	// Find the key material
	pubKey, err := e.waitForPublicKey(keyID)
	if err != nil {
		return nil, err
	}

	typedPubKey, err := pubKey.GetPublicKey()
	if err != nil {
		return nil, err
	}

	claims := structs.IdentityClaims{}

	// Validate the claims.
	if err := token.Claims(typedPubKey, &claims); err != nil {
		return nil, fmt.Errorf("invalid signature: %w", err)
	}

	// COMPAT: Until we can guarantee there are no pre-1.7 JWTs in use, we can
	// only validate the signature and have no further expectations of the
	// claims.
	if err := claims.Validate(jwt.Expected{}); err != nil {
		return nil, fmt.Errorf("invalid claims: %w", err)
	}

	return &claims, nil
}

// AddUnwrappedKey stores the key in the keystore and creates a new cipher for
// it. This is called in the RPC handlers on the leader and from the legacy
// KeyringReplicator.
func (e *Encrypter) AddUnwrappedKey(rootKey *structs.UnwrappedRootKey, isUpgraded bool) (*structs.RootKey, error) {

	// note: we don't lock the keyring here but inside addCipher

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Issue a fresh token (re-run the workload or regenerate the claim) — the old key may have been rotated out
  2. Ensure all servers share the same keystore/keys (keyring replication caught up)
  3. Verify the token's kid matches a key that still exists: nomad keyring list
  4. Check for cross-cluster token reuse and use the issuing cluster for verification

Example fix

// before: verifying a stale pre-rotation token
claims, err := encrypter.VerifyClaim(oldToken) // invalid signature
// after: fetch a fresh signed claim for the allocation
token, _, err := client.Allocations().GetAllocByID(allocID) // re-read identity token
claims, err := encrypter.VerifyClaim(token)
Defensive patterns

Strategy: try-catch

Validate before calling

// check the kid exists in the current keyring before verification
keyring, _, err := client.Keyring().List(nil)
if err != nil { return err }
haveKey := false
for _, k := range keyring {
  if strings.HasPrefix(k.KeyID, sigAlgPrefixFromToken(token)) { haveKey = true }
}
if !haveKey { return fmt.Errorf("token signed with unknown/rotated-out key") }

Try / catch

claims, err := encrypter.VerifyClaim(token)
if err != nil {
  if strings.Contains(err.Error(), "invalid signature") {
    // stale or foreign-cluster token: request a fresh token from the issuer
    return fmt.Errorf("token not signed by a current keyring key — re-issue: %w", err)
  }
  return err
}

Prevention

When it happens

Trigger: VerifyClaim resolves the kid to a public key in the keyring and calls token.Claims(pubKey, &claims); mismatched signing key, token signed by a removed/rotated key, or token from a different cluster produces this error.

Common situations: Tokens issued before a keyring rotation are presented after the old key was removed, multi-cluster setups where a token signed by cluster A's key is verified by cluster B, or the verifying server hasn't replicated the newer key yet.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/b6d2cf8c04f5ae9a. Report an issue: GitHub.