netbirdio/netbird · error

failed to decode PEM data

Error message

failed to decode PEM data

What it means

Returned by parsePublicKey (client/internal/updater/reposign/key.go:102) when pem.Decode cannot find a PEM block at the start of the data, returning nil. The reposign key format is a PEM block (with an expected type tag such as tagArtifactPublic) whose body is JSON describing the key, so input that is not PEM-armored cannot be a valid key and parsing aborts.

Source

Thrown at client/internal/updater/reposign/key.go:102

	var keys []PublicKey
	for len(bundle) > 0 {
		keyInfo, rest, err := parsePublicKey(bundle, typeTag)
		if err != nil {
			return nil, err
		}
		keys = append(keys, keyInfo)
		bundle = rest
	}
	if len(keys) == 0 {
		return nil, errors.New("no keys found in bundle")
	}
	return keys, nil
}

func parsePublicKey(data []byte, typeTag string) (PublicKey, []byte, error) {
	b, rest := pem.Decode(data)
	if b == nil {
		return PublicKey{}, nil, errors.New("failed to decode PEM data")
	}
	if b.Type != typeTag {
		return PublicKey{}, nil, fmt.Errorf("PEM type is %q, want %q", b.Type, typeTag)
	}

	// Unmarshal JSON-embedded format
	var pub PublicKey
	if err := json.Unmarshal(b.Bytes, &pub); err != nil {
		return PublicKey{}, nil, fmt.Errorf("failed to unmarshal public key: %w", err)
	}

	// Validate key length
	if len(pub.Key) != ed25519.PublicKeySize {
		return PublicKey{}, nil, fmt.Errorf("incorrect Ed25519 public key size: expected %d, got %d",
			ed25519.PublicKeySize, len(pub.Key))
	}

	// Always recompute ID to ensure integrity

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Inspect the first bytes of the data: it must start with a valid PEM BEGIN line for the expected type tag.
  2. Re-fetch the key file from the canonical source rather than repairing it by hand.
  3. If generating bundles yourself, ensure each key is emitted with the exact PEM type tag expected by the caller (tagArtifactPublic / tagArtifactRoot).

Example fix

// before
block, _ := pem.Decode(rawKeyBytes) // raw JSON, block == nil -> error later

// after: verify armor before parsing
if _, rest := pem.Decode(data); len(rest) == len(data) {
    return fmt.Errorf("input is not PEM-encoded")
}
key, rest, err := parsePublicKey(data, tagArtifactPublic)
Defensive patterns

Strategy: try-catch

Validate before calling

// Guard before parsing: PEM data begins with a BEGIN line for the expected type.
if !bytes.HasPrefix(data, []byte("-----BEGIN "+expectedType+"-----")) {
    return fmt.Errorf("key file is not a %s PEM block", expectedType)
}

Try / catch

key, rest, err := parsePublicKey(data, typeTag)
if err != nil {
    return nil, fmt.Errorf("parse public key bundle entry (expected %s PEM): %w", typeTag, err)
}

Prevention

When it happens

Trigger: Feeding parsePublicKey data that is raw JSON, base64, or binary without the -----BEGIN----- armor; a key file whose header line was corrupted or wrapped by a mail/transfer layer; an empty or whitespace-only remainder after previous keys in a multi-key bundle.

Common situations: Manual editing or re-encoding of key files strips the PEM armor; a transfer pipeline re-encodes line endings or truncates the BEGIN line; tests feeding ed25519 public keys in raw form instead of the reposign PEM+JSON envelope.

Understand the failure class

Related errors


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