From 855d1f4f5f22d252a0018c444baa210873685282 Mon Sep 17 00:00:00 2001 From: alvidofaisal Date: Sun, 1 Jun 2025 03:15:41 +0700 Subject: [PATCH 1/2] Enable --cert-not-before and --cert-not-after for X.509 tokens --- go.mod | 3 +++ go.sum | 4 ++++ token/options.go | 14 ++++++++++++++ token/options_test.go | 6 ++++++ utils/cautils/token_generator.go | 13 +++++++++---- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 639a8c5e7..c27d64a07 100644 --- a/go.mod +++ b/go.mod @@ -131,12 +131,15 @@ require ( go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.uber.org/mock v0.5.2 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect + golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.40.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.14.0 // indirect golang.org/x/text v0.25.0 // indirect golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.33.0 // indirect google.golang.org/api v0.234.0 // indirect google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect diff --git a/go.sum b/go.sum index 9281954e2..6951b017c 100644 --- a/go.sum +++ b/go.sum @@ -396,6 +396,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -474,6 +476,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.234.0 h1:d3sAmYq3E9gdr2mpmiWGbm9pHsA/KJmyiLkwKfHBqU4= google.golang.org/api v0.234.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= diff --git a/token/options.go b/token/options.go index 3ceb1d1b2..38b2b4d73 100644 --- a/token/options.go +++ b/token/options.go @@ -107,6 +107,20 @@ func WithSSH(v interface{}) Options { }) } +// WithValidityOptions returns an Options function that sets the certificate +// validity period in the token claims. +func WithValidityOptions(notBefore, notAfter provisioner.TimeDuration) Options { + return func(c *Claims) error { + if !notBefore.IsZero() { + c.Set("certNotBefore", notBefore.Time().Unix()) + } + if !notAfter.IsZero() { + c.Set("certNotAfter", notAfter.Time().Unix()) + } + return nil + } +} + // WithConfirmationFingerprint returns an Options function that sets the cnf // claim with the given CSR fingerprint. func WithConfirmationFingerprint(fp string) Options { diff --git a/token/options_test.go b/token/options_test.go index 5b7e209db..0bb909b36 100644 --- a/token/options_test.go +++ b/token/options_test.go @@ -19,6 +19,8 @@ import ( "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x25519" "golang.org/x/crypto/ssh" + + "go.step.sm/crypto/provisioner" ) func TestOptions(t *testing.T) { @@ -90,6 +92,10 @@ func TestOptions(t *testing.T) { {"WithFingerprint csr ok", WithFingerprint(testCSR), &Claims{ExtraClaims: map[string]any{"cnf": map[string]string{"x5rt#S256": "ak6j6CwuZbd_mOQ-pNOUwhpmtSN0mY0xrLvaQL4J5l8"}}}, false}, {"WithFingerprint ssh ok", WithFingerprint(testSSH), &Claims{ExtraClaims: map[string]any{"cnf": map[string]string{"x5rt#S256": "hpTQOoB7fIRxTp-FhXCIm94mGBv7_dzr_5SxLn1Pnwk"}}}, false}, {"WithFingerprint fail", WithFingerprint("unexpected type"), empty, true}, + {"WithValidityOptions ok", WithValidityOptions(provisioner.TimeDuration{Time: now}, provisioner.TimeDuration{Time: now.Add(5 * time.Minute)}), &Claims{ExtraClaims: map[string]interface{}{"certNotBefore": now.Unix(), "certNotAfter": now.Add(5 * time.Minute).Unix()}}, false}, + {"WithValidityOptions only NotBefore", WithValidityOptions(provisioner.TimeDuration{Time: now}, provisioner.TimeDuration{}), &Claims{ExtraClaims: map[string]interface{}{"certNotBefore": now.Unix()}}, false}, + {"WithValidityOptions only NotAfter", WithValidityOptions(provisioner.TimeDuration{}, provisioner.TimeDuration{Time: now.Add(5 * time.Minute)}), &Claims{ExtraClaims: map[string]interface{}{"certNotAfter": now.Add(5 * time.Minute).Unix()}}, false}, + {"WithValidityOptions zero values", WithValidityOptions(provisioner.TimeDuration{}, provisioner.TimeDuration{}), &Claims{ExtraClaims: map[string]interface{}{}}, false}, } for _, tt := range tests { diff --git a/utils/cautils/token_generator.go b/utils/cautils/token_generator.go index 7937cc658..f95e51185 100644 --- a/utils/cautils/token_generator.go +++ b/utils/cautils/token_generator.go @@ -95,12 +95,17 @@ func (t *TokenGenerator) Token(sub string, opts ...token.Options) (string, error // SignToken generates a X.509 certificate signing token. If sans is empty, we // will use the subject (common name) as the only SAN. -func (t *TokenGenerator) SignToken(sub string, sans []string, opts ...token.Options) (string, error) { +func (t *TokenGenerator) SignToken(sub string, sans []string, certNotBefore, certNotAfter provisioner.TimeDuration, opts ...token.Options) (string, error) { if len(sans) == 0 { sans = []string{sub} } opts = append(opts, token.WithSANS(sans)) + // Add validity options for the certificate + if !certNotBefore.IsZero() || !certNotAfter.IsZero() { + opts = append(opts, token.WithValidityOptions(certNotBefore, certNotAfter)) + } + // Tie certificate request to the token used in the JWK and X5C provisioners if sharedContext.CertificateRequest != nil { opts = append(opts, token.WithFingerprint(sharedContext.CertificateRequest)) @@ -258,7 +263,7 @@ func generateX5CToken(ctx *cli.Context, p *provisioner.X5C, tokType int, tokAttr switch tokType { case SignType: - return tokenGen.SignToken(tokAttrs.subject, tokAttrs.sans, tokenOpts...) + return tokenGen.SignToken(tokAttrs.subject, tokAttrs.sans, tokAttrs.certNotBefore, tokAttrs.certNotAfter, tokenOpts...) case RevokeType: return tokenGen.RevokeToken(tokAttrs.subject, tokenOpts...) case SSHUserSignType: @@ -296,7 +301,7 @@ func generateNebulaToken(ctx *cli.Context, p *provisioner.Nebula, tokType int, t tokAttrs.notBefore, tokAttrs.notAfter, jwk) switch tokType { case SignType: - return tokenGen.SignToken(tokAttrs.subject, tokAttrs.sans, token.WithNebulaCert(certFile, key)) + return tokenGen.SignToken(tokAttrs.subject, tokAttrs.sans, tokAttrs.certNotBefore, tokAttrs.certNotAfter, token.WithNebulaCert(certFile, key)) case RevokeType: return tokenGen.RevokeToken(tokAttrs.subject, token.WithNebulaCert(certFile, key)) case SSHUserSignType: @@ -454,7 +459,7 @@ func generateJWKToken(ctx *cli.Context, p *provisioner.JWK, tokType int, tokAttr tokAttrs.notBefore, tokAttrs.notAfter, jwk) switch tokType { case SignType: - return tokenGen.SignToken(tokAttrs.subject, tokAttrs.sans) + return tokenGen.SignToken(tokAttrs.subject, tokAttrs.sans, tokAttrs.certNotBefore, tokAttrs.certNotAfter) case RevokeType: return tokenGen.RevokeToken(tokAttrs.subject) case SSHUserSignType: From 3ac2bc0bd97abf26377cee4e488e08bad12a9f76 Mon Sep 17 00:00:00 2001 From: alvidofaisal Date: Sun, 1 Jun 2025 03:26:43 +0700 Subject: [PATCH 2/2] Add missing provisioner import --- token/options.go | 1 + 1 file changed, 1 insertion(+) diff --git a/token/options.go b/token/options.go index 38b2b4d73..f2faad18f 100644 --- a/token/options.go +++ b/token/options.go @@ -21,6 +21,7 @@ import ( "go.step.sm/crypto/fingerprint" "go.step.sm/crypto/jose" "go.step.sm/crypto/pemutil" + "go.step.sm/crypto/provisioner" "go.step.sm/crypto/x25519" )