0

I am trying to consume an API from a 3rd party server. The 3rd party sent me an SSL certificated named certificate.p12 which is the cert file which I use to do the handshake. I have created a custom RestTemplate with SSL as follows:

@Configuration
public class CustomRestTemplate {
    private static final String PASSWORD = "fake_password";
    private static final String RESOURCE_PATH = "keystore/certificate.p12";
    private static final String KEY_TYPE = "PKCS12";

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
        char[] password = PASSWORD.toCharArray();

        SSLContext sslContext = SSLContextBuilder
                .create()
//                .loadKeyMaterial(keyStore(RESOURCE_PATH, password), password)
                .loadKeyMaterial(keyStore(getClass().getClassLoader().getResource(RESOURCE_PATH).getFile(), password), password)
                .loadTrustMaterial(null, new TrustSelfSignedStrategy())
                .build();

        HttpClient client = HttpClients
                .custom()
                .setSSLContext(sslContext)
                .build();

        return builder
                .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(client))
                .build();
    }

    private KeyStore keyStore(String file, char[] password) throws Exception {
        KeyStore keyStore = KeyStore.getInstance(KEY_TYPE);

        File key = ResourceUtils.getFile(file);
        try (InputStream in = new FileInputStream(key)) {
            keyStore.load(in, password);
        }

        return keyStore;
    }
}

I then call the endpoint using the following code:

@Component
@Service
public class TransactionService implements TransactionInterface {
    @Autowired
    private CustomRestTemplate restTemplate = new CustomRestTemplate();

    private static final String BASE_URL = "https://41.x.x.x:xxxx/";

    @Override
    public List<Transaction> getUnsentTransactions(int connectionId) throws Exception {
        HttpEntity<?> httpEntity = new HttpEntity<>(null, new HttpHeaders());

        ResponseEntity<Transaction[]> resp = restTemplate
            .restTemplate(new RestTemplateBuilder())
            .exchange(BASE_URL + "path/end_point/" + connectionId, HttpMethod.GET, httpEntity, Transaction[].class);

        return Arrays.asList(resp.getBody());
    }
}

I get an the following stacktrace when trying to consume the api:

org.springframework.web.client.ResourceAccessException: I/O error on GET request for \"https://41.x.x.x:xxxx/path/endpoint/parameters\": Certificate for <41.x.x.x> doesn't match any of the subject alternative names: [some_name_here, some_name_here];

I do not have much experience with TLS or SSL certificates. I am really stuck at the moment and hoping I can get some help here. The 3rd party provided me with a testing site where I can test the endpoints and after importing the certificate.p12 file into my browser I can reach the endpoints using their testing site but my Springboot application still does not reach the endpoint.

Do I need to copy the cert into a specific folder? This does not seem like the case because I get a FileNotFoundException if I change the path or filename and I get a password incorrect error if I enter the wrong password for the certificate.p12 file. I tried using Postman to test it but Postman returns the same stacktrace as my web application.

Looking at the information above, am I missing something? Is the keystore not being created during runtime? Do I need to bind the certificate to the JVM or my outgoing request?

Any help would be appreciated.

Thanks

Owen Nel
  • 367
  • 3
  • 9
  • 21
  • It appears that 41.x.x.x is not listed your SSL Certificate’s SAN Entry. When you access your app via Browser, check your certificate SAN entries. In browser address bar, Click on Lock Icon -> Certificate -> Details -> Check Subject Alternative Name – Haran Apr 01 '19 at 11:40
  • @Haran When I look at the cert in the browswer under the details it only has a CN, there is no SAN listed at all – Owen Nel Apr 01 '19 at 12:03

1 Answers1

1

It looks like you are trying to connect to a server which doesn't have a valid name in the certificate. For example, if you are connecting to "stackoverflow.com", the certificate needs that domain in the "subject" or the "subject alternative names" field.

Even a testing site should have a valid certificate, but if that's not possible (as it's a third party site and you can't change it yourself), you can disable the verification using this question

Of course, this should only be done for testing.

JPinzon01
  • 116
  • 6
  • Thank you for the responses, I am a little confused (not too experienced with certificates). Should the 3rd party company generate a new certificate file which includes that information or is there something I should do from my side which will then add the `valid name` – Owen Nel Apr 01 '19 at 12:00
  • in the subject there is only a CN no SAN – Owen Nel Apr 01 '19 at 12:04
  • It's not a problem on your side. You are connecting to a server, and both you and the server have a certificate. This is mutual SSL authentication. The server must follow some rules and one of them is that the domain name must be specified a the subject or SAN. If you check the stackoverflow certificate in your browser (the green lock) you will see that it was issued to *.stackexchange.com (not the domain you are connecting to), but one of the SANS is *.stackoverflow.com (which is the right domain). This allows us to use the same certificate in more than one endpoint. – JPinzon01 Apr 01 '19 at 13:01
  • To summarize: The server's certificate does not follow the rules and ignoring the error is the only thing you can do if they don't change it. This happens sometimes with 3rd party testing pages, but it's a bad practice. – JPinzon01 Apr 01 '19 at 13:07
  • I see what you are saying. I will request from them that they fix the cert that they provided us and include the SAN. How do you ignore this error? Do I explicitly need to do it? – Owen Nel Apr 01 '19 at 13:43
  • You must tell java to ignore the error, you can see how in the link I put in the answer. – JPinzon01 Apr 01 '19 at 17:10