client bases authentication via certificate signed by ROOT CA

I have generated a ROOT CA and can successfully use it for client based authentication:

openssl req -x509 -sha256 -newkey rsa:4096 -subj "$SUBJECT"  -days 3650 -keyout root_ca.key -out root_ca.crt
openssl req -newkey rsa:4096 -nodes -sha256 -subj "$SUBJECT_CLIENT" -out client.csr -keyout client.key 
openssl x509 -req -sha256 -in client.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial -out client.crt -days 730
openssl pkcs12 -export -nodes -in client.crt -inkey client.key -out client.p12 -passout pass:

(in $SUBJECT_CLIENT the OU is different. In webserver (apache):

  SSLVerifyClient on
  SSLCACertificateFile conf/root_ca.crt

root_ca.crt imported as authority and client.p12 as personal certificate in the browser (firefox, chromium)

Now I have created an intermediate CA:

openssl req  -sha256 -newkey rsa:4096 -subj "$SUBJECT_INTER" -keyout intermediateCA.key -out intermediateCA.csr
openssl x509 -req -sha256 -in intermediateCA.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial -out intermediateCA.crt -days 1095

and use it (intermediateCA.crt) in the webserver instead of the root_ca.crt. $SUBJECT and $SUBJECT_INTER differ in the CN.

Unfortunately the web-browser (and webserver) are reporting errors when trying to access the website using client.p12.

P.S.:
Later I want to use client certificates sign by the intermediate CA for certain users. Using client_intermediate.p12 one should be able to access only the web-side with the intermediateCA.crt while other users using only client.p12 should be able to access websites using intermediateCA.crt and websites using root_ca.crt.

openssl req -newkey rsa:4096 -nodes -sha256 -subj "$SUBJECT_INTER" -out client_intermediate.csr -keyout client_intermediate.key 
openssl x509 -req -sha256 -in client_intermediate.csr -CA intermediateCA.crt -CAkey intermediateCA.key -CAcreateserial -out client_intermediate.crt -days 730
openssl pkcs12 -export -nodes -in client_intermediate.crt -inkey client_client_intermediate.key -out client_intermediate.p12 -passout pass:

There are a few problems here.

1st, while you've generated an intermediate CA, you've not signed the client certificate with it. Using this intermediate CA certificate to verify the client is therefore guaranteed to fail as it's not in the chain. If you want client.p12 to verify, then the SSLCACertificateFile must point to the client's issuer - the Root CA. If you change it to point to the intermediate CA then Apache will look for that certificate's issuer (the Root CA is listed as the issuer within the intermediate CA) and not find it - hence the error.


You can confuse matters by having non-standard attributes and extensions in your certificates. Just for clarity, the options below do work with certificates similar to the following:

  • Root CA: Subject = Issuer = "Root CA", basicConstraints = critical,CA:true
  • Intermediate CA: Subject = "Intermediate CA", Issuer = "Root CA", basicConstraints = critical,CA:true
  • Client 1: Subject = "Client 1", Issuer = "Root CA", keyUsage = digitalSignature & keyAgreement
  • Client 2: Subject = "Client 2", Issuer = "Intermediate CA", keyUsage = digitalSignature & keyAgreement

Obviously, you can alter the names to suit your environment.

If you want to insert an intermediate CA in the chain and only accept client certificates signed by this intermediate, then you can use the Require directive to filter the client certificates by an attribute, such as the OU. You then issue client certificates with specific OU values depending on which website you wish them to access.

As you don't have a OU in your example above, the following will ensure that the client certificate is signed by the intermediate CA, not the root, by checking the issuer's Common Name:

<Directory /var/www/html>

Require expr %{SSL_CLIENT_I_DN_CN} == "Intermediate CA"

...

</Directory>

The above assumes that $SUBJECT_INTER was set to Intermediate CA and your directory is /var/www/html/. You will need to ensure that this doesn't interfere with, or is affected by, other Require directives (such as a Require all granted hidden in some config) otherwise you'll be chasing your tail for quite a while. The expression variables you can use are listed here. You can use variations on this in each virtual host to check that the issuer is the intermediate CA or the root CA, or any other certificate attribute. For example, if you want to permit access to client signed by either the root CA or the intermediate CA, you could use:

<Directory /var/www/html>

<RequireAny>
    Require expr %{SSL_CLIENT_I_DN_CN} == "Intermediate CA"
    Require expr %{SSL_CLIENT_I_DN_CN} == "Root CA"
</RequireAny>    

...

</Directory>

Be careful, as this may interfere with, or may be affected by, other Require directives somewhere else within your configuration.


An alternative method is to generate multiple Root CAs for client authentication - one per website (or group of websites). You then apply the SSLCACertificateFile per virtual host, pointing to the relevant Root CA certificate. You could also potentially add other directives, such as SSLVerifyDepth, per virtual host too.


Note that you really need to revise your understanding of certificates before you do anything serious with this. As they currently stand, those certificates are not very secure. For example, the client certificate you've generated are version 1 certificates which can therefore act as a CA and potentially sign other client certificates behind your back. You should create client certificates as version 3 certificates without the Basic Constraints extension.

Of course, if you're just experimenting, you can ignore this last bit.