【问题标题】:OkHttp client authentication failing to validate server and send client certificate in the same requestOkHttp 客户端身份验证无法验证服务器并在同一请求中发送客户端证书
【发布时间】:2021-12-03 05:03:21
【问题描述】:

在深入研究这个问题的过程中,我一直在编辑这个问题。

编辑 我能够将我的 OkHttp 客户端构建到它包含 Client.SSLContext.KeyManager 中的客户端证书和 Client.SSLContext.TrustManager 中的受信任证书的位置

// Create keyManagerFactory with keystore.jks
KeyStore clientStore = KeyStore.getInstance(KeyStore.getDefaultType());
clientStore.load(new FileInputStream(new File("keystore.jks")), storePassword.toCharArray());

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientStore, storePassword.toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
        
// Create trustManagerFactory with default cacerts truststore
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
            throw new IllegalStateException("Unexpected default trust managers:"
                                                + Arrays.toString(trustManagers));
        }
trustManager = trustManagers[0];

// Create sslContext from keyManagers (from custom keystore with client key) and default trustManagers
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
sslSocketFactory = sslContext.getSocketFactory();
defaultFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();

okClient = new OkHttpClient
                 .Builder()
                 .sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManager)
                 .build();

但是,我的客户端仍然没有发送我的客户端证书(服务器证书已通过信任库成功验证)。在 ssl 调试日志中获取此信息

No X.509 certificate for client authentication, use empty Certificate message instead

这是我的 SSLContext 在 HttpClient 上的样子。 似乎应该在请求中发送名为“cureskeystore”的客户端证书?

keystore.jks 使用以下命令构建

openssl pkcs12 -export \
        -name curesKeyStore \
        -in clientCert.crt \
        -inkey privateKey.pem \
        -certfile clientCert.crt \
        -out chain.p12 \
        -passout pass:${STORE_PASSWORD}

keytool -importkeystore \
        -srckeystore chain.p12 \
        -srcstoretype pkcs12 \
        -destkeystore keystore.jks \
        -deststoretype pkcs12 \
        -storepass ${STORE_PASSWORD} \
        -srcstorepass ${STORE_PASSWORD} > /dev/null 2>&1

我还尝试使用客户端证书 + -CAfile 使用根证书和中间证书创建商店:

# client cert with CAcerts included
openssl pkcs12 -export -chain \
        -in clientCert.crt \
        -inkey privateKey.pem \
        -out keystore.p12 \
        -name p12KeyStore \
        -CAfile caCerts.crt \
        -caname root \
        -passout pass:${STORE_PASSWORD}

keytool -importkeystore \
        -srcstoretype PKCS12 \
        -destkeystore keystore.jks \
        -srckeystore keystore.p12 \
        -alias p12KeyStore \
        -storepass ${STORE_PASSWORD} \
        -srcstorepass ${STORE_PASSWORD}

另一个可能的问题是 CertificateRequest 与我的客户端证书不匹配。

javax.net.ssl|DEBUG|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:671|Consuming CertificateRequest handshake message (
"CertificateRequest": {
  "certificate types": [ecdsa_sign, rsa_sign, dss_sign]
  "supported signature algorithms": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512, rsa_pkcs1_sha256, rsa_pkcs1_sha384, rsa_pkcs1_sha512, dsa_sha256, ecdsa_sha224, rsa_sha224, dsa_sha224, ecdsa_sha1, rsa_pkcs1_sha1, dsa_sha1]
  "certificate authorities": [redacted, but does not include Entrust]
}
)
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|X509Authentication.java:213|No X.509 cert selected for EC
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:764|Unavailable authentication scheme: ecdsa_secp256r1_sha256
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|X509Authentication.java:213|No X.509 cert selected for EC
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:764|Unavailable authentication scheme: ecdsa_secp384r1_sha384
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|X509Authentication.java:213|No X.509 cert selected for EC
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:764|Unavailable authentication scheme: ecdsa_secp521r1_sha512
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pss_rsae_sha256
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pss_rsae_sha384
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pss_rsae_sha512
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|X509Authentication.java:213|No X.509 cert selected for RSASSA-PSS
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pss_pss_sha256
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|X509Authentication.java:213|No X.509 cert selected for RSASSA-PSS
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.619 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pss_pss_sha384
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for RSASSA-PSS
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pss_pss_sha512
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pkcs1_sha256
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pkcs1_sha384
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pkcs1_sha512
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for DSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: dsa_sha256
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for EC
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: ecdsa_sha224
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_sha224
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for DSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: dsa_sha224
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|X509Authentication.java:213|No X.509 cert selected for EC
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.620 EDT|CertificateRequest.java:764|Unavailable authentication scheme: ecdsa_sha1
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.621 EDT|X509Authentication.java:213|No X.509 cert selected for RSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.621 EDT|CertificateRequest.java:764|Unavailable authentication scheme: rsa_pkcs1_sha1
javax.net.ssl|ALL|24|XNIO-1 task-1|2021-10-18 11:07:18.621 EDT|X509Authentication.java:213|No X.509 cert selected for DSA
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.621 EDT|CertificateRequest.java:764|Unavailable authentication scheme: dsa_sha1
javax.net.ssl|WARNING|24|XNIO-1 task-1|2021-10-18 11:07:18.621 EDT|CertificateRequest.java:774|No available authentication scheme

我的证书的签名算法是SHA256withRSA。这和rsa_pkcs1_sha256不一样吗? 另外,我的客户端证书是由 Entrust 签署的,它没有在服务器的 CertificateRequest 的证书颁发机构中列出。

编辑:我向另一台 HTTPS 服务器发出了一些请求,该服务器在其对客户端的 CertificateRequest 中不包含 certificate authorities。我验证 SSL 可以找到预期的客户端证书并按预期将其发送回服务器。因此,这似乎是服务器请求不包括我的 CA 在他们接受的certificate authorities 列表中的问题。联系服务器请求更新。

【问题讨论】:

  • “他们似乎都与自签名证书有关。” - 他们不是。它们还与配置错误的服务器有关,例如不发送所有中间证书。它们还与发送错误证书的服务器有关,因为客户端不使用 SNI。不幸的是,由于没有提供足够的信息来重现或调试问题,因此不知道您的问题是什么。
  • sslContext.init 采用一组信任管理器。如何先将您的自定义管理器传递给它,然后再传递给默认管理器(具有所有标准证书)?或者,您可以将需要信任的根证书导入您自己的自定义信任库。
  • No X.509 for client auth之前的调试日志中,应该有一个CertificateRequest消息的解码,显示(取决于Java版本)服务器要求的Cert Authorities:certificate_authorities:。确保客户端证书链中证书的颁发者名称与服务器所需的 CA 之一完全匹配。 @ewramner+ JSSE 名义上采用一组密钥和信任管理器,但它只使用给定类型的第一个;此 API 旨在允许 PKIX 以外的身份验证形式,但实际上除了 PKIX 之外没有其他身份验证形式。
  • 我不使用Postman,也不知道是不是用Java写的,如果是,是否使用标准(JSSE)TrustManager实现。如果没有,即使它不应该发送证书,它也可以轻松发送,并且服务器可能能够验证比它要求的证书更多的证书。我不知道 Postman 是否会显示或记录此信息,但如果不是,除非您使用 TLS1.3,否则您可以使用wireshark 或类似工具在外部看到它。您也可以使用不实现名称检查的openssl s_client -cert -key 手动测试。
  • 我还注意到您创建 P12 的命令没有指定任何中间的又名链证书。你确定这个客户证书不需要吗?所有公共 CA 必须至少使用一个,但您没有说是否使用公共 CA;许多私有 CA 也这样做是为了提高根密钥的安全性。如果是这样,服务器可能 询问 根 CA,它发布了(顶部)链证书,该证书应该在您的密钥条目中(不仅仅是在存储中)并发送,但它可能能够重建如果您的客户不发送链本身(这正式违反了 RFC)。

标签: java ssl okhttp


【解决方案1】:

好的;它产生了您的问题是,当服务器请求您的客户端证书/身份验证时,它指定了一个 CA 列表,其中不包括您的证书和链使用的 CA(s?),即使在提供您的证书时也是如此-and-chain 服务器接受它。在对编写包装器 KeyManager 发表评论后,我意识到它很容易测试,并且以下示例适用于我发送与服务器要求的不同的客户端证书。为简单起见,我直接使用 SSLSocket,但使用相同 SSLContext 或 SSLSocketFactory 的任何东西(如 OkHttp)都应该可以工作。针对 OpenSSL 命令行在 8u301 中进行了测试(但如果需要,我可以检查其他一些),这让我可以为 CA X 请求客户端证书,但是当我从 CA Y 提交证书时,它只会记录验证错误而不会中止连接。

public class SO69577136KeyManagerIgnoreCAs  {
    public static void main (String[] args) throws Exception {
        // keystore.p12 pw truststore.p12 pw host port [Y: wrap KM to ignore issuers]
        KeyStore st = KeyStore.getInstance("PKCS12");
        try( InputStream is = new FileInputStream(args[0]) ){ st.load(is,args[1].toCharArray()); }
        KeyManagerFactory kf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kf.init(st,  args[1].toCharArray());
        KeyManager[] km = kf.getKeyManagers();
        try( InputStream is = new FileInputStream(args[2]) ){ st.load(is,args[3].toCharArray()); }
        TrustManagerFactory tf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tf.init(st);
        TrustManager[] tm = tf.getTrustManagers();
        
        if( args.length>6 && args[6].startsWith("Y") ){
            X509ExtendedKeyManager orig = (X509ExtendedKeyManager)km[0]; // exception if wrong type
            km[0] = new X509ExtendedKeyManager(){

                @Override
                public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
                    return orig.chooseClientAlias(keyType, null, socket);
                }

                @Override
                public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
                    // not implemented
                    return null;
                }

                @Override
                public X509Certificate[] getCertificateChain(String alias) {
                    return orig.getCertificateChain(alias);
                }

                @Override
                public String[] getClientAliases(String keyType, Principal[] issuers) {
                    // shouldn't actually be used AFAICT but just in case
                    return orig.getClientAliases(keyType, issuers);
                }

                @Override
                public PrivateKey getPrivateKey(String alias) {
                    return orig.getPrivateKey(alias);
                }

                @Override
                public String[] getServerAliases(String keyType, Principal[] issuers) {
                    // not implemented
                    return null;
                }

                public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
                    return orig.chooseEngineClientAlias(keyType, null, engine);
                    // could just forward to chooseClientAlias(socket=null), that's what underlying does
                }

                public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
                    // not implemented
                    return null;
                }
            };
        }
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(km, tm, null /* default */);
        SSLSocketFactory sf = ctx.getSocketFactory();
        SSLSocket ss = (SSLSocket) sf.createSocket(args[4], Integer.parseInt(args[5]));
        ss.startHandshake();
        System.out.println ("successful");
    }
}

【讨论】:

  • 你是一个真正的英雄。上周我告诉我的团队,我会把我的下一份薪水交给能解决这个问题的人,所以我想我欠你的。这对我来说非常有效,我在这个过程中学到了很多关于 SSL 的知识。感谢您花一些时间来解决这个问题!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-10
  • 2015-03-14
  • 2011-04-09
  • 2013-10-07
  • 2012-08-28
  • 1970-01-01
相关资源
最近更新 更多