Merge pull request #1850 from smallstep/mariano/signer

Add GetX509Signer method
pull/1860/head
Mariano Cano 4 weeks ago committed by GitHub
commit 47b5048d82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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.
//

@ -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("%q is not a prefix of %q", 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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, "smallstep test", crt.Subject.CommonName)
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, pkix.Name{}, crt.Subject)
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, "smallstep test", crt.Subject.CommonName)
assert.Equal(t, []string{"http://ca.example.org/leaf.crl"}, crt.CRLDistributionPoints)
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, "smallstep test", certs[0].Subject.CommonName)
assert.Equal(t, "smallstep Intermediate CA", certs[1].Subject.CommonName)
}
return nil
},
@ -853,46 +863,45 @@ 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, tc.code, sc.StatusCode())
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, 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) {
sassert.Equals(t, leaf.NotBefore, tc.notBefore)
sassert.Equals(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 == "" {
sassert.Equals(t, leaf.Subject, pkix.Name{})
assert.Equal(t, pkix.Name{}, leaf.Subject)
} else {
sassert.Equals(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())
sassert.Equals(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)
}
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, 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)
sassert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
sassert.Equals(t, leaf.AuthorityKeyId, issuer.SubjectKeyId)
assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
assert.Equal(t, issuer.SubjectKeyId, leaf.AuthorityKeyId)
// Verify Provisioner OID
found := 0
@ -903,9 +912,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, 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})):
@ -913,7 +922,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 +933,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, realIntermediate, intermediate)
assert.Len(t, leaf.Extensions, tc.extensionsCount)
}
}
@ -1070,19 +1079,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, tc.code, sc.StatusCode())
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, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"])
}
} 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, 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)))
@ -1092,30 +1101,29 @@ 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,
[]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth})
sassert.Equals(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)
sassert.Equals(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 {
sassert.Equals(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
@ -1135,7 +1143,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, authIssuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1164,7 +1172,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, realIntermediate, intermediate)
}
}
})
@ -1275,19 +1283,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, tc.code, sc.StatusCode())
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, tc.cert.SerialNumber.String(), ctxErr.Details["serialNumber"])
}
} 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, 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)))
@ -1297,41 +1305,39 @@ 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(),
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())
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})
sassert.Equals(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
}
sassert.Equals(t, leaf.PublicKey, expectedPK)
assert.Equal(t, expectedPK, leaf.PublicKey)
subjectKeyID, err := generateSubjectKeyID(expectedPK)
require.NoError(t, err)
sassert.Equals(t, leaf.SubjectKeyId, subjectKeyID)
assert.Equal(t, subjectKeyID, leaf.SubjectKeyId)
if tc.pk == nil {
sassert.Equals(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 {
sassert.Equals(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
@ -1351,7 +1357,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, authIssuer.SubjectKeyId, leaf.AuthorityKeyId)
// Compare extensions: they can be in a different order
for _, ext1 := range tc.cert.Extensions {
//skip SubjectKeyIdentifier
@ -1380,7 +1386,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, realIntermediate, intermediate)
}
}
})
@ -1418,7 +1424,7 @@ func TestAuthority_GetTLSOptions(t *testing.T) {
require.NoError(t, err)
opts := tc.auth.GetTLSOptions()
sassert.Equals(t, opts, tc.opts)
assert.Equal(t, tc.opts, opts)
})
}
}
@ -1488,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) {
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, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
},
}
},
@ -1528,9 +1534,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, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
},
}
},
@ -1568,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) {
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, raw, err.Details["token"])
assert.Equal(t, "44", err.Details["tokenID"])
assert.Equal(t, "step-cli:4UELJx8e0aS9m0CH3fZ0EB7D5aUPICb759zALHFejvc", err.Details["provisionerID"])
},
}
},
@ -1704,17 +1710,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, tc.code, sc.StatusCode())
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, 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)
@ -1952,3 +1958,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)
})
}
}

@ -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 {

@ -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 {

@ -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

Loading…
Cancel
Save