0

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?

Triet Doan
  • 11,455
  • 8
  • 36
  • 69

0 Answers0