强制HttpWebRequest发送客户端证书

14

我有一个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服务器将拒绝客户端证书。 - sly
是的,我忘了提到如果我将证书添加到用户证书中并尝试使用Internet Explorer连接,则连接会起作用。 - Perry
我使用一个包含私钥的PKCS12 (.pfx)证书测试了您的代码,测试成功。 - sly
我的是一个p12文件。 你能够使用证书连接吗?它安装在用户证书密钥集或计算机密钥集中?(我使用的是意大利语的Windows,不确定术语是否正确) - Perry
是的,我能够使用当前用户存储中安装的证书进行连接。由于它在IE中有效,请尝试从IE中导出带有私钥的证书到.pfx 导出证书(Windows),然后从您的代码中引用该导出的.pfx文件。 - sly
显示剩余2条评论
4个回答

31

我解决了问题,关键是P12文件(类似于PFX)包含多个证书,因此必须以这种方式加载:

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" - yaronkl
在另一台机器上使用证书时,会出现密码错误的错误,但在原机器上却可以正常工作。有任何想法是为什么会发生这种情况吗? - Scott Wilson

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服务器比其他服务器更严格... - Perry

2

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

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

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

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)。
有两个解决方案:
  1. 切换到.NET Framework
  2. 生成一个新的自签名证书,其用途/增强型密钥用途=客户端证书。然后代码在Core中也能正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接