Using SSLContext with just a CA certificate and no keystore - java

I need to setup a javax.net.ssl.SSLContext for use in a Jersey-Client application. All I want to do is the context to accept a custom root ca certificate. Is is really true that there is no way around of generating a keystore file and importing the CA certificate?

Is is really true that there is no way around of generating a keystore
file and importing the CA certificate?
There are way to do it without a keystore file, but since you would have to load the CA certificate you want to trust one way or another, you'll have to load a file or resource somehow.
(You could also certainly implement your own TrustManager that makes all the calls to use the Certification Path API, without using the KeyStore API at all, but that would only increase the complexity of your code, not reduce it. You would also need to understand the Java PKI Programmer's Guide to do this correctly.)
If you really don't want a keystore file, you could use the KeyStore API in memory and load the certificate directly.
Something along these lines should work (not tested):
InputStream is = new FileInputStream("cacert.crt");
// You could get a resource as a stream instead.
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate)cf.generateCertificate(is);
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); // You don't need the KeyStore instance to come from a file.
ks.setCertificateEntry("caCert", caCert);
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
(Remember to close everything and handle the exceptions.)
Whether loading the certificate this way or loading the certificate into a similar KeyStore instance from a keystore file is more convenient is up to you to decide.

Related

Reload Keystore without needing to restart Server

I've been looking for a solution but haven't found anything, Is it possible to reload the java keystore without restarting JVM.
I'm not sure if this is exactly what you mean, but you can interact with (create, load, update) a Java keystore via the KeyStore class.
https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html
Example of loading a keystore:
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("newKeyStoreFileName.jks"), pwdArray);

Partial chain verification on Java SSL TrustManager

I have a requirement where one can provide an intermediate CA to trust but not the CA(s) that have signed it. And using that as the trust store, I'd like to be able to trust an SSL server that has a certificate that's signed by this intermediate CA. The default implementation is to expect to build the whole chain until a trusted self-signed root CA is found. I believe that's how the whole X509 platform is based on. But for certain reasons, I can only provide the intermediate CA.
The code is the usual SSLContext creation:
// keystore part is pseudocode to make a point
KeyStore keyStore = someWayToGenerateKeyStore;
keyStore.add(intermediateCa);
//keyStore.add(rootCaThatSignedTheIntermediateCaAbove); it will work if I add this. But I don't want to for reasons.
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
ctx.init(new KeyManager[], tmf.getTrustManagers(), new SecureRandom());
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setSslContext(ctx);
client = new WebSocketClient(sslContextFactory);
OpenSSL has a parameter for this that seems to work. So I guess it's not a completely unorthodox approach.
openssl verify -CApath /dev/null -partial_chain -trusted g1 g0
There are two reasons for the requirement to only have the intermediate CA in the truststore.
It makes it easier to pass around one trusted certificate around the system that currently depends on a single self-signed CA certificate. The communication and setup of many components expect a single certificate at the moment and changing that would require significant refactoring.
We want to make sure that we only trust the certificate signed by the given intermediate CA certificate. If we add the other CA certificates in the chain into the trust store, the SSL implementation will trust any certificate signed by the other CAs also which we want to avoid. There are probably other ways to do it like checking the issuer_dn but I'd like to explore different approaches.
The default implementation is to expect to build the whole chain until a trusted self-signed root CA is found
No it isn't. It is to verify the whole chain until a trusted signing certificate is found in your truststore. 'Self-signed' has nothing to do with it, and 'trusted ... root CA' means nothing more than that it is present in your truststore.
So all you need to do is to add that certificate to your truststore. You don't need to write any code at all.
But why you want to trust an intermediate signer without trusting the root signer is a mystery.

Java TLS socket : No trusted certificate found

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.

When an SSL cert is renewed, does an Android BKS also need to be updated

So its a simple enough question but I'm not sure of the answer.
Developing SSL on android is a tricky area at times. Most people are left with two options:
* Accept all certificates and risk MITM attacks
* Package the cert as a BKS in the application.
In my apps case, I opted to package the BKS inside and read it through a HttpsURLConnection
KeyStore trustStore = loadTrustStore();
KeyStore keyStore = loadKeyStore();
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray());
SSLContext sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
URL url = new URL("https://myserver.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url
urlConnection.setSSLSocketFactory(sslCtx.getSocketFactory());
Now I've hit a bump. My certificate is fast expiring and I'm not sure the effect it will have if I upgrade it.
Q: Will renewing the SSL cert without upgrading the app on Android devices stop them from accessing the https URLs?
Q: What are the implications of not upgrading the SSL cert. Will the Android devices not be able to contact the server

Java SSL connection with self-signed certificate without copying complete keystore to client

I am setting up a licensing servlet in Java together with a client app that will post request for new licenses and validate existing licenses at that server. The servlet runs in Tomcat. I've configured Tomcat so that it only allows connections to the servlet over https, and this works just fine.
I have created a self-signed certificate using 'keytool -genkey -alias www.mysite.com -keyalg RSA -keystore license.store' which creates a file license.store and pointed tomcat to this keystoreFile with its password asdf1234.
When I just try to connect from the client to the servlet over https in Java, I receive the familiar PKIX path building failed because the certificate is not in the truststore. I tried to fixed this using this suggestion resulting in the code below:
private SSLSocketFactory getSSLFactory() throws Exception {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream is = this.getClass().getResourceAsStream("license.store");
if(is ==null) {
return null;
}
keyStore.load(is, "asdf1234".toCharArray());
is.close();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
return ctx.getSocketFactory();
}
After which I call:
HttpsURLConnection con = (HttpsURLConnection)url.openConnection();
con.setSSLSocketFactory(getSSLFactory());
which results in a succesfull connection.
Now the problem is that I only get this to work when I copy the license.store to the client and load that into the KeyStore.load(). It doesn't strike me as very safe to copy the private key and its password that the server uses to the client. Is there any way to extract only the public key from the license.store and use that? I've been searching this forum and others for a day now and just can't seem to get it.
You shouldn't be generating a public-private key pair, but rather import the certificate of the server into your (the client's) Java truststore. The certificate is not a secret, and thus does not provide a security risk on the client side. See the -import option for keytool. Here's in an example.

Categories