Skip to content

Commit 9ce7954

Browse files
authored
Merge pull request #52 from smallstep/mariano/validity
Allow to add custom validity in `step certificate create`
2 parents b3e0774 + 1496671 commit 9ce7954

File tree

4 files changed

+68
-25
lines changed

4 files changed

+68
-25
lines changed

command/ca/certificate.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,11 @@ func signCertificateTokenFlow(ctx *cli.Context, subject string) (string, error)
208208
}
209209

210210
// parse times or durations
211-
notBefore, ok := parseTimeOrDuration(ctx.String("not-before"))
211+
notBefore, ok := flags.ParseTimeOrDuration(ctx.String("not-before"))
212212
if !ok {
213213
return "", errs.InvalidFlagValue(ctx, "not-before", ctx.String("not-before"), "")
214214
}
215-
notAfter, ok := parseTimeOrDuration(ctx.String("not-after"))
215+
notAfter, ok := flags.ParseTimeOrDuration(ctx.String("not-after"))
216216
if !ok {
217217
return "", errs.InvalidFlagValue(ctx, "not-after", ctx.String("not-after"), "")
218218
}
@@ -232,11 +232,11 @@ func signCertificateRequest(ctx *cli.Context, token string, csr api.CertificateR
232232
caURL := ctx.String("ca-url")
233233

234234
// parse times or durations
235-
notBefore, ok := parseTimeOrDuration(ctx.String("not-before"))
235+
notBefore, ok := flags.ParseTimeOrDuration(ctx.String("not-before"))
236236
if !ok {
237237
return errs.InvalidFlagValue(ctx, "not-before", ctx.String("not-before"), "")
238238
}
239-
notAfter, ok := parseTimeOrDuration(ctx.String("not-after"))
239+
notAfter, ok := flags.ParseTimeOrDuration(ctx.String("not-after"))
240240
if !ok {
241241
return errs.InvalidFlagValue(ctx, "not-after", ctx.String("not-after"), "")
242242
}

command/ca/token.go

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ func newTokenAction(ctx *cli.Context) error {
151151
}
152152

153153
// parse times or durations
154-
notBefore, ok := parseTimeOrDuration(ctx.String("not-before"))
154+
notBefore, ok := flags.ParseTimeOrDuration(ctx.String("not-before"))
155155
if !ok {
156156
return errs.InvalidFlagValue(ctx, "not-before", ctx.String("not-before"), "")
157157
}
158-
notAfter, ok := parseTimeOrDuration(ctx.String("not-after"))
158+
notAfter, ok := flags.ParseTimeOrDuration(ctx.String("not-after"))
159159
if !ok {
160160
return errs.InvalidFlagValue(ctx, "not-after", ctx.String("not-after"), "")
161161
}
@@ -360,22 +360,6 @@ func newTokenFlow(ctx *cli.Context, subject, caURL, root, kid, issuer, passwordF
360360
return generateToken(subject, kid, issuer, audience, root, notBefore, notAfter, jwk)
361361
}
362362

363-
func parseTimeOrDuration(s string) (time.Time, bool) {
364-
if s == "" {
365-
return time.Time{}, true
366-
}
367-
368-
var t time.Time
369-
if err := t.UnmarshalText([]byte(s)); err != nil {
370-
d, err := time.ParseDuration(s)
371-
if err != nil {
372-
return time.Time{}, false
373-
}
374-
t = time.Now().Add(d)
375-
}
376-
return t, true
377-
}
378-
379363
// provisionerFilter returns a slice of provisioners that pass the given filter.
380364
func provisionerFilter(provisioners []*authority.Provisioner, f func(*authority.Provisioner) bool) []*authority.Provisioner {
381365
var result []*authority.Provisioner

command/certificate/create.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ $ step certificate create foo foo.crt foo.key --profile leaf \
8282
--ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key
8383
'''
8484
85+
Create a leaf certificate and key with custom validity:
86+
87+
'''
88+
$ step certificate create foo foo.crt foo.key --profile leaf \
89+
--ca ./intermediate-ca.crt --ca-key ./intermediate-ca.key \
90+
--not-before 24h --not-after 2160h
91+
'''
92+
8593
Create a root certificate and key with underlying OKP Ed25519:
8694
8795
'''
@@ -193,6 +201,22 @@ unset, default is P-256 for EC keys and Ed25519 for OKP keys.
193201
**Ed25519**
194202
: Ed25519 Curve
195203
`,
204+
},
205+
cli.StringFlag{
206+
Name: "not-before",
207+
Usage: `The <time|duration> set in the NotBefore property of the certificate. If a
208+
<time> is used it is expected to be in RFC 3339 format. If a <duration> is
209+
used, it is a sequence of decimal numbers, each with optional fraction and a
210+
unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns",
211+
"us" (or "µs"), "ms", "s", "m", "h".`,
212+
},
213+
cli.StringFlag{
214+
Name: "not-after",
215+
Usage: `The <time|duration> set in the NotAfter property of the certificate. If a
216+
<time> is used it is expected to be in RFC 3339 format. If a <duration> is
217+
used, it is a sequence of decimal numbers, each with optional fraction and a
218+
unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns",
219+
"us" (or "µs"), "ms", "s", "m", "h".`,
196220
},
197221
flags.Force,
198222
},
@@ -217,6 +241,18 @@ func createAction(ctx *cli.Context) error {
217241
return errs.EqualArguments(ctx, "CRT_FILE", "KEY_FILE")
218242
}
219243

244+
notBefore, ok := flags.ParseTimeOrDuration(ctx.String("not-before"))
245+
if !ok {
246+
return errs.InvalidFlagValue(ctx, "not-before", ctx.String("not-before"), "")
247+
}
248+
notAfter, ok := flags.ParseTimeOrDuration(ctx.String("not-after"))
249+
if !ok {
250+
return errs.InvalidFlagValue(ctx, "not-after", ctx.String("not-after"), "")
251+
}
252+
if !notAfter.IsZero() && !notBefore.IsZero() && notBefore.After(notAfter) {
253+
return errs.IncompatibleFlagValues(ctx, "not-before", ctx.String("not-before"), "not-after", ctx.String("not-after"))
254+
}
255+
220256
var typ string
221257
if ctx.Bool("csr") {
222258
typ = "x509-csr"
@@ -283,7 +319,8 @@ func createAction(ctx *cli.Context) error {
283319
return errors.WithStack(err)
284320
}
285321
profile, err = x509util.NewLeafProfile(subject, issIdentity.Crt,
286-
issIdentity.Key, x509util.GenerateKeyPair(kty, crv, size))
322+
issIdentity.Key, x509util.GenerateKeyPair(kty, crv, size),
323+
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0))
287324
if err != nil {
288325
return errors.WithStack(err)
289326
}
@@ -297,14 +334,16 @@ func createAction(ctx *cli.Context) error {
297334
}
298335
profile, err = x509util.NewIntermediateProfile(subject,
299336
issIdentity.Crt, issIdentity.Key,
300-
x509util.GenerateKeyPair(kty, crv, size))
337+
x509util.GenerateKeyPair(kty, crv, size),
338+
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0))
301339
if err != nil {
302340
return errors.WithStack(err)
303341
}
304342
}
305343
case "root-ca":
306344
profile, err = x509util.NewRootProfile(subject,
307-
x509util.GenerateKeyPair(kty, crv, size))
345+
x509util.GenerateKeyPair(kty, crv, size),
346+
x509util.WithNotBeforeAfterDuration(notBefore, notAfter, 0))
308347
if err != nil {
309348
return errors.WithStack(err)
310349
}

flags/flags.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package flags
22

33
import (
4+
"time"
5+
46
"github.com/urfave/cli"
57
)
68

@@ -19,3 +21,21 @@ var Force = cli.BoolFlag{
1921
Name: "f,force",
2022
Usage: "Force the overwrite of files without asking.",
2123
}
24+
25+
// ParseTimeOrDuration is a helper that returns the time or the current time
26+
// with an extra duration. It's used in flags like --not-before, --not-after.
27+
func ParseTimeOrDuration(s string) (time.Time, bool) {
28+
if s == "" {
29+
return time.Time{}, true
30+
}
31+
32+
var t time.Time
33+
if err := t.UnmarshalText([]byte(s)); err != nil {
34+
d, err := time.ParseDuration(s)
35+
if err != nil {
36+
return time.Time{}, false
37+
}
38+
t = time.Now().Add(d)
39+
}
40+
return t, true
41+
}

0 commit comments

Comments
 (0)