forked from smallstep/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
add.go
177 lines (159 loc) · 4.72 KB
/
add.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package provisioner
import (
"github.com/pkg/errors"
"github.com/smallstep/certificates/authority"
"github.com/smallstep/cli/errs"
"github.com/smallstep/cli/flags"
"github.com/smallstep/cli/jose"
"github.com/smallstep/cli/ui"
"github.com/smallstep/cli/utils"
"github.com/urfave/cli"
)
func addCommand() cli.Command {
return cli.Command{
Name: "add",
Action: cli.ActionFunc(addAction),
Usage: "add one or more provisioners the CA configuration",
UsageText: `**step ca provisioner add** <name> <jwk-file> [<jwk-file> ...]
[**--ca-config**=<file>] [**--create**] [**--password-file**=<file>]`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "ca-config",
Usage: "The <file> containing the CA configuration.",
},
cli.BoolFlag{
Name: "create",
Usage: `Create a new ECDSA key pair using curve P-256 and populate a new provisioner.`,
},
flags.PasswordFile,
},
Description: `**step ca provisioner add** adds one or more provisioners
to the configuration and writes the new configuration back to the CA config.
## POSITIONAL ARGUMENTS
<name>
: The name linked to all the keys.
<jwk-path>
: List of private (or public) keys in JWK or PEM format.
## EXAMPLES
Add a single provisioner:
'''
$ step ca provisioner add max@smallstep.com ./max-laptop.jwk --ca-config ca.json
'''
Add a single provisioner using an auto-generated asymmetric key pair:
'''
$ step ca provisioner add max@smallstep.com --ca-config ca.json \
--create
'''
Add a list of provisioners for a single name:
'''
$ step ca provisioner add max@smallstep.com ./max-laptop.jwk ./max-phone.pem ./max-work.pem \
--ca-config ca.json
'''`,
}
}
func addAction(ctx *cli.Context) (err error) {
if ctx.NArg() < 1 {
return errs.TooFewArguments(ctx)
}
args := ctx.Args()
name := args[0]
config := ctx.String("ca-config")
if len(config) == 0 {
return errs.RequiredFlag(ctx, "ca-config")
}
var password string
if passwordFile := ctx.String("password-file"); len(passwordFile) > 0 {
password, err = utils.ReadStringPasswordFromFile(passwordFile)
if err != nil {
return err
}
}
c, err := authority.LoadConfiguration(config)
if err != nil {
return errors.Wrapf(err, "error loading configuration")
}
var provisioners []*authority.Provisioner
provMap := make(map[string]*authority.Provisioner)
for _, prov := range c.AuthorityConfig.Provisioners {
provisioners = append(provisioners, prov)
provMap[prov.Name+":"+prov.Key.KeyID] = prov
}
create := ctx.Bool("create")
if create {
if ctx.NArg() > 1 {
return errs.IncompatibleFlag(ctx, "create", "<jwk-path> positional arg")
}
pass, err := ui.PromptPasswordGenerate("Please enter a password to encrypt the provisioner private key? [leave empty and we'll generate one]", ui.WithValue(password))
if err != nil {
return err
}
jwk, jwe, err := jose.GenerateDefaultKeyPair(pass)
if err != nil {
return err
}
encryptedKey, err := jwe.CompactSerialize()
if err != nil {
return errors.Wrap(err, "error serializing private key")
}
// Check for duplicates
if _, ok := provMap[name+":"+jwk.KeyID]; ok {
return errors.Errorf("duplicated provisioner: CA config already contains a provisioner with issuer=%s and kid=%s", name, jwk.KeyID)
}
prov := &authority.Provisioner{
Name: name,
Type: "jwk",
Key: jwk,
EncryptedKey: encryptedKey,
}
provisioners = append(provisioners, prov)
provMap[name+":"+jwk.KeyID] = prov
} else {
if ctx.NArg() < 2 {
return errs.TooFewArguments(ctx)
}
jwkFiles := args[1:]
for _, filename := range jwkFiles {
jwk, err := jose.ParseKey(filename)
if err != nil {
return errs.FileError(err, filename)
}
// Only use asymmetric cryptography
if _, ok := jwk.Key.([]byte); ok {
return errors.New("invalid JWK: a symmetric key cannot be used as a provisioner")
}
if len(jwk.KeyID) == 0 {
jwk.KeyID, err = jose.Thumbprint(jwk)
if err != nil {
return err
}
}
// Check for duplicates
if _, ok := provMap[name+":"+jwk.KeyID]; ok {
return errors.Errorf("duplicated provisioner: CA config already contains a provisioner with issuer=%s and kid=%s", name, jwk.KeyID)
}
// Encrypt JWK
var encryptedKey string
if !jwk.IsPublic() {
jwe, err := jose.EncryptJWK(jwk)
if err != nil {
return err
}
encryptedKey, err = jwe.CompactSerialize()
if err != nil {
return errors.Wrap(err, "error serializing private key")
}
}
key := jwk.Public()
prov := &authority.Provisioner{
Name: name,
Type: "jwk",
Key: &key,
EncryptedKey: encryptedKey,
}
provisioners = append(provisioners, prov)
provMap[name+":"+jwk.KeyID] = prov
}
}
c.AuthorityConfig.Provisioners = provisioners
return c.Save(config)
}