slackhq/nebula · error

bytes did not contain a proper Ed25519/ECDSA private key ban

Error message

bytes did not contain a proper Ed25519/ECDSA private key banner

What it means

pem.Decode produced a block, but its Type matches none of the accepted signing private key banners: encrypted Ed25519, encrypted ECDSA P256, plain Ed25519, or plain ECDSA P256. The input is valid PEM but is not a nebula signing private key — commonly it is a host (X25519/P256) key, a public key, or a certificate.

Source

Thrown at cert/pem.go:274

	}
	var curve Curve
	switch k.Type {
	case EncryptedEd25519PrivateKeyBanner:
		return nil, nil, Curve_CURVE25519, ErrPrivateKeyEncrypted
	case EncryptedECDSAP256PrivateKeyBanner:
		return nil, nil, Curve_P256, ErrPrivateKeyEncrypted
	case Ed25519PrivateKeyBanner:
		curve = Curve_CURVE25519
		if len(k.Bytes) != ed25519.PrivateKeySize {
			return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid Ed25519 private key", ed25519.PrivateKeySize)
		}
	case ECDSAP256PrivateKeyBanner:
		curve = Curve_P256
		if len(k.Bytes) != 32 {
			return nil, r, 0, fmt.Errorf("key was not 32 bytes, is invalid ECDSA P256 private key")
		}
	default:
		return nil, r, 0, fmt.Errorf("bytes did not contain a proper Ed25519/ECDSA private key banner")
	}
	return k.Bytes, r, curve, nil
}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Use the CA signing key file (banner 'NEBULA ED25519 SIGNING PRIVATE KEY' or 'NEBULA ECDSA P256 SIGNING PRIVATE KEY')
  2. Regenerate with nebula-cert ca / keygen to get a correctly bannered signing key
  3. Double-check config: signing_key_path must not point at the host key or certificate

Example fix

// before
signing_key_path: ./host.key // X25519 host key
// after
signing_key_path: ./ca.key // Ed25519/ECDSA P256 signing private key banner
Defensive patterns

Strategy: validation

Validate before calling

blk, _ := pem.Decode(data)
valid := map[string]bool{
    "NEBULA ED25519 SIGNING PRIVATE KEY": true,
    "NEBULA ECDSA P256 SIGNING PRIVATE KEY": true,
    "NEBULA ED25519 ENCRYPTED SIGNING PRIVATE KEY": true,
    "NEBULA ECDSA P256 ENCRYPTED SIGNING PRIVATE KEY": true,
}
if blk != nil && !valid[blk.Type] {
    return fmt.Errorf("not a nebula signing private key, PEM type is %q", blk.Type)
}

Type guard

func isSigningPrivateKey(b []byte) bool {
    blk, _ := pem.Decode(b)
    if blk == nil { return false }
    switch blk.Type {
    case "NEBULA ED25519 SIGNING PRIVATE KEY", "NEBULA ECDSA P256 SIGNING PRIVATE KEY":
        return true
    }
    return false
}

Try / catch

key, _, curve, err := nebula.UnmarshalSigningPrivateKeyFromPEM(raw)
if err != nil {
    return fmt.Errorf("%s is not a signing private key — check signing_key_path: %w", path, err)
}

Prevention

When it happens

Trigger: Call UnmarshalSigningPrivateKeyFromPEM with any PEM block whose k.Type is not one of EncryptedEd25519PrivateKeyBanner, EncryptedECDSAP256PrivateKeyBanner, Ed25519PrivateKeyBanner, ECDSAP256PrivateKeyBanner.

Common situations: Swapping signing_key_path with key/cert paths in nebula config, passing an openssl-generated 'EC PRIVATE KEY' or 'PRIVATE KEY' block, or feeding a certificate PEM to the key parser.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/88408da2736c12c0. Report an issue: GitHub.