{"record":{"id":"a5c671a5bea75074","repo":"tailscale/tailscale","slug":"public-key-has-incorrect-length-for-an-ed25519-pub","errorCode":null,"errorMessage":"public key has incorrect length for an Ed25519 public key","messagePattern":"public key has incorrect length for an Ed25519 public key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"clientupdate/distsign/distsign.go","lineNumber":462,"sourceCode":"\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rest) > 0 {\n\t\treturn nil, errors.New(\"trailing PEM data\")\n\t}\n\treturn pub, err\n}\n\nfunc parsePublicKey(data []byte, typeTag string) (pub ed25519.PublicKey, rest []byte, retErr error) {\n\tb, rest := pem.Decode(data)\n\tif b == nil {\n\t\treturn nil, nil, errors.New(\"failed to decode PEM data\")\n\t}\n\tif b.Type != typeTag {\n\t\treturn nil, nil, fmt.Errorf(\"PEM type is %q, want %q\", b.Type, typeTag)\n\t}\n\tif len(b.Bytes) != ed25519.PublicKeySize {\n\t\treturn nil, nil, errors.New(\"public key has incorrect length for an Ed25519 public key\")\n\t}\n\treturn ed25519.PublicKey(b.Bytes), rest, nil\n}\n\n// VerifyAny verifies whether sig is valid for msg using any of the keys.\n// VerifyAny will panic if any of the keys have the wrong size for Ed25519.\nfunc VerifyAny(keys []ed25519.PublicKey, msg, sig []byte) bool {\n\tfor _, k := range keys {\n\t\tif ed25519consensus.Verify(k, msg, sig) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n","sourceCodeStart":444,"sourceCodeEnd":477,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/clientupdate/distsign/distsign.go#L444-L477","documentation":"Thrown by parsePublicKey when a PEM block of the correct type decodes successfully but its payload is not exactly ed25519.PublicKeySize (32) bytes. distsign keys are raw Ed25519 keys wrapped in PEM, not DER/PKIX structures, so any other length means the material is not a raw Ed25519 public key.","triggerScenarios":"Embedding an Ed25519 private key or 64-byte private key blob in a PUBLIC KEY PEM block; embedding a PKIX/DER-encoded public key (typically 44 bytes) instead of the raw 32-byte key; using a key from a different algorithm (RSA, P-256) in the same PEM type tag; corrupted key bytes.","commonSituations":"During key rotation someone published the seed/private key PEM with the public type header; openssl-generated keys (SPKI/PKIX format) were pasted where distsign expects its custom raw format; key file truncated or mutated in transit.","solutions":["Regenerate the key material with distsign's own GenerateRootKey/GenerateSigningKey, which produce the exact expected PEM format","If the source is openssl, extract the raw 32-byte public key (e.g. 'openssl pkey -pubin -in key.pub -outform DER | tail -c 32') and wrap it in the correct PEM block","Verify the payload length is 32 bytes and the PEM Type matches the expected tag before parsing","Treat a length mismatch on a fetched server bundle as possible tampering and investigate rather than silently regenerating"],"exampleFix":"# before\n# key.pub is PKIX DER inside PEM -> 'public key has incorrect length for an Ed25519 public key'\n$ openssl pkey -pubin -in key.pub -outform DER | tail -c 32 > raw32.pub\n\n# after: wrap the raw 32 bytes with the expected PEM type header\n$ { printf -- '-----BEGIN TAILSCALE SIGNING PUBLIC KEY-----\\n'; base64 raw32.pub; printf -- '-----END TAILSCALE SIGNING PUBLIC KEY-----\\n'; } > distsign.pub","handlingStrategy":"validation","validationCode":"// Verify a PEM block is a raw Ed25519 public key before parsing.\nfunc isRawEd25519PublicPEM(data []byte, tag string) bool {\n    b, _ := pem.Decode(data)\n    return b != nil && b.Type == tag && len(b.Bytes) == ed25519.PublicKeySize // 32\n}","typeGuard":null,"tryCatchPattern":"if _, err := distsign.ParseSigningKeyBundle(bundle); err != nil {\n    if strings.Contains(err.Error(), \"incorrect length for an Ed25519 public key\") {\n        // wrong key material (private seed, PKIX, or other algorithm) — or tampering.\n        // Do NOT regenerate keys fetched from a server: investigate the source.\n    }\n    return err\n}","preventionTips":["Produce key material only via distsign's Generate* functions or by extracting exactly the raw 32 public key bytes","Never paste openssl SPKI/PKIX output or 64-byte seeds into PUBLIC KEY PEM slots","Alert on any length mismatch in downloaded key bundles — it can indicate an attempted key substitution"],"tags":["distsign","ed25519","key-format","supply-chain"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}