Create Intermediate CA for Server Certificates (eg. Radius Server)

Table of contents

  1. Inizialize PKI
  2. Remove both root and intermediate keys and certificates
  3. Copy our existing root certificate to the our PKI (not the private key, just the cert)
  4. Generate a CSR (Certificate Signing Request) for the new Intermediate CA
  5. Set up Active Revocation
  6. Run your CA
  7. Set up Polices

What we will do, in order:

  1. Install step CLI tool and step-ca (see first page)
  2. Initialize PKI
  3. Remove both root and intermediate keys and certificates
  4. Copy our existing root certificate to the our PKI (not the private key, just the cert)
  5. Generate a CSR (Certificate Signing Request) for the new Intermediate CA, to be signed by our offline root, with the settings and parameters that we need (RSA key signing, Active revocation with CRL, etc)
  6. Set up Active Revocation
  7. Set up Policies

Inizialize PKI

step ca init

Getting started

After having installed the required tools, it’s time to init our CA. This will create a boilerplate which we’ll use as our base to build our CA.


step ca init \
--deployment-type=standalone \
--name="Example Intermediate CA" \
--dns="ca.example.com" \
--address=":443" \
--provisioner="admin@example.com" \
--provisioner-password-file=<file> \
--password-file=<file>

Let’s go through all required informations:

  • –deployment-type=standalone we don’t need to connect to any cloud services, so we select this mode
  • –name=name The name of the new PKI.
  • –dns=name The DNS name or IP address of the new CA. Use the ‘–dns’ flag multiple times to configure multiple DNS names.
  • –address=address The address that the new CA will listen at.
  • –provisioner=name The name of the first provisioner (JWT).
  • –provisioner-password-file=file The path to the file containing the password to encrypt the JWT provisioner key.
  • –password-file=file The path to the file containing the password to encrypt the keys. We can skip this in this case, since we’ll delete these keys and re-generate the intermediate key after

Remove both root and intermediate keys and certificates

As said, we just need the boilerplate to be in place, we don’t need any of the certs and keys generated here. We already have our own root, so we’ll delete everything.


rm ~/.step/certs/* ~/.step/secrets/*

Copy our existing root certificate to the our PKI (not the private key, just the cert)

I will use the scp command from my workstation, but you are free to use your preferred tool.


scp user@root-ca:</path/to/your/existing/root.crt> user@intermediate-ca:/<path/to/step>/certs/

Generate a CSR (Certificate Signing Request) for the new Intermediate CA

Generate a CSR (Certificate Signing Request) for the new Intermediate CA, to be signed by our offline root, with the settings and parameters that we need (RSA key signing, Active revocation with CRL, policies).

Intermediate CA, the Secure Way

Let’s put together a template with all parameters that we need.

Configure step-ca with an RSA certificate chain

Note that we added the CRL distribution endpoint in our Intermediate CA configuration, read below for more information.


cat <<EOF > rsa_intermediate_ca.tpl
{
	"subject": {{ toJson .Subject }},
	"keyUsage": ["certSign", "crlSign"],
	"basicConstraints": {
		"isCA": true,
		"maxPathLen": 0
	},
      "crlDistributionPoints": ["https://ca.example.com/crl"]
}
EOF

From now on, we’ll skip the signatureAlgorithm statement from templates, if you need to use different algorithms, you know how do to it.

Now we are ready to create our CSR (certificate signing request), notice the –csr option


step certificate create "Example Intermediate CA" \
    intermediate_ca.csr \
    $(step path)/secrets/intermediate_ca_key \
    --csr \
    --template rsa_intermediate_ca.tpl \
    --password-file password_file.txt \
    --kty RSA \
    --size 3072

We can now move this CSR to our root and sign it.

In order to have all the X509v3 extensions, we have to move over the template as well (or we could recreate it on our root).

So once you have moved over everything, run this command from the environment where your root key pair lives.

step certificate sign


step certificate sign \
    intermediate_ca.csr \
    <path_to_root_crt>/root.crt \
    <path_to_root_key>/root.key \
    --template rsa_intermediate_ca.tpl \
    --password-file password_file.txt \
    --not-after 87660h \
    > intermediate_ca.crt

We can now move back the signed certificate to our intermediate CA.


scp user@root-ca:<path>/intermediate_ca.crt user@intermediate-ca:/root/.step/certs/

Set up Active Revocation

Now it’s time to configure active revocation.

Enable active revocation on your intermediate ca <- this doc is outdated, you can find more details here while we wait for it to be updated

We have already set in the intermediate ca template, our CRL distribution point at “http://ca.example.com/crl”.

The only thing you need to do, in order to enable the CRL, is to edit your ca.json file and add the following configuration:


"crl": { "enabled": true }

There are a few more options you can add, for sure I’d suggest you to enable the GenerateOnRevoke option. The other options are dependent on your specific needs.

Check this post for more information.

GenerateOnRevoke: if set to true, the CRL is generated any time you revoke a certificate

CacheDuration: set the validity of the CRL (default to 24h)

RenewPeriod: set how frequently to renew the CRL (default to 16h)

IDPurl: (Issuing Distribution Point), it’s the url to reach the CRL, by default it will use your first DNS Name and make it available at the /crl endpoint

Run your CA

This is as easy as:


step-ca $(step path)/config/ca.json

Set up Polices

We may want to limit certificate issuance to a specific domain or IP, in this case we can set this as a policy on our intermediate.

Policies

If you have enabled the remote management, you can run the following commands:


step ca policy authority x509 allow dns "step" 
step ca policy authority x509 allow dns "*.local.example.com"
step ca policy authority x509 allow ip 10.0.0.0/24

Notice the first line, the purpose of this policy is to avoid locking you out, in fact the default name for the first JWT provisioner (which is created when you init your step-ca) is indeed step.

Otherwise you can go the old school way, manually editing the ca.json configuration file.

The “policy” object sits inside the “authority” object (policies can only be set at authority level and not at provisioner level):


"policy": {
    "x509": {
        "allow": {
            "dns": ["*.local.example.com"],
            "ip": ["10.0.0.0/24"]
        },
        "deny": {
            "dns": ["forbidden.local.example.com"],
            "ip": ["192.168.1.153"]
        },
        "allowWildcardNames": false,
    }
}