Configuring step-ca

When the step ca init command sets up your PKI, it creates an initial configuration file for your CA and stores it in $STEPPATH/config/ca.json.

In this document we'll describe how to configure every aspect of step-ca. You'll learn how to configure your CA to:

  • bind to a non default address
  • generate ACME certificates
  • adjust the default lifetime of certificates
  • store certificates in memory, on the file system, or in a database
  • set the root and intermediate PKI chain that will be used to sign new certificates
  • and much more

Overview

Specifying a Configuration File

When you run step-ca, you must provide a path to a configuration file (ca.json). It is the only required argument. Typically you'd use step path to discover the path to ca.json:

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

For non-interactive use (eg. as a systemd service), you can also add a --password-file flag with the name of a file containing the password for the CA's keys.

Environment Variables

  • STEPDEBUG When set to 1, step-ca will provide extra diagnostic information for debugging. This variable can also be used with step.

Basic Configuration Options

Example Configuration

{ "root": "examples/pki/secrets/root_ca.crt", "federatedRoots": "examples/pki/secrets/federated_root_ca.crt", "crt": "examples/pki/secrets/intermediate_ca.crt", "key": "examples/pki/secrets/intermediate_ca_key", "address": ":9000", "dnsNames": [ "localhost" ], "logger": { "format": "text" }, "db": { "type": "badger", "dataSource": "./.step/db", }, "authority": { "claims": { "minTLSCertDuration": "5m", "maxTLSCertDuration": "24h", "defaultTLSCertDuration": "24h", "disableRenewal": false, "minHostSSHCertDuration": "5m", "maxHostSSHCertDuration": "1680h", "defaultHostSSHCertDuration": "720h", "minUserSSHCertDuration": "5m", "maxUserSSHCertDuration": "24h", "defaultUserSSHCertDuration": "16h", }, "provisioners": [ { "type": "jwk", "name": "mike@smallstep.com", "key": { "use": "sig", "kty": "EC", "kid": "YYNxZ0rq0WsT2MlqLCWvgme3jszkmt99KjoGEJJwAKs", "crv": "P-256", "alg": "ES256", "x": "LsI8nHBflc-mrCbRqhl8d3hSl5sYuSM1AbXBmRfznyg", "y": "F99LoOvi7z-ZkumsgoHIhodP8q9brXe4bhF3szK-c_w" }, "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiVERQS2dzcEItTUR4ZDJxTGo0VlpwdyJ9.2_j0cZgTm2eFkZ-hrtr1hBIvLxN0w3TZhbX0Jrrq7vBMaywhgFcGTA.mCasZCbZJ-JT7vjA.bW052WDKSf_ueEXq1dyxLq0n3qXWRO-LXr7OzBLdUKWKSBGQrzqS5KJWqdUCPoMIHTqpwYvm-iD6uFlcxKBYxnsAG_hoq_V3icvvwNQQSd_q7Thxr2_KtPIDJWNuX1t5qXp11hkgb-8d5HO93CmN7xNDG89pzSUepT6RYXOZ483mP5fre9qzkfnrjx3oPROCnf3SnIVUvqk7fwfXuniNsg3NrNqncHYUQNReiq3e9I1R60w0ZQTvIReY7-zfiq7iPgVqmu5I7XGgFK4iBv0L7UOEora65b4hRWeLxg5t7OCfUqrS9yxAk8FdjFb9sEfjopWViPRepB0dYPH8dVI.fb6-7XWqp0j6CR9Li0NI-Q", "claims": { "minTLSCertDuration": "1m0s", "defaultTLSCertDuration": "2m0s" }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" }, "ssh": { "templateFile": "templates/certs/ssh/default.tpl" } } }, { "type": "OIDC", "name": "Google", "clientID": "1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com", "clientSecret": "udTrOT3gzrO7W9fDPgZQLfYJ", "configurationEndpoint": "https://accounts.google.com/.well-known/openid-configuration", "admins": ["you@smallstep.com"], "domains": ["smallstep.com"], "listenAddress": ":10000", "claims": { "maxTLSCertDuration": "8h", "defaultTLSCertDuration": "2h", "enableSSHCA": true, "disableRenewal": true }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" }, "ssh": { "templateFile": "templates/certs/ssh/default.tpl" } } }, { "type": "SSHPOP", "name": "sshpop-smallstep", "claims": { "enableSSHCA": true } }, { "type": "ACME", "name": "my-acme-provisioner" } ] }, "tls": { "cipherSuites": [ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ], "minVersion": 1.2, "maxVersion": 1.2, "renegotiation": false }, "password": "p@55w0rd" }

Configuration Options

  • root: location of the root certificate on the filesystem. The root certificate is used to mutually authenticate all API clients of the CA.

  • federatedRoots: location of the federated root certificates on the filesystem. The federated roots are used to authenticate client and servers using TLS certificates from multiple CAs.

  • crt: location of the intermediate certificate on the filesystem. The intermediate certificate is returned alongside each new certificate, allowing the client to complete the certificate chain.

  • key: location of the intermediate private key on the filesystem. The intermediate key signs all new certificates generated by the CA.

  • kms: enables and configures cryptographic protection using cloud key management services or hardware security modules.

    • type: required. one of awskms, cloudkms, pkcs11, or yubikey

    • uri: this field can be used to specify other fields in this section, and its value will take precedence over those values. See cryptographic protection for examples.

    • region: for awskms, the AWS region

    • profile: for awskms, the AWS profile

    • credentialsFile: for cloudkms, the path to a Google Cloud Platform credentials JSON file for a role that can access cloudkms

    • pin: for yubikey, the PIN of the YubiKey PIV application

  • password: optionally store the password for decrypting the intermediate private key (this should be the same password you chose during PKI initialization). If the value is not stored in configuration then you will be prompted for it when starting the CA.

  • address: ex. 127.0.0.1:8080 - address and port on which the CA will bind and respond to requests.

  • dnsNames: comma separated list of DNS name(s) for the CA.

  • logger: the default logging format for the CA is text. The other option is json.

  • db: data persistence layer. See database documentation for more info.

    • type: badger, bbolt, mysql, etc.

    • dataSource: string that can be interpreted differently depending on the type of the database. Usually a path to where the data is stored. See the database configuration docs for more info.

    • database: name of the database. Used for back-ends that may have multiple databases. ex. MySQL

    • valueDir: directory to store the value log in (Badger specific).

  • tls: settings for negotiating communication with the CA; includes acceptable ciphersuites, min/max TLS version, etc.

  • authority: controls the request authorization and signature processes.

    • type: the type of backing CA service that issues certificates for this step-ca instance. The default is an internal certificate authority service. Other options can turn step-ca into a Registration Authority: stepcas uses a remote step-ca instance as the backend, and cloudcas uses a Google CloudCAS.

    • template: default ASN1DN values for new certificates.

    • claims: default validation for requested attributes in the certificate request. Can be overridden by similar claims objects defined by individual provisioners.

      • minTLSCertDuration: do not allow certificates with a duration less than this value.

      • maxTLSCertDuration: do not allow certificates with a duration greater than this value.

      • defaultTLSCertDuration: if no certificate validity period is specified, use this value.

      • disableIssuedAtCheck: disable a check verifying that provisioning tokens must be issued after the CA has booted. This claim is one prevention against token reuse. The default value is false. Do not change this unless you know what you are doing.

      • minUserSSHDuration: do not allow certificates with a duration less than this value.

      • maxUserSSHDuration: do not allow certificates with a duration greater than this value.

      • defaultUserSSHDuration: if no certificate validity period is specified, use this value.

      • minHostSSHDuration: do not allow certificates with a duration less than this value.

      • maxHostSSHDuration: do not allow certificates with a duration greater than this value.

      • defaultHostSSHDuration: if no certificate validity period is specified, use this value.

      • enableSSHCA: enable this provisioner to generate SSH Certificates. The default value is false.

    • provisioners: list of provisioners. Each provisioner has a name, associated authentication attributes, and an optional claims attribute that will override any values set in the global claims directly underneath authority. See provisioner documentation for more info.

    • password: plain text password for starting the CA. Used to decrypt the intermediate private key.

Provisioners

Provisioners are people or entities that are registered with the certificate authority and authorized to issue certificates. In this section we'll discuss the different provisioners, their target use cases, and how to add, remove, and configure them.

Authorization Scope by Provisioner

Every provisioner has a slightly different scope of authorization. Below is a table detailing the authorization capabilities of each provisioner.

Provisioner Capabilitiesx509-signx509-renewx509-revokessh-user-cert-signssh-host-cert-signssh-user-cert-renewssh-host-cert-renewssh-revokessh-rekey
JWK✔️✔️✔️✔️✔️𝗫𝗫✔️𝗫
OIDC✔️✔️✔️✔️✔️ 1𝗫𝗫✔️𝗫
X5C✔️✔️✔️✔️✔️𝗫𝗫𝗫𝗫
K8sSA✔️✔️✔️✔️✔️𝗫𝗫𝗫𝗫
ACME✔️✔️𝗫𝗫𝗫𝗫𝗫𝗫𝗫
SSHPOP𝗫𝗫𝗫𝗫𝗫𝗫✔️✔️✔️
AWS✔️✔️𝗫𝗫✔️𝗫𝗫𝗫𝗫
Azure✔️✔️𝗫𝗫✔️𝗫𝗫𝗫𝗫
GCP✔️✔️𝗫𝗫✔️𝗫𝗫𝗫𝗫

1
Admin OIDC users can generate Host SSH Certificates. Admins can be configured in the OIDC provisioner.

For an example of how to interpret this table, let's take the JWK provisioner. The JWK provisioner is capable of signing, renewing, and revoking X.509 certificates, as well signing user and host SSH certificates. A JWK provisioner cannot renew or rekey SSH certificates.

An SSHPOP provisioner can revoke and rekey SSH certificates and renew SSH host certificates. An SSHPOP provisioner cannot sign, renew, or revoke X.509 certificates, and it cannot sign SSH user and host certificates or renew SSH user certificates.

It's important to understand the capabilities and limitations when selecting a provisioner for a given workload.

Managing Provisioners

Common provisioner operations include:

Add a provisioner

See step ca provisioner add --help for documentation and examples on adding provisioners.

We strongly recommend using the step ca provisioner add [...] utility to generate provisioners in your ca.json configuration. We often encode fields differently in the JSON than you might expect. And you can always modify the JSON configuration manually after using the utility.

Remove a provisioner

See step ca provisioner remove --help for documentation and examples on removing provisioners.

You can also edit the ca.json configuration directly and remove the entire block containing the provisioner you'd like to remove.

List all provisioners

To get a list of all of your current provisioners, run the following command in your terminal:

step ca provisioner list
Modify a provisioner

Modify a provisioner by directly modifying the ca.json configuration.

The following diff is an example of modifications in ca.json that add few configuration options to a provisioner:

--- a/ca.json +++ b/ca.json @@ -86,7 +86,10 @@ "type": "JWK", "name": "max@smallstep.com", "claims": { - "enableSSHCA": true + "enableSSHCA": true, + "minTLSCertDuration": "20m", + "maxTLSCertDuration": "3h", + "defaultTLSCertDuration": "2" } }, {

In this example we've modified the min, max, and default duration for TLS certificates generated by this provisioner.

Provisioner configuration can be used to affect X.509 and SSH certificate lifetimes, extensions, and any other attribute belonging to the certificate. See the Modifiers section for more details.

Modifiers

Claims

Each provisioner can define an optional claims attribute. The settings in this attribute override any settings in the global claims attribute in the authority configuration.

Example claims:

... "claims": { "minTLSCertDuration": "5m", "maxTLSCertDuration": "24h", "defaultTLSCertDuration": "24h", "disableRenewal": false "minHostSSHCertDuration": "5m", "maxHostSSHCertDuration": "1680h", "minUserSSHCertDuration": "5m", "maxUserSSHCertDuration": "24h", "enableSSHCA": true, }, ...

X.509 CA properties

  • minTLSCertDuration: do not allow certificates with a duration less than this value.

  • maxTLSCertDuration: do not allow certificates with a duration greater than this value.

  • defaultTLSCertDuration: if no certificate validity period is specified, use this value.

  • disableIssuedAtCheck: disable a check verifying that provisioning tokens must be issued after the CA has booted. This claim is one prevention against token reuse. The default value is false. Do not change this unless you know what you are doing.

SSH CA properties

  • minUserSSHDuration: do not allow certificates with a duration less than this value.

  • maxUserSSHDuration: do not allow certificates with a duration greater than this value.

  • defaultUserSSHDuration: if no certificate validity period is specified, use this value.

  • minHostSSHDuration: do not allow certificates with a duration less than this value.

  • maxHostSSHDuration: do not allow certificates with a duration greater than this value.

  • defaultHostSSHDuration: if no certificate validity period is specified, use this value.

  • enableSSHCA: enable this provisioner to generate SSH Certificates. The default value is false.

Options

Each provisioner can define an optional options attribute. This attribute allows an operator to set templates that will be applied to all X.509 or SSH certificates generated using the provisioner.

Learn more about configuring X.509 and SSH templates here.

JWK

JWK is the default provisioner type. It uses public-key cryptography to sign and validate a JSON Web Token (JWT).

The step CLI tool will create a JWK provisioner when step ca init is used, and it also contains commands to add (step ca provisioner add) and remove (step ca provisioner remove) JWK provisioners.

Example

Add a JWK provisioner:

step ca provisioner add you@smallstep.com --create

In the ca.json configuration file, a complete JWK provisioner example looks like:

{ "type": "JWK", "name": "you@smallstep.com", "key": { "use": "sig", "kty": "EC", "kid": "NPM_9Gz_omTqchS6Xx9Yfvs-EuxkYo6VAk4sL7gyyM4", "crv": "P-256", "alg": "ES256", "x": "bBI5AkO9lwvDuWGfOr0F6ttXC-ZRzJo8kKn5wTzRJXI", "y": "rcfaqE-EEZgs34Q9SSH3f9Ua5a8dKopXNfEzDD8KRlU" }, "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiTlV6MjlEb3hKMVdOaFI3dUNjaGdYZyJ9.YN7xhz6RAbz_9bcuXoymBOj8bOg23ETAdmSCRyHpxGekkV0q3STYYg.vo1oBnZsZjgRu5Ln.Xop8AvZ74h_im2jxeaq-hYYWnaK_eF7MGr4xcZGodMUxp-hGPqS85oWkyprkQLYt1-jXTURfpejtmPeB4-sxgj7OFxMYYus84BdkG9BZgSBmMN9SqZItOv4pqg_NwQA0bv9g9A_e-N6QUFanxuYQsEPX_-IwWBDbNKyN9bXbpEQa0FKNVsTvFahGzOxQngXipi265VADkh8MJLjYerplKIbNeOJJbLd9CbS9fceLvQUNr3ACGgAejSaWmeNUVqbho1lY4882iS8QVx1VzjluTXlAMdSUUDHArHEihz008kCyF0YfvNdGebyEDLvTmF6KkhqMpsWn3zASYBidc9k._ch9BtvRRhcLD838itIQlw", "claims": { "minTLSCertDuration": "5m", "maxTLSCertDuration": "24h", "defaultTLSCertDuration": "24h", "disableRenewal": false, "minHostSSHCertDuration": "5m", "maxHostSSHCertDuration": "1680h", "minUserSSHCertDuration": "5m", "maxUserSSHCertDuration": "24h", "enableSSHCA": true }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" }, "ssh": { "templateFile": "templates/certs/ssh/default.tpl" } } }
  • type: for a JWK provisioner it must be JWK, this field is case insensitive.

  • name: identifies the provisioner, a good practice is to use an email address or a descriptive string that allows the identification of the owner, but it can be any non-empty string.

  • key: is the JWK (JSON Web Key) representation of a public key used to validate a signed token.

  • encryptedKey*: is the encrypted private key used to sign a token. It's a JWE compact string containing the JWK representation of the private key.

  • claims**: overwrites the default claims set in the authority, see the claims section for all the options.

  • options**: see the options section for more details.

*
Recommended
**
Optional


We can use step to see the private key encrypted with the password asdf:

$ echo ey...lw | step crypto jwe decrypt | jq Please enter the password to decrypt the content encryption key: { "use": "sig", "kty": "EC", "kid": "NPM_9Gz_omTqchS6Xx9Yfvs-EuxkYo6VAk4sL7gyyM4", "crv": "P-256", "alg": "ES256", "x": "bBI5AkO9lwvDuWGfOr0F6ttXC-ZRzJo8kKn5wTzRJXI", "y": "rcfaqE-EEZgs34Q9SSH3f9Ua5a8dKopXNfEzDD8KRlU", "d": "rsjCCM_2FQ-uk7nywBEQHl84oaPo4mTpYDgXAu63igE" }

If the ca.json does not contain the encryptedKey attribute, the private key must be provided using the --key flag when using the step ca [token | certificate | sign] utilities.

OAuth/OIDC Single Sign-on

Sometimes it's useful to issue certificates to people. So step-ca supports single sign-on with identity providers (IdPs) like Google, Okta, Azure Active Directory, Keycloak, or any other provider that supports OAuth's OpenID Connect extension..

OpenID Connect is an extension to OAuth 2.0 that adds an identity layer. Providers that support OIDC can issue identity tokens ("ID tokens") to OAuth clients. These are JSON Web Tokens (JWTs) containing user identity information (eg. full name, username, email address). Like certificates, OIDC tokens have a validity period and are cryptographically signed by a trust authority (the OAuth provider).

Here's an example OIDC identity token issued by Google:

{ "alg": "RS256", "kid": "cd49b2ab16e1e9a496c8239dac0dadd09d443012", "typ": "JWT" }.{ "iss": "https://accounts.google.com", "azp": "1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com", "aud": "1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com", "sub": "115449349109627210866", "hd": "smallstep.com", "email": "mike@smallstep.com", "email_verified": true, "at_hash": "lE6o-GdMpurFQ0WrJ9-H7g", "nonce": "5f5820880a43c3f50d55ce79af15430b14b4059bdf4efbe717da6af8bfc53122", "iat": 1621877714, "exp": 1621881314 }.[Signature]

The OIDC provisioner in step-ca can be configured to trust and accept an OAuth provider's ID tokens for authentication. By default, the issued certificate will use the subject (sub) claim from the identity token as its subject. The value of the token's email claim is also included as an email SAN in the certificate.

example of provisioner working with step ca

Fig. 3: Diagram of how step works with individuals using a single sign-on provisioner

From the user's perspective, when requesting a certificate, step detects the OIDC provisioner and initiates the OAuth login flow automatically:

$ step ca certificate mike@smallstep.com mike.crt mike.key ✔ Key ID: 650445034027-jsjdrkiskeq9ke99ud2rqkst82ft8uch.apps.googleusercontent.com (Google) ✔ CA: https://ca.internal ✔ Certificate: mike.crt ✔ Private Key: mike.key
$ step certificate inspect --short mike.crt X.509v3 TLS Certificate (ECDSA P-256) [Serial: 2581...6739] Subject: 115449349109627210866 mike@smallstep.com Issuer: Smallstep Intermediate CA Provisioner: Google [ID: 6504....com] Valid from: 2019-06-20T18:21:52Z to: 2019-06-21T18:21:52Z
Configuring your identity provider (IdP)

When creating an OAuth app, there isn't much to configure on the IdP. Most providers will ask you to specify a Redirect URI, where the ID token will be delivered at the end of the OAuth flow. Since step starts its own local web server to receive the token, use http://127.0.0.1 as the Redirect URI.

Example: Google Identity

One of the most common providers, and the one used in the following example, is Google Identity.

Add a Google provisioner:

$ step ca provisioner add Google --type oidc --ca-config $(step path)/config/ca.json \ --client-id 650445034027-jsjdrkiskeq9ke99ud2rqkst82ft8uch.apps.googleusercontent.com \ --client-secret 6Q7lGMua_Oox4nA92QBXYypT \ --configuration-endpoint https://accounts.google.com/.well-known/openid-configuration \ --domain smallstep.com --domain gmail.com

Example ca.json provisioner configuration for a Google provisioner:

{ "type": "OIDC", "name": "Google", "clientID": "1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com", "clientSecret": "udTrOT3gzrO7W9fDPgZQLfYJ", "configurationEndpoint": "https://accounts.google.com/.well-known/openid-configuration", "admins": ["you@smallstep.com"], "domains": ["smallstep.com"], "listenAddress": ":10000", "claims": { "maxTLSCertDuration": "8h", "defaultTLSCertDuration": "2h", "disableRenewal": true }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" }, "ssh": { "templateFile": "templates/certs/ssh/default.tpl" } } }
  • type: indicates the provisioner type and must be OIDC.

  • name: a string used to identify the provider when the CLI is used.

  • clientID: the client id provided by the identity provider used to initialize the authentication flow.

  • clientSecret: the shared secret provided by the identity provider; used to get the id token during the OAuth flow. Some identity providers may use an empty string as a secret. In the context of step-ca, the client "secret" is not actually a secret and is available via the CA's /provisioners configuration endpoint, because every step client needs to use it locally.

  • configurationEndpoint: is the HTTP address used by the CA to get the OpenID Connect configuration and public keys used to validate the tokens.

  • admins*: These users will be allowed to request certificates with any name (custom SANs). Non-admins can only get certificates bound to their own ID and email address.

  • domains*: is the list of domains valid. If provided only the emails with the provided domains will be able to authenticate.

  • listenAddress*: is the address (:port or host:port) where the authorization server will redirect the client's web browser at the end of the authorization flow. By default, the step client will bind to 127.0.0.1 on a random port. This parameter is only required if the authorization server demands a specific port for loopback IP redirect URIs.

  • claims*: overwrites the default claims set in the authority, see the claims section for all the options.

  • options*: see the options section for more details.

*
Optional
Further reading

X5C - X.509 Certificate

An X5C provisioner allows a client to get an x509 or SSH certificate using an existing x509 certificate that is trusted by step-ca.

An X5C provisioner is configured with a root certificate, supplied by the user, at the time the provisioner is created. The X5C provisioner can authenticate X5C tokens.

An X5C token is a JWT, signed by the certificate private key, with an x5c header that contains the chain.

Example

If you would like any certificate signed by step-ca to become a provisioner (have the ability to request new certificates with any name), you can configure the X5C provisioner using the root certificate used by step-ca, like so:

step ca provisioner add x5c-smallstep --type X5C --x5c-root $(step path)/certs/root_ca.crt

Or you can configure the X5C provisioner with an outside root, giving provisioner capabilities to a completely separate PKI.

Below is an example of an X5C provisioner in the ca.json:

... { "type": "X5C", "name": "x5c", "roots": "LS0tLS1 ... Q0FURS0tLS0tCg==", "claims": { "maxTLSCertDuration": "8h", "defaultTLSCertDuration": "2h", "disableRenewal": true }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" }, "ssh": { "templateFile": "templates/certs/ssh/default.tpl" } } }
  • type: indicates the provisioner type and must be X5C.

  • name: a string used to identify the provider when the CLI is used.

  • roots: a base64 encoded list of root certificates used for validating X5C tokens.

  • claims*: overwrites the default claims set in the authority, see the claims section for all the options.

  • options*: see the options section for more details.

*
Optional

SSHPOP - SSH Certificate

An SSHPOP provisioner allows a client to renew, revoke, or rekey an SSH certificate using that certificate for authentication with the CA. The renew and rekey methods can only be used on SSH host certificates.

An SSHPOP provisioner is configured with the user and host root ssh certificates from the ca.json. The SSHPOP provisioner can only authenticate SSHPOP tokens generated using SSH certificates created by step-ca.

An SSHPOP token is a JWT, signed by the certificate private key, with an sshpop header that contains the SSH certificate.

When configured with the --ssh option (step ca init --ssh), the ca.json will contain a default SSHPOP provisioner named sshpop.

Example

Add an SSHPOP provisioner:

step ca provisioner add sshpop --type SSHPOP

An example SSHPOP provisioner in the ca.json:

... { "type": "SSHPOP", "name": "sshpop", "claims": { "enableSSHCA": true }, "options": { "ssh": { "templateFile": "templates/certs/ssh/default.tpl" } } }
  • type: indicates the provisioner type and must be SSHPOP.

  • name: a string used to identify the provider when the CLI is used.

  • claims*: overwrites the default claims set in the authority, see the claims section for all the options.

  • options*: see the options section for more details.

*
Optional

ACME

An ACME provisioner allows a client to request a certificate from the server using the ACME Protocol. The ACME provisioner can only request X.509 certificates. All authentication of the CSR is managed by the ACME protocol.

Example

Add an ACME provisioner:

step ca provisioner add acme --type ACME

An example of an ACME provisioner in the ca.json:

... { "type": "ACME", "name": "acme", "forceCN": true, "claims": { "maxTLSCertDuration": "8h", "defaultTLSCertDuration": "2h" }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" } } }
  • type: indicates the provisioner type and must be ACME.

  • name: a string used to identify the provider when the CLI is used.

  • forceCN*: force one of the SANs to become the Common Name, if a common name is not provided.

  • claims*: overwrites the default claims set in the authority, see the claims section for all the options.

  • options*: see the options section for more details.

*
Optional


See our step-ca ACME tutorial for more guidance on configuring and using the ACME protocol with step-ca.

K8sSA - Kubernetes Service Account

A K8sSA provisioner allows a client to request a certificate from the server using a Kubernetes Service Account Token.

As of the time when this provisioner was coded, the Kubernetes Service Account API for retrieving the token from a running instance was still in beta. Therefore, our K8sSA provisioner must be configured with the public key that will be used to validate K8sSA tokens.

K8sSA tokens are very minimal. There is no place for SANs, or other details that a user may want validated in a CSR. It is essentially a bearer token. Therefore, at this time a K8sSA token can be used to sign a CSR with any SANs. Said differently, the K8sSA provisioner does little to no validation on the CSR before signing it. You should only configure and use this provisioner if you know what you are doing. If a malicious user obtains the private key they will be able to create certificates with any SANs and Subject.

Example

Add a K8sSA provsioner:

step ca provisioner add my-kube-provisioner --type K8sSA --pem-keys key.pub

An example of a K8sSA provisioner in the ca.json:

... { "type": "K8sSA", "name": "my-kube-provisioner", "publicKeys": "LS0tLS1...LS0tCg==", "claims": { "maxTLSCertDuration": "8h", "defaultTLSCertDuration": "2h", }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" } } }
  • type: indicates the provisioner type and must be K8sSA.

  • name: a string used to identify the provider when the CLI is used.

  • publicKeys: a base64 encoded list of public keys used to validate K8sSA tokens.

  • claims*: overwrites the default claims set in the authority, see the claims section for all the options.

  • options*: see the options section for more details.

*
Optional

Cloud Provisioners

step-ca can be configured to use instance identity documents (IIDs) to authorize certificate signing requests from cloud VMs running on AWS, GCP, or Azure. IIDs are signed JSON tokens, created when the instance is launched, and made available via the instance metadata API.

Here's the contents of an example IID from AWS:

{ "devpayProductCodes" : null, "marketplaceProductCodes" : [ "1abc2defghijklm3nopqrs4tu" ], "availabilityZone" : "us-west-2b", "privateIp" : "10.158.112.84", "version" : "2017-09-30", "instanceId" : "i-1234567890abcdef0", "billingProducts" : null, "instanceType" : "t2.micro", "accountId" : "123456789012", "imageId" : "ami-5fb8c835", "pendingTime" : "2016-11-19T16:32:11Z", "architecture" : "x86_64", "kernelId" : null, "ramdiskId" : null, "region" : "us-west-2" }

example of provisioner working with step ca

Fig. 2: Diagram of how step works with a cloud service as the provisioner

Once you’ve configured step-ca to accept IIDs for authentication, step will detect the provisioner type, obtain an IID token from your cloud provider's metadata API, and use it to obtain a certificate from step-ca.

From the CA's perspective, an IID is a single-use token. A host can only get one certificate, ever, per IID. A host can then renew its certificate using step ca renew. If the certificate ever expires, the host will need to use a different provisioner to issue a new one.

Security Risks and Limitations

While IIDs simplify the integration of step-ca, the approach comes with risks.

Unfortunately, an IID usually doesn't contain enough information to authenticate a host certificate. For example, while an IID may contain the private or public IP for the host, it will not contain all of the DNS names and IP addresses that you may want on the certificate.

Because of this, step-ca's cloud provisioners use the Trust on First Use (TOFU) security model, allowing any instance to get a certificate with any names (SANs) on it.

This allows for a "cryptographic perimeter": If a host presents a certificate that was signed by your CA using a cloud provisioner, and the CA is configured to verify the instance's account, project, or tenant ID in the IID, you can have some confidence that the request came from your infrastructure (not someone else's), but you cannot assume that the names on the certificate are authentic.

Because of this, every host in your infrastructure must be trusted.

Mitigating the risk of IIDs

Here are some things you can do to mitigate risk when using IIDs:

  • Configure the provisioner with instanceAge. The IID will effectively "expire" if it's not used within instanceAge after the instance is launched.
  • Use a trusted launch configuration / User Data script to obtain a certificate. Coupled with instanceAge, this will give you more assuance that the names on your certificates can be trusted.
  • Restrict provisioning by your cloud provider account or project IDs. Configure the provisioner's accounts (AWS) or projectIDs (GCP) setting.
  • Disable Custom SANs, if possible. When using the disableCustomSANs setting, the CA will only issue certificates with authentic name(s) extracted from the signed instance identity document. Unfortunately, the names on the IID may not be the names that you use to refer to your servers and services.
  • Instances that don't obtain a certificate are a risk. Anyone with access to the instance will be able to obtain a certificate binding any names, so long as the instance is younger than instanceAge. Consider requesting certificates even for instances that will never use them, so that the IID cannot later be used by an attacker.

There are a lot of details to get right to make this model secure, many of which are environment-dependent and beyond the scope of this document.

AWS

The AWS provisioner allows granting a certificate to an Amazon EC2 instance using the Instance Identity Documents

The step CLI will generate a custom JWT token containing the instance identity document and its signature and the CA will grant a certificate after validating it.

Example

Find your AWS account ID to restrict access to our VMs:

AWS Account ID

On the host running step-ca add an AWS provisioner to your configuration by running:

step ca provisioner add "AWS IID Provisioner" --type AWS --aws-account 123456789042

In the ca.json, an AWS provisioner looks like:

{ "type": "AWS", "name": "Amazon Web Services", "accounts": ["123456789042"], "disableCustomSANs": false, "disableTrustOnFirstUse": false, "instanceAge": "1h", "claims": { "maxTLSCertDuration": "2160h", "defaultTLSCertDuration": "2160h" }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" } } }
  • type: indicates the provisioner type and must be AWS.

  • name: a string used to identify the provider when the CLI is used.

  • accounts*: the list of AWS account numbers that are allowed to use this provisioner. If none is specified, all AWS accounts will be valid.

  • disableCustomSANs*: by default custom SANs are valid, but if this option is set to true only the SANs available in the instance identity document will be valid, these are the private IP and the DNS ip-<private-ip>.<region>.compute.internal.

  • disableTrustOnFirstUse*: by default only one certificate will be granted per instance, but if the option is set to true this limit is not set and different tokens can be used to get different certificates.

  • instanceAge*: The maximum age of an instance that should be allowed to obtain a certificate. Limits certificate issuance to new instances to mitigate the risk of credential-misuse from instances that don't need a certificate.

  • claims*: overwrites the default claims set in the authority, see the claims section for all the options.

*
Optional

GCP

The GCP provisioner grants certificates to Google Compute Engine instance using its identity token. The CA will validate the JWT and grant a certificate.

Example

On the host running step-ca, add an GCP provisioner to your configuration by running:

step ca provisioner add Google --type GCP \ --gcp-service-account 1234567890-compute@developer.gserviceaccount.com \ --gcp-service-account 9876543210-compute@developer.gserviceaccount.com \ --gcp-project identity --gcp-project accounting

In the ca.json, a GCP provisioner looks like:

{ "type": "GCP", "name": "Google Cloud", "serviceAccounts": ["1234567890"], "projectIDs": ["project-id"], "disableCustomSANs": false, "disableTrustOnFirstUse": false, "instanceAge": "1h", "claims": { "maxTLSCertDuration": "2160h", "defaultTLSCertDuration": "2160h" }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" } } }
  • type: indicates the provisioner type and must be GCP.

  • name: a string used to identify the provider when the CLI is used.

  • serviceAccounts*: the list of service account numbers that are allowed to use this provisioner. If none is specified, all service accounts will be valid.

  • projectIDs*: the list of project identifiers that are allowed to use this provisioner. If non is specified all project will be valid.

  • disableCustomSANs*: by default custom SANs are valid, but if this option is set to true only the SANs available in the instance identity document will be valid, these are the DNS <instance-name>.c.<project-id>.internal and <instance-name>.<zone>.c.<project-id>.internal

  • disableTrustOnFirstUse*: by default only one certificate will be granted per instance, but if the option is set to true this limit is not set and different tokens can be used to get different certificates.

  • instanceAge*: The maximum age of an instance that should be allowed to obtain a certificate. Limits certificate issuance to new instances to mitigate the risk of credential-misuse from instances that don't need a certificate.

  • claims*: overwrites the default claims set in the authority, see the claims section for all the options.

  • options*: see the options section for more details.

*
Optional

Azure

The Azure provisioner grants certificates to Microsoft Azure instances using the managed identities tokens. The CA will validate the JWT and grant a certificate.

Example

On the host running step-ca, add an GCP provisioner to your configuration by running:

step ca provisioner add Azure --type Azure \ --azure-tenant bc9043e2-b645-4c1c-a87a-78f8644bfe57 \ --azure-resource-group identity --azure-resource-group accounting

In the ca.json, an Azure provisioner looks like:

{ "type": "Azure", "name": "Microsoft Azure", "tenantId": "b17c217c-84db-43f0-babd-e06a71083cda", "resourceGroups": ["backend", "accounting"], "audience": "https://management.azure.com/", "disableCustomSANs": false, "disableTrustOnFirstUse": false, "claims": { "maxTLSCertDuration": "2160h", "defaultTLSCertDuration": "2160h" }, "options": { "x509": { "templateFile": "templates/certs/x509/default.tpl" } } }
  • type: indicates the provisioner type and must be Azure.

  • name: a string used to identify the provider when the CLI is used.

  • tenantId: the Azure account tenant id for this provisioner. This id is the Directory ID available in the Azure Active Directory properties.

  • audience*: defaults to https://management.azure.com/ but it can be changed if necessary.

  • resourceGroups*: the list of resource group names that are allowed to use this provisioner. If none is specified, all resource groups will be valid.

  • disableCustomSANs*: by default custom SANs are valid, but if this option is set to true only the SANs available in the token will be valid, in Azure only the virtual machine name is available.

  • disableTrustOnFirstUse*: by default only one certificate will be granted per instance, but if the option is set to true this limit is not set and different tokens can be used to get different certificates.

  • claims*: overwrites the default claims set in the authority, see the claims section for all the options.

  • options*: see the options section for more details.

*
Optional

Certificate Templates

People use private CAs for all sorts of things, in many different contexts: web apps, mobile apps, code signing, cloud VM instances, SSH, IoT devices, etc. So step-ca must be flexible enough to handle a wide variety of flows.

X.509 and SSH certificate templates open up these possibilities. With certificate templates, you can do things like:

  • Add custom SANs or extensions to X.509 certificates
  • Make longer certificate chains, with multiple intermediate CAs
  • Use SSH force-command or source-address extensions
  • Add conditionals around a certificate's parameters, and fail if they are not met

Certificate templates are JSON documents that describe the most important fields in the final certificate or certificate request. They use Go templates at the core and are extended with Sprig functions.

A few default X.509 and SSH templates are hardcoded into step-ca.

In the next sections, we'll cover X.509 template parameters, SSH template parameters, and how to configure the CA to use templates.

X.509 Templates

X.509 templates can be used with step certificate or step-ca. Here's what the default X.509 leaf certificate template looks like:

{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }
X.509 Template Variables

Here are some common variables available in X.509 certificate template:

  • .Subject: This is the subject that was passed in to step certificate or step ca certificate. Specifically, .Subject.CommonName contains the Common Name for the certificate.

  • .SANs: Subject Alternative Names. This is a list of maps containing SANs for the certificate. Unless SANs are specified (using the --san flag, for example), the .Subject.CommonName is the default SAN.

  • .Token: If a signed token was used to obtain the certificate —for example, with the JWK provisioner— this field contains the payload of that token.

  • .Insecure.CR*: This field holds the Certificate Request from the client.

  • .Insecure.CR.DNSNames: The DNSNames SANs provided in the certificate request

  • .Insecure.CR.EmailAddresses: The EmailAddresses SANs provided in the certificate request

  • .Insecure.CR.IPAddresses: The IPAddresses SANs provided in the certificate request

  • .Insecure.CR.URIs: The URIs SANs provided in the certificate request

  • .Insecure.CR.PublicKey: The public key provided in the certificate request. You can check the request's key type with a conditional, like {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}. You can check the request's key size with a conditional, like {{- if lt .Insecure.CR.PublicKey.Size 384 }}.

  • .Insecure.User: This field holds user-supplied data from the certificate request. Users can supply arbitrary values using --set or --set-file flags in step ca certificate.

You can also import values from the "templateData" object in your provisioner's configuration block.

SSH Templates

step-ca also supports SSH certificate templates. Here is step-ca's default SSH certificate template:

{ "type": {{ toJson .Type }}, "keyId": {{ toJson .KeyID }}, "principals": {{ toJson .Principals }}, "extensions": {{ toJson .Extensions }}, "criticalOptions": {{ toJson .CriticalOptions }} }

The OpenSSH certificate protocol spec defines the available Critical Options and Extensions. Third parties can also add custom extensions.

SSH Template Variables

Here are the most relevant parameters available in SSH certificate template:

  • .Token: This field offers the best opportunity for injecting signed, secure data into the certificate. If a token was used to obtain the certificate,
    this field contains token payload used in the certificate request. For example, when using an OAuth ID token, .Token.email will contain the user's email address. To add custom data to .Token, you could use custom OAuth claims, or sign your own JWTs.

  • .Extensions: is a map containing extensions. The default value for Extensions is:

    { "permit-X11-forwarding": "", "permit-agent-forwarding": "", "permit-port-forwarding": "", "permit-pty": "", "permit-user-rc": "" }
  • .CriticalOptions: is a map containing critical options. It is empty by default.

  • .Principals: For users, this value is derived from the OIDC token (when using the OIDC provisioner). For hosts, it is derived from the Instance Identity Document (when using the Cloud provisioners).

  • .KeyID: The key ID being requested.

  • .Type: The type of SSH certificate being requested. This will be user or host.

  • .Insecure.CR*: SSH certificate requests to step-ca are not CSRs in the X.509 sense. So, step-ca creates a virtual certificate request, and that's what this variable represents.

  • .Insecure.CR.Principals: If you trust a host to register its own custom SANs (for example, when using the IID provisioner), then the host can pass several --principal values to step ssh certificate when registering the host, and use .Insecure.CR.Principals to access those from a template.

  • .Insecure.CR.Type: The type of SSH certificate being requested. This will be user or host.

  • .Insecure.CR.KeyID: The key ID being requested.

  • .Insecure.User: This field holds user-supplied data from the certificate request. Users can supply arbitrary values using --set or --set-file flags in step ssh certificate.

You can also import parameter values from your provisioner's configuration block.

Configuring the CA to Use Templates

Within provisioner configuration, certificate templates can be set under the property "options". The following snippet shows a provisioner with custom X.509 and SSH templates:

{ "type": "JWK", "name": "jane@doe.com", "key": { "use": "sig", "kty": "EC", "kid": "lq69QCCEwEhZys_wavar9RoqRLdJ58u_OGzJK0zswSU", "crv": "P-256", "alg": "ES256", "x": "pt7T0n98qREZUkyUX6b4kXJ5FkJlIdiMfJaLFclZIng", "y": "Pw1y1xqe4g4YARwyBSkEkcjNrtPYxdKlYDLI512t2_M" }, "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiQ1dWZG5zWTR2bGZJbG9BQ1dOaUdNUSJ9.12dowlkvESpxJgrNJgP2ELDQz750HSh2w6Ux6BmatBE5-ybAJFFO7g.1cjU2-CTrV3gbUE7.m8a95nv4qLnN_K_PG7lzgzYXBGnw_aHCf-znJ34AZxzPy2QDGGEjN_V0jX3kvHH9AIg3cs8I8NRm__RDm2iezU5AhPoAHaqnPnZdKh0ReBZ4hNpYXUHlTPf4fRaCXXDQiKatxNzCMBpyqKpudf3xYUzZPRNMN78WM0ZeIzmv_jFzbryOpxD8bJ3Bnxa4e8Am_pPdAANHphodlKN2nDr4701OLKgitImm8RoA20sWdAI_LkTS_Abk_TqBo_3qOGdjmnRBtATFSu3BdQw5wZMjywfwCWKXUK_OUt-cjVIe11xUT43SoE8fR2GJJEKomAHP0vn0MUUMqY9P9icUejw.eEYI_H7WfrYDL4yhsnsJxg", "claims": { "enableSSHCA": true } "options": { "x509": { "templateFile": "templates/certs/x509/leaf.tpl", "templateData": { "OrganizationalUnit": "Engineering Team" } }, "ssh": { "templateFile": "templates/certs/ssh/host.tpl" } } }
  • options: object that allows configuration of provisioner options e.g. templates.

    • x509 or ssh: object for configuring X.509 or SSH certificate template options.

      • templateFile: path to a template stored on disk. You have a few options for how to define your path:

        • absolute path: e.g. /home/mariano/path/to/file.ptl
        • relative to $STEPPATH: e.g. templates/certs/x509/leaf.tpl the actual location of which would be $STEPPATH/templates/certs/x509/leaf.tpl.
        • relative to execution directory of step-ca: e.g. ./path/to/file.tpl or ../path/to/file.tpl
      • templateData: defines variables that can be used in the template. In the example above, you will be able to use the defined organizational unit as the variable {{ .OrganizationalUnit }}, for example in a template like:

        { "subject": { "organizationalUnit": {{ toJson .OrganizationalUnit }}, "commonName": {{ toJson .Subject.CommonName }} }, "sans": {{ toJson .SANs }}, "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }
      • template: set the full template in a string. The value can be the string representation of a JSON object, or you encoded in base64. For example:

        { "x509": { "template": "{{ toJson .Insecure.CR }}", } }

        Or using base64:

        { "x509": { "template": "e3sgdG9Kc29uIC5JbnNlY3VyZS5DUiB9fQ==", } }

Basic X.509 Template Examples

Leaf Certificates

The most common type of certificate you'll want to create is a leaf (or end-entity) certificate; this is the type of certificate that your servers or clients will use.

The default leaf certificate template is:

{ "subject": {{ toJson .Subject }}, "sans": {{ toJson .SANs }}, {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }

We will explain what each block does, but first, let's look at what a rendered template might look like. Imagine that you run the following command to create a certificate that has jane@doe.com as an email SAN.

step ca certificate jane@doe.com jane.crt

The rendered template (from which the X.509 certificate will be generated and signed) is:

{ "subject": { "commonName": "jane@smallstep.com" }, "sans": [{"type": "email", "value": "jane@smallstep.com"}], "keyUsage": ["digitalSignature"], "extKeyUsage": ["serverAuth", "clientAuth"] }

Going back to the template above, we can see that the template has blocks delimited by {{ and }}. These are called actions - actions are data evaluations or control structures. The ones in the default template are:

  • {{ toJson .Subject }}: renders .Subject as JSON. toJson is a function in Sprig that encodes the passed item into JSON. .Subject is a variable available in all templates that contains the <subject> parameter passed in the CLI, in this case, jane@smallstep.com, and to be more precise, this value is available in .Subject.CommonName.

  • {{ toJson .SANs }}: renders .SANs (Subject Alternative Names) as JSON. The variable .SANs is also available in all templates and contains the list of SANs passed from the CLI. If no SANs are specified, the .Subject.CommonName will be used as a default SAN (e.g. jane@smallstep.com in our example). If you add more SANs using the flag --san, step-ca will auto-assign the correct SAN type (dns, ip, uri, or email`).

  • {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}: defines a condition based on the key type in the certificate request. typeIs is a Sprig function that returns true if the type of the data in .Insecure.CR.PublicKey matches *rsa.PublicKey, this is the Go specific type for RSA public keys. If the condition is true, the keyUsage is set to keyEncipherment and digitalSignature, otherwise just digitalSignature. keyEncipherment is used in the RSA key exchange protocol, and it must only be set if an RSA key is used. .Insecure.CR is a structure that represents the certificate (signing) request or CSR, from it we can extract SANs, the subject, extensions, etc.

Finally we also set the extended key usage serverAuth and clientAuth so the certificate can be used in a server application or in a client for mTLS.

Intermediate Certificates

An intermediate certificate is a certificate that can be used to sign another certificate, and it has itself been signed by another intermediate certificate or by a root certificate.

The default template for an intermediate certificate is:

{ "subject": {{ toJson .Subject }}, "keyUsage": ["certSign", "crlSign"], "basicConstraints": { "isCA": true, "maxPathLen": 0 } }

This template is most commonly used when initializing a CA. But it can also be set in the ca.json and used in a specific provisioner in step-ca.

The keyUsage certSign allows a certificate to sign other certificates. crlSign allows a certificate to sign Certificate Revocation Lists or CRLs.

In basicConstraints we say that this certificate is a CA, and the path length constraint, maxPathLen, is maximum number of non-self-issued intermediate certificates that may follow this certificate in a valid certification path. An intermediate with a maxPathLen set to 0 won't be able to sign another intermediate certificate. To be precise, it can sign it, but the new intermediate won't be trusted by clients, so it won't be useful.

Knowing this we can also guess that the intermediate certificate's issuer had at least a maxPathLen of 1. So if you want to use an intermediate certificate that can sign other intermediates, make sure to set more permissive maxPathLen in the root and intermediate certificate used.

The command step certificate create with the --profile intermediate-ca flag uses the template above, but if you want to create an intermediate with a custom template you can run:

step certificate create --template intermediate.tpl \ --ca ~/.step/certs/root_ca.crt --ca-key ~/.step/secrets/root_ca_key \ "ACME Intermediate CA" intermediate_ca.crt intermediate_ca_key
Root Certificates

A root certificate is a self-signed certificate used to sign other certificates. The default root certificate template is:

{ "subject": {{ toJson .Subject }}, "issuer": {{ toJson .Subject }}, "keyUsage": ["certSign", "crlSign"], "basicConstraints": { "isCA": true, "maxPathLen": 1 } }

Here we are specifying that the issuer is equivalent to the subject, and the maxPathLen is set to 1, so it can sign intermediate certificates with maxPathLen set to 0. If you want to allow intermediates to sign other intermediates, you need to set the maxPathLen to at least 2. It's also possible to not limit the number of intermediates that can follow this certificate if we set maxPathLen to -1, and then create intermediates that set a specific limit, i.e. 0 for those that can sign only leaf certificates and 1 for those that can sign at most one downstream intermediate certificate.

The command step certificate create with the --profile root-ca flag uses the above template. To create a custom root certificate, you can run:

step certificate create --template root.tpl "ACME Root CA" root_ca.crt root_ca_key

Advanced X.509 Template Examples

In the previous section, we learned how to create and use the basic templates, but we've just scratched the surface of what's possible. With templates, you can customize and parameterize every aspect of an X.509 or SSH certificate.

Below, we'll walk through a few advanced templating examples.

Certificate Request

Let's start with one of the shortest templates:

{{ toJson .Insecure.CR }}

This template produces a signed certificate that contains all the information present in the certificate request (with a few caveats). All SANs and extensions present in the certificate request will be in the final certificate.

The only modifications that a signed certificate might have (over a CSR), would be in the certificate subject on step-ca if a template is provided in the ca.json, and it will set the Subject Alternative Name extension to critical if the subject is empty.

User-Provided Variables in Signing Requests

In step-ca, X.509 templates can also be parameterized with variables that will be provided by step at certificate creation time. A common use case for variables is when you receive a CSR from another team, or a CSR embedded in hardware, and you need to define a SAN for it.

For example, below is an X.509 template that accepts the user variable dnsName but falls back to the default leaf template if it's not present:

{ "subject": {{ toJson .Subject }}, {{- if .Insecure.User.dnsName }} "dnsNames": {{ toJson .Insecure.User.dnsName}}, {{- else }} "sans": {{ toJson .SANs }}, {{- end }} {{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }} "keyUsage": ["keyEncipherment", "digitalSignature"], {{- else }} "keyUsage": ["digitalSignature"], {{- end }} "extKeyUsage": ["serverAuth", "clientAuth"] }

You can then use step to sign a CSR like so:

$ step ca sign --set "dnsName=backend.example.com" input.csr output.crt # Or $ echo '{"dnsName": "backend.example.com"}' > vars.json $ step ca sign --set-file vars.json input.csr output.crt

Both flags, --set <name=value> and --set-file <path> are available in step ca certificate and step ca sign. If you need to pass more than one variable, you can use --set multiple times or use a JSON file with multiple properties.

It's worth mentioning the while we used "dnsNames" instead of "sans" in the example above, both can be used. "dnsNames" is an array of strings (or just one string if only one is required), while "sans" is an array of objects like:

[ {"type": "dns", "value": "backend.example.com"}, {"type": "email", "value": "jane@example.com"}, {"type": "ip", "value": "10.0.1.10"}, {"type": "uri", "value": "https://backend.example.com/root.crt"}, ]

The variable .SANs is generated by the provisioners with the values of the trusted names.

Besides "dnsNames", you can also use "emailAddresses", "ipAddresses", and "uris".

Adding Name Constraints

Name constraints are useful in intermediate certificates when you want to limit the validity of certificates that an intermediate can sign. If we want to only allow DNS name like *.example.com we can generate an intermediate with the template:

{
    "subject": {{ toJson .Subject }},
    "keyUsage": ["certSign", "crlSign"],
    "basicConstraints": {
        "isCA": true,
        "maxPathLen": 0
    },
    "nameConstraints": {
        "critical": true,
        "permittedDNSDomains": ["example.com"]
    }
}

Given a root certificate and a key, you can generate the intermediate with:

step certificate create —template intermediate.tpl --ca root_ca.crt --ca-key root_ca_key \ “Intermediate CA” intermediate.crt intermediate.key

Besides "permittedDNSDomains", the "nameConstraints" property accepts all the following properties:

{ "nameConstraints": { "critical": false, "permittedDNSDomains": ["doe.com"], "excludedDNSDomains": ["doe.org"], "permittedIPRanges": ["1.2.3.0/24"], "excludedIPRanges": ["3.2.1.0/24"], "permittedEmailAddresses": ["jane@doe.com"], "excludedEmailAddresses": ["jane@doe.org"], "permittedURIDomains": ["https://doe.com"], "excludedURIDomains": ["https://doe.org"], } }

Remember that in certificate templates, if an array only has one member, you can write it as a string:

{ "nameConstraints": { "critical": true, "permittedDNSDomains": "example.com" } }
Arbitrary X.509 Extensions

SANs, key usages, extended key usages, basic constraints, and name constraints, are all extensions in an X.509 certificate. Templates provide an easy way for assigning extensions without the need of using the actual extension bytes.

The same thing is also possible with top-level certificate template properties like:

  • unknownExtKeyUsage: defines custom extended key usages using ASN.1 object identifiers; this is useful if the templates do not support a string version of the extended key usage (RFC 5280, 4.2.1.12).

  • ocspServer: sets a list of OCSP servers in the Authority Information Access extension (RFC 5280, 4.2.2.1).

  • issuingCertificateURL: defines the issuing certificate URL in the Authority Information Access extension (RFC 5280, 4.2.2.1).

  • crlDistributionPoints: defines URLs with the certificate revocation list (RFC 5280, 4.2.1.13)).

  • policyIdentifiers: defines a list of ASN.1 object identifiers with certificate policies (RFC 5280, 4.2.1.4).

But if you need to create your own extension, or use an unsupported one, you can also write a custom extension like:

{ "extensions": [ {"id": "1.2.3.4", "critical": false, "value": "Y3VzdG9tIGV4dGVuc2lvbiB2YWx1ZQ=="} ] }

The crux here is that the value of the extension is the base64 encoding of the actual bytes that go into that extension, so if you are encoding a structure in your extension using the ASN.1 encoding, you will have to put the base64 version of the encoded bytes.

X.509 OpenVPN certificates

One interesting use case for certificate templates is the signing of OpenVPN certificates. These certificates require specific key usages not available in the default templates.

This is a template you can use in a provisioner signing OpenVPN client certificates:

{
  "subject": {"commonName": {{ toJson .Insecure.CR.Subject.CommonName }}},
  "sans": {{ toJson .SANs }},
  "keyUsage": ["digitalSignature", "keyAgreement"],
  "extKeyUsage": ["clientAuth"]
}

And the following template can be used for a provisioner signing OpenVPN server certificates:

{
  "subject": {{ toJson .Subject }},
  "sans": {{ toJson .SANs }},
  "keyUsage": ["digitalSignature", "keyEncipherment", "keyAgreement"],
  "extKeyUsage": ["serverAuth"]
}

A full configuration of step-ca and OpenVPN is available in our blog post "Announcing X.509 Certificate Flexibility"

SSH Template Examples

GitHub SSH Certificates

With GitHub Enterprise Cloud or GitHub Enterprise Server, you can configure GitHub to use SSH certificates. Specifically, GitHub will trust an SSH Certificate Authority for your team. But, to get it to work, you have to generate custom SSH certificates for your users using a login@github.com extension, where the value of the extension is their GitHub Username. This custom extension value authenticates your users to GitHub Enterprise. Which is great: The same certificates that let you SSH into your servers now also let you push code.

Here's an SSH template that supports the GitHub custom SSH certificate extension:

{ "type": {{ toJson .Type }}, "keyId": {{ toJson .KeyID }}, "principals": {{ toJson .Principals }}, "criticalOptions": {{ toJson .CriticalOptions }}, {{ if .Token.ghu }} "extensions": { "login@github.com": {{ toJson .Token.ghu }} } {{ else }} "extensions": {{ toJson .Extensions }} {{ end }} }

To use this template, you'd need to add a ghu ("GitHub Username") custom claim to the token that you send the CA to authenticate your certificate request.

If you use an OIDC provisioner with this template, you can configure your identity provider to serve a custom claim labeled ghu in your ID token. Our blog post "Clever Uses of SSH Certificate Templates" has more details on this option.

The JWK and Cloud provisioners also use tokens. Here's an example of a Bash test script that decrypts your JWK provisioner key, and uses it to sign a token that step-ca will accept for an SSH certificate:

#!/bin/bash # # This script is for testing purposes only. # # Your CA hostname and port CA=ca.local:443 # Key ID: This is the "kid" value from your JWK provisioner configuration KID=R0a0VGbbOe3j6ol5jOdV3qfiigjcezk0LG9K9Cp7mrg # Issuer: The "name" of your JWK provisioner ISS="carl@smallstep.com" if [ "$#" -ne 2 ]; then name=$(basename $0) echo "Usage: $name <princiap> <github-username>" exit 1 fi # Grab the key to encrypt KEY=`curl --cacert ~/.step-ssh-templates/certs/root_ca.crt -s https://${CA}/1.0/provisioners/${KID}/encrypted-key | jq -r .key | step crypto jwe decrypt` echo '{"step": {"ssh":{"certType": "user", "keyID": "'$1'"}}, "ghu":"'$2'"}' | step crypto jwt sign --key <(echo $KEY) --iss "$ISS" --aud "https://${CA}/1.0/sign" -sub $1 -exp $(date -v+5M +"%s")

Here's the script used in concert with the GitHub SSH template above:

$ TOKEN=$(./gh_token.sh carl tashian) Please enter the password to decrypt the content encryption key: ... $ echo $TOKEN | step crypto jwt inspect --insecure { "header": { "alg": "ES256", "kid": "R0a0VGbbOe3j6ol5jOdV3qfiigjcezk0LG9K9Cp7mrg", "typ": "JWT" }, "payload": { "aud": "https://localhost:4430/1.0/sign", "exp": 1606870196, "ghu": "tashian", "iat": 1606869896, "iss": "carl@smallstep.com", "jti": "6d62922871db260e54d97fe1f2561440177ae579e31baa954128e0fba155b4db", "nbf": 1606869896, "step": { "ssh": { "certType": "user", "keyID": "carl" } }, "sub": "carl" }, "signature": "RdYo70cCR7tMZBl8VLbxVpoCDlNED2wpRw8uPV1rNkDENBbXqml1h-TnWG23dJ4zKzPhAuO2Vk7HdOUC2q0mNg" } $ step ssh certificate carl github --token $TOKEN ✔ CA: https://localhost:4430 Please enter the password to encrypt the private key: ✔ Private Key: github ✔ Public Key: github.pub ✔ Certificate: github-cert.pub ✔ SSH Agent: yes $ step ssh inspect github-cert.pub github-cert.pub: Type: ecdsa-sha2-nistp256-cert-v01@openssh.com user certificate Public key: ECDSA-CERT SHA256:SdcgsNKaQbhYveanJYjmJk4eLN3+CBKUKkRGv4xYn5U Signing CA: ECDSA SHA256:MKwRQ/SDKk/pCJbbCk5bfhZACjSjv7uZXLyc5n4Wx6k Key ID: "carl" Serial: 886026644913671581 Valid: from 2020-12-01T16:40:32 to 2020-12-02T08:41:32 Principals: carl Critical Options: (none) Extensions: login@github.com tashian

In production, you would not want to hand out your JWK provisioner password. But you could create a token server that interoperates with step-ca and injects secure values into the token.

SSH Group Accounts

Let's say your organization has shared group accounts on your hosts (eg. "dba" or "devops"). You'd want to be able to issue people in those groups a certificate that contains their user principals ("carl", "carl@smallstep.com"), plus principals for any group accounts they need access to.

Let's use the OIDC ID token to inject additional principals into an SSH user certificate.

First, configure a custom groups claim with your identity provider, and add the claim to the payload of the ID token. In this example, we're assuming the groups claim contains a space-separated list of possible group accounts.

Then, use the following template to merge the group accounts with the user's own principals (derived from the email claim):

{ "type": {{ toJson .Type }}, "keyId": {{ toJson .KeyID }}, "principals": {{ toJson ((concat .Principals (splitList " " .Token.groups)) | uniq) }}, "criticalOptions": {{ toJson .CriticalOptions }}, "extensions": {{ toJson .Extensions }} }

This is a good example of the use of Sprig functions in templates.

Finally, test out your flow with step ssh login, and use step ssh inspect to check the principals of the certificate you're issued by the CA.

Even More Templates

To delve deeper into certificate templates and all the options, the best source of information is the code docs for the go.step.sm/crypto/x509util package:

  • The Certificate object represents a certificate template with all its properties.

  • The CertificateRequest object represents all properties for a CSR template that can be used in step certificate create --csr.

  • All key usages and supported extended key usages without using "unknownExtKeyUsage" are available here.

More information and more examples are available in the blog posts "Announcing X.509 Certificate Flexibility" and "Clever Uses of SSH Certificate Templates".

Databases

step-ca uses a simple key-value interface over popular database implementations to store persistent certificate management meta-data. Our recommended default database implementation is nosql-Badger, a NoSQL interface over the popular Badger database. As a first pass, the database layer will store every certificate (along with metadata surrounding the provisioning of the certificate) and revocation data that will be used to enforce passive revocation.

Configuring step-ca to use a database is as simple as adding a top-level db stanza to your ca.json file. Below users can find documentation and examples for supported databases:

Badger

{ ... "db": { "type": "badger", "dataSource": "./.step/db", "valueDir": "./.step/valuedb" "badgerFileLoadingMode": "MemoryMap" }, ... },

Options

  • type

    • badger: currently refers to Badger V1. However, as Badger V1 is deprecated, this will refer to Badger V2 starting with a the next major version release.
    • badgerV1: explicitly select Badger V1.
    • badgerV2: explicitly select Badger V2. Anyone looking to use Badger V2 will need to set it explicitly until it becomes the default.
  • dataSource: path, database directory.

  • valueDir [optional]: path, value directory, only if different from dataSource.

  • badgerFileLoadingMode [optional]: can be set to FileIO (instead of the default MemoryMap) to avoid memory-mapping log files. This can be useful in environments with low RAM. Make sure to use badgerV2 as the database type if using this option.

    • MemoryMap: default.
    • FileIO: This can be useful in environments with low RAM.

BoltDB

{ ... "db": { "type": "bbolt", "dataSource": "./stepdb" }, ... }

Options

  • type: bbolt
  • dataSource: path, database directory.

MySQL

{ ... "db": { "type": "mysql", "dataSource": "user:password@tcp(127.0.0.1:3306)/", "database": "myDBName" }, ... },

Options

  • type: mysql
  • dataSource: path, database directory.

Schema

As the interface is a key-value store, the schema is very simple. We support tables, keys, and values. An entry in the database is a []byte value that is indexed by []byte table and []byte key.

Exporting Data

At this time step-ca does not have any API or interface for easily exporting data. Because the data is stored in a noSQL like manner, it is difficult to explore the data even when using a SQL backend like MySQL. We do have a scripted example for accessing the DB to give users a jumping off point for writing their own reporting and logging tools.

Cryptographic Protection

By default, step-ca stores its signing keys encrypted on disk, on the CA. Some use cases may require more advanced cryptographic protection. For protection of you signing keys, step-ca integrates with Google Cloud KMS, AWS KMS, YubiKey PIV, and PKCS #11 hardware security modules (HSMs).

For a complete, end-to-end tutorial using YubiKeys, see our blog post Build a Tiny Certificate Authority For Your Homelab.

Google Cloud KMS

Cloud KMS is Google's cloud-hosted KMS that allows you to store the cryptographic keys and sign certificates using their infrastructure. Cloud KMS supports two different protection levels: SOFTWARE and HSM.

To configure Cloud KMS in your certificate authority, add the kms property to your ca.json file and replace the property key with the Cloud KMS key name of your intermediate key:

{ "root": "/home/step/certs/root_ca.crt", "crt": "/home/step/certs/intermediate_ca.crt", "key": "projects/<project-id>/locations/global/keyRings/<ring-id>/cryptoKeys/<key-id>/cryptoKeyVersions/<version-number>", "kms": { "type": "cloudkms", "uri": "cloudkms:credentials-file=/path/to/kms-credentials.json" } }

In a similar way for SSH certificates, the SSH keys must be Cloud KMS names:

{ "ssh": { "hostKey": "projects/<project-id>/locations/global/keyRings/<ring-id>/cryptoKeys/<key-id>/cryptoKeyVersions/<version-number>", "userKey": "projects/<project-id>/locations/global/keyRings/<ring-id>/cryptoKeys/<key-id>/cryptoKeyVersions/<version-number>" } }

Currently, step does not provide an automatic way to initialize the public key infrastructure (PKI) using Cloud KMS. Still, an experimental tool named step-cloudkms-init addresses this use case. This tool generates the public certificates root_ca.crt and intermediate_ca.crt for inclusion in ca.json and prints the intermediate key name for use in the key property. In a future release, this tool will be integrated into step.

To use step-cloudkms-init, install it from the latest step-ca release tarball, enable Cloud KMS in your project, and run:

$ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json $ step-cloudkms-init --project your-project-id --ssh Creating PKI ... ✔ Root Key: projects/your-project-id/locations/global/keyRings/pki/cryptoKeys/root/cryptoKeyVersions/1 ✔ Root Certificate: root_ca.crt ✔ Intermediate Key: projects/your-project-id/locations/global/keyRings/pki/cryptoKeys/intermediate/cryptoKeyVersions/1 ✔ Intermediate Certificate: intermediate_ca.crt Creating SSH Keys ... ✔ SSH User Public Key: ssh_user_ca_key.pub ✔ SSH User Private Key: projects/your-project-id/locations/global/keyRings/pki/cryptoKeys/ssh-user-key/cryptoKeyVersions/1 ✔ SSH Host Public Key: ssh_host_ca_key.pub ✔ SSH Host Private Key: projects/your-project-id/locations/global/keyRings/pki/cryptoKeys/ssh-host-key/cryptoKeyVersions/1

By default, the keys are generated using the SOFTWARE protection level. Add the flag ---protection-level HSM to use a Hardware Security Module (HSM).

Run the step-cloudkms-init --help command for more options.

AWS KMS

AWS KMS is Amazon's managed encryption and key management service. It creates and stores the cryptographic keys and uses AWS infrastructure for signing operations. Amazon KMS operations are always backed by HSMs.

To configure AWS KMS in your certificate authority, add the kms property to ca.json and replace the key property with the AWS KMS key name of your intermediate key:

{ "root": "/home/step/certs/root_ca.crt", "crt": "/home/step/certs/intermediate_ca.crt", "key": "awskms:key-id=f879f239-feb6-4596-9ed2-b1606277c7fe", "kms": { "type": "awskms", "uri": "awskms:region=us-west-2;profile=foo;credentials-file=/path/to/credentials" } }

By default, step-ca uses the credentials stored in ~/.aws/credentials. Use the credentials-file option to override. The region and profile options specify the AWS region and configuration profiles respectively. These options can also be configured using environment variables as described in the AWS SDK for Go session documentation.

In similar manner, to configure SSH certificate signing, replace the SSH keys with the keys from AWS KMS:

{ "ssh": { "hostKey": "awskms:key-id=d48e502a-09bc-4bf7-9af8-ae1bccedc931", "userKey": "awskms:key-id=cf28e942-1e10-4a08-b84c-5359af1b5f12" } }

step-ca can also accept just the Amazon Key ID or the ARN as key options, but using the format based on the RFC7512 will allow more flexibility for future step releases.

Currently, step does not provide an automatic way to initialize the public key infrastructure (PKI) using AWS KMS, but an experimental tool named step-awskms-init addresses this use case. This tool generates the public certificates root_ca.crt and intermediate_ca.crt for inclusion in ca.json and prints the intermediate key name for use in the key property. In a future release, this tool will be integrated into step.

To use step-awskms-init, make sure to configure your environment using aws configure, install step-awskms-init from the latest step-ca release tarball, and run:

$ step-awskms-init --ssh --region us-east-1 Creating PKI ... ✔ Root Key: awskms:key-id=f53fb767-4029-40ff-b650-0dd35fb661df ✔ Root Certificate: root_ca.crt ✔ Intermediate Key: awskms:key-id=f879f239-feb6-4596-9ed2-b1606277c7fe ✔ Intermediate Certificate: intermediate_ca.crt Creating SSH Keys ... ✔ SSH User Public Key: ssh_user_ca_key.pub ✔ SSH User Private Key: awskms:key-id=cf28e942-1e10-4a08-b84c-5359af1b5f12 ✔ SSH Host Public Key: ssh_host_ca_key.pub ✔ SSH Host Private Key: awskms:key-id=cf28e942-1e10-4a08-b84c-5359af1b5f12

The --region parameter is only required if your AWS configuration does not define a region.

Run the step-awskms-init --help command for more options.

YubiKey PIV

You can leverage a hardware YubiKey—and the YubiKey PIV application—to store your CA keys and sign TLS certificates.

Prerequisites and Caveats
  • To enable YubiKey support in step-ca, you must follow our Instructions for building from source using CGO
  • You will need a YubiKey that supports the PIV application: YubiKey 5 NFC, YubiKey 5 Nano, YubiKey 5C, or YubiKey 5C Nano
  • Certificate slots 9a, 9c, 9d, 9e, and 82-95 are supported
  • You can use the YubiKey for X.509 and SSH CAs; however, the step-yubikey-init utility only supports X.509 at this time. For SSH support, you must manually generate and configure SSH CA keys on the YubiKey.

The initialization of the public key infrastructure (PKI) for YubiKeys is not currently integrated into step, but an experimental tool named step-yubikey-init addresses this use case. In a future release, this tool will be integrated into step.

To configure your YubiKey, install step-yubikey-init from the latest step-ca release tarball and run:

$ step-yubikey-init What is the YubiKey PIN?: Creating PKI ... ✔ Root Key: yubikey:slot-id=9a ✔ Root Certificate: root_ca.crt ✔ Intermediate Key: yubikey:slot-id=9c ✔ Intermediate Certificate: intermediate_ca.crt

Run the step-yubikey-init --help command for more options.

Finally, to enable it in ca.json, point the root and crt options to the generated certificates, replace the key option with the YubiKey URI generated in the previous part, and configure the kms option with the appropriate type and pin.

{ "root": "/path/to/root_ca.crt", "crt": "/path/to/intermediate_ca.crt", "key": "yubikey:slot-id=9c", "kms": { "type": "yubikey", "uri": "yubikey:management-key=01020304...?pin-value=123456" } }

PKCS #11

A Hardware Security Module (HSM) is a specialized piece of hardware that is designed to generate and store private keys, and sign messages using those keys. The private keys on an HSM cannot be exported from the device. One can only run signing operations using the key. This is an excellent way to protect private keys for a Certificate Authority, which in normal operation simply needs to be able to sign Certificate Signing Requests.

Public-Key Cryptography Standards #11 (PKCS #11) is the most common platform-independent API used by HSM hardware. It's supported by most HSM hardware, like Yubico's YubiHSM2, and the Nitrokey HSM 2. There's also a software-based "HSM," SoftHSMv2, which offers a PKCS #11 interface without the hardware.

1. Initialize your PKI using the step-pkcs11-init tool.

The step-pkcs11-init tool will create or import CA keys and/or certificates onto your HSM, for use with step-ca. It is configured to work with the YubiHSM2 by default, but you can provide a different PKCS #11 URI by using the --kms flag.

Here are some examples of PKCS #11 URIs for various HSMs:

HSMURI format
YubiHSM2 (Linux)pkcs11:module-path=/usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so;token=YubiHSM
AWS CloudHSMpkcs11:module-path=/opt/cloudhsm/lib/libcloudhsm_pkcs11.so;token=cavium?pin-value=$HSM_USER:$HSM_PASSWORD
SoftHSMpkcs11:module-path=/usr/lib/softhsm/libsofthsm2.so;token=token1?pin-value=$HSM_PASSWORD
nCipher nShieldpkcs11:module-path=/opt/nfast/toolkits/pkcs11/libcknfast.so;token=rjk?pin-source=/etc/step-ca/hsm-pin.txt

You'll need to substitute $HSM_USER and $HSM_PASSWORD with your own values.

In this URI,

  • module-path points to your PKCS #11 .dll, .so, or .dylib library file,
  • token is the label (CKA_LABEL) of the HSM you're using,
  • pin-value contains hardcoded HSM credentials. It may be a PIN, username and password, password, or a filename depending on the HSM,
  • Or, pin-source is a filename containing HSM credentials.

Once the utility creates your PKI, it will output a few paths for your keys and certificates before exiting. You'll need these for step 2.

2. Configure the CA to use PKCS #11

One you've created your PKI on the HSM using step-pkcs11-init, you'll need to configure step-ca to use the HSM.

Start with a standard CA configuration, created using step ca init or step ca init --ssh.

Now open up `.step/config/ca.json in an editor. You'll want the top of the file to reference the HSM key paths provided by the step-pkcs11-init utility.

For this, you'll need to change the key and ssh values to match the pkcs11: URIs above. These are the values you need to change:

{ ... "key": "pkcs11:id=7331;object=intermediate-key", "ssh": { "hostKey": "pkcs11:id=7332;object=ssh-host-key", "userKey": "pkcs11:id=7333;object=ssh-user-key" }, ... }

You'll also need to add a kms top-level object that contains your pkcs11 module URI:

{ ... "kms": { "type": "pkcs11", "uri": "pkcs11:module-path=/opt/cloudhsm/lib/libcloudhsm_pkcs11.so;token=cavium?pin-value=step_ca:RiFJrg93Tn_EXAMPLE" }, ... }

Registration Authority (RA) Mode

Sometimes you may wish to run a Registration Authority (RA). An RA acts as a front-end to a CA, authenticating certificate requests from its clients before passing them along to an upstream Certificate Authority that trusts the RA and will issue the certificates. RAs are often used to distribute certificates across several cloud providers or on-prem environments.

The RA operational mode for step-ca centralizes and simplifies key management, because a single backing CA can serve several RAs.

In RA mode, step-ca currently supports two backing CA types: Another step-ca instance ("StepCAS"), or a Google Cloud CAS private CA.

Example PKI topology with StepCAS RA Mode

StepCAS RA mode

StepCAS allows configuring a step-ca server as an RA, with a second, upstream step-ca server acting as the main CA.

StepCAS supports the JWK and X5C provisioners for requests between RAs and the CA. The JWK provisioner balances security and simplicity, and it covers the most common use cases. The X5C provisioner allows for more complex trust relationships and expiring certificates, but it requires setting up ongoing certificate lifecycle management on the RAs.

Example: A Simple RA ↔ CA Configuration

Let's set up a simple RA ↔ CA pair, with one RA and one upstream CA.

The CA will be configured with a JWK provisioner, and the RA will use that provisioner to make authenticated certificate requests to the CA.

The RA, in turn, can offer its clients certificates using any step-ca provisioners. In this example, we'll configure an ACME provisioner in the RA. Configure additional RA provisioners just as you would configure provisioners for a CA.

Setting up the CA

You can set up the CA using the Getting Started guide. You will need the name and password of the first provisioner you created during the step ca init process.

Setting up the RA

Here's an example configuration file for an RA that uses the JWK provisioner to connect to the CA, and that offers an ACME provisioner to its clients:

{ "address": ":9100", "dnsNames": ["ra.smallstep.com"], "db": { "type": "badgerV2", "dataSource": "/etc/step-ca/db" }, "logger": {"format": "text"}, "authority": { "type": "stepcas", "certificateAuthority": "https://ca.smallstep.com:9000", "certificateAuthorityFingerprint": "b4fc6b547ca4610b69cfcc53c6933e7a37170476dfe134a2c257726f92c403f5", "certificateIssuer": { "type" : "jwk", "provisioner": "ra@smallstep.com" }, "provisioners": [{ "type": "ACME", "name": "acme" }] }, "tls": { "cipherSuites": [ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ], "minVersion": 1.2, "maxVersion": 1.3, "renegotiation": false } }

The crucial bit of configuration here is the "certificateIssuer" section. This section connects this RA to the CA using the JWK provisioner named ra@smallstep.com on the CA.

When step-ca starts up with this RA configuration, it will download the encrypted JWK key from the CA automatically. The RA will still need the JWK provisioner password, to decrypt the key. The JWK password can be supplied in a "password" field in the "certificateIssuer" block, or it can be passed in via a file when starting the RA:

$ step-ca --issuer-password-file=$(step path)/secrets/issuer_jwk_password.txt $(step path)/config/ca.json

Your RA is ready to process certificate requests.

Securing the issuer credentials with a different password

If you don't want your RA to hold the CA's provisioner password, you can create a new key for your RA by decrypting the CA's JWK provisioner key and re-encrypting it with a new password for the RA.

  1. On the CA, create a new private key that the RA will use:

    $ jq -r '.authority.provisioners[] | select(.type == "JWK") | .encryptedKey' < $(step path)/config/ca.json | step crypto key format --pkcs8 > ra.key.pem

    You will be asked for the JWK password to decrypt the provisioner key, and a new password to encrypt a new PEM file for your RA.

  2. Transfer ra.key.pem to your RA

    On the RA, save the PEM file into $STEPPATH/secrets and configure the "certificateIssuer" as follows:

    { "type": "jwk", "provisioner": "jane@smallstep.com", "key": "/home/jane/.step/secrets/ra.key.pem" }

Finally, use --issuer-password-file to provide a password file when starting up the CA.

Using the X5C provisioner

Instead of a JWK, the RA can alternatively use certificate authentication with the CA, via the X5C Provisioner. While this approach is more complex, it offers more security because it uses expiring certificates instead of non-expiring JWKs.

In this scenario, the RA "certificateIssuer" block will need to be configured with a certificate and key signed by a CA that the CA's X5C provisioner is configured to trust.

To configure an RA ↔ CA connection using an X5C provisioner, first add an X5C provisioner to the CA. (See the X5C Provisioner configuration section.)

Next, change the "certificateIssuer" object in the RA configuration as follows:

{ "type": "x5c", "provisioner": "X5C", "crt": "/home/jane/.step/certs/x5c.crt", "key": "/home/jane/.step/secrets/x5c.key" }

These files will are read by step-ca upon every certificate request made to the RA.

Finally, use --issuer-password-file to provide a private key password file when starting up the CA.

StepCAS Caveats

An RA can use any step-ca provisioners to authenticate requests, but there are some caveats:

  • step ca renew (mTLS renewals) are not yet supported between step clients and a StepCAS RA.
  • ACME load balancing across StepCAS RAs is not officially supported, but if may work just fine if you use a shared mysql database for your RAs.
  • StepCAS RA keys cannot be stored in KMS or HSMs; only files are supported.

Google Cloud CAS RA mode

This RA mode allows you to use Google Cloud Certificate Authority Service (CAS) as your CA.

The top of an example ca.json for cloudCAS looks like:

{ "address": ":9000", "dnsNames": ["ra.smallstep.com"], "authority": { "type": "cloudCAS", "credentialsFile": "/path/to/credentials.json", "certificateAuthority": "projects/<name>/locations/<loc>/certificateAuthorities/<intermediate-name>" }, "..." }

When CloudCAS is enabled, it will retrieve the root certificate from the configured certificate authority, so there is no need to configure the "root" or "crt" in ca.json.

Creating a CloudCAS RA

Before enabling CloudCAS in step-ca, you'll need to set up your Google Cloud project using the gcloud CLI:

  1. Create or define a Google Cloud Platform project to use. In this example, we'll call it smallstep-cas-test.

  2. Enable the CA service API. Run:

    $ gcloud services enable privateca.googleapis.com
  3. Configure API access.

    Start by creating a service account for accessing CAS:

    $ gcloud iam service-accounts create step-ca-sa \ --project smallstep-cas-test \ --description "Step-CA Service Account" \ --display-name "Step-CA Service Account"

    Now add permissions to use the CAS API:

    $ gcloud projects add-iam-policy-binding smallstep-cas-test \ --member=serviceAccount:step-ca-sa@smallstep-cas-test.iam.gserviceaccount.com \ --role=roles/privateca.caManager $ gcloud projects add-iam-policy-binding smallstep-cas-test \ --member=serviceAccount:step-ca-sa@smallstep-cas-test.iam.gserviceaccount.com \ --role=roles/privateca.certificateRequester

    Finally, download the service account credentials, which you will use to configure the RA:

    $ gcloud iam service-accounts keys create credentials.json \ --iam-account step-ca-sa@smallstep-cas-test.iam.gserviceaccount.com

    The RA will use the service account you just created, via the keys you exported into credentials.json.

  4. Initialize your PKI.

    Run step ca init --ra cloudcas to create a new PKI in CAS. This will generate a root and intermediate certificate in Google CAS. You will be prompted for your GCP project id, a resource id, region, and other information.

    Here's an example of what a ca.json for a CloudCAS RA might look like:

    { "address": ":443", "dnsNames": ["ra.example.com"], "logger": {"format": "text"}, "db": { "type": "badger", "dataSource": "/home/jane/.step/db", }, "authority": { "type": "cloudCAS", "credentialsFile": "/home/jane/.step/credentials.json", "certificateAuthority": "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/prod-intermediate-ca", "provisioners": [ { "type": "JWK", "name": "jane@example.com", "key": { "use": "sig", "kty": "EC", "kid": "ehFT9BkVOY5k_eIiMax0ZxVZCe2hlDVkMwZ2Y78av4s", "crv": "P-256", "alg": "ES256", "x": "GtEftN0_ED1lNc2SEUJDXV9EMi7JY-kqINPIEQJIkjM", "y": "8HYFdNe1MbWcbclF-hU1L80SCmMcZQI6vZfTOXfPOjg" }, "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjEwMDAwMCwicDJzIjoiSjBSWnY5UFZrM3JKRUJkem5RbExzZyJ9.Fiwvo-RIKU5G6v5udeCT1nlX87ElxrocP2FcgNs3AqEz5OH9H4suew.NmzUJR_9xv8ynQC8.dqOveA_G5kn5lxjxnEZoJCystnJMVYLkZ_8CVzfJQhYchbZfNk_-FKdIuQxeWWBzvmomsILFNtLOIUoqSt30qk83lFyGQWN8Ke2bK5DhuwojF7RI_UqkMyiKP0F28Z4ZFhfQP5D2ZT_stoFaMlU8eak0-T8MOiBIfdAJTWM9x2DN-68mtUBuL5z5eU8bqsxELnjGauD_GHTdnduOosmYsw8vp_PmffTTwqUzDFH1RhkeSmRFRZntAizZMGYkxLamquHI3Jvuqiv4eeJ3yLqh3Ppyo_mVQKnxM7P9TyTxcvLkb2dB3K-cItl1fpsz92cy8euKsKG8n5-hKFRyPfY.j7jBN7nUwatoSsIZuNIwHA" } ] }, "tls": { "cipherSuites": [ "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ], "minVersion": 1.2, "maxVersion": 1.3, "renegotiation": false } }

    The "credentialsFile" field isn't the only option for providing Google Cloud credentials.json filename to the RA. You can alternatively set the GOOGLE_APPLICATION_CREDENTIALS environment variable.

  5. Start step-ca

    step-ca will automatically download the root certificate from Google CAS. In ca.json, you don't need to configure "root", and because the intermediate is in Google Cloud, "crt" and "key" are also not needed.

    The RA will print your CA's root fingerprint upon startup:

    $ step-ca /home/jane/.step/config/ca.json 2020/09/22 13:17:15 Using root fingerprint '3ef16343cf0952eedbe2b843066bb798fa7a7bceb16aa285e8b0399f661b28b7' 2020/09/22 13:17:15 Serving HTTPS on :9000 ...

    Save the fingerprint! You will need it to bootstrap new clients into your PKI:

    $ step ca bootstrap --ca-url https://ra.example.com --fingerprint 3ef16343cf0952eedbe2b843066bb798fa7a7bceb16aa285e8b0399f661b28b7

    Finally, we can sign sign a certificate as always:

    $ step ca certificate test.example.com test.crt test.key
Subscribe

Unsubscribe anytime. See our privacy policy.