【问题标题】:Asking SslStream to accept ONLY a certificate signed by a particular public key要求 SslStream 仅接受由特定公钥签名的证书
【发布时间】:2026-01-22 05:00:02
【问题描述】:

我有一个可行的实现,但想确保它是安全的。目标是使用 SSLStream 并且只接受来自服务器的由特定 RSA 密钥签名的 SSL 证书。

这是我的连接代码:

        var client = new TcpClient("server_address", port_number);
        var sslStream = new SslStream(client.GetStream(), false,
            new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
        sslStream.AuthenticateAsClient("SpeechGrid");

这是我对 ValidateServerCertificate 的实现:

    private static bool ValidateServerCertificate(object sender, X509Certificate certificate,
            X509Chain chain, SslPolicyErrors sslPolicyErrors) {

        // Only accept our specific key pair
        foreach (var cert in chain.ChainElements) {
            if (cert.Certificate.GetPublicKeyString() == k_prodPublicKey) {
                return true;
            }
        }

        return false;
    }

由于 X509Chain 对象的丰富性,我想确保不需要检查 X509ChainStatusFlags.NotSignatureValid 等内容。

例如,攻击者是否有可能“声称”由我的公钥签名,发送无效签名,并且由于 .NET 假定我正在检查所有这些标志,所以这种攻击会起作用?

谢谢!!

更新:好的,到目前为止,我决定将以下检查放在原始 foreach 之上。请注意,这在某种程度上是特定于应用程序的;例如,如果我希望证书过期,我会检查 NotTimeValid 等。

        foreach (var status in chain.ChainStatus) {
            switch (status.Status) {
                case X509ChainStatusFlags.Cyclic:
                case X509ChainStatusFlags.NotSignatureValid:
                case X509ChainStatusFlags.PartialChain:
                    return false;
            }
        }

【问题讨论】:

  • public 密钥用于一般公众...不过,只有您(拥有私钥的人)可以解密加密的消息。如果您想确保数据确实来自第三方,请从他们那里获取公钥(他们将使用他们的密钥加密数据)。

标签: c# .net security ssl sslstream


【解决方案1】:

我会颠倒您在问题更新中添加的检查逻辑。而不是寻找可能的错误并接受其他一切:

foreach (thing that I can think of that might be wrong)
 return false;

if (public key matches regardless of other policy errors)
 return true;

...我会寻找可能有问题但可以接受的地方,并拒绝任何其他政策错误:

if (policy errors)
{
 foreach (error that is acceptable: remote name mismatch, untrusted root, etc.)
   policy errors -= that particular error
}

if (any policy errors left)
 return false;
else if (public key matches)
 return true;
else
 return false;

第一部分是这样的(我没有测试或编译它):

if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateNameMismatch) == SslPolicyErrors.RemoteCertificateNameMismatch)
{
    sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNameMismatch;
}

if ((sslPolicyErrors & SslPolicyErrors.RemoteCertificateChainErrors) == SslPolicyErrors.RemoteCertificateChainErrors)
{
    var otherFlagsFound =
        from i in chain.ChainStatus
        where (i.Status & ~X509ChainStatusFlags.UntrustedRoot) != X509ChainStatusFlags.NoError
        select i;

    if (otherFlagsFound.Count() == 0)
    {
        sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateChainErrors;
    }
}

【讨论】:

    【解决方案2】:

    您可以检查 sslPolicyErrors 参数是否有其他错误,例如过期或证书不受信任。如果一切正常,它应该返回 SslPolicyErrors.None。从公钥派生私钥在计算上是不可行的,因此您不必担心其他人会创建相同的密钥对并对其进行签名。

    【讨论】:

    • 好吧,在我的情况下,它将 SslPolicyErrors 显示为“System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch | System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors”。在链状态下,它显示 UntrustedRoot。请注意,我的证书未由 CA 签名。那么我应该检查以确保 UntrustedRoot 是唯一的链状态吗?
    • RemoteCertificateNameMismatch 意味着您的客户端连接到的主机名与证书上的内容不匹配。如果您连接到“www.myapp.com”,那么证书上的名称也应该与之匹配。您收到 untrustedRoot 错误,因为证书未作为受信任的根 CA 安装在客户端计算机的证书存储中。您可以在商店中安装该 CA,也可以忽略该错误,因为您需要对公钥进行另一次显式检查。
    最近更新 更多