Restriction based on client certificates

ssl security

In this tutorial, I will walk you through the configuration of creating and installing the client certificates in the nginx web server and the browser so that we can achieve the restrtiction based on client certificates.

So, what are client certificates? Heck, I also did not knew this until today when one of our client’s mentioned them. Before diving into the configuration, I would like to mentioned the difference between server certificates and client certificates:

Server certificates: are used on the server side to check the authenticity of the website that a user is visiting along with the encryption. When using server certificates, the user has to decide weather to trust the website for it’s authenticity or not.

Client certificates: are used by the server to check if the client is authenticated to view certain parts of the website. For example, you have a wordpress installation which is accessible to general public, along with that, if you try to access the wordpress admin page, it will also be visible to the end users. This opens up a security hole to brute force the wordpress admin page and a malicious user can gain access by brute forcing the admin page. If no other measures are taken, your wordpress, or in fact, any other app’s admin page is in jeopardy. Administrators issue client certificates so that the authenticated user can install them in their browsers so that when the admin page is accessed from the very same browser, the client certificates are sent over to the server so that the server can authenticate the user and allow access to specific parts of a web application, in this case, the admin page for wordpress.

Now that we are clear about the difference between server and client certificates, let’s dive in the configuration.

Generate the certificates:

First step is to generate the required certificates on the server. I will simply quote the steps here as there are much more advanced tutorials on the web about generating self signed certificates with explanation. If you want to dig deep about the certificates, then I would recommend you to search a bit on any seach engine for “generating self signed certificates” etc. Follow the steps mentioned below:

1. We will first create the CA key file and CA Certificate in order to self sign the certificate. These certificates can also be used as server certificates, so I will not generate extra server certificates.


openssl genrsa -des3 -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt

2. Next, we will create client key and certificate signing request (CSR) and use it again to self sign this client certificate. NOTE: We are signing the certificates here ourselves but in production environment it is not recommended at all and highly discouraged.


openssl genrsa -des3 -out client.key 1024
openssl req -new -key client.key -out client.csr

3. Now we will self sign this certificate as:


openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

Converting client key to .P12 format:

Next thing is to convert the client.key to .p12 format so that it can be converted into browser compatible format. PKCS (.p12) format is a binary format for storing the server certificates (or any intermediate certificate as a matter of fact) in a single file. For more information on various certificates and their encryption types, you can refer to this article.


openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

Now we have a client.p12 which can be imported into any compatible browser. This has been tested by me on firefox for Windows OS (Windows 8.1) and Linux (arch linux) system architectures.

Nginx configuration:

In this part, we will configure our nginx configuration to check the client certificates when a request is recevied at the server and decide if the user should be given the privilege to access the ‘admin’ page of our test wordpress installation. We have all our certificates for this purpose saved at one location that is: /root/certs. Open up the nginx configuration file for your blog (in my case, it is: /etc/nginx/conf.d/wordpress.conf). We will also incorporate a setting such that any request received at plain “http://” will be forwarded to “https://” automatically. After installing the certificates, we will add a specific rule that if the client certificate is passed by the browser (that will initially be requested by web server) and is valid, he will be allowed to access the admin page. Otherwise, a 403 Forbidden error will be returned. Let’s get to the configuration.

1. First of all, I will redirect all traffic from http:// to https:// in wordpress.conf:


server {
listen  80;
server_name     example.com;
return 301 https://$host$request_uri;
}

The above server block will redirect the traffic coming on port 80 to port 443 (https://). 301 here stands for permanent redirect.

2. Next, we will define another server block for https:// requests, this server block will also have all the required configuration for server and client certificates.


server {
   listen       443;
   server_name  example.com;
   root   /usr/share/nginx/html;
   index  index.php index.html index.htm;

   access_log  /var/log/nginx/blog.acc.log;
   error_log  /var/log/nginx/blog.err.log error;

   ssl                  on;
   ssl_certificate      /root/certs/ca.crt;
   ssl_certificate_key  /root/certs/ca.key;
   ssl_client_certificate      /root/certs/client.crt;
   ssl_verify_client   optional;
   ssl_session_timeout  5m;

   location ~ .php$ {
      root           /usr/share/nginx/html;
      fastcgi_pass   127.0.0.1:9000;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param   VERIFIED $ssl_client_verify;
      fastcgi_param   DN $ssl_client_s_dn;
      include        fastcgi_params;
      }
   }

Explanation:

ssl_certificate: This is the server side certificate that we have generated above.

ssl_certificate_key: This is the key used with the above certificate.

ssl_client_certificate: This is the client side certificate that the web server expects from the web browser.

ssl_verify_client: Here, the server will act accordingly, for example, if we define ‘optional’ (as I did in the above configuration), the web server expects the client certificate to be returned by the browser but it is not mandatory. Second option that we can define here is: ‘on’ which mean that the web server will strictly look for the client certificate, if it is received, the access is granted otherwise 400 error is returned in the browser. The values returned are stored in a variable called: ssl_client_verify and can have values as: “SUCCESS“, if the client certificate is received and verified; “FAILED“, if the client certificate is received but failed the authenticity and “NONE“, if no certificate is received.

 fastcgi_param   VERIFIED $ssl_client_verify: This variable is passed to PHP-FPM location block and we have passed ‘VERIFIED’ to this parameter. This means that any value that will be stored in this variable (SUCCESS, FAILED or NONE) will be verified against our CA cert and further processing will be done on this basis.

fastcgi_param   DN $ssl_client_s_dn: This will be useful if you want to check the DN of client certificate received and the part we might be interested in is the Common Name (CN). An example could be of following type:

/C=COUNTRY/ST=STATE-PROVINCE/L=LOCATION-CITY/O=ORGANIZATION/OU=ORGANIZATIONAL UNIT/CN=CLIENT NAME/

Next, we will tell the browser about what to do with the requests for /admin, /login or /wp-login.php requests. We will make use of simple if-else statements, yes, it is possible to use this syntax with nginx 😉 Configuration is as follows:


location ~^/(admin|login|wp-admin)(.*)$ {
   index   index.php index.html;
   if ($ssl_client_verify = "NONE") {
      return 403;
   }
location ~ .php$ {
   fastcgi_pass   127.0.0.1:9000;
   fastcgi_index  index.php;
   fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
   fastcgi_param   VERIFIED $ssl_client_verify;
   fastcgi_param   DN $ssl_client_s_dn;
   include        fastcgi_params;
   }
}

Explanation:

location ~^/(admin|login|wp-admin)(.*)$: This line here starts the location block for /admin* , /login* and /wp-admin* followed by the type of page you want to serve (index.php or index.html or index.htm).

if ($ssl_client_verify = “NONE”): This starts the if statement within the location block which checks a condition that what value is stored in $ssl_client_verify variable. The value stored in this variable is governed by ssl_verify_client parameter in the configuration (see above).

return 403: This is self explanatory, if the value is NONE, means no certificate is returned by the browser or the certificate returned does not match the one defined above, then the server will return 403 Forbidden error to the user. If not, the request will be forwarded to php-fpm location block for processing PHP requests. If you just want to forward plain html requests after the client verification, then this php-fpm block is not needed here.

Full configuration file will look like the following:

server {
   listen  80;
   server_name     example.com;
   return 301 https://$host$request_uri;
}
server {
   listen       443;
   server_name  example.com;
   root   /usr/share/nginx/html;
   index  index.php index.html index.htm;

   access_log  /var/log/nginx/blog.acc.log;
   error_log  /var/log/nginx/blog.err.log error;

   ssl                  on;
   ssl_certificate      /root/certs/ca.crt;
   ssl_certificate_key  /root/certs/ca.key;
   ssl_client_certificate      /root/certs/client.crt;
   ssl_verify_client   optional;
   ssl_session_timeout  5m;

   location ~^/(admin|login|wp-admin)(.*)$ {
      index   index.php index.html;
      if ($ssl_client_verify = "NONE") {
         return 403;
      }
   location ~ .php$ {
      fastcgi_pass   127.0.0.1:9000;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param   VERIFIED $ssl_client_verify;
      fastcgi_param   DN $ssl_client_s_dn;
      include        fastcgi_params;
      }
}
   location ~ .php$ {
      root           /usr/share/nginx/html;
      fastcgi_pass   127.0.0.1:9000;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
      fastcgi_param   VERIFIED $ssl_client_verify;
      fastcgi_param   DN $ssl_client_s_dn;
      include        fastcgi_params;
      }
}

This finishes our server configuration part and next, we will head over to the client in order to install the client certificate.

Installing client certificates in a browser:

Now that we have our server configured for client certificates, it’s time to install the client certificate in a browser. I have tested this firefox browser on Windows 8.1 and Arch Linux as mentioned above. You can install the same certificate in any browser such as Google Chrome (or Chromium in Linux world), Safari for iOS etc. The instructions for installing certificates manually depends on different browsers, for which you may consult your browsers documentation on how to install certificates manually.

Before we install the certificate, let’s first try to access the admin page of wordpress. If you do so, you will receive a 403 Forbidden error as per the configuration above. If you receive this error, then pat yourself on your back because you have configured the server correctly.

Steps to install the client certificates on firefox browser:

1. Download the client.p12 file that we have created in the starting along with other certificates. I would recommend you to first create a tar archive which have this file and then download that one to maintain the integrity of the file. Once you have it downloaded at your client machine, extract it and open firefox.

Goto: Options/Preferences >> Advanced >> Certificates >> View Certificates >> Your Certificates >> Import >> Browse to the location where client.p12 is located >> select file >> Open

This will import the certificate and you should see your certificate under Your Certificates heading. Once this is done, you have your certificate installed. Try to access your wordpress admin page again and voila! you can access it without any issues. Now try another browser (in which you have not imported the certificate yet) to check if you can access the admin page. I bet you cannot if you have all the instructions followed mentioned here.

Pretty neat stuff, ain’t it!

2 thoughts on “Restriction based on client certificates

Leave a Reply

Your email address will not be published. Required fields are marked *