A certification authority is required for signing client certificates, so the steps for creating one are these:
create the CA key
openssl genrsa -out ca.key
create a certificate signing request
openssl req -new -key ca.key -out ca.csr
self-sign the request for the creation of the certificate
openssl x509 -req -in ca.csr -out ca.crt -signkey ca.key
Now you might want to check what actually happened.
openssl x509 -in ca.crt -text
we just created the key (ca.key) and signed the certificate request: now we have a valid certificate (valid only for ourselves).
This step can be skipped if we have already a server certificate (a paid one from the trusted CAs).
In order to create our webserver certificates we have to follow these steps:
create the key
openssl genrsa -out example.com.key
create the signing request
openssl req -new -key example.com.key -out example.com.csr
sign the webserver's certificate
openssl ca -in example.com.csr -cert ca.crt -keyfile ca.key -out example.com.crt
Again, we can check the generated certificate
openssl x509 -in example.com.crt -text
The first attempt to see if the server certificate works:
[...] http { [...] server { listen 443; server_name example.com; ssl on; ssl_certificate /path/of/my/example.com.crt; ssl_certificate_key /path/of/my/example.com.key; [...] } [...]
Again the steps are key - request -sign, plus the pkcs12 export of the certificate, well known format by the web browsers:
create the client's key
openssl genrsa -out client.key
create the signing request
openssl req -new -key client.key -out client.csr
sign the request with the CA certificate
openssl ca -in client.csr -cert ca.crt -keyfile ca.key -out client.crt
export the certificate in pkcs12 format
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
We have to edit the previous nginx.conf
[...] http { [...] server { listen 443; server_name example.com; ssl on; ssl_certificate /path/of/my/example.com.crt; ssl_certificate_key /path/of/my/example.com.key; [...] ssl_client_certificate /path/of/my/ca.crt; ssl_verify_client optional; [...] #example to get some custom headers with ssl infos location / { proxy_pass http://127.0.0.1:8080; proxy_set_header X-SSL-client-serial $ssl_client_serial; proxy_set_header X-SSL-client-s-dn $ssl_client_s_dn; proxy_set_header X-SSL-client-i-dn $ssl_client_i_dn; proxy_set_header X-SSL-client-session-id $ssl_session_id; proxy_set_header X-SSL-client-verify $ssl_client_verify; } [...] } [...]
In the example configuration I chose the reverse-proxy, so I'll create a backend server to display that everithing is working well.
This task can be accomplished with almost every programming language and framework of your choice (.net, java, python, ruby...).
I'll create a node.js server just for the small effort that it require.
The server code (server.js) will simply output a copy of the request(without the request body):
require('http').createServer(function(req, res) { res.writeHead(200) res.write("<pre>") res.write(req.method + " " + req.url + " HTTP/" + req.httpVersion + "\n") for (var name in req.headers) { res.write(name + ": " + req.headers[name] + "\n") } res.end("</pre>") }).listen(8080)
And launch it with:
node server.js
The first run, without client certificates, this is the output pointing at https://example.com:
GET / HTTP/1.0 x-ssl-client-session-id: [...very long unique session id...] x-ssl-client-verify: NONE host: 127.0.0.1:8080 connection: close user-agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1 accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 accept-language: en-us,en;q=0.5 accept-encoding: gzip, deflate accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 dnt: 1
Now I'll install the client's certificate. In firefox go to
Preferences > Advanced > View Certificates > "Your certificates" tab > Importand pick the client.p12 file.
Now restart the browser (to get rid of the session id), and point again the browser to our website. This time firefox will ask which client certificate to use. Select our client's certificate and this is what should happen:
GET / HTTP/1.0 x-ssl-client-serial: 02 x-ssl-client-s-dn: /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=client_certificate x-ssl-client-i-dn: /C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=my_ca x-ssl-client-session-id: [...very long unique session id...] x-ssl-client-verify: SUCCESS host: 127.0.0.1:8080 connection: close user-agent: Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1 accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 accept-language: en-us,en;q=0.5 accept-encoding: gzip, deflate accept-charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 dnt: 1
This is a small shell script that I wrote to automate the generation of client certificates (batch generation).
#!/bin/sh SERIAL=$(cat demoCA/serial) KEY=client-$SERIAL.key openssl genrsa -out $KEY SUBJECT="/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=client-$SERIAL" CSR=client-$SERIAL.csr openssl req -new -subj $SUBJECT -key $KEY -out $CSR -batch CRT=client-$SERIAL.crt openssl ca -in $CSR -cert ca.crt -keyfile ca.key -out $CRT -batch P12=client-$SERIAL.p12 openssl pkcs12 -export -clcerts -in $CRT -inkey $KEY -out $P12 -password pass:
I've tested the steps with archlinux and encountered these small problems.
The path /etc/ssl has to be replaced with /etc/openssl or something else in some systems
openssl complains that /etc/ssl/index.txt can't be found
touch /etc/ssl/index.txt
openssl complains that /etc/ssl/serial can't be found
echo 01 | tee /etc/ssl/serial
openssl complains that the /etc/ssl/newcerts directory don't exists
mkdir /etc/ssl/newcerts
TXT_DB error number 2
We are signing two certificates with the same common name (CN). Every operation is recorded by openssl in its database, we can clear everything if it's a test run, or revoke the signatures if we have the old certificates (the suitable action if we are replacing an expired certificate).
To clean the mess instead, for our testing purpouses delete /etc/ssl/index.txt, /etc/ssl/serial and /etc/ssl/newcerts and start over with a fresh configuration.