Monday, September 10, 2012

2 factor authentication. SSL client certificate


A way of creating 2-factor authentication to login to a website is to use SSL certificate on the client side (the client would import it into his browser) with the combination of normal login (e.g. .htaccess file in website directory)

You can not used any purchased from third party SSL certificates.  The certificate that you use in this case has to be self-generated SSL certificate.  If you are looking for the "proper" solution where your CA (certificate of authority) is signed by a third party, you'd need a PKI solution.  The price tag that I got from Digicert was 100 client certs per year for $2500.

So here are the instructions on how to generate self-signed CA, webserver SSL cert, client side SSL cert.  After you implement this solution you'll get the following:

You'll have a Web server with SSL enabled but the uses will be able to login and use your web site only if they have a special file that you gave them (client certificate) that they import into their favorite browser.

The following instructions are for CentOS-5.8, running Apache.


CA SSL server certificate

Pick a dedicated CA server. It is used to generate SSL certificates and it should be a separate server from your web server.  SSL key is the most sensitive piece of it and it should be kept in a safe place.
Modify OpenSSL config file:
/etc/pki/tls/openssl.cnf
change the following lines:
default_days    = 3650
[ req_distinguished_name ]
countryName_default             = US
stateOrProvinceName_default     = California
localityName_default            = Palo Alto
0.organizationName_default      = You Company
organizationalUnitName_default  = Ops

Also change the location of crt and key file if you use your custom files:

certificate     = $dir/yourca.crt
private_key     = $dir/private/yourca.key
Generate CA:
cd /etc/pki/CA/
openssl genrsa -des3 -out private/yourca.key 4096
openssl req -new -x509 -days 3650 -key private/yourca.key -out yourca.crt


SSL server certificate



cd /etc/pki/CA/certs/
  1. Generate Webserver key:
    openssl genrsa -des3 -out webserver.key 1024
  2. Create Certificate request using that key:
    openssl req -new -key webserver.key -out webserver.csr
  3. Sign Cert request using CA certificate and key:
    openssl ca -in webserver.csr -cert ../yourca.crt -keyfile ../private/yourca.key -out webserver.crt
You can take a look at your cert:
openssl x509 -in webserver.crt -text
And you should be able to verify it against your main CA certificate:
openssl verify -CAfile ../yourca.crt webserver.crt
webserver.crt: OK
Create Certificate revocation file (you'll need that to be able to disable certificates):
echo "01" > /etc/pki/CA/crlnumber
[root@secureserver private]# openssl ca -gencrl -out webserver.crl


Add your certificates to Apache

Copy the following 4 files from your secureserver to your webserver: /etc/pki/CA/certs/
webserver.crt webserver.key ../yourca.crt webserver.crl
Do not copy your "yourca.key" file!
Point Apache to your cert files:
/etc/httpd/conf/sites-enabled/yoursite.com
<VirtualHost *:443>
  ServerName yoursite.com
...
  SSLEngine on
  SSLVerifyClient require
  SSLVerifyDepth 1
  SSLCertificateFile /etc/pki/CA/certs/webserver.crt
  SSLCertificateKeyFile /etc/pki/CA/certs/webserver.key
  SSLCACertificateFile /etc/pki/CA/certs/yourca.crt
# List of the certificates we want to purposely deny:
  SSLCARevocationFile /etc/pki/CA/certs/webserver.crl
Every time you start Apache server, it will prompt you for SSL cert password.  The fix is:
Add the following line to your /etc/httpd/conf/httpd.conf
SSLPassPhraseDialog  exec:/etc/pki/CA/certs/pp.sh

Your pp.sh file will look like this:
#!/bin/sh

case "$1" in
        yoursite.com*)
                echo "yoursslpassword"
                ;;
esac



Create client certificate

Client certificates have to be generated on secureserver (CA server) since they require CA cert and CA key to be present.
  1. Generate client cert request using CS server's key:
    [root@secureserver newcerts]# openssl req -new -key ../private/yourca.key -out igor.csr
  2. Sign the request using server's cert:
    openssl ca -in igor.csr -cert ../yourca.crt -keyfile ../private/yourca.key -out igor.crt
  3. Export client key to p12 format that browser will understand:
    openssl pkcs12 -export -clcerts -in igor.crt -inkey ../private/yourca.key -out igor.p12
Pick some export password that you'll give to the user for importing

The following python script creates users' certs secureserver:/root/clientcert.py:
(I assume that your client certificates go under /etc/pki/CA/newcerts)

#!/usr/bin/env python
import stat, sys, os, string, commands, subprocess
# No argument - print help and exit
if len(sys.argv)!=2:
   print 'Usage: newcert.py username'
   sys.exit(0)

# Otherwise - execute
# Variables
USER = sys.argv[1]
CERT_DIR = "/etc/pki/CA/newcerts"
# Cert request
CERT_REQ = "openssl req -new -batch -key ../private/smca.key -passin pass:yourpassword -config /etc/pki/tls/openssl.cnf -subj \"/C=US/ST=California/L=Palo Alto/O=YourCompany/OU=Ops/CN=" + USER + "/emailAddress=ops@yoursite.com\" -out " + USER + ".csr"
# Cert signing
CERT_SIGN = "openssl ca -batch -passin pass:yourpassword -in " + USER + ".csr -cert ../smca.crt -keyfile ../private/smca.key -out " + USER + ".crt"
# Cert convert
CERT_CONV = "openssl pkcs12 -passin pass:yourpassword -passout pass:exportpassword -export -clcerts -in " + USER + ".crt -inkey ../private/smca.key -out " + USER + ".p12"
# Zip cert
CERT_ZIP = "zip " + USER + ".zip smca.crt " + USER + ".p12"

# Execute
subprocess.call([CERT_REQ], cwd=CERT_DIR, shell=True)
subprocess.call([CERT_SIGN], cwd=CERT_DIR, shell=True)
subprocess.call([CERT_CONV], cwd=CERT_DIR, shell=True)
subprocess.call([CERT_ZIP], cwd=CERT_DIR, shell=True)
E-mail those zip files to a bunch of clients in /tmp/list:
for i in `cat /tmp/list`; do mutt -a $i.zip -s "Your Certificate" $i@yoursite.com < /tmp/mailmessage.txt; done


How to revoke certificate

Revoke a specific cert:
[root@secureserver CA]# openssl ca -revoke newcerts/igor.crt
Update crl file:
openssl ca -gencrl -out certs/webserver.crl
Copy updated crl file to the webserver:
scp certs/webserver.crl webserver:/etc/pki/CA/certs/
Restart Apache on the webserver
[root@webserver]# /etc/init.d/httpd restart
tail /var/log/http/error_log
Verify:
openssl crl -in webserver.crl -noout -text
or
cat index.txt


Apache login authentication
is outside of this wiki and can be easily found on the Internet


Troubleshooting

I had to establish connection to a webserver using wget and I was getting an error message:
wget --debug --ca-certificate=/etc/pki/CA/certs/yourca.crt  --certifi
cate=/etc/pki/CA/newcerts/client.crt  --http-user=client --http-password=password https://yoursite.com

Connecting to yoursite.com|10.10.23.104|:443... connected.
Created socket 3.
Releasing 0x00000000141e40d0 (new refcount 1).
Initiating SSL handshake.
SSL handshake failed.
OpenSSL: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure
Closed fd 3   
Unable to establish SSL connection.
Apparently wget requires a certificate key that has to be used. Below is the proper line:
wget --debug --ca-certificate=/etc/pki/CA/certs/yourca.crt  --certifi
cate=/etc/pki/CA/newcerts/client.crt --private-key=/etc/pki/CA/newcerts/client.key  --http-user=client --http-password=password https://yoursite.com
A good way to verify that your SSL keys are good is by using openssl s_client command:
openssl s_client -cert client.crt -key client.key -connect yoursite.com:443 -debug
 
 
If you try to create a certificate with the same name, you'll get this error message:
failed to update database
TXT_DB error number 2
 
SSL keeps track of the certificates issued.  Every cert has a uniq serial number.
When you create a new cert - you should get a new cert file, e.g. 47.pem
SSL will update those files:
/etc/pki/CA/index.txt  (the third column is your serial number)
/etc/pki/CA/serial  (should have the next available serial number, e.g. 48)

/etc/pki/CA/index.txt.attr is the file that controls the unique subject behavior:
unique_subject = yes

If you change it to "no", it will allow you to create multiple certs with the same name
(that's a bad thing, don't do it).
 

 

No comments: