Unable to find valid certification path to requested target

A few weeks ago, I upgraded my laptop. Due to a bug in the latest OS/X, I wasn't able to transfer all of my files from my old computer to the new one, but since everything I do is in Subversion anyway, I didn't anticipate a major issue just reinstalling everything I needed. When it came time to install Java, I installed the latest JDK (1.8). Thinking little of it, I went back to my normal work, ran Maven, and immediately got the following stack trace:

[ERROR] Failed to execute goal on project reports: Could not resolve dependencies 
for project com.xxoffice.reporting:reports:war:1.0-SNAPSHOT: Failed to collect 
dependencies at com.qoppa.pdf:jPDFAssemble:jar:1.0: Failed to read artifact 
descriptor for com.qoppa.pdf:jPDFAssemble:jar:1.0: Could not transfer artifact 
com.qoppa.pdf:jPDFAssemble:pom:1.0 from/to xxoffice (https://maven.2xoffice.com/m2/repository): 
sun.security.validator.ValidatorException: PKIX path building failed: 
unable to find valid certification path to requested target -> [Help 1]
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1341)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:153)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:868)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:804)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
Whoa, what the heck was that? Scrolling up a bit, I saw that it was failing while trying to connect to my local Maven repository that I maintain for internal build artifacts. So, why would JDK 1.8 not be able to connect to my internal repository?

Fortunately for me, I know a thing or two about PKI and certificate hierarchies, so the error message wasn't complete gibberish. To make sense of the error and what it's actually complaining about, it's necessary to understand a little bit about how SSL works. The server in question — and any server which connecting to might result in the error message above — is protected by HTTPS, which is HTTP with SSL added on. SSL was designed to protect against a lot of different security problems, one of the most complex of which is the man-in-the-middle attack. For various reasons, internet traffic is particularly susceptible to active attacks whereby a malicious party pretends to be the server you're trying to talk to and intercepts your communications. To guard against this, SSL mandates (at the risk of slightly oversimplifying) that the server present a certificate that identifies it as the true bearer of the hostname you're trying to connect to; maven.2xoffice.com in this case. Of course, in and of itself, this provision is next to useless — after all, any attacker who can intercept your communications and masquerade as the target server can just as easily forge a certificate claiming to be the correct server. As it turns out, this was a well-studied problem in cryptography circles in the mid 90's when SSL was designed — the solution is a Public Key Infrastructure (PKI). In this system, a handful of trusted parties are authorized to digitally sign certificates — in effect, "vouching for" the legitimacy of the bearer of the certificate. Such trusted parties are called certificate authorities.

With this brief background, the error message "unable to find valid certification path to requested target" begins to makes some sense — what Java (by way of Maven) is trying to tell me is that the server presented a certificate, and the certificate did identify itself as the rightful bearer of the hostname maven.2xoffice.com. Furthermore, the certificate was properly digitally signed — unfortunately, not by a legitimate certificate authority.

So, what makes a certificate authority a "legitimate" one? Every SSL-capable client has its own answer; browsers, for example, have a list of trusted certificate authorities. They're identified by their own certificates (more specifically, by the secure hash of their certificate's contents). In Chrome, for instance, you can see a list of trusted certificate authorities' certificates by going into Settings->Manage Certificates (Figure 1) and seeing a list of several trusted "root" certificates (Figure 2). Any certificate signed by one of these roots will be trusted; untrusted ones will result in a warning message.

Figure 1: Manage Certificate Settings

Figure 2: Trusted Certificates

Java, on the other hand, doesn't have a "Settings" tab; instead, it has a setup folder. Specifically, $JRE_HOME/lib/security. Here, there's a file named cacerts that lists all of the trusted root certificate authorities. You can view this list using the keytool that comes with the JDK:

$ keytool -list -keystore $JRE_HOMe/lib/security/cacerts 
Enter keystore password:  

Keystore type: JKS
Keystore provider: SUN

Your keystore contains 85 entries

digicertassuredidrootca, Apr 16, 2008, trustedCertEntry, 
Certificate fingerprint (SHA1): 05:63:B8:63:0D:62:D7:5A:BB:C8:AB:1E:4B:DF:B5:A8:99:B2:4D:43
trustcenterclass2caii, Apr 29, 2008, trustedCertEntry, 
Certificate fingerprint (SHA1): AE:50:83:ED:7C:F4:5C:BC:8F:61:C6:21:FE:68:5D:79:42:21:15:6E
thawtepremiumserverca, Dec 11, 2009, trustedCertEntry, 
Certificate fingerprint (SHA1): E0:AB:05:94:20:72:54:93:05:60:62:02:36:70:F7:CD:2E:FC:66:66
Note, in particular, the "Certificate fingerprint". This is the (hopefully) unforgeable SHA-1 hash of the contents of the certificate identifies by the nickname (digicertassuredidrootca, trustcenterclass2caii, thawtepremiumserverca, etc.) When the JDK, via the internal, undocumented sun.security.ssl.SSLSocketImpl class attempts to establish a secure connection with a remote server, the server must present it with (at least) two certificates: one claiming that it's the rightful owner of the domain name being connected to, and another that is the signer of the first certificate and, of course, the actual signature. The JDK searches its list of trusted root certificates from the cacerts file and, if it doesn't find one with a matching fingerprint, rejects the conection with a "unable to find valid certification path to requested target". Notice, however, that I said "at least two". The designers of PKI foresaw that it would be burdensome for a handful of certificate authorities (85 in the case of JDK 1.8) to be responsible for validating every single entity that needed to be trusted; it's therefore possible for a certificate authority to delegate authorization to sub-certificate authorities. So it's probable (and was the case for me) that the server certificate is signed by a certificate that itself is signed by a self-signed root certificate. This list of certificates, each one signed by the next, is called a certificate chain, and must end in a trusted certificate, or the connection will be rejected.

I said "at least two" but even that isn't true — the server must present an identifying certificate and a signing certificate in order for the SSL protocol to work, but they can actually be the same certificate — the identifying certificate can be a self-signed "root" certificate. It's then up to the client to choose to accept this "all in one" certificate or not. This scenario is fairly rare in day-to-day e-commerce, but can be very useful when testing.

With that out of the way, though, how to go about fixing this? When you're connecting securely to a website through a browser, the browser presents a warning message which you can choose to ignore once or ignore permanently. Java code, of course, has no way to present a warning message to a user in an arbitrary context (how would that work in a Maven build, for example?) You can actually completely disable the certificate check by installing a null TrustManager instance, but that's not really what you want here; what you really want is to import the signing certificate so that the connection is always trusted. The keytool allows you to do so via:

$ keytool -importcert -file ./certificate_file
which takes as input the certificate that you want to have imported as a trusted root. So how do you get your hands on that certificate in the first place? Most browsers will show you the certificate chain, but not let you download the actual certificates (IE used to, but the most recent versions don't). Fortunately, the same keytool that ships with the JDK that you can use to view the contents of a keystore will download a certificate chain for you:
$ keytool -printcert -rfc -sslserver maven.2xoffice.com
The -rfc option outputs the certificate chain in PEM-encoded format for easy import back into a keystore. In my case, it was the last certificate in the list that I wanted, so I saved the whole thing (including the BEGIN CERTIFICATE and END CERTIFICATE lines, which are significant in this case) as godaddyg2.pem and imported it into my trust store via:
$ keytool -importcert -file ./godaddyg2.pem -keystore $JRE_LIB/lib/security/cacerts
after verifying in a browser that this was, in fact, the certificate I wanted.

Add a comment:

Completely off-topic or spam comments will be removed at the discretion of the moderator.

You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)

Name: Name is required
Email (will not be displayed publicly):
Comment is required
melvin, 2015-03-08
porfa necesito la certificacion para poder navegar en wet
Jobby Joseph, 2015-03-17
Excellent article, explained in a very simple manner. Thanks,
hvgeertruy, 2015-08-05
This article helped me a lot, I tried other sources but those were incomplete on the subject or just plain wrong. It also gave me a good background explanation on the context of the certificates, this will be a great help in the future Thanks mate
Josh, 2015-08-05
Glad I could help!
Robb01, 2015-12-30
Thanks for posting this. Searching on my errors produced very few hits and your post is the closest. It also gives great insight into what is going on.
My error is slightly different:
[ERROR] [g.openhab.io.net.http.HttpUtil] - Fatal transport error: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

I am running openhab 1.7.1 on a raspberry pi with Java:
java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) Client VM (build 25.65-b01, mixed mode)

and only a few bindings including http and zwave. Does this message tell me I need to import a new certificate and where do I find it (the right one)?

matt jordan, 2016-03-29
i followed your logic which was perfect until the moment of trying to install a certificate in my keystore at which time i was asked for a password for the keystore. There is no password as far as i am aware; i was able to list the contents of the cacerts file by typing an empty password. why would it ask for a non-empty password now?
Josh, 2016-03-29
There is a password on it, actually - it's sort of an odd behavior on the part of the java keytool, but you can list out the contents of a keystore without providing the password, but you can't update the keystore without it. By default, the password for cacerts is "changeit" (which virtually nobody does, creating a pretty massive security hole in most Java installations).
matt jordan, 2016-03-30
thanks josh, that was exactly the right thing!
Anne, 2016-04-12
Great article! But how do I verify the cert with a browser?
David, 2016-06-25
Thanks, it works!! And in addition, now I understand the problem and the solution!
Randy, 2016-07-01
When I do the import it tells me that the certificate is not an X.509 certificate.
I'm using JDK 1.8.
keytool -printcert -rfc -sslserver javalibs.com > ./javalibs.pem
keytool -importcert -file .\\javalibs.pem -keystore $env:JAVA_HOME\\jre\\lib\\security\\cacerts
keytool error: java.lang.Exception: Input not an X.509 certificate
Josh, 2016-07-01
Strange - that sequence does work for me, using both JDK 1.7 and JDK 1.8; I'm on a Mac, though, not Windows. What actually shows up in the "javalibs.pem" file? Does it begin with -----BEGIN CERTIFICATE-----?
Arijit Ghosh, 2016-09-21
Wonderful article and following this solved my problem completely.
My java application was able to communicate in SSL mode with a JMS Server, when it was executed on my local machine.

But it failed on the Test Server, as Java there was unable to recognize the JMS Server CA as an legitimate CA.

Followed the steps...and it worked bang on!!!


Makoy, 2017-01-09
After running "keytool -printcert -rfc -sslserver maven.2xoffice.com" where does the godaddyg2.pem being saved?
Josh, 2017-01-13
Unfortunately, it doesn't actually get saved, but printed out to the console. You can either redirect the output to a file or just (as I did) cut and paste what you want into another file.
David, 2017-01-17
Excellent article, explained in a very simple manner. Thanks!
My Book

I'm the author of the book "Implementing SSL/TLS Using Cryptography and PKI". Like the title says, this is a from-the-ground-up examination of the SSL protocol that provides security, integrity and privacy to most application-level internet protocols, most notably HTTP. I include the source code to a complete working SSL implementation, including the most popular cryptographic algorithms (DES, 3DES, RC4, AES, RSA, DSA, Diffie-Hellman, HMAC, MD5, SHA-1, SHA-256, and ECC), and show how they all fit together to provide transport-layer security.

My Picture

Joshua Davies

Past Posts