Setting up your own certificate authority
Sometimes I need to play around with some digital certificates and I do not feel like shelling out a lot of money each time to buy real ones. Here's how to set up your own CA (certificate authority) in a quick-and-dirty way.
The scenario in which I am interested is to set up a single root-CA, which signs the certificates of two sub-authorities. The sub-authorities are the entities that actually sign the end-user certificates.
- Create some directories and set up some initial files:
$ mkdir -p ca/root-ca/{private,certs} ca/person-ca/{private,certs} ca/site-ca/{private,certs} $ touch ca/{root,site,person}-ca/certindex.txt $ for i in ca/{root,site,person}-ca; do echo '100001' > $i/serial;done - Generate the root certificate:
$ cd ca/root-ca
- Create a new file called openssl.cnf with the following content:
# # OpenSSL configuration file. # # Establish working directory. dir = . [ ca ] default_ca = CA_default [ CA_default ] serial = $dir/serial database = $dir/certindex.txt new_certs_dir = $dir/certs certificate = $dir/cacert.pem private_key = $dir/private/cakey.pem default_days = 365 default_md = md5 preserve = no email_in_dn = no nameopt = default_ca certopt = default_ca policy = policy_match [ policy_match ] countryName = match stateOrProvinceName = match organizationName = match organizationalUnitName = optional commonName = supplied emailAddress = optional [ req ] default_bits = 1024 # Size of keys default_keyfile = key.pem # name of generated keys default_md = md5 # message digest algorithm string_mask = nombstr # permitted characters distinguished_name = req_distinguished_name req_extensions = v3_req [ req_distinguished_name ] # Variable name Prompt string #------------------------- ---------------------------------- 0.organizationName = Organization Name (company) organizationalUnitName = Organizational Unit Name (department, division) emailAddress = Email Address emailAddress_max = 40 localityName = Locality Name (city, district) stateOrProvinceName = State or Province Name (full name) countryName = Country Name (2 letter code) countryName_min = 2 countryName_max = 2 commonName = Common Name (hostname, IP, or your name) commonName_max = 64 # Default values for the above, for consistency and less typing. # Variable name Value #------------------------ ------------------------------ 0.organizationName_default = Your Corp localityName_default = Your Town stateOrProvinceName_default = Your State countryName_default = US [ v3_ca ] basicConstraints = CA:TRUE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always [ v3_req ] basicConstraints = CA:FALSE subjectKeyIdentifier = hash
- Generate a new root CA certificate:
$ openssl req -new -x509 -out root-ca-crt.pem -newkey rsa:2048 \ -keyout private/root-ca-key.pem -days 365 -extensions v3_ca \ -config openssl.cnf - The first sub-authority is a little more work.
$ cd ../site-ca $ openssl req -new -config ../root-ca/openssl.cnf -newkey rsa:2048 \ -keyout private/site-ca-key.pem -days 365 -extensions v3_ca \ -out site-ca-req.pem
- Sign the new certificate with the root cert:
$ cd ../root-ca $ openssl ca -config openssl.cnf -days 365 -extensions v3_ca \ -keyfile private/root-ca-key.pem \ -cert root-ca-crt.pem \ -out ../site-ca/site-ca-crt.pem -in ../site-ca/site-ca-req.pem
- Repeat for the person CA:
$ cd ../person-ca $ openssl req -new -config ../root-ca/openssl.cnf -newkey rsa:2048 \ -keyout private/person-ca-key.pem -days 365 -extensions v3_ca \ -out person-ca-req.pem
- Sign the new certificate with the root cert:
$ cd ../root-ca $ openssl ca -config openssl.cnf -days 365 -extensions v3_ca \ -keyfile private/root-ca-key.pem \ -cert root-ca-crt.pem \ -out ../person-ca/person-ca-crt.pem -in ../person-ca/person-ca-req.pem
- Generate a new certificate signing request for a person certificate:
$ cd ../person-ca $ mkdir csr $ openssl req -config ../root-ca/openssl.cnf -newkey rsa:2048 \ -keyout private/kees.leune-key.pem -out csr/kees.leune-req.pem \ -days 365 -nodes
This generates a signing request without passphrase protection. To be prompted for a phrase, omit the -nodes switch. - Sign the request with the person-ca key:
$ openssl ca -config ../root-ca/openssl.cnf -days 365 \ -keyfile private/person-ca-key.pem \ -cert person-ca-crt.pem \ -in csr/kees.leune-req.pem \ -out csr/kees.leune-crt.pem
- Repeat for a site and generate a new certificate signing request for a person certificate:
$ cd ../site-ca $ mkdir csr $ openssl req -config ../root-ca/openssl.cnf -newkey rsa:2048 \ -keyout private/site-key.pem -out csr/site-req.pem \ -days 365 -nodes
This generates a signing request without passphrase protection. To be prompted for a phrase, omit the -nodes switch. - Sign the request with the site-ca key:
$ openssl ca -config ../root-ca/openssl.cnf -days 365 \ -keyfile private/site-ca-key.pem \ -cert site-ca-crt.pem \ -in csr/site-req.pem \ -out csr/site-crt.pem
- Copy root-ca-crt.pem, site-ca-crt.pem, and person-ca-crt.pem to a directory and run c_rehash . in that directory. In an Apache mod_ssl configuration, point the SSLCACertificatePath to the directory with the certificates. Copy the files site-crt.pem and site-key.pem to a place where the web server can read them and point the SSLCertificateFile and SSLCertificateKeyFile to them. This will lead to an Apache Virtual Host definition like:
SSLEngine On SSLCertificateFile /etc/apache2/ssl/airt-dev-vm.crt SSLCertificateKeyFile /etc/apache2/ssl/airt-dev-vm.key SSLCACertificatePath /etc/apache2/ssl ServerAdmin webmaster@localhost ServerName your-host-name SSLVerifyDepth 3 SSLVerifyClient require SSLOptions +ExportCertData - Make sure you install the same certificates that you installed in the Apache CA directory in your browser.
- Lastly, generate a p12 certificate request. Make sure you are in the person CA directory:
$ openssl pkcs12 -export -in csr/kees.leune-crt.pem -inkey private/kees.leune-key.pem \ -certfile ../person-ca/person-ca-crt.pem \ -name "Kees Leune" \ -out csr/kees.leune-crt.p12