{"record":{"id":"8c5f68eb5507087c","repo":"router-for-me/CLIProxyAPI","slug":"home-ca-fingerprint-mismatch","errorCode":null,"errorMessage":"home ca fingerprint mismatch","messagePattern":"home ca fingerprint mismatch","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/home/certificate.go","lineNumber":194,"sourceCode":"func verifyCACertificateFile(path string, expectedFingerprint string) error {\n\traw, errRead := os.ReadFile(path)\n\tif errRead != nil {\n\t\treturn errRead\n\t}\n\treturn verifyCACertificatePEM(raw, expectedFingerprint)\n}\n\nfunc verifyCACertificatePEM(raw []byte, expectedFingerprint string) error {\n\tactual, errFingerprint := certificateFingerprintPEM(raw)\n\tif errFingerprint != nil {\n\t\treturn errFingerprint\n\t}\n\texpected := normalizeFingerprint(expectedFingerprint)\n\tif expected == \"\" {\n\t\treturn fmt.Errorf(\"home ca fingerprint is required\")\n\t}\n\tif actual != expected {\n\t\treturn fmt.Errorf(\"home ca fingerprint mismatch\")\n\t}\n\treturn nil\n}\n\nfunc certificateFingerprintPEM(raw []byte) (string, error) {\n\tblock, _ := pem.Decode(raw)\n\tif block == nil || block.Type != \"CERTIFICATE\" {\n\t\treturn \"\", fmt.Errorf(\"home ca certificate pem is invalid\")\n\t}\n\tcert, errParse := x509.ParseCertificate(block.Bytes)\n\tif errParse != nil {\n\t\treturn \"\", errParse\n\t}\n\tsum := sha256.Sum256(cert.Raw)\n\treturn hex.EncodeToString(sum[:]), nil\n}\n\nfunc normalizeFingerprint(fingerprint string) string {","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/home/certificate.go#L176-L212","documentation":"Thrown by verifyCACertificatePEM in internal/home/certificate.go when the SHA-256 fingerprint of the supplied CA PEM does not match the normalized expected fingerprint. This is a trust-on-first-use pin: the CA file on disk differs from the CA the deployment pinned. Note comparison is exact-string on the lowercased hex digest, so any byte difference in the certificate fails.","triggerScenarios":"CA certificate file at the configured path was rotated, re-issued, or regenerated (different key) while the expected fingerprint stayed the same; wrong CA file copied into auths/ or the cert directory; fingerprint recorded from a different cert (e.g. leaf instead of CA).","commonSituations":"Home server rotated its CA and clients still pin the old fingerprint; staging fingerprint left in production config; someone re-ran the CA generation step and overwrote ca.pem; fingerprint copied with truncation or extra characters is handled by normalization but a wrong-hash is not.","solutions":["Recompute the fingerprint of the CA actually deployed: openssl x509 -in ca.pem -noout -fingerprint -sha256, and update ca-fingerprint in config","If the CA was rotated legitimately, redistribute the new ca.pem and its fingerprint to all clients","If the CA file is wrong, restore the original ca.pem that matches the pinned fingerprint"],"exampleFix":"# before\nhome:\n  ca-cert: /etc/cliproxy/ca.pem\n  ca-fingerprint: \"oldhash...\"\n\n# after — after CA rotation\nhome:\n  ca-cert: /etc/cliproxy/new-ca.pem\n  ca-fingerprint: \"$(openssl x509 -in /etc/cliproxy/new-ca.pem -noout -fingerprint -sha256 | cut -d= -f2 | tr -d : | tr A-F a-f)\"","handlingStrategy":"validation","validationCode":"// pin check at startup, before the client relies on the CA\nfp := strings.ToLower(hex.EncodeToString(certSHA256(caPEM)))\nif fp != normalize(cfg.CAFingerprint) {\n    log.Fatalf(\"ca.pem fingerprint drift: file=%s pinned=%s — rotate config or restore ca.pem\", fp, cfg.CAFingerprint)\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"home ca fingerprint mismatch\") {\n    // do NOT retry: trust mismatch requires operator decision (new fingerprint or restored CA)\n    haltAndAlert(\"home CA trust mismatch\")\n}","preventionTips":["Automate fingerprint updates whenever the CA is rotated (single pipeline for cert + hash)","Alert on CA expiry so rotation is planned, not accidental"],"tags":["go","tls","certificate","pinning","home"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}