Certificate validation errors are a frequent cause of issues when dealing with APIs and Web services calls, especially when self-signed certificates are used.
The error message is usually javax.net.ssl.SSLHandshakeException: PKIX path building failed.

How to Troubleshoot


Run your code with the following system property: javax.net.debug="ssl,handshake,trustmanager". This trace will provide the most relevant output. You may want to save the entire output to a file.

At the top you will the exact trust store file used by the JVM as well as the available certificates:

javax.net.ssl|DEBUG|01|main|2019-07-24 16:11:55.923 EDT|TrustStoreManager.java:112|trustStore is: ./src/test/resources/com/myarch/sec/cryptofiles/app_truststore.pkcs12

Later on, after “ServerHello”, you will see the certificate chain (could be a single certificate too, in the case of a self-signed certificate, it’s always one cert) presented by the server:

javax.net.ssl|DEBUG|01|main|2019-07-24 16:11:56.511 EDT|CertificateMessage.java:358|Consuming server Certificate handshake message (
"Certificates": [
  "certificate" : {
    "version"            : "v3",
    "serial number"      : "00 FD FF 7C 02 1F DF B2 51",
    "signature algorithm": "SHA256withRSA",
    "issuer"             : "CN=Go Daddy Secure Certificate Authority - G2, OU=http://certs.godaddy.com/repository/, O="GoDaddy.com, Inc.", L=Scottsdale, ST=Arizona, C=US",
    "not before"         : "2017-10-27 19:32:01.000 EDT",
    "not  after"         : "2019-10-27 19:32:01.000 EDT",
    "subject"            : "CN=myarch.com, O=My Arch Inc., L=Centreville, ST=Virginia, C=US,

To see an abbreviated list of serial numbers of certificates in the trust store and certificates from the server, run:

cat log_file | grep  "serial\|ServerHello"

The output will look like this

   "serial number"      : "00 86 9A E3 5A 4D FE 72 BA",
javax.net.ssl|DEBUG|01|main|2019-07-24 16:11:56.415 EDT|ServerHello.java:866|Consuming ServerHello handshake message (
"ServerHello": {
javax.net.ssl|DEBUG|01|main|2019-07-24 16:11:56.416 EDT|ServerHello.java:962|Negotiated protocol version: TLSv1.2
    "serial number"      : "00 FD FF 7C 02 1F DF B2 51",
    "serial number"      : "07",
    "serial number"      : "1B E7 15",
    "serial number"      : "00",

One of the certificates (and one of the serials) in the trust store must match one of the certificates from the server.

How to Fix

You have a choice of adding the end entity’s certificate (with the subject’s CN usually matching the domain/hostname) or one of its issuers to the trust store.

Going with the end-entity certificate is more secure, however, you will need to update it when it changes/expires on the server. CA certificates have a much longer validity period, but having the CA cert in the trust store will make ALL of the certificates issued by that CA trusted by default (unless it was revoked by the CA).

The easiest is to obtain the certificates from the server is by using openssl:

openssl s_client -connect myarch.com:443  -showcerts

Copy the certificate that you want to import (starting with “—–BEGIN CERTIFICATE—–” and including “—–END CERTIFICATE—–“) into a file. This gives you a PEM-encoded certificate.

Import the certificate into the truststore:

keytool -import -alias cert_alias -file cert_file -keystore truststore_file

Background

By default, Java uses a separate “truststore” (the same concept as the keystore, only for certificates, it does not contain any private keys), called “cacert”. It is pre-populated with all known CAs (trust anchors). So all certificates issued by Verisign or Digicert will be trusted. As an aside, from the security standpoint, it is much better to use separate application-specific truststore.

You can see all the certificates in the default trustore by running

keytool -list -cacerts -storepass changeit

For a non-default trustore:

keytool -list -v -keystore trustore_file -storepass truststore_password

Certificates issued by internal CAs or self-issued certificates (a.k.a. self-signed) are never trusted by default.

During the TLS handshake a server presents its certificates (usually including the issuer’s certificate) to the client as part of the “ServerHello” message. One of the certificates presented by the server must be in the truststore. This could be the end-entity cert (most secure) or one of its issuers.

The Java client is still going to validate the entire certificate chain, starting with the end-entity certificate, following the Certification path validation algorithm. This includes many additional checks, including the expiration date and the revocation (using OCSP starting with Java 9). If none of the certificate from the chain is in the truststore, the validation will fail in the very beginning with the “PKIX path building failed” exception.