From 508b6e86688dd6176f3390bd7d0052e83582132c Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 28 Dec 2023 17:09:39 -0800 Subject: [PATCH 1/6] Check cnf claim with CSR or SSH public key fingerprint This commit allows tying tokens with the provided CSR or SSH public key. Tokens with a confirmation claim kid (cnf.kid) will validate that the provided fingerprint (kid) matches the CSR or SSH public key. This check will only be present in JWK and X5C provisioners. Fixes #1637 --- authority/authorize_test.go | 35 +++++++- authority/provisioner/jwk.go | 21 ++++- authority/provisioner/jwk_test.go | 79 +++++++++++++---- authority/provisioner/sign_options.go | 21 +++++ authority/provisioner/sign_ssh_options.go | 28 ++++++ authority/provisioner/ssh_test.go | 9 ++ authority/provisioner/utils_test.go | 31 +++++++ authority/provisioner/x5c.go | 19 +++- authority/provisioner/x5c_test.go | 100 ++++++++++++++++++---- authority/ssh.go | 26 +++++- authority/tls_test.go | 76 ++++++++++++++++ 11 files changed, 398 insertions(+), 47 deletions(-) diff --git a/authority/authorize_test.go b/authority/authorize_test.go index bec34fd6..975ffc01 100644 --- a/authority/authorize_test.go +++ b/authority/authorize_test.go @@ -88,6 +88,39 @@ func generateToken(sub, iss, aud string, sans []string, iat time.Time, jwk *jose return jose.Signed(sig).Claims(claims).CompactSerialize() } +func generateCustomToken(sub, iss, aud string, jwk *jose.JSONWebKey, extraHeaders, extraClaims map[string]any) (string, error) { + so := new(jose.SignerOptions) + so.WithType("JWT") + so.WithHeader("kid", jwk.KeyID) + + for k, v := range extraHeaders { + so.WithHeader(jose.HeaderKey(k), v) + } + + sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, so) + if err != nil { + return "", err + } + + id, err := randutil.ASCII(64) + if err != nil { + return "", err + } + + iat := time.Now() + claims := jose.Claims{ + ID: id, + Subject: sub, + Issuer: iss, + IssuedAt: jose.NewNumericDate(iat), + NotBefore: jose.NewNumericDate(iat), + Expiry: jose.NewNumericDate(iat.Add(5 * time.Minute)), + Audience: []string{aud}, + } + + return jose.Signed(sig).Claims(claims).Claims(extraClaims).CompactSerialize() +} + func TestAuthority_authorizeToken(t *testing.T) { a := testAuthority(t) @@ -491,7 +524,7 @@ func TestAuthority_authorizeSign(t *testing.T) { } } else { if assert.Nil(t, tc.err) { - assert.Equals(t, 10, len(got)) // number of provisioner.SignOptions returned + assert.Equals(t, 11, len(got)) // number of provisioner.SignOptions returned } } }) diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 3a7512b8..2f73c8e5 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -19,8 +19,9 @@ import ( // jwtPayload extends jwt.Claims with step attributes. type jwtPayload struct { jose.Claims - SANs []string `json:"sans,omitempty"` - Step *stepPayload `json:"step,omitempty"` + SANs []string `json:"sans,omitempty"` + Step *stepPayload `json:"step,omitempty"` + Confirmation *cnfPayload `json:"cnf,omitempty"` } type stepPayload struct { @@ -28,6 +29,10 @@ type stepPayload struct { RA *RAInfo `json:"ra,omitempty"` } +type cnfPayload struct { + Kid string `json:"kid,omitempty"` +} + // JWK is the default provisioner, an entity that can sign tokens necessary for // signature requests. type JWK struct { @@ -183,6 +188,12 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er } } + // Check the fingerprint of the certificate request if given. + var fingerprint string + if claims.Confirmation != nil { + fingerprint = claims.Confirmation.Kid + } + return []SignOption{ self, templateOptions, @@ -190,6 +201,7 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID).WithControllerOptions(p.ctl), profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()), // validators + fingerprintValidator(fingerprint), commonNameSliceValidator(append([]string{claims.Subject}, claims.SANs...)), defaultPublicKeyValidator{}, newDefaultSANsValidator(ctx, claims.SANs), @@ -229,6 +241,11 @@ func (p *JWK) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}), } + // Check the fingerprint of the certificate request if given. + if claims.Confirmation != nil && claims.Confirmation.Kid != "" { + signOptions = append(signOptions, sshFingerprintValidator(claims.Confirmation.Kid)) + } + // Default template attributes. certType := sshutil.UserCert keyID := claims.Subject diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 794fe1ea..2471130a 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -13,7 +13,9 @@ import ( "testing" "time" + "go.step.sm/crypto/fingerprint" "go.step.sm/crypto/jose" + "golang.org/x/crypto/ssh" "github.com/smallstep/assert" "github.com/smallstep/certificates/api/render" @@ -247,6 +249,9 @@ func TestJWK_AuthorizeSign(t *testing.T) { t2, err := generateToken("subject", p1.Name, testAudiences.Sign[0], "name@smallstep.com", []string{}, time.Now(), key1) assert.FatalError(t, err) + t3, err := generateCustomToken("subject", p1.Name, testAudiences.Sign[0], key1, nil, map[string]any{"cnf": map[string]any{"kid": "fingerprint"}}) + assert.FatalError(t, err) + // invalid signature failSig := t1[0 : len(t1)-2] @@ -254,12 +259,13 @@ func TestJWK_AuthorizeSign(t *testing.T) { token string } tests := []struct { - name string - prov *JWK - args args - code int - err error - sans []string + name string + prov *JWK + args args + code int + err error + sans []string + fingerprint string }{ { name: "fail-signature", @@ -284,6 +290,15 @@ func TestJWK_AuthorizeSign(t *testing.T) { err: nil, sans: []string{"subject"}, }, + { + name: "ok-cnf", + prov: p1, + args: args{t3}, + code: http.StatusOK, + err: nil, + sans: []string{"subject"}, + fingerprint: "fingerprint", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -297,7 +312,7 @@ func TestJWK_AuthorizeSign(t *testing.T) { } } else { if assert.NotNil(t, got) { - assert.Equals(t, 10, len(got)) + assert.Equals(t, 11, len(got)) for _, o := range got { switch v := o.(type) { case *JWK: @@ -321,6 +336,8 @@ func TestJWK_AuthorizeSign(t *testing.T) { case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) case *WebhookController: + case fingerprintValidator: + assert.Equals(t, tt.fingerprint, string(v)) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -393,17 +410,6 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) { jwk, err := decryptJSONWebKey(p1.EncryptedKey) assert.FatalError(t, err) - iss, aud := p1.Name, testAudiences.SSHSign[0] - - t1, err := generateSimpleSSHUserToken(iss, aud, jwk) - assert.FatalError(t, err) - - t2, err := generateSimpleSSHHostToken(iss, aud, jwk) - assert.FatalError(t, err) - - // invalid signature - failSig := t1[0 : len(t1)-2] - key, err := generateJSONWebKey() assert.FatalError(t, err) @@ -417,6 +423,39 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) { rsa1024, err := rsa.GenerateKey(rand.Reader, 1024) assert.FatalError(t, err) + // Calculate fingerprint + sshPub, err := ssh.NewPublicKey(pub) + assert.FatalError(t, err) + fp, err := fingerprint.New(sshPub.Marshal(), crypto.SHA256, fingerprint.Base64RawURLFingerprint) + assert.FatalError(t, err) + + iss, aud := p1.Name, testAudiences.SSHSign[0] + + t1, err := generateSimpleSSHUserToken(iss, aud, jwk) + assert.FatalError(t, err) + + t2, err := generateSimpleSSHHostToken(iss, aud, jwk) + assert.FatalError(t, err) + + t3, err := generateCustomToken("sub", iss, aud, jwk, nil, map[string]any{ + "step": map[string]any{ + "ssh": map[string]any{"certType": "host", "principals": []string{"smallstep.com"}}, + }, + "cnf": map[string]any{"kid": fp}, + }) + assert.FatalError(t, err) + + t4, err := generateCustomToken("sub", iss, aud, jwk, nil, map[string]any{ + "step": map[string]any{ + "ssh": map[string]any{"certType": "host", "principals": []string{"smallstep.com"}}, + }, + "cnf": map[string]any{"kid": "bad-fingerprint"}, + }) + assert.FatalError(t, err) + + // invalid signature + failSig := t1[0 : len(t1)-2] + userDuration := p1.ctl.Claimer.DefaultUserSSHCertDuration() hostDuration := p1.ctl.Claimer.DefaultHostSSHCertDuration() expectedUserOptions := &SignSSHOptions{ @@ -451,9 +490,11 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) { {"host-type", p1, args{t2, SignSSHOptions{CertType: "host"}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"host-principals", p1, args{t2, SignSSHOptions{Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"host-options", p1, args{t2, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"host-cnf", p1, args{t3, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"fail-sshCA-disabled", p2, args{"foo", SignSSHOptions{}, pub}, expectedUserOptions, http.StatusUnauthorized, true, false}, {"fail-signature", p1, args{failSig, SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, - {"rail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, + {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, + {"fail-cnf", p1, args{t4, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusUnauthorized, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index fec9b9f6..62017fad 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -5,7 +5,10 @@ import ( "crypto/ecdsa" "crypto/ed25519" "crypto/rsa" + "crypto/sha256" + "crypto/subtle" "crypto/x509" + "encoding/base64" "encoding/json" "net" "net/http" @@ -492,3 +495,21 @@ func (o *provisionerExtensionOption) Modify(cert *x509.Certificate, _ SignOption cert.ExtraExtensions = append(cert.ExtraExtensions, ext) return nil } + +// fingerprintValidator is a CertificateRequestValidator that checks the +// fingerprint of the certificate with the provided one. +type fingerprintValidator string + +func (s fingerprintValidator) Valid(cr *x509.CertificateRequest) error { + if s != "" { + expected, err := base64.RawURLEncoding.DecodeString(string(s)) + if err != nil { + return errs.ForbiddenErr(err, "error decoding fingerprint") + } + sum := sha256.Sum256(cr.Raw) + if subtle.ConstantTimeCompare(expected, sum[:]) != 1 { + return errs.Forbidden("certificate request fingerprint does not match %q", s) + } + } + return nil +} diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index ee74ded3..648b4672 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -2,6 +2,9 @@ package provisioner import ( "crypto/rsa" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" "encoding/binary" "encoding/json" "fmt" @@ -44,6 +47,13 @@ type SSHCertOptionsValidator interface { Valid(got SignSSHOptions) error } +// SSHPublicKeyValidator is the interface used to validate the public key of an +// SSH certificate. +type SSHPublicKeyValidator interface { + SignOption + Valid(got ssh.PublicKey) error +} + // SignSSHOptions contains the options that can be passed to the SignSSH method. type SignSSHOptions struct { CertType string `json:"certType"` @@ -419,6 +429,24 @@ func (v *sshNamePolicyValidator) Valid(cert *ssh.Certificate, _ SignSSHOptions) } } +// sshFingerprintValidator is a SSHPublicKeyValidator that checks the +// fingerprint of the public key with the provided one. +type sshFingerprintValidator string + +func (s sshFingerprintValidator) Valid(key ssh.PublicKey) error { + if s != "" { + expected, err := base64.RawURLEncoding.DecodeString(string(s)) + if err != nil { + return errs.ForbiddenErr(err, "error decoding fingerprint") + } + sum := sha256.Sum256(key.Marshal()) + if subtle.ConstantTimeCompare(expected, sum[:]) != 1 { + return errs.Forbidden("ssh public key fingerprint does not match %q", s) + } + } + return nil +} + // sshCertTypeUInt32 func sshCertTypeUInt32(ct string) uint32 { switch ct { diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index 6ad71459..8d165e92 100644 --- a/authority/provisioner/ssh_test.go +++ b/authority/provisioner/ssh_test.go @@ -51,6 +51,7 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si var mods []SSHCertModifier var certOptions []sshutil.Option var validators []SSHCertValidator + var keyValidators []SSHPublicKeyValidator for _, op := range signOpts { switch o := op.(type) { @@ -71,11 +72,19 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si } // call webhooks case *WebhookController: + case sshFingerprintValidator: + keyValidators = append(keyValidators, o) default: return nil, fmt.Errorf("signSSH: invalid extra option type %T", o) } } + for _, v := range keyValidators { + if err := v.Valid(pub); err != nil { + return nil, err + } + } + // Simulated certificate request with request options. cr := sshutil.CertificateRequest{ Type: opts.CertType, diff --git a/authority/provisioner/utils_test.go b/authority/provisioner/utils_test.go index a599a835..18538d0c 100644 --- a/authority/provisioner/utils_test.go +++ b/authority/provisioner/utils_test.go @@ -765,6 +765,37 @@ func generateToken(sub, iss, aud, email string, sans []string, iat time.Time, jw return jose.Signed(sig).Claims(claims).CompactSerialize() } +func generateCustomToken(sub, iss, aud string, jwk *jose.JSONWebKey, extraHeaders, extraClaims map[string]any) (string, error) { + so := new(jose.SignerOptions) + so.WithType("JWT") + so.WithHeader("kid", jwk.KeyID) + + for k, v := range extraHeaders { + so.WithHeader(jose.HeaderKey(k), v) + } + + sig, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.ES256, Key: jwk.Key}, so) + if err != nil { + return "", err + } + + id, err := randutil.ASCII(64) + if err != nil { + return "", err + } + iat := time.Now() + claims := jose.Claims{ + ID: id, + Subject: sub, + Issuer: iss, + IssuedAt: jose.NewNumericDate(iat), + NotBefore: jose.NewNumericDate(iat), + Expiry: jose.NewNumericDate(iat.Add(5 * time.Minute)), + Audience: []string{aud}, + } + return jose.Signed(sig).Claims(claims).Claims(extraClaims).CompactSerialize() +} + func generateOIDCToken(sub, iss, aud, email, preferredUsername string, iat time.Time, jwk *jose.JSONWebKey, tokOpts ...tokOption) (string, error) { so := new(jose.SignerOptions) so.WithType("JWT") diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index 9b1f2b08..a1f6a497 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -21,9 +21,10 @@ import ( // x5cPayload extends jwt.Claims with step attributes. type x5cPayload struct { jose.Claims - SANs []string `json:"sans,omitempty"` - Step *stepPayload `json:"step,omitempty"` - chains [][]*x509.Certificate + SANs []string `json:"sans,omitempty"` + Step *stepPayload `json:"step,omitempty"` + Confirmation *cnfPayload `json:"cnf,omitempty"` + chains [][]*x509.Certificate } // X5C is the default provisioner, an entity that can sign tokens necessary for @@ -233,6 +234,12 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er } } + // Check the fingerprint of the certificate request if given. + var fingerprint string + if claims.Confirmation != nil { + fingerprint = claims.Confirmation.Kid + } + return []SignOption{ self, templateOptions, @@ -243,6 +250,7 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er x5cLeaf.NotBefore, x5cLeaf.NotAfter, }, // validators + fingerprintValidator(fingerprint), commonNameValidator(claims.Subject), newDefaultSANsValidator(ctx, claims.SANs), defaultPublicKeyValidator{}, @@ -285,6 +293,11 @@ func (p *X5C) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}), } + // Check the fingerprint of the certificate request if given. + if claims.Confirmation != nil && claims.Confirmation.Kid != "" { + signOptions = append(signOptions, sshFingerprintValidator(claims.Confirmation.Kid)) + } + // Default template attributes. certType := sshutil.UserCert keyID := claims.Subject diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 22545446..eb3946dd 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -3,6 +3,7 @@ package provisioner import ( "context" "crypto/x509" + "encoding/base64" "errors" "fmt" "net/http" @@ -413,11 +414,12 @@ func TestX5C_AuthorizeSign(t *testing.T) { assert.FatalError(t, err) type test struct { - p *X5C - token string - code int - err error - sans []string + p *X5C + token string + code int + err error + sans []string + fingerprint string } tests := map[string]func(*testing.T) test{ "fail/invalid-token": func(t *testing.T) test { @@ -456,13 +458,36 @@ func TestX5C_AuthorizeSign(t *testing.T) { sans: []string{"127.0.0.1", "foo", "max@smallstep.com"}, } }, + "ok/cnf": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + + x5c := make([]string, len(certs)) + for i, cert := range certs { + x5c[i] = base64.StdEncoding.EncodeToString(cert.Raw) + } + extraHeaders := map[string]any{"x5c": x5c} + extraClaims := map[string]any{ + "sans": []string{"127.0.0.1", "foo", "max@smallstep.com"}, + "cnf": map[string]any{"kid": "fingerprint"}, + } + + tok, err := generateCustomToken("foo", p.GetName(), testAudiences.Sign[0], jwk, extraHeaders, extraClaims) + assert.FatalError(t, err) + return test{ + p: p, + token: tok, + sans: []string{"127.0.0.1", "foo", "max@smallstep.com"}, + fingerprint: "fingerprint", + } + }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { tc := tt(t) ctx := NewContextWithMethod(context.Background(), SignIdentityMethod) if opts, err := tc.p.AuthorizeSign(ctx, tc.token); err != nil { - if assert.NotNil(t, tc.err) { + if assert.NotNil(t, tc.err, err.Error()) { var sc render.StatusCodedError if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { assert.Equals(t, sc.StatusCode(), tc.code) @@ -472,7 +497,7 @@ func TestX5C_AuthorizeSign(t *testing.T) { } else { if assert.Nil(t, tc.err) { if assert.NotNil(t, opts) { - assert.Equals(t, 10, len(opts)) + assert.Equals(t, 11, len(opts)) for _, o := range opts { switch v := o.(type) { case *X5C: @@ -502,6 +527,8 @@ func TestX5C_AuthorizeSign(t *testing.T) { assert.Len(t, 0, v.webhooks) assert.Equals(t, linkedca.Webhook_X509, v.certType) assert.Len(t, 2, v.options) + case fingerprintValidator: + assert.Equals(t, tc.fingerprint, string(v)) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } @@ -627,11 +654,13 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { _, fn := mockNow() defer fn() type test struct { - p *X5C - token string - claims *x5cPayload - code int - err error + p *X5C + token string + claims *x5cPayload + fingerprint string + count int + code int + err error } tests := map[string]func(*testing.T) test{ "fail/sshCA-disabled": func(t *testing.T) test { @@ -719,7 +748,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { Audience: []string{testAudiences.SSHSign[0]}, }, Step: &stepPayload{SSH: &SignSSHOptions{ - CertType: SSHHostCert, + CertType: SSHUserCert, KeyID: "foo", Principals: []string{"max", "mariano", "alan"}, ValidAfter: TimeDuration{d: 5 * time.Minute}, @@ -732,6 +761,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { p: p, claims: claims, token: tok, + count: 12, } }, "ok/without-claims": func(t *testing.T) test { @@ -759,6 +789,42 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { p: p, claims: claims, token: tok, + count: 10, + } + }, + "ok/cnf": func(t *testing.T) test { + p, err := generateX5C(nil) + assert.FatalError(t, err) + + id, err := randutil.ASCII(64) + assert.FatalError(t, err) + now := time.Now() + claims := &x5cPayload{ + Claims: jose.Claims{ + ID: id, + Subject: "foo", + Issuer: p.GetName(), + IssuedAt: jose.NewNumericDate(now), + NotBefore: jose.NewNumericDate(now), + Expiry: jose.NewNumericDate(now.Add(5 * time.Minute)), + Audience: []string{testAudiences.SSHSign[0]}, + }, + Step: &stepPayload{SSH: &SignSSHOptions{ + CertType: SSHHostCert, + Principals: []string{"host.smallstep.com"}, + }}, + Confirmation: &cnfPayload{ + Kid: "fingerprint", + }, + } + tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts)) + assert.FatalError(t, err) + return test{ + p: p, + claims: claims, + token: tok, + fingerprint: "fingerprint", + count: 11, } }, } @@ -808,16 +874,14 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { assert.Len(t, 0, v.webhooks) assert.Equals(t, linkedca.Webhook_SSH, v.certType) assert.Len(t, 2, v.options) + case sshFingerprintValidator: + assert.Equals(t, tc.fingerprint, string(v)) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } tot++ } - if len(tc.claims.Step.SSH.CertType) > 0 { - assert.Equals(t, tot, 12) - } else { - assert.Equals(t, tot, 10) - } + assert.Equals(t, tc.count, tot) } } } diff --git a/authority/ssh.go b/authority/ssh.go index f9371d60..868dd013 100644 --- a/authority/ssh.go +++ b/authority/ssh.go @@ -148,12 +148,16 @@ func (a *Authority) GetSSHBastion(ctx context.Context, user, hostname string) (* // SignSSH creates a signed SSH certificate with the given public key and options. func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provisioner.SignSSHOptions, signOpts ...provisioner.SignOption) (*ssh.Certificate, error) { var ( - certOptions []sshutil.Option - mods []provisioner.SSHCertModifier - validators []provisioner.SSHCertValidator + certOptions []sshutil.Option + mods []provisioner.SSHCertModifier + validators []provisioner.SSHCertValidator + keyValidators []provisioner.SSHPublicKeyValidator ) - // Validate given options. + // Validate given key and options + if key == nil { + return nil, errs.BadRequest("ssh public key cannot be nil") + } if err := opts.Validate(); err != nil { return nil, err } @@ -177,6 +181,10 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision case provisioner.SSHCertModifier: mods = append(mods, o) + // validate the ssh public key + case provisioner.SSHPublicKeyValidator: + keyValidators = append(keyValidators, o) + // validate the ssh.Certificate case provisioner.SSHCertValidator: validators = append(validators, o) @@ -196,6 +204,16 @@ func (a *Authority) SignSSH(_ context.Context, key ssh.PublicKey, opts provision } } + // Validate public key + for _, v := range keyValidators { + if err := v.Valid(key); err != nil { + return nil, errs.ApplyOptions( + errs.ForbiddenErr(err, err.Error()), + errs.WithKeyVal("signOptions", signOpts), + ) + } + } + // Simulated certificate request with request options. cr := sshutil.CertificateRequest{ Type: opts.CertType, diff --git a/authority/tls_test.go b/authority/tls_test.go index efcb78f8..f0192fea 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -18,6 +18,7 @@ import ( "testing" "time" + "go.step.sm/crypto/fingerprint" "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/minica" @@ -593,6 +594,43 @@ ZYtQ9Ot36qc= code: http.StatusForbidden, } }, + "fail with cnf": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + + auth := testAuthority(t) + auth.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template + auth.db = &db.MockAuthDB{ + MUseToken: func(id, tok string) (bool, error) { + return true, nil + }, + MStoreCertificate: func(crt *x509.Certificate) error { + assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equals(t, crt.DNSNames, []string{"test.smallstep.com"}) + return nil + }, + } + + // Create a token with cnf + tok, err := generateCustomToken("smallstep test", "step-cli", testAudiences.Sign[0], key, nil, map[string]any{ + "sans": []string{"test.smallstep.com"}, + "cnf": map[string]any{"kid": "bad-fingerprint"}, + }) + assert.FatalError(t, err) + + opts, err := auth.Authorize(ctx, tok) + assert.FatalError(t, err) + + return &signTest{ + auth: auth, + csr: csr, + extraOpts: opts, + signOpts: signOpts, + notBefore: signOpts.NotBefore.Time().Truncate(time.Second), + notAfter: signOpts.NotAfter.Time().Truncate(time.Second), + err: errors.New(`certificate request fingerprint does not match "bad-fingerprint"`), + code: http.StatusForbidden, + } + }, "ok": func(t *testing.T) *signTest { csr := getCSR(t, priv) _a := testAuthority(t) @@ -840,6 +878,44 @@ ZYtQ9Ot36qc= extensionsCount: 6, } }, + "ok with cnf": func(t *testing.T) *signTest { + csr := getCSR(t, priv) + fingerprint, err := fingerprint.New(csr.Raw, crypto.SHA256, fingerprint.Base64RawURLFingerprint) + assert.FatalError(t, err) + + auth := testAuthority(t) + auth.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template + auth.db = &db.MockAuthDB{ + MUseToken: func(id, tok string) (bool, error) { + return true, nil + }, + MStoreCertificate: func(crt *x509.Certificate) error { + assert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equals(t, crt.DNSNames, []string{"test.smallstep.com"}) + return nil + }, + } + + // Create a token with cnf + tok, err := generateCustomToken("smallstep test", "step-cli", testAudiences.Sign[0], key, nil, map[string]any{ + "sans": []string{"test.smallstep.com"}, + "cnf": map[string]any{"kid": fingerprint}, + }) + assert.FatalError(t, err) + + opts, err := auth.Authorize(ctx, tok) + assert.FatalError(t, err) + + return &signTest{ + auth: auth, + csr: csr, + extraOpts: opts, + signOpts: signOpts, + notBefore: signOpts.NotBefore.Time().Truncate(time.Second), + notAfter: signOpts.NotAfter.Time().Truncate(time.Second), + extensionsCount: 6, + } + }, } for name, genTestCase := range tests { From a7a3a4c5d9152a5e1ab6a1cdbf5590a43b730e4b Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 12 Jun 2024 15:42:27 -0700 Subject: [PATCH 2/6] Fix comments from code review --- authority/provisioner/sign_options.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 62017fad..997a175d 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -497,7 +497,7 @@ func (o *provisionerExtensionOption) Modify(cert *x509.Certificate, _ SignOption } // fingerprintValidator is a CertificateRequestValidator that checks the -// fingerprint of the certificate with the provided one. +// fingerprint of the certificate request with the provided one. type fingerprintValidator string func (s fingerprintValidator) Valid(cr *x509.CertificateRequest) error { From 6c6ed46fefcbbeaca18434cb640afd6ec726ba29 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 23 Jul 2024 11:48:46 -0700 Subject: [PATCH 3/6] Remove sshFingerprintValidator and rename fingerprintValidator --- authority/provisioner/gcp.go | 4 ++-- authority/provisioner/jwk.go | 7 +------ authority/provisioner/jwk_test.go | 4 ++-- authority/provisioner/sign_options.go | 6 +++--- authority/provisioner/sign_ssh_options.go | 21 --------------------- authority/provisioner/ssh_test.go | 9 --------- authority/provisioner/x5c.go | 7 +------ authority/provisioner/x5c_test.go | 6 ++---- 8 files changed, 11 insertions(+), 53 deletions(-) diff --git a/authority/provisioner/gcp.go b/authority/provisioner/gcp.go index 8f6211d3..43763e2f 100644 --- a/authority/provisioner/gcp.go +++ b/authority/provisioner/gcp.go @@ -493,8 +493,8 @@ func (p *GCP) genHostOptions(_ context.Context, claims *gcpPayload) (SignSSHOpti return SignSSHOptions{CertType: SSHHostCert}, keyID, principals, sshutil.HostCert, sshutil.DefaultIIDTemplate } -func FormatServiceAccountUsername(serviceAccountId string) string { - return fmt.Sprintf("sa_%v", serviceAccountId) +func FormatServiceAccountUsername(serviceAccountID string) string { + return fmt.Sprintf("sa_%v", serviceAccountID) } func (p *GCP) genUserOptions(_ context.Context, claims *gcpPayload) (SignSSHOptions, string, []string, sshutil.CertType, string) { diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 5105a881..7371fa8d 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -201,7 +201,7 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er newProvisionerExtensionOption(TypeJWK, p.Name, p.Key.KeyID).WithControllerOptions(p.ctl), profileDefaultDuration(p.ctl.Claimer.DefaultTLSCertDuration()), // validators - fingerprintValidator(fingerprint), + csrFingerprintValidator(fingerprint), commonNameSliceValidator(append([]string{claims.Subject}, claims.SANs...)), defaultPublicKeyValidator{}, newDefaultSANsValidator(ctx, claims.SANs), @@ -241,11 +241,6 @@ func (p *JWK) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}), } - // Check the fingerprint of the certificate request if given. - if claims.Confirmation != nil && claims.Confirmation.Kid != "" { - signOptions = append(signOptions, sshFingerprintValidator(claims.Confirmation.Kid)) - } - // Default template attributes. certType := sshutil.UserCert keyID := claims.Subject diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 2471130a..2aa1969b 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -336,7 +336,7 @@ func TestJWK_AuthorizeSign(t *testing.T) { case *x509NamePolicyValidator: assert.Equals(t, nil, v.policyEngine) case *WebhookController: - case fingerprintValidator: + case csrFingerprintValidator: assert.Equals(t, tt.fingerprint, string(v)) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) @@ -491,10 +491,10 @@ func TestJWK_AuthorizeSSHSign(t *testing.T) { {"host-principals", p1, args{t2, SignSSHOptions{Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"host-options", p1, args{t2, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"host-cnf", p1, args{t3, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, + {"ignore-bad-cnf", p1, args{t4, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusOK, false, false}, {"fail-sshCA-disabled", p2, args{"foo", SignSSHOptions{}, pub}, expectedUserOptions, http.StatusUnauthorized, true, false}, {"fail-signature", p1, args{failSig, SignSSHOptions{}, pub}, nil, http.StatusUnauthorized, true, false}, {"fail-rsa1024", p1, args{t1, SignSSHOptions{}, rsa1024.Public()}, expectedUserOptions, http.StatusOK, false, true}, - {"fail-cnf", p1, args{t4, SignSSHOptions{CertType: "host", Principals: []string{"smallstep.com"}}, pub}, expectedHostOptions, http.StatusUnauthorized, false, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/authority/provisioner/sign_options.go b/authority/provisioner/sign_options.go index 69708630..b7cf0dbc 100644 --- a/authority/provisioner/sign_options.go +++ b/authority/provisioner/sign_options.go @@ -507,11 +507,11 @@ func (o *provisionerExtensionOption) Modify(cert *x509.Certificate, _ SignOption return nil } -// fingerprintValidator is a CertificateRequestValidator that checks the +// csrFingerprintValidator is a CertificateRequestValidator that checks the // fingerprint of the certificate request with the provided one. -type fingerprintValidator string +type csrFingerprintValidator string -func (s fingerprintValidator) Valid(cr *x509.CertificateRequest) error { +func (s csrFingerprintValidator) Valid(cr *x509.CertificateRequest) error { if s != "" { expected, err := base64.RawURLEncoding.DecodeString(string(s)) if err != nil { diff --git a/authority/provisioner/sign_ssh_options.go b/authority/provisioner/sign_ssh_options.go index 623ea6fb..512a8f0e 100644 --- a/authority/provisioner/sign_ssh_options.go +++ b/authority/provisioner/sign_ssh_options.go @@ -2,9 +2,6 @@ package provisioner import ( "crypto/rsa" - "crypto/sha256" - "crypto/subtle" - "encoding/base64" "encoding/binary" "encoding/json" "fmt" @@ -429,24 +426,6 @@ func (v *sshNamePolicyValidator) Valid(cert *ssh.Certificate, _ SignSSHOptions) } } -// sshFingerprintValidator is a SSHPublicKeyValidator that checks the -// fingerprint of the public key with the provided one. -type sshFingerprintValidator string - -func (s sshFingerprintValidator) Valid(key ssh.PublicKey) error { - if s != "" { - expected, err := base64.RawURLEncoding.DecodeString(string(s)) - if err != nil { - return errs.ForbiddenErr(err, "error decoding fingerprint") - } - sum := sha256.Sum256(key.Marshal()) - if subtle.ConstantTimeCompare(expected, sum[:]) != 1 { - return errs.Forbidden("ssh public key fingerprint does not match %q", s) - } - } - return nil -} - // sshCertTypeUInt32 func sshCertTypeUInt32(ct string) uint32 { switch ct { diff --git a/authority/provisioner/ssh_test.go b/authority/provisioner/ssh_test.go index 8d165e92..6ad71459 100644 --- a/authority/provisioner/ssh_test.go +++ b/authority/provisioner/ssh_test.go @@ -51,7 +51,6 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si var mods []SSHCertModifier var certOptions []sshutil.Option var validators []SSHCertValidator - var keyValidators []SSHPublicKeyValidator for _, op := range signOpts { switch o := op.(type) { @@ -72,19 +71,11 @@ func signSSHCertificate(key crypto.PublicKey, opts SignSSHOptions, signOpts []Si } // call webhooks case *WebhookController: - case sshFingerprintValidator: - keyValidators = append(keyValidators, o) default: return nil, fmt.Errorf("signSSH: invalid extra option type %T", o) } } - for _, v := range keyValidators { - if err := v.Valid(pub); err != nil { - return nil, err - } - } - // Simulated certificate request with request options. cr := sshutil.CertificateRequest{ Type: opts.CertType, diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index a1f6a497..e426e4c6 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -250,7 +250,7 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er x5cLeaf.NotBefore, x5cLeaf.NotAfter, }, // validators - fingerprintValidator(fingerprint), + csrFingerprintValidator(fingerprint), commonNameValidator(claims.Subject), newDefaultSANsValidator(ctx, claims.SANs), defaultPublicKeyValidator{}, @@ -293,11 +293,6 @@ func (p *X5C) AuthorizeSSHSign(_ context.Context, token string) ([]SignOption, e sshCertOptionsValidator(SignSSHOptions{KeyID: claims.Subject}), } - // Check the fingerprint of the certificate request if given. - if claims.Confirmation != nil && claims.Confirmation.Kid != "" { - signOptions = append(signOptions, sshFingerprintValidator(claims.Confirmation.Kid)) - } - // Default template attributes. certType := sshutil.UserCert keyID := claims.Subject diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index eb3946dd..2b1046bc 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -527,7 +527,7 @@ func TestX5C_AuthorizeSign(t *testing.T) { assert.Len(t, 0, v.webhooks) assert.Equals(t, linkedca.Webhook_X509, v.certType) assert.Len(t, 2, v.options) - case fingerprintValidator: + case csrFingerprintValidator: assert.Equals(t, tc.fingerprint, string(v)) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) @@ -824,7 +824,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { claims: claims, token: tok, fingerprint: "fingerprint", - count: 11, + count: 10, } }, } @@ -874,8 +874,6 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { assert.Len(t, 0, v.webhooks) assert.Equals(t, linkedca.Webhook_SSH, v.certType) assert.Len(t, 2, v.options) - case sshFingerprintValidator: - assert.Equals(t, tc.fingerprint, string(v)) default: assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) } From 656a03e5d1b128829f440ece5646553dcf6804e4 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 23 Jul 2024 12:51:11 -0700 Subject: [PATCH 4/6] Use x5rt#S256 claim instead of kid --- authority/provisioner/jwk.go | 4 ++-- authority/provisioner/jwk_test.go | 2 +- authority/provisioner/x5c.go | 2 +- authority/provisioner/x5c_test.go | 4 ++-- authority/tls_test.go | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/authority/provisioner/jwk.go b/authority/provisioner/jwk.go index 7371fa8d..ed481877 100644 --- a/authority/provisioner/jwk.go +++ b/authority/provisioner/jwk.go @@ -30,7 +30,7 @@ type stepPayload struct { } type cnfPayload struct { - Kid string `json:"kid,omitempty"` + Fingerprint string `json:"x5rt#S256,omitempty"` } // JWK is the default provisioner, an entity that can sign tokens necessary for @@ -191,7 +191,7 @@ func (p *JWK) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er // Check the fingerprint of the certificate request if given. var fingerprint string if claims.Confirmation != nil { - fingerprint = claims.Confirmation.Kid + fingerprint = claims.Confirmation.Fingerprint } return []SignOption{ diff --git a/authority/provisioner/jwk_test.go b/authority/provisioner/jwk_test.go index 2aa1969b..68fb7f47 100644 --- a/authority/provisioner/jwk_test.go +++ b/authority/provisioner/jwk_test.go @@ -249,7 +249,7 @@ func TestJWK_AuthorizeSign(t *testing.T) { t2, err := generateToken("subject", p1.Name, testAudiences.Sign[0], "name@smallstep.com", []string{}, time.Now(), key1) assert.FatalError(t, err) - t3, err := generateCustomToken("subject", p1.Name, testAudiences.Sign[0], key1, nil, map[string]any{"cnf": map[string]any{"kid": "fingerprint"}}) + t3, err := generateCustomToken("subject", p1.Name, testAudiences.Sign[0], key1, nil, map[string]any{"cnf": map[string]any{"x5rt#S256": "fingerprint"}}) assert.FatalError(t, err) // invalid signature diff --git a/authority/provisioner/x5c.go b/authority/provisioner/x5c.go index e426e4c6..fd77fe75 100644 --- a/authority/provisioner/x5c.go +++ b/authority/provisioner/x5c.go @@ -237,7 +237,7 @@ func (p *X5C) AuthorizeSign(ctx context.Context, token string) ([]SignOption, er // Check the fingerprint of the certificate request if given. var fingerprint string if claims.Confirmation != nil { - fingerprint = claims.Confirmation.Kid + fingerprint = claims.Confirmation.Fingerprint } return []SignOption{ diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 2b1046bc..8f9c0adc 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -469,7 +469,7 @@ func TestX5C_AuthorizeSign(t *testing.T) { extraHeaders := map[string]any{"x5c": x5c} extraClaims := map[string]any{ "sans": []string{"127.0.0.1", "foo", "max@smallstep.com"}, - "cnf": map[string]any{"kid": "fingerprint"}, + "cnf": map[string]any{"x5rt#S256": "fingerprint"}, } tok, err := generateCustomToken("foo", p.GetName(), testAudiences.Sign[0], jwk, extraHeaders, extraClaims) @@ -814,7 +814,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { Principals: []string{"host.smallstep.com"}, }}, Confirmation: &cnfPayload{ - Kid: "fingerprint", + Fingerprint: "fingerprint", }, } tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts)) diff --git a/authority/tls_test.go b/authority/tls_test.go index e949c685..c7bd6f10 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -621,7 +621,7 @@ ZYtQ9Ot36qc= // Create a token with cnf tok, err := generateCustomToken("smallstep test", "step-cli", testAudiences.Sign[0], key, nil, map[string]any{ "sans": []string{"test.smallstep.com"}, - "cnf": map[string]any{"kid": "bad-fingerprint"}, + "cnf": map[string]any{"x5rt#S256": "bad-fingerprint"}, }) require.NoError(t, err) @@ -907,7 +907,7 @@ ZYtQ9Ot36qc= // Create a token with cnf tok, err := generateCustomToken("smallstep test", "step-cli", testAudiences.Sign[0], key, nil, map[string]any{ "sans": []string{"test.smallstep.com"}, - "cnf": map[string]any{"kid": fingerprint}, + "cnf": map[string]any{"x5rt#S256": fingerprint}, }) require.NoError(t, err) From ad70982cdad1f81f1a748ddeaa257af01167ec8d Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 24 Jul 2024 12:13:57 -0700 Subject: [PATCH 5/6] Use testify packages in x5c_test.go --- authority/provisioner/x5c_test.go | 235 +++++++++++++++--------------- 1 file changed, 121 insertions(+), 114 deletions(-) diff --git a/authority/provisioner/x5c_test.go b/authority/provisioner/x5c_test.go index 8f9c0adc..99d10b68 100644 --- a/authority/provisioner/x5c_test.go +++ b/authority/provisioner/x5c_test.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net/http" + "strings" "testing" "time" @@ -15,13 +16,19 @@ import ( "go.step.sm/crypto/randutil" "go.step.sm/linkedca" - "github.com/smallstep/assert" "github.com/smallstep/certificates/api/render" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func assertHasPrefix(t *testing.T, s, p string) bool { + t.Helper() + return assert.True(t, strings.HasPrefix(s, p), "%q is not a prefix of %q", p, s) +} + func TestX5C_Getters(t *testing.T) { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) id := "x5c/" + p.Name if got := p.GetID(); got != id { t.Errorf("X5C.GetID() = %v, want %v:%v", got, p.Name, id) @@ -80,7 +87,7 @@ func TestX5C_Init(t *testing.T) { }, "fail/invalid-duration": func(t *testing.T) ProvisionerValidateTest { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) p.Claims = &Claims{DefaultTLSDur: &Duration{0}} return ProvisionerValidateTest{ p: p, @@ -89,7 +96,7 @@ func TestX5C_Init(t *testing.T) { }, "ok": func(t *testing.T) ProvisionerValidateTest { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) return ProvisionerValidateTest{ p: p, } @@ -118,7 +125,7 @@ VR0RBA0wC4IJcm9vdC10ZXN0MAoGCCqGSM49BAMCA0kAMEYCIQC2vgqwla0u8LHH 1MHob14qvS5o76HautbIBW7fcHzz5gIhAIx5A2+wkJYX4026kqaZCk/1sAwTxSGY M46l92gdOozT -----END CERTIFICATE-----`)) - assert.FatalError(t, err) + require.NoError(t, err) return ProvisionerValidateTest{ p: p, extraValid: func(p *X5C) error { @@ -144,11 +151,11 @@ M46l92gdOozT err := tc.p.Init(config) if err != nil { if assert.NotNil(t, tc.err) { - assert.Equals(t, tc.err.Error(), err.Error()) + assert.EqualError(t, tc.err, err.Error()) } } else { if assert.Nil(t, tc.err) { - assert.Equals(t, *tc.p.ctl.Audiences, config.Audiences.WithFragment(tc.p.GetID())) + assert.Equal(t, *tc.p.ctl.Audiences, config.Audiences.WithFragment(tc.p.GetID())) if tc.extraValid != nil { assert.Nil(t, tc.extraValid(tc.p)) } @@ -160,9 +167,9 @@ M46l92gdOozT func TestX5C_authorizeToken(t *testing.T) { x5cCerts, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt") - assert.FatalError(t, err) + require.NoError(t, err) x5cJWK, err := jose.ReadKey("./testdata/secrets/x5c-leaf.key") - assert.FatalError(t, err) + require.NoError(t, err) type test struct { p *X5C @@ -173,7 +180,7 @@ func TestX5C_authorizeToken(t *testing.T) { tests := map[string]func(*testing.T) test{ "fail/bad-token": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: "foo", @@ -193,15 +200,15 @@ DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFNLJ P9K7MAoGCCqGSM49BAMCA0gAMEUCIQC5c1ldDcesDb31GlO5cEJvOcRrIrNtkk8m a5wpg+9s6QIgHIW6L60F8klQX+EO3o0SBqLeNcaskA4oSZsKjEdpSGo= -----END CERTIFICATE-----`)) - assert.FatalError(t, err) + require.NoError(t, err) jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) + require.NoError(t, err) p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("", p.Name, testAudiences.Sign[0], "", []string{"test.smallstep.com"}, time.Now(), jwk, withX5CHdr(certs)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -232,15 +239,15 @@ BgNVHREECTAHggVsZWFmMjAKBggqhkjOPQQDAgNIADBFAiB7gMRy3t81HpcnoRAS ELZmDFaEnoLCsVfbmanFykazQQIhAI0sZjoE9t6gvzQp7XQp6CoxzCc3Jv3FwZ8G EXAHTA9L -----END CERTIFICATE-----`)) - assert.FatalError(t, err) + require.NoError(t, err) jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) + require.NoError(t, err) p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("", p.Name, testAudiences.Sign[0], "", []string{"test.smallstep.com"}, time.Now(), jwk, withX5CHdr(certs)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -273,16 +280,16 @@ E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1 2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC lgsqsR63is+0YQ== -----END CERTIFICATE-----`)) - assert.FatalError(t, err) + require.NoError(t, err) jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) + require.NoError(t, err) p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("", p.Name, testAudiences.Sign[0], "", []string{"test.smallstep.com"}, time.Now(), jwk, withX5CHdr(certs)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -315,15 +322,15 @@ E4IRaW50ZXJtZWRpYXRlLXRlc3QwCgYIKoZIzj0EAwIDSAAwRQIgII8XpQ8ezDO1 2xdq3hShf155C5X/5jO8qr0VyEJgzlkCIQCTqph1Gwu/dmuf6dYLCfQqJyb371LC lgsqsR63is+0YQ== -----END CERTIFICATE-----`)) - assert.FatalError(t, err) + require.NoError(t, err) jwk, err := jose.GenerateJWK("EC", "P-256", "ES256", "sig", "", 0) - assert.FatalError(t, err) + require.NoError(t, err) p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("", "foobar", testAudiences.Sign[0], "", []string{"test.smallstep.com"}, time.Now(), jwk, withX5CHdr(certs)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -333,11 +340,11 @@ lgsqsR63is+0YQ== }, "fail/invalid-issuer": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("", "foobar", testAudiences.Sign[0], "", []string{"test.smallstep.com"}, time.Now(), x5cJWK, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -347,11 +354,11 @@ lgsqsR63is+0YQ== }, "fail/invalid-audience": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("", p.GetName(), "foobar", "", []string{"test.smallstep.com"}, time.Now(), x5cJWK, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -361,11 +368,11 @@ lgsqsR63is+0YQ== }, "fail/empty-subject": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("", p.GetName(), testAudiences.Sign[0], "", []string{"test.smallstep.com"}, time.Now(), x5cJWK, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -375,11 +382,11 @@ lgsqsR63is+0YQ== }, "ok": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "", []string{"test.smallstep.com"}, time.Now(), x5cJWK, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -393,12 +400,12 @@ lgsqsR63is+0YQ== if assert.NotNil(t, tc.err) { var sc render.StatusCodedError if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) } - assert.HasPrefix(t, err.Error(), tc.err.Error()) + assertHasPrefix(t, err.Error(), tc.err.Error()) } } else { - if assert.Nil(t, tc.err) { + if assert.NoError(t, tc.err) { assert.NotNil(t, claims) assert.NotNil(t, claims.chains) } @@ -409,9 +416,9 @@ lgsqsR63is+0YQ== func TestX5C_AuthorizeSign(t *testing.T) { certs, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt") - assert.FatalError(t, err) + require.NoError(t, err) jwk, err := jose.ReadKey("./testdata/secrets/x5c-leaf.key") - assert.FatalError(t, err) + require.NoError(t, err) type test struct { p *X5C @@ -424,7 +431,7 @@ func TestX5C_AuthorizeSign(t *testing.T) { tests := map[string]func(*testing.T) test{ "fail/invalid-token": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: "foo", @@ -434,11 +441,11 @@ func TestX5C_AuthorizeSign(t *testing.T) { }, "ok/empty-sans": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "", []string{}, time.Now(), jwk, withX5CHdr(certs)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -447,11 +454,11 @@ func TestX5C_AuthorizeSign(t *testing.T) { }, "ok/multi-sans": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("foo", p.GetName(), testAudiences.Sign[0], "", []string{"127.0.0.1", "foo", "max@smallstep.com"}, time.Now(), jwk, withX5CHdr(certs)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -460,7 +467,7 @@ func TestX5C_AuthorizeSign(t *testing.T) { }, "ok/cnf": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) x5c := make([]string, len(certs)) for i, cert := range certs { @@ -473,7 +480,7 @@ func TestX5C_AuthorizeSign(t *testing.T) { } tok, err := generateCustomToken("foo", p.GetName(), testAudiences.Sign[0], jwk, extraHeaders, extraClaims) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -490,47 +497,47 @@ func TestX5C_AuthorizeSign(t *testing.T) { if assert.NotNil(t, tc.err, err.Error()) { var sc render.StatusCodedError if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) } - assert.HasPrefix(t, err.Error(), tc.err.Error()) + assertHasPrefix(t, err.Error(), tc.err.Error()) } } else { if assert.Nil(t, tc.err) { if assert.NotNil(t, opts) { - assert.Equals(t, 11, len(opts)) + assert.Len(t, opts, 11) for _, o := range opts { switch v := o.(type) { case *X5C: case certificateOptionsFunc: case *provisionerExtensionOption: - assert.Equals(t, v.Type, TypeX5C) - assert.Equals(t, v.Name, tc.p.GetName()) - assert.Equals(t, v.CredentialID, "") - assert.Len(t, 0, v.KeyValuePairs) + assert.Equal(t, TypeX5C, v.Type) + assert.Equal(t, tc.p.GetName(), v.Name) + assert.Equal(t, "", v.CredentialID) + assert.Len(t, v.KeyValuePairs, 0) case profileLimitDuration: - assert.Equals(t, v.def, tc.p.ctl.Claimer.DefaultTLSCertDuration()) + assert.Equal(t, tc.p.ctl.Claimer.DefaultTLSCertDuration(), v.def) claims, err := tc.p.authorizeToken(tc.token, tc.p.ctl.Audiences.Sign) - assert.FatalError(t, err) - assert.Equals(t, v.notAfter, claims.chains[0][0].NotAfter) + require.NoError(t, err) + assert.Equal(t, claims.chains[0][0].NotAfter, v.notAfter) case commonNameValidator: - assert.Equals(t, string(v), "foo") + assert.Equal(t, "foo", string(v)) case defaultPublicKeyValidator: case *defaultSANsValidator: - assert.Equals(t, v.sans, tc.sans) - assert.Equals(t, MethodFromContext(v.ctx), SignIdentityMethod) + assert.Equal(t, tc.sans, v.sans) + assert.Equal(t, SignIdentityMethod, MethodFromContext(v.ctx)) case *validityValidator: - assert.Equals(t, v.min, tc.p.ctl.Claimer.MinTLSCertDuration()) - assert.Equals(t, v.max, tc.p.ctl.Claimer.MaxTLSCertDuration()) + assert.Equal(t, tc.p.ctl.Claimer.MinTLSCertDuration(), v.min) + assert.Equal(t, tc.p.ctl.Claimer.MaxTLSCertDuration(), v.max) case *x509NamePolicyValidator: - assert.Equals(t, nil, v.policyEngine) + assert.Equal(t, nil, v.policyEngine) case *WebhookController: - assert.Len(t, 0, v.webhooks) - assert.Equals(t, linkedca.Webhook_X509, v.certType) - assert.Len(t, 2, v.options) + assert.Len(t, v.webhooks, 0) + assert.Equal(t, linkedca.Webhook_X509, v.certType) + assert.Len(t, v.options, 2) case csrFingerprintValidator: - assert.Equals(t, tc.fingerprint, string(v)) + assert.Equal(t, tc.fingerprint, string(v)) default: - assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) + require.NoError(t, fmt.Errorf("unexpected sign option of type %T", v)) } } } @@ -550,7 +557,7 @@ func TestX5C_AuthorizeRevoke(t *testing.T) { tests := map[string]func(*testing.T) test{ "fail/invalid-token": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: "foo", @@ -560,16 +567,16 @@ func TestX5C_AuthorizeRevoke(t *testing.T) { }, "ok": func(t *testing.T) test { certs, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt") - assert.FatalError(t, err) + require.NoError(t, err) jwk, err := jose.ReadKey("./testdata/secrets/x5c-leaf.key") - assert.FatalError(t, err) + require.NoError(t, err) p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("foo", p.GetName(), testAudiences.Revoke[0], "", []string{"test.smallstep.com"}, time.Now(), jwk, withX5CHdr(certs)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -583,9 +590,9 @@ func TestX5C_AuthorizeRevoke(t *testing.T) { if assert.NotNil(t, tc.err) { var sc render.StatusCodedError if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) } - assert.HasPrefix(t, err.Error(), tc.err.Error()) + assertHasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.Nil(t, tc.err) @@ -604,12 +611,12 @@ func TestX5C_AuthorizeRenew(t *testing.T) { tests := map[string]func(*testing.T) test{ "fail/renew-disabled": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) // disable renewal disable := true p.Claims = &Claims{DisableRenewal: &disable} p.ctl.Claimer, err = NewClaimer(p.Claims, globalProvisionerClaims) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, code: http.StatusUnauthorized, @@ -618,7 +625,7 @@ func TestX5C_AuthorizeRenew(t *testing.T) { }, "ok": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, } @@ -634,9 +641,9 @@ func TestX5C_AuthorizeRenew(t *testing.T) { if assert.NotNil(t, tc.err) { var sc render.StatusCodedError if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) } - assert.HasPrefix(t, err.Error(), tc.err.Error()) + assertHasPrefix(t, err.Error(), tc.err.Error()) } } else { assert.Nil(t, tc.err) @@ -647,9 +654,9 @@ func TestX5C_AuthorizeRenew(t *testing.T) { func TestX5C_AuthorizeSSHSign(t *testing.T) { x5cCerts, err := pemutil.ReadCertificateBundle("./testdata/certs/x5c-leaf.crt") - assert.FatalError(t, err) + require.NoError(t, err) x5cJWK, err := jose.ReadKey("./testdata/secrets/x5c-leaf.key") - assert.FatalError(t, err) + require.NoError(t, err) _, fn := mockNow() defer fn() @@ -665,12 +672,12 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { tests := map[string]func(*testing.T) test{ "fail/sshCA-disabled": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) // disable sshCA enable := false p.Claims = &Claims{EnableSSHCA: &enable} p.ctl.Claimer, err = NewClaimer(p.Claims, globalProvisionerClaims) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: "foo", @@ -680,7 +687,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { }, "fail/invalid-token": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: "foo", @@ -690,11 +697,11 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { }, "fail/no-Step-claim": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) tok, err := generateToken("foo", p.GetName(), testAudiences.SSHSign[0], "", []string{"test.smallstep.com"}, time.Now(), x5cJWK, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -704,10 +711,10 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { }, "fail/no-SSH-subattribute-in-claims": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) id, err := randutil.ASCII(64) - assert.FatalError(t, err) + require.NoError(t, err) now := time.Now() claims := &x5cPayload{ Claims: jose.Claims{ @@ -722,7 +729,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { Step: &stepPayload{}, } tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, token: tok, @@ -732,10 +739,10 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { }, "ok/with-claims": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) id, err := randutil.ASCII(64) - assert.FatalError(t, err) + require.NoError(t, err) now := time.Now() claims := &x5cPayload{ Claims: jose.Claims{ @@ -756,7 +763,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { }}, } tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, claims: claims, @@ -766,10 +773,10 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { }, "ok/without-claims": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) id, err := randutil.ASCII(64) - assert.FatalError(t, err) + require.NoError(t, err) now := time.Now() claims := &x5cPayload{ Claims: jose.Claims{ @@ -784,7 +791,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { Step: &stepPayload{SSH: &SignSSHOptions{}}, } tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, claims: claims, @@ -794,10 +801,10 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { }, "ok/cnf": func(t *testing.T) test { p, err := generateX5C(nil) - assert.FatalError(t, err) + require.NoError(t, err) id, err := randutil.ASCII(64) - assert.FatalError(t, err) + require.NoError(t, err) now := time.Now() claims := &x5cPayload{ Claims: jose.Claims{ @@ -818,7 +825,7 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { }, } tok, err := generateX5CSSHToken(x5cJWK, claims, withX5CHdr(x5cCerts)) - assert.FatalError(t, err) + require.NoError(t, err) return test{ p: p, claims: claims, @@ -835,9 +842,9 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { if assert.NotNil(t, tc.err) { var sc render.StatusCodedError if assert.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") { - assert.Equals(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) } - assert.HasPrefix(t, err.Error(), tc.err.Error()) + assertHasPrefix(t, err.Error(), tc.err.Error()) } } else { if assert.Nil(t, tc.err) { @@ -852,34 +859,34 @@ func TestX5C_AuthorizeSSHSign(t *testing.T) { tc.claims.Step.SSH.ValidAfter.t = time.Time{} tc.claims.Step.SSH.ValidBefore.t = time.Time{} if firstValidator { - assert.Equals(t, SignSSHOptions(v), *tc.claims.Step.SSH) + assert.Equal(t, *tc.claims.Step.SSH, SignSSHOptions(v)) } else { - assert.Equals(t, SignSSHOptions(v), SignSSHOptions{KeyID: tc.claims.Subject}) + assert.Equal(t, SignSSHOptions{KeyID: tc.claims.Subject}, SignSSHOptions(v)) } firstValidator = false case sshCertValidAfterModifier: - assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix()) + assert.Equal(t, tc.claims.Step.SSH.ValidAfter.RelativeTime(nw).Unix(), int64(v)) case sshCertValidBeforeModifier: - assert.Equals(t, int64(v), tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix()) + assert.Equal(t, tc.claims.Step.SSH.ValidBefore.RelativeTime(nw).Unix(), int64(v)) case *sshLimitDuration: - assert.Equals(t, v.Claimer, tc.p.ctl.Claimer) - assert.Equals(t, v.NotAfter, x5cCerts[0].NotAfter) + assert.Equal(t, tc.p.ctl.Claimer, v.Claimer) + assert.Equal(t, x5cCerts[0].NotAfter, v.NotAfter) case *sshCertValidityValidator: - assert.Equals(t, v.Claimer, tc.p.ctl.Claimer) + assert.Equal(t, tc.p.ctl.Claimer, v.Claimer) case *sshNamePolicyValidator: - assert.Equals(t, nil, v.userPolicyEngine) - assert.Equals(t, nil, v.hostPolicyEngine) + assert.Nil(t, v.userPolicyEngine) + assert.Nil(t, v.hostPolicyEngine) case *sshDefaultPublicKeyValidator, *sshCertDefaultValidator, sshCertificateOptionsFunc: case *WebhookController: - assert.Len(t, 0, v.webhooks) - assert.Equals(t, linkedca.Webhook_SSH, v.certType) - assert.Len(t, 2, v.options) + assert.Len(t, v.webhooks, 0) + assert.Equal(t, linkedca.Webhook_SSH, v.certType) + assert.Len(t, v.options, 2) default: - assert.FatalError(t, fmt.Errorf("unexpected sign option of type %T", v)) + require.NoError(t, fmt.Errorf("unexpected sign option of type %T", v)) } tot++ } - assert.Equals(t, tc.count, tot) + assert.Equal(t, tc.count, tot) } } } From 683f2dfff3d27a9bcd96c9bf6830a4cd2057b428 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 24 Jul 2024 12:24:03 -0700 Subject: [PATCH 6/6] Fix unit test --- ca/ca_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ca/ca_test.go b/ca/ca_test.go index a8c173c4..30a3fbbb 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -625,7 +625,7 @@ func TestCARenew(t *testing.T) { cert, err := x509util.NewCertificate(cr) assert.FatalError(t, err) crt := cert.GetCertificate() - crt.NotBefore = time.Now() + crt.NotBefore = now crt.NotAfter = leafExpiry crt, err = x509util.CreateCertificate(crt, intermediateCert, pub, intermediateKey.(crypto.Signer)) assert.FatalError(t, err)