Search code examples
sslopensslssl-certificatex509certificate2

SSL TrustStore without CA, only client certificate


I have a custom CAroot.crt (generated by me) and all my clients certificates are signed with this CAroot.crt. I have a TrustStore.jks where I put only clients certificates and not CAroot.crt, because I would like to have the possibility to remove a client from my truststore any time.

When I try to start my application I got the following:

*** Certificate chain
<Empty>
***
main, fatal error: 42: null cert chain

This is my trustStore.jks:

Keystore type: jks
Keystore provider: SUN

Your keystore contains 1 entry

Alias name: localhost
Creation date: Nov 23, 2019
Entry type: trustedCertEntry

Owner: CN=localhost, OU=IT, O=QUEROBUY, L=SAO CAETANO DO SUL, ST=SAO PAULO, C=BR
Issuer: CN=localhost, OU=IT, O=LANHELLAS XYZ, L=SAO CAETANO DO SUL, ST=SAO PAULO, C=BR
Serial number: 5416c04e360f9d50323c52d8a5b04be2969c9b86
Valid from: Sat Nov 23 16:39:54 BRT 2019 until: Tue Apr 06 16:39:54 BRT 2021
Certificate fingerprints:
     MD5:  8F:29:1C:1F:05:89:0B:E6:A0:57:84:FE:B0:78:68:2D
     SHA1: 95:C8:EA:0E:C8:7C:4E:99:E4:73:85:49:57:D6:BB:88:AF:52:52:12
     SHA256: 7E:ED:19:AF:02:DB:CC:88:98:D0:10:4E:39:67:AA:4D:3F:70:DA:76:03:1B:CB:41:06:DC:3B:51:38:16:78:5F
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 1


*******************************************
*******************************************

If I add "CARoot.crt" to my trustStore.jks everything works fine, but I lose the chance to invalidate some specific client certificate. Imagine, I have 10 clients, each one with your own certificate (.crt) assigned by "CARoot.crt" but Client 001 for some reason should be invalidated immediately, I will just remove your public key from trustStore.jks in server, without that I should wait for certificate expiration date.


Solution

  • WHY: if you look slightly earlier in the debuglog you will see the CertificateRequest message, which specifies (among other things) the Cert[ificate] Authorities the server asks for; see rfc5246. Java defines this as the Subject names of the certs in the truststore, because the certs in the truststore are normally expected to be CA certs (and usually CA root certs, as the predefined ones are). Most client software obeys this instruction, though there are exceptions. Thus if your truststore contains certs like

    Subject=Client1 Issuer=MyCA
    Subject=Client2 Issuer=MyCA
    Subject=Client3 Issuer=MyCA
    

    then your server will ask for certs issued by any of Client1 Client2 Client3 but not MyCA. If the client actually has only one cert and it is for e.g. Client2 but is issued by MyCA -- not by any Clientn -- most client software will consider that cert unacceptable for this server/handshake.

    THE PKI WAY. It is not true that a certificate can't be invalidated before expiration. PKI in general is explicitly designed to deal with such cases, which are generically termed revocation. There are various reasons a certificate can be revoked before expiring; for the particular PKI scheme used for SSL/TLS (and by Java for other things as well, like code signing), namely PKIX (or the effectively equivalent X.509) see rfc5280 5.3.1 as well as the rest of section 5 for Certificate Revocation Lists (CRLs), the older and traditional way of handling revocation, and rfc6960 for Online Certificate Status Protocol (OCSP), the newer way.

    For the 'real' (public) PKI this mostly just works. Java implements PKIX revocation checking, but for SSL/TLS (JSSE) it is disabled by default; you must set sysprop com.sun.net.ssl.checkRevocation to use it. You may also need to set com.sun.security.enableCRLDP for CRLs and AFAICT always need to set security property (not sysprop) ocsp.enable for OCSP. See e.g.:
    Check X509 certificate revocation status in Spring-Security before authenticating
    Checking a X509 Certificate Revocation
    Java SSL Certificate Revocation Checking

    But running CRL distribution point(s) and/or OCSP responder(s) so that they are correct and available when needed -- which can be any time -- is non-trivial; this is one of the things real CAs charge money for (or get subsidized). Doing this for your own personal CA can vary from a pain in the butt to effectively impossible, but if you want to, be much more specific about your CA.

    Your situation is in principle simpler; you have only one CA and you operate it, so you know when revocations occur. You could trivially provide the CRL(s) to the server when it(they) change. But AFAICS the builtin code has no way to use that information, so you'll have to use the hook that allows writing your own TrustManager in place of the builtin one, and modify it to use a validator with a CertStore that uses the CRLs. This is probably also a fair bit of work, but only once.

    WORKAROUNDS. Instead of doing it 'right' in PKI terms, you could continue with your approach of trusting the leaf certs individually by modifying the server or the clients.

    You could change the (X509)TrustManager hook in the server to validate certs as usual, but override getAcceptedIssuers to return a different (and correct for your case) list of 'requested' CAs, causing JSSE to send a CertificateRequest that causes the client(s) to use their correct cert(s).

    Depending on the clients you might be able to change them to ignore the 'requested' CAs and send their cert anyway -- which the server's default TrustManager will validate if it is in the truststore (or the CA is). For OpenSSL this is easy; OpenSSL already ignores the requested CA list and just sends whatever is configured. For Java you could hook the client KeyManager and override chooseClientAliases method to not check against the list of desired issuers as it normally does. For other clients add to your Q or ask a new one.