{"record":{"id":"795ccc739146c827","repo":"netbirdio/netbird","slug":"failed-to-parse-signature-w-795ccc","errorCode":null,"errorMessage":"failed to parse signature: %w","messagePattern":"failed to parse signature: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/cmd/signer/revocation.go","lineNumber":187,"sourceCode":"\t\treturn fmt.Errorf(\"failed to read signature file: %w\", err)\n\t}\n\n\t// Read public root key file\n\tpubKeyPEM, err := os.ReadFile(publicRootKeyFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read public root key file: %w\", err)\n\t}\n\n\t// Parse public root key\n\tpublicKey, err := reposign.ParseRootPublicKey(pubKeyPEM)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse public root key: %w\", err)\n\t}\n\n\t// Parse signature\n\tsignature, err := reposign.ParseSignature(sigBytes)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse signature: %w\", err)\n\t}\n\n\t// Validate revocation list\n\trl, err := reposign.ValidateRevocationList([]reposign.PublicKey{publicKey}, rlBytes, *signature)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to validate revocation list: %w\", err)\n\t}\n\n\t// Display results\n\tcmd.Println(\"✅ Revocation list signature is valid\")\n\tcmd.Printf(\"Last Updated: %s\\n\", rl.LastUpdated.Format(time.RFC3339))\n\tcmd.Printf(\"Expires At: %s\\n\", rl.ExpiresAt.Format(time.RFC3339))\n\tcmd.Printf(\"Number of revoked keys: %d\\n\", len(rl.Revoked))\n\n\tif len(rl.Revoked) > 0 {\n\t\tcmd.Println(\"\\nRevoked Keys:\")\n\t\tfor keyID, revokedTime := range rl.Revoked {\n\t\t\tcmd.Printf(\"  - %s (revoked at: %s)\\n\", keyID, revokedTime.Format(time.RFC3339))","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/cmd/signer/revocation.go#L169-L205","documentation":"reposign.ParseSignature failed (signature.go:17): it is a plain json.Unmarshal into the Signature struct {signature []byte, timestamp time.Time, key_id KeyID, algorithm, hash_algo}. Failure means the .sig bytes are not valid JSON, have wrong field types, or carry a key_id string that the KeyID unmarshaler rejects (not 16 hex chars). Note json.Unmarshal ignores unknown and missing fields, so passing the revocation list itself as --signature-file parses successfully into zero values and blows up later in ValidateRevocationList with timestamp errors instead.","triggerScenarios":"A binary or garbage .sig file; a signature JSON edited so base64 or timestamps no longer unmarshal; a key_id field rewritten to a non-16-hex string; feeding the list JSON into --signature-file (fails downstream, not here).","commonSituations":"Signature file corrupted in transfer (CRLF mangling, HTML error page saved as .sig); hand-crafted signature files; tooling that re-serializes JSON with different field types.","solutions":["Check the file is JSON and has the expected shape: jq '{signature, timestamp, key_id}' rl.json.sig","If corrupted, regenerate the pair by re-running create/extend-revocation-list with the root private key","Ensure the key_id value is exactly 16 hex characters","Never hand-edit signature files — they are only meaningful as produced by the signer"],"exampleFix":"# before: rl.json.sig contains an HTML error page\njq . rl.json.sig   # parse error\n\n# after: regenerate the matched pair\nsigner extend-revocation-list --key-id 1a2b3c4d5e6f7080 --revocation-list-file rl.json --private-root-key root.pem","handlingStrategy":"validation","validationCode":"func looksLikeSignature(data []byte) bool {\n    var v map[string]json.RawMessage\n    return json.Unmarshal(data, &v) == nil &&\n        len(v[\"signature\"]) > 0 &&\n        len(v[\"timestamp\"]) > 0 &&\n        len(v[\"key_id\"]) > 0\n}\n\n// sig, err := os.ReadFile(signatureFile)\n// if err == nil && !looksLikeSignature(sig) { /* wrong or corrupt .sig */ }","typeGuard":"func isSignatureJSON(data []byte) bool {\n    var v struct {\n        Signature json.RawMessage `json:\"signature\"`\n        Timestamp json.RawMessage `json:\"timestamp\"`\n        KeyID     json.RawMessage `json:\"key_id\"`\n    }\n    return json.Unmarshal(data, &v) == nil &&\n        len(v.Signature) > 0 && len(v.Timestamp) > 0 && len(v.KeyID) > 0\n}","tryCatchPattern":null,"preventionTips":["jq-validate .sig files after every transfer: jq 'keys' rl.json.sig","Never hand-craft or re-serialize signature files","Detect silent mix-ups early: a list passed as .sig parses to zero values and only fails later — validate field presence up front"],"tags":["go","cli","json","signature","reposign"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}