netbirdio/netbird · error

failed to parse root public key(s): %w

Error message

failed to parse root public key(s): %w

What it means

parseRootPublicKeys failed: it calls reposign.ParseRootPublicKey, which requires a PEM block of type "ROOT PUBLIC KEY" with a JSON body and a 32-byte Ed25519 key (reposign/root.go:41-47, key.go:99-124). Wrong tag (most commonly an ARTIFACT PUBLIC KEY passed to --root-key-file), bad PEM, corrupt JSON, or wrong key length fails. Note the helper parses only the first block despite the plural "key(s)" in the message (artifactsign.go:270-276).

Source

Thrown at client/cmd/signer/artifactsign.go:219

func handleVerifyArtifactKey(cmd *cobra.Command, artifactKeyFile, rootKeyFile, signatureFile, revocationFile string) error {
	cmd.Println("🔍 Verifying artifact key...")

	// Read artifact key data
	artifactKeyData, err := os.ReadFile(artifactKeyFile)
	if err != nil {
		return fmt.Errorf("read artifact key file: %w", err)
	}

	// Read root public key(s)
	rootKeyData, err := os.ReadFile(rootKeyFile)
	if err != nil {
		return fmt.Errorf("read root key file: %w", err)
	}

	rootPublicKeys, err := parseRootPublicKeys(rootKeyData)
	if err != nil {
		return fmt.Errorf("failed to parse root public key(s): %w", err)
	}

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

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

	// Read optional revocation list
	var revocationList *reposign.RevocationList
	if revocationFile != "" {
		revData, err := os.ReadFile(revocationFile)
		if err != nil {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Pass the ROOT PUBLIC KEY PEM produced by `signer generate-root-key`: head -1 must read -----BEGIN ROOT PUBLIC KEY-----
  2. Confirm you did not swap --root-key-file and --artifact-key-file
  3. If using concatenated root keys, ensure the first PEM block is intact and correctly tagged

Example fix

// before
./signer verify-artifact-key --root-key-file artifact-key-pub.pem ...

// after
./signer verify-artifact-key --root-key-file root-key-pub.pem ...
Defensive patterns

Strategy: type-guard

Type guard

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

Prevention

When it happens

Trigger: Passing the artifact public key bundle to --root-key-file; passing a root private key; a standard PEM public key; first block of a concatenated root key bundle being corrupt.

Common situations: Two key files with similar names in the release directory; rotating root keys and grabbing the wrong generation; using openssl-generated keys outside the signer toolchain.

Understand the failure class

Related errors


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