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:
sun.security.provider.certpath.SunCertPathBuilderException:
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
-----BEGIN CERTIFICATE-----
MIIFNTCCBB2gAwIBAgIHJ73QrVnyJjANBgkqhkiG9w0BAQsFADCBtDELMAkGA1UEBhMCVVMxEDAO
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE3jCCA8agAwIBAgICAwEwDQYJKoZIhvcNAQEFBQAwYzELMAkGA1UEBhMCVVMxITAfBgNVBAoT
...
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
...
-----END CERTIFICATE-----
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)
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)?
Thanks
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
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!!!
Regards,
Arijit
Donald Trump loves you!! I love you and rest of the world love you!!
May God bless you!!
Gr8 I was struggling for so long to fix this issue. Finally this article helped me.
It worked like magic.Thanks a Tonne.
Regards
Thanks for the detailed information. After searching for few hours I find this article which is really help full, But After generating the certificate chain I tried to import into my trust store by fallowing the command that provided in the article (password used : changeit)there it saying "certificate is added in first line in next line it is saying file not found exception and in brackets permission denied". Can you please let us know steps to be fallowed after generating the certificate chain. It will be great helpful .
thanks in advance
Satya
I spent a whole day trying to fix a similar issue (in my case it was the maven repo) but none of the suggestions on StackOverflow worked for me until I found this hidden gem and it worked like a charm.