Securing TCP traffic and certificate pinning?

huangapple go评论60阅读模式
英文:

Securing TCP traffic and certificate pinning?

问题

以下是翻译好的部分:

We develop a Java application that serves requests over TCP. We also develop client libraries for the application, where each library supports a different language/platform (Java, .NET, etc.).

直到最近,TCP流量仅限于安全网络。为了支持在不安全网络上的使用,我们使用了TLS,使用了 java-plain-and-tls-socket-examples 中的示例。该示例包含了服务器和客户端的配置以及生成X.509证书的脚本。以下是我们用于进行 仅服务器认证 的TLS 的摘要:

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

这似乎是一个证书固定(certificate pinning)的设置,因为客户端在连接之前拥有服务器证书的副本。显然,当连接时,客户端将以某种方式使用这些数据来验证服务器发送的证书。

我们目前认为这种方法对于保护TCP流量是有效的。因为我们控制服务器和客户端,所以似乎不需要通过证书颁发机构签署证书。

初始测试显示,该实现在我们的Java服务器和Java客户端上运行正常:

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

.NET客户端

我们使用 SslStream 来加密TCP流量。正如文档建议的那样,我们不指定TLS版本;而是如果版本低于1.2,则引发异常。

我们不确定如何正确使用 X509Chain.ChainPolicy.CustomTrustStore,因为文档省略了此类型的用例信息,以及如 X509KeyStorageFlagsX509VerificationFlags 这样的选项类型的信息。

下面的代码旨在模仿上面概述的示例,即为客户端配置一个信任库数据结构,用于在验证服务器证书时使用。这种方法似乎等同于将证书导入操作系统的信任库。

// 导入信任库。
private X509Certificate2Collection GetCertificates(string storePath, string storePassword)
{
    byte[] bytes = File.ReadAllBytes(storePath);

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

// 用于验证从服务器接收的证书的回调函数。
// fCertificates 存储了 GetCertificates 函数的结果。
private bool ValidateServerCertificate(
    object sender,
    X509Certificate certificate,
    X509Chain chain,
    SslPolicyErrors sslPolicyErrors)
{
    // 不允许此客户端与未经身份验证的服务器通信。
    //
    // 对于自签名证书,sslPolicyErrors 应该始终等于 SslPolicyErrors.RemoteCertificateChainErrors。
    var result = (SslPolicyErrors.RemoteCertificateChainErrors == sslPolicyErrors);
    if (result)
    {
        // 下面的值是默认值:将其设置为明确值。
        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;
}

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

    try
    {
        sslStream.AuthenticateAsClient(targetHost);

        // 要求TLS 1.2或更高版本
        if (sslStream.SslProtocol < SslProtocols.Tls12)
        {
            throw new AuthenticationException($"SSL协议 ({sslStream.SslProtocol}) 必须是 {SslProtocols.Tls12} 或更高版本。");
        }
    }
    catch (AuthenticationException caught)
    {
        sslStream.Dispose();
        throw caught;
    }

    return sslStream;
}

初始测试结果因操作系统而异:

  • .NET 6 ASP.NET Web客户端在Ubuntu上的WSL2:

    • 接受有效的服务器证书:
      • 回调函数参数 sslPolicyErrors 等于 SslPolicyErrors.RemoteCertificateChainErrors(符合预期)。
      • 加密TCP数据包(使用 tcpdump 验证)。
      • 自动使用TLS 1.3。
    • 拒绝无效的服务器证书:X509Chain.Build 返回 false,唯一的链状态项是 "UntrustedRoot, self signed certificate"。
  • .NET 6 MAUI客户端在MacOS上:

    1. 拒绝有效的服务器证书:回调函数参数 sslPolicyErrors 包括以下值:
      • SslPolicyErrors.RemoteCertificateNameMismatch(不符合预期)。
      • SslPolicyErrors.RemoteCertificateChainErrors(符合预期)。
    2. 如果我们更改代码以忽略 sslPolicyErrors,则它:
      • 接受有效的服务器证书并自动使用TLS 1.2。
      • 拒绝无效的服务器证书(如上所述)。

英文:

We develop a Java application that serves requests over TCP. We also develop client libraries for the application, where each library supports a different language/platform (Java, .NET, etc.).

Until recently, TCP traffic was confined to a secure network. To support usage on an insecure network we implemented TLS using the recipes in java-plain-and-tls-socket-examples. There are recipes here for both server and client, and a script to generate an X.509 certificate. Below is a summary of the recipe that we are using for TLS with server-only authentication:

  • Create an X.509 root certificate that is self-signed.
  • Configure the server with a keystore file that contains the certificate's identifying data plus the public and private keys.
  • Configure the client with a trust store file that contains the same identifying data plus only the public key.

This looks like a setup for certificate pinning, as the client has a copy of the server certificate before connecting. Apparently, when connecting, the client will use this data somehow to validate the certificate that the server sends.

We assume for now that this approach is valid for securing TCP traffic. Signing by a certificate authority seems unnecessary because we control both server and client.

Initial testing shows that the implementation is working in our Java server and Java client (both running locally):

  • Client accepts a server certificate that matches data in the client's trust store.
  • Client rejects a server certificate that does not matches data in the client's trust store.
  • tcpdump shows that TCP packets contain encrypted data.

.NET Client

We use SslStream to encrypt TCP traffic. As the documentation suggests, we do not specify a TLS version; instead we throw an exception if the version is below 1.2.

We're not confident about how to use X509Chain.ChainPolicy.CustomTrustStore correctly, because the documentation omits information like use cases for this type, and for option types like X509KeyStorageFlags and X509VerificationFlags.

The code below aims to mimic the recipe outlined above, i.e. configure a trust store data structure for the client to use when validating a server certificate. This approach seems equivalent to importing the certificate into the operating system's trust store.

// 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;
}

Initial testing has yielded results that vary depending on the operating system:

  • .NET 6 ASP.NET web client on Ubuntu on WSL2:
    • Accepts a valid server certificate:
      • The callback function argument sslPolicyErrors is equal to SslPolicyErrors.RemoteCertificateChainErrors (expected).
      • Encrypts TCP packets (verified using tcpdump).
      • Automatically uses TLS 1.3.
    • Rejects an invalid server certificate: X509Chain.Build returns false and the only chain status item is "UntrustedRoot, self signed certificate".
  • .NET 6 MAUI client on MacOS:
    1. Rejects a valid server certificate: the callback function argument sslPolicyErrors includes these values:
      • SslPolicyErrors.RemoteCertificateNameMismatch (unexpected).
      • SslPolicyErrors.RemoteCertificateChainErrors (expected).
    2. If we change the code to ignore sslPolicyErrors then it:
      • Accepts a valid server certificate and automatically uses TLS 1.2.
      • Rejects an invalid server certificate (as above).

Questions

  1. In what ways, if any, could security be compromised with this .NET code?
  2. Upon reviewing discussions about "certificate name mismatch" (see SslPolicyErrors.RemoteCertificateNameMismatch above), it seems that our server certificate should include a subjectAltName field to specify allowed DNS names. Is this necessary, or would it be reasonable, as we are using certificate pinning, to ignore sslPolicyErrors when validating the server certificate?

答案1

得分: 2

我无法回答您的具体问题,但以下是一些思考:

您没有提到服务器如何对客户端进行身份验证。因此,您可以考虑实施类似客户端证书的方式。如果您控制双方,可能需要确保随机攻击者无法连接。

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

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

I can't answer your specific questions but here is some thoughts:

You have not mentioned anything about how the server authenticates clients. So you might consider implementing something like client certificates. If you control both you probably want some way to ensure random attackers cannot connect.

You might consider creating a threat model. In many cases it is the things that you haven't thought about that cause problems.

  • If you are handling national security data or financial data you might want an external audit. Such might even be required in some cases.
  • If there is no way an attacker could sell, use, or ransom the data, then you will probably not be directly targeted. So you might worry more about mass attacks against known vulnerabilities, i.e. keep all your software up to date.
  • Consider other ways to mitigate risks. Are your server/client running in least privilege possible? Are you using a DMZ? Are firewalls correctly configured? Are credentials for support etc well managed?

huangapple
  • 本文由 发表于 2023年2月10日 13:49:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/75407392.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定