Note: This page is quite old and is likely out of date. My opinions may have also changed dramatically since this was written. It is here as a reference until I get around to updating it.

This page goes through how to create a local PKI infrastructure for use with all the other components listed in my notes and many may not mention it at all. It uses OpenSSL and provides scripts, information about choices made during the process and where to go next.

Security Notes

There are varying levels of security that need to be taken into account throughout this process. The first and most important certificate is the 'root' certificate authority's certificate. If the private key for this is compromised everything else will need to be done by scratch. Additionally the private key needs to be generated from a true random source. This is important as a little bit of predictability will impair all future operations done with this certificate and thus all encrypted operations done from certificates at the heart of this system.

Second level certificate authorities should still be protected with utmost caution, but these are for secondary services such as one for the [Linux/389 Directory Server] to hand out certificates, another for [Linux/OpenVPN] certificates, one for websites and perhaps even another for personal certificates for email and code signing. If one of these gets compromised the root certificate can issue a CRL automatically invalidating all certificates from that authority and limits the damage done.

Root CA

Caution needs to be taken when working with the root certificate from the get go. It should be generated on a clean machine disconnected from the network.

Support Structure

The following set of commands builds a directory structure in the current user's home drive in a folder called pki. It adjusts the permissions on the folders to ensure that it is somewhat protected (though not protected from the root user). Once again this should be performed on a trusted machine.

mkdir -p ~/pki/root/{certs,crl,newcerts,private}
chown -R `id -u`:`id -g` ~/pki
chmod -R 700 ~/pki

Create the Configuration File

Create a file in the root CA's directory named openssl.cnf with the following contents:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
HOME                    = .
RANDFILE                = $ENV::HOME/.rnd
oid_section             = new_oids

[ new_oids ]

[ ca ]
default_ca = CA_default

[ CA_default ]
dir = /home/sstelfox/pki             # The root directory
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 = yes
new_certs_dir = $dir/newcerts        # Default place for new certs.

certificate = $dir/cacert.pem        # The CA certificate
serial = $dir/serial                 # The current serial number
crlnumber = $dir/crlnumber           # The current crl number
crl = $dir/crl.pem                   # The current CRL
private_key = $dir/private/ca.key # The private key
RANDFILE = $dir/private/.rand        # Private random number file

x509_extensions = usr_cert           # The extentions to add to the cert

name_opt = ca_default                # Subject Name options
cert_opt = ca_default                # Certificate field options

crl_extensions = crl_ext

default_days = 365                   # How long to certify for
default_crl_days = 7                 # How long before next CRL
default_md = sha256                  # Use sha256 for the hash
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

[ policy_anything ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional

###################### CA Request Defaults ##########################
[ req ]
default_bits = 4096
default_md = sha256
default_keyfile = priv.key
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca # The extentions to add to the self signed cert

string_mask = utf8only

[ req_distinguished_name ]
countryName = Country Name
countryName_default = AC
countryName_min = 2
countryName_max = 2

stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = YmVkcm9vbXByb2dyYW1tZXJzLm5ldA==

localityName = Locality Name
localityName_default = dHJ1ZWR1YWxpdHk=

0.organizationName = Organization Name
0.organizationName_default = QmVkcm9vbSBQcm9ncmFtbWVycyAtIENvZGUgVGhlIFBsYW5ldA==

organizationalUnitName = Organizational Unit Name (eg, section)
organizationalUnitName_default = QWRtaW5pc3RyYXRpb24=

commonName  = Common Name (User\'s name the server\'s hostname)
commonName_max = 64

emailAddress = Email Address
emailAddress_max = 64

[ req_attributes ]
challengePassword = A challenge password
challengePassword_min = 4
challengePassword_max = 24

[ usr_cert ]
basicConstraints = CA:false

# These two are the only expected use cases at the time for this CA. If it is
# omitted the certificate can be used for anything *except* object signing.
# nsCertType = server
# nsCertType = client, email, objsign

# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment

nsComment = "Certificate issued by BedroomProgrammers.net"

# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer

# This stuff is for subjectAltName and issuerAltname.
# Import the email address.
# subjectAltName=email:copy
# An alternative to produce certificates that aren't
# deprecated according to PKIX.
# subjectAltName=email:move

# Location of the public cert
issuerAltName = URI:http://bedroomprogrammers.net/bpca.crt

# Information about the certificates
nsCaRevocationUrl = http://bedroomprogrammers.net/bpca.crl
nsBaseUrl = http://bedroomprogrammers.net/ssl/
nsRevocationUrl = http://bedroomprogrammers.net/ssl/revoke/?
nsRenewalUrl = http://bedroomprogrammers.net/ssl/renew/?
nsCaPolicyUrl = http://bedroomprogrammers.net/ssl/policy/

# This is required for TSA certificates.
# extendedKeyUsage = critical,timeStamping

[ v3_req ]
# Extensions to add to a certificate request

basicConstraints = CA:false
keyUsage = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]
# Extensions for a typical CA

# PKIX recommendation.
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer

# This may break some older clients
basicConstraints = critical,CA:true
# If this happens the certificate will have to be remade using this option:
#basicConstraints = CA:true

# Some might want this also
# nsCertType = sslCA, emailCA

# Include email address in subject alt name: another PKIX recommendation
# subjectAltName=email:copy
# Copy issuer details
issuerAltName = URI:http://bedroomprogrammers.net/bpca.crt

[ crl_ext ]
issuerAltName = URI:http://bedroomprogrammers.net/bpca.crt
authorityKeyIdentifier = keyid:always

[ proxy_cert_ext ]
basicConstraints=CA:false

# nsCertType = server
# nsCertType = objsign
# nsCertType = client, email
# nsCertType = client, email, objsign

# This is typical in keyUsage for a client certificate.
# keyUsage = nonRepudiation, digitalSignature, keyEncipherment

nsComment = "Certificate issued by BedroomProgrammers.net"

# PKIX recommendations harmless if included in all certificates.
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer

# Copy subject details
issuerAltName = issuer:copy

nsCaRevocationUrl = http://bedroomprogrammers.net/bpca.crl
nsBaseUrl = http://bedroomprogrammers.net/ssl/
nsRevocationUrl = http://bedroomprogrammers.net/ssl/revoke/?
nsRenewalUrl = http://bedroomprogrammers.net/ssl/renew/?
nsCaPolicyUrl = http://bedroomprogrammers.net/ssl/policy/

# This really needs to be in place for it to be a proxy certificate.
proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo

Create CA Key & Certificate

This section creates the CA private key, public certificate, and serial file using the openssl.cnf file above.

openssl req -new -keyout private/ca.key -out ca.csr -config openssl.cnf
openssl ca -create_serial -config ./openssl.cnf -out ca.crt -days 1095 -batch \
  -keyfile private/ca.key -selfsign -extensions v3_ca -infiles ca.csr

You will now want to distribute "ca.crt" to any client that will use a service with your CA.

Common Tasks

Simple Client Certificate Creation

First we'll need to create a key for client, this should be done on the client itself. It may be easier if the CA config (openssl.cnf) was distributed as well as there are some strict signing requirements in the above configuration where the country, state, and org name need to match exactly.

openssl genrsa 2048 > host.example.org.key
openssl req -new -key host.example.org.key -out host.example.org.csr \
  -extensions v3_req

Once the CSR has been generated transfer it to the host containing the CA, this assumes you have placed the file (and are currently within) the CA's directory.

openssl ca -config ./openssl.cnf -out host.example.org.crt -days 365 -batch \
  -keyfile private/ca.key -cert ca.crt -infiles host.example.org.csr

Advanced Client Certificate Creation

Creating a certificate with multiple domains requires one extra step, create a key and certificate as normal but create an additional file test.domain.net.cnf that includes something along the following lines:

subjectAltName=DNS:www.test.domain.net,DNS:*.test.domain.net,DNS:other.domain.net

When you sign the key you'll need to add the flag -extfile and specify the file with the above contents. This will append those domains to the certificate. An example way to sign the CSR:

openssl ca -config ./openssl.cnf -out test.domain.net.crt -days 365 -batch \
  -keyfile private/ca.key -cert ca.crt -infiles test.domain.net.csr \
  -extfile test.domain.net.cnf

Convert a Certificate for Microsoft Certificate Store

This will convert a standard OpenSSL certificate/key pair into a pfx for use by Microsoft products (fuck them for being different).

openssl pkcs12 -export -out keycert.pfx -inkey priv.key -in cert.crt

View the Contents of a Certificate

To view the text content of a certificate you can use the following command:

openssl x509 -text -noout -in cert.crt

You can also view the contents of a signing request using:

openssl req -text -noout -in cert.csr

Quick Self Signed Cert

openssl req -new -x509 -newkey rsa:4096 -keyout server.key -nodes -days 365 \
  -out server.crt

Add / Change a Password on a Keyfile

openssl rsa -des -in unprotected.key -out encrypted.key