1
0
mirror of https://github.com/smallstep/cli.git synced 2025-08-09 03:22:43 +03:00

Merge branch 'master' into ssh-ca

This commit is contained in:
Mariano Cano
2019-09-05 23:48:18 +02:00
47 changed files with 853 additions and 533 deletions

11
.github/ISSUE_TEMPLATE/enhancement.md vendored Normal file
View File

@@ -0,0 +1,11 @@
---
name: CLI Enhancement
about: Suggest an enhancement to step cli
labels: area/cert-management enhancement
---
### What would you like to be added
### Why this is needed

4
.gitignore vendored
View File

@@ -19,3 +19,7 @@ coverage.txt
output
vendor
step
# Ignore modules until switch from gopkg
go.mod
go.sum

67
.golangci.yml Normal file
View File

@@ -0,0 +1,67 @@
linters-settings:
govet:
check-shadowing: true
settings:
printf:
funcs:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
golint:
min-confidence: 0
gocyclo:
min-complexity: 10
maligned:
suggest-new: true
dupl:
threshold: 100
goconst:
min-len: 2
min-occurrences: 2
depguard:
list-type: blacklist
packages:
# logging is allowed only by logutils.Log, logrus
# is allowed to use only in logutils package
- github.com/sirupsen/logrus
misspell:
locale: US
lll:
line-length: 140
goimports:
local-prefixes: github.com/golangci/golangci-lint
gocritic:
enabled-tags:
- performance
- style
- experimental
disabled-checks:
- wrapperFunc
- dupImport # https://github.com/go-critic/go-critic/issues/845
linters:
disable-all: true
enable:
- gofmt
- golint
- vet
- misspell
- ineffassign
- deadcode
run:
skip-dirs:
- pkg
issues:
exclude:
- can't lint
- declaration of "err" shadows declaration at line
- should have a package comment, unless it's in another file for this package
# golangci.com configuration
# https://github.com/golangci/golangci/wiki/Configuration
service:
golangci-lint-version: 1.17.x # use the fixed version to not introduce new linters unexpectedly
prepare:
- echo "here I can run custom commands, but no preparation needed for this repo"

122
Gopkg.lock generated
View File

@@ -9,14 +9,6 @@
pruneopts = "UT"
revision = "e2d15f34fcf99d5dbb871c820ec73f710fca9815"
[[projects]]
branch = "master"
digest = "1:c10265d5a71326618d37e97169eddb3582f78e8ac7dcf87403b4cf619efd519a"
name = "github.com/DHowett/go-plist"
packages = ["."]
pruneopts = "UT"
revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876"
[[projects]]
branch = "master"
digest = "1:655f3b07160fbe90713062296ef215c096ad4308bdc0081620cacd9b9d46dce5"
@@ -25,21 +17,6 @@
pruneopts = "UT"
revision = "5482f03509440585d13d8f648989e05903001842"
[[projects]]
digest = "1:304cb78c285eaf02ab529ad02a257cad9b4845022915e6c82f87860ac53222d8"
name = "github.com/alecthomas/gometalinter"
packages = ["."]
pruneopts = "UT"
revision = "bae2f1293d092fd8167939d5108d1b025eaef9de"
[[projects]]
branch = "master"
digest = "1:c198fdc381e898e8fb62b8eb62758195091c313ad18e52a3067366e1dda2fb3c"
name = "github.com/alecthomas/units"
packages = ["."]
pruneopts = "UT"
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]]
digest = "1:320e7ead93de9fd2b0e59b50fd92a4d50c1f8ab455d96bc2eb083267453a9709"
name = "github.com/asaskevich/govalidator"
@@ -68,17 +45,6 @@
pruneopts = "UT"
revision = "2972be24d48e78746da79ba8e24e8b488c9880de"
[[projects]]
digest = "1:848ef40f818e59905140552cc49ff3dc1a15f955e4b56d1c5c2cc4b54dbadf0c"
name = "github.com/client9/misspell"
packages = [
".",
"cmd/misspell",
]
pruneopts = "UT"
revision = "b90dc15cfd220ecf8bbc9043ecb928cef381f011"
version = "v0.3.4"
[[projects]]
branch = "master"
digest = "1:cc439e1d9d8cff3d575642f5401033b00f2b8d0cd9f859db45604701c990879a"
@@ -134,17 +100,6 @@
revision = "72cd26f257d44c1114970e19afddcd812016007e"
version = "v1.4.1"
[[projects]]
branch = "travis-1.9"
digest = "1:e8f5d9c09a7209c740e769713376abda388c41b777ba8e9ed52767e21acf379f"
name = "github.com/golang/lint"
packages = [
".",
"golint",
]
pruneopts = "UT"
revision = "883fe33ffc4344bad1ecd881f61afd5ec5d80e0a"
[[projects]]
digest = "1:318f1c959a8a740366fce4b1e1eb2fd914036b4af58fbd0a003349b305f118ad"
name = "github.com/golang/protobuf"
@@ -169,22 +124,6 @@
revision = "3629d6846518309d22c16fee15d1007262a459d2"
version = "v1.0.21"
[[projects]]
branch = "master"
digest = "1:750e747d0aad97b79f4a4e00034bae415c2ea793fd9e61438d966ee9c79579bf"
name = "github.com/google/shlex"
packages = ["."]
pruneopts = "UT"
revision = "6f45313302b9c56850fc17f99e40caebce98c716"
[[projects]]
branch = "master"
digest = "1:824d147914b40e56e9e1eebd602bc6bb9761989d52fd8e4a498428467980eb17"
name = "github.com/gordonklaus/ineffassign"
packages = ["."]
pruneopts = "UT"
revision = "1003c8bd00dc2869cb5ca5282e6ce33834fed514"
[[projects]]
branch = "master"
digest = "1:22725c01ecd8ed0c0f0078944305a57053340d92878b02db925c660cc4accf64"
@@ -302,27 +241,6 @@
revision = "f5bce3387232559bcbe6a5f8227c4bf508dac1ba"
version = "v1.11.0"
[[projects]]
digest = "1:07140002dbf37da92090f731b46fa47be4820b82fe5c14a035203b0e813d0ec2"
name = "github.com/nicksnyder/go-i18n"
packages = [
"i18n",
"i18n/bundle",
"i18n/language",
"i18n/translation",
]
pruneopts = "UT"
revision = "0dc1626d56435e9d605a29875701721c54bc9bbd"
version = "v1.10.0"
[[projects]]
digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e"
name = "github.com/pelletier/go-toml"
packages = ["."]
pruneopts = "UT"
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
version = "v1.2.0"
[[projects]]
digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
name = "github.com/pkg/errors"
@@ -410,11 +328,11 @@
[[projects]]
branch = "master"
digest = "1:c3207093bfee46dc9f408a55408d6fa6ed59431bdeb54df2ab89ffa1d8e1bfaf"
digest = "1:f4d37f61cbbd5adb7066017d7e5f303b722a39c3408b41d46a5ea04f81adba8c"
name = "github.com/smallstep/certinfo"
packages = ["."]
pruneopts = "UT"
revision = "fef09aeb6b3b6451151ae248670cf020454c0d5b"
revision = "203093530c86c19d79cfe5ce9ad0b8897e3cce9b"
[[projects]]
branch = "master"
@@ -432,15 +350,15 @@
[[projects]]
branch = "master"
digest = "1:4bde64565730a308d3cebca9b93f19c8e1137f9ba5b57174669ad0f732dec044"
digest = "1:f41de3b55032e81c12f4d109e6c5222e1cff573197a3652b800ca8ac2aaada35"
name = "github.com/smallstep/truststore"
packages = ["."]
pruneopts = "UT"
revision = "b8300b931ab584b7aa01fe43b3c92d5a61cf2ce3"
revision = "8418f8a7d0b74e79026254b4ad23c67dd77fe5f0"
[[projects]]
branch = "master"
digest = "1:167fb96bb586d7abc0714a63d5bd30f24563d369012b295268db4d64008c4f7d"
digest = "1:822ad7c8c41fe68fb9c9c95ad7e77a1172e216d9e3e527451819927448b6dee6"
name = "github.com/smallstep/zcrypto"
packages = [
"json",
@@ -449,7 +367,7 @@
"x509/pkix",
]
pruneopts = "UT"
revision = "0eaa490bf930eb2c8f1fd0dec8750619588aadae"
revision = "6bab21fcaafc3d150cf793b6d5f25fe32f49c80e"
[[projects]]
branch = "master"
@@ -474,14 +392,6 @@
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
branch = "master"
digest = "1:ba52e5a5fb800ce55108b7a5f181bb809aab71c16736051312b0aa969f82ad39"
name = "github.com/tsenart/deadcode"
packages = ["."]
pruneopts = "UT"
revision = "210d2dc333e90c7e3eedf4f2242507a8e83ed4ab"
[[projects]]
branch = "master"
digest = "1:6743b69de0d73e91004e4e201cf4965b59a0fa5caf6f0ffbe0cb9ee8807738a7"
@@ -617,13 +527,6 @@
revision = "54a98f90d1c46b7731eb8fb305d2a321c30ef610"
version = "v1.5.0"
[[projects]]
digest = "1:39efb07a0d773dc09785b237ada4e10b5f28646eb6505d97bc18f8d2ff439362"
name = "gopkg.in/alecthomas/kingpin.v3-unstable"
packages = ["."]
pruneopts = "UT"
revision = "63abe20a23e29e80bbef8089bd3dee3ac25e5306"
[[projects]]
digest = "1:9593bab40e981b1f90b7e07faeab0d09b75fe338880d08880f986a9d3283c53f"
name = "gopkg.in/square/go-jose.v2"
@@ -638,23 +541,19 @@
version = "v2.3.1"
[[projects]]
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
name = "gopkg.in/yaml.v2"
branch = "master"
digest = "1:c10265d5a71326618d37e97169eddb3582f78e8ac7dcf87403b4cf619efd519a"
name = "howett.net/plist"
packages = ["."]
pruneopts = "UT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
revision = "591f970eefbbeb04d7b37f334a0c4c3256e32876"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/ThomasRooney/gexpect",
"github.com/alecthomas/gometalinter",
"github.com/chzyer/readline",
"github.com/client9/misspell/cmd/misspell",
"github.com/golang/lint/golint",
"github.com/gordonklaus/ineffassign",
"github.com/icrowley/fake",
"github.com/manifoldco/promptui",
"github.com/pkg/errors",
@@ -674,7 +573,6 @@
"github.com/smallstep/zlint",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"github.com/tsenart/deadcode",
"github.com/urfave/cli",
"golang.org/x/crypto/argon2",
"golang.org/x/crypto/bcrypt",

View File

@@ -23,18 +23,6 @@
# non-go = false
# go-tests = true
# unused-packages = true
required = [
"github.com/alecthomas/gometalinter",
"github.com/golang/lint/golint",
"github.com/client9/misspell/cmd/misspell",
"github.com/gordonklaus/ineffassign",
"github.com/tsenart/deadcode",
]
[[constraint]]
name = "github.com/alecthomas/gometalinter"
revision = "bae2f1293d092fd8167939d5108d1b025eaef9de"
[[override]]
name = "gopkg.in/alecthomas/kingpin.v3-unstable"
revision = "63abe20a23e29e80bbef8089bd3dee3ac25e5306"

207
README.md
View File

@@ -1,11 +1,6 @@
# Step CLI
`step` is a zero trust swiss army knife. It's an easy-to-use and hard-to-misuse
utility for building, operating, and automating systems that use zero trust
technologies like authenticated encryption (X.509, TLS), single sign-on (OAuth
OIDC, SAML), multi-factor authentication (OATH OTP, FIDO U2F),
encryption mechanisms (JSON Web Encryption, NaCl), and verifiable
claims (JWT, SAML assertions).
`step` is a zero trust swiss army knife that integrates with [`step-ca`](https://github.com/smallstep/certificates) for automated certificate management. It's an easy-to-use and hard-to-misuse utility for building, operating, and automating systems that use zero trust technologies like authenticated encryption (X.509, TLS), single sign-on (OAuth OIDC, SAML), multi-factor authentication (OATH OTP, FIDO U2F), encryption mechanisms (JSON Web Encryption, NaCl), and verifiable claims (JWT, SAML assertions).
[Website](https://smallstep.com) |
[Documentation](https://smallstep.com/docs/cli) |
@@ -26,6 +21,52 @@ claims (JWT, SAML assertions).
![Animated terminal showing step in practice](https://smallstep.com/images/blog/2018-08-07-unfurl.gif)
## Features
`step` is a powerful security tool that's been carefully designed to be safe and easy to use, even if you don't have a favorite elliptic curve or if you're inclined to forget to check the `aud` when you verify a JWT.
- Safe and sane defaults everywhere encourage best practices by making the right thing easy
- Insecure or subtle operations are gated with flags to prevent accidental misuse
- In-depth help with examples is available via `step help`
### Work with [JWTs](https://jwt.io) ([RFC7519](https://tools.ietf.org/html/rfc7519)) and [other JOSE constructs](https://datatracker.ietf.org/wg/jose/documents/)
- [Sign](https://smallstep.com/docs/cli/crypto/jwt/sign), [verify](https://smallstep.com/docs/cli/crypto/jwt/verify), and [inspect](https://smallstep.com/docs/cli/crypto/jwt/inspect) JSON Web Tokens (JWTs)
- [Sign](https://smallstep.com/docs/cli/crypto/jws/sign), [verify](https://smallstep.com/docs/cli/crypto/jws/verify), and [inspect](https://smallstep.com/docs/cli/crypto/jws/inspect/) arbitrary data using JSON Web Signature (JWS)
- [Encrypt](https://smallstep.com/docs/cli/crypto/jwe/encrypt/) and [decrypt](https://smallstep.com/docs/cli/crypto/jwe/decrypt/) data and wrap private keys using JSON Web Encryption (JWE)
- [Create JWKs](https://smallstep.com/docs/cli/crypto/jwk/create/) and [manage key sets](https://smallstep.com/docs/cli/crypto/jwk/keyset) for use with JWT, JWE, and JWS
### Work with X.509 (TLS/HTTPS) certificates
- Create key pairs (RSA, ECDSA, EdDSA) and certificate signing requests (CSRs)
- Create [RFC5280](https://tools.ietf.org/html/rfc5280) and [CA/Browser Forum](https://cabforum.org/baseline-requirements-documents/) compliant X.509 certificates that work **for TLS and HTTPS**
- [Create](https://smallstep.com/docs/cli/certificate/create/) root and intermediate signing certificates (CA certificates)
- Create self-signed & CA-signed certificates, and [sign CSRs](https://smallstep.com/docs/cli/certificate/sign/)
- [Inspect](https://smallstep.com/docs/cli/certificate/inspect/) and [lint](https://smallstep.com/docs/cli/certificate/lint/) certificates on disk or in use by a remote server
- [Install root certificates](https://smallstep.com/docs/cli/certificate/install/) so your CA is trusted by default (issue development certificates **that [work in browsers](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html)**)
- Get certificates from any ACME compliant CA (*coming soon*)
### Connect to [`step-ca`](https://github.com/smallstep/certificates) and get certificates from your own private certificate authority
- [Authenticate and obtain a certificate](https://smallstep.com/docs/cli/ca/certificate/) using any enrollment mechanism supported by `step-ca`
- Securely [distribute root certificates](https://smallstep.com/docs/cli/ca/root/) and [bootstrap](https://smallstep.com/docs/cli/ca/bootstrap/) PKI relying parties
- [Renew](https://smallstep.com/docs/cli/ca/renew/) and [revoke](https://smallstep.com/docs/cli/ca/revoke/) certificates issued by `step-ca`
- [Submit CSRs](https://smallstep.com/docs/cli/ca/sign/) to be signed by `step-ca`
### Command line OAuth and MFA
- [Get OAuth access tokens](https://smallstep.com/docs/cli/oauth/) and OIDC identity tokens at the command line from any provider
- Supports OAuth authorization code, implicit, OOB, jwt-bearer, and refresh token flows
- Automatically launch browser to complete OAuth flow (or use console flow)
- Verify OIDC identity tokens (using `step crypt jwt verify`)
- [Generate and verify](https://smallstep.com/docs/cli/crypto/otp/) TOTP tokens
### NaCl and other crypto utilities
- [Work with NaCl](https://smallstep.com/docs/cli/crypto/nacl/) box, secretbox, and sign constructs
- [Apply key derivation functions](https://smallstep.com/docs/cli/crypto/kdf/) (KDFs) and [verify passwords](https://smallstep.com/docs/cli/crypto/kdf/compare/) using `scrypt`, `bcrypt`, and `argo2`
- Generate and check [file hashes](https://smallstep.com/docs/cli/crypto/hash/)
## Installation Guide
These instructions will install an OS specific version of the `step` binary on
@@ -36,17 +77,13 @@ development](docs/local-development.md) below.
Install `step` via [Homebrew](https://brew.sh/):
<pre><code>
<b>$ brew install step</b>
</code></pre>
<pre><code><b>$ brew install step</b></code></pre>
> Note: If you have installed `step` previously through the `smallstep/smallstep`
> tap you will need to run the following commands before installing:
<pre><code>
<b>$ brew untap smallstep/smallstep</b>
<b>$ brew uninstall step</b>
</code></pre>
>
> <pre><code><b>$ brew untap smallstep/smallstep</b>
> <b>$ brew uninstall step</b></code></pre>
### Linux
@@ -54,12 +91,10 @@ Install `step` via [Homebrew](https://brew.sh/):
Download and install the latest Debian package from [releases](https://github.com/smallstep/cli/releases):
<pre><code>
<b>$ wget https://github.com/smallstep/cli/releases/download/X.Y.Z/step_X.Y.Z_amd64.deb</b>
<pre><code><b>$ wget https://github.com/smallstep/cli/releases/download/X.Y.Z/step_X.Y.Z_amd64.deb</b>
# Install the Debian package:
<b>$ sudo dpkg -i step_X.Y.Z_amd64.deb</b>
</code></pre>
<b>$ sudo dpkg -i step_X.Y.Z_amd64.deb</b></code></pre>
#### Arch Linux
@@ -73,8 +108,7 @@ a sibling repository) can be found [here](https://aur.archlinux.org/packages/ste
You can use [pacman](https://www.archlinux.org/pacman/) to install the packages.
### Test
<pre><code>
<b>$ step certificate inspect https://smallstep.com</b>
<pre><code><b>$ step certificate inspect https://smallstep.com</b>
Certificate:
Data:
Version: 3 (0x2)
@@ -85,18 +119,86 @@ Certificate:
Not Before: Feb 8 13:07:44 2019 UTC
Not After : May 9 13:07:44 2019 UTC
Subject: CN=smallstep.com
[...]
</code></pre>
[...]</code></pre>
## Examples
### X.509 Certificates from `step-ca`
This example assumes you already have [`step-ca`](https://github.com/smallstep/certificates) running at `https://ca.local`.
Get your root certificate fingerprint from the machine running `step-ca`:
<pre><code><b>ca$ step certificate fingerprint $(step path)/certs/root_ca.crt</b>
0eea955785796f0a103637df88f29d8dfc8c1f4260f35c8e744be155f06fd82d</pre></code>
Bootstrap a new machine to trust and connect to `step-ca`:
<pre><code><b>$ step ca bootstrap --ca-url https://ca.local \
--fingerprint 0eea955785796f0a103637df88f29d8dfc8c1f4260f35c8e744be155f06fd82d</b></pre></code>
Create a key pair, generate a CSR, and get a certificate from `step-ca`:
<pre><code><b>$ step ca certificate foo.local foo.crt foo.key</b>
Use the arrow keys to navigate: ↓ ↑ → ←
What provisioner key do you want to use?
▸ bob@smallstep.com (JWK) [kid: XXX]
Google (OIDC) [client: XXX.apps.googleusercontent.com]
Auth0 (OIDC) [client: XXX]
AWS IID Provisioner (AWS)
✔ CA: https://ca.local
✔ Certificate: foo.crt
✔ Private Key: foo.key</code></pre>
Use `step certificate inspect` to check our work:
<pre><code><b>$ step certificate inspect --short foo.crt</b>
X.509v3 TLS Certificate (ECDSA P-256) [Serial: 2982...2760]
Subject: foo.local
Issuer: Intermediate CA
Provisioner: bob@smallstep.com [ID: EVct...2B-I]
Valid from: 2019-08-31T00:14:50Z
to: 2019-09-01T00:14:50Z</code></pre>
Renew certificate:
<pre><code><b>$ step ca renew foo.crt foo.key --force</b>
Your certificate has been saved in foo.crt.</code></pre>
Revoke certificate:
<pre><code><b>$ step ca revoke --cert foo.crt --key foo.key</b>
✔ CA: https://ca.local
Certificate with Serial Number 202784089649824696691681223134769107758 has been revoked.
<b>$ step ca renew foo.crt foo.key --force</b>
error renewing certificate: Unauthorized</code></pre>
You can install your root certificate locally:
<pre><code><b>$ step certificate install $(step path)/certs/root_ca.crt</b></code></pre>
And issued certificates will work in your browser and with tools like `curl`. See [our blog post](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html) for more info.
![Browser demo of HTTPS working without warnings](https://smallstep.com/images/blog/2019-02-25-localhost-tls.png)
Alternatively, for internal service-to-service communication, you can [configure your code and infrastructure to trust your root certificate](https://github.com/smallstep/autocert/tree/master/examples/hello-mtls).
### X.509 Certificates
The `step certificate` command group can also be used to create an offline CA and self-signed certificates.
Create a self-signed certificate:
<pre><code><b>$ step certificate create foo.local foo.crt foo.key --profile self-signed --subtle</b>
Your certificate has been saved in foo.crt.
Your private key has been saved in foo.key.</code></pre>
Create a root CA, an intermediate, and a leaf X.509 certificate. Bundle the
leaf with the intermediate for use with TLS:
<pre><code>
<b>$ step certificate create --profile root-ca \
<pre><code><b>$ step certificate create --profile root-ca \
"Example Root CA" root-ca.crt root-ca.key</b>
Please enter the password to encrypt the private key:
Your certificate has been saved in root-ca.crt.
@@ -120,41 +222,23 @@ Your private key has been saved in example.com.key.
<b>$ step certificate bundle \
example.com.crt intermediate-ca.crt example.com-bundle.crt</b>
Your certificate has been saved in example.com-bundle.crt.
</code></pre>
Your certificate has been saved in example.com-bundle.crt.</code></pre>
Extract the expiration date from a certificate (requires
[`jq`](https://stedolan.github.io/jq/)):
<pre><code>
<b>$ step certificate inspect example.com.crt --format json | jq -r .validity.end</b>
<pre><code><b>$ step certificate inspect example.com.crt --format json | jq -r .validity.end</b>
2019-02-28T17:46:16Z
<b>$ step certificate inspect https://smallstep.com --format json | jq -r .validity.end</b>
2019-05-09T13:07:44Z
</code></pre>
You can install your root certificate locally:
```
$ step certificate install root-ca.crt
```
And issued certificates will work in your browser and with tools like `curl`. See [our blog post](https://smallstep.com/blog/step-v0-8-6-valid-HTTPS-certificates-for-dev-pre-prod.html) for more info.
![Browser demo of HTTPS working without warnings](https://smallstep.com/images/blog/2019-02-25-localhost-tls.png)
Alternatively, for internal service-to-service communication, you can [configure your code and infrastructure to trust your root certificate](https://github.com/smallstep/certificates/tree/master/autocert/examples/hello-mtls).
If you need certificates for your microservices, containers, or other internal services see [step certificates](https://github.com/smallstep/certificates), a sub-project that adds an online certificate authority and automated certificate management tools to `step`.
2019-05-09T13:07:44Z</code></pre>
### JSON Object Signing & Encryption (JOSE)
Create a [JSON Web Key](https://tools.ietf.org/html/rfc7517) (JWK), add the
public key to a keyset, and sign a [JSON Web Token](https://tools.ietf.org/html/rfc7519) (JWT):
<pre><code>
<b>$ step crypto jwk create pub.json key.json</b>
<pre><code><b>$ step crypto jwk create pub.json key.json</b>
Please enter the password to encrypt the private JWK:
Your public key has been saved in pub.json.
Your private key has been saved in key.json.
@@ -187,16 +271,14 @@ Please enter the password to decrypt key.json:
"sub": "subject@example.com"
},
"signature": "JU7fPGqBJcIfauJHA7KP9Wp292g_G9s4bLMVLyRgEQDpL5faaG-3teJ81_igPz1zP7IjHmz8D6Gigt7kbnlasw"
}
</code></pre>
}</code></pre>
### Single Sign-On
Login with Google, get an access token, and use it to make a request to
Google's APIs:
<pre><code>
<b>$ curl -H"$(step oauth --header)" https://www.googleapis.com/oauth2/v3/userinfo</b>
<pre><code><b>$ curl -H"$(step oauth --header)" https://www.googleapis.com/oauth2/v3/userinfo</b>
Your default web browser has been opened to visit:
https://accounts.google.com/o/oauth2/v2/auth?client_id=1087160488420-AAAAAAAAAAAAAAA.apps.googleusercontent.com&code_challenge=XXXXX
@@ -207,13 +289,11 @@ https://accounts.google.com/o/oauth2/v2/auth?client_id=1087160488420-AAAAAAAAAAA
"email": "bob@smallstep.com",
"email_verified": true,
"hd": "smallstep.com"
}
</code></pre>
}</code></pre>
Login with Google and obtain an OAuth OIDC identity token for single sign-on:
<pre><code>
<b>$ step oauth \
<pre><code><b>$ step oauth \
--provider https://accounts.google.com \
--client-id 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com \
--client-secret udTrOT3gzrO7W9fDPgZQLfYJ \
@@ -222,13 +302,11 @@ Your default web browser has been opened to visit:
https://accounts.google.com/o/oauth2/v2/auth?client_id=[...]
xxx-google-xxx.yyy-oauth-yyy.zzz-token-zzz
</code></pre>
xxx-google-xxx.yyy-oauth-yyy.zzz-token-zzz</code></pre>
Obtain and verify a Google-issued OAuth OIDC identity token:
<pre><code>
<b>$ step oauth \
<pre><code><b>$ step oauth \
--provider https://accounts.google.com \
--client-id 1087160488420-8qt7bavg3qesdhs6it824mhnfgcfe8il.apps.googleusercontent.com \
--client-secret udTrOT3gzrO7W9fDPgZQLfYJ \
@@ -260,26 +338,21 @@ https://accounts.google.com/o/oauth2/v2/auth?client_id=[...]
"exp": 1551296734
},
"signature": "[...]"
}
</code></pre>
}</code></pre>
### Multi-factor Authentication
Generate a [TOTP](https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm)
token and a QR code:
<pre><code>
<b>$ step crypto otp generate \
<pre><code><b>$ step crypto otp generate \
--issuer smallstep.com --account name@smallstep.com \
--qr smallstep.png > smallstep.totp</b>
</code></pre>
--qr smallstep.png > smallstep.totp</b></code></pre>
Scan the QR Code (`smallstep.png`) using Google Authenticator, Authy or similar
software and use it to verify the TOTP token:
<pre><code>
<b>$ step crypto otp verify --secret smallstep.totp</b>
</code></pre>
<pre><code><b>$ step crypto otp verify --secret smallstep.totp</b></code></pre>
## Documentation

View File

@@ -32,7 +32,7 @@ Bootstrap will store the root certificate in <$STEPPATH/certs/root_ca.crt> and
create a configuration file in <$STEPPATH/configs/defaults.json> with the CA
url, the root certificate location and its fingerprint.
After the bootstrap, ca commands do not need to specify the flags
After the bootstrap, ca commands do not need to specify the flags
--ca-url, --root or --fingerprint if we want to use the same environment.`,
Flags: []cli.Flag{
caURLFlag,
@@ -76,11 +76,11 @@ func bootstrapAction(ctx *cli.Context) error {
return errors.Wrap(err, "error downloading root certificate")
}
if err := os.MkdirAll(filepath.Dir(rootFile), 0700); err != nil {
if err = os.MkdirAll(filepath.Dir(rootFile), 0700); err != nil {
return errs.FileError(err, rootFile)
}
if err := os.MkdirAll(filepath.Dir(configFile), 0700); err != nil {
if err = os.MkdirAll(filepath.Dir(configFile), 0700); err != nil {
return errs.FileError(err, configFile)
}

View File

@@ -20,9 +20,9 @@ func certificateCommand() cli.Command {
Action: command.ActionFunc(certificateAction),
Usage: "generate a new private key and certificate signed by the root certificate",
UsageText: `**step ca certificate** <subject> <crt-file> <key-file>
[**--token**=<token>] [**--issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<file>]
[**--not-before**=<time|duration>] [**--not-after**=<time|duration>]
[**--san**=<SAN>]`,
[**--token**=<token>] [**--issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<file>]
[**--not-before**=<time|duration>] [**--not-after**=<time|duration>] [**--san**=<SAN>]
[**--kty**=<type>] [**--curve**=<curve>] [**--size**=<size>] [**--console**]`,
Description: `**step ca certificate** command generates a new certificate pair
## POSITIONAL ARGUMENTS
@@ -72,6 +72,16 @@ $ step ca certificate --offline internal.example.com internal.crt internal.key
Request a new certificate using an OIDC provisioner:
'''
$ step ca certificate --token $(step oauth --oidc --bare) joe@example.com joe.crt joe.key
'''
Request a new certificate using an OIDC provisioner while remaining in the console:
'''
$ step ca certificate joe@example.com joe.crt joe.key --issuer Google --console
'''
Request a new certificate with an RSA public key (default is ECDSA256):
'''
$ step ca certificate foo.internal foo.crt foo.key --kty RSA --size 4096
'''`,
Flags: []cli.Flag{
tokenFlag,
@@ -82,15 +92,22 @@ $ step ca certificate --token $(step oauth --oidc --bare) joe@example.com joe.cr
notAfterCertFlag,
cli.StringSliceFlag{
Name: "san",
Usage: `Add DNS or IP Address Subjective Alternative Names (SANs) that the token is
authorized to request. A certificate signing request using this token must match
the complete set of subjective alternative names in the token 1:1. Use the '--san'
flag multiple times to configure multiple SANs. The '--san' flag and the '--token'
flag are mutually exlusive.`,
Usage: `Add DNS Name, IP Address, or Email Address Subjective Alternative Names (SANs)
that the token is authorized to request. A certificate signing request using
this token must match the complete set of subjective alternative names in the
token 1:1. Use the '--san' flag multiple times to configure multiple SANs. The
'--san' flag and the '--token' flag are mutually exlusive.`,
},
offlineFlag,
caConfigFlag,
cli.BoolFlag{
Name: "console",
Usage: "Complete the flow while remaining inside the terminal",
},
flags.Force,
flags.KTY,
flags.Size,
flags.Curve,
},
}
}
@@ -125,7 +142,7 @@ func certificateAction(ctx *cli.Context) error {
}
}
req, pk, err := flow.CreateSignRequest(tok, subject, sans)
req, pk, err := flow.CreateSignRequest(ctx, tok, subject, sans)
if err != nil {
return err
}
@@ -157,7 +174,7 @@ func certificateAction(ctx *cli.Context) error {
return errors.New("token is not supported")
}
if err := flow.Sign(ctx, tok, req.CsrPEM, crtFile); err != nil {
if err = flow.Sign(ctx, tok, req.CsrPEM, crtFile); err != nil {
return err
}

View File

@@ -22,7 +22,7 @@ func initCommand() cli.Command {
Action: cli.ActionFunc(initAction),
Usage: "initialize the CA PKI",
UsageText: `**step ca init**
[**--root**=<path>] [**--key**=<path>] [**--pki**] [**--ssh**] [**--name**=<name>]
[**--root**=<path>] [**--key**=<path>] [**--pki**] [**--ssh**] [**--name**=<name>]
[**dns**=<dns>] [**address**=<address>] [**provisioner**=<name>]
[**provisioner-password-file**=<path>] [**password-file**=<path>]
[**with-ca-url**=<url>] [**no-db**]`,
@@ -84,7 +84,7 @@ func initCommand() cli.Command {
}
func initAction(ctx *cli.Context) (err error) {
if err := assertCryptoRand(); err != nil {
if err = assertCryptoRand(); err != nil {
return err
}
@@ -100,7 +100,6 @@ func initAction(ctx *cli.Context) (err error) {
case len(root) == 0 && len(key) > 0:
return errs.RequiredWithFlag(ctx, "key", "root")
case len(root) > 0 && len(key) > 0:
var err error
if rootCrt, err = pemutil.ReadCertificate(root); err != nil {
return err
}
@@ -145,7 +144,8 @@ func initAction(ctx *cli.Context) (err error) {
}
if configure {
names, err := ui.Prompt("What DNS names or IP addresses would you like to add to your new CA? (e.g. ca.smallstep.com[,1.1.1.1,etc.])",
var names string
names, err = ui.Prompt("What DNS names or IP addresses would you like to add to your new CA? (e.g. ca.smallstep.com[,1.1.1.1,etc.])",
ui.WithValidateFunc(ui.DNS()), ui.WithValue(ctx.String("dns")))
if err != nil {
return err
@@ -160,13 +160,15 @@ func initAction(ctx *cli.Context) (err error) {
dnsNames = append(dnsNames, strings.TrimSpace(name))
}
address, err := ui.Prompt("What address will your new CA listen at? (e.g. :443)",
var address string
address, err = ui.Prompt("What address will your new CA listen at? (e.g. :443)",
ui.WithValidateFunc(ui.Address()), ui.WithValue(ctx.String("address")))
if err != nil {
return err
}
provisioner, err := ui.Prompt("What would you like to name the first provisioner for your new CA? (e.g. you@smallstep.com)",
var provisioner string
provisioner, err = ui.Prompt("What would you like to name the first provisioner for your new CA? (e.g. you@smallstep.com)",
ui.WithValidateNotEmpty(), ui.WithValue(ctx.String("provisioner")))
if err != nil {
return err
@@ -187,11 +189,11 @@ func initAction(ctx *cli.Context) (err error) {
if configure {
// Generate provisioner key pairs.
if len(provisionerPassword) > 0 {
if err := p.GenerateKeyPairs(provisionerPassword); err != nil {
if err = p.GenerateKeyPairs(provisionerPassword); err != nil {
return err
}
} else {
if err := p.GenerateKeyPairs(pass); err != nil {
if err = p.GenerateKeyPairs(pass); err != nil {
return err
}
}
@@ -211,7 +213,7 @@ func initAction(ctx *cli.Context) (err error) {
} else {
fmt.Println()
fmt.Print("Copying root certificate... \n")
if err := p.WriteRootCertificate(rootCrt, rootKey, pass); err != nil {
if err = p.WriteRootCertificate(rootCrt, rootKey, pass); err != nil {
return err
}
fmt.Println("all done!")
@@ -246,7 +248,7 @@ func initAction(ctx *cli.Context) (err error) {
return p.Save(opts...)
}
// assertCrytoRand asserts that a cryptographically secure random number
// assertCryptoRand asserts that a cryptographically secure random number
// generator is available, it will return an error otherwise.
func assertCryptoRand() error {
buf := make([]byte, 64)

View File

@@ -2,6 +2,7 @@ package ca
import (
"crypto/tls"
"crypto/x509"
"net/http"
"os"
"strconv"
@@ -233,14 +234,15 @@ func revokeCertificateAction(ctx *cli.Context) error {
if len(serial) > 0 {
errs.IncompatibleFlagWithFlag(ctx, "cert", "serial")
}
cert, err := pemutil.ReadCertificateBundle(certFile)
var cert []*x509.Certificate
cert, err = pemutil.ReadCertificateBundle(certFile)
if err != nil {
return err
}
serial = cert[0].SerialNumber.String()
} else {
// Must be using serial number so verify that only 1 command line args was given.
if err := errs.NumberOfArguments(ctx, 1); err != nil {
if err = errs.NumberOfArguments(ctx, 1); err != nil {
return err
}
if len(token) == 0 {
@@ -397,7 +399,8 @@ func (f *revokeFlow) Revoke(ctx *cli.Context, serial, token string) error {
certFile, keyFile := ctx.String("cert"), ctx.String("key")
// If there is no token then we must be doing a Revoke over mTLS.
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
var cert tls.Certificate
cert, err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return errors.Wrap(err, "error loading certificates")
}
@@ -407,11 +410,12 @@ func (f *revokeFlow) Revoke(ctx *cli.Context, serial, token string) error {
root := ctx.String("root")
if len(root) == 0 {
root = pki.GetRootCAPath()
if _, err := os.Stat(root); err != nil {
if _, err = os.Stat(root); err != nil {
return errs.RequiredUnlessFlag(ctx, "root", "token")
}
}
rootCAs, err := x509util.ReadCertPool(root)
var rootCAs *x509.CertPool
rootCAs, err = x509util.ReadCertPool(root)
if err != nil {
return err
}

View File

@@ -22,8 +22,9 @@ func signCertificateCommand() cli.Command {
Action: command.ActionFunc(signCertificateAction),
Usage: "generate a new certificate signing a certificate request",
UsageText: `**step ca sign** <csr-file> <crt-file>
[**--token**=<token>] [**--issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<file>]
[**--not-before**=<time|duration>] [**--not-after**=<time|duration>]`,
[**--token**=<token>] [**--issuer**=<name>] [**--ca-url**=<uri>] [**--root**=<file>]
[**--not-before**=<time|duration>] [**--not-after**=<time|duration>]
[**--console**]`,
Description: `**step ca sign** command signs the given csr and generates a new certificate.
## POSITIONAL ARGUMENTS
@@ -63,6 +64,10 @@ $ step ca sign --offline internal internal.csr internal.crt
offlineFlag,
caConfigFlag,
flags.Force,
cli.BoolFlag{
Name: "console",
Usage: "Complete the flow while remaining inside the terminal",
},
},
}
}
@@ -156,5 +161,11 @@ func mergeSans(ctx *cli.Context, csr *x509.CertificateRequest) []string {
m[s] = true
}
}
for _, s := range csr.EmailAddresses {
if _, ok := m[s]; !ok {
uniq = append(uniq, s)
m[s] = true
}
}
return uniq
}

View File

@@ -25,8 +25,8 @@ func createCommand() cli.Command {
Usage: "create a certificate or certificate signing request",
UsageText: `**step certificate create** <subject> <crt_file> <key_file>
[**ca**=<issuer-cert>] [**ca-key**=<issuer-key>] [**--csr**]
[**--curve**=<curve>] [**no-password**] [**--profile**=<profile>]
[**--size**=<size>] [**--type**=<type>] [**--san**=<SAN>]`,
[**no-password**] [**--profile**=<profile>] [**--san**=<SAN>] [**--bundle**]
[**--kty**=<type>] [**--curve**=<curve>] [**--size**=<size>]`,
Description: `**step certificate create** generates a certificate or a
certificate signing requests (CSR) that can be signed later using 'step
certificates sign' (or some other tool) to produce a certificate.
@@ -113,6 +113,12 @@ $ step certificate create foo foo.crt foo.key --profile leaf \
--not-before 24h --not-after 2160h
'''
Create a self-signed leaf certificate and key:
'''
$ step certificate create self-signed-leaf.local leaf.crt leaf.key --profile self-signed --subtle
'''
Create a root certificate and key with underlying OKP Ed25519:
'''
@@ -179,51 +185,12 @@ recommended. Requires **--insecure** flag.`,
: Generate a certificate that can be used to sign additional leaf or intermediate certificates.
**root-ca**
: Generate a new self-signed root certificate suitable for use as a root CA.`,
},
cli.StringFlag{
Name: "kty",
Value: "EC",
Usage: `The <kty> to build the certificate upon.
If unset, default is EC.
: Generate a new self-signed root certificate suitable for use as a root CA.
: <kty> is a case-sensitive string and must be one of:
**EC**
: Create an **elliptic curve** keypair
**OKP**
: Create an octet key pair (for **"Ed25519"** curve)
**RSA**
: Create an **RSA** keypair
`,
},
cli.IntFlag{
Name: "size",
Usage: `The <size> (in bits) of the key for RSA and oct key types. RSA keys require a
minimum key size of 2048 bits. If unset, default is 2048 bits for RSA keys and 128 bits for oct keys.`,
},
cli.StringFlag{
Name: "crv, curve",
Usage: `The elliptic <curve> to use for EC and OKP key types. Corresponds
to the **"crv"** JWK parameter. Valid curves are defined in JWA [RFC7518]. If
unset, default is P-256 for EC keys and Ed25519 for OKP keys.
: <curve> is a case-sensitive string and must be one of:
**P-256**
: NIST P-256 Curve
**P-384**
: NIST P-384 Curve
**P-521**
: NIST P-521 Curve
**Ed25519**
: Ed25519 Curve
`,
**self-signed**
: Generate a new self-signed leaf certificate suitable for use with TLS.
This profile requires the **--subtle** flag because the use of self-signed leaf
certificates is discouraged unless absolutely necessary.`,
},
cli.StringFlag{
Name: "not-before",
@@ -246,7 +213,16 @@ unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns",
Usage: `Add DNS or IP Address Subjective Alternative Names (SANs). Use the '--san'
flag multiple times to configure multiple SANs.`,
},
cli.BoolFlag{
Name: "bundle",
Usage: `Bundle the new leaf certificate with the signing certificate. This flag requires
the **--ca** flag.`,
},
flags.KTY,
flags.Size,
flags.Curve,
flags.Force,
flags.Subtle,
},
}
}
@@ -297,15 +273,19 @@ func createAction(ctx *cli.Context) error {
if len(sans) == 0 {
sans = []string{subject}
}
dnsNames, ips := x509util.SplitSANs(sans)
dnsNames, ips, emails := x509util.SplitSANs(sans)
var (
priv interface{}
pubPEM *pem.Block
pubPEMs []*pem.Block
outputType string
bundle = ctx.Bool("bundle")
)
switch typ {
case "x509-csr":
if bundle {
return errs.IncompatibleFlagWithFlag(ctx, "bundle", "csr")
}
if ctx.IsSet("profile") {
return errs.IncompatibleFlagWithFlag(ctx, "profile", "csr")
}
@@ -318,28 +298,32 @@ func createAction(ctx *cli.Context) error {
Subject: pkix.Name{
CommonName: subject,
},
DNSNames: dnsNames,
IPAddresses: ips,
DNSNames: dnsNames,
IPAddresses: ips,
EmailAddresses: emails,
}
csrBytes, err := stepx509.CreateCertificateRequest(rand.Reader, _csr, priv)
var csrBytes []byte
csrBytes, err = stepx509.CreateCertificateRequest(rand.Reader, _csr, priv)
if err != nil {
return errors.WithStack(err)
}
pubPEM = &pem.Block{
pubPEMs = []*pem.Block{{
Type: "CERTIFICATE REQUEST",
Bytes: csrBytes,
Headers: map[string]string{},
}
}}
outputType = "certificate signing request"
case "x509":
var (
err error
prof = ctx.String("profile")
caPath = ctx.String("ca")
caKeyPath = ctx.String("ca-key")
profile x509util.Profile
)
if bundle && prof != "leaf" {
return errs.IncompatibleFlagValue(ctx, "bundle", "profile", prof)
}
switch prof {
case "leaf", "intermediate-ca":
if caPath == "" {
@@ -350,7 +334,8 @@ func createAction(ctx *cli.Context) error {
}
switch prof {
case "leaf":
issIdentity, err := loadIssuerIdentity(ctx, prof, caPath, caKeyPath)
var issIdentity *x509util.Identity
issIdentity, err = loadIssuerIdentity(ctx, prof, caPath, caKeyPath)
if err != nil {
return errors.WithStack(err)
}
@@ -358,15 +343,14 @@ func createAction(ctx *cli.Context) error {
issIdentity.Key, x509util.GenerateKeyPair(kty, crv, size),
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
x509util.WithDNSNames(dnsNames),
x509util.WithIPAddresses(ips))
x509util.WithIPAddresses(ips),
x509util.WithEmailAddresses(emails))
if err != nil {
return errors.WithStack(err)
}
case "intermediate-ca":
issIdentity, err := loadIssuerIdentity(ctx, prof, caPath, caKeyPath)
if err != nil {
return errors.WithStack(err)
}
var issIdentity *x509util.Identity
issIdentity, err = loadIssuerIdentity(ctx, prof, caPath, caKeyPath)
if err != nil {
return errors.WithStack(err)
}
@@ -375,7 +359,8 @@ func createAction(ctx *cli.Context) error {
x509util.GenerateKeyPair(kty, crv, size),
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
x509util.WithDNSNames(dnsNames),
x509util.WithIPAddresses(ips))
x509util.WithIPAddresses(ips),
x509util.WithEmailAddresses(emails))
if err != nil {
return errors.WithStack(err)
}
@@ -385,21 +370,41 @@ func createAction(ctx *cli.Context) error {
x509util.GenerateKeyPair(kty, crv, size),
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
x509util.WithDNSNames(dnsNames),
x509util.WithIPAddresses(ips))
x509util.WithIPAddresses(ips),
x509util.WithEmailAddresses(emails))
if err != nil {
return errors.WithStack(err)
}
case "self-signed":
if !ctx.Bool("subtle") {
return errs.RequiredWithFlagValue(ctx, "profile", "self-signed", "subtle")
}
profile, err = x509util.NewSelfSignedLeafProfile(subject,
x509util.GenerateKeyPair(kty, crv, size),
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0),
x509util.WithDNSNames(dnsNames),
x509util.WithIPAddresses(ips),
x509util.WithEmailAddresses(emails))
if err != nil {
return errors.WithStack(err)
}
default:
return errs.InvalidFlagValue(ctx, "profile", prof, "leaf, intermediate-ca, root-ca")
return errs.InvalidFlagValue(ctx, "profile", prof, "leaf, intermediate-ca, root-ca, self-signed")
}
crtBytes, err := profile.CreateCertificate()
var crtBytes []byte
crtBytes, err = profile.CreateCertificate()
if err != nil {
return errors.WithStack(err)
}
pubPEM = &pem.Block{
Type: "CERTIFICATE",
Bytes: crtBytes,
Headers: map[string]string{},
pubPEMs = []*pem.Block{{
Type: "CERTIFICATE",
Bytes: crtBytes,
}}
if bundle {
pubPEMs = append(pubPEMs, &pem.Block{
Type: "CERTIFICATE",
Bytes: profile.Issuer().Raw,
})
}
priv = profile.SubjectPrivateKey()
outputType = "certificate"
@@ -407,7 +412,11 @@ func createAction(ctx *cli.Context) error {
return errs.NewError("unexpected type: %s", typ)
}
if err := utils.WriteFile(crtFile, pem.EncodeToMemory(pubPEM), 0600); err != nil {
pubBytes := []byte{}
for _, pp := range pubPEMs {
pubBytes = append(pubBytes, pem.EncodeToMemory(pp)...)
}
if err = utils.WriteFile(crtFile, pubBytes, 0600); err != nil {
return errs.FileError(err, crtFile)
}
@@ -417,7 +426,8 @@ func createAction(ctx *cli.Context) error {
return errors.WithStack(err)
}
} else {
pass, err := ui.PromptPassword("Please enter the password to encrypt the private key")
var pass []byte
pass, err = ui.PromptPassword("Please enter the password to encrypt the private key")
if err != nil {
return errors.Wrap(err, "error reading password")
}

View File

@@ -13,10 +13,11 @@ import (
func signCommand() cli.Command {
return cli.Command{
Name: "sign",
Action: cli.ActionFunc(signAction),
Usage: "sign a certificate signing request (CSR)",
UsageText: `**step certificate sign** <csr_file> <crt_file> <key_file>`,
Name: "sign",
Action: cli.ActionFunc(signAction),
Usage: "sign a certificate signing request (CSR)",
UsageText: `**step certificate sign** <csr_file> <crt_file> <key_file>
[**--bundle**]`,
Description: `**step certificate sign** generates a signed
certificate from a certificate signing request (CSR).
@@ -43,7 +44,20 @@ Sign a certificate signing request:
$ step certificate sign ./certificate-signing-request.csr \
./issuer-certificate.crt ./issuer-private-key.priv
'''
Sign a certificate signing request and bundle the new certificate with the issuer:
'''
$ step certificate sign ./certificate-signing-request.csr \
./issuer-certificate.crt ./issuer-private-key.priv --bundle
'''
`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "bundle",
Usage: `Bundle the new leaf certificate with the signing certificate.`,
},
},
}
}
@@ -64,7 +78,7 @@ func signAction(ctx *cli.Context) error {
if err != nil {
return errors.WithStack(err)
}
if err := x509util.CheckCertificateRequestSignature(csr); err != nil {
if err = x509util.CheckCertificateRequestSignature(csr); err != nil {
return errors.Wrapf(err, "Certificate Request has invalid signature")
}
@@ -83,11 +97,21 @@ func signAction(ctx *cli.Context) error {
if err != nil {
return errors.Wrapf(err, "failure creating new leaf certificate from input csr")
}
block := &pem.Block{
pubPEMs := []*pem.Block{{
Type: "CERTIFICATE",
Bytes: crtBytes,
}}
if ctx.Bool("bundle") {
pubPEMs = append(pubPEMs, &pem.Block{
Type: "CERTIFICATE",
Bytes: issuerIdentity.Crt.Raw,
})
}
fmt.Printf("%s", string(pem.EncodeToMemory(block)))
pubBytes := []byte{}
for _, pp := range pubPEMs {
pubBytes = append(pubBytes, pem.EncodeToMemory(pp)...)
}
fmt.Printf("%s", string(pubBytes))
return nil
}

View File

@@ -96,7 +96,6 @@ func verifyAction(ctx *cli.Context) error {
}
var (
err error
crtFile = ctx.Args().Get(0)
host = ctx.String("host")
roots = ctx.String("roots")
@@ -153,6 +152,7 @@ func verifyAction(ctx *cli.Context) error {
}
if roots != "" {
var err error
rootPool, err = x509util.ReadCertPool(roots)
if err != nil {
errors.Wrapf(err, "failure to load root certificate pool from input path '%s'", roots)

View File

@@ -20,13 +20,14 @@ import (
func changePassCommand() cli.Command {
return cli.Command{
Name: "change-pass",
Action: command.ActionFunc(changePassAction),
Usage: "change password of an encrypted private key (PEM or JWK format)",
UsageText: `**step crypto change-pass** <key-file> [**--out**=<file>]`,
Description: `**step crypto change-pass** extracts the private key from
a file and encrypts disk using a new password by either overwriting the original
encrypted key or writing a new file to disk.
Name: "change-pass",
Action: command.ActionFunc(changePassAction),
Usage: "change password of an encrypted private key (PEM or JWK format)",
UsageText: `**step crypto change-pass** <key-file>
[**--out**=<file>] [**--insecure**] [**--no-password**]`,
Description: `**step crypto change-pass** extracts and decrypts
the private key from a file and encrypts and serializes the key to disk using a
new password.
## POSITIONAL ARGUMENTS
@@ -40,6 +41,11 @@ Change password for PEM formatted key:
$ step crypto change-pass key.pem
'''
Remove password for PEM formatted key:
'''
$ step crypto change-pass key.pem --no-password --insecure
'''
Change password for PEM formatted key and write encrypted key to different file:
'''
$ step crypto change-pass key.pem --out new-key.pem
@@ -50,6 +56,11 @@ Change password for JWK formatted key:
$ step crypto change-pass key.jwk
'''
Removed password for JWK formatted key:
'''
$ step crypto change-pass key.jwk --no-password --insecure
'''
Change password for JWK formatted key:
'''
$ step crypto change-pass key.jwk --out new-key.jwk
@@ -60,6 +71,13 @@ $ step crypto change-pass key.jwk --out new-key.jwk
Usage: "The <file> new encrypted key path. Default to overwriting the <key> positional argument",
},
flags.Force,
flags.Insecure,
cli.BoolFlag{
Name: "no-password",
Usage: `Do not ask for a password to encrypt the private key.
Sensitive key material will be written to disk unencrypted. This is not
recommended. Requires **--insecure** flag.`,
},
},
}
}
@@ -72,8 +90,14 @@ func changePassAction(ctx *cli.Context) error {
if err := errs.NumberOfArguments(ctx, 1); err != nil {
return err
}
keyPath := ctx.Args().Get(0)
insecure := ctx.Bool("insecure")
noPass := ctx.Bool("no-password")
if noPass && !insecure {
return errs.RequiredWithFlag(ctx, "insecure", "no-password")
}
keyPath := ctx.Args().Get(0)
newKeyPath := ctx.String("out")
if len(newKeyPath) == 0 {
newKeyPath = keyPath
@@ -89,11 +113,16 @@ func changePassAction(ctx *cli.Context) error {
if err != nil {
return err
}
pass, err := ui.PromptPassword(fmt.Sprintf("Please enter the password to encrypt %s", newKeyPath))
if err != nil {
return errors.Wrap(err, "error reading password")
var opts []pemutil.Options
if !noPass {
pass, err := ui.PromptPassword(fmt.Sprintf("Please enter the password to encrypt %s", newKeyPath))
if err != nil {
return errors.Wrap(err, "error reading password")
}
opts = append(opts, pemutil.WithPassword(pass))
}
if _, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(newKeyPath, 0644)); err != nil {
opts = append(opts, pemutil.ToFile(newKeyPath, 0644))
if _, err := pemutil.Serialize(key, opts...); err != nil {
return err
}
} else {
@@ -101,12 +130,21 @@ func changePassAction(ctx *cli.Context) error {
if err != nil {
return err
}
jwe, err := jose.EncryptJWK(jwk)
if err != nil {
return err
var b []byte
if noPass {
b, err = jwk.MarshalJSON()
if err != nil {
return err
}
} else {
jwe, err := jose.EncryptJWK(jwk)
if err != nil {
return err
}
b = []byte(jwe.FullSerialize())
}
var out bytes.Buffer
if err := json.Indent(&out, []byte(jwe.FullSerialize()), "", " "); err != nil {
if err := json.Indent(&out, b, "", " "); err != nil {
return errors.Wrap(err, "error formatting JSON")
}
if err := utils.WriteFile(newKeyPath, out.Bytes(), 0600); err != nil {

View File

@@ -203,7 +203,8 @@ func digestAction(ctx *cli.Context) error {
}
for _, filename := range ctx.Args() {
st, err := os.Stat(filename)
var st os.FileInfo
st, err = os.Stat(filename)
if err != nil {
return errs.FileError(err, filename)
}

View File

@@ -129,7 +129,7 @@ func decryptAction(ctx *cli.Context) error {
}
// Validate jwk
if err := jose.ValidateJWK(jwk); err != nil {
if err = jose.ValidateJWK(jwk); err != nil {
return err
}

View File

@@ -249,7 +249,7 @@ func encryptAction(ctx *cli.Context) error {
}
// Validate jwk
if err := jose.ValidateJWK(jwk); err != nil {
if err = jose.ValidateJWK(jwk); err != nil {
return err
}

View File

@@ -177,32 +177,8 @@ If unset, default is EC.
: Create an **RSA** keypair
`,
},
cli.IntFlag{
Name: "size",
Usage: `The <size> (in bits) of the key for RSA and oct key types. RSA keys require a
minimum key size of 2048 bits. If unset, default is 2048 bits for RSA keys and 128 bits for oct keys.`,
},
cli.StringFlag{
Name: "crv, curve",
Usage: `The elliptic <curve> to use for EC and OKP key types. Corresponds
to the **"crv"** JWK parameter. Valid curves are defined in JWA [RFC7518]. If
unset, default is P-256 for EC keys and Ed25519 for OKP keys.
: <curve> is a case-sensitive string and must be one of:
**P-256**
: NIST P-256 Curve
**P-384**
: NIST P-384 Curve
**P-521**
: NIST P-521 Curve
**Ed25519**
: Ed25519 Curve
`,
},
flags.Size,
flags.Curve,
cli.StringFlag{
Name: "alg, algorithm",
Usage: `The <algorithm> intended for use with this key. Corresponds to the
@@ -404,7 +380,7 @@ existing <pem-file> instead of creating a new key.`,
func createAction(ctx *cli.Context) (err error) {
// require public and private files
if err := errs.NumberOfArguments(ctx, 2); err != nil {
if err = errs.NumberOfArguments(ctx, 2); err != nil {
return err
}
@@ -502,7 +478,8 @@ func createAction(ctx *cli.Context) (err error) {
} else {
// A hash of a symmetric key can leak information, so we only thumbprint asymmetric keys.
if kty != "oct" {
hash, err := jwk.Thumbprint(crypto.SHA256)
var hash []byte
hash, err = jwk.Thumbprint(crypto.SHA256)
if err != nil {
return errors.Wrap(err, "error generating JWK thumbprint")
}
@@ -515,7 +492,7 @@ func createAction(ctx *cli.Context) (err error) {
jwk.Algorithm = alg
}
if err := jose.ValidateJWK(jwk); err != nil {
if err = jose.ValidateJWK(jwk); err != nil {
return err
}
@@ -531,7 +508,7 @@ func createAction(ctx *cli.Context) (err error) {
if err != nil {
return errors.Wrap(err, "error marshaling JWK")
}
if err := utils.WriteFile(pubFile, b, 0600); err != nil {
if err = utils.WriteFile(pubFile, b, 0600); err != nil {
return errs.FileError(err, pubFile)
}
@@ -547,12 +524,14 @@ func createAction(ctx *cli.Context) (err error) {
var rcpt jose.Recipient
// Generate JWE encryption key.
if jose.SupportsPBKDF2 {
key, err := ui.PromptPassword("Please enter the password to encrypt the private JWK", ui.WithValue(password))
var key []byte
key, err = ui.PromptPassword("Please enter the password to encrypt the private JWK", ui.WithValue(password))
if err != nil {
return errors.Wrap(err, "error reading password")
}
salt, err := randutil.Salt(pbkdf2SaltSize)
var salt []byte
salt, err = randutil.Salt(pbkdf2SaltSize)
if err != nil {
return err
}
@@ -564,7 +543,8 @@ func createAction(ctx *cli.Context) (err error) {
PBES2Salt: salt,
}
} else {
key, err := randutil.Alphanumeric(32)
var key string
key, err = randutil.Alphanumeric(32)
if err != nil {
return errors.Wrap(err, "error generating password")
}
@@ -579,18 +559,20 @@ func createAction(ctx *cli.Context) (err error) {
opts := new(jose.EncrypterOptions)
opts.WithContentType(jose.ContentType("jwk+json"))
encrypter, err := jose.NewEncrypter(jose.DefaultEncAlgorithm, rcpt, opts)
var encrypter jose.Encrypter
encrypter, err = jose.NewEncrypter(jose.DefaultEncAlgorithm, rcpt, opts)
if err != nil {
return errors.Wrap(err, "error creating cipher")
}
obj, err := encrypter.Encrypt(b)
var obj *jose.JSONWebEncryption
obj, err = encrypter.Encrypt(b)
if err != nil {
return errors.Wrap(err, "error encrypting JWK")
}
var out bytes.Buffer
if err := json.Indent(&out, []byte(obj.FullSerialize()), "", " "); err != nil {
if err = json.Indent(&out, []byte(obj.FullSerialize()), "", " "); err != nil {
return errors.Wrap(err, "error formatting JSON")
}
b = out.Bytes()
@@ -600,7 +582,7 @@ func createAction(ctx *cli.Context) (err error) {
return errors.Wrap(err, "error marshaling JWK")
}
}
if err := utils.WriteFile(privFile, b, 0600); err != nil {
if err = utils.WriteFile(privFile, b, 0600); err != nil {
return errs.FileError(err, privFile)
}

View File

@@ -135,7 +135,7 @@ func keysetAddAction(ctx *cli.Context) error {
// Unmarshal the plain (or decrypted JWK)
var jwk jose.JSONWebKey
if err := json.Unmarshal(b, &jwk); err != nil {
if err = json.Unmarshal(b, &jwk); err != nil {
return errors.New("error reading JWK: unsupported format")
}

View File

@@ -37,7 +37,7 @@ func publicAction(ctx *cli.Context) error {
}
// Unmarshal the plain (or decrypted JWK)
if err := json.Unmarshal(b, jwk); err != nil {
if err = json.Unmarshal(b, jwk); err != nil {
return errors.New("error reading JWK: unsupported format")
}

View File

@@ -40,7 +40,7 @@ func thumbprintAction(ctx *cli.Context) error {
}
// Unmarshal the plain (or decrypted JWK)
if err := json.Unmarshal(b, jwk); err != nil {
if err = json.Unmarshal(b, jwk); err != nil {
return errors.New("error reading JWK: unsupported format")
}

View File

@@ -238,7 +238,7 @@ func signAction(ctx *cli.Context) error {
if jwk.Algorithm == "" {
return errors.New("flag '--alg' is required with the given key")
}
if err := jose.ValidateJWK(jwk); err != nil {
if err = jose.ValidateJWK(jwk); err != nil {
return err
}

View File

@@ -158,7 +158,7 @@ func verifyAction(ctx *cli.Context) error {
if jwk.Algorithm == "" {
return errors.New("flag '--alg' is required with the given key")
}
if err := jose.ValidateJWK(jwk); err != nil {
if err = jose.ValidateJWK(jwk); err != nil {
return err
}

View File

@@ -285,7 +285,7 @@ func signAction(ctx *cli.Context) error {
if jwk.Algorithm == "" {
return errors.New("flag '--alg' is required with the given key")
}
if err := jose.ValidateJWK(jwk); err != nil {
if err = jose.ValidateJWK(jwk); err != nil {
return err
}

View File

@@ -76,50 +76,9 @@ $ step crypto keypair foo.pub foo.key --kty OKP --curve Ed25519
'''
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "kty",
Value: "EC",
Usage: `The <kty> (key type) to create.
If unset, default is EC.
: <kty> is a case-sensitive string and must be one of:
**EC**
: Create an **elliptic curve** keypair
**OKP**
: Create an octet key pair (for **"Ed25519"** curve)
**RSA**
: Create an **RSA** keypair
`,
},
cli.IntFlag{
Name: "size",
Usage: `The <size> (in bits) of the key for RSA and oct key types. RSA keys require a
minimum key size of 2048 bits. If unset, default is 2048 bits for RSA keys and 128 bits for oct keys.`,
},
cli.StringFlag{
Name: "crv, curve",
Usage: `The elliptic <curve> to use for EC and OKP key types. Corresponds
to the **"crv"** JWK parameter. Valid curves are defined in JWA [RFC7518]. If
unset, default is P-256 for EC keys and Ed25519 for OKP keys.
: <curve> is a case-sensitive string and must be one of:
**P-256**
: NIST P-256 Curve
**P-384**
: NIST P-384 Curve
**P-521**
: NIST P-521 Curve
**Ed25519**
: Ed25519 Curve
`,
},
flags.KTY,
flags.Size,
flags.Curve,
cli.StringFlag{
Name: "from-jwk",
Usage: `Create a PEM representing the key encoded in an
@@ -134,7 +93,7 @@ existing <jwk-file> instead of creating a new key.`,
}
func createAction(ctx *cli.Context) (err error) {
if err := errs.NumberOfArguments(ctx, 2); err != nil {
if err = errs.NumberOfArguments(ctx, 2); err != nil {
return err
}
@@ -175,7 +134,8 @@ func createAction(ctx *cli.Context) (err error) {
return errs.IncompatibleFlagWithFlag(ctx, "from-jwk", "size")
}
jwk, err := jose.ParseKey(fromJWK)
var jwk *jose.JSONWebKey
jwk, err = jose.ParseKey(fromJWK)
if err != nil {
return err
}
@@ -187,7 +147,11 @@ func createAction(ctx *cli.Context) (err error) {
priv = jwk.Key
}
} else {
kty, crv, size, err := utils.GetKeyDetailsFromCLI(ctx, insecure, "kty",
var (
kty, crv string
size int
)
kty, crv, size, err = utils.GetKeyDetailsFromCLI(ctx, insecure, "kty",
"curve", "size")
if err != nil {
return err
@@ -217,7 +181,8 @@ func createAction(ctx *cli.Context) (err error) {
return err
}
} else {
pass, err := ui.PromptPassword("Please enter the password to encrypt the private key", ui.WithValue(password))
var pass []byte
pass, err = ui.PromptPassword("Please enter the password to encrypt the private key", ui.WithValue(password))
if err != nil {
return errors.Wrap(err, "error reading password")
}

View File

@@ -29,7 +29,7 @@ NaCl crypto_box function is designed to meet the standard notions of
privacy and third-party unforgeability for a public-key authenticated-encryption
scheme using nonces. For formal definitions see, e.g., Jee Hea An,
"Authenticated encryption in the public-key setting: security notions and
analyses," https://eprint.iacr.org/2001/079. Distinct messages between the same
analyzes," https://eprint.iacr.org/2001/079. Distinct messages between the same
{sender, receiver} set are required to have distinct nonces. For example, the
lexicographically smaller public key can use nonce 1 for its first message to
the other key, nonce 3 for its second message, nonce 5 for its third message,

View File

@@ -9,6 +9,7 @@ import (
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
@@ -129,6 +130,10 @@ func init() {
Name: "jwt",
Usage: "Generate a JWT Auth token instead of an OAuth Token (only works with service accounts)",
},
cli.StringFlag{
Name: "listen",
Usage: "Callback listener URL",
},
cli.BoolFlag{
Name: "implicit",
Usage: "Uses the implicit flow to authenticate the user. Requires **--insecure** and **--client-id** flags.",
@@ -148,10 +153,11 @@ func init() {
func oauthCmd(c *cli.Context) error {
opts := &options{
Provider: c.String("provider"),
Email: c.String("email"),
Console: c.Bool("console"),
Implicit: c.Bool("implicit"),
Provider: c.String("provider"),
Email: c.String("email"),
Console: c.Bool("console"),
Implicit: c.Bool("implicit"),
CallbackListener: c.String("listen"),
}
if err := opts.Validate(); err != nil {
return err
@@ -199,7 +205,7 @@ func oauthCmd(c *cli.Context) error {
return errors.Wrapf(err, "error reading account from %s", filename)
}
account := make(map[string]interface{})
if err := json.Unmarshal(b, &account); err != nil {
if err = json.Unmarshal(b, &account); err != nil {
return errors.Wrapf(err, "error reading %s: unsupported format", filename)
}
@@ -274,16 +280,17 @@ func oauthCmd(c *cli.Context) error {
}
type options struct {
Provider string
Email string
Console bool
Implicit bool
Provider string
Email string
Console bool
Implicit bool
CallbackListener string
}
// Validate validates the options.
func (o *options) Validate() error {
if o.Provider != "google" && !strings.HasPrefix(o.Provider, "https://") {
return errors.New("Use a valid provider: google")
return errors.New("use a valid provider: google")
}
return nil
}
@@ -302,6 +309,7 @@ type oauth struct {
codeChallenge string
nonce string
implicit bool
CallbackListener string
errCh chan error
tokCh chan *token
}
@@ -337,6 +345,7 @@ func newOauth(provider, clientID, clientSecret, authzEp, tokenEp, scope string,
codeChallenge: challenge,
nonce: nonce,
implicit: opts.Implicit,
CallbackListener: opts.CallbackListener,
errCh: make(chan error),
tokCh: make(chan *token),
}, nil
@@ -371,6 +380,7 @@ func newOauth(provider, clientID, clientSecret, authzEp, tokenEp, scope string,
codeChallenge: challenge,
nonce: nonce,
implicit: opts.Implicit,
CallbackListener: opts.CallbackListener,
errCh: make(chan error),
tokCh: make(chan *token),
}, nil
@@ -385,7 +395,7 @@ func disco(provider string) (map[string]interface{}, error) {
// TODO: OIDC and OAuth specify two different ways of constructing this
// URL. This is the OIDC way. Probably want to try both. See
// https://tools.ietf.org/html/rfc8414#section-5
if strings.Index(url.Path, "/.well-known/openid-configuration") == -1 {
if !strings.Contains(url.Path, "/.well-known/openid-configuration") {
url.Path = path.Join(url.Path, "/.well-known/openid-configuration")
}
resp, err := http.Get(url.String())
@@ -398,17 +408,37 @@ func disco(provider string) (map[string]interface{}, error) {
return nil, errors.Wrapf(err, "error retrieving %s", url.String())
}
details := make(map[string]interface{})
if err := json.Unmarshal(b, &details); err != nil {
if err = json.Unmarshal(b, &details); err != nil {
return nil, errors.Wrapf(err, "error reading %s: unsupported format", url.String())
}
return details, err
}
// NewServer creates http server
func (o *oauth) NewServer() (*httptest.Server, error) {
if o.CallbackListener == "" {
return httptest.NewServer(o), nil
}
l, err := net.Listen("tcp", o.CallbackListener)
if err != nil {
return nil, errors.Wrapf(err, "error listening on %s", o.CallbackListener)
}
srv := &httptest.Server{
Listener: l,
Config: &http.Server{Handler: o},
}
srv.Start()
return srv, nil
}
// DoLoopbackAuthorization performs the log in into the identity provider
// opening a browser and using a redirect_uri in a loopback IP address
// (http://127.0.0.1:port or http://[::1]:port).
func (o *oauth) DoLoopbackAuthorization() (*token, error) {
srv := httptest.NewServer(o)
srv, err := o.NewServer()
if err != nil {
return nil, err
}
o.redirectURI = srv.URL
defer srv.Close()
@@ -686,7 +716,6 @@ func (o *oauth) implicitHandler(w http.ResponseWriter, req *http.Request) {
w.Write([]byte(`<strong style='font-size: 28px; color: #000;'>Success</strong><br />`))
w.Write([]byte(`Click <a href="javascript:redirect();">here</a> if your browser does not automatically redirect you`))
w.Write([]byte(`</p></body></html>`))
return
}
// Auth returns the OAuth 2.0 authentication url.

View File

@@ -152,7 +152,8 @@ func ReadCertificate(filename string, opts ...Options) (*x509.Certificate, error
// PEM format
if bytes.HasPrefix(b, []byte("-----BEGIN ")) {
crt, err := Read(filename, opts...)
var crt interface{}
crt, err = Read(filename, opts...)
if err != nil {
return nil, err
}
@@ -190,7 +191,8 @@ func ReadCertificateBundle(filename string) ([]*x509.Certificate, error) {
if block.Type != "CERTIFICATE" {
return nil, errors.Errorf("error decoding PEM: file '%s' is not a certificate bundle", filename)
}
crt, err := x509.ParseCertificate(block.Bytes)
var crt *x509.Certificate
crt, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, errors.Wrapf(err, "error parsing %s", filename)
}
@@ -220,7 +222,8 @@ func ReadStepCertificate(filename string) (*stepx509.Certificate, error) {
// PEM format
if bytes.HasPrefix(b, []byte("-----BEGIN ")) {
crt, err := Read(filename, []Options{WithStepCrypto()}...)
var crt interface{}
crt, err = Read(filename, []Options{WithStepCrypto()}...)
if err != nil {
return nil, err
}
@@ -335,12 +338,13 @@ func Read(filename string, opts ...Options) (interface{}, error) {
// Serialize will serialize the input to a PEM formatted block and apply
// modifiers.
func Serialize(in interface{}, opts ...Options) (p *pem.Block, err error) {
func Serialize(in interface{}, opts ...Options) (*pem.Block, error) {
ctx := new(context)
if err := ctx.apply(opts); err != nil {
return nil, err
}
var p *pem.Block
switch k := in.(type) {
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
b, err := MarshalPKIXPublicKey(k)
@@ -424,11 +428,13 @@ func Serialize(in interface{}, opts ...Options) (p *pem.Block, err error) {
// Apply options on the PEM blocks.
if ctx.password != nil {
if _, ok := in.(crypto.PrivateKey); ok && ctx.pkcs8 {
var err error
p, err = EncryptPKCS8PrivateKey(rand.Reader, p.Bytes, ctx.password, DefaultEncCipher)
if err != nil {
return nil, err
}
} else {
var err error
p, err = x509.EncryptPEMBlock(rand.Reader, p.Type, p.Bytes, ctx.password, DefaultEncCipher)
if err != nil {
return nil, errors.Wrap(err, "failed to serialize to PEM")

View File

@@ -450,7 +450,8 @@ func TestSerialize(t *testing.T) {
assert.Equals(t, p.Type, "RSA PRIVATE KEY")
assert.Equals(t, p.Headers["Proc-Type"], "4,ENCRYPTED")
der, err := x509.DecryptPEMBlock(p, []byte(test.pass))
var der []byte
der, err = x509.DecryptPEMBlock(p, []byte(test.pass))
assert.FatalError(t, err)
assert.Equals(t, der, x509.MarshalPKCS1PrivateKey(k))
}
@@ -458,7 +459,8 @@ func TestSerialize(t *testing.T) {
assert.False(t, x509.IsEncryptedPEMBlock(p))
assert.Equals(t, p.Type, "PUBLIC KEY")
b, err := x509.MarshalPKIXPublicKey(k)
var b []byte
b, err = x509.MarshalPKIXPublicKey(k)
assert.FatalError(t, err)
assert.Equals(t, p.Bytes, b)
case *ecdsa.PrivateKey:
@@ -474,17 +476,20 @@ func TestSerialize(t *testing.T) {
actualBytes, err = x509.DecryptPEMBlock(p, []byte(test.pass))
assert.FatalError(t, err)
}
expectedBytes, err := x509.MarshalECPrivateKey(k)
var expectedBytes []byte
expectedBytes, err = x509.MarshalECPrivateKey(k)
assert.FatalError(t, err)
assert.Equals(t, actualBytes, expectedBytes)
if test.file != "" {
// Check key permissions
fileInfo, err := os.Stat(test.file)
var fileInfo os.FileInfo
fileInfo, err = os.Stat(test.file)
assert.FatalError(t, err)
assert.Equals(t, fileInfo.Mode(), os.FileMode(0600))
// Verify that key written to file is correct
keyFileBytes, err := ioutil.ReadFile(test.file)
var keyFileBytes []byte
keyFileBytes, err = ioutil.ReadFile(test.file)
assert.FatalError(t, err)
pemKey, _ := pem.Decode(keyFileBytes)
assert.Equals(t, pemKey.Type, "EC PRIVATE KEY")

View File

@@ -238,7 +238,7 @@ func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error) {
}
}
// MarshalPKIXPublicKey serialises a public key to DER-encoded PKIX format. The
// MarshalPKIXPublicKey serializes a public key to DER-encoded PKIX format. The
// following key types are supported: *rsa.PublicKey, *ecdsa.PublicKey,
// ed25519.Publickey. Unsupported key types result in an error.
func MarshalPKIXPublicKey(pub interface{}) ([]byte, error) {
@@ -265,7 +265,7 @@ func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) {
switch k := key.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey:
b, err := x509.MarshalPKCS8PrivateKey(key)
return b, errors.Wrap(err, "error marshalling PKCS#8")
return b, errors.Wrap(err, "error marshaling PKCS#8")
case ed25519.PrivateKey:
var priv pkcs8
priv.PrivateKey = append([]byte{4, 32}, k.Seed()...)[:34]
@@ -273,9 +273,9 @@ func MarshalPKCS8PrivateKey(key interface{}) ([]byte, error) {
Algorithm: asn1.ObjectIdentifier{1, 3, 101, 112},
}
b, err := asn1.Marshal(priv)
return b, errors.Wrap(err, "error marshalling PKCS#8")
return b, errors.Wrap(err, "error marshaling PKCS#8")
default:
return nil, errors.Errorf("x509: unknown key type while marshalling PKCS#8: %T", key)
return nil, errors.Errorf("x509: unknown key type while marshaling PKCS#8: %T", key)
}
}
@@ -428,7 +428,7 @@ func EncryptPKCS8PrivateKey(rand io.Reader, data, password []byte, alg x509.PEMC
b, err := asn1.Marshal(pki)
if err != nil {
return nil, errors.Wrap(err, "error marshalling encrypted key")
return nil, errors.Wrap(err, "error marshaling encrypted key")
}
return &pem.Block{
Type: "ENCRYPTED PRIVATE KEY",

View File

@@ -8,9 +8,11 @@ import (
"encoding/json"
"encoding/pem"
"fmt"
"html"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"golang.org/x/crypto/ssh"
@@ -76,7 +78,7 @@ func GetRootCAPath() string {
return filepath.Join(config.StepPath(), publicPath, "root_ca.crt")
}
// GetOTTKeyPath returns the path where the ont-time token key is stored based
// GetOTTKeyPath returns the path where the one-time token key is stored based
// on the STEPPATH environment variable.
func GetOTTKeyPath() string {
return filepath.Join(config.StepPath(), privatePath, "ott_key")
@@ -125,37 +127,34 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
// PKI represents the Public Key Infrastructure used by a certificate authority.
type PKI struct {
root, rootKey, rootFingerprint string
intermediate, intermediateKey string
sshHostCert, sshHostKey string
sshUserCert, sshUserKey string
country, locality, organization string
config, defaults string
ottPublicKey *jose.JSONWebKey
ottPrivateKey *jose.JSONWebEncryption
provisioner string
address string
dnsNames []string
caURL string
enableSSH bool
root, rootKey, rootFingerprint string
intermediate, intermediateKey string
sshHostCert, sshHostKey string
sshUserCert, sshUserKey string
config, defaults string
ottPublicKey *jose.JSONWebKey
ottPrivateKey *jose.JSONWebEncryption
provisioner string
address string
dnsNames []string
caURL string
enableSSH bool
}
// New creates a new PKI configuration.
func New(public, private, config string) (*PKI, error) {
var err error
if _, err = os.Stat(public); os.IsNotExist(err) {
if _, err := os.Stat(public); os.IsNotExist(err) {
if err = os.MkdirAll(public, 0700); err != nil {
return nil, errs.FileError(err, public)
}
}
if _, err = os.Stat(private); os.IsNotExist(err) {
if _, err := os.Stat(private); os.IsNotExist(err) {
if err = os.MkdirAll(private, 0700); err != nil {
return nil, errs.FileError(err, private)
}
}
if len(config) > 0 {
if _, err = os.Stat(config); os.IsNotExist(err) {
if _, err := os.Stat(config); os.IsNotExist(err) {
if err = os.MkdirAll(config, 0700); err != nil {
return nil, errs.FileError(err, config)
}
@@ -168,6 +167,7 @@ func New(public, private, config string) (*PKI, error) {
return s, errors.Wrapf(err, "error getting absolute path for %s", name)
}
var err error
p := &PKI{
provisioner: "step-cli",
address: "127.0.0.1:9000",
@@ -320,10 +320,27 @@ func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
return nil
}
func (p *PKI) askFeedback() {
ui.Println()
ui.Printf("\033[1mFEEDBACK\033[0m %s %s\n",
html.UnescapeString("&#"+strconv.Itoa(128525)+";"),
html.UnescapeString("&#"+strconv.Itoa(127867)+";"))
ui.Println(" The \033[1mstep\033[0m utility is not instrumented for usage statistics. It does not")
ui.Println(" phone home. But your feedback is extremely valuable. Any information you")
ui.Println(" can provide regarding how youre using `step` helps. Please send us a")
ui.Println(" sentence or two, good or bad: \033[1mfeedback@smallstep.com\033[0m or join")
ui.Println(" \033[1mhttps://gitter.im/smallstep/community\033[0m.")
}
// TellPKI outputs the locations of public and private keys generated
// generated for a new PKI. Generally this will consist of a root certificate
// and key and an intermediate certificate and key.
func (p *PKI) TellPKI() {
p.tellPKI()
p.askFeedback()
}
func (p *PKI) tellPKI() {
ui.Println()
ui.PrintSelected("Root certificate", p.root)
ui.PrintSelected("Root private key", p.rootKey)
@@ -372,7 +389,7 @@ func WithoutDB() Option {
// Save stores the pki on a json file that will be used as the certificate
// authority configuration.
func (p *PKI) Save(opt ...Option) error {
p.TellPKI()
p.tellPKI()
key, err := p.ottPrivateKey.CompactSerialize()
if err != nil {
@@ -420,7 +437,7 @@ func (p *PKI) Save(opt ...Option) error {
b, err := json.MarshalIndent(config, "", " ")
if err != nil {
return errors.Wrapf(err, "error marshalling %s", p.config)
return errors.Wrapf(err, "error marshaling %s", p.config)
}
if err = utils.WriteFile(p.config, b, 0666); err != nil {
return errs.FileError(err, p.config)
@@ -429,7 +446,8 @@ func (p *PKI) Save(opt ...Option) error {
// Generate the CA URL.
if p.caURL == "" {
p.caURL = p.dnsNames[0]
_, port, err := net.SplitHostPort(p.address)
var port string
_, port, err = net.SplitHostPort(p.address)
if err != nil {
return errors.Wrapf(err, "error parsing %s", p.address)
}
@@ -448,7 +466,7 @@ func (p *PKI) Save(opt ...Option) error {
}
b, err = json.MarshalIndent(defaults, "", " ")
if err != nil {
return errors.Wrapf(err, "error marshalling %s", p.defaults)
return errors.Wrapf(err, "error marshaling %s", p.defaults)
}
if err = utils.WriteFile(p.defaults, b, 0666); err != nil {
return errs.FileError(err, p.defaults)
@@ -462,5 +480,7 @@ func (p *PKI) Save(opt ...Option) error {
ui.Println()
ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.")
p.askFeedback()
return nil
}

View File

@@ -24,14 +24,17 @@ func Fingerprint(cert *x509.Certificate) string {
// SplitSANs splits a slice of Subject Alternative Names into slices of
// IP Addresses and DNS Names. If an element is not an IP address, then it
// is bucketed as a DNS Name.
func SplitSANs(sans []string) (dnsNames []string, ips []net.IP) {
func SplitSANs(sans []string) (dnsNames []string, ips []net.IP, emails []string) {
dnsNames = []string{}
ips = []net.IP{}
emails = []string{}
if sans == nil {
return
}
for _, san := range sans {
if ip := net.ParseIP(san); ip != nil {
if strings.Contains(san, "@") {
emails = append(emails, san)
} else if ip := net.ParseIP(san); ip != nil {
ips = append(ips, ip)
} else {
// If not IP then assume DNSName.

View File

@@ -4,7 +4,10 @@ import (
"crypto/x509"
"encoding/pem"
"io/ioutil"
"net"
"testing"
"github.com/smallstep/assert"
)
func TestFingerprint(t *testing.T) {
@@ -40,3 +43,49 @@ func mustParseCertificate(t *testing.T, filename string) *x509.Certificate {
}
return cert
}
func TestSplitSANs(t *testing.T) {
tests := []struct {
name string
sans, dns, emails []string
ips []net.IP
}{
{name: "empty", sans: []string{}, dns: []string{}, ips: []net.IP{}, emails: []string{}},
{
name: "all-dns",
sans: []string{"foo.internal", "bar.internal"},
dns: []string{"foo.internal", "bar.internal"},
ips: []net.IP{},
emails: []string{},
},
{
name: "all-ip",
sans: []string{"0.0.0.0", "127.0.0.1"},
dns: []string{},
ips: []net.IP{net.ParseIP("0.0.0.0"), net.ParseIP("127.0.0.1")},
emails: []string{},
},
{
name: "all-email",
sans: []string{"max@smallstep.com", "mariano@smallstep.com"},
dns: []string{},
ips: []net.IP{},
emails: []string{"max@smallstep.com", "mariano@smallstep.com"},
},
{
name: "mix",
sans: []string{"foo.internal", "max@smallstep.com", "mariano@smallstep.com", "1.1.1.1", "bar.internal"},
dns: []string{"foo.internal", "bar.internal"},
ips: []net.IP{net.ParseIP("1.1.1.1")},
emails: []string{"max@smallstep.com", "mariano@smallstep.com"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dns, ips, emails := SplitSANs(tt.sans)
assert.Equals(t, dns, tt.dns)
assert.Equals(t, ips, tt.ips)
assert.Equals(t, emails, tt.emails)
})
}
}

View File

@@ -31,6 +31,20 @@ func NewLeafProfile(cn string, iss *x509.Certificate, issPriv crypto.PrivateKey,
return newProfile(&Leaf{}, sub, iss, issPriv, withOps...)
}
// NewSelfSignedLeafProfile returns a new leaf x509 Certificate profile.
// A new public/private key pair will be generated for the Profile if
// not set in the `withOps` profile modifiers.
func NewSelfSignedLeafProfile(cn string, withOps ...WithOption) (Profile, error) {
sub := defaultLeafTemplate(pkix.Name{CommonName: cn}, pkix.Name{CommonName: cn})
p, err := newProfile(&Leaf{}, sub, sub, nil, withOps...)
if err != nil {
return nil, err
}
// self-signed certificate
p.SetIssuerPrivateKey(p.SubjectPrivateKey())
return p, nil
}
// NewLeafProfileWithCSR returns a new leaf x509 Certificate Profile with
// Subject Certificate fields populated directly from the CSR.
// A public/private keypair **WILL NOT** be generated for this profile because

View File

@@ -190,6 +190,16 @@ func WithIPAddresses(ips []net.IP) WithOption {
}
}
// WithEmailAddresses returns a Profile modifier which sets the Email Addresses
// that will be bound to the subject alternative name extension of the Certificate.
func WithEmailAddresses(emails []string) WithOption {
return func(p Profile) error {
crt := p.Subject()
crt.EmailAddresses = emails
return nil
}
}
// WithHosts returns a Profile modifier which sets the DNS Names and IP Addresses
// that will be bound to the subject Certificate.
//
@@ -351,7 +361,7 @@ func (b *base) CreateWriteCertificate(crtOut, keyOut, pass string) ([]byte, erro
if err != nil {
return nil, errors.WithStack(err)
}
if err := utils.WriteFile(crtOut, pem.EncodeToMemory(&pem.Block{
if err = utils.WriteFile(crtOut, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: crtBytes,
}), 0600); err != nil {

View File

@@ -96,14 +96,20 @@ func OpenInBrowser(url string) error {
// Step executes step with the given commands and returns the standard output.
func Step(args ...string) ([]byte, error) {
var stderr bytes.Buffer
var stdout bytes.Buffer
cmd := exec.Command(os.Args[0], args...)
cmd.Stderr = &stderr
out, err := cmd.Output()
if err != nil {
return nil, errors.Wrapf(err, "error running %s %s:\n%s", os.Args[0], strings.Join(args, " "), stderr.String())
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = &stdout
if err := cmd.Start(); nil != err {
return nil, errors.Wrapf(err, "error starting: %s %s", os.Args[0], strings.Join(args, " "))
}
return out, nil
if err := cmd.Wait(); nil != err {
return nil, errors.Wrapf(err, "error running: %s %s", os.Args[0], strings.Join(args, " "))
}
return stdout.Bytes(), nil
}
// Command executes the given command with it's arguments and returns the
@@ -158,7 +164,12 @@ func errorAndExit(name string, err error) {
// signalHandler forwards all the signals to the cmd.
func signalHandler(cmd *exec.Cmd, exitCh chan int) {
signals := make(chan os.Signal)
// signal.Notify prefers a buffered channel. From documentation: "For a
// channel used for notification of just one signal value, a buffer of size
// 1 is sufficient." As we do not know how many signal values the cmd is
// expecting we select 1 as a sane default. In the future maybe we can make
// this value configurable.
signals := make(chan os.Signal, 1)
signal.Notify(signals)
defer signal.Stop(signals)
for {

View File

@@ -11,6 +11,54 @@ import (
)
var (
// KTY is the flag to set the key type.
KTY = cli.StringFlag{
Name: "kty",
Value: "EC",
Usage: `The <kty> to build the certificate upon.
If unset, default is EC.
: <kty> is a case-sensitive string and must be one of:
**EC**
: Create an **elliptic curve** keypair
**OKP**
: Create an octet key pair (for **"Ed25519"** curve)
**RSA**
: Create an **RSA** keypair`,
}
// Size is the flag to set the key size.
Size = cli.IntFlag{
Name: "size",
Usage: `The <size> (in bits) of the key for RSA and oct key types. RSA keys require a
minimum key size of 2048 bits. If unset, default is 2048 bits for RSA keys and 128 bits for oct keys.`,
}
// Curve is the flag to se the key curve.
Curve = cli.StringFlag{
Name: "crv, curve",
Usage: `The elliptic <curve> to use for EC and OKP key types. Corresponds
to the **"crv"** JWK parameter. Valid curves are defined in JWA [RFC7518]. If
unset, default is P-256 for EC keys and Ed25519 for OKP keys.
: <curve> is a case-sensitive string and must be one of:
**P-256**
: NIST P-256 Curve
**P-384**
: NIST P-384 Curve
**P-521**
: NIST P-521 Curve
**Ed25519**
: Ed25519 Curve`,
}
// Subtle is the flag required for delicate operations.
Subtle = cli.BoolFlag{
Name: "subtle",

View File

@@ -87,7 +87,7 @@ func ParseKey(filename string, opts ...Option) (*JSONWebKey, error) {
}
// Unmarshal the plain (or decrypted JWK)
if err := json.Unmarshal(b, jwk); err != nil {
if err = json.Unmarshal(b, jwk); err != nil {
return nil, errors.Errorf("error reading %s: unsupported format", filename)
}
case pemKeyType:

View File

@@ -20,25 +20,17 @@ SHELL := /bin/bash
bootstra%:
$Q which dep || go get github.com/golang/dep/cmd/dep
$Q dep ensure
$Q GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.17.1
vendor: Gopkg.lock
$Q dep ensure
BOOTSTRAP=\
github.com/golang/lint/golint \
github.com/client9/misspell/cmd/misspell \
github.com/gordonklaus/ineffassign \
github.com/tsenart/deadcode \
github.com/alecthomas/gometalinter
define VENDOR_BIN_TMPL
vendor/bin/$(notdir $(1)): vendor
$Q go build -o $$@ ./vendor/$(1)
VENDOR_BINS += vendor/bin/$(notdir $(1))
endef
$(foreach pkg,$(BOOTSTRAP),$(eval $(call VENDOR_BIN_TMPL,$(pkg))))
.PHONY: bootstra% vendor
#################################################
@@ -121,26 +113,10 @@ integration: bin/$(BINNAME)
# Linting
#########################################
LINTERS=\
gofmt \
golint \
vet \
misspell \
ineffassign \
deadcode
lint:
$Q LOG_LEVEL=error golangci-lint run
$(patsubst %,%-bin,$(filter-out gofmt vet,$(LINTERS))): %-bin: vendor/bin/%
gofmt-bin vet-bin:
$(LINTERS): %: vendor/bin/gometalinter %-bin vendor
$Q PATH=`pwd`/vendor/bin:$$PATH gometalinter --tests --disable-all --vendor \
--deadline=5m -s data -s pkg --enable $@ ./...
fmt:
$Q gofmt -l -w $(SRC)
lint: $(LINTERS)
.PHONY: $(LINTERS) lint fmt
.PHONY: lint
#########################################
# Install

View File

@@ -41,6 +41,10 @@ func HelpCommand() cli.Command {
Name: "report",
Usage: "Writes a JSON report to the HTML docs directory.",
},
cli.BoolFlag{
Name: "hugo",
Usage: "Writes hugo (vs jekyll) compatible markdown files",
},
},
}
}

View File

@@ -30,52 +30,65 @@ func markdownHelpAction(ctx *cli.Context) error {
return errs.FileError(err, dir)
}
isHugo := ctx.Bool("hugo")
// app index
index := path.Join(dir, "step.md")
w, err := os.Create(index)
if err != nil {
return errs.FileError(err, index)
}
markdownHelpPrinter(w, mdAppHelpTemplate, ctx.App)
markdownHelpPrinter(w, mdAppHelpTemplate, "", ctx.App)
if err := w.Close(); err != nil {
return errs.FileError(err, index)
}
// Subcommands
for _, cmd := range ctx.App.Commands {
if err := markdownHelpCommand(ctx.App, cmd, path.Join(dir, cmd.Name)); err != nil {
if err := markdownHelpCommand(ctx.App, cmd, cmd, path.Join(dir, cmd.Name), isHugo); err != nil {
return err
}
}
return nil
}
func markdownHelpCommand(app *cli.App, cmd cli.Command, base string) error {
func markdownHelpCommand(app *cli.App, cmd cli.Command, parent cli.Command, base string, isHugo bool) error {
if err := os.MkdirAll(base, 0755); err != nil {
return errs.FileError(err, base)
}
index := path.Join(base, "index.md")
fileName := "index.md"
// preserve jekyll compatibility for transition period
if isHugo && len(cmd.Subcommands) > 0 {
fileName = "_index.md"
}
index := path.Join(base, fileName)
w, err := os.Create(index)
if err != nil {
return errs.FileError(err, index)
}
parentName := parent.HelpName
if cmd.HelpName == parent.HelpName {
parentName = "step"
}
if len(cmd.Subcommands) == 0 {
markdownHelpPrinter(w, mdCommandHelpTemplate, cmd)
markdownHelpPrinter(w, mdCommandHelpTemplate, parentName, cmd)
return errs.FileError(w.Close(), index)
}
ctx := cli.NewContext(app, nil, nil)
ctx.App = createCliApp(ctx, cmd)
markdownHelpPrinter(w, mdSubcommandHelpTemplate, ctx.App)
markdownHelpPrinter(w, mdSubcommandHelpTemplate, parentName, ctx.App)
if err := w.Close(); err != nil {
return errs.FileError(err, index)
}
for _, sub := range cmd.Subcommands {
sub.HelpName = fmt.Sprintf("%s %s", cmd.HelpName, sub.Name)
if err := markdownHelpCommand(app, sub, path.Join(base, sub.Name)); err != nil {
if err := markdownHelpCommand(app, sub, cmd, path.Join(base, sub.Name), isHugo); err != nil {
return err
}
}

View File

@@ -16,6 +16,11 @@ var sectionRe = regexp.MustCompile(`(?m:^##)`)
//var sectionRe = regexp.MustCompile(`^## [^\n]*$`)
type frontmatterData struct {
Data interface{}
Parent string
}
// HelpPrinter overwrites cli.HelpPrinter and prints the formatted help to the terminal.
func HelpPrinter(w io.Writer, templ string, data interface{}) {
b := helpPreprocessor(w, templ, data)
@@ -34,11 +39,24 @@ func htmlHelpPrinter(w io.Writer, templ string, data interface{}) []byte {
return html
}
func markdownHelpPrinter(w io.Writer, templ string, data interface{}) {
func markdownHelpPrinter(w io.Writer, templ string, parent string, data interface{}) {
b := helpPreprocessor(w, templ, data)
frontmatter := frontmatterData{
Data: data,
Parent: parent,
}
var frontMatterTemplate = `---
layout: auto-doc
title: {{.HelpName}}
title: {{.Data.HelpName}}
menu:{{if .Parent}}
docs:
parent: {{.Parent}}{{else}}
main:
name: "Reference"
parent: "Documentation"
weight: 300{{end}}
---
`
@@ -46,7 +64,7 @@ title: {{.HelpName}}
if err != nil {
panic(err)
}
err = t.Execute(w, data)
err = t.Execute(w, frontmatter)
if err != nil {
panic(err)
}

View File

@@ -3,6 +3,8 @@ package usage
import (
"bytes"
"fmt"
"html"
"strconv"
"strings"
"text/template"
)
@@ -116,6 +118,15 @@ This documentation is available online at https://smallstep.com/docs/cli
## COPYRIGHT
{{.Copyright}}
## FEEDBACK ` +
html.UnescapeString("&#"+strconv.Itoa(128525)+";") + " " +
html.UnescapeString("&#"+strconv.Itoa(127867)+";") +
`
The **step** utility is not instrumented for usage statistics. It does not phone home.
But your feedback is extremely valuable. Any information you can provide regarding how youre using **step** helps.
Please send us a sentence or two, good or bad: **feedback@smallstep.com** or join https://gitter.im/smallstep/community.
{{end}}
`

View File

@@ -213,23 +213,22 @@ func (f *CertificateFlow) Sign(ctx *cli.Context, token string, csr api.Certifica
// CreateSignRequest is a helper function that given an x509 OTT returns a
// simple but secure sign request as well as the private key used.
func (f *CertificateFlow) CreateSignRequest(tok, subject string, sans []string) (*api.SignRequest, crypto.PrivateKey, error) {
func (f *CertificateFlow) CreateSignRequest(ctx *cli.Context, tok, subject string, sans []string) (*api.SignRequest, crypto.PrivateKey, error) {
jwt, err := token.ParseInsecure(tok)
if err != nil {
return nil, nil, err
}
pk, err := keys.GenerateDefaultKey()
kty, crv, size, err := utils.GetKeyDetailsFromCLI(ctx, false, "kty", "curve", "size")
if err != nil {
return nil, nil, err
}
pk, err := keys.GenerateKey(kty, crv, size)
if err != nil {
return nil, nil, err
}
var emails []string
dnsNames, ips := splitSANs(sans, jwt.Payload.SANs)
if jwt.Payload.Email != "" {
emails = append(emails, jwt.Payload.Email)
}
dnsNames, ips, emails := splitSANs(sans, jwt.Payload.SANs)
switch jwt.Payload.Type() {
case token.AWS:
doc := jwt.Payload.Amazon.InstanceIdentityDocument
@@ -241,7 +240,7 @@ func (f *CertificateFlow) CreateSignRequest(tok, subject string, sans []string)
if !sharedContext.DisableCustomSANs {
defaultSANs = append(defaultSANs, subject)
}
dnsNames, ips = splitSANs(defaultSANs)
dnsNames, ips, emails = splitSANs(defaultSANs)
}
case token.GCP:
ce := jwt.Payload.Google.ComputeEngine
@@ -253,7 +252,7 @@ func (f *CertificateFlow) CreateSignRequest(tok, subject string, sans []string)
if !sharedContext.DisableCustomSANs {
defaultSANs = append(defaultSANs, subject)
}
dnsNames, ips = splitSANs(defaultSANs)
dnsNames, ips, emails = splitSANs(defaultSANs)
}
case token.Azure:
if len(ips) == 0 && len(dnsNames) == 0 {
@@ -263,8 +262,13 @@ func (f *CertificateFlow) CreateSignRequest(tok, subject string, sans []string)
if !sharedContext.DisableCustomSANs {
defaultSANs = append(defaultSANs, subject)
}
dnsNames, ips = splitSANs(defaultSANs)
dnsNames, ips, emails = splitSANs(defaultSANs)
}
case token.OIDC:
if jwt.Payload.Email != "" {
emails = append(emails, jwt.Payload.Email)
}
subject = jwt.Payload.Subject
default: // Use common name in the token
subject = jwt.Payload.Subject
}
@@ -273,10 +277,9 @@ func (f *CertificateFlow) CreateSignRequest(tok, subject string, sans []string)
Subject: pkix.Name{
CommonName: subject,
},
SignatureAlgorithm: keys.DefaultSignatureAlgorithm,
DNSNames: dnsNames,
IPAddresses: ips,
EmailAddresses: emails,
DNSNames: dnsNames,
IPAddresses: ips,
EmailAddresses: emails,
}
csr, err := x509.CreateCertificateRequest(rand.Reader, template, pk)
@@ -297,8 +300,8 @@ func (f *CertificateFlow) CreateSignRequest(tok, subject string, sans []string)
}
// splitSANs unifies the SAN collections passed as arguments and returns a list
// of DNS names and a list of IP addresses.
func splitSANs(args ...[]string) (dnsNames []string, ipAddresses []net.IP) {
// of DNS names, a list of IP addresses, and a list of emails.
func splitSANs(args ...[]string) (dnsNames []string, ipAddresses []net.IP, email []string) {
m := make(map[string]bool)
var unique []string
for _, sans := range args {

View File

@@ -50,7 +50,7 @@ func NewOfflineCA(configFile string) (*OfflineCA, error) {
}
var config authority.Config
if err := json.Unmarshal(b, &config); err != nil {
if err = json.Unmarshal(b, &config); err != nil {
return nil, errors.Wrapf(err, "error reading %s", configFile)
}
@@ -91,7 +91,7 @@ func (c *OfflineCA) VerifyClientCert(certFile, keyFile string) error {
return err
}
// Validate that the certificate and key match
if _, err := tls.X509KeyPair(pem.EncodeToMemory(certPem), pem.EncodeToMemory(keyPem)); err != nil {
if _, err = tls.X509KeyPair(pem.EncodeToMemory(certPem), pem.EncodeToMemory(keyPem)); err != nil {
return errors.Wrap(err, "error loading x509 key pair")
}
@@ -109,7 +109,7 @@ func (c *OfflineCA) VerifyClientCert(certFile, keyFile string) error {
Intermediates: intermediatePool,
}
if _, err := cert.Verify(opts); err != nil {
if _, err = cert.Verify(opts); err != nil {
return errors.Wrapf(err, "failed to verify certificate")
}
@@ -266,7 +266,8 @@ func (c *OfflineCA) GenerateToken(ctx *cli.Context, typ int, subject string, san
switch p := p.(type) {
case *provisioner.OIDC: // Run step oauth
out, err := exec.Step("oauth", "--oidc", "--bare",
var out []byte
out, err = exec.Step("oauth", "--oidc", "--bare",
"--provider", p.ConfigurationEndpoint,
"--client-id", p.ClientID, "--client-secret", p.ClientSecret)
if err != nil {

View File

@@ -85,9 +85,13 @@ func NewTokenFlow(ctx *cli.Context, typ int, subject string, sans []string, caUR
switch p := p.(type) {
case *provisioner.OIDC: // Run step oauth
out, err := exec.Step("oauth", "--oidc", "--bare",
args := []string{"oauth", "--oidc", "--bare",
"--provider", p.ConfigurationEndpoint,
"--client-id", p.ClientID, "--client-secret", p.ClientSecret)
"--client-id", p.ClientID, "--client-secret", p.ClientSecret}
if ctx.IsSet("console") {
args = append(args, "--console")
}
out, err := exec.Step(args...)
if err != nil {
return "", err
}