Production considerations when running a certificate authority server

step-ca is built for robust certificate management in distributed systems. Running step-ca effectively in production requires some knowledge of its strengths and limitations. This document aims to illuminate and simplify the process of deploying step-ca safely and securely in a production environment.

Overview

Good Security Practices

In this section we recommend a few best practices when it comes to running, deploying, and managing your own online certificate authority server and PKI. Security is a moving target. We expect our recommendations to change and evolve as well.

Safeguard Your Root And Intermediate Keys

When you initialize a two-tier CA, two private keys are generated: one intermediate private key, and one root private key. It is very important that these private keys be kept secret.

The intermediate key is used by the CA to sign certificates. The root key is not needed for day-to-day CA operation and should be stored offline. The keys can be generated on an air-gapped device or on a Hardware Security Module (HSM).

Here's an example key protection strategy for a high-security production PKI.

In this example, step-ca acts as a subordinate CA to an offline root CA.

  1. Generate a root CA (private key and certificate) on a Hardware Security Module (HSM) or air-gapped device that is kept in "cold storage", off the internet. HSMs are ideal for storing private keys and performing signing operations securely. For durability, keep at least two copies of your root key, in separate locations.
  2. Generate intermediate key(s) on a separate, online cloud HSM or in a key management service (KMS) that will be used by the CA for signing operations in production
  3. Generate Certificate Signing Requests (CSRs) for your intermediate CA(s)
  4. Sign the generated CSR using the root HSM
  5. Configure step-ca to use the signed root and intermediate certificates
  6. Configure step-ca to access the cloud HSM or KMS intermediate key for online signing operations

See Cryptographic Protection to learn more about your options for using HSMs or cloud KMS with step-ca.

Use Strong Passwords and Store Them Well

When you initialize your PKI, the root and intermediate private keys will be encrypted with the same password.

After initializing your CA, we recommend that you immediately change the password for the intermediate CA private key:

step crypto change-pass $(step path)/secrets/intermediate_ca_key

You'll use this new intermediate key password to start step-ca.

Once you've changed the intermediate private key password, you should never have to use the root private key password again. So, then what should you do with it?

Bury it in a cave high in the mountains.

Or, store it in a password manager or secrets manager. There are many to choose from and the choice will depend on the risk & security profile of your organization.

In addition to using a password manager to store all passwords (private key, provisioner password, etc.) we recommend using a threshold cryptography algorithm like Shamir's Secret Sharing to divide the root private key password across a handful of trusted parties.

Avoid Storing Passwords in Environment Variables

systemd discourages using the environment for secrets because it doesn't consider it secure and exposes a unit's environment over dbus. From systemd.exec(5):

Note that environment variables are not suitable for passing secrets (such as passwords, key material, …) to service processes. Environment variables set for a unit are exposed to unprivileged clients via D-Bus IPC, and generally not understood as being data that requires protection. Moreover, environment variables are propagated down the process tree, including across security boundaries (such as setuid/setgid executables), and hence might leak to processes that should not have access to the secret data.

For some isolated environments, we could see an argument for the convenience of an environment variable. Even then, there can be subtle issues. For example, anyone with access to the Docker daemon can view all of the environment variables of running Docker containers, using docker inspect.

For posterity, however, if you've secured your environment and rely on it for secrets, there is a way to pass a password into step-ca from an environment variable in Bash:

step-ca --password-file <(echo -n "$STEP_CA_PASSWORD") $(step path)/config/ca.json

This method is known as Bash Process Subsitution, and on most systems the password will not appear in ps output. However, this approach is not recommended simply because it's so difficult to ensure security with environment variables.

See our blog How to Handle Secrets on the Command Line for an in-depth exploration of this topic.

Replace Your Default Provisioner

When you initialize your PKI, a default JWK provisioner will be created. If you're not going to use this provisioner, we recommend deleting it.

If you are going to use this provisioner, secure it with a different password than your CA's signing keys. You can do this by passing separate --password-file and --provisioner-password-file files when running step ca init.

See the section on managing provisioners.

Use Short-Lived Certificates

We recommend the following:

  • User certificates should have the lifespan of a mayfly: about a day or less[^1].
  • Host or service account certificates should have a lifetime of one month or less.

Certificates from step-ca expire in 24 hours by default. We made it easy for you to automate the renewal of your certificates using the step command. Carpe diem!

You can configure certificate lifetimes in the ca.json file.

1

One mayfly species, dolania americana, lives for five minutes or less! So do some certificates. But it can be difficult to operationalize such short-lived certificates.

Consider Active Revocation

You may require Active Revocation if you need immediate certificate revocation, or if you are issuing long-lived certificates. For this purpose, step-ca contains a built-in, minimal Certificate Revocation List (CRL) server.

This section describes how to enable CRL for your intermediate CA and leaf certificates.

When To Use Active Revocation?

The value of a two-tiered PKI is in the decoupling of Root and Intermediate CAs. You can add your Root CA certificate to the certificate trust store on all of your nodes, and store the private key completely offline. When step-ca issues a certificate to a client, it comes inside a PEM bundle that contains both the Intermediate CA certificate(s) and the end entity certificate. When establishing a TLS connection, any client that trusts your Root CA can use this bundle to verify a complete chain of trust.

Now, what if one day your Intermediate CA key is compromised? You could issue a new Intermediate using your root CA key, but your old Intermediate had a 10 year validity period! So, you're stuck having to rotate your Root CA too, and that may be a big project: you have to distribute the new CA certificate to clients, and ensure the old one is no longer trusted.

To avoid this scenario, you can use active revocation, making it possible to immediately revoke a compromised certificate.

Active revocation can also be used on leaf certificates. If a long-lived leaf certificate is compromised, it can be rendered unusable by an attacker through revocation.

But there are downsides: Hosting a Certificate Revocation List (CRL) adds a service dependency to your PKI. Clients check the CRL endpoint on every new connection, adding significant latency to the TLS handshake, and load on your CRL endpoint.

You can add active revocation to your Intermediate CA, to your leaf certificates, or both, using the instructions below.

Enable CRL for an Intermediate CA

To use active revocation on an Intermediate CA certificate, you'll need a new Intermediate CA that contains your CRL server URL.

If you're planning to use step-ca's built in CRL server, the CRL will be hosted at /1.0/crl.

  1. First, enable the built-in CRL server.

    Add the following to the top level of your $(step path)/config/ca.json file:

    "insecureAddress": ":9001",
    "crl": {
      "enabled": true,
      "idpURL": "http://ca.example.com/1.0/crl"
    },
    

    Reload the configuration by restarting step-ca or sending it a HUP signal.

  2. Create an Intermediate CA that includes a CRL endpoint. Save the following template to intermediate.tpl:

    {
            "subject": {{ toJson .Subject }},
            "keyUsage": ["certSign", "crlSign"],
            "basicConstraints": {
                    "isCA": true,
                    "maxPathLen": 0
            },
            "crlDistributionPoints": ["http://ca.example.com/1.0/crl"]
    }
    

    You'll need this template to manually create your Intermediate CA. The CRL endpoint in this example will be served by step-ca as configured below; the CRL file itself is signed.

    Use the template to create your Intermediate CA. You will need your root CA certificate and key:

    $ step certificate create \
         --template intermediate.tpl \
         --ca $(step path)/certs/root_ca.crt \
         --ca-key $(step path)/secrets/root_ca_key \
         --not-after 87660h \
         "Example Intermediate CA" \
         $(step path)/certs/intermediate_ca.crt \
         $(step path)/secrets/intermediate_ca_key
    
  3. Retart step-ca. Clients will be able to renew certificates that were issued by your previous Intermediate CA.

Enable CRL for Leaf Certificates

Let's add the CRL server URL to every leaf certificate issued by step-ca.

If you're planning to use step-ca's built in CRL server, the CRL will be hosted at /1.0/crl.

  1. If you haven't done so already, enable the built-in CRL server:

    Add the following to the top level of your $(step path)/config/ca.json file:

    "insecureAddress": ":9001",
    "crl": {
      "enabled": true,
      "idpURL": "http://ca.example.com/1.0/crl"
    },
    

    Reload the configuration by restarting step-ca or sending it a HUP signal.

  2. Add an X.509 certificate template, and enable crlDistributionPoints in the template:

    cd $(step path)/templates
    mkdir -p x509
    cat <<EOF > x509/leaf.tpl
    {
     "subject": {{ toJson .Subject }},
     "sans": {{ toJson .SANs }},
    {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
     "keyUsage": ["keyEncipherment", "digitalSignature"],
    {{- else }}
     "keyUsage": ["digitalSignature"],
    {{- end }}
     "extKeyUsage": ["serverAuth", "clientAuth"],
     "crlDistributionPoints": ["http://ca.example.com/1.0/crl"]
    }
    EOF
    
  3. Configure your step-ca provisioners to use the template you created.

    You can use, for example, step ca provisioner update with --x509-template x509/leaf.tpl. Or add the template to provisioner definitions in ca.json. See Configuring step-ca to Use Templates for an example.

  4. Create a test certificate using the provisioner you just reconfigured, and check that the correct CRL endpoint is shown.

  5. Confirm your CRL. Revoke the certificate you just created (using step ca revoke), then check the CRL served by step-ca:

    step crl inspect --ca $(step path)/certs/root_ca.crt http://ca.example.com:9001/1.0/crl
    

    Your certificate's serial number should be listed in the CRL.

Use Templates With Care

Certificate templates add a lot of flexibility to step-ca, and they can be a source of subtle vulnerabilities. Here are some tips for limiting your risk:

  • Write templates that are tightly restricted for your use case.
  • Use extreme caution when referencing user-supplied data, especially when it's marked as .Insecure in templates.
  • When enriching a certificate via webhooks, OIDC tokens, or other external sources, consider potential attack vectors and limit your CA's exposure as much as possible.
  • Finally, avoid JSON injection attacks by applying toJson to all template data evaluations.

Create a Service User to Run step-ca

Make sure that the configuration folders, private keys, and password file used by the CA are only accessible by this user. If you're running step-ca on port 443, you'll need the step-ca binary to be able to bind to that port. See Running step-ca as a Daemon for details.

Operational Concerns

Running step-ca as a Daemon

Note: This section requires a Linux OS running systemd version 245 or greater.

  1. Add a service user for the CA.

The service user will only be used by systemd to manage the CA. Run:

$ sudo useradd --user-group --system --home /etc/step-ca --shell /bin/false step

If your CA will bind to port 443, the step-ca binary will need to be given low port-binding capabilities:

$ sudo setcap CAP_NET_BIND_SERVICE=+eip $(which step-ca)
  1. Move your CA configuration into a system-wide location. Run:
$ sudo mv $(step path)/* /etc/step-ca

Make sure your CA password is located in /etc/step-ca/password.txt, so that it can be read upon server startup.

You'll also need to edit the file /etc/step-ca/config/defaults.json to reflect the new path.

Set the step user as the owner of your CA configuration directory:

$ sudo chown -R step:step /etc/step-ca

Modify your ca.conf file to point to the new database location:

cat <<< $(jq '.db.dataSource = "/etc/step-ca/db"' /etc/step-ca/config/ca.json) > /etc/step-ca/config/ca.json
  1. Create a systemd unit file.
$ sudo touch /etc/systemd/system/step-ca.service

Add the following contents:

[Unit]
Description=step-ca service
Documentation=https://smallstep.com/docs/step-ca
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=30
StartLimitBurst=3
ConditionFileNotEmpty=/etc/step-ca/config/ca.json
ConditionFileNotEmpty=/etc/step-ca/password.txt

[Service]
Type=simple
User=step
Group=step
Environment=STEPPATH=/etc/step-ca
WorkingDirectory=/etc/step-ca
ExecStart=/usr/bin/step-ca config/ca.json --password-file password.txt
ExecReload=/bin/kill --signal HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitInterval=30
StartLimitBurst=3

; Process capabilities & privileges
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SecureBits=keep-caps
NoNewPrivileges=yes

; Sandboxing
ProtectSystem=full
ProtectHome=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
PrivateTmp=true
PrivateDevices=true
ProtectClock=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelLogs=true
ProtectKernelModules=true
LockPersonality=true
RestrictSUIDSGID=true
RemoveIPC=true
RestrictRealtime=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
MemoryDenyWriteExecute=true
ReadWriteDirectories=/etc/step-ca/db

[Install]
WantedBy=multi-user.target

(This file is also hosted on GitHub)

Here are some notes on the security properties in this file:

  • User and Group cause step-ca to run as a non-privileged user.
  • AmbientCapabilities allows the process to receive ambient capabilities. CAP_NET_BIND_SERVICE allows the process to bind to ports < 1024. See capabiliites(7).
  • CapabilityBoundingSet limits the set of capabilities the process can have.
  • SecureBits allows the service to keep its capabilities even after switching to the step user.
  • NoNewPrivileges ensures no future privilege escalation by the process.
  • ProtectSystem and ProtectHome configure sandboxing via a read-only file system namespace dedicated to the process.
  • ProtectNamespaces prevents the process from creating kernel namespaces.
  • RestrictAddressFamilies prevents the service from allocating esoteric sockets such as AF_PACKET.
  • PrivateTmp gives the service its own private /tmp.
  • PrivateDevices presents a very limited /dev to the service.
  • Protect* limits access to system resources.
  • LockPersonality locks the process's execution domain.
  • RestrictSUIDSGID restricts setuid/setgid file creation.
  • RemoveIPC removes any IPC objects created by the service when it is stopped.
  • RestrictRealtime restricts real-time scheduling access.
  • SystemCallFilter defines an allow list of system calls the service can use.
  • SystemCallArchitectures restricts the service to only be able to call native system calls.
  • MemoryDenyWriteExecute prevents the service from creating writable-executable memory mappings.
  • ReadWriteDirectories ensures that the process can write its state directories.
  1. Enable and start the service.

The following are a few useful commands for checking the status of your CA, enabling it on system startup, and starting your CA.

# Rescan the systemd unit files
$ sudo systemctl daemon-reload

# Check the current status of the step-ca service
$ sudo systemctl status step-ca

# Enable and start the `step-ca` process
$ sudo systemctl enable --now step-ca

# Follow the log messages for step-ca
$ sudo journalctl --follow --unit=step-ca

High Availability

Whether your goal is load balancing or high availability, step-ca is built to scale horizontally. Our Helm chart is a popular approach for HA deployments in Kubernetes. We also have community-contributed Ansible, Salt, and Puppet examples for step-ca deployments.

A few things to consider when running multiple concurrent instances of step-ca:

  • Use a MySQL or Postgres database. The default Badger database has no concurrency support. Our MySQL and Postgres drivers support multiple instances. See our database documentation.
  • Respect concurrency limits. The ACME server has known concurrency limitations when using the same account to manage multiple orders. The recommended temporary workaround is to generate an ephemeral account keypair for each new ACME order, or to ensure that ACME orders owned by the same account are managed serially. The issue tracking this limitation can be found here.
  • Enable remote provisioner management.* With remote provisoiner management, your provisioner configuration is stored in the database and can be shared by all step-ca instances.
  • Synchronize ca.json across instances. step-ca reads all of it's configuration (and all of the provisioner configuration) from the ca.json file specified on the command line. If the ca.json of one instance is modified, the other instances will not pick up on this change until the ca.json is copied over to the correct location for each instance and the instance is sent SIGHUP or restarted. Use a configuration management tool like Ansible or Puppet to synchronize ca.json across instances.

Proxying step-ca traffic

You can reverse proxy step-ca traffic, using either a layer 4 (network or "TLS passthrough") or layer 7 (application) proxy server.

There's a few things you should know before you deploy this setup:

  • step will expect to be able to perform a TLS handshake with the proxy, and use the CA's root certificate to complete the trust chain. So, for inbound TLS connections, the proxy should use a server certificate issued by step-ca. See below for an example.
  • Certificate renewal via step ca renew uses mutual TLS authentication by default. Mutual TLS is incompatible with application-layer reverse proxying. When proxying traffic, pass --mtls false to step ca renew (or set STEP_MTLS to false). This will trigger an alternative renewal flow that employs authentication tokens. (See step ca renew --help for more details.)
  • By design, step-ca does not have an option to run in HTTP only. Philosophically, we value perimeterless security and we believe people should use encryption everywhere. Your proxy server should be configured to trust the step-ca root, to establish a verified TLS connection with your CA. Our design decision to require TLS in step-ca is detailed in certificates#246.

Here's an example of a Caddy reverse proxy configuration that uses ACME to get a server certificate from step-ca, for use with inbound connections to the proxy. In this setup, step-ca runs on the same machine as Caddy, on localhost:4443.

https://ca.example.com {
  # Inbound TLS connection configuration
  tls {
    # Configure Caddy's internal ACME client
    # to get a server certificate from `step-ca`
    issuer acme {
      dir https://127.0.0.1:4443/acme/acme/directory
      email carl@smallstep.com
      trusted_roots /etc/caddy/root_ca.crt
      disable_tlsalpn_challenge
    }
  }
  # Backend TLS connection to step-ca
  reverse_proxy https://127.0.0.1:4443 {
    transport http {
      # This allows the proxy to complete a trust
      # chain by trusting step-ca's root.
      tls_trust_pool file {
        pem_file /etc/caddy/root_ca.crt
      }
    }
  }
}

Further Reading

Exposing step-ca to the internet

With some precautions, step-ca may be exposed to the public internet.

If you choose to expose a CA to the internet, consider these potential risks:

  • Exposed, unauthenticated ACME provisioners
    By default, any ACME client that can reach the CA and satisfy an ACME challenge can enroll in your PKI.
  • Enumeration of SSH infrastructure
    The /ssh/hosts endpoint lists all enrolled hosts.
  • Enumeration of CA provisioners
    The /provisioners exposes provisioner configuration and is included for client convenience. It includes JWK encrypted private keys, OIDC client credentials and endpoint URLs, and more. Without it, clients will need to pass more configuration flags into step ca certificate.

Run A Reverse Proxy

Rather than expose your entire CA to the internet, use a reverse proxy to expose only the necessary CA endpoints:

  • /root/* will allow clients to bootstrap with step ca bootstrap
  • /renew for mutual TLS or token-based renewals
  • /1.0/sign for JWK certificate issuance
  • /provisioners adds client conveniences—without it, clients will need a copy of the JWK private key to sign JWTs

Expose the minimum endpoints required by your clients for enrollment.

Proxying an ACME Provisioner

When connecting an ACME provisioner to the internet, you will need to take precautions:

  • Use explicit policies to limit which subject names can request certificates via ACME.
  • Hide your ACME provisioner
    1. Block internet traffic to /provisioners, and the ACME provisioner URL cannot be discovered.

    2. Then, use an unguessable shared secret as your ACME provisioner name:

      {
      PROVISIONER_NAME=$(< /dev/urandom  LC_ALL=C tr -dc A-Za-z0-9 | head -c80)
      step ca provisioner update acme --name $PROVISIONER_NAME
      echo https://ca.example.com/acme/$PROVISIONER_NAME/directory
      }
      

Alternative: Use Federation

If your clients are spread across different private networks— eg. in multi-cloud environments— there's no need to expose your CA to the internet.

Certificate Lifecycle Management

Automate X.509 Certificate Lifecycle Management

We recommend automating certificate renewal when possible. Renewal can be easily automated in many environments. See our Renewal documentation for details.

Certificate Revocation

By default, step-ca uses passive revocation. Certificates can be revoked using the step ca revoke subcommand. See our Revocation documentation for details.

Root Distribution

To bootstrap trust, you will need to deploy your root CA certificate to clients. The step ca bootstrap command can be used on any CLI-based clients. In practice, however, certificates are everywhere, and step can't be used everywhere.

While we can't cover all the options for root distribution here, we can offer some approaches and examples.

Generally speaking:

  • You can inject a root CA PEM bundle into the environment when creating or configuring a resource. For example, in the User Data script for a new virtual machine, or in a new Dockerfile layer. Or, at container startup, you could volume mount your CA certificates into the container.
  • You can inject the root CA fingerprint, and download the certificate on startup or first boot. Use the /roots.pem endpoint in step-ca to download the root CA bundle. To bootstrap, you'll have to temporarily disable TLS server certificate validation in your client (eg. curl -k). After downloading the CA certificate, compare its SHA256 fingerprint with your known value. If you have access to openssl or the OpenSSL libraries, you can use openssl x509 -in root-ca.crt -noout -sha256 -fingerprint to get a certificate's fingerprint.
  • If your deployment environment is able to trust the Web PKI, you can leverage Web PKI trust to bootstrap your own PKI. Just host your CA certificate as a static file on an HTTPS endpoint.
  • For DevOps infrastructure, IaC tools can simplify root distribution and simplify the root rotation process down the road. However, many of these tools rely on ssh, which comes with its own bootstrapping challenges. See our blog post If you’re not using SSH certificates you’re doing SSH wrong for more.
  • For MDM environments, you can leverage the MDM channel's TLS connection, which is established upon MDM enrollment.

Installing a CA certificate

Your CA certificate may need to be installed and trusted into the target operating system's certificate trust store. The command for doing this various by operating system, distribution, and the target application. Confusingly, there may be several trust store libraries and locations on a given system.

Example: Linux Trust Anchors

Unfortunately, there is no single place on a Linux system where you can reliably mount this volume, because applications use several methods to access system CA certificates:

  • OpenSSL-based applications and many Linux distributions use the /etc/ssl/certs directory as the system trust store.
  • NSS uses a hard-coded list of trusted CA certificates inside the libnssckbi.so library, and it stores user-supplied certificates in SQL databases inside $HOME/.pki/nssdb or /etc/pki/nssdb.
  • Java applications use a special Java Keystore file and the keytool command to manage it.
  • Some applications (like step) don't trust any system trust store, or they have their own independent trust store or need to be configured with a CA bundle filename. Servers that need to validate client certificates generally won't trust the system trust store, and will require a CA bundle be passed in as a config parameter.

For distros that use standard ca-certificates paths

  • Debian
  • BusyBox
  • Ubuntu
  • Alpine

The recommended approach is to add the CA's PEM file to /usr/local/share/ca-certificates/, and run the update-ca-certificates command. This will generate the system-managed /etc/ssl/certs/ca-certificates.crt list of trusted certificates.

Arch Linux

Mount your PEM file into /etc/ca-certificates/trust-source/anchors/, then run update-ca-trust extract in the container to update the trust store.

RHEL 8

Put CA certificates into /etc/pki/ca-trust/source/anchors/ or /usr/share/pki/ca-trust-source/anchors/, and run update-ca-trust.

Further Reading

Rotating CA Certificates

It's possible to rotate both root and intermediate X.509 CA certificates.

A two-tiered PKI hierarchy simplifies intermediate CA rotation. Each leaf certificate is issued in a PEM bundle that includes the intermediate that signed it. The leaf certificate and intermediate CA travel together and are used to complete the trust chain.

Root CA rotation is trickier. It requires a migration period where the new root is introduced while the old root is still active and trusted. The old root has to be carefully decommissioned. Root rotation is out of scope for this document.

Rotating an Intermediate CA

Creating a new Intermediate CA will not affect previously-issued leaf certificates. When they were issued, these certificates were bundled with the old intermediate CA. When an old certificate is renewed, the entire PEM bundle is replaced. This way, intermediate CA rotation can be seamless.

To rotate your intermediate CA, you will need your root CA certificate and private key.

  1. Back up your previous intermediate CA and key. They are located in $(step path)/certs/intermediate_ca.crt and $(step path)/secrets/intermediate_ca_key
  2. Sign a new intermediate CA using your root CA. Be sure the intermediate expires at or before the root expiration date.
    $ step certificate create \
         --profile intermediate-ca \
         --ca $(step path)/certs/root_ca.crt \
         --ca-key $(step path)/secrets/root_ca_key \
         "Example Intermediate CA" \
         $(step path)/certs/intermediate_ca.crt \
         $(step path)/secrets/intermediate_ca_key
    
  3. Send a HUP signal to step-ca to read the new configuration.

New leaf certificates will now include and be signed by your new intermediate CA.

Caveat: Root + Intermediate Bundles

Typically, endpoints are configured to trust your root CA certificate only. But in some rare applications, intermediates and roots must be bundled together. This is not common.

If you have PEM bundles containing both your root and intermediate CAs, you will need an intermediate CA migration process:

  1. Create your new intermediate CA certificate and key, but do not start issuing certificates with it yet.
  2. Append the new intermediate certificate to the bundles that need it.
  3. Configure step-ca to use your new intermediate.
  4. Wait for all certificates issued by the old intermediate to expire.
  5. It is now safe to remove the old intermediate from the bundles.

Sane Cryptographic Defaults

We've done our best to provide sane defaults in step-ca, so that it's easy to use and hard to misuse. This section explains our reasoning behind step-ca's default parameters for tokens, keys, and certificates. These values are subject to change to reflect the current security and cryptography landscape.

Tokens

We use JWTs (JSON Web Tokens) to prove authenticity and identity within the step ecosystem. When configured well, JWTs are a great way to sign and encode data. It's easy to use JWTs insecurely, though, so you must be deliberate about how you validate and verify them (see RFC7519).

step-ca produces JWTs that:

  • are short-lived (5 minute lifespan)
  • are one-time-use tokens (during the lifetime of the step-ca)
  • have a 1 minute clock drift leeway

If you're using step-ca JWTs in your code, be sure to verify and validate every standard attribute of the JWT. step crypto jwt verify can validate any JWT for you, and it follows the spec to the letter.

Key Types and Ciphers

Supported Key Types: ECDSA, EdDSA, and RSA
Default Key Type: ECDSA
Default Curve Bits: P-256

We chose ECDSA keys because they offer better security and performance than RSA keys. At 256 bits, ECDSA keys provide 128 bits of security, and they are supported by most modern clients.

More notes on the choice of key type:

  • RSA keys are often chosen for compliance reasons.
  • EdDSA keys are even smaller and faster than ECDSA keys. Were it supported by more clients, it would be the default.
  • The NIST standard curves for ECDSA are hard to implement correctly, so there's concern that the implementations of them may have problems.
  • If the NSA is in your threat model, you may not want to use ECDSA keys. The NSA has never published how they chose the magic numbers that drive ECDSA implementations.

Default PEM Cipher: AES128
Supported PEM Key Sizes: 128, 192, and 256 bits

We've chosen the AES encryption algorithm for writing private keys to disk because it was the official choice of the Advanced Encryption Standard contest.

All supported key sizes are considered to be unbreakable for the foreseeable future. We chose 128 bits as our default because the performance is better as compared to the greater key sizes, and because 128 bits are sufficient for most security needs.

X.509 Certificates

Root CA Certificate

Validity (10 year window)

  • Not Before: Now
  • Not After: Now + 10 years

A 10 year window is advisable until software and tools can be written for rotating the root certificate.

Basic Constraints

  • CA: TRUE

    The root certificate is a certificate authority and will be used to sign other Certificates.

  • Path Length: 1

    The Path Length constraint expresses the number of possible intermediate CA certificates in a path built from an end-entity certificate up to the CA certificate.

    The default step PKI has only one intermediate CA certificate between end-entity certificates and the root CA certificate.

Key Usage

Key Usage describes how the certificate can be used.

  • Certificate Sign: indicates that our root public key will be used to verify a signature on certificates.
  • CRL Sign: indicates that our root public key will be used to verify a signature on revocation information, such as CRL.

Intermediate CA Certificate

The Path Length of the intermediate certificate is 0. Otherwise it uses the same defaults as the root certificate.

A Path Length of zero indicates that there can be no additional intermediary certificates in the path between the intermediate CA certificate and end-entity certificates.

Leaf (End Entity) Certificate

These are the certificates issued by the step-ca server.

Validity (24 hour window)

  • Not Before: Now
  • Not After: Now + 24 hours

The default is a 24hr window. This value is somewhat arbitrary. However, our goal is to have seamless end-entity certificate rotation. Rotating certificates frequently is a good security measure because it gives attackers very little time to form an attack and limits the usefulness of any single private key in the system.

We will continue to work towards decreasing this window because we believe it significantly reduces the probability and effectiveness of any attack.

Key Usage

Key Usage describes how the certificate can be used.

  • Key Encipherment: indicates that a certificate will be used with a protocol that encrypts keys.
  • Digital Signature: indicates that this public key may be used as a digital signature to support security services that enable entity authentication and data origin authentication with integrity.

Extended Key Usage

  • TLS Web Server Authentication: certificate can be used as the server side certificate in the TLS protocol.

  • TLS Web Client Authentication: certificate can be used as the client side certificate in the TLS protocol.

TLS Defaults

These are the defaults used for communication between step and step-ca.

Min TLS Version: TLS 1.2
Max TLS Version: TLS 1.3

The PCI Security Standards Council required all payment processors and merchants to move to TLS 1.2 and above by June 30, 2018. By setting TLS 1.2 as the default for all TLS protocol negotiation, we encourage our users to adopt the same security conventions.

Renegotiation: Never

TLS renegotiation significantly complicates the state machine and has been the source of numerous, subtle security issues. Therefore, by default we disable it.

Default TLS Cipher Suites

[
  "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
  "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
]

The default 'ciphersuites' are a list of two cipher combinations. For communication between services running step there is no need for cipher suite negotiation. The server can specify a single cipher suite which the client is already known to support.

Reasons for selecting TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305:

  • ECDHE key exchange algorithm has perfect forward secrecy
  • ECDSA has smaller keys and better performance than RSA
  • CHACHA20 with POLY1305 is the cipher mode used by Google.
  • CHACHA20's performance is better than GCM and CBC.

The http2 spec requires the TLS_ECDHE_(RSA|ECDSA)_WITH_AES_128_GCM_SHA256 ciphersuite be accepted by the server, therefore it makes our list of default ciphersuites.

Approved TLS Cipher Suites

[
  "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
  "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
  "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
  "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
  "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
  "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
  "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
  "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
  "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
  "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
  "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
  "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
]

Above is a list of step-approved cipher suites. Not all communication can be resolved with step TLS functionality. For those connections, the list of server supported cipher suites must have more options in case older clients do not support our favored cipher suite.

Reasons for selecting these cipher suites can be found in the following ssllabs article.

Last updated on February 12, 2024