Friday, January 13, 2012

Creating a Self-Signed SSL Certificate for Multiple Domains

This morning's adventure was trying to figure out how to generate a self-signed SSL certificate for multiple domains using OpenSSL. I found lots of discussions online, but they all didn't quite work. There were a few different ways to get a Certificate Signing Request with the SubjectAltName fields correct, but the signed certificate itself didn't have them. Finally, I got something that worked.

Credit where credit is due: The final steps that worked were based on this message from the openssl-users list and the command to generate the certificate from this page in the Linode Library.

First, save the following in a config file, for this example I'll call it example.conf and it will be for various similar domains:

[ ca ]
default_ca              = CA_default


[ CA_default ]
dir                     = .
serial                  = $dir/serial
database                = $dir/index.txt
new_certs_dir           = $dir/newcerts
certs                   = $dir/certs
certificate             = $certs/cacert.pem
private_key             = $dir/private/cakey.pem
default_days            = 365
default_md              = sha1
preserve                = no
email_in_dn             = no
nameopt                 = default_ca
certopt                 = default_ca
policy                  = policy_match
copy_extensions         = copy


[ policy_match ]
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional


[ req ]
default_bits            = 2048                  # Size of keys
default_keyfile         = example.key           # name of generated keys
default_md              = sha1                  # message digest algorithm
string_mask             = nombstr               # permitted characters
distinguished_name      = req_distinguished_name
req_extensions          = v3_req
x509_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
#------------------------------   ------------------------------
commonName_default              = www.example.com
0.organizationName_default      = Example Company
localityName_default            = Honolulu
stateOrProvinceName_default     = Hawaii
countryName_default             = US
emailAddress_default            = webmaster@example.com


[ v3_ca ]
basicConstraints        = CA:TRUE
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always,issuer:always


[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment


# Some CAs do not yet support subjectAltName in CSRs.
# Instead the additional names are form entries on web
# pages where one requests the certificate...
subjectAltName          = @alt_names


[alt_names]
DNS.1   = www.example.com
DNS.2   = www2.example.com
DNS.3   = www.example.net
DNS.4   = example.com


[ server ]
# Make a cert with nsCertType set to "server"
basicConstraints=CA:FALSE
nsCertType                      = server
nsComment                       = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer:always


[ client ]
# Make a cert with nsCertType set to "client"
basicConstraints=CA:FALSE
nsCertType                      = client
nsComment                       = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer:always

Before saving the file, some changes will need to be made to be specific to your site. The one critical change is to change the [alt_names] section to be relevant to your domain, since these needed to be specified in the configuration file rather than being requested later. If you need more or fewer DNS names you can add or remove lines. One thing to note is that the first time I tried this, Firefox didn't like the URL when I tried to connect using the domain name listed in the commonName field, so I went ahead and added it to the alt_names section as well.

You may also want to change the default keyfile names and other defaults to save yourself some typing later, especially if you're only going to be generating one certificate.

Once you have the configuration file the way you like it, you're ready to generate the key and certificate. We'll be doing it in just one step, without saving the intermediate certificate signing request:

$ openssl req -new -x509 -days 365 -nodes -out example.crt -keyout example.key -config example.conf

You'll be promoted for various certificate parameters; if you didn't change the defaults to what you want, you will need to enter them when prompted, otherwise you can just hit Enter at each prompt to accept the default specified in the configuration file. The certificate will be good for one year, if you want to change it, you can alter the number of days specified in the command line (or change the default in the config file).

Once this is done, you'll see two new files: example.key (which contains the private key) and example.crt (which contains the public certificate). Do whatever it is to need to do with them for your application.