You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core HttpClient使用ECDsa证书认证失败:服务器返回无证书

更新

经排查,问题出在SslStream(HttpClient基于该组件实现)上。在当前7.xx版本中,客户端发送请求时不会将证书链提交给服务器,这是一个已知问题。

我会保留这篇内容,因为下方的代码可能对他人有帮助(仅使用客户端证书发起请求时不会有问题)。


问题描述

我花费了大量时间排查.NET Core(7.0.100版本,也尝试过6.x版本)原生HttpClient使用基于ECDsa的证书进行客户端证书认证的问题,但始终无法正常运行。(顺带一提,使用相同方法处理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());
}

内容的提问来源于stack exchange,提问作者hbertsch

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 06:35:31