为了账号安全,请及时绑定邮箱和手机立即绑定

C#/.NET 中的根证书固定

C#/.NET 中的根证书固定

C#
动漫人物 2022-10-23 15:48:45
我想在我的 C# 应用程序中实现证书/公钥固定。我已经看到了很多直接固定服务器证书的解决方案,例如在这个问题中。但是,为了更灵活,我只想固定根证书。服务器在设置中获得的证书由中间 CA 签名,该 CA 本身由根签名。到目前为止,我实现的是一个服务器,它从 PKCS#12 (.pfx) 文件中加载自己的证书、私钥、中间证书和根证书。我使用以下命令创建了文件:openssl pkcs12 -export -inkey privkey.pem -in server_cert.pem -certfile chain.pem -out outfile.pfxchain.pem文件包含根证书和中间证书。服务器加载此证书并希望对客户端进行身份验证:// certPath is the path to the .pfx file created beforevar cert = new X509Certificate2(certPath, certPass)var clientSocket = Socket.Accept();var sslStream = new SslStream(    new NetworkStream(clientSocket),    false);try {    sslStream.AuthenticateAsServer(cert, false, SslProtocols.Tls12, false);} catch(Exception) {     // Error during authentication}现在,客户端想要对服务器进行身份验证:public void Connect() {    var con = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);    con.Connect(new IPEndPoint(this.address, this.port));    var sslStream = new SslStream(        new NetworkStream(con),        false,        new RemoteCertificateValidationCallback(ValidateServerCertificate),        null    );    sslStream.AuthenticateAsClient("serverCN");}public static bool ValidateServerCertificate(    object sender,    X509Certificate certificate,    X509Chain chain,    SslPolicyErrors sslPolicyErrors){    // ??}现在的问题是服务器只将自己的证书发送给客户端。链参数也不包含更多信息。这在某种程度上是合理的,因为X509Certificate2 证书(在服务器代码中)仅包含服务器证书,没有关于中间证书或根证书的信息。但是,客户端无法验证整个链,因为(至少)缺少中间证书。到目前为止,我还没有发现任何让 .NET 发送整个证书链的可能性,但我不想固定服务器证书本身或中间证书,因为这破坏了根证书固定的灵活性。因此,有没有人知道让 SslStream 发送整个链进行身份验证或使用其他方法实现功能的可能性?还是我必须以不同的方式包装证书?谢谢!编辑:我做了一些其他测试来检测问题。正如评论中所建议的,我创建了一个X509Store包含所有证书的证书。之后,我X509Chain使用我的服务器证书和商店构建了一个。在服务器本身上,新链正确包含所有证书,但不在ValidateServerCertificate函数中。
查看完整描述

1 回答

?
达令说

TA贡献1821条经验 获得超6个赞

SslStream 永远不会发送整个链(自我颁发的证书除外)。约定是发送除根以外的所有内容,因为另一方要么已经拥有并信任根,要么没有(因此/或不信任根),无论哪种方式都是浪费带宽。

但是 SslStream 只有在理解中间体时才能发送中间体。

var cert = new X509Certificate2(certPath, certPass);

这只会提取最终实体证书(带有私钥的证书),它会丢弃 PFX 中的任何其他证书。如果要加载需要使用的所有证书X509Certificate2Collection.Import。但是......这也对你没有帮助。SslStream 只接受最终实体证书,它希望系统能够为它构建一个功能链。

为了构建功能链,您的中间证书和根证书需要位于以下任一位置:

  • 通过 X509Chain.ChainPolicy.ExtraStore 作为手动输入提供

    • 由于有问题的链是由 SslStream 构建的,因此您不能在这里真正做到这一点。

  • 当前用户\我的 X509Store

  • *LocalMachine\My X509Store

  • CurrentUser\CA X509Store

  • **LocalMachine\CA X509Store

  • CurrentUser\Root X509Store

  • **LocalMachine\Root X509Store

  • *LocalMachine\ThirdPartyRoot X509Store

  • http在证书的授权访问标识符扩展中标识的(非 s)位置。

*在 Linux 上的 .NET Core 上不存在标记为的商店。标记 的存储**在 Linux 上确实存在,但不能由 .NET 应用程序修改。

这还不够,因为(至少对于 Linux 上的 SslStream 和 .NET Core 上的 macOS 而言)它仍然只在构建它信任的链时才发送中间体。因此,服务器需要真正信任根证书才能发送中间证书。(或者客户端需要信任客户端证书的根)


另一方面,同样的规则也适用。不同之处在于,在回调中您可以选择重建链以添加额外的证书。

private static bool IsExpectedRootPin(X509Chain chain)

{

    X509Certificate2 lastCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;

    return lastCert.RawBytes.SequenceEquals(s_pinnedRootBytes);

}


private static bool ValidateServerCertificate(

    object sender,

    X509Certificate certificate,

    X509Chain chain,

    SslPolicyErrors sslPolicyErrors

)

{

    if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)

    {

        // No cert, or name mismatch (or any future errors)

        return false;

    }


    if (IsExpectedRootPin(chain))

    {

        return true;

    }


    chain.ChainPolicy.ExtraStore.Add(s_intermediateCert);

    chain.ChainPolicy.ExtraStore.Add(s_pinnedRoot);

    chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;


    if (chain.Build(chain.ChainElements[0].Certificate))

    {

        return IsExpectedRootPin(chain);

    }


    return false;

}

当然,这种方法的问题是你还需要了解并提供远程端的中间件。真正的解决方案是中间体应该在 HTTP 分发端点上可用,并且颁发的证书应该带有授权信息访问扩展,以便能够动态定位它们。


查看完整回答
反对 回复 2022-10-23
  • 1 回答
  • 0 关注
  • 87 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信