使用客户端证书身份验证的HttpClient后请求

时间:2017-07-31 10:29:07

标签: java ssl httpclient client-certificates

一点背景

我试图将测试结果文件发送到eVpT。这是通过GKV服务器完成的:https://verarbeitung.gkv-kommunikationsserver.de/meldung/extra14.meldung

我们对生产对应设备有类似的设置,它会发送到同一个GKV服务器。

解决问题

首先我生成了一个密钥对,如下所示:

keytool -genkeypair -v -alias "TEST" -validity 1095 -keyalg RSA -sigalg SHA256withRSA -keysize 2048 -keystore TEST.keystore -dname "CN=NAME, OU=BN1234, OU=COMPANY NAME, O=ITSG TrustCenter fuer Arbeitgeber, C=DE"

使用生成的密钥库文件,我创建了证书请求:

keytool -certreq -alias "TEST" -file TEST.p10 -sigalg SHA256withRSA -keystore TEST.keystore

这被发送到GKV服务器进行签名,然后返回了我导入上述密钥库文件的.p7c文件:

keytool -import -keystore TEST.keystore -alias TEST -trustcacerts -file TEST.p7c

如果重要的话,密钥库类型是JKS。

请求的设置如下:

public HttpResponse send(String request, String serverUrl, boolean isEvptTest) {
    HttpClient httpClient = new HttpClient();
    return httpClient.post(request, serverUrl, new HttpClient.HttpPostCallback() {
        @Override
        public void configure(DefaultHttpClient defaultHttpClient, HttpPost httpPost) {
            try {
                httpPost.setHeader("Content-Type", "application/octet-stream");
                registerHttpsScheme(defaultHttpClient);
                addProxy(defaultHttpClient);
            } catch (Exception e) {
                throw new RuntimeException("could not register https scheme", e);
            }
        }
    });
}

private void addProxy(DefaultHttpClient httpClient) {
    String proxyHost = System.getProperty(Constants.HTTP_PROXY_HOST_PROPERTY);
    if (!Util.isEmpty(proxyHost)) {
        String proxyPort = System.getProperty(Constants.HTTP_PROXY_PORT_PROPERTY);
        HttpHost proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http");
        httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
        log.debug(String.format("added proxy host %s port %s", proxyHost, proxyPort));
    }
}

private void registerHttpsScheme(DefaultHttpClient httpClient) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
    SSLSocketFactory socketFactory = new SSLSocketFactory(testKeyStore, testKeyStoreService.getKeyStorePassword(), trustStore);
    port = 443;
    Scheme sch = new Scheme("https", port, socketFactory);
    httpClient.getConnectionManager().getSchemeRegistry().register(sch);
}

HttpClient中的post方法:

public HttpResponse post(String content, String url, HttpPostCallback callback) {
    DefaultHttpClient httpClient = null;
    org.apache.http.HttpResponse response = null;
    HttpResponse serverResponse = new HttpResponse();
    try {
        httpClient = new DefaultHttpClient();
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(new StringEntity(content));

        if (callback != null) {
            callback.configure(httpClient, httpPost);
        }

        if (log.isDebugEnabled()) {
            log.debug(format("will post to url[%s] the following content[%s]", url, content));
            Header[] allHeaders = httpPost.getAllHeaders();
            for (Header header : allHeaders) {
                log.debug("header = " + header);
            }
        }

        response = httpClient.execute(httpPost);
        serverResponse.setStatusCode(response.getStatusLine().getStatusCode());
        log.debug(format("server response statuscode[%s]", serverResponse.getStatusCode()));
        String entity = EntityUtils.toString(response.getEntity());
        serverResponse.setResponse(entity);
        if (log.isDebugEnabled()) {
            log.debug(format("received response [%s]", entity));
        }
    } catch (Exception e) {
        log.error(format("error while sending to [%s]", url), e);
        serverResponse.setResponse(e.getMessage());
    } finally {
        consumeContent(httpClient, response);
    }
    return serverResponse;
}

这适用于与GKV服务器的正常交易,但是当我尝试使用测试密钥库时,我得到的是403响应。

我已经使用建议的javax.net.debug标志运行我的程序并得到以下内容(如果需要更多细节的任何部分请告诉我):

found key for : *1234*
chain [0] = [
[
  Version: V3
  Subject: CN=*NAME*, OU=*BN1234*, OU=*COMPANY NAME*, O=ITSG TrustCenter fuer Arbeitgeber, C=DE
  Signature Algorithm: SHA256withRSA, OID = *SOMEID*
...
然后是

列出了所有可信任的证书,包括:

*.gkv-kommunikationsserver.de

当发出实际的http POST请求时:

http-bio-8080-exec-4, setSoTimeout(0) called
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 for TLSv1.1
%% No cached client session
*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1484796190 bytes = { 224, 120, 124, 66, 118, 48, 83, 225, 119, 151, 135, 164, 71, 96, 51, 216, 132, 136, 223, 123, 66, 151, 254, 6, 198, 88, 86, 67 }
Session ID:  {}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA
***

...

http-bio-8080-exec-4, READ: TLSv1.2 Handshake, length = 3043
*** ServerHello, TLSv1.2
RandomCookie:  GMT: 1484796191 bytes = { 74, 209, 14, 128, 123, 109, 178, 53, 206, 74, 209, 82, 66, 241, 252, 13, 234, 164, 2, 171, 46, 102, 203, 234, 224, 28, 213, 70 }
Session ID:  {154, 66, 0, 0, 252, 124, 28, 13, 62, 10, 104, 127, 66, 84, 189, 234, 174, 203, 170, 168, 194, 0, 146, 12, 98, 164, 135, 5, 176, 186, 95, 180}
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: <empty>
***

...

*** Certificate chain
chain [0] = [
[
  Version: V3
  Subject: CN=*.gkv-kommunikationsserver.de, O=ITSG GmbH, OU=Systeme und Netze, L=Heusenstamm, ST=Hessen, C=DE
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

...

***
Found trusted certificate:
[
[
  Version: V3
  Subject: CN=*.gkv-kommunikationsserver.de, O=ITSG GmbH, OU=Systeme und Netze, L=Heusenstamm, ST=Hessen, C=DE
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

...

*** ECDH ServerKeyExchange
Signature Algorithm SHA1withRSA
Server key: Sun EC public key, 521 bits

...

*** ECDHClientKeyExchange
SESSION KEYGEN:
PreMaster Secret:
CONNECTION KEYGEN:
Client Nonce:
Server Nonce:
Master Secret:
Client MAC write Secret:
Server MAC write Secret:
Client write key:
Server write key:
... no IV derived for this protocol
http-bio-8080-exec-4, WRITE: TLSv1.2 Change Cipher Spec, length = 1

... 然后它开始验证看起来很好的数据。然后由于某种原因发出了新的Hello请求:

*** HelloRequest (empty)
%% Client cached [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384]
%% Try resuming [Session-1, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384] from port 62362
*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1484796208 bytes = { 130, 109, 91, 183, 192, 75, 115, 223, 4, 85, 231, 11, 50, 168, 37, 5, 25, 155, 187, 103, 246, 21, 104, 162, 129, 137, 11, 19 }
Session ID:  {154, 66, 0, 0, 252, 124, 28, 13, 62, 10, 104, 127, 66, 84, 189, 234, 174, 203, 170, 168, 194, 0, 146, 12, 98, 164, 135, 5, 176, 186, 95, 180}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA
Extension renegotiation_info, renegotiated_connection: 39:a7:89:65:54:45:31:c8:c1:bc:93:b0
***

...

*** ServerHello, TLSv1.2
RandomCookie:  GMT: 1484796208 bytes = { 104, 194, 244, 49, 210, 153, 252, 205, 241, 25, 177, 239, 212, 114, 206, 70, 186, 6, 152, 31, 243, 128, 145, 3, 86, 42, 208, 145 }
Session ID:  {158, 32, 0, 0, 233, 255, 55, 252, 77, 3, 17, 171, 9, 23, 239, 78, 0, 229, 23, 91, 244, 92, 163, 129, 33, 72, 196, 250, 199, 121, 51, 85}
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Compression Method: 0
Extension renegotiation_info, renegotiated_connection: 39:a7:89:65:54:45:31:c8:c1:bc:93:b0:f1:24:0e:aa:fc:54:f3:7a:1a:79:26:8e
***

...

*** CertificateRequest
Cert Types: RSA, DSS, ECDSA
Supported Signature Algorithms: SHA512withRSA, SHA512withECDSA, SHA256withRSA, SHA384withRSA, SHA1withRSA, SHA256withECDSA, SHA384withECDSA, SHA1withECDSA, SHA1withDSA
Cert Authorities:
<CN=AddTrust External CA Root, OU=AddTrust External TTP Network, O=AddTrust AB, C=SE>
<CN=StartCom Certification Authority, OU=Secure Digital Certificate Signing, O=StartCom Ltd., C=IL>
<CN=VeriSign Class 3 Public Primary Certification Authority - G5, OU="(c) 2006 VeriSign, Inc. - For authorized use only", OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US>
<OU=Class 3 Public Primary Certification Authority, O="VeriSign, Inc.", C=US>
<CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE>
<CN=Baltimore CyberTrust Root, OU=CyberTrust, O=Baltimore, C=IE>
<CN=Microsoft Root Certificate Authority 2010, O=Microsoft Corporation, L=Redmond, ST=Washington, C=US>
<O=Datenaustausch im Gesundheits- und Sozialwesen, C=DE>
<CN=Microsoft Root Certificate Authority 2011, O=Microsoft Corporation, L=Redmond, ST=Washington, C=US>
<CN=Microsoft Root Authority, OU=Microsoft Corporation, OU=Copyright (c) 1997 Microsoft Corp.>
<CN=Microsoft Root Certificate Authority, DC=microsoft, DC=com>
[read] MD5 and SHA1 hashes:  len = 1336

...

*** ServerHelloDone
[read] MD5 and SHA1 hashes:  len = 4
0000: 0E 00 00 00                                        ....
Warning: no suitable certificate found - continuing without client authentication
*** Certificate chain

... 它只是我还是告诉我我的客户端证书还没有添加到服务器信任库?

1 个答案:

答案 0 :(得分:2)

要尝试的事情:

  1. 您信任库中的客户端证书的CA颁发链的根目录?如果没有,请添加,否则它将无法协调颁发证书的位置,也不会选择它。
  2. 您可以使用-Djavax.net.debug=all运行程序。这会将一个公制的信息转储到您的控制台。它将显示作为可信发行者加载的所有证书,握手参数,以及服务器和客户端之间的交互,以确定谁必须在交换中提供什么。
  3. 经过长时间的讨论,我们找到了问题的根源。除了信托商店发行链的根证书外,Ricco几乎掌握了所有必要的东西。

    在TLS握手的机制中,服务器提供了发行机构列表

    *** CertificateRequest
    Cert Types: RSA, DSS, ECDSA
    Supported Signature Algorithms: SHA512withRSA, SHA512withECDSA, SHA256withRSA, SHA384withRSA, SHA1withRSA, SHA256withECDSA, SHA384withECDSA, SHA1withECDSA, SHA1withDSA
    Cert Authorities:
    <CN=AddTrust External CA Root, OU=AddTrust External TTP Network, O=AddTrust AB, C=SE>
    <CN=StartCom Certification Authority, OU=Secure Digital Certificate Signing, O=StartCom Ltd., C=IL>
    <CN=VeriSign Class 3 Public Primary Certification Authority - G5, OU="(c) 2006 VeriSign, Inc. - For authorized use only", OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US>
    <OU=Class 3 Public Primary Certification Authority, O="VeriSign, Inc.", C=US>
    <CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE>
    <CN=Baltimore CyberTrust Root, OU=CyberTrust, O=Baltimore, C=IE>
    <CN=Microsoft Root Certificate Authority 2010, O=Microsoft Corporation, L=Redmond, ST=Washington, C=US>
    <O=Datenaustausch im Gesundheits- und Sozialwesen, C=DE>
    <CN=Microsoft Root Certificate Authority 2011, O=Microsoft Corporation, L=Redmond, ST=Washington, C=US>
    <CN=Microsoft Root Authority, OU=Microsoft Corporation, OU=Copyright (c) 1997 Microsoft Corp.>
    <CN=Microsoft Root Certificate Authority, DC=microsoft, DC=com>
    

    信任引擎将尝试通过它在信任存储中的存在来检查它是否知道其中一个颁发机构。解决之后,它将尝试在密钥库中找到第一个privateKeyEntry,在其链中具有匹配的证书,然后,如果可以恢复私钥,则使用该证书提供认证的后半部分。有关协议握手的详细信息,请参阅RFC 5246

    添加与“&lt; O = Datenaustausch im Gesundheits- und Sozialwesen,C = DE&gt;”相关联的证书后到了信托商店,Ricco取得了成功,并且超越了问题中描述的403错误。

    另请注意,此案例中的服务器配置为“想要”而不是“要求”客户端证书身份验证。在“想要”模式下,如果有可用的话,将显示客户端证书,否则将以正常的TLS连接模式运行。在“require”模式下,需要提供客户端证书才能完成TLS连接。