tailscale/tailscale · error
no signing keys found in the bundle
Error message
no signing keys found in the bundle
What it means
Thrown by parsePublicKeyBundle when the input bundle contains zero PEM blocks of the expected type. The distsign package (used by tailscale update to verify downloadable binaries) expects a concatenation of PEM-encoded Ed25519 public keys, e.g. the distsign.pub bundle fetched from the download server or validated by RootKey.SignSigningKeys. An empty or whitespace-only input parses to zero keys and is rejected because there would be nothing to trust.
Source
Thrown at clientupdate/distsign/distsign.go:437
}
// ParseRootKeyBundle parses the bundle of PEM-encoded public root keys.
func ParseRootKeyBundle(bundle []byte) ([]ed25519.PublicKey, error) {
return parsePublicKeyBundle(bundle, pemTypeRootPublic)
}
func parsePublicKeyBundle(bundle []byte, typeTag string) ([]ed25519.PublicKey, error) {
var keys []ed25519.PublicKey
for len(bundle) > 0 {
pub, rest, err := parsePublicKey(bundle, typeTag)
if err != nil {
return nil, err
}
keys = append(keys, pub)
bundle = rest
}
if len(keys) == 0 {
return nil, errors.New("no signing keys found in the bundle")
}
return keys, nil
}
func parseSinglePublicKey(data []byte, typeTag string) (ed25519.PublicKey, error) {
pub, rest, err := parsePublicKey(data, typeTag)
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 {View on GitHub (pinned to cfe32b8be6)
Solutions
- Print/inspect the bytes passed to the parser (len and first lines) to confirm the bundle is empty
- Fix the source: point the client at the correct distsign.pub URL or regenerate the bundle by concatenating the PEM outputs of GenerateSigningKey for each signing key
- If publishing manually, verify the file on the server is non-empty and starts with '-----BEGIN' before shipping
- Re-run the download/verification once the server serves a valid bundle
Example fix
// before
bundle, err := os.ReadFile("distsign.pub")
if err != nil { return err }
keys, err := distsign.ParseSigningKeyBundle(bundle) // errors: no signing keys found
// after
bundle, err := os.ReadFile("distsign.pub")
if err != nil { return err }
if len(bytes.TrimSpace(bundle)) == 0 {
return fmt.Errorf("distsign.pub is empty; rebuild/republish the key bundle")
}
keys, err := distsign.ParseSigningKeyBundle(bundle) Defensive patterns
Strategy: validation
Validate before calling
// Pre-check a key bundle before handing it to distsign.
func validBundle(bundle []byte, tag string) bool {
rest := bundle
n := 0
for {
var b *pem.Block
b, rest = pem.Decode(rest)
if b == nil {
return n > 0 && len(rest) == 0
}
if b.Type != tag {
return false
}
n++
}
} Try / catch
keys, err := distsign.ParseSigningKeyBundle(data)
if err != nil {
if strings.Contains(err.Error(), "no signing keys found") {
// empty/blank bundle: fix the source file or URL, do not retry blindly
}
return fmt.Errorf("parsing signing key bundle: %w", err)
} Prevention
- Never serve key bundles from endpoints that can return 200 with an empty body; fail HTTP >= 400 explicitly
- Add a CI step asserting distsign.pub is non-empty and contains at least one BEGIN/END block before publish
- Log bundle length and a hash alongside parse errors to distinguish empty vs corrupt input
When it happens
Trigger: Calling distsign.ParseSigningKeyBundle / ParseRootKeyBundle (or RootKey.SignSigningKeys, which internally validates the bundle) with an empty []byte or data containing no PEM blocks; serving an empty distsign.pub file from the download server that the client fetches before verifying a download.
Common situations: Download server returns HTTP 200 with an empty body for the key-bundle URL; the bundle file was truncated or created empty during a release-pipeline failure; a wrong URL/path serves blank content; CI passes an unset environment variable holding the bundle.
Related errors
- private key has incorrect length for an Ed25519 private key
- public key has incorrect length for an Ed25519 public key
- 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/d173be7d0ecab4ef.
Report an issue: GitHub.