You've already forked step-ca-cli
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:
11
.github/ISSUE_TEMPLATE/enhancement.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE/enhancement.md
vendored
Normal 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
4
.gitignore
vendored
@@ -19,3 +19,7 @@ coverage.txt
|
||||
output
|
||||
vendor
|
||||
step
|
||||
|
||||
# Ignore modules until switch from gopkg
|
||||
go.mod
|
||||
go.sum
|
||||
|
67
.golangci.yml
Normal file
67
.golangci.yml
Normal 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
122
Gopkg.lock
generated
@@ -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",
|
||||
|
12
Gopkg.toml
12
Gopkg.toml
@@ -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
207
README.md
@@ -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).
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -21,8 +21,8 @@ func certificateCommand() cli.Command {
|
||||
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>]`,
|
||||
[**--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
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -23,7 +23,8 @@ func signCertificateCommand() cli.Command {
|
||||
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>]`,
|
||||
[**--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
|
||||
}
|
||||
|
@@ -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")
|
||||
}
|
||||
@@ -320,26 +300,30 @@ func createAction(ctx *cli.Context) error {
|
||||
},
|
||||
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{
|
||||
pubPEMs = []*pem.Block{{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: crtBytes,
|
||||
Headers: map[string]string{},
|
||||
}}
|
||||
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")
|
||||
}
|
||||
|
@@ -16,7 +16,8 @@ func signCommand() 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>`,
|
||||
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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -23,10 +23,11 @@ func changePassCommand() 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.
|
||||
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
|
||||
}
|
||||
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")
|
||||
}
|
||||
if _, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(newKeyPath, 0644)); err != nil {
|
||||
opts = append(opts, pemutil.WithPassword(pass))
|
||||
}
|
||||
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
|
||||
}
|
||||
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 {
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -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")
|
||||
}
|
||||
|
||||
|
@@ -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")
|
||||
}
|
||||
|
||||
|
@@ -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")
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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")
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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.",
|
||||
@@ -152,6 +157,7 @@ func oauthCmd(c *cli.Context) error {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -278,12 +284,13 @@ type options struct {
|
||||
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.
|
||||
|
@@ -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")
|
||||
|
@@ -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")
|
||||
|
@@ -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",
|
||||
|
@@ -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")
|
||||
@@ -129,7 +131,6 @@ type PKI struct {
|
||||
intermediate, intermediateKey string
|
||||
sshHostCert, sshHostKey string
|
||||
sshUserCert, sshUserKey string
|
||||
country, locality, organization string
|
||||
config, defaults string
|
||||
ottPublicKey *jose.JSONWebKey
|
||||
ottPrivateKey *jose.JSONWebEncryption
|
||||
@@ -142,20 +143,18 @@ type PKI struct {
|
||||
|
||||
// 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 you’re 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
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
25
exec/exec.go
25
exec/exec.go
@@ -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 {
|
||||
|
@@ -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",
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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 you’re using **step** helps.
|
||||
Please send us a sentence or two, good or bad: **feedback@smallstep.com** or join https://gitter.im/smallstep/community.
|
||||
{{end}}
|
||||
`
|
||||
|
||||
|
@@ -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,7 +277,6 @@ func (f *CertificateFlow) CreateSignRequest(tok, subject string, sans []string)
|
||||
Subject: pkix.Name{
|
||||
CommonName: subject,
|
||||
},
|
||||
SignatureAlgorithm: keys.DefaultSignatureAlgorithm,
|
||||
DNSNames: dnsNames,
|
||||
IPAddresses: ips,
|
||||
EmailAddresses: emails,
|
||||
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user