Building an OpenSSL Certificate Authority - Creating Your Root Certificate

Creating Your Root Certificate Authority

In our previous article, Introductions and Design Considerations for Eliptical Curves we covered the design requirements to create a two-tier ECC certificate authority based on NSA Suite B's PKI requirements. We can now begin creating our CA's root configuration. Creating the root CA requires us to generate a certificate and private key, since this is the first certificate we're creating, it will be self-signed. The root CA will not sign client and server certificates, it's job it only to create intermeidary certificates and act as the root of our chain of trust. This is standard practice across the public and private PKI configurations and so too should your lab environments.

Create Your Directory Structure

Create a directory to store your root CA pair and config files.

# sudo bash
# mkdir /root/ca

Yep, I did that. This is for a test lab and permissions may not match real world requirements. I sudoed into bash and created everything under root; aka playing with fire.  This affects ownership down the line if you

 private key files and directories to user access only so determine for yourself what user/permission will be accessing files for certificate creation. I have a small team and trust them with root within a lab environment (snapshots allow me to be this trusting).

Create your CA database to keep track of signed certificates

# cd /root/ca
# mkdir private certs crl
# touch index.txt
# echo 1000 > serial

We begin by creating a working root directory with sub directories for the various files we'll be creating. This will allow you to apply your preferred security practices should you choose to do so. Since this is a test lab and I am operating as root, I won't be chmod'ing anything today. 

Create Your OpenSSL Config File

OpenSSL uses configuration files to simplify/template the components of a certificate.  Copy the GIST openssl_root.cnf file to

which is already prepared for this demo. For the root CA certificate creation, the
[ CA ]
 section is required and will gather it's configuration from the
[ CA_default ]

[ ca ]
# `man ca`
default_ca = CA_default


section in the openssl_root.cnf file contains the variables OpenSSL will use for the root CA. If you're using alternate directory names from this demo, update the file accordingly. Note the long values for default days (10 years) as we don't care about renewing the root certificate anytime soon.

  [ CA_default ]
  # Directory and file locations.
  dir               = /root/ca
  certs             = $dir/certs
  crl_dir           = $dir/crl
  new_certs_dir     = $dir/certs
  database          = $dir/index.txt
  serial            = $dir/serial
  RANDFILE          = $dir/private/.rand

  # The root key and root certificate.
  private_key       = $dir/private/ca.cheese.key.pem
  certificate       = $dir/certs/ca.cheese.crt.pem

  # For certificate revocation lists.
  crlnumber         = $dir/crlnumber
  crl               = $dir/crl/ca.cheese.crl.pem
  crl_extensions    = crl_ext
  default_crl_days  = 3650

  # SHA-1 is deprecated, so use SHA-2 or SHA-3 instead.
  default_md        = sha384

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

For the root CA, we define

which will later force the intermediary's certificate to match country, state/province, and organization name fields.

[ policy_strict ]
The root CA should only sign intermediate certificates that match.
# See POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional


[ req ]
section is used for OpenSSL certificate requests.  Some of the values listed will not be used since we are manually specifying them during certificate creation.

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, please use SHA-2 or greater instead.
default_md          = sha384

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

I pre-populate the

[ req_distinguished_name 
] section with values I'll commonly used to save typing down the road.

[ req_distinguished_name ]
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             = US
stateOrProvinceName_default     = WA
localityName_default            = Seattle
0.organizationName_default      = Grilled Cheese Inc.
organizationalUnitName_default  = Grilled Cheese Root CA
emailAddress_default            =


[ v3_ca ]
section will further define the Suite B PKI requirements, namely
and acceptable
values for a CA certificate. This section will be used for creating the root CA's certificate.

[ 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

Selecting the Suite B compliant elliptical curve

We're creating a Suite B infrastructure so we'll need to pick an acceptable curve following P-256 or P-384. To do this, run the following OpenSSL command:

openssl ecparam -list_curves

This will give you a long list of options but which one to pick? Let's isolate the suites within the 256 & 384 prime fields; we can grep the results for easier curve identification.

openssl ecparam -list_curves | grep '256\|384'

And we get the following results (your results may vary depending on the version of OpenSSL running):

# openssl ecparam -list_curves | grep '256\|384'

    secp256k1 : SECG curve over a 256 bit prime field
    secp384r1 : NIST/SECG curve over a 384 bit prime field
    prime256v1: X9.62/SECG curve over a 256 bit prime field
    brainpoolP256r1: RFC 5639 curve over a 256 bit prime field
    brainpoolP256t1: RFC 5639 curve over a 256 bit prime field
    brainpoolP384r1: RFC 5639 curve over a 384 bit prime field
    brainpoolP384t1: RFC 5639 curve over a 384 bit prime field

I am going to use secp384r1 as my curve of choice. It's good to mention that RFC5480 notes secp256r1 (not listed) is referred to as prime256v1 for this output's purpose. Why not use something larger than 384? Thank Google. People absolutely were using secp521r1 then Google dropped support for it (read Chromium Bug 478225 for more). The theory is since NSA Suite B PKI did not explicitly call out anything besides 256 or 384, the Chromium team quietly decided it wasn't needed and dropped support for it. Yea... it kinda annoyed a few people. So to avoid future browser issues, we're sticking with what's defined in public standards.

Create the Root CA's Private Key

Using the names defined in the

private_key value and our selected secp384r1 ECC curve we will create and encrypt the root certificates private key.

# openssl ecparam -genkey -name secp384r1 | openssl ec -aes256 -out private/ca.cheese.key.pem

  read EC key
  writing EC key
  Enter PEM pass phrase: ******
  Verifying - Enter PEM pass phrase: ******

Note:The ecparam function within OpenSSL does not encrypt the private key like genrsa/gendsa/gendh does. Instead we combined the private key creation (openssl ecparam) with a secondary encryption command (openssl ec) to encrypt private key before it is written to disk. Keep the password safe.

Create the Root CA's Certificate

Using the new private key, we can now generate our root's self-signed certificate. We do this because the root has no authority above it to request trust authority from; it is the absolute source of authority in our certificate chain.

# openssl req -config openssl_root.cnf -new -x509 -sha384 -extensions v3_ca -key private/ca.cheese.key.pem -out certs/ca.cheese.crt.pem

  Enter pass phrase for private/ca.cheese.key.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) [US]:
  State or Province Name [WA]:
  Locality Name [Seattle]:
  Organization Name [Grilled Cheese Inc.]:
  Organizational Unit Name [Grilled Cheese Root CA]:
  Common Name []:Grilled Cheese Root Certificate Authority
  Email Address []:

Using OpenSSL we can validate the Certificate contents to ensure we're following the NSA Suite B requirements.

# openssl x509 -noout -text -in certs/ca.cheese.crt.pem

          Version: 3 (0x2)
          Serial Number:
      Signature Algorithm: ecdsa-with-SHA384
          Issuer: C = US, ST = WA, L = Seattle, O = Grilled Cheese Inc., OU = Grilled Cheese Root CA, CN = Grilled Cheese Inc. Root Certificate Authority, emailAddress =
              Not Before: Aug 22 23:53:05 2017 GMT
              Not After : Aug 20 23:53:05 2027 GMT
          Subject: C = US, ST = WA, L = Seattle, O = Grilled Cheese Inc., OU = Grilled Cheese Root CA, CN = Grilled Cheese Inc. Root Certificate Authority, emailAddress =
          Subject Public Key Info:
              Public Key Algorithm: id-ecPublicKey
                  Public-Key: (384 bit)
                  ASN1 OID: secp384r1
                  NIST CURVE: P-384
          X509v3 extensions:
              X509v3 Subject Key Identifier:
              X509v3 Authority Key Identifier:

              X509v3 Basic Constraints: critical
              X509v3 Key Usage: critical
                  Digital Signature, Certificate Sign, CRL Sign
      Signature Algorithm: ecdsa-with-SHA384

Reviewing the above we can verify the certificate details:

  • The Suite B Signature Algorithm: ecdsa-with-SHA384
  • The certificate date validity when we specificed -days 3650:
    • Not Before: Aug 22 23:53:05 2017 GMT
    • Not After : Aug 20 23:53:05 2027 GMT
  • The Public-Key bit length: (384 bit)
  • The Issuer we defined in the openssl_root.cnf: C = US, ST = WA, L = Seattle, O = Grilled Cheese Inc., OU = Grilled Cheese Root CA, CN = Grilled Cheese Inc. Root Certificate Authority
  • The Certificate Subject, since this is self-signed, refers back to itself: Subject: C = US, ST = WA, L = Seattle, O = Grilled Cheese Inc., OU = Grilled Cheese Root CA, CN = Grilled Cheese Inc. Root Certificate Authority
  • The eliptical curve used when we created the private key: NIST CURVE: P-384

Verify the X.509 v3 extensions we defined within the

for a Suite B CA use:

X509v3 Basic Constraints: critical
X509v3 Key Usage: critical
    Digital Signature, Certificate Sign, CRL Sign

The root certificate and private key are now compete and we have the first part of our CA complete. Step 1 complete! In our next article we will create the intermediary certificate to complete the chain of trust in our two-tier hierarchy.

Updated Jun 06, 2023
Version 2.0

Was this article helpful?


  • Very helpful series. Thanks for taking the time. Maybe it's a bug in my version of openssl (1.1.0f), maybe I've got an error somewhere in my root.conf file, maybe it's just a feature of openssl I don't know about - but while creating the root CA certificate, I must specify the -days option on the command line. Otherwise openssl completely ignores the 'default_days' configuration value, and generates a certificate that is valid for exactly one month.


  • Hi tty72. I deleted my VPC with this CA to move to a new AWS account. I'll rebuild this all and let you know what I run into with 1.1.0f. I am using this root CA config file and calling it out in my openssl req command at -config openssl_root.cnf.


  • Hello Chase, thanks for the great series. I got the same issue highlighted by tty72, where the root CA validity was set to exactly 1 month after the issue date, instead of 10 years as is specified in the config file. I am using OpenSSL 1.1.0f on Ubuntu 18.04 as well.

  • @fesoliveira I still have some more updates to do on this and will get that fixed.

  • The github link is not working anymore, where can we get the openssl_root.dnf example?

  • if you want to have something simple on Windows / MacOS with a GUI have a look at xca

  • Hi Chase,

    Thanks very much for excellent and detailed lessons on how to create Root CA and Intermediate Certificates. You've covered all the revelant variables in each scenario, of which I still coudn't find after extensive searches from other websites. 

    The Root CA lesson was easy to follow.

    However, the Intermediate Certificate lesson was a bit curly to follow, as detailed below:

    1. Uncertain whether to use "[CA]" and "[CA_default]" sections OR "[int_ca]", but I used the former sections and they work:


    and modify the contents for your own naming conventions. Similar to the


    , the


    is required and will gather it's configuration from the


    section. Changes to the



    [ CA_default ]
    # Directory and file locations.
    dir = /root/ca/intermediate
    private_key = $dir/private/int.cheese.key.pem
    certificate = $dir/cers/int.cheese.crt.pem
    crlnumber = $dir/crlnumber
    crl = $dir/crl/int.cheese.crl.pem
    crl_extensions = crl_ext
    policy = policy_loose

    We have new certificate names for our intermediary use and define


    2. v3_intermediate_ca:

    Does not matched with Root CA.

    Output: [root@ca ca]# openssl ca -config ca.cnf -extensions v3_intermediate_ca -days 256 -md sha384 -in intermediate/csr/int.sansui.csr -out intermediate/certs/int.sansui.crt.pem
    Using configuration from ca.cnf
    Enter pass phrase for /root/ca/private/ca.sansui.key.pem:
    Error Loading extension section v3_intermediate_ca
    140437868918672:error:02001002:system library:fopen:No such file or directory:bss_file.c:175:fopen('/root/ca/index.txt.attr','rb')
    140437868918672:error:2006D080:BIO routines:BIO_new_file:no such file:bss_file.c:182:
    140437868918672:error:0E078072:configuration file routines:DEF_LOAD:no such file:conf_def.c:195:
    140437868918672:error:0E06D06C:configuration file routines:NCONF_get_string:no value:conf_lib.c:324:group=CA_default name=email_in


    3. Missing Distinguished Name in "intermediate.cnf" file:


    [root@ca ca]# openssl req -config intermediate/intermediate.cnf -new -newkey ec:<(openssl ecparam -name secp384r1) -keyout intermediate/private/int.sansui.key.pem -out intermediate/csr/int.sansui.csr
    Generating a 384 bit EC private key
    writing new private key to 'intermediate/private/int.sansui.key.pem'
    Enter PEM pass phrase:
    Verifying - Enter PEM pass phrase:
    unable to find 'distinguished_name' in config
    problems making Certificate Request
    140128708568976:error:0E06D06C:configuration file routines:NCONF_get_string:no value:conf_lib.c:324:group=req name=distinguished_name

    I got it working with some minor changes.


    Can you please clarify where do I put the following certificates:




    Can you please explain the next Step with regards to the End Entity Certifate, ie which Certificate is used to sign it or what do I need to do with it to complete the chain of trust, etc..?




    Phuc Le