slackhq/nebula · error

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

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.

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.

Appendix: 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)