0
votes

I have a SpringBoot app that must call a REST API which requires a certificate. I was provided 2 files from the service that propose this REST Service : a P12 file and a CA Root file.

I first created a keystore (JKS) :

keytool -keystore keystore.jks -genkey -alias client

Then I added a CA root to the JKS file :

keytool -keystore keystore.jks -import -file certeurope_root_ca_3.cer -alias cacert

Now in my app I have to call the rest API :

public DocumentDto sendRequest(DocumentDto documentDto) throws Exception {

    // Set variables
    String ts = "C:\\keystore\\keystore.jks";
    String ks = "C:\\keystore\\CERTIFICATE.p12";

    String tsPassword = properties.getProperty("signature.api.passphrase");
    String ksPassword = properties.getProperty("signature.api.passphrase");

    KeyStore clientStore = KeyStore.getInstance("PKCS12");
    clientStore.load(new FileInputStream(ks), ksPassword.toCharArray());
    log.warn("# clientStore : " + clientStore);
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(clientStore, ksPassword.toCharArray());
    KeyManager[] kms = kmf.getKeyManagers();

    KeyStore trustStore = KeyStore.getInstance("JKS");
    trustStore.load(new FileInputStream(ts), tsPassword.toCharArray());

    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    tmf.init(trustStore);
    TrustManager[] tms = tmf.getTrustManagers();

    SSLContext sslContext = null;
    sslContext = SSLContext.getInstance("TLS");
    sslContext.init(kms, tms, new SecureRandom());

    // set the URL to send the request
    HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
    URL url = new URL(properties.getProperty("signature.api.url.full"));

    // opening the connection
    HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();

    // Create all-trusting host name verifier
    HostnameVerifier allHostsValid = new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) { return true; }
    };

    // Install the all-trusting host verifier
    HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
    HttpsURLConnection.setDefaultAllowUserInteraction( true );
    HttpsURLConnection.setFollowRedirects( false );
    HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

    urlConnection.setRequestMethod("POST");
    urlConnection.setDoOutput(true);
    urlConnection.setDoInput(true);
    urlConnection.setUseCaches(false);
    urlConnection.setAllowUserInteraction(true);
    urlConnection.setReadTimeout(15000);

    // create the JSON String
    ObjectMapper mapper = new ObjectMapper();
    // convert an oject to a json string
    String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(documentDto);

    InputStreamReader isr=null;
    try(OutputStream os = urlConnection.getOutputStream()) {
        byte[] input = jsonInString.getBytes(StandardCharsets.UTF_8);
        os.write(input, 0, input.length);
        // check 400 & 403
        if(urlConnection.getResponseCode() == 400 || urlConnection.getResponseCode() == 403) {
            isr = new InputStreamReader(urlConnection.getErrorStream(), StandardCharsets.UTF_8);
            String st= IOUtils.toString(isr);

            log.warn("# errorStream :" + st );
        } else if(urlConnection.getResponseCode() != 200) {
            isr = new InputStreamReader(urlConnection.getErrorStream(), StandardCharsets.UTF_8);
            String st= IOUtils.toString(isr);
        } else {
            isr = new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8);
        }
    }

    // read the response
    try(BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8))) {
        StringBuilder response = new StringBuilder();
        String responseLine = null;
        while ((responseLine = br.readLine()) != null) {
            response.append(responseLine.trim());
        }
        System.out.println(response.toString());
    }

    System.out.println(jsonInString);
    return documentDto;
}

I also changed my port server : server.port=8443. I have 2 issues : If i have : TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

I obtain : javax.net.ssl.SSLHandshakeException: No trusted certificate found

If I have : TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

I obtain : javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

I'm stuck on that stuff for a while and I don't see what's going wrong.

1
Your code looks mostly good to me; your nobbling of default HostnameVerifier is too late to affect your connection, but your problem isn't in hostname verification; user interaction on an API would be weird; and creating a dummy keypair in your truststore is useless but not harmful. You should look at the certs (i.e. chain) actually used by the server you're connecting to and compare to the claimed root they gave you. There are many ways to do this but an easy one is keytool -printcert -sslserver $host[:$port] and keytool -printcert -file certeurope_root_ca_3.cer. - dave_thompson_085
I looked at the certs used by the server and the ones provided and it was matching. I finally found out a solution without CA and so on. thanks - davidvera

1 Answers

0
votes

Well i found out a solution which may not be the most elegant at all. But at least it work. I also made some refactor...

public DocumentCreateRequestDto sendRequest(DocumentDto documentDto) throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {

    // Set variables
    String certificate = properties.getProperty("signature.api.certificate");
    String PwdPk12 = properties.getProperty("signature.api.passphrase");
    String httpsRestUrl = properties.getProperty("signature.api.url.full");

    HttpsURLConnection con = getHttpsURLConnection(certificate, PwdPk12, httpsRestUrl);

    // create the JSON String
    ObjectMapper mapper = new ObjectMapper();
    // convert an oject to a json string

    String jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(documentDto);
    jsonInString = "[" + jsonInString + "]";
    StringBuilder response = getStringBuilder(con, jsonInString);

    String output = response.toString();
    output = output.substring(1, output.length()-1);

    return mapper.readValue(output, DocumentCreateRequestDto.class);
}



private HttpsURLConnection getHttpsURLConnection(String certificate, String pwdPk12, String httpsRestUrl) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException {

    KeyStore ks = KeyStore.getInstance("PKCS12");
    ks.load(new FileInputStream(certificate), pwdPk12.toCharArray());

    TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    // return new X509Certificate[0];
                    return null;
                }
            }
    };

    SSLContext ctx = SSLContext.getInstance("TLS");

    // Create all-trusting host name verifier
    HostnameVerifier allHostsValid = new HostnameVerifier() {
        public boolean verify(String hostname, SSLSession session) { return true; }
    };


    KeyManagerFactory kmf = KeyManagerFactory.getInstance( "SunX509" );
    kmf.init( ks, pwdPk12.toCharArray() );
    ctx.init( kmf.getKeyManagers(), trustAllCerts, new SecureRandom() );

    // Install the all-trusting host verifier
    HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
    HttpsURLConnection.setDefaultAllowUserInteraction( true );
    HttpsURLConnection.setFollowRedirects( false );
    HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory());


    URL url = new URL(httpsRestUrl);
    HttpsURLConnection con = (HttpsURLConnection) url.openConnection();

    //connection
    con.setRequestMethod("POST");
    con.setDoOutput(true);
    con.setRequestProperty("Accept-Encoding", "gzip,deflate");
    con.setRequestProperty("Content-Type", "application/json");
    con.setRequestProperty("Connection", "Keep-Alive");
    con.setRequestProperty("User-Agent", "Apache-HttpClient/4.1.1 (java 1.5)");
    con.setReadTimeout(15000);
    con.setDoInput(true);
    con.setUseCaches(false);
    con.setAllowUserInteraction(true);
    return con;
}

And :

private StringBuilder getStringBuilder(HttpsURLConnection con, String jsonInString) throws IOException {
    InputStreamReader isr = null;
    try (OutputStream os = con.getOutputStream()) {
        byte[] input = jsonInString.getBytes(StandardCharsets.UTF_8);
        os.write(input, 0, input.length);
        // check 400 & 403
        if (con.getResponseCode() == 400 || con.getResponseCode() == 403) {
            isr = new InputStreamReader(con.getErrorStream(), StandardCharsets.UTF_8);
            String st = IOUtils.toString(isr);

            log.warn("# errorStream :" + st);
        } else if (con.getResponseCode() != 200) {
            isr = new InputStreamReader(con.getErrorStream(), StandardCharsets.UTF_8);
        } else {
            isr = new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8);
        }
    }

    // read the response
    String responseLine;
    StringBuilder response = new StringBuilder();
    try (BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
        while ((responseLine = br.readLine()) != null) {
            response.append(responseLine.trim());
        }
    }
    return response;
}