Introducing step Certificates, secure, automated certificate management
Mike Malone
An open source solution for secure automated certificate management
At smallstep we've been focused, lately, on building technology that makes it easier for you to access your stuff. As things stand today, access is really hard. It's really hard for developers to access internal services in production and pre-production environments (e.g., to debug using curl
). It's really hard for your stuff running in AWS to access your stuff running in GCP and vice-versa. It's really hard for employees to access enterprise IT applications from their iPhones while they're on the bus heading into work. Supporting these use cases, and dozens more like them, is so hard that it's often not done. Access that would make people more productive, make systems better, reduce costs, improve performance, and generally improve software and employee well-being is simply denied. That sucks.
There's a simple and universal answer that makes access easy. Fundamentally, if you're able to authenticate the identities of two things that are trying to communicate, and if that communication is encrypted, nothing else matters. Instead of relying on trusted networks, access can be authorized based on identity, and security can be guaranteed cryptographically. Even better, you can ditch VPNs and whatever operational nightmare powers your network policy rube goldberg machine. For code and devices it's actually really easy to do this using TLS. But there's a catch: to use TLS you need certificates — a sort of credential that TLS uses for authentication.
Certificates are the best way to identify code and devices. The problem is that certificate management is really hard. So hard, and so abstruse, that many people assume it's not for them… or at least not worth their while. That's wrong. It is. But it's true that there's no good way to automate the secure delivery of certificates to devices and workloads, and to manage certificate lifecycle (renewal and whatnot). That's why we built step certificates (GitHub). It's an open source project that makes secure automated certificate management easy, so you can use TLS and easily access anything, running anywhere, from everywhere.
Before we start, you might want to install step
so you can follow along.
On macOS using brew:
$ brew install step
Binary tarballs are also available for Linux. For instructions on building from source see GitHub.
More than a Certificate Authority
At the core of step certificates is an online certificate authority (CA): a piece of infrastructure that most production environments don't have (but should). A CA signs certificates. Other components verify certificates by checking signatures. The step CA is lightweight, fast, and easy to operate. It scales horizontally, and multiple instances can be deployed for high availability.
But step certificates is more than a certificate authority. It provides all the missing bits you need to run your own internal public key infrastructure (PKI). It exposes APIs and extends the step CLI (blog, GitHub) with subcommands to fill all the gaps that keep internal PKI out of reach for most teams and organizations. It's all the stuff you need to issue certificates everywhere, and to securely access your stuff from anywhere using TLS.
To use TLS your code and devices need to generate keys and obtain certificates from the CA. The root certificate that belongs to the CA also needs to be distributed everywhere (it's used to verify certificate signatures). Certificates expire and stop working, so they need to be renewed before that happens. These processes need to be secure. They also need to be automated. Step certificates does all this and more.
Let's look at some example workflows to see how.
Generating keys and getting certificates
Once you have a CA up and running, step certificates makes generating keys and obtaining a certificate trivial. You need just one simple command:
$ step ca certificate \ > --ca-url https://127.0.0.1:4443 svc.example.com svc.crt svc.key ✔ Key ID: ngipT8B4OflglLFbHeoUALBqgbcf2mxRyvXLIbwqPHI (mike@example.com) Please enter the password to decrypt the provisioner key: ✔ CA: https://127.0.0.1:4443/1.0/sign
The positional arguments in the step ca certificate
command indicate the name we'd like bound in the certificate (e.g., the DNS host name of a service) and the filenames to write the certificate and private key out to.
We can check our work using step certificate inspect
:
$ step certificate inspect svc.crt Certificate: Data: Version: 3 (0x2) Serial Number: 99511617599707626862884213396651347299 (0x4add3d8bd5dccde77b1822af06038d63) Signature Algorithm: ECDSA-SHA256 Issuer: CN=Smallstep Intermediate CA Validity Not Before: Dec 8 00:27:47 2018 UTC Not After : Dec 9 00:27:47 2018 UTC Subject: CN=svc.example.com Subject Public Key Info: Public Key Algorithm: ECDSA Public-Key: (256 bit) X: 46:84:96:f3:f9:12:cc:36:e4:40:15:a9:d0:e1:39: 15:ae:36:49:7e:77:39:bb:c4:b9:0f:8a:da:3e:88: 33:5b Y: c7:59:12:eb:7e:76:b4:d1:f8:9e:19:07:c0:81:a9: 1a:c5:25:33:ee:16:9b:76:d4:c2:10:bf:9d:45:e6: d0:23 Curve: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication, TLS Web Client Authentication X509v3 Subject Key Identifier: 11:E7:0C:13:59:99:1F:ED:C5:DB:FE:CD:84:A1:E9:98:64:9D:E6:53 X509v3 Authority Key Identifier: keyid:F5:56:DA:43:03:26:5C:3B:01:20:E7:48:47:2C:89:47:05:AE:24:53 X509v3 Subject Alternative Name: DNS:svc.example.com X509v3 Step Provisioner: Type: JWK Name: puppet CredentialID: 9-xRy5cOcz95L_JrsRy9VdPBccXcgmli-xkVMkkDMb4 Signature Algorithm: ECDSA-SHA256 30:45:02:21:00:b1:34:bd:fc:84:c2:63:5c:9d:9c:27:06:f9: f0:b9:d0:e5:5a:5e:5f:c1:3a:ef:2b:92:48:99:2e:f1:b4:1b: 97:02:20:4b:4b:f7:44:9c:02:62:b8:50:75:1d:54:61:98:b4: 62:a9:34:b4:6c:4e:8e:31:44:4e:a8:1f:67:8a:8a:ef:6c
If you already have a certificate signing request, that's fine too. Just use step ca sign
instead:
$ step certificate create --csr foo.example.com foo.csr foo.key Please enter the password to encrypt the private key: $ step ca sign --ca-url https://127.0.0.1:4443 foo.csr foo.crt ✔ Key ID: ngipT8B4OflglLFbHeoUALBqgbcf2mxRyvXLIbwqPHI (mike@example.com) Please enter the password to decrypt the provisioner key: ✔ CA: https://127.0.0.1:4443/1.0/sign
Subscribe to updates
Unsubscribe anytime, see Privacy Policy
Using certificates with TLS
As mentioned, clients and servers need to be configured to use the CA's root certificate. step
can grab this root certificate from the CA and verify it using a fingerprint (which can be produced using step certificate fingerprint
and embedded in scripts):
$ step certificate fingerprint $(step path)/certs/root_ca.crt 88396437f52fce12d8e281cb4f30dc8cdab3a7b69fc801d1b1407719dfad5fe2 $ step ca root --ca-url https://127.0.0.1:4443 \ > --fingerprint 88396437f52fce12d8e281cb4f30dc8cdab3a7b69fc801d1b1407719dfad5fe2 \ > root.crt $ step certificate inspect root.crt Certificate: Data: Version: 3 (0x2) Serial Number: 97076258001818714665341080528039489243 (0x4908350c345c28a45b766721789caedb) Signature Algorithm: ECDSA-SHA256 Issuer: CN=Smallstep Root CA Validity Not Before: Dec 7 23:20:20 2018 UTC Not After : Dec 4 23:20:20 2028 UTC Subject: CN=Smallstep Root CA Subject Public Key Info: Public Key Algorithm: ECDSA Public-Key: (256 bit) X: f8:38:f7:6a:a8:ae:d9:3f:9e:4f:bc:47:5d:b1:04: ea:89:1a:8c:22:d9:7b:2b:69:d4:f6:5f:54:f5:27: 95:13 Y: de:7b:bc:59:67:3f:c3:13:4d:46:ad:9f:fd:aa:a1: b2:21:be:7a:cd:90:f4:33:dc:fd:1e:dd:44:f8:eb: 0e:34 Curve: P-256 X509v3 extensions: X509v3 Key Usage: critical Digital Signature, Key Encipherment, Certificate Sign, CRL Sign X509v3 Basic Constraints: critical CA:TRUE, pathlen:1 X509v3 Subject Key Identifier: A1:22:E7:72:B1:5B:AA:06:CD:CA:EF:13:7E:9D:4B:DE:63:6E:13:B3 Signature Algorithm: ECDSA-SHA256 30:44:02:20:69:f8:69:59:84:a9:9a:6c:59:7f:73:27:0c:3d: eb:a2:c5:58:bb:e0:34:0c:a5:ec:6c:70:83:61:02:29:c0:93: 02:20:4c:b0:78:bf:e0:03:1d:6d:14:a6:5d:86:66:b3:22:7e: ea:38:8b:c6:7b:1c:f0:f1:ad:cb:2e:0a:36:7f:ba:37
You can use this root certificate to curl
a service using TLS:
$ curl --cacert root.crt https://svc.example.com hi
If your service requires mutual authentication (mTLS) you can also generate a short-lived certificate for yourself, for client authentication with curl
:
$ step ca certificate mike@example.com mike.crt mike.key \ > --not-after 10m \ > --ca-url https://127.0.0.1:4443 $ curl --cacert root.crt --cert mike.crt --key mike.key https://svc.example.com hi
If work takes longer than expected, you can use step
to renew this certificate before it expires:
$ step certificate inspect mike.crt --format json | jq .validity.end "2018-12-07T23:37:16Z" $ step ca renew mike.crt mike.key --ca-url https://127.0.0.1:4443 Would you like to overwrite mike.crt [Y/n]: Y $ step certificate inspect mike.crt --format json | jq .validity.end "2018-12-07T23:40:43Z"
Setting up services to use these certificates is generally not that hard (though it helps to have a good understanding of how certificates and PKI work). One nice thing about TLS is that you have the option of baking it into your applications for complete end-to-end encryption or deploying a sidecar proxy to terminate TLS with no code change required. Let's look quickly at both scenarios.
Baking TLS into an application might sound hard, but once you have certificates it's really not that bad. You'll have to check out the docs for your language or framework for particulars but, generally, you'll just need to parameterize some TLS API with your root certificate, leaf certificate, and private key. Here's a working example in golang:
package main import ( "net/http" "log" ) func HiHandler(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "text/plain") w.Write([]byte("Hello, world!\n")) } func main() { http.HandleFunc("/hi", HiHandler) err := http.ListenAndServeTLS(":8443", "svc.crt", "svc.key", nil) if err != nil { log.Fatal(err) } }
There's still some work to do because this certificate will eventually expire. We'll need to renew and rotate it somehow before that happens. If you're doing end-to-end TLS like this, the only thing that ever needs the private key is the application itself. So it's unfortunate that we're generating the private key outside the application and putting it on disk. Fixing all this is a bit more work. To make all of this easy, step certificates includes a golang SDK that generates keys and obtains a certificate for you and will automatically renew it before it expires. There's an example in the step certificates repository. Another example provides a more thorough demonstration of everything the SDK can do.
We'd like to provide SDKs for other languages. Right now it's golang only. This is a relatively easy and high value area to contribute, if anyone's interested!
If you'd rather not change any code, pretty much every proxy can do [m]TLS with a bit of light configuration. For this sort of deployment you can throw step ca renew
in a cron job with the --expires-in
and --force
flags to keep your certificates fresh. Some proxies require a kill -HUP
to pick up the new certificates, so consult your docs.
So far we've used step ca certificate
to generate a key pair, construct a certificate signing request (CSR), and make an authenticated request to the CA to obtain a certificate with a single command. To authenticate we've been prompted for a password to decrypt a provisioner key. A provisioner is simply a thing that's authorized by the CA to assign names and enroll people and things in your PKI (i.e., get certificates). We'll discuss how all this works in more detail in the next section.
Provisioners are specified simply in the CA's configuration file, located by default at $(step path)/config/ca.json
. You can use step
to manage this file, or edit it directly. A single provisioner is added automatically when the CA is first initialized, but you can add more by creating a key pair and registering it with the CA (i.e., adding the provisioner's name and key to the CA configuration file). If you do add multiple provisioners, you'll be prompted to select which to use when you create certificates.
$ step crypto jwk create puppet.pub.json puppet.key.json Please enter the password to encrypt the private JWK: $ step ca provisioner add \ > --ca-config $(step path)/config/ca.json \ > puppet puppet.key.json Please enter the password to decrypt puppet.key.json: Please enter the password to encrypt the private JWK: $ step ca certificate \ > --not-after 10m \ > --ca-url https://127.0.0.1:4443 \ > max@example.com max.crt max.key Use the arrow keys to navigate: ↓ ↑ → ← What provisioner key do you want to use? ngipT8B4OflglLFbHeoUALBqgbcf2mxRyvXLIbwqPHI (mike@example.com) ▸ 9-xRy5cOcz95L_JrsRy9VdPBccXcgmli-xkVMkkDMb4 (puppet)
Interactive workflows and passwords make sense for people, but they're no good for automation. Let's take a closer look at the bootstrapping protocol and, more broadly, at how step certificates makes it easy to automate certificate management.
Automated Certificate Management
Provisioners are instrumental in facilitating automation. Name assignments are made via signed attestations that are generated by the provisioner, passed to the thing being provisioned, then used to authenticate certificate requests submitted to the CA — to obtain a certificate.
The hardest problem here is securing this first interaction between a thing that needs a certificate and the CA. Authentication is required, but we can't use certificates because a thing that needs a certificate obviously doesn't have one yet.
What's needed is some trusted component that can measure and attest to the identity of a thing that needs a certificate. For systems under operational automation, the right tool for this job is whatever's already being used to provision systems and deploy code: Puppet, Chef, Ansible, Kubernetes, Terraform, etc. You already trust this stuff to deploy the right code to the right places. To do so these tools must have some notion of identity. So they're trusted and capable of assigning names and signing attestations. The stuff you're already using for provisioning should be your step certificates "provisioner" (hence the name). Step certificates makes using these tools for this purpose super easy. That's how we automate certificate management.
Summarizing, provisioners help authenticate the initial request for a certificate. They do this by putting the name of the thing being provisioned in an attestation that the CA can verify: a short-lived one-time token called a bootstrap token. Bootstrap tokens are signed by the provisioner's private key, and passed to whatever's being provisioned to authenticate with the CA.
The unified step ca certificate
command we used above is actually a wrapper around this more elaborate process. For automation we'll break this process into its constituent parts. The bootstrap token will be generated in one place and used in another.
If you're confused, an example should clear things up. Let's start by generating a bootstrap token for svc.example.com
and use it to obtain a certificate:
$ step ca token --ca-url https://127.0.0.1:4443 svc.example.com ✔ Key ID: 9-xRy5cOcz95L_JrsRy9VdPBccXcgmli-xkVMkkDMb4 (puppet) Please enter the password to decrypt the provisioner key: eyJhbGciOiJFUzI1NiIsImtpZCI6IjkteFJ5NWNPY3o5NUxfSnJzUnk5VmRQQmNjWGNnbWxpLXhrVk1ra0RNYjQiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovLzEyNy4wLjAuMTo0NDQzLzEuMC9zaWduIiwiZXhwIjoxNTQ0MjQ3MTE5LCJpYXQiOjE1NDQyNDY4MTksImlzcyI6InB1cHBldCIsImp0aSI6IjMyNDViN2Y5MmJjYjA1NjIxNDNlZWNhYWRjYzQ3ZWI5Y2NmYjdiNDIzYzVkODUzNTg1NGNiOTE5MDAzOTE4MWMiLCJuYmYiOjE1NDQyNDY4MTksInNoYSI6Ijg4Mzk2NDM3ZjUyZmNlMTJkOGUyODFjYjRmMzBkYzhjZGFiM2E3YjY5ZmM4MDFkMWIxNDA3NzE5ZGZhZDVmZTIiLCJzdWIiOiJzdmMuZXhhbXBsZS5jb20ifQ.kqvZ4UdZSJI3sPEWVHqVBFB3xtnTwgDaho4ud-nOr8l5D37lvuFrfIxpWXZY1fZq0rU9mkYAr8mP7s8J2OLegA
This opaque looking token is actually a JWT: an RFC standard, JSON-based, base64-encoded signed assertion with good library support in pretty much every programming language. For fun, let's take a peek at the token contents (using step crypto jwt inspect
):
$ step ca token --ca-url https://127.0.0.1:4443 svc.example.com \ > | step crypto jwt inspect --insecure ✔ Key ID: 9-xRy5cOcz95L_JrsRy9VdPBccXcgmli-xkVMkkDMb4 (puppet) Please enter the password to decrypt the provisioner key: { "header": { "alg": "ES256", "kid": "9-xRy5cOcz95L_JrsRy9VdPBccXcgmli-xkVMkkDMb4", "typ": "JWT" }, "payload": { "aud": "https://127.0.0.1:4443/1.0/sign", "exp": 1544247162, "iat": 1544246862, "iss": "puppet", "jti": "5de525fc8e6aea2c78a3fca50b537dc212a0441db97224aab1e88e2a69048f7d", "nbf": 1544246862, "sha": "88396437f52fce12d8e281cb4f30dc8cdab3a7b69fc801d1b1407719dfad5fe2", "sub": "svc.example.com" }, "signature": "AG4vyZDDjI3Wn9y_E6vP9xhZAI9-5iEK4XnCSWccpa0B5yoxqn9JcImbnheuaB2L1ICVr2wsJ0tJKG8efYDvxw" }
The best way to generate bootstrap tokens and get them where they're needed depends on which tools you're using and the particulars of your environment. Stay tuned for more guidance here as we document best practices for various tools.
For now, in general, the easiest thing to do is to place the private key on a machine that's been locked down and is only accessible by your configuration management or orchestration system. With a provisioner's unencrypted private key available, a bootstrap token can be generated non-interactively to support automation:
$ step crypto jwe decrypt < puppet.key.json > puppet-cleartext.key.json Please enter the password to decrypt the content encryption key: $ step ca token svc.example.com \ > --offline \ > --kid 9-xRy5cOcz95L_JrsRy9VdPBccXcgmli-xkVMkkDMb4 \ > --issuer puppet \ > --key puppet-cleartext.key.json \ > --ca-url https://127.0.0.1:443 eyJhbGciOiJFUzI1NiIsImtpZCI6IjkteFJ5NWNPY3o5NUxfSnJzUnk5VmRQQmNjWGNnbWxpLXhrVk1ra0RNYjQiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovLzEyNy4wLjAuMTo0NDMvMS4wL3NpZ24iLCJleHAiOjE1NDQyMjg2NjIsImlhdCI6MTU0NDIyODM2MiwiaXNzIjoicHVwcGV0IiwianRpIjoiZGJkZTEwN2E2OTg1YjNmMDc4ZTA1NDU2MDAzNjc1YzY2ZDk1M2FlOGYyN2U1ZGM0ODhhNDRkNmQ3YWRkNzE1YSIsIm5iZiI6MTU0NDIyODM2Miwic2hhIjoiODgzOTY0MzdmNTJmY2UxMmQ4ZTI4MWNiNGYzMGRjOGNkYWIzYTdiNjlmYzgwMWQxYjE0MDc3MTlkZmFkNWZlMiIsInN1YiI6InN2Yy5leGFtcGxlLmNvbSJ9.82nW7kAAbd8H3W_HypU003nilbpGkbUl33HRSVqZDFfzBEcK3UbDwY3DQYijnVE-YIJ3hUAytIWU-_qI8up1cw
The various flags here specify which provisioner to use (the key ID and issuer). You can get this information using step ca provisioner list
.
Once a token has been generated it can be transmitted and used locally wherever a certificate is required:
$ export TOKEN="eyJhbGciOiJFUzI1NiIsImtpZCI6IjkteFJ5NWNPY3o5NUxfSnJzUnk5VmRQQmNjWGNnbWxpLXhrVk1ra0RNYjQiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovLzEyNy4wLjAuMTo0NDMvMS4wL3NpZ24iLCJleHAiOjE1NDQyMjg2NjIsImlhdCI6MTU0NDIyODM2MiwiaXNzIjoicHVwcGV0IiwianRpIjoiZGJkZTEwN2E2OTg1YjNmMDc4ZTA1NDU2MDAzNjc1YzY2ZDk1M2FlOGYyN2U1ZGM0ODhhNDRkNmQ3YWRkNzE1YSIsIm5iZiI6MTU0NDIyODM2Miwic2hhIjoiODgzOTY0MzdmNTJmY2UxMmQ4ZTI4MWNiNGYzMGRjOGNkYWIzYTdiNjlmYzgwMWQxYjE0MDc3MTlkZmFkNWZlMiIsInN1YiI6InN2Yy5leGFtcGxlLmNvbSJ9.82nW7kAAbd8H3W_HypU003nilbpGkbUl33HRSVqZDFfzBEcK3UbDwY3DQYijnVE-YIJ3hUAytIWU-_qI8up1cw" $ step ca certificate --token $TOKEN svc.example.com svc.crt svc.key ✔ CA: https://127.0.0.1:4443/1.0/sign
Starting the CA
To wrap up our demo let's initialize a CA from scratch and start it running.
Like provisioner management, step certificates provides a command line workflow to simplify initial configuration. It asks you a couple questions then writes a working configuration to the default location:
$ step ca init ✔ What would you like to name your new PKI? (e.g. Smallstep): Smallstep ✔ What DNS/IP address(es) will your CA use? (e.g. ca.smallstep.com[,1.1.1.1,etc.]): 127.0.0.1 ✔ What address will your new CA listen on? (e.g. :443): :4443 ✔ What would you like to name your first provisioner? (e.g. you@example.com): mike@example.com ✔ What do you want your password to be? [leave empty and we'll generate one]: Generating root certificate... all done! Generating intermediate certificate... all done! ✔ Root certificate: ~/.step/certs/root_ca.crt ✔ Root private key: ~/.step/secrets/root_ca_key ✔ Root fingerprint: 922eb076c7430e5b0ceca3a2be13a7329f7879a2c35a514017554cb11fd1c249 ✔ Intermediate certificate: ~/.step/certs/intermediate_ca.crt ✔ Intermediate private key: ~/.step/secrets/intermediate_ca_key ✔ Certificate Authority configuration: ~/.step/config/ca.json Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.
If you have an existing PKI, step certificates can also federate or operate as a subordinate to your existing root. See the documentation for step ca init
for more information.
That's it. Once you've run this initialization process you're ready to go. The online certificate authority itself ships as a separate binary called step-ca
, so it's not necessary to have step
installed to run the CA (though it doesn't hurt and is useful to have available).
All that's left is to start the CA:
$ step-ca $(step path)/config/ca.json Please enter the password to decrypt ~/.step/secrets/intermediate_ca_key: 2018/12/07 17:49:59 Serving HTTPS on :4443 …
Roadmap
Everything documented here is ready to go, but we've got a long roadmap of features we'd like to add to step certificates in the future. We're committed to open source — we believe everyone deserves world class PKI — and we're also working on an enterprise offering to better integrate in enterprise environments and address enterprise-specific use cases. We'd love your input on where to draw the line between these two offerings.
Anyways, there are a bunch of advanced features to add like revocation (CRL and OCSP), HSM support, and certificate transparency (CT). We'd also like to add a UI to supplement the command line tool and to help with certificate management (e.g., present a searchable certificate inventory built off of CT). The bootstrap protocol is great, but we'd like to support alternative authentication mechanisms like mTLS and OAuth OIDC for agent-based certificate provisioning and to allow people to easily obtain certificates for themselves, respectively. Bootstrapping was designed to be simple and universal for MVP, but we'd like to harden the protocol with support for additional factors like instance identity documents in cloud environments and MFA for people. As mentioned, we'd also like to provide SDKs in other languages.
Try it out
We'd love to hear from you if there are any other missing features, bugs, or oversights, and to help prioritize these existing ideas. If you have thoughts, please shoot us a tweet or open an issue on GitHub.
Please, clone, and fork us on GitHub! Step certificates builds on step CLI, so you might want to check that out too. We'd love to hear your feedback and merge your pull requests.
Subscribe to updates
Unsubscribe anytime, see Privacy Policy
Mike Malone has been working on making infrastructure security easy with Smallstep for six years as CEO and Founder. Prior to Smallstep, Mike was CTO at Betable. He is at heart a distributed systems enthusiast, making open source solutions that solve big problems in Production Identity and a published research author in the world of cybersecurity policy.