How to create your own PKI with openssl

Today certificates are widely used to verify, authenticate a client/user or server, to encrypt or sign emails or to sign other types of objects (e.g. source code). You are using a certificate at the moment, due to the secure http (https) connection.
In this post I will show you how to create your own Root Certificate Authority (CA).

1. Create a self signed root certificate
2. Create a sub ca certificate
3. Create a server certificate
4. Create a user certificate
5. Generate a certificate revocation list
6. Revoke a certificate
7. Export a certificate to PKCS#12 format
8. Bash script to manage your own CA

I recommend to configure your openssl.cnf (located at /etc/ssl/openssl.cnf). This is the most annoying part, but it simplifies the next steps. You can find an example of an openssl.cnf I’ll use at the end of this post. Be careful at the policy_match section, this can be a problem while signing a certificate signing request.


1. Create a self signed root certificate:

First of all we have to create a key for the root ca. The following commands will create a file with random noise (8192 Bytes) and a 2048 bit RSA key which is encrypted with AES 256 Bit:

$ openssl rand -out private/.randRootCA 8192
$ openssl genrsa -out rootca.key -aes256 2048 -rand private/.randRootCA

Explanation of the first command:

rand         create random noise
-out <file>  output file of the random noise
8192         Number of bytes of random noise

Explanation of the second command:

genrsa        generate private RSA key
-out <file>   output file of the RSA key
-aes256       encrypt the key with AES 256 Bit
2048          Bitssize of the key
-rand <file>  use random noise 

If you don’t want to encrypt the key, just remove the option -aes256, but I recommend it to encrypt the key. Feel free to change the bits size of the key to 4096 or whatever you want.

The next step is to create the self-signed root certificate:

$ openssl req -new -x509 -days 3650 -key private/rootca.key -out rootca.crt \
 -config openssl.cnf

Explanation of the command:

req             create a certificate signing request
-new            create a new signing request (you have to enter the entity data)
-x509           output a self signed certificate instead of a certificate request
-days <n>       number of days to certify the certificate for
-key <file>     specifies the private key file
-out <file>     self signed root ca certificate
-config <file>  use the given openssl config file

You can take a look at the content of the certificate with the following command:

$ openssl x509 -text -in rootca.crt

Explanation of the command:

x509       X.509 data management
-text      print the certificate in text form
-in <file> input certificate to print to stdout


2. Create a sub ca certificate (signed by the root ca):

The next step is to create a certificate signing request (CSR) for the Sub CA. To create the CSR we have to generate a key first. The generation of the key is equal to the key generation of the Root CA.

$ openssl rand -out .randSubCA 8192
$ openssl genrsa -out private/subca.key -aes256 -rand .randSubCA 2048

Now we can create the CSR:

$ openssl req -new -key private/subca.key -out subca.csr -config openssl.cnf 

Explanation of the command:

req             create a certificate signing request
-new            create a new signing request (you have to enter the entity data)
-key <file>     specifies the private key file
-out <file>     output file of Sub CA CSR
-config <file>  use the given openssl config file

The CSR can now be signed by the Root CA:

$ openssl ca -name CA_RootCA -in subca.csr -out certs/subca.crt \
 -extensions subca_cert -config openssl.cnf

Explanation of the command:

ca                    certificate authority management
-name <section>       name of the CA (section within openssl.cnf)
-in <file>            input file (the CSR)
-out <file>           output file (signed certificate)
-extensions <section> used extension, which will be added to the final certificate.
                      The given section has to be present within the openssl.cnf
-config <file>        use the given openssl config file

It is also possible to change the message digest (MD) algorithm. The default MD algorithm is given in the openssl.cnf. To change the MD algorithm you can use the -md <algorithm> option. I recommend NOT to use MD5 as MD algorithm, you should use at least SHA-1. The openssl documentation says that md5, sha1 and mdc2 are possible algorithms. But it’s possible to use e.g. SHA-512 you just have to use the option: -md sha512

I got some error messages while signing the Sub CA CSR and here are the solutions I found to fix this errors:

  1. Error message: unable to open ‘./RootCA/index.txt’
    Solution:

    $ touch ./RootCA/index.txt
  2. Error message: error while loading serial number
    Solution:

    $ echo '01' > ./RootCA/serial
  3. Error message: Check that the request matches the signature \ Signature ok \ The commonName field needed to be supplied and was missing
    Solution: change policy_match options in openssl.cnf or create a new CSR with a commonName


3. Create a server certificate (singed by the sub ca):

To create a server certificate, e.g. for a webserver http://www.example.com, you have to create a key and a CSR. So the first step to create the key is equal to the key creation of the Root or Sub CA:

$ openssl rand -out .randServer 8192
$ openssl genrsa -out private/server.key -aes256 -rand .randServer 2048

The next step is to create the CSR. But make sure that you enter the domain name of the server as common Name (in this example: commonName=www.example.com).

$ openssl req -new -key private/server.key -out server.csr -config openssl.cnf 

Now we can sign the server CSR:

$ openssl ca -name CA_SubCA -in server.csr -out certs/server.crt 
 -extensions server_cert -config openssl.cnf

Some applications use the subject alternative name as identifier, so it’s a good idea to set the subjectAltName with the domain name (in this example: subjectAltName=DNS:www.example.com). You can set the subjectAltName attribute in your openssl.cnf. For more details take a look at the opennssl doc: https://www.openssl.org/docs/apps/x509v3_config.html#Subject_Alternative_Name_


4. Create a user certificate (signed by the sub ca):

The creation of a user certificate is very similar to the creation of a server certificate, so I will keep this part short.
Key generation:

$ openssl rand -out .randUser 8192
$ openssl genrsa -out private/user.key -aes256 -rand .randUser 2048

Create CSR:

$ openssl req -new -key private/user.key -out user.csr -extensions user_cert \
 -config openssl.cnf 

Sign CSR:

$ openssl ca -name CA_SubCA -in user.csr -out certs/user.crt 
 -extensions user_cert -config openssl.cnf

Some applications use the subject alternative name as identifier, so it’s a good idea to set the subjectAltName with the email address (in this example: subjectAltName=email:user@example.com). You can set this attribute in your openssl.cnf. I recommend to set this attribute to subjectAltName=email:copy, i.e. the email address will be set automatically.


5. Generate a certificate revocation list:

The creation of a certificate revocation list (CRL) is very easy. In this example I’ll create a CRL of the Sub CA:

$ openssl ca -name CA_SubCA -gencrl -out crl/crl.pem -config openssl.cnf

Explanation of the command:

ca                    certificate authority management
-name <section>       name of the CA (section within openssl.cnf)
-gencrl               create certificate revocation list
-out <file>           output file (the final CRL)
-config <file>        use the given openssl config file

You can change the MD algorithm with the -md option, e.g. -md sha512.

I got an error while generating the CRL and here is the solution I found to fix this error:

  1. Error message: error while loading CRL number
    Solution:

    $ echo '01' > ./SubCA/crlnumber


6. Revoke a certificate

To revoke a certificate is very easy too. In this example I will revoke the certificate user.crt which was signed by the Sub CA:

$ openssl ca -name CA_SubCA -revoke certs/user.crt -config openssl.cnf

Explanation of the command:

ca                    certificate authority management
-name <section>       name of the CA (section within openssl.cnf)
-revoke <file>        input file (the certificate to revoke)
-config <file>        use the given openssl config file

I recommend to create a new CRL every time you revoke a certificate.


7. Export a certificate to PKCS#12 format

If you want to import a certificate to your browser or your email client you have to export the certificate to another format. Most of the applications need a certificate in PKCS#12 format. This format contains the public and private key. You have to enter a (backup) passphrase for exported certificate, to protect you private key.

$ openssl pkcs12 -export -in certs/user.crt -inkey private/user.key \
 -out user.pfx -name "User Certificate"

Explanation of the command:

pkcs12          PKCS#12 management
-export         create a PKCS#12 file
-in <file>      input certificate
-inkey <file>   input key (corresponding to the certificate)
-out <file>     output file (final exported certificate)
-name <string>  name of the certificate (Some apps display 
                this name in the list box)

The exported certificate user.pfx can be imported to e.g. your browser or your email client.


8. Bash script to manage your own CA

I’ve programmed a “little” Bash script to make all these steps a bit easier. The script is available at Github: https://github.com/linuxm0nk3ys/ca-script. Feel free to modify the script. If you find any bugs or you have any suggestions, please contact me (leave a comment or via Twitter: @linuxm0nk3ys).

Appendix

Full content of openssl.cnf:

# This definition stops the following lines choking if HOME isn't
# defined.
HOME			= .
RANDFILE		= $ENV::HOME/.rnd

# Extra OBJECT IDENTIFIER info:
oid_section		= new_oids

[ new_oids ]
# Policies used by the TSA examples.
tsa_policy1 = 1.2.3.4.1
tsa_policy2 = 1.2.3.4.5.6
tsa_policy3 = 1.2.3.4.5.7

####################################################################
[ ca ]
default_ca	= CA_RootCA		# The default ca section
####################################################################
[ CA_RootCA ]
dir		= ./RootCA       # 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.
#unique_subject	= no		 # Set to 'no' to allow creation of
				 # several ctificates with same subject.
new_certs_dir	= $dir/newcerts	 # default place for new certs.
certificate	= $dir/rootca.crt	# The CA certificate
serial		= $dir/serial 		# The current serial number
crlnumber	= $dir/crlnumber	# the current crl number
				        # must be commented out to leave a V1 CRL
crl		= $dir/crl.pem 		# The current CRL
private_key	= $dir/private/rootca.key   # The private key
RANDFILE	= $dir/private/.rootca.rand # private random number file
x509_extensions	= user_cert		# The extentions to add to the cert
name_opt 	= ca_default		# Subject Name options
cert_opt 	= ca_default		# Certificate field options
default_days	= 3650			# how long to certify for
default_crl_days= 30			# how long before next CRL
default_md      = sha512  		# use public key default MD
preserve        = no			# keep passed DN ordering
policy		= policy_anything       # CHANGE THIS

####################################################################
[ CA_SubCA ]
dir		= ./SubCA	    	# 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/subca.crt 	# The CA certificate
serial		= $dir/serial 		# The current serial number
crlnumber	= $dir/crlnumber	# the current crl number
       					# must be commented out to leave a V1 CRL
crl             = $dir/crl.pem          # The current CRL
private_key	= $dir/private/subca.key    # The private key
RANDFILE	= $dir/private/.subca.rand  # private random number file
x509_extensions	= user_cert		# The extentions to add to the cert
name_opt 	= ca_default	# Subject Name options
cert_opt 	= ca_default	# Certificate field options
default_days	= 3650		# how long to certify for
default_crl_days= 30		# how long before next CRL
default_md	= sha512	# use public key default MD
preserve	= no		# keep passed DN ordering
policy		= policy_match

####################################################################
# For the CA policy
[ policy_match ]
countryName	        = match
stateOrProvinceName	= match
organizationName	= match
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional

####################################################################
# For the 'anything' policy
# At this point in time, you must list all acceptable 'object'
# types.
[ policy_anything ]
countryName		= optional
stateOrProvinceName	= optional
localityName		= optional
organizationName	= optional
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional

####################################################################
[ req ]
default_bits		= 2048
default_keyfile 	= privkey.pem
distinguished_name	= req_distinguished_name
attributes		= req_attributes
x509_extensions	= root_ca	# The extentions to add to the self signed cert
string_mask = utf8only

####################################################################
[ req_distinguished_name ]
countryName			= Country Name (2 letter code)
countryName_default		= XX
countryName_min			= 2
countryName_max			= 2
stateOrProvinceName		= State or Province Name (full name)
stateOrProvinceName_default	= Some-State
localityName			= Locality Name (eg, city)
0.organizationName		= Organization Name (eg, company)
0.organizationName_default	= Example Organisation      # CHANGE THIS
organizationalUnitName	= Organizational Unit Name (eg, section)
#organizationalUnitName_default	=
commonName                      = Common Name (e.g. server FQDN or YOUR name)
commonName_max			= 64
emailAddress			= Email Address
emailAddress_max		= 64

####################################################################
[ req_attributes ]
challengePassword	= A challenge password
challengePassword_min	= 4
challengePassword_max	= 20
unstructuredName	= An optional company name

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

####################################################################
[ root_ca ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:1
keyUsage = cRLSign, keyCertSign
subjectAltName=email:copy
# URI of the CA certificate 
authorityInfoAccess = caIssuers;URI:http://my.ca/ca.html # CHANGE URI
# URI of the CRL 
crlDistributionPoints=URI:http://crl1.example.com/my.crl # CHANGE THIS

####################################################################
[ subca_cert ]
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = cRLSign, keyCertSign
subjectAltName=email:copy
# URI of the CA certificate 
authorityInfoAccess = caIssuers;URI:http://my.ca/ca.html # CHANGE URI
# URI of the CRL 
crlDistributionPoints=URI:http://crl1.example.com/my.crl # CHANGE THIS

####################################################################
[ user_cert ]
basicConstraints=CA:FALSE
# Feel free to add "dataEncipherment" or "keysAgreement"
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
subjectAltName=email:copy
extendedKeyUsage = clientAuth, emailProtection, codeSigning
# URI of the CA certificate 
authorityInfoAccess = caIssuers;URI:http://my.ca/ca.html # CHANGE URI
# URI of the CRL 
crlDistributionPoints=URI:http://crl1.example.com/my.crl # CHANGE THIS

####################################################################
[ server_cert ]
basicConstraints=CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
# add subject Altname
# IMPORTANT: You have to set the environment variable "ALTNAME", e.g.
# export ALTNAME="DNS:www.example.com, DNS:www2.example.com"
subjectAltName=$ENV::ALTNAME
# URI of the CA certificate 
authorityInfoAccess = caIssuers;URI:http://my.ca/ca.html # CHANGE URI
# URI of the CRL 
crlDistributionPoints=URI:http://crl1.example.com/my.crl # CHANGE THIS

####################################################################
[ crl_ext ]
authorityKeyIdentifier=keyid:always

####################################################################
[ tsa ]
default_tsa = tsa_config1	# the default TSA section

####################################################################
[ tsa_config1 ]
dir		= ./demoCA		# TSA root directory
serial		= $dir/tsaserial	# The current serial number (mandatory)
crypto_device	= builtin		# OpenSSL engine to use for signing
signer_cert	= $dir/tsacert.pem 	# The TSA signing certificate
					# (optional)
certs		= $dir/cacert.pem	# Certificate chain to include in reply
					# (optional)
signer_key	= $dir/private/tsakey.pem # The TSA private key (optional)
default_policy	= tsa_policy1		# Policy if request did not specify it
					# (optional)
other_policies	= tsa_policy2, tsa_policy3	# acceptable policies (optional)
digests		= sha1		# Acceptable message digests (mandatory)
accuracy	= secs:1, millisecs:500, microsecs:100	# (optional)
clock_precision_digits  = 0	# number of digits after dot. (optional)
ordering		= yes	# Is ordering defined for timestamps?
				# (optional, default: no)
tsa_name		= yes	# Must the TSA name be included in the reply?
				# (optional, default: no)
ess_cert_id_chain	= no	# Must the ESS cert id chain be included?
				# (optional, default: no)

Source:
www.openssl.org
RFC 2459
RFC 3280

Posted on June 19, 2013, in Bash-Scripts, Command-Line, Cryptography, Network, Security and tagged , , , , , , , , , , , , , , , , , , . Bookmark the permalink. 3 Comments.

  1. First, we want to thank you for the great bash script to manage the PKI with openssl.
    Maybe, you know that great Fortigate-UTM which supports PKI to authenticate for example the Forticlient VPN.

    To be compatible with it, there is need for some changes in your bash-Script:

    a) In openssl.cnf, [ user_cert ], change:
    Optional: keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
    Must have: extendedKeyUsage = serverAuth, clientAuth, emailProtection, codeSigning
    If there is no serverAuth-value in the X509-env, the Fortigate will deny the login ☹

    b) In conf.sh, change the Keysize to “2048”.

    c) And the very last change to let it work: in the inc/ssl_config_parser.sh change the SHA512 to SHA256 in line 263 and 332.

    Would be nice, if you maybe could add this to your docs because I think it will help the guys out there very much.
    It was tested against FortiOS 5.6 with success.

    Thank you!

  1. Pingback: Elliptic Curve PKI with OpenSSL | Mostly Harmless

  2. Pingback: Eigene Zertifizierungsstelle (CA) mit Sub-Zertifizierungsstellen (Sub-CA’s) | veloc1ty's Paradise

Leave a comment