This How-To is intended to give a step-by-step configuration guide for creating your own certificate authority (CA) and how to configure apache to allow only certain self-signed user certificates to connect to the webserver. Any other browser connects will be discarded. So let's start with the requirements.
Let's assume that the needed software components are located in following installation directories:
While doing this always keep in mind that the root CA certificate you will create now is used by the webserver as comparison to the final webserver certificate and that this root CA certificate must be imported in your browser. For doing this without any oncoming problems we advise to create RSA keys without a passphrase; otherwise apache and your browser will not be able to resolve the certificate chain!
All the data you enter here is really important and should be rememberd as you entered it. Therefore I recommend to modify openssl's configuration so you don't have to keep it always in mind. The data are needed later for the client's certificates.
Let's try it by an example (openssl.cnf):
... [ ca ] default_ca = garexCA # The default ca section ... [ garexCA ] dir = /opt/openssl-0.9.6/garexCA # Where everything is kept certs = $dir/certs # Where the issued certs are kept crl_dir = $dir/crl # Where the issued crl are kept database = $dir/index.txt # database index file. new_certs_dir = $dir/newcerts # default place for new certs. certificate = $dir/CA/garexCA.CRT # The CA certificate serial = $dir/serial # The current serial number crl = $dir/CA/garexCA.CRL # The current CRL private_key = $dir/CA/garexCA.KEY # The private key # Choose the prngd socket for a better random seed or if you don't # have a random device, e.g. /dev/random or /dev/urandom # prngd of course has to be compiled first :) #RANDFILE = $dir/private/.rand # private random number file RANDFILE = /var/run/prngd-socket # private random number file ... [ req_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = DE # MUST be in capitals countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = Bavaria localityName = Locality, e.g. Hempels Couch localityName_default = Munich 0.organizationName = Organization Name 0.organizationName_default = garex AG # we can do this but it is not needed normally :-) #1.organizationName = Second Organization Name (eg, company) #1.organizationName_default = World Wide Web Pty Ltd organizationalUnitName = Organizational Unit organizationalUnitName_default = Administration commonName = Common Name (eg, YOUR name) commonName_max = 64 emailAddress = Email Adress emailAddress_default = support@garex.net emailAddress_max = 40
First, let us change the directory to /opt/openssl-0.9.6
# cd /opt/openssl-0.9.6Create a CA subdirectory in /opt/openssl-0.9.6
# mkdir -p ./garexCA/CACreate CA key with 1024 bit
# ./bin/openssl genrsa -out ./garexCA/CA/garexCA.KEYCreate Certificate Request
# ./bin/openssl req -new -key ./garexCA/CA/garexCA.KEY -out \ ./garexCA/CA/garexCA.CSRSelf-sign certificate
# ./bin/openssl x509 -req -days 365 -in ./garexCA/CA/garexCA.CSR \ -out ./garexCA/CA/garexCA.CRT -signkey /opt/openssl-0.9.6/garexCA/CA/garexCA.key
Now we finally have our CA certificate and our CA key, which we will use to sign our webserver and our client certificates. To check the certificates content you can openssl tell to print it in text:
# openssl x509 -in garexCA.CRT -text
This is quite straightforward like we had it above with the garex CA. But this time we won't use the same key for signing but the garex CA Key. A good idea would be to sort out the keys, the certificate requests and the final certificates. This can be done by creating the following directory structure:
/opt/openssl-0.9.6/garexCA/server /opt/openssl-0.9.6/garexCA/server/certificates /opt/openssl-0.9.6/garexCA/server/requests /opt/openssl-0.9.6/garexCA/server/keys /opt/openssl-0.9.6/garexCA/user /opt/openssl-0.9.6/garexCA/user/certificates /opt/openssl-0.9.6/garexCA/user/requests /opt/openssl-0.9.6/garexCA/user/keys
Again we first go to openssl's home directory:
# cd /opt/openssl-0.9.6/garexCA/server/keysThen create the key by
# openssl genrsa -des3 -out garexWEB.KEYThe "-des3" option tell openssl to encrypt the key with a 3DES (Triple-DES) Passphrase. For later, this will be the startup passphrase for our webserver certificate. Always keep the passphrase in mind or save it to a file and pgp encrypt it later!!!
Now go to the server request directory and create the server certificate request:
# cd /opt/openssl-0.9.6/garexCA/server/requests # openssl req -new -key /opt/openssl-0.9.6/garexCA/server/keys/garexWEB.KEY \ -out garexWEB.CSR
IMPORTANT NOTE: During the creation process you will be asked several questions, e.g. the "Common Name" (=CN). As the CN has no default value in openssl.cnf you MUST enter the complete webserver's name (FQDN=Fully Qualified Domain Name), e.g. hidden.garex.net!!! This string will be later compared by apache to the config directive "ServerName". If these strings are not identical, the webserver will not be able to start up!
Go to the server directory:
# cd /opt/openssl-0.9.6/garexCA/server # openssl ca -in requests/garexWEB.CSR -cert ../CA/garexCA.CRT -keyfile \ ../CA/garexCA.KEY -out certificates/garexWEB.CRT
Again, we check the generated certificate with
# openssl x509 -in certificates/garexWEB.CRT -text
if everything is ok. The only difference to the CA certificate should be
the common name entry, e.g. CN=garex CA != CN=hidden.garex.net.
Now we're going to put the following three files in one tar-file:
(zip will do also)
/opt/openssl-0.9.6/garexCA/server/certificates/garexWEB.CRT /opt/openssl-0.9.6/garexCA/server/keys/garexWEB.KEY /opt/openssl-0.9.6/garexCA/CA/garexCA.CRT
and put this tar file to our Webserver, where you extract it in the appropriate places.
Now we have to edit the "httpd.conf". But first we better create a special subdirectory where the webserver key, the CA certificate and the webserver certificate will be placed in. Let's say we name it "certs". Now you extract the former created tar or zip archive in it:
# cd /opt/apache-1.3.26-ssl ; mkdir certs # cd certs ; gtar xvfz TARFILE # ls CA server
Now edit the httpd.conf and change the following configuration options:
ServerName hidden.garex.net SSLEngine on SSLCertificateFile /opt/apache-1.3.26-ssl/certs/server/certificates/garexWEB.CRT SSLCertificateKeyFile /opt/apache-1.3.26-ssl/certs/server/keys/garexWEB.KEY SSLCACertificateFile /opt/apache-1.3.26-ssl/certs/CA/garexCA.CRT
Just for convenience for not always being asked for the passphrase during apache's start phase we create a small and simple shellscript called "pp" (=pass phrase) and put it to /opt/apache-1.3.26-ssl/bin/pp. For instance, this would look like this one:
#!/bin/sh
case "$1" in
hidden.garex.net*)
echo "pw4support"
;;
esac
Now change the directive
SSLPassPhraseDialog builtin
to
SSLPassPhraseDialog exec:/opt/apache-1.3.26-ssl/bin/pp
I will definitely not explain how to get apache listening on port 443, which is the default https port :). Just take a look at "Listen". After saving, you should now be able to start the webserver by
# /opt/apache-1.3.26-ssl/bin/apachectl startssl
Back to openssl usage. This is quite similar to the creation of the webserver's certificate:
# cd /opt/openssl-0.9.6/garexCA ; mkdir -p usercerts/garex # cd usercerts/garex
# openssl genrsa -des3 -out garex.key 1024
(Keep the passphrase in mind!)
# openssl req -new -key garex.KEY -out garex.CSR
This time we enter for the common name (CN) our full name, e.g. "Martin Allert", and for the organizational unit "Fun dept.".
# openssl ca -in garex.CSR -cert ../../CA/garexCA.CRT -keyfile \ ../../CA/garexCA.KEY -out garex.CRT
Check again, if everything is ok
# openssl x509 -in garex.CRT -text
and if the certificate string "/C=DE..." is quite identical with the ones from above.
BE CAREFUL, THERE'S A TRAP:Most common webbrowsers like Mozilla or Netscape can't cope with this certificate type. In the former sections we created certificates in PEM format. But those browsers need the certificate to be in another type, like PKCS#12. Therefore we do a conversion:
# openssl pkcs12 -export -clcerts -in garex.CRT -inkey garex.KEY -out garex.P12
During the conversion dialog you will be asked for an export password; enter anything you can remember, but don't let it be empty. What you get now is a file which not only keeps the certificate, but also your private Key. Copy this file to your workstation (Windows/Linux/Mac OS X), start Mozilla and go through the browsers menu structure like
Preferences -> Privacy & Security -> Manage Certificates -> Your Certificates -> Import -> Choose file
Now enter your formerly chosen export password, then the passphrase of your previously generated private key, which is contained in the P12 file. Finished! But still there's a catch: the browser does not know anything about the CA which created and signed your new user certificate. To complete this task we have to import the root CA certificate as well. This is very easy, although it took me 2h to find out how to do with Mozilla :). Just put the garexCA.CRT on a public http port 80 webserver, enter the URL in your browser and click on the garexCA.CRT.
http://www.garex.net/garexCA.CRT
and - what a surprise - the browser recognizes this certifiacte as a new root CA certificate and offers you to import this certificate to your root CA chain. :))
Internet Explorer, the thing from a different worldOnce again Microsoft's Internet Explorer has its own standards: it only accepts certificates of the type DER. Therefore we have to convert our user certificate and the root CA certificate:
# openssl x509 -inform PEM -in garex.CRT -outform DER -out garex.CRT.der # openssl x509 -inform PEM -in garexCA.CRT -outform DER -out garexCA.CRT.der
Import these two certificates via IE and you are finished.
Ok, we got an installed webserver certificate and we got an imported user certificate. But how can we force apache to not accept any certificate a user browser might come with? At this moment, our apache accepts any SSL connection. Therefore we have to force him to crosscheck whether the presented user certificate is valid, allowed to connect and if a username/password combination was verified successfully. For this, we change the following apache configuration options:
SSLVerifyClient require SSLVerifyDepth 2
Usually, apache would verify the whole certificate chain up and down, e.g. if you bought a commercial certificate at VeriSign it would verify the signer of this VeriSign certificate, then the signer of VeriSign's root certificate, then the signer's signed certificate and so on. You see, it's a chain, a so called "web of trust". Apache's default value is 10, so in terms 10 root CA in the chain. Here the depth level of 2 is enough, as we only have one CA and one webserver/user certificate.
OK folks, but now we want only certain certificates to be allowed to connect. How can we manage this? Very easy, as we are now going to modify settings concerning the document root of our small SSL webserver. After the "DocumentRoot" directive we add:
...
<Directory "/www/hidden/docs">
<IfDefine SSL>
SSLRequireSSL
SSLRequire %{SSL_CLIENT_S_DN_O} eq "garex AG" and
%{SSL_CLIENT_S_DN_OU} in {"Fun dept."}
</IfDefine>
...
</Directory>
...
You see, if the "Organisation" we defined earlier in the openssl.cnf does not match the webserver certificate AND the user certificate AND the root CA certificate, EVERY request to this webserver will be dropped!!!
Effectively, the certificate content - the certificate string hidden within your certificate - will be checked; in this case we broke the check down to several components like organisation and organizational unit. Therefore it is a MUST, that the client certificate is verified against these two conditions, e.g. organisation equals to "garex AG" and organizational unit to "Fun dept." If you want to add other departments, just add them at the end of the verification line.
But we are not finished yet. Certificates are a nice thing, but what if the person leaves the room for a short time and a "bad guy" sits in front of the unlocked PC? So we add an "AuthConfig" line after the upper "</IfDefine>":
AllowOverride AuthConfig
Order deny,allow
Allow from all
AuthType Basic
AuthName "Authentication required"
AuthUserFile /opt/apache-1.3.26-ssl/etc/passwd
AuthGroupFile /opt/apache-1.3.26-ssl/etc/group
require valid-user
require group support
Satisfy all
Last but not least we create a passwd file and a group file:
# mkdir /opt/apache-1.3.26-ssl/etc # /opt/apache-1.3.26-ssl/bin/htpasswd -c \ /opt/apache-1.3.26-ssl/etc/passwd garex
The group file looks like this:
support: garex
NOW we have to restart apache for the new settings. You have to do a hard stop/start, as changes to the SSL configuration will otherwise not be loaded, as mod_ssl is already loaded by the httpd. And NOW you will see, that only an user with the proper certificate and username/password can get access to this webserver's content! Of course what is missing are so-called "Certificate Revocation Lists", which allows us to disable a user certificate with openssl, export this list into a file, load this file into apache and - voila! - the former valid user certificate is invalid.
For increasing security some of the following suggestions may be applicable to your environment: