I have a client/server which is using SSL to communicate.
The keystore JKS in the client contains two certs with different key usage. Cert 1 is for SSL communication and cert 2 is for signing data:
One cert has KU=keyEncipherment,digitalSignature, EKU=clientAuth
One cert has KU=nonRepudiation,digitalSignature
When I run with this, it picks the first one (correct!). If I remove the first cert from the JKS, it picks the cert with nonRepudiation when connecting to the server (wrong!?).
That makes no sense to me as nonRep should never be used like that afaik.
So, what can I do to fix this, how can I make Java SSL pick only certs with sane key usage?
Btw, JDK1.70_21 on Windows. The code I'm using is standard stuff. Just getting default keymanagers etc
解决方案
Firstly, your second certificate may have the nonRepudiation key usage extension, but as far as I know, this doesn't make it invalid for client-certificate use. The TLS specification doesn't really say much about key usage on the client side, although technically, it uses a digital signature (so both your certs could be valid). Assuming digitalSignature is indeed required (which it is in practise, as far as I know), both your certs have it, so both your certs are suitable. The fact that one also has other key usages is a different matter.
In addition, clientAuth is not a key usage extension, but an extended key usage extension (or a Netscape extension). Your second certificate, lacking any extended key usage extension, leaves any extended key usage possible.
Secondly, the authors of the default X509KeyManager implementation in the JSSE made the choice of returning imperfect matches when perfect matches were missing. See code comments:
/*
* Return the best alias that fits the given parameters.
* The algorithm we use is:
* . scan through all the aliases in all builders in order
* . as soon as we find a perfect match, return
* (i.e. a match with a cert that has appropriate key usage
* and is not expired).
* . if we do not find a perfect match, keep looping and remember
* the imperfect matches
* . at the end, sort the imperfect matches. we prefer expired certs
* with appropriate key usage to certs with the wrong key usage.
* return the first one of them.
*/
private String chooseAlias(List keyTypeList, Principal[] issuers,
Hence, when the certificate with keyEncipherment,digitalSignature,clientAuth is missing, it will fall back on the other one.
It's an arguable choice, but it's not necessarily a bad one. There are implementations and deployments out there that are not necessarily 100% standard compliant, and where it's handy to be able to send something regardless of what your CA gave you. At the end of the day, it's not a massive risk since (a) it's really up to you to choose what you put in your keystore and (b) it's up to the server to check the key usage in all cases.
It seems quite odd to put a certificate that you wouldn't want to use for SSL/TLS usage in a keystore you set with the javax.net.ssl.keyStore property, or to build an SSLContext with it.
I'm not sure why you would put it in this keystore, but if you need that other certificate to be available for something else in your application, it's generally easier to place it in a different keystore.
Alternatively, you can implement your own X509KeyManager and override its chooseClientAlias method as required. In this case, I would create a class that delegates all the calls to the default X509KeyManager (obtained from a KeyManagerFactory with the default algorithm and initialised with your keystore), except chooseClientAlias where you'd check the key usage type before returning the alias. It's certainly feasible, but a bit more work than using two keystores.
For reference:
https://developer.mozilla.org/en/docs/NSS/nss_tech_notes/nss_tech_note3 (note that, as far as I'm aware, there is no standard requirement for there to be any Nescape Cert Type or Extended Key Usage extension for a client-certificate to be valid).