I'm trying to implement mutual authentication in my Java program. Basically, my program has to make an HTTP request to another server. I need to attach a client certificate to that request and ignore the self-signed certificate of the server.
In short, I'm trying to convert this part of the curl
command into Java:
curl --insecure --key ${USERKEY} --cert ${USERCERT} ...
This is my attempt so far to create a proper OkHttpClient
. The idea was taken from okhttp-tls.
public OkHttpClient getClient()
throws NoSuchAlgorithmException, IOException, InvalidKeySpecException, CertificateException {
String algorithm = "RSA";
// Read public and private key to create a keypair
PrivateKey privateKey = KeyUtils.getPemPrivateKey(PRIVATE_KEY_PATH, algorithm);
PublicKey publicKey = KeyUtils.getPemPublicKey(PUBLIC_KEY_PATH, algorithm);
KeyPair keyPair = new KeyPair(publicKey, privateKey);
// Read the certificate
X509Certificate certificate = KeyUtils.getX509Certificate(CERTIFICATE_PATH);
// Client certificate, used to authenticate the client
HeldCertificate clientCertificate = new HeldCertificate(keyPair, certificate);
// Create handshake certificate (which root certificate/hostname to trust)
HandshakeCertificates.Builder clientCertificatesBuilder = new HandshakeCertificates.Builder()
.addPlatformTrustedCertificates() // trust all certificate which are trusted on the platform
.heldCertificate(clientCertificate); // attach the client certificate
// Do not verify certificate of the host
clientCertificatesBuilder.addInsecureHost(HOST);
HandshakeCertificates clientCertificates = clientCertificatesBuilder.build();
return new OkHttpClient.Builder()
.sslSocketFactory(clientCertificates.sslSocketFactory(), clientCertificates.trustManager())
.hostnameVerifier((hostname, session) -> true) // Do not verify the hostname
.build();
}
KeyUtils
is just my utility class for reading key and certificate files.
The code works fine if the HOST
is a domain name, but it's failed when HOST
is an IP address. Below is part of the stacktrace.
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:324) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:267) ~[na:na]
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:262) ~[na:na]
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:645) ~[na:na]
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:464) ~[na:na]
at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:360) ~[na:na]
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396) ~[na:na]
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444) ~[na:na]
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:422) ~[na:na]
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:181) ~[na:na]
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164) ~[na:na]
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1460) ~[na:na]
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1368) ~[na:na]
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:437) ~[na:na]
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.kt:367) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.RealConnection.establishProtocol(RealConnection.kt:325) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.RealConnection.connect(RealConnection.kt:197) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.ExchangeFinder.findConnection(ExchangeFinder.kt:249) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.ExchangeFinder.findHealthyConnection(ExchangeFinder.kt:108) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.ExchangeFinder.find(ExchangeFinder.kt:76) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.RealCall.initExchange$okhttp(RealCall.kt:245) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:32) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:96) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:100) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:197) ~[okhttp-4.7.2.jar:na]
at okhttp3.internal.connection.RealCall.execute(RealCall.kt:148) ~[okhttp-4.7.2.jar:na]
How can I make it works when the address of the server is an IP, not the domain name?