强制 HttpWebRequest 发送客户端证书

Posted

技术标签:

【中文标题】强制 HttpWebRequest 发送客户端证书【英文标题】:Force HttpWebRequest to send client certificate 【发布时间】:2017-01-24 12:49:30 【问题描述】:

我有一个 p12 证书,我以这种方式加载它:

X509Certificate2 certificate = new X509Certificate2(certName, password,
        X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet |
        X509KeyStorageFlags.Exportable);

它被正确加载,事实上如果我这样做certificate.PrivateKey.ToXmlString(true); 它会返回一个完整的 xml 而没有错误。 但如果我这样做:

try

    X509Chain chain = new X509Chain();
    var chainBuilt = chain.Build(certificate);
    Console.WriteLine("Chain building status: "+ chainBuilt);

    if (chainBuilt == false)
        foreach (X509ChainStatus chainStatus in chain.ChainStatus)
            Console.WriteLine("Chain error: "+ chainStatus.Status);

catch (Exception ex)

    Console.WriteLine(ex);

它写道:

Chain building status: False
Chain error: RevocationStatusUnknown 
Chain error: OfflineRevocation 

所以当我这样做时:

        ServicePointManager.CheckCertificateRevocationList = false;
    ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true;
    ServicePointManager.Expect100Continue = true;
    Console.WriteLine("connessione a:" + host);
    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(host);
    req.PreAuthenticate = true;
    req.AllowAutoRedirect = true;
    req.ClientCertificates.Add(certificate);
    req.Method = "POST";
    req.ContentType = "application/x-www-form-urlencoded";
    string postData = "login-form-type=cert";
    byte[] postBytes = Encoding.UTF8.GetBytes(postData);
    req.ContentLength = postBytes.Length;
    Stream postStream = req.GetRequestStream();
    postStream.Write(postBytes, 0, postBytes.Length);
    postStream.Flush();
    postStream.Close();

    WebResponse resp = req.GetResponse();

服务器说证书未发送/无效。

我的问题是:

即使链构建错误,我如何发送证书? 还有另一个类发布证书,在发送之前不检查证书验证?

非常感谢。 安东尼诺

【问题讨论】:

Web 服务器是否信任您的客户端证书的颁发者?否则,Web 服务器将拒绝客户端证书。 是的,我忘了提到,如果我将证书添加到用户证书并尝试与 Internet Explorer 连接,则连接正常。 我使用包含私钥的 PKCS12 (.pfx) 证书测试了您的代码,结果成功 我的是 p12 文件。你能连接证书吗?它安装在用户证书密钥集中还是计算机密钥集中? (我有意大利语的窗口,我不确定条款) 是的,我能够连接到当前用户存储中安装的证书。由于它在 IE 中工作,请尝试将带有私钥的证书从 IE 导出到 .pfx Export Certificates (Windows),然后从您的代码中引用导出的 .pfx 文件。 【参考方案1】:

我正在尝试使用自签名证书,其唯一目的是“服务器证书”作为客户端证书,使用以下代码:

        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("https://serverurl.com/test/certauth");
        X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        store.Open(OpenFlags.ReadWrite);
        var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, "a909502dd82ae41433e6f83886b00d4277a32a7b", true)[0];
        request.ClientCertificates.Add(certificate);
        HttpWebResponse response = (HttpWebResponse)request.GetResponse();

我了解到 .NET Framework 将接受这样的证书作为客户端证书,但 .NET Core 不会(请参阅https://github.com/dotnet/runtime/issues/26531)。

有两种解决方案:

    切换到 .NET 框架 生成新的自签名证书,其用途/增强密钥用法 = 客户端证书。然后代码也可以在 Core 中运行。

【讨论】:

【参考方案2】:

我解决了问题,关键是P12文件(作为PFX)包含超过1个证书,因此必须以这种方式加载:

X509Certificate2Collection certificates = new X509Certificate2Collection();
certificates.Import(certName, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

并以这种方式添加到HttpWebRequest:request.ClientCertificates = certificates;

感谢大家的支持。

完整的示例代码

string host = @"https://localhost/";
string certName = @"C:\temp\cert.pfx";
string password = @"password";

try

    X509Certificate2Collection certificates = new X509Certificate2Collection();
    certificates.Import(certName, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);

    ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true;
    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(host);
    req.AllowAutoRedirect = true;
    req.ClientCertificates = certificates;
    req.Method = "POST";
    req.ContentType = "application/x-www-form-urlencoded";
    string postData = "login-form-type=cert";
    byte[] postBytes = Encoding.UTF8.GetBytes(postData);
    req.ContentLength = postBytes.Length;

    Stream postStream = req.GetRequestStream();
    postStream.Write(postBytes, 0, postBytes.Length);
    postStream.Flush();
    postStream.Close();
    WebResponse resp = req.GetResponse();

    Stream stream = resp.GetResponseStream();
    using (StreamReader reader = new StreamReader(stream))
    
        string line = reader.ReadLine();
        while (line != null)
        
            Console.WriteLine(line);
            line = reader.ReadLine();
        
    

    stream.Close();

catch(Exception e)

    Console.WriteLine(e);

【讨论】:

谢谢你。我帮了我很多。有没有办法从证书存储中添加证书,而不是指定实际文件?我曾经使用 WINHTTP.DLL 执行此操作,使用这行代码:" m_ServerObj.SetClientCertificate "LOCAL_MACHINE\My\certname"【参考方案3】:

我使用您的代码的修改版本创建了一个命令行程序,其中包含从 IE 导出的私钥的 pfx 证书,并且我能够对安全网站进行身份验证并检索受保护的页面:

string host = @"https://localhost/";
string certName = @"C:\temp\cert.pfx";
string password = @"password";

try

    X509Certificate2 certificate = new X509Certificate2(certName, password);

    ServicePointManager.CheckCertificateRevocationList = false;
    ServicePointManager.ServerCertificateValidationCallback = (a, b, c, d) => true;
    ServicePointManager.Expect100Continue = true;
    HttpWebRequest req = (HttpWebRequest)WebRequest.Create(host);
    req.PreAuthenticate = true;
    req.AllowAutoRedirect = true;
    req.ClientCertificates.Add(certificate);
    req.Method = "POST";
    req.ContentType = "application/x-www-form-urlencoded";
    string postData = "login-form-type=cert";
    byte[] postBytes = Encoding.UTF8.GetBytes(postData);
    req.ContentLength = postBytes.Length;

    Stream postStream = req.GetRequestStream();
    postStream.Write(postBytes, 0, postBytes.Length);
    postStream.Flush();
    postStream.Close();
    WebResponse resp = req.GetResponse();

    Stream stream = resp.GetResponseStream();
    using (StreamReader reader = new StreamReader(stream))
    
        string line = reader.ReadLine();
        while (line != null)
        
            Console.WriteLine(line);
            line = reader.ReadLine();
        
    

    stream.Close();

catch(Exception e)

    Console.WriteLine(e);

【讨论】:

这段代码和我的差不多,但是它不起作用,我不明白为什么......我尝试添加日志,并且发送了证书。所以也许 IBM 服务器比其他服务器更严格......【参考方案4】:

问题是您将私钥安装到机器存储中,通常不允许将私钥用于不在本地系统帐户下运行或具有显式私钥权限的进程的客户端身份验证。您需要在当前用户存储中安装密钥:

X509Certificate2 certificate = new X509Certificate2(certName, password,
        X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet |
        X509KeyStorageFlags.Exportable);

【讨论】:

我不确定是否可以在 IIS 用户中安装证书,但继续设置计算机证书,在证书上按右-单击并所有活动管理私钥,并添加IIS_USER完成控制和读取。 但这是不正确的。您确实应该在用户存储中安装客户端身份验证证书。 我尝试使用安装在当前用户存储中的证书,并使用 UserKeySet 标志,问题是 Internet Explorer 成功访问,我的程序没有:(

以上是关于强制 HttpWebRequest 发送客户端证书的主要内容,如果未能解决你的问题,请参考以下文章

强制 Chrome 在 TLS 期间发送链中的所有证书

强制 SSL 客户端套接字发送证书

强制Chrome在TLS期间发送链中的所有证书

即使没有 CA 匹配,也强制 ASP.NET WebAPI 客户端发送客户端证书

后端向服务器发送客户端请求--HttpWebRequest

如何向 WebClient (C#) 添加证书?