确保 TCP 流量和证书锁定安全?

gro*_*boy 5 .net c# java ssl ssl-certificate

我们开发了一个通过 TCP 处理请求的 Java 应用程序。我们还为应用程序开发客户端库,其中每个库支持不同的语言/平台(Java、.NET 等)。

直到最近,TCP 流量还被限制在安全网络中。为了支持在不安全网络上的使用,我们使用java-plain-and-tls-socket-examples中的配方实现了 TLS 。这里有适用于服务器和客户端的配方,以及生成 X.509 证书的脚本。以下是我们用于仅服务器身份验证的 TLS 的配方摘要:

  • 创建自签名的 X.509 根证书。
  • 使用包含证书的标识数据以及公钥和私钥的密钥库文件配置服务器。
  • 使用包含相同标识数据以及仅公钥的信任存储文件配置客户端。

这看起来像是证书固定的设置,因为客户端在连接之前拥有服务器证书的副本。显然,在连接时,客户端将以某种方式使用此数据来验证服务器发送的证书。

我们现在假设这种方法对于保护 TCP 流量是有效的。由证书颁发机构签名似乎没有必要,因为我们同时控制服务器和客户端。

初步测试表明该实现在我们的 Java 服务器和 Java 客户端(均在本地运行)中正常运行:

  • 客户端接受与客户端信任存储中的数据相匹配的服务器证书。
  • 客户端拒绝与客户端信任存储中的数据不匹配的服务器证书。
  • tcpdump显示 TCP 数据包包含加密数据。

.NET客户端

我们使用SslStream来加密 TCP 流量。正如文档所示,我们没有指定 TLS 版本;相反,如果版本低于 1.2,我们会抛出异常。

我们对如何正确使用X509Chain.ChainPolicy.CustomTrustStore没有信心,因为文档忽略了此类型的用例以及 和 等选项类型的X509KeyStorageFlags信息X509VerificationFlags。

下面的代码旨在模仿上面概述的方法,即配置信任存储数据结构,供客户端在验证服务器证书时使用。这种方法似乎相当于将证书导入到操作系统的信任存储中。

// Import the trust store.
private X509Certificate2Collection GetCertificates(string storePath, string storePassword)
{
    byte[] bytes = File.ReadAllBytes(storePath);

    var result = new X509Certificate2Collection();
    result.Import(bytes, storePassword, X509KeyStorageFlags.EphemeralKeySet);
    return result;
}

// Callback function to validate a certificate received from the server.
// fCertificates stores the result of function GetCertificates.
private bool ValidateServerCertificate(
    object sender,
    X509Certificate certificate,
    X509Chain chain,
    SslPolicyErrors sslPolicyErrors)
{
    // Do not allow this client to communicate with unauthenticated servers.
    //
    // With a self-signed certficate, sslPolicyErrors should be always equal to
    // SslPolicyErrors.RemoteCertificateChainErrors.
    var result = (SslPolicyErrors.RemoteCertificateChainErrors == sslPolicyErrors);
    if (result)
    {
        // The values below are default values: set them to be explicit.
        chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
        chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
    
        chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
        chain.ChainPolicy.CustomTrustStore.AddRange(fCertificates);
        result = chain.Build((X509Certificate2)certificate);
    }

    return result;
}

// Initialize SslStream.
private SslStream GetStream(TcpClient tcpClient, string targetHost)
{
    SslStream sslStream = new SslStream(
        tcpClient.GetStream(),
        false,
        new RemoteCertificateValidationCallback(ValidateServerCertificate),
        null
    );

    try
    {
        sslStream.AuthenticateAsClient(targetHost);

        // require TLS 1.2 or higher
        if (sslStream.SslProtocol < SslProtocols.Tls12)
        {
            throw new AuthenticationException($"The SSL protocol ({sslStream.SslProtocol}) must be {SslProtocols.Tls12} or higher.");
        }
    }
    catch (AuthenticationException caught)
    {
        sslStream.Dispose();
        throw caught;
    }

    return sslStream;
}
Run Code Online (Sandbox Code Playgroud)

初步测试产生的结果因操作系统而异:

  • WSL2 上 Ubuntu 上的 .NET 6 ASP.NET Web 客户端:
    • 接受有效的服务器证书:
    • 拒绝无效的服务器证书:X509Chain.Build返回false且唯一的链状态项是“UntrustedRoot,自签名证书”。
  • MacOS 上的 .NET 6 MAUI 客户端:
    1. 拒绝有效的服务器证书:回调函数参数sslPolicyErrors包括以下值:
      • SslPolicyErrors.RemoteCertificateNameMismatch(意外)。
      • SslPolicyErrors.RemoteCertificateChainErrors(预期的)。
    2. 如果我们将代码更改为忽略,sslPolicyErrors则:
      • 接受有效的服务器证书并自动使用 TLS 1.2。
      • 拒绝无效的服务器证书(如上所述)。

问题

  1. 此 .NET 代码会以哪些方式(如果有)损害安全性?
  2. 在查看有关“证书名称不匹配”的讨论(见SslPolicyErrors.RemoteCertificateNameMismatch上文)后,我们的服务器证书似乎应该包含一个subjectAltName字段来指定允许的 DNS 名称。sslPolicyErrors当我们使用证书固定时,在验证服务器证书时忽略这一点是否必要,或者是否合理?

Jon*_*asH 2

我无法回答你的具体问题,但这里有一些想法:

您还没有提到服务器如何对客户端进行身份验证。因此,您可能会考虑实施诸如客户端证书之类的东西。如果您控制两者,您可能需要某种方法来确保随机攻击者无法连接。

您可以考虑创建威胁模型。在许多情况下,正是您没有考虑到的事情导致了问题。

  • 如果您正在处理国家安全数据或财务数据,您可能需要外部审计。在某些情况下甚至可能需要这样。
  • 如果攻击者无法出售、使用或勒索数据,那么您可能不会成为直接目标。因此,您可能更担心针对已知漏洞的大规模攻击,即保持所有软件最新。
  • 考虑其他降低风险的方法。您的服务器/客户端是否以最低权限运行?您使用的是 DMZ 吗?防火墙配置是否正确?支持等凭证是否管理良好?