tailscale/tailscale · critical
public key has incorrect length for an Ed25519 public key
Error message
public key has incorrect length for an Ed25519 public key
What it means
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.
Source
Thrown at clientupdate/distsign/distsign.go:462
if err != nil {
return nil, err
}
if len(rest) > 0 {
return nil, errors.New("trailing PEM data")
}
return pub, err
}
func parsePublicKey(data []byte, typeTag string) (pub ed25519.PublicKey, rest []byte, retErr error) {
b, rest := pem.Decode(data)
if b == nil {
return nil, nil, errors.New("failed to decode PEM data")
}
if b.Type != typeTag {
return nil, nil, fmt.Errorf("PEM type is %q, want %q", b.Type, typeTag)
}
if len(b.Bytes) != ed25519.PublicKeySize {
return nil, nil, errors.New("public key has incorrect length for an Ed25519 public key")
}
return ed25519.PublicKey(b.Bytes), rest, nil
}
// VerifyAny verifies whether sig is valid for msg using any of the keys.
// VerifyAny will panic if any of the keys have the wrong size for Ed25519.
func VerifyAny(keys []ed25519.PublicKey, msg, sig []byte) bool {
for _, k := range keys {
if ed25519consensus.Verify(k, msg, sig) {
return true
}
}
return false
}
View on GitHub (pinned to cfe32b8be6)
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
Example fix
# before
# key.pub is PKIX DER inside PEM -> 'public key has incorrect length for an Ed25519 public key'
$ openssl pkey -pubin -in key.pub -outform DER | tail -c 32 > raw32.pub
# after: wrap the raw 32 bytes with the expected PEM type header
$ { printf -- '-----BEGIN TAILSCALE SIGNING PUBLIC KEY-----\n'; base64 raw32.pub; printf -- '-----END TAILSCALE SIGNING PUBLIC KEY-----\n'; } > distsign.pub Defensive patterns
Strategy: validation
Validate before calling
// Verify a PEM block is a raw Ed25519 public key before parsing.
func isRawEd25519PublicPEM(data []byte, tag string) bool {
b, _ := pem.Decode(data)
return b != nil && b.Type == tag && len(b.Bytes) == ed25519.PublicKeySize // 32
} Try / catch
if _, err := distsign.ParseSigningKeyBundle(bundle); err != nil {
if strings.Contains(err.Error(), "incorrect length for an Ed25519 public key") {
// wrong key material (private seed, PKIX, or other algorithm) — or tampering.
// Do NOT regenerate keys fetched from a server: investigate the source.
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- private key has incorrect length for an Ed25519 private key
- no signing keys found in the bundle
- PEM type is %q, want %q
- failed to decode PEM data
- trailing PEM data
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/a5c671a5bea75074.
Report an issue: GitHub.