(Edited after changes to the question.)
Method 1:
One way to solve this would be to use a CA, possibly your own.
Method 2:
Accept any client certificate at the handshake level. Although accepting any server certificate from a client point of view is a bad idea, since it introduces the possibility of a MITM attack (see here and here, for example), accepting any client certificate during the handshake doesn't present the same problems.
At the end of the handshake, provided that the server certificate is still verified, you will know that:
- The communication between the client and the server is established securely, as it would with HTTPS without client-certificate authentication.
- The client has the private key for the certificate it has presented during the handshake. This is guaranteed by the TLS
CertificateVerify
message, which uses the client's private key to sign the concatenation of all messages exchanged during the handshake so far, including the server certificate and the client certificate. This doesn't actually depend on the (trust) verification of the client certificate itself, and will only work if the public key in the client certificate can validate the signature in the TLS CertificateVerify
message.
With this, you will know that the client certificate the server gets is used by someone who has its matching private key.
What you won't know, is who that public key certificate belongs to, i.e. who the user at the other end is, because you're missing the verification step normally performed using a CA, which should itself rely on an out-of-band mechanism before issuing the certificate. Since they're using self-signed certificates, you would have to perform this out-of-band step anyway, perhaps via some information during the registration.
Doing this would allow you to manage your users and the way they use their certificates more easily. You could simply store and/or lookup the current user's certificate in a database common to your servers. You can get access to the certificate by looking at the SSLSession
within your application. You will find the user certificate in position 0 of the array returned by getPeerCertificates()
. You can then simply compare the credential you see (again, because the two-way handshake was successful) with those you've seen before, for the purpose of authentication (and authorization) within your application.
Note that even if you just accepted self-signed certs that you keep adding, you would still have to track this public key information as part of your system, in addition to the Subject DNs because you would have no control over the Subject DNs (two users could choose to use the same, intentionally or not).
To implement this, you will need to configure your server with an SSLContext
which uses an X509TrustManager
that doesn't throw any exception in its checkClientTrusted
method. (If you're using the same SSLContext
for other purposes, I would get the default PKIX TrustManager
and delegate the calls to checkServerTrusted
to it.) Since you're probably not going to know what the Subject/Issuer DN of these self-signed certificates are going to be, you should sent an empty list of accepted CA (*) by returning an empty array in getAcceptedIssuers()
(see example here).
(*) TLS 1.0 is silent on this subject, but TLS 1.1 explicitly allows empty lists (with unspecified behaviour). In practice, it will work with most browsers, even with SSLv3/TLSv1.0: the browser will present the full list of certificates to choose from in this case (or pick the first one it finds it it's configure to select one automatically).
(I'm leaving what's more specific about HTTP here... A bit out of context now, but this might be of interest to others.)
Method 1:
You could integrate the certificate issuing as part of your initial registration. When the users register their details, they also apply for a certificate which is immediately issued by your registration server into their browser. (If you're not familiar with this, there are ways to issue certificate within the browser, using <keygen />
, CRMF (on Firefox), or ActiveX controls on IE, which one depends on the version of Windows.)
Method 2:
In a Servlet environment you can get it in the servlet request javax.servlet.request.X509Certificate
attribute
In addition, this tends to improve the user experience, since the rejection of a client certificate doesn't necessarily terminate the connection. You could still serve a web page (with an HTTP 401 status perhaps, although technically, it would need to be accompanied by a challenge mechanism, and there isn't one for certs) at least telling your user something is wrong. Handshake failures (which the client-cert verification within the handshake would cause in case of problem) can be very confusing for users who don't really know about certificates. The downside is that it's quite hard to "log out" (similarly to HTTP Basic authentication), because of lack of user interface support in most browsers.
For the implementation, if you're using Apache Tomcat, you may be interested in this SSLImplementation
or this answer if you're using Apache Tomcat 6.0.33 or above.