shadow1ng/fscan · critical
failed to get server public key
Error message
failed to get server public key
What it means
During the RDP secure handshake in sec/sec.go, the client extracts the RSA public key from the server's X.509 certificate (ServerSecurityData.ServerCertificate.CertData.GetPublicKey). If extraction fails or returns nil, the client emits 'failed to get server public key' and aborts the connection, since it cannot encrypt the client random with PKCS1v15.
Solutions
- Inspect the server certificate bytes: run GetPublicKey against the CertData manually and check the error to see whether it's a parse failure or a nil key.
- Ensure the client negotiates standard TLS/SSL (PROTOCOL_SSL or HYBRID) so the server sends a full X.509 certificate rather than a proprietary one.
- Verify the ServerSecurityData unpacked correctly — log CertData length and dwVersion; a short read upstream yields an empty cert.
- If the cert uses an EC/non-RSA key, add key-type handling in GetPublicKey or connect with encryption negotiated to match (the EncryptPKCS1v15 path requires RSA).
- Retry the connection once; intermittent truncation can produce empty CertData.
Example fix
// before
serverPubKey, err := c.ServerSecurityData().ServerCertificate.CertData.GetPublicKey()
if err != nil || serverPubKey == nil {
glog.Error("GetPublicKey failed:", err)
c.Emit("error", errors.New("failed to get server public key"))
return false
}
// after
serverPubKey, err := c.ServerSecurityData().ServerCertificate.CertData.GetPublicKey()
if err != nil {
glog.Error("GetPublicKey failed:", err)
c.Emit("error", fmt.Errorf("failed to get server public key: %w", err))
return false
}
if serverPubKey == nil {
c.Emit("error", errors.New("failed to get server public key: empty cert data"))
return false
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-connect sanity check on the certificate blob
cert := c.ServerSecurityData().ServerCertificate.CertData
if cert == nil || len(cert) == 0 {
return errors.New("server returned empty certificate data; check security protocol negotiation")
} Type guard
func hasServerPubKey(c *grdpClient) bool {
key, err := c.ServerSecurityData().ServerCertificate.CertData.GetPublicKey()
return err == nil && key != nil
} Try / catch
client.On("error", func(err error) {
if strings.Contains(err.Error(), "failed to get server public key") {
glog.Error("server cert unusable; check TLS/NLA negotiation and cert format: ", err)
return
}
glog.Error("rdp error: ", err)
}) Prevention
- Negotiate standard SSL/HYBRID security protocol so the server presents a parseable X.509 cert
- Verify server cert compatibility before deploying (test with openssl s_client or mstsc)
- Handle RSA-only assumption: reject non-RSA server keys up front
- Retry on intermittent connections that may truncate the security exchange
When it happens
Trigger: Server's cert blob (PROPRIARYSERVERCERT / X.509 CertData) is malformed, uses an unsupported certificate type (e.g. proprietary format without a parseable key), contains a non-RSA key, or GetPublicKey returns (nil, nil) for an empty/truncated cert body — all reached during the ClientInfoPDU/security exchange after receiving the server security data.
Common situations: Connecting to a server with an unusual or self-signed cert chain the parser mishandles; NLA/TLS negotiation mismatch leaving the security header fields wrong; older servers sending a proprietary server certificate (dwVersion 0x00000001) that GetPublicKey can't parse; truncated cert data over flaky networks.
Related errors
- empty certificate chain
- NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID
- NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM
- Not a valid license packet
- parse certificate
AI-assisted analysis of shadow1ng/fscan@95cc12e753 (2026-09-06).
Data as JSON: /api/errors/3b635aaf9c2dea55.
Report an issue: GitHub.
Appendix: source
Thrown at libs/grdp/protocol/sec/sec.go:696
if err != nil {
glog.Error("generateKeys failed:", err)
c.Emit("error", err)
return false
}
//initialize keys
c.currentDecrytKey = c.initialDecrytKey
c.currentEncryptKey = c.initialEncryptKey
//verify certificate
if !c.ServerSecurityData().ServerCertificate.CertData.Verify() {
glog.Warn("Cannot verify server identity")
}
serverPubKey, err := c.ServerSecurityData().ServerCertificate.CertData.GetPublicKey()
if err != nil || serverPubKey == nil {
glog.Error("GetPublicKey failed:", err)
c.Emit("error", errors.New("failed to get server public key"))
return false
}
ret, err := rsa.EncryptPKCS1v15(rand.Reader, serverPubKey, core.Reverse(clientRandom))
if err != nil {
glog.Error("EncryptPKCS1v15 err:", err)
c.Emit("error", err)
return false
}
message := ClientSecurityExchangePDU{}
message.EncryptedClientRandom = core.Reverse(ret)
message.Length = uint32(len(message.EncryptedClientRandom) + 8)
message.Padding = make([]byte, 8)
glog.Debug("message:", message)
c.sendFlagged(EXCHANGE_PKT, message.serialize())
return true
}View on GitHub (pinned to 95cc12e753)