简体   繁体   中英

How to resolve SSLHandshakeException ? Why am I getting it?

I am using java ee 6 and tomcat . One of the statement throws the following exception :

sun.security.validator.ValidatorException: PKIX path building failed: 
sun.security.provider.certpath.SunCertPathBuilderException: unable to 
find valid certification path to requested target
.
.
Caused by: 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

My application uses an API that talks to the Twitter API . In the following snippet that commented statement throws an exception:

try {
        RequestToken rToken = twitter.getOAuthRequestToken(); //THROWS EXCEPTION
        authURL = rToken.getAuthorizationURL();
    }catch(Exception exc) {
        System.out.println("$$$$$$$$Inside exception block$$$$$$$$$$");
        exc.printStackTrace();
    }

What could be the reason for this exception ?

It sounds like you have an incomplete certificate path in your trust store. For example, you might have installed a certificate you're trusting for a server, but didn't install one or more intermediate certificates that signed that certificate. Make sure you have the completely path back to a trusted root.

Edit:

  • To see the full certificate path used by the server, use the commands described here .
  • To turn on debugging for the JSSE SSL/TLS layer, set this system property: -Djavax.net.debug=ssl

the simplest and fastest way is to import the twitter certificate into the keystore of your jvm (used by Tomcat):

Go with your browser to the twitter API page and download the certificate locally. Then import the certificate into your JVM like this:

keytool -importcert -file /path/to/twitter.crt -keystore /usr/java/jdk1.6.0_16/jre/lib/security/cacerts -alias TwitterCert 

assuming your JVM (JAVA_HOME) is at /usr/java/jdk1.6.0_16/

It will ask you for a password, if you have not changed it, it is "changeit"

Restart your Tomcat and the Exception will disappear :-)

I think Rob and Alex pointed out the problem and answer. Here are the details of the answers. It shows you how to dial in a root CA to validate a chain in Java using SSLSocketFactory with the root CA of interest. And it shows you how to use s_client to inspect a certificate chain.

The code also avoids the need of putting it in a Keystore. Skipping the keystore makes a lot of sense when you already know the certificate you need to use and don't want the fuss of asking users to modify their keystore. Plus, certificate are public and generally don't need storage protection. Finally, it avoids the CA Zoo, which is always a good thing (there's no need to allow Diginotar to claim to be Twitter's CA).

static String CA_FILE = "twitter-ca.pem";
...

FileInputStream fis = new FileInputStream(CA_FILE);
X509Certificate ca = (X509Certificate) CertificateFactory.getInstance(
        "X.509").generateCertificate(new BufferedInputStream(fis));

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry(Integer.toString(1), ca);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(
        TrustManagerFactory.getDefaultAlgorithm());
tmf.init(ks);

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

URL url = new URL("https://example.com:8443");

HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(context.getSocketFactory());
...

In the above code, a HttpsURLConnection is used to plumb the custom SSL/TLS stuff. I don't know what object you are using for twitter.getOAuthRequestToken() or how to plumb it. You'll have to show more code or figure it out yourself.

If you are using Twitter4J , then the following may be of interest to you: Connect to twitter after use SSLSocketFactory . Also see setSSLSocketFactory support? on Twitter4J mailing list. On the bad side of Twitter4J (if you are using it):

$ cd twitter4j-4.0.1
$ grep -R setSSLSocketFactory *
$
$ grep -R SSLSocketFactory *
$

So the library may not even have what you need to do things securely.


You can get twitter-ca.pem from OpenSSL's s_client . Use the URL you are using for twitter.getOAuthRequestToken() (rather than just twitter.com ):

$ openssl s_client -connect twitter.com:443 
CONNECTED(00000003)
depth=1 C = US, O = "VeriSign, Inc.", OU = VeriSign Trust Network, OU = Terms of use at https://www.verisign.com/rpa (c)06, CN = VeriSign Class 3 Extended Validation SSL CA
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=Delaware/businessCategory=Private Organization/serialNumber=4337446/C=US/postalCode=94103-1307/ST=California/L=San Francisco/street=1355 Market St/O=Twitter, Inc./OU=Twitter Security/CN=twitter.com
   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)06/CN=VeriSign Class 3 Extended Validation SSL CA
 1 s:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)06/CN=VeriSign Class 3 Extended Validation SSL CA
   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5

From s_client , you know you need VeriSign Class 3 Public Primary Certification Authority - G5 . You can get that from VeriSign Root Certificates . The filename is PCA-3G5.pem .

So download VeriSign Class 3 Public Primary Certification Authority - G5 , save it as twitter-ca.pem and then use it in the program above.

With the proper CA, you'll be able to execute s_client and validate the chain. Notice the Verify return code: 0 (ok) . It will also clear your javax.net.ssl.SSLHandshakeException issue:

$ openssl s_client -connect twitter.com:443 -CAfile twitter-ca.pem 
CONNECTED(00000003)
depth=2 C = US, O = "VeriSign, Inc.", OU = VeriSign Trust Network, OU = "(c) 2006 VeriSign, Inc. - For authorized use only", CN = VeriSign Class 3 Public Primary Certification Authority - G5
verify return:1
depth=1 C = US, O = "VeriSign, Inc.", OU = VeriSign Trust Network, OU = Terms of use at https://www.verisign.com/rpa (c)06, CN = VeriSign Class 3 Extended Validation SSL CA
verify return:1
depth=0 1.3.6.1.4.1.311.60.2.1.3 = US, 1.3.6.1.4.1.311.60.2.1.2 = Delaware, businessCategory = Private Organization, serialNumber = 4337446, C = US, postalCode = 94103-1307, ST = California, L = San Francisco, street = 1355 Market St, O = "Twitter, Inc.", OU = Twitter Security, CN = twitter.com
verify return:1
---
Certificate chain
 0 s:/1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=Delaware/businessCategory=Private Organization/serialNumber=4337446/C=US/postalCode=94103-1307/ST=California/L=San Francisco/street=1355 Market St/O=Twitter, Inc./OU=Twitter Security/CN=twitter.com
   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)06/CN=VeriSign Class 3 Extended Validation SSL CA
 1 s:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)06/CN=VeriSign Class 3 Extended Validation SSL CA
   i:/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5
---
...
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: DA2581658CBA4B9B2B5061CCA69166D7...
    Session-ID-ctx: 
    Master-Key: 6CF97D758C953E627015F2EF0EBA9918...
    Key-Arg   : None
    ...
    Start Time: 1398555988
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)

$ cat twitter-ca.pem 
-----BEGIN CERTIFICATE-----
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
-----END CERTIFICATE-----

Are you maybe in an environment where you access the Internet through a proxy? If the proxy uses a self-signed certificate, your JDK cannot do SSL with any Internet host because it doesn't recognize the proxy's CA. This issue can happen because the JDK does not use the operating systems's truststore, into which your IT would have added the self-signed certificate. The solution would be to import the proxy's self-signed certificate into your JDK's truststore (ie the cacerts file). In my scenario, you would be able to get the proxy's certificate from your browser (because that uses the operating systems's truststore). Then you would use the JDK's keytool to import the certificate into the JDK's truststore (but make a backup beforehand!).

I got the same error and spent time researching on it. Tried out all options suggested like adding the twitter .crt file to my java cacerts, modifying the code by providing all the relevant key/token/secrets in twitter4j.properties instead of setting them through APIs in my code etc ... But finally found that the issue was due to the firewall present in my network.

I was successfully able to execute the same code from a different network! Check out this option and see if this works out for you ... Cheers!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM