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

@@ -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
}