slackhq/nebula · error

bytes did not contain a proper Ed25519/ECDSA public key bann

Error message

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

What it means

The PEM block decoded but its Type banner is not Ed25519PublicKeyBanner or ECDSAP256PublicKeyBanner, the only banners UnmarshalSigningPublicKeyFromPEM accepts. The function deliberately rejects key-agreement banners and directs callers to UnmarshalPublicKeyFromPEM for those.

Source

Thrown at cert/pem.go:197

// consumed data or an error on failure. Only Ed25519/ECDSA public key banners are accepted.
// Use UnmarshalPublicKeyFromPEM for X25519/P256 (ECDH) banners.
func UnmarshalSigningPublicKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
	k, r := pem.Decode(b)
	if k == nil {
		return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
	}
	var expectedLen int
	var curve Curve
	switch k.Type {
	case Ed25519PublicKeyBanner:
		expectedLen = 32
		curve = Curve_CURVE25519
	case ECDSAP256PublicKeyBanner:
		// Uncompressed
		expectedLen = 65
		curve = Curve_P256
	default:
		return nil, r, 0, fmt.Errorf("bytes did not contain a proper Ed25519/ECDSA public key banner")
	}
	if len(k.Bytes) != expectedLen {
		return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid %s public key", expectedLen, curve)
	}
	return k.Bytes, r, curve, nil
}

func MarshalPrivateKeyToPEM(curve Curve, b []byte) []byte {
	switch curve {
	case Curve_CURVE25519:
		return pem.EncodeToMemory(&pem.Block{Type: X25519PrivateKeyBanner, Bytes: b})
	case Curve_P256:
		return pem.EncodeToMemory(&pem.Block{Type: P256PrivateKeyBanner, Bytes: b})
	default:
		return nil
	}
}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Use UnmarshalPublicKeyFromPEM if the PEM type is an X25519/P256 key-agreement banner
  2. Ensure the input banner is '-----BEGIN NEBULA ED25519 PUBLIC KEY-----' or '-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----'
  3. Extract the signing public key from the certificate object (e.g. cert.GetPublicKey) instead of feeding the certificate PEM into this function

Example fix

// before
_, rest, curve, err := cert.UnmarshalSigningPublicKeyFromPEM(x25519PubPEM) // ECDH banner
// after
_, rest, curve, err := cert.UnmarshalPublicKeyFromPEM(x25519PubPEM)
Defensive patterns

Strategy: validation

Validate before calling

func isSigningBanner(b []byte) bool {
	blk, _ := pem.Decode(b)
	if blk == nil {
		return false
	}
	return blk.Type == cert.Ed25519PublicKeyBanner || blk.Type == cert.ECDSAP256PublicKeyBanner
}

Try / catch

pub, rest, curve, err := cert.UnmarshalSigningPublicKeyFromPEM(b)
if err != nil {
	if strings.Contains(err.Error(), "Ed25519/ECDSA public key banner") {
		if blk, _ := pem.Decode(b); blk != nil && (blk.Type == cert.X25519PublicKeyBanner || blk.Type == cert.P256PublicKeyBanner) {
			_, _, _, err = cert.UnmarshalPublicKeyFromPEM(b)
			return err
		}
	}
	return err
}

Prevention

When it happens

Trigger: Calling UnmarshalSigningPublicKeyFromPEM with a PEM block typed 'NEBULA X25519 PUBLIC KEY', 'CERTIFICATE', 'NEBULA ED25519 ENCRYPTED PRIVATE KEY', or any other non-signing-public-key banner.

Common situations: Swapping the two key-loading functions — passing an X25519 ECDH public key where an Ed25519 signing public key is needed (e.g. when verifying certificate signatures or loading the CA signing key's public half), or passing a full certificate PEM instead of the extracted public key.

Related errors


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