【发布时间】:2022-12-30 20:59:07
【问题描述】:
更新
事实证明,SslStream(HttpClient 所基于的)存在问题。在当前版本 (7.xx) 中,证书链在通过客户端发送时不会提交给服务器。这是一个已知问题并讨论了 here 和 here。
我会把这篇文章留在网上,因为下面的代码可能对其他人有帮助(如果你只想在你的请求中使用客户端证书,它不会造成问题)。
我花了很多时间试图找到我们使用基于ECDsa 的证书和 .Net Core 的本地HttpClient 的客户端证书身份验证有什么问题(版本 7.0.100 但也尝试过 v.6xxx)但是从来没有得到这个东西在跑(顺便说一句,我对基于 RSA 的客户端证书使用了相同的方法,没有任何问题)。
出于安全原因,我必须使用 ECDsa 客户端证书 + 链。
我不明白或找不到信息为什么这不起作用/不支持,结果让我感到困惑。
加载证书和密钥并使用它们对某些数据进行签名和验证时,所有测试均通过(见代码)。最后,所需的客户端证书没有发送到服务器,导致 SSL 异常(使用 Python 脚本测试了相同的证书以验证它们是否正确,我遇到了零问题)。
我无法想象(至少我希望不会)不支持这些类型的客户端证书。对于替代解决方法的任何帮助或提示,我将不胜感激。在这一点上切换到一些不同的语言将是非常糟糕的:/
参考代码
包含一个测试证书和可以使用的密钥。此示例中缺少链,可以简单地附加到证书字符串部分测试摘自:Use X509Certificate2 to sign and validate ECDSA-SHA256 signatures
[Test]
// Just some test to better understand whether the certificate and key is working and belong together
public void TestEcdsaFunctionality()
{
var ecdsaCertificate = @"-----BEGIN CERTIFICATE-----
MIIBgzCCASoCCQD+iUUbrH+BOzAKBggqhkjOPQQDAjBKMQswCQYDVQQGEwJQRDEL
MAkGA1UECAwCQlcxEDAOBgNVBAcMB1BhbmRvcmExDzANBgNVBAoMBkFueU9yZzEL
MAkGA1UEAwwCNDIwHhcNMjIxMTIxMTE0MjExWhcNMjMxMTIxMTE0MjExWjBKMQsw
CQYDVQQGEwJQRDELMAkGA1UECAwCQlcxEDAOBgNVBAcMB1BhbmRvcmExDzANBgNV
BAoMBkFueU9yZzELMAkGA1UEAwwCNDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC
AAT6vBU2iIcESep8UeQhfNFgfTArFYvtb2Pmlbk1+R9gdNaWEg1UK7dlt3/mH/X3
Mrg80JaTY3OPM92MY9e9gs7ZMAoGCCqGSM49BAMCA0cAMEQCIA3p2mMOYqGEzReY
br7nYLsLdF0+dV6iZSZaG1iMHwblAiA5UaJlVr5CsCkG+j1ZJEICSOnVMyx4DjA5
oZuoMYa42w==
-----END CERTIFICATE-----";
var ecdsaPrivateKey = @"MDECAQEEIM6BExC2G7P1KpViQmZ/Z65nukv8yQmvw6PqGGQcKn9boAoGCCqGSM49
AwEH";
var cert = X509Certificate2.CreateFromPem(ecdsaCertificate.ToCharArray());
var key = ECDsa.Create("ECDsa");
var keybytes = Convert.FromBase64String(ecdsaPrivateKey);
key.ImportECPrivateKey(keybytes, out _);
var helloBytes = Encoding.UTF8.GetBytes("Hello World");
// Sign data with the ECDsa key
var signed = key.SignData(helloBytes, 0, helloBytes.Count(), HashAlgorithmName.SHA256);
// Verify the data signature with the certificates public key
var verified = cert.GetECDsaPublicKey().VerifyData(helloBytes, signed, HashAlgorithmName.SHA256);
// Assume that everything went well and the data signature is valid
Assert.That(verified, Is.EqualTo(true));
// Additional tests with the X509Certificate2 object type
X509Certificate2 keyCert = ECDsaCertificateExtensions.CopyWithPrivateKey(cert, key);
// Sing using the certificate that contains the private key
using (ECDsa ecdsa = keyCert.GetECDsaPrivateKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must have an ECDSA private key", nameof(cert));
signed = ecdsa.SignData(helloBytes, HashAlgorithmName.SHA256);
}
// Verify signed data using the certificate that contains the private key
using (ECDsa ecdsa = keyCert.GetECDsaPublicKey())
{
if (ecdsa == null)
throw new ArgumentException("Cert must be an ECDSA cert", nameof(cert));
Assert.That(ecdsa.VerifyData(helloBytes, signed, HashAlgorithmName.SHA256), Is.EqualTo(true));
}
WorkshopRegistration(keyCert);
}
// This would be what I really want to use the client certificate for
private void WorkshopRegistration(X509Certificate2 clientCert)
{
var payload = "{somepayload}";
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificates.Add(clientCert);
handler.ServerCertificateCustomValidationCallback =
(httpRequestMessage, cert, cetChain, policyErrors) =>
{
return true;
};
var content = new StringContent(payload, System.Text.Encoding.UTF8, "application/json");
var client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Accept", "application/json");
var result = client.PutAsync("https://someHostname.com/registration",
content).GetAwaiter().GetResult();
if (result.StatusCode != HttpStatusCode.OK)
throw new SuccessException("Registration failed with conent: " + result.Content.ToString());
}
【问题讨论】:
-
你能提供失败的代码并指出代码失败的那一行吗?
-
顺便说一句,您仅在访问
cert.PrivateKeygetter 时收到异常,它已过时并且仅支持旧版 RSA/DSA 密钥,因此预计会出现此异常。 -
好吧,代码本身并没有失败。只有 HttpClient 没有按预期发送客户端证书。我在上面描绘的异常仅在您调试代码并尝试在加载私钥后检查
keyCert时才可见。 -
您好 Crypt32,感谢您指出这一点。这解释了调试器引发此异常的原因,并解释了我为何对签名和验证工作感到困惑(因为这需要 X509Certificate2 加载证书和密钥)。除此之外,我只能猜测我使用的证书不正确(?),因为服务器接受了相同的证书,当通过
curl或python发送时
标签: c# ssl x509certificate dotnet-httpclient ecdsa