XTLS/Xray-core · critical

parse server public key: %w

Error message

parse server public key: %w

What it means

The client received the server's Encryption Request packet and the raw public key bytes matched the configured key, but Go's x509.ParsePKIXPublicKey failed to decode those bytes as a DER-encoded SubjectPublicKeyInfo. This means the configured RsaPublicKey passed the byte-equality check yet is not a valid PKIX/DER structure. In practice this only happens when both sides share the same malformed key material.

Source

Thrown at transport/internet/finalmask/xmc/client.go:147

	var (
		serverId    String
		publicKey   Bytes
		verifyToken Bytes
	)

	err = pkt.readFields(&serverId, &publicKey, &verifyToken)
	if err != nil {
		return fmt.Errorf("read encryption request fields: %w", err)
	}

	if !bytes.Equal(publicKey, c.rsaPublicKey) {
		return fmt.Errorf("server public key mismatch")
	}

	k, err := x509.ParsePKIXPublicKey(publicKey)
	if err != nil {
		return fmt.Errorf("parse server public key: %w", err)
	}

	rsaPublicKey, ok := k.(*rsa.PublicKey)
	if !ok {
		return fmt.Errorf("parse server public key: not rsa")
	}

	sharedSecret := make([]byte, 16)
	if _, err = rand.Read(sharedSecret); err != nil {
		return fmt.Errorf("generate shared secret: %w", err)
	}

	encryptedSharedSecret, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPublicKey, sharedSecret)
	if err != nil {
		return fmt.Errorf("encrypt shared secret: %w", err)
	}

	verifyToken = append(verifyToken, []byte(c.password)...) // append pre-shared password

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Regenerate the key and export it as DER PKIX: x509.MarshalPKIXPublicKey(&priv.PublicKey) — this is the exact format ParsePKIXPublicKey expects
  2. If the key is stored as PEM, strip headers and base64-decode before putting it in Config.RsaPublicKey
  3. Verify with a quick Go check: x509.ParsePKIXPublicKey(cfg.RsaPublicKey) must succeed before starting the tunnel
  4. Ensure both client (RsaPublicKey) and server (RsaPublicKey/RsaPrivateKey) configs use the same DER bytes

Example fix

// before (PEM text in config)
cfg.RsaPublicKey = []byte(`-----BEGIN PUBLIC KEY-----\nMIIB...\n-----END PUBLIC KEY-----`)

// after (raw DER PKIX bytes)
block, _ := pem.Decode(pemBytes)
derBytes := block.Bytes // already DER PKIX for a PUBLIC KEY block
cfg.RsaPublicKey = derBytes
Defensive patterns

Strategy: validation

Validate before calling

der, err := base64.StdEncoding.DecodeString(strings.TrimSpace(cfgB64))
if err != nil {
    return fmt.Errorf("rsa key is not valid base64: %w", err)
}
if _, err := x509.ParsePKIXPublicKey(der); err != nil {
    return fmt.Errorf("rsa_public_key is not DER PKIX: %w", err)
}

Type guard

func isValidPKIXRSAKey(b []byte) bool {
    if len(b) == 0 {
        return false
    }
    k, err := x509.ParsePKIXPublicKey(b)
    return err == nil && k.(*rsa.PublicKey) != nil
}

Try / catch

if _, err := conn.Read(buf); err != nil && strings.Contains(err.Error(), "parse server public key") {
    log.Fatalf("bad RsaPublicKey config (not DER PKIX): %v", err)
}

Prevention

When it happens

Trigger: Calling Config.WrapConnClient (or the first Read/Write on the wrapped conn) against an xmc server whose rsa_public_key config bytes are not DER PKIX output (e.g. PEM text, base64, raw modulus, or truncated DER). Because the client first checks bytes.Equal(publicKey, c.rsaPublicKey), the server must be echoing back the exact same malformed bytes.

Common situations: The key was copied from a PEM file including the '-----BEGIN-----' headers, pasted as base64 instead of raw DER, generated with a different encoding (PKCS#1 instead of PKIX), or truncated by a protobuf/JSON config pipeline that treats it as a string.

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/20d5dce2fbc9c46f. Report an issue: GitHub.