Create a certificate authority based on ECC

In this article I’m going to show you how to create a certificate authority based on Elliptic Curve Cryptography (ECC). It is useful in many situations, such as issuing server certificates to secure an intranet website, and issuing certificates to clients to allow them to authenticate to a server.

Here are the definitions of the acronyms used:

Information and requirement

These elements are to be taken into consideration to follow this article:

Preliminary

Directories for the root CA

sudo mkdir -p /etc/pki/tls/ca/{certs,crl,newcerts,private}
sudo chmod 700 /etc/pki/tls/ca/private
sudo touch /etc/pki/tls/ca/index.txt

Directories for the intermediate CA

sudo mkdir -p /etc/pki/tls/ca/intermediate/{certs,crl,csr,newcerts,private}
sudo chmod 700 /etc/pki/tls/ca/intermediate/private
sudo touch /etc/pki/tls/ca/intermediate/index.txt

Root CA configuration file

Create the general CA configuration file

Copy the following snippet into /etc/pki/tls/ca/openssl.cnf. Things you have to modify are:

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# directory and file locations
dir               = /etc/pki/tls/ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# the root key and root certificate
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

# for certificate revocation lists
crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_strict

[ policy_strict ]
# the root CA should only sign intermediate certificates that match
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# allow the intermediate CA to sign a more diverse range of certificates
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# options for the `req` tool (`man req`)
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead
default_md          = sha256

# extension to add when the -x509 option is used
x509_extensions     = v3_ca

[ req_distinguished_name ]
# see <https://en.wikipedia.org/wiki/Certificate_signing_request>
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# optionally, specify some defaults
countryName_default             = FR
stateOrProvinceName_default     = Rhone-Alpes
localityName_default            = Lyon
0.organizationName_default      = 
organizationalUnitName_default  =
emailAddress_default            =

[ v3_ca ]
# extensions for a typical CA (`man x509v3_config`)
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# extensions for a typical intermediate CA (`man x509v3_config`)
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# extensions for client certificates (`man x509v3_config`)
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# extensions for server certificates (`man x509v3_config`)
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName  = @alt_names
authorityInfoAccess = OCSP;URI:http://ocsp.domain.lan

[ crl_ext ]
# extension for CRLs (`man x509v3_config`)
authorityKeyIdentifier=keyid:always

[ ocsp ]
# extension for OCSP signing certificates (`man ocsp`)
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

[ alt_names ]
DNS.0 = Company name CA

Create the root key

In the following examples, the curve prime256v1 (NIST P-256) is used. You can choose another one by listing available curves for your OpenSSL version with openssl ecparam -list_curves.

sudo openssl ecparam -genkey -name prime256v1 -out /etc/pki/tls/ca/private/ca.key.pem

Set the correct permission

sudo chmod 400 /etc/pki/tls/ca/private/ca.key.pem

Create the root certificate

Use the root key (ca.key.pem) to create a root certificate (ca.cert.pem). Give the root certificate a long expiration date, such as twenty years. Once the root certificate expires, all certificates signed by the CA become invalid.

sudo openssl req -config /etc/pki/tls/ca/openssl.cnf -key /etc/pki/tls/ca/private/ca.key.pem -new -x509 -days 7300 -sha256 -extensions v3_ca -out /etc/pki/tls/ca/certs/ca.cert.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [FR]:
State or Province Name [Rhone-Alpes]:
Locality Name [Lyon]:
Organization Name []:Company name
Organizational Unit Name []:IT Department
Common Name []:Company name Root CA
Email Address []:contact@domain.lan

Verify the root certificate

sudo openssl x509 -noout -text -in /etc/pki/tls/ca/certs/ca.cert.pem

Intermediate CA configuration file

Create the general CA configuration file

Since Google Chrome 58, the company started making it mandatory to perform certificate checks on subject alternative names to match the site name with the certificate. Indeed if these two parameters do not match your domain name, Google Chrome and others will report you an error as follows.

NET::ERR_CERT_COMMON_NAME_INVALID

Copy the following snippet into /etc/pki/tls/ca/intermediate/openssl.cnf. Things you have to modify are:

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# directory and file locations
dir               = /etc/pki/tls/ca/intermediate
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

# the root key and root certificate
private_key       = $dir/private/intermediate.key.pem
certificate       = $dir/certs/intermediate.cert.pem

# for certificate revocation lists
crlnumber         = $dir/crlnumber
crl               = $dir/crl/intermediate.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_loose

[ policy_strict ]
# the root CA should only sign intermediate certificates that match
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# allow the intermediate CA to sign a more diverse range of certificates
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# options for the `req` tool (`man req`)
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead
default_md          = sha256

# extension to add when the -x509 option is used
x509_extensions     = v3_ca

[ req_distinguished_name ]
# see <https://en.wikipedia.org/wiki/Certificate_signing_request>
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# optionally, specify some defaults
countryName_default             = FR
stateOrProvinceName_default     = Rhone-Alpes
localityName_default            = Lyon
0.organizationName_default      = 
organizationalUnitName_default  =
emailAddress_default            =

[ v3_ca ]
# extensions for a typical CA (`man x509v3_config`)
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# extensions for a typical intermediate CA (`man x509v3_config`)
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# extensions for client certificates (`man x509v3_config`)
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# extensions for server certificates (`man x509v3_config`)
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName  = @alt_names
authorityInfoAccess = OCSP;URI:http://ocsp.domain.lan

[ crl_ext ]
# extension for CRLs (`man x509v3_config`)
authorityKeyIdentifier=keyid:always

[ ocsp ]
# extension for OCSP signing certificates (`man ocsp`)
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning

[ alt_names ]
DNS.0 = domain.lan
DNS.1 = *.domain.lan

Create the intermediate key

sudo openssl ecparam -genkey -name prime256v1 -out /etc/pki/tls/ca/intermediate/private/intermediate.key.pem

Create the intermediate certificate

Use the intermediate key to create a CSR. The details should generally match the root CA. The CN, however, must be different.

sudo openssl req -config /etc/pki/tls/ca/intermediate/openssl.cnf -new -sha256 -key /etc/pki/tls/ca/intermediate/private/intermediate.key.pem -out /etc/pki/tls/ca/intermediate/csr/intermediate.csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [FR]:
State or Province Name [Rhone-Alpes]:
Locality Name [Lyon]:
Organization Name []:Company name
Organizational Unit Name []:IT Department
Common Name []:Company name intermediate CA
Email Address []:contact@domain.lan

To create an intermediate certificate, use the root CA with the v3_intermediate_ca extension to sign the intermediate CSR. The intermediate certificate should be valid for a shorter period than the root certificate. Ten years would be reasonable.

sudo openssl ca -config /etc/pki/tls/ca/openssl.cnf -create_serial -extensions v3_intermediate_ca -days 3650 -notext -md sha256 -in /etc/pki/tls/ca/intermediate/csr/intermediate.csr.pem -out /etc/pki/tls/ca/intermediate/certs/intermediate.cert.pem
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
sudo chmod 444 /etc/pki/tls/ca/intermediate/certs/intermediate.cert.pem

The index.txt file is where OpenSSL stores the certificate database. Do not delete or edit this file by hand. It should now contain a line that refers to the intermediate certificate.

Verify the intermediate certificate

As we did for the root certificate, check that the details of the intermediate certificate are correct.

sudo openssl x509 -noout -text -in /etc/pki/tls/ca/intermediate/certs/intermediate.cert.pem

Verify the intermediate certificate against the root certificate. An OK indicates that the chain of trust is intact.

sudo openssl verify -CAfile /etc/pki/tls/ca/certs/ca.cert.pem /etc/pki/tls/ca/intermediate/certs/intermediate.cert.pem
/etc/pki/tls/ca/intermediate/certs/intermediate.cert.pem: OK

Create the certificate chain file

When an application (e.g. a web browser) tries to verify a certificate signed by the intermediate CA, it must also verify the intermediate certificate against the root certificate. To complete the chain of trust, create a CA certificate chain to present to the application.

To create the CA certificate chain, concatenate the intermediate and root certificates together. We will use this file later to verify certificates signed by the intermediate CA.

sudo cat /etc/pki/tls/ca/intermediate/certs/intermediate.cert.pem /etc/pki/tls/ca/certs/ca.cert.pem | sudo tee -a /etc/pki/tls/ca/intermediate/certs/ca-chain.cert.pem > /dev/null
sudo chmod 444 /etc/pki/tls/ca/intermediate/certs/ca-chain.cert.pem

Our certificate chain file must include the root certificate because no client application knows about it yet. A better option, particularly if you’re administrating an intranet, is to install your root certificate on every client who needs to connect. In that case, the chain file only contains your intermediate certificate.

Sign server and client certificates

We will be signing certificates using our intermediate CA. You can use these signed certificates in many situations, such as securing connections to a web server or authenticating clients connecting to a service.

The steps below are from your perspective as the certificate authority. A third-party, however, can instead create his own private key and certificate signing request (CSR) without revealing his private key to you. They give you their CSR, and you give back a signed certificate. In that scenario, skip the ecparam and req commands.

Create a key

sudo openssl ecparam -genkey -name prime256v1 -out /etc/pki/tls/ca/intermediate/private/domain.lan.key.pem
sudo chmod 400 /etc/pki/tls/ca/intermediate/private/domain.lan.key.pem

Create a certificate

Use the private key to create a CSR. The CSR details don’t need to match the intermediate CA. For server certificates, the CN must be a fully qualified domain name (e.g. domain.lan), whereas for client certificates it can be any unique identifier (e.g. an e-mail address). Note that the CN cannot be the same as either your root or intermediate certificate.

sudo openssl req -config /etc/pki/tls/ca/intermediate/openssl.cnf -key /etc/pki/tls/ca/intermediate/private/domain.lan.key.pem -new -sha256 -out /etc/pki/tls/ca/intermediate/csr/domain.lan.csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [FR]:
State or Province Name [Rhone-Alpes]:
Locality Name [Lyon]:
Organization Name []:Great Corporation
Organizational Unit Name []:IT Department
Common Name []:domain.lan
Email Address []:contact@domain.lan

To create a certificate, use the intermediate CA to sign the CSR. If the certificate is going to be used on a server, use the server_cert extension. If the certificate is going to be used for user authentication, use the usr_cert extension. Certificates are usually given a validity of one year, though a CA will typically give a few days extra for convenience.

sudo openssl ca -config /etc/pki/tls/ca/intermediate/openssl.cnf -create_serial -extensions server_cert -days 375 -notext -md sha256 -in /etc/pki/tls/ca/intermediate/csr/domain.lan.csr.pem -out /etc/pki/tls/ca/intermediate/certs/domain.lan.cert.pem
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
sudo chmod 444 /etc/pki/tls/ca/intermediate/certs/domain.lan.cert.pem

The /etc/pki/tls/ca/intermediate/index.txt file should contain a line referring to this new certificate.

Verify the certificate

sudo openssl x509 -noout -text -in /etc/pki/tls/ca/intermediate/certs/domain.lan.cert.pem

The Issuer is the intermediate CA. The Subject refers to the certificate itself.

The output will also show the X509v3 extensions. When creating the certificate, you used either the server_cert or usr_cert extension. The options from the corresponding configuration section will be reflected in the output.

Use the CA certificate chain file we created earlier (ca-chain.cert.pem) to verify that the new certificate has a valid chain of trust.

sudo openssl verify -CAfile /etc/pki/tls/ca/intermediate/certs/ca-chain.cert.pem /etc/pki/tls/ca/intermediate/certs/domain.lan.cert.pem
/etc/pki/tls/ca/intermediate/certs/domain.lan.cert.pem: OK

Deploy the certificate

You can now either deploy your new certificate to a server or distribute the certificate to a client. When deploying to a server application (e.g. Apache2 or Nginx), you need to make the following files available.

The certificate chain

/etc/pki/tls/ca/intermediate/certs/ca-chain.cert.pem

The key

/etc/pki/tls/ca/intermediate/private/domain.lan.key.pem

The certificate

/etc/pki/tls/ca/intermediate/certs/domain.lan.cert.pem

If you’re signing a CSR from a third-party, you don’t have access to his private key so you only need to give back the chain file (ca-chain.cert.pem) and the certificate (domain.lan.cert.pem).

Online Certificate Status Protocol

The Online Certificate Status Protocol (OCSP) was created as an alternative to CRL. Similar to CRL, OCSP enables a requesting party (e.g. a web browser) to determine the revocation state of a certificate.

When a CA signs a certificate, it will typically include an OCSP server address (here http://ocsp.domain.lan) in the certificate.

As an example, when a web browser is presented with a server certificate, it will send a query to the OCSP server address specified in the certificate. At this address, an OCSP responder listens to queries and responds with the revocation status of the certificate.

Please note that it’s recommended to use OCSP as far as possible, though realistically you will tend to only need OCSP for website certificates. Some web browsers have deprecated or removed support for CRL.

Create the OCSP pair

The OCSP responder requires a cryptographic pair for signing the response that it sends to the requesting party. The OCSP cryptographic pair must be signed by the same CA that signed the certificate being checked.

Create a key

sudo openssl ecparam -genkey -name prime256v1 -out /etc/pki/tls/ca/intermediate/private/ocsp.domain.lan.key.pem
sudo chmod 400 /etc/pki/tls/ca/intermediate/private/ocsp.domain.lan.key.pem

Create a certificate

The details should generally match those of the signing CA. The Common Name, however, must be the fully qualified domain name (ocsp.domain.lan).

sudo openssl req -config /etc/pki/tls/ca/intermediate/openssl.cnf -key /etc/pki/tls/ca/intermediate/private/ocsp.domain.lan.key.pem -new -sha256 -out /etc/pki/tls/ca/intermediate/csr/ocsp.domain.lan.csr.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [FR]:
State or Province Name [Rhone-Alpes]:
Locality Name [Lyon]:
Organization Name []:Company name
Organizational Unit Name []:IT Department
Common Name []:ocsp.domain.lan
Email Address []:contact@domain.lan

Sign the CSR with the intermediate CA

sudo openssl ca -config /etc/pki/tls/ca/intermediate/openssl.cnf -create_serial -extensions ocsp -days 375 -notext -md sha256 -in /etc/pki/tls/ca/intermediate/csr/ocsp.domain.lan.csr.pem -out /etc/pki/tls/ca/intermediate/certs/ocsp.domain.lan.cert.pem
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
sudo chmod 444 /etc/pki/tls/ca/intermediate/certs/ocsp.domain.lan.cert.pem

Verify that the certificate has the correct X509v3 extensions.

sudo openssl x509 -noout -text -in /etc/pki/tls/ca/intermediate/certs/ocsp.domain.lan.cert.pem
[...]
X509v3 Key Usage: critical
    Digital Signature
X509v3 Extended Key Usage: critical
    OCSP Signing
[...]

Create a test certificate

To do this, please follow the Sign server and client certificates part. For this example, I will create a test certificate linked to prod.domain.lan.

Launch the OpenSSL OCSP responder tool

The OpenSSL ocsp tool can act as an OCSP responder, but it’s only intended for testing.

sudo openssl ocsp -index /etc/pki/tls/ca/intermediate/index.txt -port 2560 -rsigner /etc/pki/tls/ca/intermediate/certs/ocsp.domain.lan.cert.pem -rkey /etc/pki/tls/ca/intermediate/private/ocsp.domain.lan.key.pem -CA /etc/pki/tls/ca/intermediate/certs/ca-chain.cert.pem -text -nrequest 1

Run the OCSP responder on 127.0.0.1:2560. Rather than storing revocation status in a separate CRL file, the OCSP responder reads index.txt directly. The response is signed with the OCSP cryptographic pair (using the -rkey and -rsigner flags).

In another terminal, send a query to the OCSP responder. The -cert flag specifies the certificate to query.

sudo openssl ocsp -CAfile /etc/pki/tls/ca/intermediate/certs/ca-chain.cert.pem -url http://127.0.0.1:2560 -resp_text -issuer /etc/pki/tls/ca/intermediate/certs/intermediate.cert.pem -cert /etc/pki/tls/ca/intermediate/certs/prod.domain.lan.cert.pem

The start of the output shows:

Revoke the test certificate

sudo openssl ca -config /etc/pki/tls/ca/intermediate/openssl.cnf -revoke /etc/pki/tls/ca/intermediate/certs/prod.domain.lan.cert.pemA
Using configuration from /etc/pki/tls/ca/intermediate/openssl.cnf
Revoking Certificate 767C...
Data Base Updated

As before, run the OCSP responder and send the query on another terminal. This time, the output shows Cert Status: revoked and a Revocation Time.

As soon as I’m done testing to set up an OCSP responder, I’ll add the information here. Check back soon!

Miscellaneous

Add the root certificate into the trust policy store

Rocky Linux

sudo trust anchor --store /etc/pki/tls/ca/ca.cert.pem
sudo update-ca-trust

Windows

Windows (servers and workstations) use .pfx files that contain the public key file and the associated private key. You can bundle them with openssl-pkcs12.

sudo openssl pkcs12 -inkey /etc/pki/tls/ca/private/ca.key.pem -in /etc/pki/tls/ca/certs/ca.cert.pem -export -out /etc/pki/tls/ca/private/domain.lan.pfx
Enter Export Password:
Verifying - Enter Export Password:

Then add it via the certmgr.msc console.