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.

  1. 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
  2. Generate the root certificate:
    $ cd ca/root-ca
  3. 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
    
    
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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.
  10. 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
  11. 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.
  12. 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
  13. 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
    
    
  14. Make sure you install the same certificates that you installed in the Apache CA directory in your browser.
  15. 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

Latest Tweet