router-for-me/CLIProxyAPI · error
home jwt ca_fingerprint is required
Error message
home jwt ca_fingerprint is required
What it means
Claim-level validation in parseHomeJWTClaims: the ca_fingerprint claim is empty after normalization (normalizeFingerprint strips separators/whitespace). The fingerprint pins the expected home CA so the client can verify the CA certificate it receives during mTLS enrollment.
Source
Thrown at internal/home/certificate.go:101
parts := strings.Split(strings.TrimSpace(rawJWT), ".")
if len(parts) != 3 {
return claims, fmt.Errorf("home jwt is invalid")
}
payload, errDecode := decodeJWTPart(parts[1])
if errDecode != nil {
return claims, errDecode
}
if errUnmarshal := json.Unmarshal(payload, &claims); errUnmarshal != nil {
return claims, errUnmarshal
}
if strings.TrimSpace(claims.CertificateID) == "" {
return claims, fmt.Errorf("home jwt certificate_id is required")
}
if strings.TrimSpace(claims.ClusterID) == "" {
return claims, fmt.Errorf("home jwt cluster_id is required")
}
if normalizeFingerprint(claims.CAFingerprint) == "" {
return claims, fmt.Errorf("home jwt ca_fingerprint is required")
}
if strings.TrimSpace(claims.EnrollmentSecret) == "" {
return claims, fmt.Errorf("home jwt enrollment_secret is required")
}
if strings.TrimSpace(claims.IP) == "" || claims.Port <= 0 {
return claims, fmt.Errorf("home jwt target address is invalid")
}
return claims, nil
}
func decodeJWTPart(part string) ([]byte, error) {
if decoded, errDecode := base64.RawURLEncoding.DecodeString(part); errDecode == nil {
return decoded, nil
}
return base64.URLEncoding.DecodeString(part)
}
func defaultCertificatePaths() (certificatePaths, error) {View on GitHub (pinned to 78f0c4079e)
Solutions
- Get a fresh enrollment JWT that carries the real CA fingerprint (hex, with or without colons)
- If self-minting, compute it from the CA cert: openssl x509 -in home-ca-crt.pem -noout -fingerprint -sha256 | cut -d= -f2
- Decode the token payload and confirm the claim before retrying enrollment
Example fix
// before (JWT payload)
{"certificate_id":"c","cluster_id":"k","ca_fingerprint":""}
// after (JWT payload)
{"certificate_id":"c","cluster_id":"k","ca_fingerprint":"AA:BB:CC:DD:..."} Defensive patterns
Strategy: validation
Validate before calling
func fingerprintClaimOK(payload []byte) bool {
var c struct {
CAFingerprint string `json:"ca_fingerprint"`
}
return json.Unmarshal(payload, &c) == nil && strings.ReplaceAll(c.CAFingerprint, ":", "") != ""
} Prevention
- Verify the ca_fingerprint claim is a real hex string, not just present
- Keep the CA cert that matches the fingerprint available for cross-checking
- Include claim-presence assertions in coordinator token-minting tests
When it happens
Trigger: JWT payload omits ca_fingerprint, or contains a value that is only colons/spaces (e.g. ":::") so normalization yields an empty string.
Common situations: Hand-built test tokens skipping the fingerprint, coordinator versions that stopped embedding it, or a malformed fingerprint pasted with only separators.
Related errors
- home jwt is invalid
- home jwt certificate_id is required
- home jwt cluster_id is required
- home jwt enrollment_secret is required
- home jwt target address is invalid
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/a728306b1d0362d6.
Report an issue: GitHub.