From 9e8087fbb15cf3be083db24cc70da9b486d4167f Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Wed, 15 May 2024 17:41:33 -0700 Subject: [PATCH 1/3] Add GetX509Signer method This commit adds a method to the Authority type that returns the signer used to sign X509 certificates. --- authority/tls.go | 15 +++ authority/tls_test.go | 250 +++++++++++++++++++++--------------- cas/apiv1/services.go | 10 +- cas/softcas/softcas.go | 7 + cas/softcas/softcas_test.go | 42 ++++++ 5 files changed, 221 insertions(+), 103 deletions(-) diff --git a/authority/tls.go b/authority/tls.go index ebc9d0d8..679c28ac 100644 --- a/authority/tls.go +++ b/authority/tls.go @@ -91,6 +91,21 @@ func withDefaultASN1DN(def *config.ASN1DN) provisioner.CertificateModifierFunc { } } +// GetX509Signer returns a [crypto.Signer] implementation using the intermediate +// key. +// +// This method can return a [NotImplementedError] if the CA is configured with a +// Certificate Authority Service (CAS) that does not implement the +// CertificateAuthoritySigner interface. +// +// [NotImplementedError]: https://pkg.go.dev/github.com/smallstep/certificates/cas/apiv1#NotImplementedError +func (a *Authority) GetX509Signer() (crypto.Signer, error) { + if s, ok := a.x509CAService.(casapi.CertificateAuthoritySigner); ok { + return s.GetSigner() + } + return nil, casapi.NotImplementedError{} +} + // Sign creates a signed certificate from a certificate signing request. It // creates a new context.Context, and calls into SignWithContext. // diff --git a/authority/tls_test.go b/authority/tls_test.go index b481ca68..93b75ec9 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -15,6 +15,7 @@ import ( "fmt" "net/http" "reflect" + "strings" "testing" "time" @@ -24,11 +25,11 @@ import ( "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" - sassert "github.com/smallstep/assert" "github.com/smallstep/certificates/api/render" "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/policy" "github.com/smallstep/certificates/authority/provisioner" + "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/softcas" "github.com/smallstep/certificates/db" "github.com/smallstep/certificates/errs" @@ -223,6 +224,15 @@ func generateSubjectKeyID(pub crypto.PublicKey) ([]byte, error) { return hash[:], nil } +func assertHasPrefix(t *testing.T, s, p string) bool { + if strings.HasPrefix(s, p) { + return true + } + t.Helper() + t.Errorf("'%s' is not a prefix of '%s'", p, s) + return false +} + type basicConstraints struct { IsCA bool `asn1:"optional"` MaxPathLen int `asn1:"optional,default:-1"` @@ -418,7 +428,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -447,7 +457,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -476,7 +486,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -494,7 +504,7 @@ ZYtQ9Ot36qc= aa := testAuthority(t) aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -519,7 +529,7 @@ ZYtQ9Ot36qc= })) aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -539,7 +549,7 @@ ZYtQ9Ot36qc= aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { fmt.Println(crt.Subject) - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -600,7 +610,7 @@ ZYtQ9Ot36qc= _a := testAuthority(t) _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -634,7 +644,7 @@ ZYtQ9Ot36qc= _a := testAuthority(t) _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -668,7 +678,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -702,7 +712,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -739,7 +749,7 @@ ZYtQ9Ot36qc= _a.config.AuthorityConfig.Template = &ASN1DN{} _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject, pkix.Name{}) + assert.Equal(t, crt.Subject, pkix.Name{}) return nil }, } @@ -764,8 +774,8 @@ ZYtQ9Ot36qc= aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") - sassert.Equals(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"}) + assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"}) return nil }, } @@ -785,7 +795,7 @@ ZYtQ9Ot36qc= aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - sassert.Equals(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, crt.Subject.CommonName, "smallstep test") return nil }, } @@ -818,13 +828,13 @@ ZYtQ9Ot36qc= MStoreCertificateChain: func(prov provisioner.Interface, certs ...*x509.Certificate) error { p, ok := prov.(attProvisioner) if assert.True(t, ok) { - sassert.Equals(t, &provisioner.AttestationData{ + assert.Equal(t, &provisioner.AttestationData{ PermanentIdentifier: "1234567890", }, p.AttestationData()) } if assert.Len(t, certs, 2) { - sassert.Equals(t, certs[0].Subject.CommonName, "smallstep test") - sassert.Equals(t, certs[1].Subject.CommonName, "smallstep Intermediate CA") + assert.Equal(t, certs[0].Subject.CommonName, "smallstep test") + assert.Equal(t, certs[1].Subject.CommonName, "smallstep Intermediate CA") } return nil }, @@ -853,26 +863,26 @@ ZYtQ9Ot36qc= if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) var sc render.StatusCodedError - sassert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - sassert.Equals(t, sc.StatusCode(), tc.code) - sassert.HasPrefix(t, err.Error(), tc.err.Error()) + require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") + assert.Equal(t, sc.StatusCode(), tc.code) + assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error - sassert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - sassert.Equals(t, ctxErr.Details["csr"], tc.csr) - sassert.Equals(t, ctxErr.Details["signOptions"], tc.signOpts) + require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") + assert.Equal(t, ctxErr.Details["csr"], tc.csr) + assert.Equal(t, ctxErr.Details["signOptions"], tc.signOpts) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - sassert.Equals(t, leaf.NotBefore, tc.notBefore) - sassert.Equals(t, leaf.NotAfter, tc.notAfter) + assert.Equal(t, leaf.NotBefore, tc.notBefore) + assert.Equal(t, leaf.NotAfter, tc.notAfter) tmplt := a.config.AuthorityConfig.Template if tc.csr.Subject.CommonName == "" { - sassert.Equals(t, leaf.Subject, pkix.Name{}) + assert.Equal(t, leaf.Subject, pkix.Name{}) } else { - sassert.Equals(t, leaf.Subject.String(), + assert.Equal(t, leaf.Subject.String(), pkix.Name{ Country: []string{tmplt.Country}, Organization: []string{tmplt.Organization}, @@ -881,18 +891,18 @@ ZYtQ9Ot36qc= Province: []string{tmplt.Province}, CommonName: "smallstep test", }.String()) - sassert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com"}) + assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com"}) } - sassert.Equals(t, leaf.Issuer, intermediate.Subject) - sassert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - sassert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - sassert.Equals(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) + assert.Equal(t, leaf.Issuer, intermediate.Subject) + assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) + assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) + assert.Equal(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) issuer := getDefaultIssuer(a) subjectKeyID, err := generateSubjectKeyID(pub) require.NoError(t, err) - sassert.Equals(t, leaf.SubjectKeyId, subjectKeyID) - sassert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) + assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) // Verify Provisioner OID found := 0 @@ -903,9 +913,9 @@ ZYtQ9Ot36qc= val := stepProvisionerASN1{} _, err := asn1.Unmarshal(ext.Value, &val) require.NoError(t, err) - sassert.Equals(t, val.Type, provisionerTypeJWK) - sassert.Equals(t, val.Name, []byte(p.Name)) - sassert.Equals(t, val.CredentialID, []byte(p.Key.KeyID)) + assert.Equal(t, val.Type, provisionerTypeJWK) + assert.Equal(t, val.Name, []byte(p.Name)) + assert.Equal(t, val.CredentialID, []byte(p.Key.KeyID)) // Basic Constraints case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 19})): @@ -913,7 +923,7 @@ ZYtQ9Ot36qc= _, err := asn1.Unmarshal(ext.Value, &val) require.NoError(t, err) assert.False(t, val.IsCA, false) - sassert.Equals(t, val.MaxPathLen, 0) + assert.Equal(t, val.MaxPathLen, 0) // SAN extension case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 17})): @@ -924,10 +934,10 @@ ZYtQ9Ot36qc= } } } - sassert.Equals(t, found, 1) + assert.Equal(t, found, 1) realIntermediate, err := x509.ParseCertificate(issuer.Raw) require.NoError(t, err) - sassert.Equals(t, intermediate, realIntermediate) + assert.Equal(t, intermediate, realIntermediate) assert.Len(t, leaf.Extensions, tc.extensionsCount) } } @@ -1070,19 +1080,19 @@ func TestAuthority_Renew(t *testing.T) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) var sc render.StatusCodedError - sassert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - sassert.Equals(t, sc.StatusCode(), tc.code) - sassert.HasPrefix(t, err.Error(), tc.err.Error()) + require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") + assert.Equal(t, sc.StatusCode(), tc.code) + assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error - sassert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - sassert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) + require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") + assert.Equal(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - sassert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) + assert.Equal(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) @@ -1092,30 +1102,30 @@ func TestAuthority_Renew(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - sassert.Equals(t, leaf.RawSubject, tc.cert.RawSubject) - sassert.Equals(t, leaf.Subject.Country, []string{tmplt.Country}) - sassert.Equals(t, leaf.Subject.Organization, []string{tmplt.Organization}) - sassert.Equals(t, leaf.Subject.Locality, []string{tmplt.Locality}) - sassert.Equals(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress}) - sassert.Equals(t, leaf.Subject.Province, []string{tmplt.Province}) - sassert.Equals(t, leaf.Subject.CommonName, tmplt.CommonName) - - sassert.Equals(t, leaf.Issuer, intermediate.Subject) - - sassert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - sassert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - sassert.Equals(t, leaf.ExtKeyUsage, + assert.Equal(t, leaf.RawSubject, tc.cert.RawSubject) + assert.Equal(t, leaf.Subject.Country, []string{tmplt.Country}) + assert.Equal(t, leaf.Subject.Organization, []string{tmplt.Organization}) + assert.Equal(t, leaf.Subject.Locality, []string{tmplt.Locality}) + assert.Equal(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress}) + assert.Equal(t, leaf.Subject.Province, []string{tmplt.Province}) + assert.Equal(t, leaf.Subject.CommonName, tmplt.CommonName) + + assert.Equal(t, leaf.Issuer, intermediate.Subject) + + assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) + assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) + assert.Equal(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) - sassert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) + assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey) require.NoError(t, err) - sassert.Equals(t, leaf.SubjectKeyId, subjectKeyID) + assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) // We did not change the intermediate before renewing. authIssuer := getDefaultIssuer(tc.auth) if issuer.SerialNumber == authIssuer.SerialNumber { - sassert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1135,7 +1145,7 @@ func TestAuthority_Renew(t *testing.T) { } } else { // We did change the intermediate before renewing. - sassert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) + assert.Equal(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1164,7 +1174,7 @@ func TestAuthority_Renew(t *testing.T) { realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) require.NoError(t, err) - sassert.Equals(t, intermediate, realIntermediate) + assert.Equal(t, intermediate, realIntermediate) } } }) @@ -1275,19 +1285,19 @@ func TestAuthority_Rekey(t *testing.T) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { assert.Nil(t, certChain) var sc render.StatusCodedError - sassert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - sassert.Equals(t, sc.StatusCode(), tc.code) - sassert.HasPrefix(t, err.Error(), tc.err.Error()) + require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") + assert.Equal(t, sc.StatusCode(), tc.code) + assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error - sassert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - sassert.Equals(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) + require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") + assert.Equal(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - sassert.Equals(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) + assert.Equal(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) @@ -1297,7 +1307,7 @@ func TestAuthority_Rekey(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - sassert.Equals(t, leaf.Subject.String(), + assert.Equal(t, leaf.Subject.String(), pkix.Name{ Country: []string{tmplt.Country}, Organization: []string{tmplt.Organization}, @@ -1306,32 +1316,32 @@ func TestAuthority_Rekey(t *testing.T) { Province: []string{tmplt.Province}, CommonName: tmplt.CommonName, }.String()) - sassert.Equals(t, leaf.Issuer, intermediate.Subject) + assert.Equal(t, leaf.Issuer, intermediate.Subject) - sassert.Equals(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - sassert.Equals(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - sassert.Equals(t, leaf.ExtKeyUsage, + assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) + assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) + assert.Equal(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) - sassert.Equals(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) + assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) // Test Public Key and SubjectKeyId expectedPK := tc.pk if tc.pk == nil { expectedPK = cert.PublicKey } - sassert.Equals(t, leaf.PublicKey, expectedPK) + assert.Equal(t, leaf.PublicKey, expectedPK) subjectKeyID, err := generateSubjectKeyID(expectedPK) require.NoError(t, err) - sassert.Equals(t, leaf.SubjectKeyId, subjectKeyID) + assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) if tc.pk == nil { - sassert.Equals(t, leaf.SubjectKeyId, cert.SubjectKeyId) + assert.Equal(t, leaf.SubjectKeyId, cert.SubjectKeyId) } // We did not change the intermediate before renewing. authIssuer := getDefaultIssuer(tc.auth) if issuer.SerialNumber == authIssuer.SerialNumber { - sassert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1351,7 +1361,7 @@ func TestAuthority_Rekey(t *testing.T) { } } else { // We did change the intermediate before renewing. - sassert.Equals(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) + assert.Equal(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1380,7 +1390,7 @@ func TestAuthority_Rekey(t *testing.T) { realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) require.NoError(t, err) - sassert.Equals(t, intermediate, realIntermediate) + assert.Equal(t, intermediate, realIntermediate) } } }) @@ -1418,7 +1428,7 @@ func TestAuthority_GetTLSOptions(t *testing.T) { require.NoError(t, err) opts := tc.auth.GetTLSOptions() - sassert.Equals(t, opts, tc.opts) + assert.Equal(t, opts, tc.opts) }) } } @@ -1488,9 +1498,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("authority.Revoke; no persistence layer configured"), code: http.StatusNotImplemented, checkErrDetails: func(err *errs.Error) { - sassert.Equals(t, err.Details["token"], raw) - sassert.Equals(t, err.Details["tokenID"], "44") - sassert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, err.Details["token"], raw) + assert.Equal(t, err.Details["tokenID"], "44") + assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") }, } }, @@ -1528,9 +1538,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("authority.Revoke: force"), code: http.StatusInternalServerError, checkErrDetails: func(err *errs.Error) { - sassert.Equals(t, err.Details["token"], raw) - sassert.Equals(t, err.Details["tokenID"], "44") - sassert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, err.Details["token"], raw) + assert.Equal(t, err.Details["tokenID"], "44") + assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") }, } }, @@ -1568,9 +1578,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("certificate with serial number 'sn' is already revoked"), code: http.StatusBadRequest, checkErrDetails: func(err *errs.Error) { - sassert.Equals(t, err.Details["token"], raw) - sassert.Equals(t, err.Details["tokenID"], "44") - sassert.Equals(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, err.Details["token"], raw) + assert.Equal(t, err.Details["tokenID"], "44") + assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") }, } }, @@ -1704,17 +1714,17 @@ func TestAuthority_Revoke(t *testing.T) { if err := tc.auth.Revoke(tc.ctx, tc.opts); err != nil { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { var sc render.StatusCodedError - sassert.Fatal(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - sassert.Equals(t, sc.StatusCode(), tc.code) - sassert.HasPrefix(t, err.Error(), tc.err.Error()) + require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") + assert.Equal(t, sc.StatusCode(), tc.code) + assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error - sassert.Fatal(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - sassert.Equals(t, ctxErr.Details["serialNumber"], tc.opts.Serial) - sassert.Equals(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode) - sassert.Equals(t, ctxErr.Details["reason"], tc.opts.Reason) - sassert.Equals(t, ctxErr.Details["MTLS"], tc.opts.MTLS) - sassert.Equals(t, ctxErr.Details["context"], provisioner.RevokeMethod.String()) + require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") + assert.Equal(t, ctxErr.Details["serialNumber"], tc.opts.Serial) + assert.Equal(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode) + assert.Equal(t, ctxErr.Details["reason"], tc.opts.Reason) + assert.Equal(t, ctxErr.Details["MTLS"], tc.opts.MTLS) + assert.Equal(t, ctxErr.Details["context"], provisioner.RevokeMethod.String()) if tc.checkErrDetails != nil { tc.checkErrDetails(ctxErr) @@ -1952,3 +1962,39 @@ func TestAuthority_CRL(t *testing.T) { }) } } + +type notImplementedCAS struct{} + +func (notImplementedCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { + return nil, apiv1.NotImplementedError{} +} +func (notImplementedCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { + return nil, apiv1.NotImplementedError{} +} +func (notImplementedCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { + return nil, apiv1.NotImplementedError{} +} + +func TestAuthority_GetX509Signer(t *testing.T) { + auth := testAuthority(t) + require.IsType(t, &softcas.SoftCAS{}, auth.x509CAService) + signer := auth.x509CAService.(*softcas.SoftCAS).Signer + require.NotNil(t, signer) + + tests := []struct { + name string + authority *Authority + want crypto.Signer + assertion assert.ErrorAssertionFunc + }{ + {"ok", auth, signer, assert.NoError}, + {"fail", testAuthority(t, WithX509CAService(notImplementedCAS{})), nil, assert.Error}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.authority.GetX509Signer() + tt.assertion(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/cas/apiv1/services.go b/cas/apiv1/services.go index 00ecc2a8..37c5adba 100644 --- a/cas/apiv1/services.go +++ b/cas/apiv1/services.go @@ -1,6 +1,7 @@ package apiv1 import ( + "crypto" "crypto/x509" "net/http" "strings" @@ -26,13 +27,20 @@ type CertificateAuthorityGetter interface { GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error) } -// CertificateAuthorityCreator is an interface implamented by a +// CertificateAuthorityCreator is an interface implemented by a // CertificateAuthorityService that has a method to create a new certificate // authority. type CertificateAuthorityCreator interface { CreateCertificateAuthority(req *CreateCertificateAuthorityRequest) (*CreateCertificateAuthorityResponse, error) } +// CertificateAuthoritySigner is an optional interface implemented by a +// CertificateAuthorityService that has a method that returns a [crypto.Signer] +// using the same key used to issue certificates. +type CertificateAuthoritySigner interface { + GetSigner() (crypto.Signer, error) +} + // SignatureAlgorithmGetter is an optional implementation in a crypto.Signer // that returns the SignatureAlgorithm to use. type SignatureAlgorithmGetter interface { diff --git a/cas/softcas/softcas.go b/cas/softcas/softcas.go index dd961975..1e590eff 100644 --- a/cas/softcas/softcas.go +++ b/cas/softcas/softcas.go @@ -58,6 +58,13 @@ func (c *SoftCAS) Type() apiv1.Type { return apiv1.SoftCAS } +// GetSigner implements [apiv1.CertificateAuthoritySigner] and returns a +// [crypto.Signer] with the intermediate key. +func (c *SoftCAS) GetSigner() (crypto.Signer, error) { + _, signer, err := c.getCertSigner() + return signer, err +} + // CreateCertificate signs a new certificate using Golang or KMS crypto. func (c *SoftCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { switch { diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 8c04de3a..1c20d277 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -19,8 +19,11 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.step.sm/crypto/kms" kmsapi "go.step.sm/crypto/kms/apiv1" + "go.step.sm/crypto/minica" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" ) @@ -269,6 +272,45 @@ func TestSoftCAS_Type(t *testing.T) { } } +func TestSoftCAS_GetSigner(t *testing.T) { + ca, err := minica.New() + require.NoError(t, err) + + type fields struct { + CertificateChain []*x509.Certificate + Signer crypto.Signer + CertificateSigner func() ([]*x509.Certificate, crypto.Signer, error) + KeyManager kms.KeyManager + } + tests := []struct { + name string + fields fields + want crypto.Signer + assertion assert.ErrorAssertionFunc + }{ + {"ok signer", fields{[]*x509.Certificate{ca.Intermediate}, ca.Signer, nil, nil}, ca.Signer, assert.NoError}, + {"ok certificateSigner", fields{[]*x509.Certificate{ca.Intermediate}, nil, func() ([]*x509.Certificate, crypto.Signer, error) { + return []*x509.Certificate{ca.Intermediate}, ca.Signer, nil + }, nil}, ca.Signer, assert.NoError}, + {"fail certificateSigner", fields{[]*x509.Certificate{ca.Intermediate}, nil, func() ([]*x509.Certificate, crypto.Signer, error) { + return nil, nil, apiv1.NotImplementedError{} + }, nil}, nil, assert.Error}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SoftCAS{ + CertificateChain: tt.fields.CertificateChain, + Signer: tt.fields.Signer, + CertificateSigner: tt.fields.CertificateSigner, + KeyManager: tt.fields.KeyManager, + } + got, err := c.GetSigner() + tt.assertion(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + func TestSoftCAS_CreateCertificate(t *testing.T) { mockNow(t) // Set rand.Reader to EOF From d0548f9ec96326c1a3b3c6479b0faaecc3bc3af4 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 16 May 2024 13:52:37 -0700 Subject: [PATCH 2/3] Use %q instead of '%s' Co-authored-by: Herman Slatman --- authority/tls_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authority/tls_test.go b/authority/tls_test.go index 93b75ec9..2e682dca 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -229,7 +229,7 @@ func assertHasPrefix(t *testing.T, s, p string) bool { return true } t.Helper() - t.Errorf("'%s' is not a prefix of '%s'", p, s) + t.Errorf("%q is not a prefix of %q", p, s) return false } From 812ffd3c4016b2d15491fdcd95b3ed5672bea6e7 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 16 May 2024 14:11:34 -0700 Subject: [PATCH 3/3] Reverse assert statements --- authority/tls_test.go | 204 +++++++++++++++++++++--------------------- 1 file changed, 100 insertions(+), 104 deletions(-) diff --git a/authority/tls_test.go b/authority/tls_test.go index 2e682dca..4965fe46 100644 --- a/authority/tls_test.go +++ b/authority/tls_test.go @@ -428,7 +428,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -457,7 +457,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -486,7 +486,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -504,7 +504,7 @@ ZYtQ9Ot36qc= aa := testAuthority(t) aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -529,7 +529,7 @@ ZYtQ9Ot36qc= })) aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -549,7 +549,7 @@ ZYtQ9Ot36qc= aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { fmt.Println(crt.Subject) - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -610,7 +610,7 @@ ZYtQ9Ot36qc= _a := testAuthority(t) _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -644,7 +644,7 @@ ZYtQ9Ot36qc= _a := testAuthority(t) _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -678,7 +678,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -712,7 +712,7 @@ ZYtQ9Ot36qc= require.NoError(t, err) testAuthority.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") + assert.Equal(t, "smallstep test", crt.Subject.CommonName) return nil }, } @@ -749,7 +749,7 @@ ZYtQ9Ot36qc= _a.config.AuthorityConfig.Template = &ASN1DN{} _a.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject, pkix.Name{}) + assert.Equal(t, pkix.Name{}, crt.Subject) return nil }, } @@ -774,8 +774,8 @@ ZYtQ9Ot36qc= aa.config.AuthorityConfig.Template = a.config.AuthorityConfig.Template aa.db = &db.MockAuthDB{ MStoreCertificate: func(crt *x509.Certificate) error { - assert.Equal(t, crt.Subject.CommonName, "smallstep test") - assert.Equal(t, crt.CRLDistributionPoints, []string{"http://ca.example.org/leaf.crl"}) + assert.Equal(t, "smallstep test", crt.Subject.CommonName) + assert.Equal(t, []string{"http://ca.example.org/leaf.crl"}, crt.CRLDistributionPoints) return nil }, } @@ -833,8 +833,8 @@ ZYtQ9Ot36qc= }, p.AttestationData()) } if assert.Len(t, certs, 2) { - assert.Equal(t, certs[0].Subject.CommonName, "smallstep test") - assert.Equal(t, certs[1].Subject.CommonName, "smallstep Intermediate CA") + assert.Equal(t, "smallstep test", certs[0].Subject.CommonName) + assert.Equal(t, "smallstep Intermediate CA", certs[1].Subject.CommonName) } return nil }, @@ -864,45 +864,44 @@ ZYtQ9Ot36qc= assert.Nil(t, certChain) var sc render.StatusCodedError require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equal(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equal(t, ctxErr.Details["csr"], tc.csr) - assert.Equal(t, ctxErr.Details["signOptions"], tc.signOpts) + assert.Equal(t, tc.csr, ctxErr.Details["csr"]) + assert.Equal(t, tc.signOpts, ctxErr.Details["signOptions"]) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equal(t, leaf.NotBefore, tc.notBefore) - assert.Equal(t, leaf.NotAfter, tc.notAfter) + assert.Equal(t, tc.notBefore, leaf.NotBefore) + assert.Equal(t, tc.notAfter, leaf.NotAfter) tmplt := a.config.AuthorityConfig.Template if tc.csr.Subject.CommonName == "" { - assert.Equal(t, leaf.Subject, pkix.Name{}) + assert.Equal(t, pkix.Name{}, leaf.Subject) } else { - assert.Equal(t, leaf.Subject.String(), - pkix.Name{ - Country: []string{tmplt.Country}, - Organization: []string{tmplt.Organization}, - Locality: []string{tmplt.Locality}, - StreetAddress: []string{tmplt.StreetAddress}, - Province: []string{tmplt.Province}, - CommonName: "smallstep test", - }.String()) - assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com"}) + assert.Equal(t, pkix.Name{ + Country: []string{tmplt.Country}, + Organization: []string{tmplt.Organization}, + Locality: []string{tmplt.Locality}, + StreetAddress: []string{tmplt.StreetAddress}, + Province: []string{tmplt.Province}, + CommonName: "smallstep test", + }.String(), leaf.Subject.String()) + assert.Equal(t, []string{"test.smallstep.com"}, leaf.DNSNames) } - assert.Equal(t, leaf.Issuer, intermediate.Subject) - assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equal(t, leaf.ExtKeyUsage, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) + assert.Equal(t, intermediate.Subject, leaf.Issuer) + assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm) + assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm) + assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage) issuer := getDefaultIssuer(a) subjectKeyID, err := generateSubjectKeyID(pub) require.NoError(t, err) - assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) - assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, subjectKeyID, leaf.SubjectKeyId) + assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId) // Verify Provisioner OID found := 0 @@ -913,9 +912,9 @@ ZYtQ9Ot36qc= val := stepProvisionerASN1{} _, err := asn1.Unmarshal(ext.Value, &val) require.NoError(t, err) - assert.Equal(t, val.Type, provisionerTypeJWK) - assert.Equal(t, val.Name, []byte(p.Name)) - assert.Equal(t, val.CredentialID, []byte(p.Key.KeyID)) + assert.Equal(t, provisionerTypeJWK, val.Type) + assert.Equal(t, []byte(p.Name), val.Name) + assert.Equal(t, []byte(p.Key.KeyID), val.CredentialID) // Basic Constraints case ext.Id.Equal(asn1.ObjectIdentifier([]int{2, 5, 29, 19})): @@ -937,7 +936,7 @@ ZYtQ9Ot36qc= assert.Equal(t, found, 1) realIntermediate, err := x509.ParseCertificate(issuer.Raw) require.NoError(t, err) - assert.Equal(t, intermediate, realIntermediate) + assert.Equal(t, realIntermediate, intermediate) assert.Len(t, leaf.Extensions, tc.extensionsCount) } } @@ -1081,18 +1080,18 @@ func TestAuthority_Renew(t *testing.T) { assert.Nil(t, certChain) var sc render.StatusCodedError require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equal(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equal(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) + assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"]) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equal(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) + assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore)) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) @@ -1102,30 +1101,29 @@ func TestAuthority_Renew(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - assert.Equal(t, leaf.RawSubject, tc.cert.RawSubject) - assert.Equal(t, leaf.Subject.Country, []string{tmplt.Country}) - assert.Equal(t, leaf.Subject.Organization, []string{tmplt.Organization}) - assert.Equal(t, leaf.Subject.Locality, []string{tmplt.Locality}) - assert.Equal(t, leaf.Subject.StreetAddress, []string{tmplt.StreetAddress}) - assert.Equal(t, leaf.Subject.Province, []string{tmplt.Province}) - assert.Equal(t, leaf.Subject.CommonName, tmplt.CommonName) - - assert.Equal(t, leaf.Issuer, intermediate.Subject) - - assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equal(t, leaf.ExtKeyUsage, - []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) - assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) + assert.Equal(t, tc.cert.RawSubject, leaf.RawSubject) + assert.Equal(t, []string{tmplt.Country}, leaf.Subject.Country) + assert.Equal(t, []string{tmplt.Organization}, leaf.Subject.Organization) + assert.Equal(t, []string{tmplt.Locality}, leaf.Subject.Locality) + assert.Equal(t, []string{tmplt.StreetAddress}, leaf.Subject.StreetAddress) + assert.Equal(t, []string{tmplt.Province}, leaf.Subject.Province) + assert.Equal(t, tmplt.CommonName, leaf.Subject.CommonName) + + assert.Equal(t, intermediate.Subject, leaf.Issuer) + + assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm) + assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm) + assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage) + assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames) subjectKeyID, err := generateSubjectKeyID(leaf.PublicKey) require.NoError(t, err) - assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) + assert.Equal(t, subjectKeyID, leaf.SubjectKeyId) // We did not change the intermediate before renewing. authIssuer := getDefaultIssuer(tc.auth) if issuer.SerialNumber == authIssuer.SerialNumber { - assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1145,7 +1143,7 @@ func TestAuthority_Renew(t *testing.T) { } } else { // We did change the intermediate before renewing. - assert.Equal(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) + assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1174,7 +1172,7 @@ func TestAuthority_Renew(t *testing.T) { realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) require.NoError(t, err) - assert.Equal(t, intermediate, realIntermediate) + assert.Equal(t, realIntermediate, intermediate) } } }) @@ -1286,18 +1284,18 @@ func TestAuthority_Rekey(t *testing.T) { assert.Nil(t, certChain) var sc render.StatusCodedError require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equal(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equal(t, ctxErr.Details["serialNumber"], tc.cert.SerialNumber.String()) + assert.Equal(t, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"]) } } else { leaf := certChain[0] intermediate := certChain[1] if assert.Nil(t, tc.err) { - assert.Equal(t, leaf.NotAfter.Sub(leaf.NotBefore), tc.cert.NotAfter.Sub(cert.NotBefore)) + assert.Equal(t, tc.cert.NotAfter.Sub(cert.NotBefore), leaf.NotAfter.Sub(leaf.NotBefore)) assert.True(t, leaf.NotBefore.After(now.Add(-2*time.Minute))) assert.True(t, leaf.NotBefore.Before(now.Add(time.Minute))) @@ -1307,41 +1305,39 @@ func TestAuthority_Rekey(t *testing.T) { assert.True(t, leaf.NotAfter.Before(expiry.Add(time.Hour))) tmplt := a.config.AuthorityConfig.Template - assert.Equal(t, leaf.Subject.String(), - pkix.Name{ - Country: []string{tmplt.Country}, - Organization: []string{tmplt.Organization}, - Locality: []string{tmplt.Locality}, - StreetAddress: []string{tmplt.StreetAddress}, - Province: []string{tmplt.Province}, - CommonName: tmplt.CommonName, - }.String()) - assert.Equal(t, leaf.Issuer, intermediate.Subject) - - assert.Equal(t, leaf.SignatureAlgorithm, x509.ECDSAWithSHA256) - assert.Equal(t, leaf.PublicKeyAlgorithm, x509.ECDSA) - assert.Equal(t, leaf.ExtKeyUsage, - []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}) - assert.Equal(t, leaf.DNSNames, []string{"test.smallstep.com", "test"}) + assert.Equal(t, pkix.Name{ + Country: []string{tmplt.Country}, + Organization: []string{tmplt.Organization}, + Locality: []string{tmplt.Locality}, + StreetAddress: []string{tmplt.StreetAddress}, + Province: []string{tmplt.Province}, + CommonName: tmplt.CommonName, + }.String(), leaf.Subject.String()) + assert.Equal(t, intermediate.Subject, leaf.Issuer) + + assert.Equal(t, x509.ECDSAWithSHA256, leaf.SignatureAlgorithm) + assert.Equal(t, x509.ECDSA, leaf.PublicKeyAlgorithm) + assert.Equal(t, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, leaf.ExtKeyUsage) + assert.Equal(t, []string{"test.smallstep.com", "test"}, leaf.DNSNames) // Test Public Key and SubjectKeyId expectedPK := tc.pk if tc.pk == nil { expectedPK = cert.PublicKey } - assert.Equal(t, leaf.PublicKey, expectedPK) + assert.Equal(t, expectedPK, leaf.PublicKey) subjectKeyID, err := generateSubjectKeyID(expectedPK) require.NoError(t, err) - assert.Equal(t, leaf.SubjectKeyId, subjectKeyID) + assert.Equal(t, subjectKeyID, leaf.SubjectKeyId) if tc.pk == nil { - assert.Equal(t, leaf.SubjectKeyId, cert.SubjectKeyId) + assert.Equal(t, cert.SubjectKeyId, leaf.SubjectKeyId) } // We did not change the intermediate before renewing. authIssuer := getDefaultIssuer(tc.auth) if issuer.SerialNumber == authIssuer.SerialNumber { - assert.Equal(t, leaf.AuthorityKeyId, issuer.SubjectKeyId) + assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1361,7 +1357,7 @@ func TestAuthority_Rekey(t *testing.T) { } } else { // We did change the intermediate before renewing. - assert.Equal(t, leaf.AuthorityKeyId, authIssuer.SubjectKeyId) + assert.Equal(t, authIssuer.SubjectKeyId, leaf.AuthorityKeyId) // Compare extensions: they can be in a different order for _, ext1 := range tc.cert.Extensions { //skip SubjectKeyIdentifier @@ -1390,7 +1386,7 @@ func TestAuthority_Rekey(t *testing.T) { realIntermediate, err := x509.ParseCertificate(authIssuer.Raw) require.NoError(t, err) - assert.Equal(t, intermediate, realIntermediate) + assert.Equal(t, realIntermediate, intermediate) } } }) @@ -1428,7 +1424,7 @@ func TestAuthority_GetTLSOptions(t *testing.T) { require.NoError(t, err) opts := tc.auth.GetTLSOptions() - assert.Equal(t, opts, tc.opts) + assert.Equal(t, tc.opts, opts) }) } } @@ -1498,9 +1494,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("authority.Revoke; no persistence layer configured"), code: http.StatusNotImplemented, checkErrDetails: func(err *errs.Error) { - assert.Equal(t, err.Details["token"], raw) - assert.Equal(t, err.Details["tokenID"], "44") - assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, raw, err.Details["token"]) + assert.Equal(t, "44", err.Details["tokenID"]) + assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"]) }, } }, @@ -1538,9 +1534,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("authority.Revoke: force"), code: http.StatusInternalServerError, checkErrDetails: func(err *errs.Error) { - assert.Equal(t, err.Details["token"], raw) - assert.Equal(t, err.Details["tokenID"], "44") - assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, raw, err.Details["token"]) + assert.Equal(t, "44", err.Details["tokenID"]) + assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"]) }, } }, @@ -1578,9 +1574,9 @@ func TestAuthority_Revoke(t *testing.T) { err: errors.New("certificate with serial number 'sn' is already revoked"), code: http.StatusBadRequest, checkErrDetails: func(err *errs.Error) { - assert.Equal(t, err.Details["token"], raw) - assert.Equal(t, err.Details["tokenID"], "44") - assert.Equal(t, err.Details["provisionerID"], "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc") + assert.Equal(t, raw, err.Details["token"]) + assert.Equal(t, "44", err.Details["tokenID"]) + assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"]) }, } }, @@ -1715,16 +1711,16 @@ func TestAuthority_Revoke(t *testing.T) { if assert.NotNil(t, tc.err, fmt.Sprintf("unexpected error: %s", err)) { var sc render.StatusCodedError require.True(t, errors.As(err, &sc), "error does not implement StatusCodedError interface") - assert.Equal(t, sc.StatusCode(), tc.code) + assert.Equal(t, tc.code, sc.StatusCode()) assertHasPrefix(t, err.Error(), tc.err.Error()) var ctxErr *errs.Error require.True(t, errors.As(err, &ctxErr), "error is not of type *errs.Error") - assert.Equal(t, ctxErr.Details["serialNumber"], tc.opts.Serial) - assert.Equal(t, ctxErr.Details["reasonCode"], tc.opts.ReasonCode) - assert.Equal(t, ctxErr.Details["reason"], tc.opts.Reason) - assert.Equal(t, ctxErr.Details["MTLS"], tc.opts.MTLS) - assert.Equal(t, ctxErr.Details["context"], provisioner.RevokeMethod.String()) + assert.Equal(t, tc.opts.Serial, ctxErr.Details["serialNumber"]) + assert.Equal(t, tc.opts.ReasonCode, ctxErr.Details["reasonCode"]) + assert.Equal(t, tc.opts.Reason, ctxErr.Details["reason"]) + assert.Equal(t, tc.opts.MTLS, ctxErr.Details["MTLS"]) + assert.Equal(t, provisioner.RevokeMethod.String(), ctxErr.Details["context"]) if tc.checkErrDetails != nil { tc.checkErrDetails(ctxErr)