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

  1. Get a fresh enrollment JWT that carries the real CA fingerprint (hex, with or without colons)
  2. If self-minting, compute it from the CA cert: openssl x509 -in home-ca-crt.pem -noout -fingerprint -sha256 | cut -d= -f2
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/a728306b1d0362d6. Report an issue: GitHub.