Skip to content

Commit 3f37257

Browse files
authored
Merge pull request #66 from smallstep/mariano/non-interactive
Add password-file flag on key creation and independent provisioner password
2 parents a329403 + 7dd55c2 commit 3f37257

File tree

7 files changed

+114
-37
lines changed

7 files changed

+114
-37
lines changed

command/ca/init.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ func initCommand() cli.Command {
6060
Name: "password-file",
6161
Usage: `The path to the <file> containing the password to encrypt the keys.`,
6262
},
63+
cli.StringFlag{
64+
Name: "provisioner-password-file",
65+
Usage: `The path to the <file> containing the password to encrypt the provisioner key.`,
66+
},
6367
cli.StringFlag{
6468
Name: "with-ca-url",
6569
Usage: `<URI> of the Step Certificate Authority to write in defaults.json`,
@@ -68,12 +72,11 @@ func initCommand() cli.Command {
6872
}
6973
}
7074

71-
func initAction(ctx *cli.Context) error {
75+
func initAction(ctx *cli.Context) (err error) {
7276
if err := assertCryptoRand(); err != nil {
7377
return err
7478
}
7579

76-
var password string
7780
var rootCrt *stepx509.Certificate
7881
var rootKey interface{}
7982

@@ -96,13 +99,22 @@ func initAction(ctx *cli.Context) error {
9699
}
97100
}
98101

99-
passwordFile := ctx.String("password-file")
100-
if passwordFile != "" {
101-
b, err := utils.ReadPasswordFromFile(passwordFile)
102+
var password string
103+
if passwordFile := ctx.String("password-file"); passwordFile != "" {
104+
password, err = utils.ReadStringPasswordFromFile(passwordFile)
105+
if err != nil {
106+
return err
107+
}
108+
}
109+
110+
// Provisioner password will be equal to the certificate private keys if
111+
// --provisioner-password-file is not provided.
112+
var provisionerPassword []byte
113+
if passwordFile := ctx.String("provisioner-password-file"); passwordFile != "" {
114+
provisionerPassword, err = utils.ReadPasswordFromFile(passwordFile)
102115
if err != nil {
103116
return err
104117
}
105-
password = string(b)
106118
}
107119

108120
p, err := pki.New(pki.GetPublicPath(), pki.GetSecretsPath(), pki.GetConfigPath())
@@ -157,9 +169,15 @@ func initAction(ctx *cli.Context) error {
157169
}
158170

159171
if configure {
160-
// Generate ott key pairs.
161-
if err := p.GenerateKeyPairs(pass); err != nil {
162-
return err
172+
// Generate provisioner key pairs.
173+
if len(provisionerPassword) > 0 {
174+
if err := p.GenerateKeyPairs(provisionerPassword); err != nil {
175+
return err
176+
}
177+
} else {
178+
if err := p.GenerateKeyPairs(pass); err != nil {
179+
return err
180+
}
163181
}
164182
}
165183

command/ca/provisioner/add.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"github.com/pkg/errors"
55
"github.com/smallstep/certificates/authority"
66
"github.com/smallstep/cli/errs"
7+
"github.com/smallstep/cli/flags"
78
"github.com/smallstep/cli/jose"
89
"github.com/smallstep/cli/ui"
10+
"github.com/smallstep/cli/utils"
911
"github.com/urfave/cli"
1012
)
1113

@@ -15,7 +17,7 @@ func addCommand() cli.Command {
1517
Action: cli.ActionFunc(addAction),
1618
Usage: "add one or more provisioners the CA configuration",
1719
UsageText: `**step ca provisioner add** <name> <jwk-file> [<jwk-file> ...]
18-
[**--ca-config**=<file>] [**--create**]`,
20+
[**--ca-config**=<file>] [**--create**] [**--password-file**=<file>]`,
1921
Flags: []cli.Flag{
2022
cli.StringFlag{
2123
Name: "ca-config",
@@ -25,6 +27,7 @@ func addCommand() cli.Command {
2527
Name: "create",
2628
Usage: `Create a new ECDSA key pair using curve P-256 and populate a new provisioner.`,
2729
},
30+
flags.PasswordFile,
2831
},
2932
Description: `**step ca provisioner add** adds one or more provisioners
3033
to the configuration and writes the new configuration back to the CA config.
@@ -58,7 +61,7 @@ $ step ca provisioner add [email protected] ./max-laptop.jwk ./max-phone.pem ./m
5861
}
5962
}
6063

61-
func addAction(ctx *cli.Context) error {
64+
func addAction(ctx *cli.Context) (err error) {
6265
if ctx.NArg() < 1 {
6366
return errs.TooFewArguments(ctx)
6467
}
@@ -71,6 +74,14 @@ func addAction(ctx *cli.Context) error {
7174
return errs.RequiredFlag(ctx, "ca-config")
7275
}
7376

77+
var password string
78+
if passwordFile := ctx.String("password-file"); len(passwordFile) > 0 {
79+
password, err = utils.ReadStringPasswordFromFile(passwordFile)
80+
if err != nil {
81+
return err
82+
}
83+
}
84+
7485
c, err := authority.LoadConfiguration(config)
7586
if err != nil {
7687
return errors.Wrapf(err, "error loading configuration")
@@ -89,7 +100,7 @@ func addAction(ctx *cli.Context) error {
89100
if ctx.NArg() > 1 {
90101
return errs.IncompatibleFlag(ctx, "create", "<jwk-path> positional arg")
91102
}
92-
pass, err := ui.PromptPasswordGenerate("Please enter a password to encrypt the provisioner private key? [leave empty and we'll generate one]")
103+
pass, err := ui.PromptPasswordGenerate("Please enter a password to encrypt the provisioner private key? [leave empty and we'll generate one]", ui.WithValue(password))
93104
if err != nil {
94105
return err
95106
}

command/crypto/jwk/create.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func createCommand() cli.Command {
3333
UsageText: `**step crypto jwk create** <public-jwk-file> <private-jwk-file>
3434
[**--kty**=<type>] [**--alg**=<algorithm>] [**--use**=<use>]
3535
[**--size**=<size>] [**--crv**=<curve>] [**--kid**=<kid>]
36-
[**--from-pem**=<pem-file>]`,
36+
[**--from-pem**=<pem-file>] [**--password-file**=<file>]`,
3737
Description: `**step crypto jwk create** generates a new JWK (JSON Web Key) or constructs a
3838
JWK from an existing key. The generated JWK conforms to RFC7517 and can be used
3939
to sign and encrypt data using JWT, JWS, and JWE.
@@ -393,28 +393,28 @@ related.`,
393393
Usage: `Create a JWK representing the key encoded in an
394394
existing <pem-file> instead of creating a new key.`,
395395
},
396-
cli.BoolFlag{
397-
Name: "no-password",
398-
Usage: `Do not ask for a password to encrypt the JWK. Sensitive
399-
key material will be written to disk unencrypted. This is not
400-
recommended. Requires **--insecure** flag.`,
401-
},
396+
flags.PasswordFile,
397+
flags.NoPassword,
402398
flags.Subtle,
403399
flags.Insecure,
404400
flags.Force,
405401
},
406402
}
407403
}
408404

409-
func createAction(ctx *cli.Context) error {
405+
func createAction(ctx *cli.Context) (err error) {
410406
// require public and private files
411407
if err := errs.NumberOfArguments(ctx, 2); err != nil {
412408
return err
413409
}
414410

415411
// Use password to protect private JWK by default
416412
usePassword := true
413+
passwordFile := ctx.String("password-file")
417414
if ctx.Bool("no-password") {
415+
if len(passwordFile) > 0 {
416+
return errs.IncompatibleFlag(ctx, "no-password", "password-file")
417+
}
418418
if ctx.Bool("insecure") {
419419
usePassword = false
420420
} else {
@@ -428,6 +428,15 @@ func createAction(ctx *cli.Context) error {
428428
return errs.EqualArguments(ctx, "public-jwk-file", "private-jwk-file")
429429
}
430430

431+
// Read password if necessary
432+
var password string
433+
if len(passwordFile) > 0 {
434+
password, err = utils.ReadStringPasswordFromFile(passwordFile)
435+
if err != nil {
436+
return err
437+
}
438+
}
439+
431440
kty := ctx.String("kty")
432441
crv := ctx.String("crv")
433442
alg := ctx.String("alg")
@@ -476,7 +485,6 @@ func createAction(ctx *cli.Context) error {
476485
}
477486

478487
// Generate or read secrets
479-
var err error
480488
var jwk *jose.JSONWebKey
481489
switch {
482490
case pemFile != "":
@@ -539,7 +547,7 @@ func createAction(ctx *cli.Context) error {
539547
var rcpt jose.Recipient
540548
// Generate JWE encryption key.
541549
if jose.SupportsPBKDF2 {
542-
key, err := ui.PromptPassword("Please enter the password to encrypt the private JWK")
550+
key, err := ui.PromptPassword("Please enter the password to encrypt the private JWK", ui.WithValue(password))
543551
if err != nil {
544552
return errors.Wrap(err, "error reading password")
545553
}

command/crypto/keypair.go

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ func createKeyPairCommand() cli.Command {
1919
Action: command.ActionFunc(createAction),
2020
Usage: "generate a public / private keypair in PEM format",
2121
UsageText: `**step crypto keypair** <pub_file> <priv_file>
22-
[**--curve**=<curve>] [**--no-password**] [**--size**=<size>]
23-
[**--kty**=<key-type>]`,
22+
[**--kty**=<key-type>] [**--curve**=<curve>] [**--size**=<size>]
23+
[**--password-file**=<file>] [**--no-password**]`,
2424
Description: `**step crypto keypair** generates a raw public /
2525
private keypair in PEM format. These keys can be used by other operations
2626
to sign and encrypt data, and the public key can be bound to an identity
@@ -125,22 +125,15 @@ unset, default is P-256 for EC keys and Ed25519 for OKP keys.
125125
Usage: `Create a PEM representing the key encoded in an
126126
existing <jwk-file> instead of creating a new key.`,
127127
},
128-
cli.BoolFlag{
129-
Name: "insecure",
130-
Hidden: true,
131-
},
132-
cli.BoolFlag{
133-
Name: "no-password",
134-
Usage: `Do not ask for a password to encrypt the private key.
135-
Sensitive key material will be written to disk unencrypted. This is not
136-
recommended. Requires **--insecure** flag.`,
137-
},
128+
flags.PasswordFile,
129+
flags.NoPassword,
130+
flags.Insecure,
138131
flags.Force,
139132
},
140133
}
141134
}
142135

143-
func createAction(ctx *cli.Context) error {
136+
func createAction(ctx *cli.Context) (err error) {
144137
if err := errs.NumberOfArguments(ctx, 2); err != nil {
145138
return err
146139
}
@@ -153,11 +146,23 @@ func createAction(ctx *cli.Context) error {
153146

154147
insecure := ctx.Bool("insecure")
155148
noPass := ctx.Bool("no-password")
149+
passwordFile := ctx.String("password-file")
150+
if noPass && len(passwordFile) > 0 {
151+
return errs.IncompatibleFlag(ctx, "no-password", "password-file")
152+
}
156153
if noPass && !insecure {
157154
return errs.RequiredWithFlag(ctx, "insecure", "no-password")
158155
}
159156

160-
var err error
157+
// Read password if necessary
158+
var password string
159+
if len(passwordFile) > 0 {
160+
password, err = utils.ReadStringPasswordFromFile(passwordFile)
161+
if err != nil {
162+
return err
163+
}
164+
}
165+
161166
var pub, priv interface{}
162167
fromJWK := ctx.String("from-jwk")
163168
if len(fromJWK) > 0 {
@@ -212,7 +217,7 @@ func createAction(ctx *cli.Context) error {
212217
return err
213218
}
214219
} else {
215-
pass, err := ui.PromptPassword("Please enter the password to encrypt the private key")
220+
pass, err := ui.PromptPassword("Please enter the password to encrypt the private key", ui.WithValue(password))
216221
if err != nil {
217222
return errors.Wrap(err, "error reading password")
218223
}

flags/flags.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ var Force = cli.BoolFlag{
2222
Usage: "Force the overwrite of files without asking.",
2323
}
2424

25+
// PasswordFile is a cli.Flag used to pass a file to encrypt or decrypt a
26+
// private key.
27+
var PasswordFile = cli.StringFlag{
28+
Name: "password-file",
29+
Usage: `The path to the <file> containing the password to encrypt or decrypt the private key.`,
30+
}
31+
32+
// NoPassword is a cli.Flag used to avoid using a password to encrypt private
33+
// keys.
34+
var NoPassword = cli.BoolFlag{
35+
Name: "no-password",
36+
Usage: `Do not ask for a password to encrypt a private key. Sensitive key material will
37+
be written to disk unencrypted. This is not recommended. Requires **--insecure** flag.`,
38+
}
39+
2540
// ParseTimeOrDuration is a helper that returns the time or the current time
2641
// with an extra duration. It's used in flags like --not-before, --not-after.
2742
func ParseTimeOrDuration(s string) (time.Time, bool) {

utils/read.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ func ReadPasswordFromFile(filename string) ([]byte, error) {
4545
return password, nil
4646
}
4747

48+
// ReadStringPasswordFromFile reads and returns the password from the given filename.
49+
// The contents of the file will be trimmed at the right.
50+
func ReadStringPasswordFromFile(filename string) (string, error) {
51+
b, err := ReadPasswordFromFile(filename)
52+
if err != nil {
53+
return "", err
54+
}
55+
return string(b), nil
56+
}
57+
4858
// ReadInput from stdin if something is detected or ask the user for an input
4959
// using the given prompt.
5060
func ReadInput(prompt string) ([]byte, error) {

utils/read_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ func TestReadPasswordFromFile(t *testing.T) {
4848
require.True(t, bytes.Equal([]byte("my-password-on-file"), b), "expected %s to equal %s", b, content)
4949
}
5050

51+
func TestStringReadPasswordFromFile(t *testing.T) {
52+
content := []byte("my-password-on-file\n")
53+
f, cleanup := newFile(t, content)
54+
defer cleanup()
55+
56+
s, err := ReadStringPasswordFromFile(f.Name())
57+
require.NoError(t, err)
58+
require.Equal(t, "my-password-on-file", s, "expected %s to equal %s", s, content)
59+
}
60+
5161
// Returns a temp file and a cleanup function to delete it.
5262
func newFile(t *testing.T, data []byte) (file *os.File, cleanup func()) {
5363
f, err := ioutil.TempFile("" /* dir */, "utils-read-test")

0 commit comments

Comments
 (0)