hashicorp/nomad · error

ErrInvalidKeyIDHeader

ErrInvalidKeyIDHeader

Error message

%w; must be one of: "x5t", "x5t#S256"

What it means

hashKeyID computes the key-ID header (x5t or x5t#S256) for the client-assertion JWT. It returns ErrInvalidKeyIDHeader wrapped when the configured header type is neither "x5t" nor "x5t#S256". The comment notes this is normally validated at upsert, so hitting it means an unvalidated value reached the builder.

Source

Thrown at lib/auth/oidc/client_assertion.go:205

}

// hashKeyID derives a "certificate thumbprint" that the OIDC provider uses
// to find the certificate to verify the private key JWT signature.
// https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.7
func hashKeyID(cert *x509.Certificate, header structs.OIDCClientAssertionKeyIDHeader) (string, error) {
	var hasher hash.Hash
	switch header {
	case structs.OIDCClientAssertionHeaderX5t:
		if fips140.Enabled() {
			return "", errors.New("x5t assertion headers use SHA-1, which is forbidden in FIPS-140 mode")
		}

		hasher = sha1.New()
	case structs.OIDCClientAssertionHeaderX5tS256:
		hasher = sha256.New()
	default:
		// this should be validated long before here, at upsert
		return "", fmt.Errorf(`%w; must be one of: "x5t", "x5t#S256"`, structs.ErrInvalidKeyIDHeader)
	}
	hasher.Write(cert.Raw)
	hashed := hasher.Sum(nil)
	return base64.RawURLEncoding.EncodeToString(hashed), nil
}

// newlineHeaders allows flexible copy-paste of a one-line key/cert PEM
// by adding newlines around "----BEGIN.*-----" and
// "-----END.*(KEY|CERTIFICATE)-----"
// it's okay to have extra whitespace, but it's imperative that there be
// at least one newline between the header/footer and the content.
func newlineHeaders(bts []byte) []byte {
	cp := bytes.Clone(bts)
	cp = bytes.TrimSpace(cp)
	cp = bytes.ReplaceAll(cp, []byte("-----BEGIN"), []byte("\n-----BEGIN"))
	cp = bytes.ReplaceAll(cp, []byte("-----END"), []byte("\n-----END"))
	// key may be "PRIVATE KEY" or "RSA PRIVATE KEY", so just look for "KEY"
	cp = bytes.ReplaceAll(cp, []byte("KEY-----"), []byte("KEY-----\n"))

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set the key-ID header to exactly "x5t" or "x5t#S256" (re-upsert the OIDC client assertion config)
  2. If stored state has a bad value, update it via the API/CLI so upsert validation runs again
  3. Check for tooling or scripts writing the field directly to the store and route them through the validated API

Example fix

// before
"key_id_header": "x5t-S256"
// after
"key_id_header": "x5t#S256"
Defensive patterns

Strategy: validation

Validate before calling

var validKeyIDHeaders = map[string]bool{"x5t": true, "x5t#S256": true}
if !validKeyIDHeaders[keyIDHeader] {
	return fmt.Errorf("key_id_header %q invalid; must be x5t or x5t#S256", keyIDHeader)
}

Type guard

func isValidKeyIDHeader(h string) bool {
	return h == "x5t" || h == "x5t#S256"
}

Try / catch

// wrap and inspect via errors.Is
if err != nil {
	if errors.Is(err, structs.ErrInvalidKeyIDHeader) {
		// fix config and re-upsert the OIDC client
	}
	return err
}

Prevention

When it happens

Trigger: BuildClientAssertionJWT calls hashKeyID with an OIDC client assertion key-ID header value that is not "x5t" or "x5t#S256" — typically a value written directly to state/store bypassing upsert validation, or an API client typo.

Common situations: Hand-edited Nomad ACL/OIDC objects in state; older clients or tooling writing unsupported header names; schema/version drift after an upgrade where validation rules changed; typo like "x5t-S256".

Related errors


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