slackhq/nebula · error

input did not contain a valid PEM encoded block

Error message

input did not contain a valid PEM encoded block

What it means

UnmarshalPublicKeyFromPEM calls pem.Decode on the input; if no valid PEM block can be parsed it returns this error. Only key-agreement (ECDH) public key PEM banners (X25519, P256) are accepted by this function once parsing succeeds.

Source

Thrown at cert/pem.go:157

// if your public key came from a certificate, prefer Certificate.PublicKeyPEM() if possible, to avoid mistakes!
func MarshalSigningPublicKeyToPEM(curve Curve, b []byte) []byte {
	switch curve {
	case Curve_CURVE25519:
		return pem.EncodeToMemory(&pem.Block{Type: Ed25519PublicKeyBanner, Bytes: b})
	case Curve_P256:
		return pem.EncodeToMemory(&pem.Block{Type: ECDSAP256PublicKeyBanner, Bytes: b})
	default:
		return nil
	}
}

// UnmarshalPublicKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non
// consumed data or an error on failure. Only key-agreement (ECDH) public key banners are accepted.
// Use UnmarshalSigningPublicKeyFromPEM for Ed25519/ECDSA banners.
func UnmarshalPublicKeyFromPEM(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 X25519PublicKeyBanner:
		expectedLen = 32
		curve = Curve_CURVE25519
	case P256PublicKeyBanner:
		// Uncompressed
		expectedLen = 65
		curve = Curve_P256
	default:
		return nil, r, 0, fmt.Errorf("bytes did not contain a proper 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

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Ensure the input is PEM text starting with '-----BEGIN NEBULA X25519 PUBLIC KEY-----' or '-----BEGIN NEBULA ECDSA P256 PUBLIC KEY-----'
  2. If you have a signing (Ed25519/ECDSA) public key instead, call UnmarshalSigningPublicKeyFromPEM — but note that still requires PEM, so PEM-armor raw keys first
  3. Check the source of the bytes (file read error handling, HTTP response body) — the value may be empty or not what you expect

Example fix

// before
pub := rawKeyBytes // raw 32 bytes, no PEM
_, rest, curve, err := cert.UnmarshalPublicKeyFromPEM(pub)
// after
pemBytes := pem.EncodeToMemory(&pem.Block{Type: cert.X25519PublicKeyBanner, Bytes: rawKeyBytes})
_, rest, curve, err := cert.UnmarshalPublicKeyFromPEM(pemBytes)
Defensive patterns

Strategy: validation

Validate before calling

func hasValidPEM(b []byte) bool {
	blk, _ := pem.Decode(b)
	return blk != nil
}
// guard: if !hasValidPEM(pubBytes) { return error before calling UnmarshalPublicKeyFromPEM }

Type guard

func isECDHPublicKeyPEM(b []byte) bool {
	blk, _ := pem.Decode(b)
	if blk == nil {
		return false
	}
	switch blk.Type {
	case cert.X25519PublicKeyBanner, cert.P256PublicKeyBanner:
		return len(blk.Bytes) == 32 || len(blk.Bytes) == 65
	}
	return false
}

Try / catch

pub, rest, curve, err := cert.UnmarshalPublicKeyFromPEM(b)
if err != nil {
	if strings.Contains(err.Error(), "valid PEM encoded block") {
		return fmt.Errorf("public key input is not PEM-encoded; check the source of the bytes: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: Calling UnmarshalPublicKeyFromPEM(b) where b contains no PEM block: empty bytes, raw key bytes, plain text, an invalid base64 body, or a certificate in DER (binary) form instead of PEM.

Common situations: Fetching a public key over HTTP and getting JSON/DER instead of PEM, env-var or template substitution leaving the value empty, passing raw ed25519.PublicKey bytes without PEM armor.

Related errors


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