繁体   English   中英

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

[英]Root Certificate Pinning in C#/.NET

我想在C#应用程序中实现证书/公钥固定。 我已经看到很多解决方案,例如直接在此问题中固定服务器证书。 但是,为了更加灵活,我只想固定根证书。 服务器在设置中获取的证书由中间CA签名,中间CA本身由根签名。

到目前为止,我实现的是一台服务器,该服务器从PKCS#12(.pfx)文件中加载其自己的证书,私钥,中间证书和根证书。 我使用以下命令创建了文件:

openssl pkcs12 -export -inkey privkey.pem -in server_cert.pem -certfile chain.pem -out outfile.pfx

chain.pem文件包含根证书和中间证书。

服务器加载此证书,并希望针对客户端进行身份验证:

// certPath is the path to the .pfx file created before
var 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
)
{
    // ??
}

现在的问题是服务器仅将自己的证书发送给客户端。 同样, chain参数不包含更多信息。 这在某种程度上是合理的,因为X509Certificate2证书 (在服务器代码中)仅包含服务器证书,而没有有关中间或根证书的信息。 但是,客户端(至少)缺少中间证书,因此无法验证整个链。

到目前为止,我没有发现使.NET发送整个证书链的任何可能性,但是我不想固定服务器证书iself或中间证书,因为这破坏了根证书固定的灵活性。

因此,有人知道让SslStream发送整个链进行身份验证或使用另一种方法实现功能的可能性吗? 还是我必须以其他方式打包证书?

谢谢!

编辑:我做了一些其他测试来检测问题。 如评论中所建议,我创建了一个包含所有证书的X509Store 之后,我使用服务器的证书和商店构建了X509Chain 在服务器本身上,新链正确地包含了所有证书,但在ValidateServerCertificate函数中却没有。

SslStream永远不会发送整个链(自发证书除外)。 约定是发送除根以外的所有内容,因为另一端已经拥有并信任根或不具有根(因此/或不信任根),并且这两种方式都浪费带宽。

但是SslStream仅在了解中间物时才能发送中间物。

var cert = new X509Certificate2(certPath, certPass);

这仅提取最终实体证书(带有私钥的证书),并丢弃PFX中的所有其他证书。 如果要加载所有证书,则需要使用X509Certificate2Collection.Import 但是...那也没有帮助。 SslStream仅接受最终实体证书,它期望系统能够为其建立功能链。

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

  • 通过X509Chain.ChainPolicy.ExtraStore作为手动输入提供
    • 由于所讨论的链是由SslStream构建的,因此您不能在这里真正做到这一点。
  • CurrentUser \\我的X509Store
  • *本地机器\\我的X509Store
  • CurrentUser \\ CA X509Store
  • ** LocalMachine \\ CA X509Store
  • CurrentUser \\ Root X509Store
  • ** LocalMachine \\ Root X509Store
  • * LocalMachine \\ ThirdPartyRoot X509Store
  • 证书中的“权限访问标识符”扩展名中标识的http (非s)位置。

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

这也是不是充分,因为(至少对于SslStream在Linux上,可能对.NET核心的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分发端点上可用,并且颁发的证书应带有Authority Information Access扩展名,以便能够动态定位它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM