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 passwordView on GitHub (pinned to 7d214f8b09)
Solutions
- Regenerate the key and export it as DER PKIX: x509.MarshalPKIXPublicKey(&priv.PublicKey) — this is the exact format ParsePKIXPublicKey expects
- If the key is stored as PEM, strip headers and base64-decode before putting it in Config.RsaPublicKey
- Verify with a quick Go check: x509.ParsePKIXPublicKey(cfg.RsaPublicKey) must succeed before starting the tunnel
- 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
- Store keys as raw DER bytes in config, never PEM text
- Add a unit test that parses the configured key with x509.ParsePKIXPublicKey at config-load time
- Use x509.MarshalPKIXPublicKey to produce keys so the format is correct by construction
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
- parse server public key: not rsa
- Failed to build REALITY config.
- encrypt shared secret: %w
- encrypt verify token: %w
- failed to compute mod inverse
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/20d5dce2fbc9c46f.
Report an issue: GitHub.