OkHttp 客户端身份验证无法验证服务器并在同一请求中发送客户端证书

OkHttp client authentication failing to validate server and send client certificate in the same request

我在深入研究时不断编辑这个问题。

编辑 我能够将我的 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。正在联系服务器请求更新。

好的;你的问题是,当服务器请求你的 client-cert/auth 时,它指定了一个不包括你的证书和链使用的 CA 的 CA 列表,即使与你的证书一起出现-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");
    }
}