I am calling a remote https URL with the following code:
def inputStream = new URL("https://somewebsite.com").openStream()
This works great on my local machine, but when I deploy to the server, I get the following Exception:
java.security.cert.CertPathValidatorException: CA key usage check failed: keyCertSign bit is not set
What is the cause of this error, and what could account for it working on one machine and not another?
UPDATE
I'm running an Ubuntu server in production and developing on a Mac locally. The site I'm trying to access (let's call it peopleware.com) has the following certificate info:
AddTrust External CA Root
UTN-USERFirst-Hardware
peopleware.com
I have tried saving the .cer files from my browser and installing them into the keystore at /etc/ssl/certs/java/castore
I'm assuming that you're talking about this certificate from UTN-USERFirst-Hardware:
-----BEGIN CERTIFICATE-----
MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
-----END CERTIFICATE-----
In a human-readable version:
Version: 3 (0x2)
Serial Number:
44:be:0c:8b:50:00:24:b4:11:d3:36:2a:fe:65:0a:fd
Signature Algorithm: sha1WithRSAEncryption
Issuer: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
Validity
Not Before: Jul 9 18:10:42 1999 GMT
Not After : Jul 9 18:19:22 2019 GMT
Subject: C=US, ST=UT, L=Salt Lake City, O=The USERTRUST Network, OU=http://www.usertrust.com, CN=UTN-USERFirst-Hardware
Subject Public Key Info:
[...]
X509v3 extensions:
X509v3 Key Usage:
Digital Signature, Non Repudiation, Certificate Sign, CRL Sign
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Subject Key Identifier:
A1:72:5F:26:1B:28:98:43:95:5D:07:37:D5:85:96:9D:4B:D2:C3:45
X509v3 CRL Distribution Points:
Full Name:
URI:http://crl.usertrust.com/UTN-USERFirst-Hardware.crl
X509v3 Extended Key Usage:
TLS Web Server Authentication, IPSec End System, IPSec Tunnel, IPSec User
Essentially, we have here a CA certificate with both X509v3 Key Usage and X509v3 Extended Key Usage.
However, RFC 3280 says the following about the extended key usage extension:
In general, this extension will appear only in end entity
certificates.
This doesn't start too well for a CA cert, but later on, the same section also says this:
If a certificate contains both a key usage extension and an extended
key usage extension, then both extensions MUST be processed
independently and the certificate MUST only be used for a purpose
consistent with both extensions. If there is no purpose consistent
with both extensions, then the certificate MUST NOT be used for any
purpose.
The only extended key usage extension in this cert that is in this RFC is TLS Web Server Authentication:
id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
-- TLS WWW server authentication
-- Key usage bits that may be consistent: digitalSignature,
-- keyEncipherment or keyAgreement
Of course, this is not consistent with keyCertSign, which, according to RFC 3280 (and RFC 5280). (I also doubt any of the IPSec extensions are compatible with keyCertSign). This makes this certificate useless to issue certificates (not very useful for a CA certificate).
I would contact the website using this certificate to ask them to contact their CA (UTN-USERFirst-Hardware, apparently Comodo) and ask them to fix this. I must say it doesn't look good coming from people who make their money on the back of these RFCs.
Of course, this could take a while and wouldn't solve your immediate problem.
I think I've seen this Subject DN (UTN-USERFirst-Hardware) in other intermediate CA certificates, so the one above might not be the one you're using.
What you might be able to do (provided that you're able to verify the server certificate itself manually despite these problems) is to use an SSLContext and a TrustManager specifically limited to use that very certificate, for this connection. This could prevent the certification path algorithm to try to find the issuer certificate and fall into this problem.
EDIT:
Here are more details on this workaround (which should still keep your connection secure).
Connect with Firefox to this website.
Click on the blue/green bar and choose 'More information...'
Security -> View Certificate -> Details
Choose the server certificate from the list at the top and choose 'Export...'
Same the PEM file somewhere.
Use keytool to create a new keystore (choose to trust that certificate and choose a sensible password):
keytool -importcert -keystore example.jks -file example.pem
Then, use this Java code, which shouldn't be too hard to port to Groovy:
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream("/.../example.jks");
ks.load(fis, null);
// or ks.load(fis, "thepassword".toCharArray());
fis.close();
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
URL url = new URL("https://somewebsite.com");
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(sslContext.getSocketFactory());
InputStream is = conn.getInputStream();
Related
I generated a private key pair using KeyStore Explorer that was signed by Sectigo [formerly Comodo] yesterday. The goal is to employ this in an SSL mutual authentication scenario. I work with iWay, a java application, that will post messages to SAP's CPI, a service on cloud. The service on CPI has client authentication enabled [specific to the resource, not at host:port]. I have created a keystore with our private key and a truststore with some public certs in it. In response to a failed communication, I've enabled SSL debug on the application and here are my findings:
*** ServerHelloDone
Warning: no suitable certificate found - continuing without client authentication
*** Certificate chain
<Empty>
***
This is reported at a stage when server cert was validated and accepted. In order to ensure whether or not the key in question took effect, I verified the logs and noticed the following, which assures that, it did [key name changed for security reasons]:
found key for : self_***.com_priv
I also verified that SAP accepts certificates when signed by valid CAs in the log that looked like this:
Cert Authorities:
...
<CN=USERTrust RSA Certification Authority, O=The USERTRUST Network, L=Jersey City, ST=New Jersey, C=US>
...
This is how the signed cert look like, when opened on Windows:
I have the following questions:
1) The chain is displayed in the certificate when opened on Windows but not on KeyStore Explorer. Anyone know why?
2) The USERTrust root CA appears to be imported into SAP's trust store based on the log given above. In the picture above, the root cert, although, reported as Sectigo, which happens to be a friendly name, has its issuer name conforming to the one imported onto SAP's trust store:
I hence am not sure why java will not render this cert to SAP during TLS handshake. Any thoughts please?
3) The intermediate certificate has a property named Authority Information Access, in which, a link to its root is present. The properties of this root is entirely different from that of the root I see in picture at item 2 above. Does this matter at all?
Any help will be appreciated. Thanks.
Problem solved. It was due to the way, I imported the CA Reply into my private key. I imported only the signed entity certificate while it looks like I must had imported in PEM format. Thanks to Bruno who have explained the resolution HERE. I am relieved!
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 (i.e. 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!
I was given a PFX "wildcard" SSL certificate (I believe its a VeriSign cert) for *.ourdomain.example.org. I then used this answer to help me convert the PFX into a JKS keystore entry and add it to a JKS keystore. When I run keytool -list -keystore mykeys.jks I see:
Keystore type: JKS
Keystore provider: SUN
Your keystore contains 1 entry
<my key's long GUUID>, May 1, 2014, PrivateKeyEntry,
Certificate fingerprint (SHA1): <cert's fingerprint>
So I know the converted PFX cert is in there. But at runtime, when JNDI is using SSL to establish a connection to my AD server (over LDAPS), I am seeing the following exception:
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'm wondering if something got botched in the conversion from PFX to JKS. I'm pretty sure nothing else is wrong with the SSL cert, because I was using a self-signed cert before using this PFX and my browser was giving me the typical "I do not trust this certificate" warning. After I added the converted PFX and restarted my app, those warnings went away. Any ideas as to what could be going on here?
Firstly, in many cases, you don't need to convert from PKCS#12 (PFX) to JKS, many applications will let you set the keystore type to PKCS12 (instead of the default JKS), which will allow you to use your PFX file directly.
It looks like you're mixing up the usage of trust store and key store here (see this question).
Having a keystore with a certificate and its PrivateKeyEntry (here, coming from a PKCS#12 file) is normally used as a "keystore keystore" (as opposed to a keystore used as a truststore). This would normally be used for the certificate you present to your clients as a server, or the client certificate you use when connecting to a server that requests one.
Here, the "PKIX path building failed" exceptions means that your Java application doesn't trust the remote certificate it is connecting to. Presumably, your AD server has nothing to do with this JKS keystore. Rather, your server that uses your keystore is also a client to that AD server, and it cannot trust the AD server certificate.
You don't say how you've configured your application. Perhaps you've used your keystore for both the keystore and truststore settings (effectively preventing the use of the CA certificates bundled with the default JRE truststore), or perhaps your AD certificate is self-signed or not issued by a CA that is part of the default truststore. If the latter is the case, simply import the AD certificate (just the certificate, not the certificate and its private key) into the trust store (you can either do this into the JRE's cacert file or make a copy of it and use that using the javax.net.ssl.trustStore and related system properties).
Let me explain quickly what I'm trying to do. I'm trying to build my own Apple's Push Notification service in java (for testing purposes). This service works thanks to TLS socket.
I have a java client to create a TLS socket to send push notifications to the APNs. I changed the host url to redirect the socket to localhost:2195. Now I'm trying to write a java socket server to get the notification request.
However, I get an exception during the handshake and can't find how to fix it.
Note : I'm using the same certificate on both sides, it's a standard .p12 file that works to send push notifications to the APNs.
Here is the client (simplified) :
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(certificatePath), password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(ks, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
tmf.init((KeyStore)null);
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLSocketFactory ssf = sc.getSocketFactory();
SSLSocket socket = (SSLSocket) ssf.createSocket(InetAddress.getLocalHost(), 2195);
socket.startHandshake();
Here is the server :
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream(certificatePath), password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
kmf.init(ks, password.toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), null, null);
SSLServerSocketFactory ssf = context.getServerSocketFactory();
serverSocket = (SSLServerSocket) ssf.createServerSocket(2195);
And here is the exception :
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: No trusted certificate found
I guess the client isn't trusting the server's certificate. I tryed to set the client's TrustManager to accept the server's p12 and it worked, however I need this to work without editing the client (since it's working that way with the real APNs).
What kind of certificate needs the server to be trusted by the client ?
Thanks in advance.
EDIT: I WAS WRONG! tmf.init(null) DOES use the default keystore just like sslctx.init(,null,) !
That default is normally the cacerts file in JRE/lib/security which DOES trust many established CAs
so now I think we can be confident the real server is using a cert under an established CA (and so
is trusted by your client) while the cert in your p12 apparently does not;
but there are two possibilities here:
it is selfsigned, or issued by an unknown, obscure, or unproven CA
it is issued by a 'real' CA under an 'intermediate' CA that needs a chain cert (or several)
and you do not have the chain cert(s) in your p12. Note this could still work for client auth
to the real server, because the real server can easily have the chain cert(s) 'preloaded'
in its truststore even though they aren't in Java's.
To distinguish these, look at keytool -keystore file -storetype pkcs12 -list -v
and see what cert or sequence of certs you have there.
Then there may be several approaches to solution:
if you are only missing chain cert(s) for an established CA get them and add them.
keytool only allows you to replace the whole chain so you must get all needed certs;
openssl (if you have or get it) can break out the key and cert(s) from a pkcs12,
replace or add individual certs, and join them back together.
create a different store and key for the server and get it a cert (chain) from an established CA.
Usually costs some money and requires you prove control of the server's domain name.
(Your client can and should still use this p12. The two sides needn't be the same.)
locate the trust anchor (from the p12, or from somewhere else like the CA) and have it
in a truststore the client explicitly loads. You effectively tried this by using the
p12 as the truststore and say you don't want that.
put the trust anchor in the client's default truststore, so the client continues
using the default. If you don't mind modifying your JRE (and no other user or application
on your system is bothered) just add to JRE/lib/security/cacerts. Or, assuming you can set
system properties, put the anchor in a store or just leave it in the p12
and set javax.net.ssl.trustStore{,Password,Type} to point to that store.
(If you copy you should take only the cert; a p12 is a KEY AND cert not just a cert.
Don't just -importkeystore; -importcert a cert file, created with -exportcert if necessary.)
(You can System.setProperty in your code, but that's changing your code. If you run from
commandline you can use 'java -Dname=value...'. For other cases YMMV.)
There is one possible 'type' issue: if the cert was issued with ExtendedKeyUsage extension
and that value specifies only TLSclient and not TLSserver (which the CA can choose to do)
then using it for server probably won't work -- it appears JSSE enforces EKU restrictions.
But if that is a problem you'll get a very different Exception.
And you can see this also in the keytool -list -v above.
Since you (rightly) want to use this p12 for your client, your server logic similarly needs
to trust it. (Using it for outgoing auth does NOT automatically make it trusted for incoming auth.)
But only if/when clientAuth is actually done, which is not the default; does your server code
.setNeedClientAuth(true) on the SSLServerSocket before accepting the connection?
Possible approaches are equivalent to the above except skipping #2 as inapplicable.
If both client and server use the same JRE, that makes the cacerts way a little easier.
Finally, yes TrustManager 'PKIX' is newer and generally more featureful than 'SunX509'.
But for the basic test 'is the trust anchor in our truststore' they are equivalent.
Sorry again for the mislead.
I believe I have done everything I should here according to related articles and yet I still get this error. I have created my own CA and signed the server certificates with this CA. On the Android side I have created a custom TrustManager using a custom truststore which has this CA root certificate in it. Using System.setProperty("javax.net.debug", "ssl") on the server side (it doesn't work on the Android side unfortunately even in 4.4) I get a little bit more information. I get past the server hello and the exchange of the secret keys. Then Android gives me the above error (Trust anchor for certification path not found) and on the server side I get
javax.net.ssl.SSLException: Inbound closed before receiving peer's close_notify: possible truncation attack?
I have mirrored the application (client) on my PC and it works.
Is it that Android does not support 2048 keys with SHA512withRSA?
I have no problem with self-signed certificates (1024 and SHA1withRSA); have not tried self-signed certificates with 2048 and SHA512.
Somehow I believe this is a shortcoming of Android that is not documented or hard to find (kind of like System.setProperty("javax.net.debug", "ssl") not working).
I actually implement my own KeyManager and TrustManager and keystores and truststores because eventually I will need mutual TLS ... all working on a PC. Hoped it would be an easy migration to Android.
Here is the Android setup of the Keystores/Truststores (gets the files and loads them)
LoadFile(getString(R.string.truststore_filename), R.raw.androidtruststore);
LoadFile(getString(R.string.keystore_filename), R.raw.androidkeystore);
String basePath = getFilesDir().getAbsolutePath() + "/";
SecureRawHttpWanSender.setSecureProperties(basePath + getString(R.string.truststore_filename),
getString(R.string.truststore_password),
basePath + getString(R.string.keystore_filename),
getString(R.string.keystore_password),
true);
Here is the setting of the TrustManagers etc. done in setSecureProperties() which is also used on the PC. Only the loading of the files is different (PC uses jks and Android uses bks)
FileInputStream fIS = null;
try
{
// On Android this is "BKS". Otherwise Sun Java is "JKS"
trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
Manager.log.log(Level.Info, Task.WanSecure, "Type of truststore: " + trustStore.getType());
fIS = new FileInputStream(trustStoreFileName);
trustStore.load(fIS, trustStorePassword.toCharArray());
fIS.close();
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
if(keyStoreFileName != null)
{
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
Manager.log.log(Level.Info, Task.WanSecure, "Type of keystore: " + keyStore.getType());
fIS = new FileInputStream(keyStoreFileName);
keyStore.load(fIS, keyStorePassword.toCharArray());
fIS.close();
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyStorePassword.toCharArray());
}
return true;
}
Thanks for any help (and the discovery of anything I did that was really stupid!)
I found a solution to the above problem but I don't like it. I may have actually seen it mentioned somewhere else in one of the questions related to this topic. What I did was add to the android truststore the server's certificate that is signed by the CA. So now in my truststore I have TWO certificates for the server, the server's certificate signed by the CA and the CA root certificate.
What I don't like is the inconsistency. On my Windows 7 PC I do not need the server's certificate signed by the CA, it is sufficient to have the CA root. I can understand the need for intermediary certificates IF the server certificate was signed by a CA that perhaps is validated by the root CA in the truststore but that intermediary CA's certificate is not in the truststore. The chain is then broken.
Why I need the server certificate signed by the CA root plus the root CA on Android but
only need the root CA on my PC is point of significant confusion. I take it as a typically incomplete implementation of Android. As I am finding out is the case for XML schema validation!