netbirdio/netbird · error
failed to parse private root key: %w
Error message
failed to parse private root key: %w
What it means
reposign.ParseRootKey failed in create-revocation-list: it expects a single PEM block of type "ROOT PRIVATE KEY" whose payload is JSON with a 64-byte Ed25519 private key (reposign/root.go:32-38, key.go:131-156). It rejects wrong PEM tags (passing the public root key or an artifact private key), trailing PEM data, undecodable PEM, bad JSON, and wrong key length.
Source
Thrown at client/cmd/signer/revocation.go:104
panic(err)
}
if err := verifyRevocationListCmd.MarkFlagRequired("signature-file"); err != nil {
panic(err)
}
if err := verifyRevocationListCmd.MarkFlagRequired("public-root-key"); err != nil {
panic(err)
}
}
func handleCreateRevocationList(cmd *cobra.Command, revocationListFile string, privateRootKeyFile string) error {
privKeyPEM, err := os.ReadFile(privateRootKeyFile)
if err != nil {
return fmt.Errorf("failed to read private root key file: %w", err)
}
privateRootKey, err := reposign.ParseRootKey(privKeyPEM)
if err != nil {
return fmt.Errorf("failed to parse private root key: %w", err)
}
rlBytes, sigBytes, err := reposign.CreateRevocationList(*privateRootKey, expirationDuration)
if err != nil {
return fmt.Errorf("failed to create revocation list: %w", err)
}
if err := writeOutputFiles(revocationListFile, revocationListFile+".sig", rlBytes, sigBytes); err != nil {
return fmt.Errorf("failed to write output files: %w", err)
}
cmd.Println("✅ Revocation list created successfully")
return nil
}
func handleExtendRevocationList(cmd *cobra.Command, keyID, revocationListFile, privateRootKeyFile string) error {
privKeyPEM, err := os.ReadFile(privateRootKeyFile)
if err != nil {View on GitHub (pinned to 93e97f4bf1)
Solutions
- Check head -1 of the file: it must read -----BEGIN ROOT PRIVATE KEY-----
- Confirm it is the private half of the root key pair from `signer generate-root-key`, not the public half and not the artifact key
- Ensure the PEM block structure survived copying (64-column base64 lines, single block)
- Regenerate the root key pair only as a last resort — it invalidates the whole existing chain of trust
Example fix
// before ./signer create-revocation-list --private-root-key root-key-pub.pem ... // after ./signer create-revocation-list --private-root-key root-key-priv.pem ...
Defensive patterns
Strategy: type-guard
Type guard
func isRootPrivateKeyPEM(data []byte) bool {
block, rest := pem.Decode(data)
return block != nil && block.Type == "ROOT PRIVATE KEY" && len(rest) == 0
} Prevention
- Check the BEGIN line before every use: ROOT PRIVATE KEY, not ROOT PUBLIC KEY or ARTIFACT PRIVATE KEY
- Transfer keys only as files through the secret store; never paste PEM text through terminals or chat
- Keep exactly one PEM block per key file
When it happens
Trigger: Passing the ROOT PUBLIC KEY file (most common — same name, different suffix); passing the ARTIFACT PRIVATE KEY; PEM mangled when copied through a terminal or chat (lost newlines); two concatenated PEM blocks.
Common situations: Key vault stores root-key.pem (public) and root-key-priv.pem side by side and the wrong one is selected; key copied with newlines collapsed; key pasted with trailing content.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to parse artifact private key: %w
- failed to parse artifact public key: %w
- failed to parse root public key(s): %w
- sign artifact: %w
- artifact verification failed: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/12a0a0d76f3d519a.
Report an issue: GitHub.