I apologize that this is so long. If you are familiar with doing client-auth in Java you can probably skim/skip to the bottom. I took me a long time to find all the relevant bits of information from disparate sources. Maybe this will help someone else.
I'm working on a proof-of-concept to get client-side authentication working from a Java client using the Windows keystore. I have a servlet that I have created that can request or require client certificates. It simply returns an HTML response containing the certificate information including subject DN and the serial number. I've worked through a number of experiments with browsers (mainly Chrome and IE) to verify it is working. I've gotten successful client authentication working with both certs I've generated with SSL and also using certs issued by my companies internal CA.
My next step is to have a Java client that works with the MSCAPI keystore in Java. The reason I went this route is that the certificates we want to use are issued by our internal CA and are automatically added to the Personal keystore in Windows and are marked as non-exportable. I know there are ways to get the private key out of the keystore with some free tools if you are an admin on your workstation. This isn't a viable option in this case for various reasons. Here's the code I ended up with:
private static SSLSocketFactory getFactory() throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException
{
KeyStore roots = KeyStore.getInstance("Windows-ROOT", new SunMSCAPI());
KeyStore personal = KeyStore.getInstance("Windows-MY", new SunMSCAPI());
roots.load(null,null);
personal.load(null,null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(roots);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(personal, "".toCharArray());
SSLContext context = SSLContext.getInstance("TLS");
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}
This works but when I connect, the Java client is authenticating with the client-certs I had previously generated. I don't see an obvious way to force the selection of a specific key so I figured it was just picking the first key it came across that the host trusts. I then removed these certs that I had created from the windows keystore and it would no longer authenticate. I triple-checked that the server-side was still works with the browsers and it does. I then readded these certs to the personal keystore and removed the trust of my pseudo-CA from the server-side. Still works in the browser and not in the client.
I added -Djavax.net.debug=ssl:handshake to my VM parameters and started looking through the output. One thing I see is are lines "found key for : [alias]" for each of the certs that I have added but not for the ones added through the 'self-enrollment' feature of certmgr.msc. Initially I thought it was because they were exportable but I removed them and added one back as non-exportable and java can still use it. I'm at a loss as to why SunMSCAPI won't use these certs. Both the ones created by me and internal CA are sha256RSA. Both are enabled for client authentication. When I add the following code, I see that isKeyEntry() is false for all the key-pairs I want to use:
Enumeration<String> aliases = personal.aliases();
while (aliases.hasMoreElements())
{
String alias = aliases.nextElement();
System.out.println(alias + " " + personal.isKeyEntry(alias));
}
I found someone else with a similar issue here but no definitive answer.
Related
I am receiving the following String from a certificate stored in Azure Key Vault. I am using the Secret API in order to retrieve both the certificate and the private key related to this cert.
Initially the certificate was uploaded using a .pfx file to Azure Key vault. Now I need to create a Certificate and a PrivateKey to allow client authentication to a 3rd party system and I am using the given String retrieved from the API, however I am note sure how to get around that in Java.
I took some hints from this link in C# however I am pretty certain that this method doesn't work like that in Java. In particular an X509Certificate or a Certificate in general doesn't hold any information about the PrivateKey in Java, unlike C#, and I am not sure how to extract that information from given String in Java.
This works as expected to retrieve the certificate from the String retrieved from the API
String secret = azureSecret.getValue();
byte[] certkey = Base64.getDecoder().decode(secret);
ByteArrayInputStream inputStream = new ByteArrayInputStream(certkey);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(inputStream);
The azureSecret.getValue() format is like the following however I am not sure how to get PrivateKey out of the given String
MIIKvgIBaaZd6Euf3EYwYdHrIIKYzCC...
YES, Java X509Certificate and Certificate is only the certificate. Instead use KeyStore which can contain multiple entries each of which is either a 'trusted' certificate (for someone else), or a privatekey plus certificate plus other chain cert(s) (if applicable) for yourself, or (not relevant here) a 'secret' (symmetric) key. PKCS12 is supported as one type of KeyStore along with others not relevant here, so after the base64-decoding you already have do something like:
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inputstreamfromvaultvalue, password);
// then
PrivateKey pkey = (PrivateKey) ks.getKey(alias, password);
// and
Certificate cert = ks.getCertificate(alias); // if you only need the leaf cert
// or
Certificate[] chain = ks.getCertificateChain(alias); // usually
But if you want to do client authentication in TLS/SSL (including HTTPS), you give the JSSE KeyManager the whole keystore object not the individual pieces (privatekey and certificates). Similarly to verify the peer in TLS/SSL, you give TrustManager a keystore containing trusted certificates, usually root CAs and often defaulted to a built-in set of public root CAs.
Im trying to validate self-signed certificate against self-signed root CA (given by service provider who will make calls to my service). Now, if i enable two way ssl all works fine but SSL requirement is globally enforced. I need to use it just for one request, and for one user. Other services should not be affected and since there is no way (unless im mistaken) of enabling it for specific paths, i deactivated two ssl and instead, in my controller im doing this:
String auth = request.getHeader("Authorization");
X509Certificate[] cert = (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
ResourceLoader resourceLoader = new DefaultResourceLoader();
try {
KeyStore trustStore = KeyStore.getInstance("JKS");
InputStream certInStream = resourceLoader.getClassLoader().getResourceAsStream("keystore/CaCertificate.jks");
trustStore.load(certInStream, "changeit".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
for (TrustManager tm: trustManagerFactory.getTrustManagers())
((X509TrustManager)tm).checkClientTrusted(cert, "RSA");
}
Thus my question is, is this method of validating certificate same as default?
Another thing is that unless i configure the following correctly:
server.ssl.trust-store=keystore/truststore.jks
server.ssl.trust-store-password=pass
server.ssl.trust-store-type= JKS
server.ssl.client-auth=want
then getAttribute method returns null. And i very much want to know why it happens. So the certificate still goes validation but request doesnt fail and its attrbute(SSL one) is not set if client certificate is not trusted? So essentially the code i wrote is useless and i can just check if following is null?
X509Certificate[] cert = (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
please help im thoroughly lost.
EDIT:
For testing purposes im using .net 4.5 HttpWebRequest. With this:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) =>
{
return true;
}
I'm trying to develop an application in java that basically gets a large number of data from REST-JSONs APIs and insert that on a DB. My problem is that two of the URLs I was working with, thrown errors about SSL certificates. To solve that, I created a jks file and inserted their certificates. The problem is, once I put that on my code, jvm only uses the certificates on that file, all others are rejected. So I want to make my application accepts jsons from either "cacerts.jks" and "custom.jks". Please don't tell me to add the certificates to the "teste.jks" (native from jvm), because the app won't run on my computer, I need the certificates on a sepparated file.
One more observation, I'm working with about 90 different URLs and I'm using the following line to indicate jvm, which keystore to use:
System.setProperty("javax.net.ssl.trustStore",System.getProperty("user.dir")+"/libs/teste.jks");
//or (for native one):
System.setProperty("javax.net.ssl.trustStore", "cacerts.jks");
Implement a subclass of javax.net.ssl.TrustManager or one of its subclasses like javax.net.ssl.X509TrustManager (MyTrustManager in the example below) and tweak the SSLContext to use it. Your trust manager implementation can do whatever you want with regard to validating/trusting certificates.
SSLContext context = SSLContext.getInstance("TLS");
MyTrustManager tm = new MyTrustManager();
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "SUN");
context.init(null, new TrustManager[] { tm }, sr);
SSLContext.setDefault(context);
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
Create two TrustManagers, one that uses the default truststore and one that uses your own, and initialize the SSLContext with both:
sslContext.init(null, new TrustManager[]{tm1, tm2}, null);
See the JSSE Reference Guide for further details.
I am trying to load & use a self-signed, dynamically generated PEM (or DER) X.509 certificate from a file and use that to load content via HTTPClient or HttpsURLConnection in order to inject it into a WebView.
I have searched for how to use a WebView with a custom certificate, but apparently there is only the possibility to ignore the SSL error completely (which is not what I want to do - I am looking for certificate validation).
Coding along the lines of this Android developers training I always get exceptions from the key functions: TrustManagerFactory, KeyStore and CertificateFactory.
For TrustManagerFactory:
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
-> Unhandled exception type NoSuchAlgorithmException in getInstance()
For KeyStore I tried the syntax from the tutorial as well as syntax as described in the documentation:
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
-> Unhandled exception type KeyStoreException in getInstance()
and
//Init with empty KeyStore
KeyStore keyStoreData;
//Use no protection for key store since it contains public cert
KeyStore.ProtectionParameter keyStoreProtect = null;
//Use default type
String keyStoreType = KeyStore.getDefaultType();
KeyStore.Builder builder = KeyStore.Builder.newInstance(keyStoreType, null, keyStoreProtect);
keyStoreData = builder.getKeyStore();
-> Unhandled exception type KeyStoreException in getKeyStore()
For CertificateFactory:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
-> Unhandled exception type CertificateException
In conclusion, I cannot use any of the functions required for the job because they all fail with exceptions even though I am using default tutorial code (which should be still valid according to documentation).
Switching the IDE (tried Android Studio and Eclipse with ADT), switching from OpenJDK to Oracle JDK, nothing seems to help here.
Also, similar questions on StackOverflow and on the Net are all answered with the use of one or more of these functions - so they don't help me.
If anybody knows what I am doing wrong, I would be glad for any help!
I am connecting an SSL client to my SSL server.
When the client fails to verify a certificate due to the root not existing in the client's key store, I need the option to add that certificate to the local key store in code and continue.
There are examples for always accepting all certificates, but I want the user to verify the cert and add it to local key store without leaving the application.
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("localhost", 23467);
try{
sslsocket.startHandshake();
} catch (IOException e) {
//here I want to get the peer's certificate, conditionally add to local key store, then reauthenticate successfully
}
There is a whole lot of stuff about custom SocketFactory, TrustManager, SSLContext, etc and I don't really understand how they all fit together or which would be the shortest path to my goal.
You could implement this using a X509TrustManager.
Obtain an SSLContext with
SSLContext ctx = SSLContext.getInstance("TLS");
Then initialize it with your custom X509TrustManager by using SSLContext#init. The SecureRandom and the KeyManager[] may be null. The latter is only useful if you perform client authentication, if in your scenario only the server needs to authenticate you don't need to set it.
From this SSLContext, get your SSLSocketFactory using SSLContext#getSocketFactory and proceed as planned.
As concerns your X509TrustManager implementation, it could look like this:
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
//do nothing, you're the client
}
public X509Certificate[] getAcceptedIssuers() {
//also only relevant for servers
}
public void checkServerTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
/* chain[chain.length -1] is the candidate for the
* root certificate.
* Look it up to see whether it's in your list.
* If not, ask the user for permission to add it.
* If not granted, reject.
* Validate the chain using CertPathValidator and
* your list of trusted roots.
*/
}
};
Edit:
Ryan was right, I forgot to explain how to add the new root to the existing ones. Let's assume your current KeyStore of trusted roots was derived from cacerts (the 'Java default trust store' that comes with your JDK, located under jre/lib/security). I assume you loaded that key store (it's in JKS format) with KeyStore#load(InputStream, char[]).
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream in = new FileInputStream("<path to cacerts"");
ks.load(in, "changeit".toCharArray);
The default password to cacerts is "changeit" if you haven't, well, changed it.
Then you may add addtional trusted roots using KeyStore#setEntry. You can omit the ProtectionParameter (i.e. null), the KeyStore.Entry would be a TrustedCertificateEntry that takes the new root as parameter to its constructor.
KeyStore.Entry newEntry = new KeyStore.TrustedCertificateEntry(newRoot);
ks.setEntry("someAlias", newEntry, null);
If you'd like to persist the altered trust store at some point, you may achieve this with KeyStore#store(OutputStream, char[].
In the JSSE API (the part that takes care of SSL/TLS), checking whether a certificate is trusted doesn't necessarily involve a KeyStore. This will be the case in the vast majority of cases, but assuming there will always be one is incorrect. This is done via a TrustManager.
This TrustManager is likely to use the default trust store KeyStore or the one specified via the javax.net.ssl.trustStore system property, but that's not necessarily the case, and this file isn't necessarily $JAVA_HOME/jre/lib/security/cacerts (all this depends on the JRE security settings).
In the general case, you can't get hold of the KeyStore that's used by the trust manager you're using from the application, more so if the default trust store is used without any system property settings.
Even if you were able to find out what that KeyStore is, you would still be facing two possible problems (at least):
You might not be able to write to that file (which is not necessarily a problem if you're not saving the changes permanently).
This KeyStore might not even be file-based (e.g. it could be the Keychain on OSX) and you might not have write access to it either.
What I would suggest is to write a wrapper around the default TrustManager (more specifically, X509TrustManager) which performs the checks against the default trust manager and, if this initial check fails, performs a callback to the user-interface to check whether to add it to a "local" trust store.
This can be done as shown in this example (with a brief unit test), if you want to use something like jSSLutils.
Preparing your SSLContext would be done like this:
KeyStore keyStore = // ... Create and/or load a keystore from a file
// if you want it to persist, null otherwise.
// In ServerCallbackWrappingTrustManager.CheckServerTrustedCallback
CheckServerTrustedCallback callback = new CheckServerTrustedCallback {
public boolean checkServerTrusted(X509Certificate[] chain,
String authType) {
return true; // only if the user wants to accept it.
}
}
// Without arguments, uses the default key managers and trust managers.
PKIXSSLContextFactory sslContextFactory = new PKIXSSLContextFactory();
sslContextFactory
.setTrustManagerWrapper(new ServerCallbackWrappingTrustManager.Wrapper(
callback, keyStore));
SSLContext sslContext = sslContextFactory.buildSSLContext();
SSLSocketFactory sslSocketFactory = sslContext.getSslSocketFactory();
// ...
// Use keyStore.store(...) if you want to save the resulting keystore for later use.
(Of course, you don't need to use this library and its SSLContextFactory, but implement your own X509TrustManager, "wrapping" or not the default one, as you prefer.)
Another factor you have to take into account is the user interaction with this callback. By the time the user has made the decision to click on accept or reject (for example), the handshake may have timed out, so you may have to make a second attempt to connect when the user has accepted the certificate.
Another point to take into account in the design of the callback is that the trust manager doesn't know which socket is being used (unlike its X509KeyManager counterpart), so there should be as little ambiguity as to what user action caused that pop-up (or however you want to implement the callback). If multiple connections are made, you wouldn't want to validate the wrong one.
It seems possible to solve this by using a distinct callback, SSLContext and SSLSocketFactory per SSLSocket that's supposed to make a new connection, some way of tying up the SSLSocket and the callback to the action taken by the user to trigger that connection attempt in the first place.