{"record":{"id":"04cf75323beee79a","repo":"XTLS/Xray-core","slug":"server-public-key-mismatch","errorCode":null,"errorMessage":"server public key mismatch","messagePattern":"server public key mismatch","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"transport/internet/finalmask/xmc/client.go","lineNumber":142,"sourceCode":"\t}\n\n\tif pkt.packetID != 0x01 {\n\t\treturn fmt.Errorf(\"bad encrypt request packet id\")\n\t}\n\n\tvar (\n\t\tserverId    String\n\t\tpublicKey   Bytes\n\t\tverifyToken Bytes\n\t)\n\n\terr = pkt.readFields(&serverId, &publicKey, &verifyToken)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"read encryption request fields: %w\", err)\n\t}\n\n\tif !bytes.Equal(publicKey, c.rsaPublicKey) {\n\t\treturn fmt.Errorf(\"server public key mismatch\")\n\t}\n\n\tk, err := x509.ParsePKIXPublicKey(publicKey)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"parse server public key: %w\", err)\n\t}\n\n\trsaPublicKey, ok := k.(*rsa.PublicKey)\n\tif !ok {\n\t\treturn fmt.Errorf(\"parse server public key: not rsa\")\n\t}\n\n\tsharedSecret := make([]byte, 16)\n\tif _, err = rand.Read(sharedSecret); err != nil {\n\t\treturn fmt.Errorf(\"generate shared secret: %w\", err)\n\t}\n\n\tencryptedSharedSecret, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPublicKey, sharedSecret)","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/transport/internet/finalmask/xmc/client.go#L124-L160","documentation":"The client pins the server's RSA key: the public key bytes in the Encryption Request must equal the rsa_public_key configured locally, and a mismatch aborts the handshake. This is deliberate certificate-style pinning; it catches both man-in-the-middle substitution and simple key/config drift between client and server.","triggerScenarios":"Server rotated or regenerated its RSA key pair while the client config still holds the old public key; client config pasted with a different server's key; an actual MITM presenting its own key. The byte-for-byte bytes.Equal comparison leaves no tolerance for encoding differences (e.g. PEM vs DER, re-encoded PKIX).","commonSituations":"Server restart with auto-generated keys; copying configs between environments; DER-vs-PEM encoding mixups; key regenerated by an orchestrator on redeploy.","solutions":["Re-export the server's current public key exactly as the server sends it (PKIX DER) and update rsa_public_key on the client.","Ensure both sides load the same key pair and that no re-encoding (PEM wrappers, whitespace) altered the bytes.","If the key was not intentionally changed, treat the mismatch as a possible MITM and investigate before trusting."],"exampleFix":"// before (stale key)\n\"rsaPublicKey\": \"MIIBIjANBgkq...old...\"\n// after (re-exported from server)\n\"rsaPublicKey\": \"MIIBIjANBgkq...new...\"","handlingStrategy":"validation","validationCode":"pub, err := x509.ParsePKIXPublicKey(cfg.RsaPublicKey)\nif err != nil || pub == nil {\n\treturn errors.New(\"rsa_public_key is not valid PKIX DER\")\n}\nif _, ok := pub.(*rsa.PublicKey); !ok {\n\treturn errors.New(\"rsa_public_key is not an RSA key\")\n}","typeGuard":"func isRSAPublicKeyDER(der []byte) bool {\n\tk, err := x509.ParsePKIXPublicKey(der)\n\tif err != nil {\n\t\treturn false\n\t}\n\t_, ok := k.(*rsa.PublicKey)\n\treturn ok\n}","tryCatchPattern":"if err := cc.Handshake(); err != nil {\n\tif strings.Contains(err.Error(), \"server public key mismatch\") {\n\t\t// Key pinning failure: either rotate config from the server's current key,\n\t\t// or halt and investigate a possible MITM. Never auto-trust the new key.\n\t\treturn fmt.Errorf(\"pinned key mismatch for %s: verify out-of-band before updating config\", hostname)\n\t}\n\treturn err\n}","preventionTips":["After any server key rotation, re-export the PKIX DER public key and update the client config on both ends.","Pass the key as the exact DER bytes the server sends; do not re-encode from PEM or re-serialize the parsed key.","Treat unexpected mismatches as a security event, not a config nuisance."],"tags":["security","crypto","xmc","finalmask","key-pinning"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}