netbirdio/netbird · error

failed to parse public root key: %w

Error message

failed to parse public root key: %w

What it means

reposign.ParseRootPublicKey rejected the PEM (root.go:41 delegating to key.go:99). It requires a PEM block whose type is exactly "ROOT PUBLIC KEY" and a JSON body with a 32-byte Ed25519 public key; it then recomputes the KeyID from the key bytes. Wrapped errors: 'failed to decode PEM data', 'PEM type is %q, want %q' (e.g. you passed the private key), 'failed to unmarshal public key', or 'incorrect Ed25519 public key size: expected 32, got N'.

Source

Thrown at client/cmd/signer/revocation.go:181

		return fmt.Errorf("failed to read revocation list file: %w", err)
	}

	// Read signature file
	sigBytes, err := os.ReadFile(signatureFile)
	if err != nil {
		return fmt.Errorf("failed to read signature file: %w", err)
	}

	// Read public root key file
	pubKeyPEM, err := os.ReadFile(publicRootKeyFile)
	if err != nil {
		return fmt.Errorf("failed to read public root key file: %w", err)
	}

	// Parse public root key
	publicKey, err := reposign.ParseRootPublicKey(pubKeyPEM)
	if err != nil {
		return fmt.Errorf("failed to parse public root key: %w", err)
	}

	// Parse signature
	signature, err := reposign.ParseSignature(sigBytes)
	if err != nil {
		return fmt.Errorf("failed to parse signature: %w", err)
	}

	// Validate revocation list
	rl, err := reposign.ValidateRevocationList([]reposign.PublicKey{publicKey}, rlBytes, *signature)
	if err != nil {
		return fmt.Errorf("failed to validate revocation list: %w", err)
	}

	// Display results
	cmd.Println("✅ Revocation list signature is valid")
	cmd.Printf("Last Updated: %s\n", rl.LastUpdated.Format(time.RFC3339))
	cmd.Printf("Expires At: %s\n", rl.ExpiresAt.Format(time.RFC3339))

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Read the wrapped error — the 'PEM type is ...' variant means the wrong key file was passed
  2. Check the header: head -1 must show -----BEGIN ROOT PUBLIC KEY-----
  3. Use the public half that create-root-key wrote alongside the private key
  4. If size/unmarshal checks fail, redistribute the public key from a trusted copy; do not repair PEM bodies by hand

Example fix

# before
signer verify-revocation-list --revocation-list-file rl.json --signature-file rl.json.sig --public-root-key root-private.pem
# error: failed to parse public root key: failed to parse root public key: PEM type is "ROOT PRIVATE KEY", want "ROOT PUBLIC KEY"

# after
signer verify-revocation-list --revocation-list-file rl.json --signature-file rl.json.sig --public-root-key root-public.pem
Defensive patterns

Strategy: type-guard

Validate before calling

func isRootPublicKeyPEM(data []byte) bool {
    block, _ := pem.Decode(data)
    return block != nil && block.Type == "ROOT PUBLIC KEY" && json.Valid(block.Bytes)
}

// pub, err := os.ReadFile(publicRootKeyFile)
// if err == nil && !isRootPublicKeyPEM(pub) { /* wrong key file */ }

Type guard

func isRootPublicKeyPEM(data []byte) bool {
    block, _ := pem.Decode(data)
    return block != nil && block.Type == "ROOT PUBLIC KEY" && json.Valid(block.Bytes)
}

Prevention

When it happens

Trigger: Passing the private root key file to --public-root-key (type is ROOT PRIVATE KEY); passing an artifact-signing public key instead of the root public key; a PEM corrupted or re-wrapped in transit; a key bundle with the wrong block type.

Common situations: Verifying with the wrong half of the pair after rotation; public keys distributed under generic names like key.pem; PEMs mangled by editors that wrap base64 lines.

Understand the failure class

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/91bc8624e84d391b. Report an issue: GitHub.