From 2b4b902975eafd1b76d06350d9f9d7210907e2c5 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Fri, 23 Oct 2020 15:04:09 -0700 Subject: [PATCH 01/17] Add initial support for `step ca init` with cloud cas. Fixes smallstep/cli#363 --- cas/apiv1/options.go | 13 ++ cas/apiv1/requests.go | 77 ++++++++++ cas/apiv1/services.go | 7 + cas/cas.go | 36 ++++- cas/cloudcas/certificate.go | 44 ++++++ cas/cloudcas/cloudcas.go | 268 +++++++++++++++++++++++++++++++++- cas/cloudcas/cloudcas_test.go | 60 ++++++++ cas/softcas/softcas.go | 125 ++++++++++++++-- commands/onboard.go | 10 +- go.mod | 10 +- go.sum | 6 + pki/pki.go | 181 +++++++++++------------ 12 files changed, 720 insertions(+), 117 deletions(-) diff --git a/cas/apiv1/options.go b/cas/apiv1/options.go index 3ee1434c..e8072437 100644 --- a/cas/apiv1/options.go +++ b/cas/apiv1/options.go @@ -5,6 +5,7 @@ import ( "crypto/x509" "github.com/pkg/errors" + "github.com/smallstep/certificates/kms" ) // Options represents the configuration options used to select and configure the @@ -24,6 +25,18 @@ type Options struct { // They are configured in ca.json crt and key properties. Issuer *x509.Certificate `json:"-"` Signer crypto.Signer `json:"-"` + + // IsCreator is set to true when we're creating a certificate authority. Is + // used to skip some validations when initializing a CertificateAuthority. + IsCreator bool `json:"-"` + + // KeyManager is the KMS used to generate keys in SoftCAS. + KeyManager kms.KeyManager `json:"-"` + + // Project and Location are parameters used in CloudCAS to create a new + // certificate authority. + Project string `json:"-"` + Location string `json:"-"` } // Validate checks the fields in Options. diff --git a/cas/apiv1/requests.go b/cas/apiv1/requests.go index 2a233b8a..5305ff43 100644 --- a/cas/apiv1/requests.go +++ b/cas/apiv1/requests.go @@ -1,8 +1,53 @@ package apiv1 import ( + "crypto" "crypto/x509" "time" + + "github.com/smallstep/certificates/kms/apiv1" +) + +// CertificateAuthorityType indicates the type of Certificate Authority to +// create. +type CertificateAuthorityType int + +const ( + // RootCA is the type used to create a self-signed certificate suitable for + // use as a root CA. + RootCA CertificateAuthorityType = iota + 1 + + // IntermediateCA is the type used to create a subordinated certificate that + // can be used to sign additional leaf certificates. + IntermediateCA +) + +// SignatureAlgorithm used for cryptographic signing. +type SignatureAlgorithm int + +const ( + // Not specified. + UnspecifiedSignAlgorithm SignatureAlgorithm = iota + // RSASSA-PKCS1-v1_5 key and a SHA256 digest. + SHA256WithRSA + // RSASSA-PKCS1-v1_5 key and a SHA384 digest. + SHA384WithRSA + // RSASSA-PKCS1-v1_5 key and a SHA512 digest. + SHA512WithRSA + // RSASSA-PSS key with a SHA256 digest. + SHA256WithRSAPSS + // RSASSA-PSS key with a SHA384 digest. + SHA384WithRSAPSS + // RSASSA-PSS key with a SHA512 digest. + SHA512WithRSAPSS + // ECDSA on the NIST P-256 curve with a SHA256 digest. + ECDSAWithSHA256 + // ECDSA on the NIST P-384 curve with a SHA384 digest. + ECDSAWithSHA384 + // ECDSA on the NIST P-521 curve with a SHA512 digest. + ECDSAWithSHA512 + // EdDSA on Curve25519 with a SHA512 digest. + PureEd25519 ) // CreateCertificateRequest is the request used to sign a new certificate. @@ -58,3 +103,35 @@ type GetCertificateAuthorityRequest struct { type GetCertificateAuthorityResponse struct { RootCertificate *x509.Certificate } + +// CreateCertificateAuthorityRequest ... +// This is a work in progress +type CreateCertificateAuthorityRequest struct { + Name string + Type CertificateAuthorityType + Template *x509.Certificate + Lifetime time.Duration + Backdate time.Duration + RequestID string + Project string + Location string + + // Parent is the signer of the new CertificateAuthority. + Parent *CreateCertificateAuthorityResponse + + // CreateKey defines the KMS CreateKeyRequest to use when creating a new + // CertificateAuthority. If CreateKey is nil, a default algorithm will be + // used. + CreateKey *apiv1.CreateKeyRequest +} + +// CreateCertificateAuthorityResponse ... +// This is a work in progress +type CreateCertificateAuthorityResponse struct { + Name string + Certificate *x509.Certificate + CertificateChain []*x509.Certificate + PublicKey crypto.PublicKey + PrivateKey crypto.PrivateKey + Signer crypto.Signer +} diff --git a/cas/apiv1/services.go b/cas/apiv1/services.go index f41650d8..58a8f139 100644 --- a/cas/apiv1/services.go +++ b/cas/apiv1/services.go @@ -18,6 +18,13 @@ type CertificateAuthorityGetter interface { GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error) } +// CertificateAuthorityCreator is an interface implamented by a +// CertificateAuthorityService that has a method to create a new certificate +// authority. +type CertificateAuthorityCreator interface { + CreateCertificateAuthority(req *CreateCertificateAuthorityRequest) (*CreateCertificateAuthorityResponse, error) +} + // Type represents the CAS type used. type Type string diff --git a/cas/cas.go b/cas/cas.go index 3df83460..0592fed5 100644 --- a/cas/cas.go +++ b/cas/cas.go @@ -6,14 +6,16 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" - - // Enable default implementation - _ "github.com/smallstep/certificates/cas/softcas" + "github.com/smallstep/certificates/cas/softcas" ) // CertificateAuthorityService is the interface implemented by all the CAS. type CertificateAuthorityService = apiv1.CertificateAuthorityService +// CertificateAuthorityCreator is the interface implemented by all CAS that can create a new authority. +type CertificateAuthorityCreator = apiv1.CertificateAuthorityCreator + +// New creates a new CertificateAuthorityService using the given options. func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService, error) { if err := opts.Validate(); err != nil { return nil, err @@ -26,7 +28,33 @@ func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService, fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(t) if !ok { - return nil, errors.Errorf("unsupported kms type '%s'", t) + return nil, errors.Errorf("unsupported cas type '%s'", t) } return fn(ctx, opts) } + +// NewCreator creates a new CertificateAuthorityCreator using the given options. +func NewCreator(ctx context.Context, opts apiv1.Options) (CertificateAuthorityCreator, error) { + t := apiv1.Type(strings.ToLower(opts.Type)) + if t == apiv1.DefaultCAS { + t = apiv1.SoftCAS + } + if t == apiv1.SoftCAS { + return &softcas.SoftCAS{ + KeyManager: opts.KeyManager, + }, nil + } + + svc, err := New(ctx, opts) + if err != nil { + return nil, err + } + + creator, ok := svc.(CertificateAuthorityCreator) + if !ok { + + return nil, errors.Errorf("cas type '%s' does not implements CertificateAuthorityCreator", t) + } + + return creator, nil +} diff --git a/cas/cloudcas/certificate.go b/cas/cloudcas/certificate.go index dc9584e3..51e221b4 100644 --- a/cas/cloudcas/certificate.go +++ b/cas/cloudcas/certificate.go @@ -8,8 +8,10 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/pem" + "fmt" "github.com/pkg/errors" + kmsapi "github.com/smallstep/certificates/kms/apiv1" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -326,3 +328,45 @@ func findExtraExtension(cert *x509.Certificate, oid asn1.ObjectIdentifier) (pkix } return pkix.Extension{}, false } + +func createKeyVersionSpec(alg kmsapi.SignatureAlgorithm, bits int) (*pb.CertificateAuthority_KeyVersionSpec, error) { + switch alg { + case kmsapi.UnspecifiedSignAlgorithm, kmsapi.ECDSAWithSHA256: + return &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_EC_P256_SHA256, + }, + }, nil + case kmsapi.ECDSAWithSHA384: + return &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_EC_P384_SHA384, + }, + }, nil + case kmsapi.SHA256WithRSAPSS: + algo, err := getRSAPSSAlgorithm(bits) + if err != nil { + return nil, err + } + return &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: algo, + }, + }, nil + default: + return nil, fmt.Errorf("unknown or unsupported signature algorithm '%s'", bits) + } +} + +func getRSAPSSAlgorithm(bits int) (pb.CertificateAuthority_SignHashAlgorithm, error) { + switch bits { + case 0, 3072: + return pb.CertificateAuthority_RSA_PSS_3072_SHA_256, nil + case 2048: + return pb.CertificateAuthority_RSA_PSS_2048_SHA_256, nil + case 4096: + return pb.CertificateAuthority_RSA_PSS_4096_SHA_256, nil + default: + return 0, fmt.Errorf("unsupported RSA-PSS key size '%d'", bits) + } +} diff --git a/cas/cloudcas/cloudcas.go b/cas/cloudcas/cloudcas.go index 8820304a..d0519777 100644 --- a/cas/cloudcas/cloudcas.go +++ b/cas/cloudcas/cloudcas.go @@ -6,6 +6,8 @@ import ( "crypto/x509" "encoding/asn1" "encoding/pem" + "regexp" + "strings" "time" privateca "cloud.google.com/go/security/privateca/apiv1beta1" @@ -24,12 +26,20 @@ func init() { }) } +// The actual regular expression that matches a certificate authority is: +// ^projects/[a-z][a-z0-9-]{4,28}[a-z0-9]/locations/[a-z0-9-]+/certificateAuthorities/[a-zA-Z0-9-_]+$ +// But we will allow a more flexible one to fail if this changes. +var caRegexp = regexp.MustCompile("^projects/[^/]+/locations/[^/]+/certificateAuthorities/[^/]+$") + // CertificateAuthorityClient is the interface implemented by the Google CAS // client. type CertificateAuthorityClient interface { CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) RevokeCertificate(ctx context.Context, req *pb.RevokeCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) GetCertificateAuthority(ctx context.Context, req *pb.GetCertificateAuthorityRequest, opts ...gax.CallOption) (*pb.CertificateAuthority, error) + CreateCertificateAuthority(ctx context.Context, req *pb.CreateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.CreateCertificateAuthorityOperation, error) + FetchCertificateAuthorityCsr(ctx context.Context, req *pb.FetchCertificateAuthorityCsrRequest, opts ...gax.CallOption) (*pb.FetchCertificateAuthorityCsrResponse, error) + ActivateCertificateAuthority(ctx context.Context, req *pb.ActivateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.ActivateCertificateAuthorityOperation, error) } // recocationCodeMap maps revocation reason codes from RFC 5280, to Google CAS @@ -51,6 +61,8 @@ var revocationCodeMap = map[int]pb.RevocationReason{ type CloudCAS struct { client CertificateAuthorityClient certificateAuthority string + project string + location string } // newCertificateAuthorityClient creates the certificate authority client. This @@ -70,8 +82,29 @@ var newCertificateAuthorityClient = func(ctx context.Context, credentialsFile st // New creates a new CertificateAuthorityService implementation using Google // Cloud CAS. func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) { - if opts.CertificateAuthority == "" { - return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty") + if opts.IsCreator { + switch { + case opts.Project == "": + return nil, errors.New("cloudCAS 'project' cannot be empty") + case opts.Location == "": + return nil, errors.New("cloudCAS 'location' cannot be empty") + } + } else { + if opts.CertificateAuthority == "" { + return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty") + } + if !caRegexp.MatchString(opts.CertificateAuthority) { + return nil, errors.New("cloudCAS 'certificateAuthority' is not valid certificate authority resource") + } + // Extract project and location from CertificateAuthority + if parts := strings.Split(opts.CertificateAuthority, "/"); len(parts) == 6 { + if opts.Project == "" { + opts.Project = parts[1] + } + if opts.Location == "" { + opts.Location = parts[3] + } + } } client, err := newCertificateAuthorityClient(ctx, opts.CredentialsFile) @@ -82,6 +115,8 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) { return &CloudCAS{ client: client, certificateAuthority: opts.CertificateAuthority, + project: opts.Project, + location: opts.Location, }, nil } @@ -101,6 +136,7 @@ func (c *CloudCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityReq Name: name, }) if err != nil { + println(name) return nil, errors.Wrap(err, "cloudCAS GetCertificateAuthority failed") } if len(resp.PemCaCertificates) == 0 { @@ -160,7 +196,7 @@ func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1. }, nil } -// RevokeCertificate a certificate using Google Cloud CAS. +// RevokeCertificate revokes a certificate using Google Cloud CAS. func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { reason, ok := revocationCodeMap[req.ReasonCode] switch { @@ -203,6 +239,140 @@ func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv }, nil } +// CreateCertificateAuthority creates a new root or intermediate certificate +// using Google Cloud CAS. +func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthorityRequest) (*apiv1.CreateCertificateAuthorityResponse, error) { + switch { + case c.project == "": + return nil, errors.New("cloudCAS `project` cannot be empty") + case c.location == "": + return nil, errors.New("cloudCAS `location` cannot be empty") + case req.Template == nil: + return nil, errors.New("createCertificateAuthorityRequest `template` cannot be nil") + case req.Lifetime == 0: + return nil, errors.New("createCertificateAuthorityRequest `lifetime` cannot be 0") + case req.Type == apiv1.IntermediateCA && req.Parent == nil: + return nil, errors.New("createCertificateAuthorityRequest `parent` cannot be nil") + case req.Type == apiv1.IntermediateCA && req.Parent.Name == "": + return nil, errors.New("createCertificateAuthorityRequest `parent.name` cannot be empty") + } + + // Select key and signature algorithm to use + var err error + var keySpec *pb.CertificateAuthority_KeyVersionSpec + if req.CreateKey == nil { + if keySpec, err = createKeyVersionSpec(0, 0); err != nil { + return nil, errors.Wrap(err, "createCertificateAuthorityRequest `createKey` is not valid") + } + } else { + if keySpec, err = createKeyVersionSpec(req.CreateKey.SignatureAlgorithm, req.CreateKey.Bits); err != nil { + return nil, errors.Wrap(err, "createCertificateAuthorityRequest `createKey` is not valid") + } + } + + // Normalize or generate id. + certificateAuthorityID := normalizeCertificateAuthorityName(req.Name) + if certificateAuthorityID == "" { + id, err := createCertificateID() + if err != nil { + return nil, err + } + certificateAuthorityID = id + } + + // Add CertificateAuthority extension + casExtension, err := apiv1.CreateCertificateAuthorityExtension(apiv1.CloudCAS, certificateAuthorityID) + if err != nil { + return nil, err + } + req.Template.ExtraExtensions = append(req.Template.ExtraExtensions, casExtension) + + // Prepare CreateCertificateAuthorityRequest + pbReq := &pb.CreateCertificateAuthorityRequest{ + Parent: "projects/" + c.project + "/locations/" + c.location, + CertificateAuthorityId: certificateAuthorityID, + RequestId: req.RequestID, + CertificateAuthority: &pb.CertificateAuthority{ + Type: pb.CertificateAuthority_TYPE_UNSPECIFIED, + Tier: pb.CertificateAuthority_ENTERPRISE, + Config: &pb.CertificateConfig{ + SubjectConfig: &pb.CertificateConfig_SubjectConfig{ + Subject: createSubject(req.Template), + CommonName: req.Template.Subject.CommonName, + }, + ReusableConfig: createReusableConfig(req.Template), + }, + Lifetime: durationpb.New(req.Lifetime), + KeySpec: keySpec, + IssuingOptions: &pb.CertificateAuthority_IssuingOptions{ + IncludeCaCertUrl: true, + IncludeCrlAccessUrl: true, + }, + Labels: map[string]string{}, + }, + } + + switch req.Type { + case apiv1.RootCA: + pbReq.CertificateAuthority.Type = pb.CertificateAuthority_SELF_SIGNED + case apiv1.IntermediateCA: + pbReq.CertificateAuthority.Type = pb.CertificateAuthority_SUBORDINATE + default: + return nil, errors.Errorf("createCertificateAuthorityRequest `type=%d' is invalid or not supported", req.Type) + } + + // Create certificate authority. + ctx, cancel := defaultContext() + defer cancel() + + resp, err := c.client.CreateCertificateAuthority(ctx, pbReq) + if err != nil { + return nil, errors.Wrap(err, "cloudCAS CreateCertificateAuthority failed") + } + + // Wait for the long-running operation. + ctx, cancel = defaultInitiatorContext() + defer cancel() + + ca, err := resp.Wait(ctx) + if err != nil { + return nil, errors.Wrap(err, "cloudCAS CreateCertificateAuthority failed") + } + + // Sign Intermediate CAs with the parent. + if req.Type == apiv1.IntermediateCA { + ca, err = c.signIntermediateCA(ca.Name, req) + if err != nil { + return nil, err + } + } + + if len(ca.PemCaCertificates) == 0 { + return nil, errors.New("cloudCAS CreateCertificateAuthority failed: PemCaCertificates is empty") + } + + cert, err := parseCertificate(ca.PemCaCertificates[0]) + if err != nil { + return nil, err + } + + var chain []*x509.Certificate + if pemChain := ca.PemCaCertificates[1:]; len(pemChain) > 0 { + chain = make([]*x509.Certificate, len(pemChain)) + for i, s := range pemChain { + if chain[i], err = parseCertificate(s); err != nil { + return nil, err + } + } + } + + return &apiv1.CreateCertificateAuthorityResponse{ + Name: ca.Name, + Certificate: cert, + CertificateChain: chain, + }, nil +} + func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Duration, requestID string) (*x509.Certificate, []*x509.Certificate, error) { // Removes the CAS extension if it exists. apiv1.RemoveCertificateAuthorityExtension(tpl) @@ -245,10 +415,82 @@ func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Durati return getCertificateAndChain(cert) } +func (c *CloudCAS) signIntermediateCA(name string, req *apiv1.CreateCertificateAuthorityRequest) (*pb.CertificateAuthority, error) { + id, err := createCertificateID() + if err != nil { + return nil, err + } + + // Fetch intermediate CSR + ctx, cancel := defaultInitiatorContext() + defer cancel() + + csr, err := c.client.FetchCertificateAuthorityCsr(ctx, &pb.FetchCertificateAuthorityCsrRequest{ + Name: name, + }) + if err != nil { + return nil, errors.Wrap(err, "cloudCAS FetchCertificateAuthorityCsr failed") + } + + // Sign the CSR with the ca. + ctx, cancel = defaultInitiatorContext() + defer cancel() + + sign, err := c.client.CreateCertificate(ctx, &pb.CreateCertificateRequest{ + Parent: req.Parent.Name, + CertificateId: id, + Certificate: &pb.Certificate{ + // Name: "projects/" + c.project + "/locations/" + c.location + "/certificates/" + id, + CertificateConfig: &pb.Certificate_PemCsr{ + PemCsr: csr.PemCsr, + }, + Lifetime: durationpb.New(req.Lifetime), + Labels: map[string]string{}, + }, + RequestId: req.RequestID, + }) + if err != nil { + return nil, errors.Wrap(err, "cloudCAS CreateCertificate failed") + } + + ctx, cancel = defaultInitiatorContext() + defer cancel() + resp, err := c.client.ActivateCertificateAuthority(ctx, &pb.ActivateCertificateAuthorityRequest{ + Name: name, + PemCaCertificate: sign.PemCertificate, + SubordinateConfig: &pb.SubordinateConfig{ + SubordinateConfig: &pb.SubordinateConfig_PemIssuerChain{ + PemIssuerChain: &pb.SubordinateConfig_SubordinateConfigChain{ + PemCertificates: sign.PemCertificateChain, + }, + }, + }, + RequestId: req.RequestID, + }) + if err != nil { + return nil, errors.Wrap(err, "cloudCAS ActivateCertificateAuthority1 failed") + } + + // Wait for the long-running operation. + ctx, cancel = defaultInitiatorContext() + defer cancel() + + ca, err := resp.Wait(ctx) + if err != nil { + return nil, errors.Wrap(err, "cloudCAS ActivateCertificateAuthority failed") + } + + return ca, nil +} + func defaultContext() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), 15*time.Second) } +func defaultInitiatorContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), 60*time.Second) +} + func createCertificateID() (string, error) { id, err := uuid.NewRandomFromReader(rand.Reader) if err != nil { @@ -287,3 +529,23 @@ func getCertificateAndChain(certpb *pb.Certificate) (*x509.Certificate, []*x509. return cert, chain, nil } + +// Normalize a certificate authority name to comply with [a-zA-Z0-9-_]. +func normalizeCertificateAuthorityName(name string) string { + return strings.Map(func(r rune) rune { + switch { + case r >= 'a' && r <= 'z': + return r + case r >= 'A' && r <= 'Z': + return r + case r >= '0' && r <= '9': + return r + case r == '-': + return r + case r == '_': + return r + default: + return '-' + } + }, name) +} diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index 38446325..16e4386d 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "crypto/x509" + "crypto/x509/pkix" "encoding/asn1" "io" "os" @@ -673,3 +674,62 @@ func Test_getCertificateAndChain(t *testing.T) { }) } } + +func TestCloudCAS(t *testing.T) { + cas, err := New(context.Background(), apiv1.Options{ + Type: "cloudCAS", + CertificateAuthority: "projects/smallstep-cas-test/locations/us-west1", + CredentialsFile: "/Users/mariano/smallstep-cas-test-8a068f3e4540.json", + }) + if err != nil { + t.Fatal(err) + } + + // resp, err := cas.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ + // Type: apiv1.RootCA, + // Template: &x509.Certificate{ + // Subject: pkix.Name{ + // CommonName: "Test Mariano Root CA", + // }, + // BasicConstraintsValid: true, + // IsCA: true, + // MaxPathLen: 1, + // MaxPathLenZero: false, + // KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + // }, + // Lifetime: time.Duration(30 * 24 * time.Hour), + // Project: "smallstep-cas-test", + // Location: "us-west1", + // }) + // if err != nil { + // t.Fatal(err) + // } + // debug(resp) + resp := &apiv1.CreateCertificateAuthorityResponse{ + Name: "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/9a593da4-61af-4426-a2f8-0650373b9c8e", + } + + resp, err = cas.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: &x509.Certificate{ + Subject: pkix.Name{ + Country: []string{"US"}, + CommonName: "Test Mariano Intermediate CA", + }, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 0, + MaxPathLenZero: true, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + }, + Lifetime: time.Duration(24 * time.Hour), + Parent: resp, + Project: "smallstep-cas-test", + Location: "us-west1", + }) + if err != nil { + t.Fatal(err) + } + // debug(resp) + t.Error("foo") +} diff --git a/cas/softcas/softcas.go b/cas/softcas/softcas.go index 844c5c3c..16ae9547 100644 --- a/cas/softcas/softcas.go +++ b/cas/softcas/softcas.go @@ -4,10 +4,12 @@ import ( "context" "crypto" "crypto/x509" - "errors" "time" + "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" + "github.com/smallstep/certificates/kms" + kmsapi "github.com/smallstep/certificates/kms/apiv1" "go.step.sm/crypto/x509util" ) @@ -24,22 +26,26 @@ var now = func() time.Time { // SoftCAS implements a Certificate Authority Service using Golang or KMS // crypto. This is the default CAS used in step-ca. type SoftCAS struct { - Issuer *x509.Certificate - Signer crypto.Signer + Issuer *x509.Certificate + Signer crypto.Signer + KeyManager kms.KeyManager } // New creates a new CertificateAuthorityService implementation using Golang or KMS // crypto. func New(ctx context.Context, opts apiv1.Options) (*SoftCAS, error) { - switch { - case opts.Issuer == nil: - return nil, errors.New("softCAS 'issuer' cannot be nil") - case opts.Signer == nil: - return nil, errors.New("softCAS 'signer' cannot be nil") + if !opts.IsCreator { + switch { + case opts.Issuer == nil: + return nil, errors.New("softCAS 'issuer' cannot be nil") + case opts.Signer == nil: + return nil, errors.New("softCAS 'signer' cannot be nil") + } } return &SoftCAS{ - Issuer: opts.Issuer, - Signer: opts.Signer, + Issuer: opts.Issuer, + Signer: opts.Signer, + KeyManager: opts.KeyManager, }, nil } @@ -113,3 +119,102 @@ func (c *SoftCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1 }, }, nil } + +// CreateCertificateAuthority creates a root or an intermediate certificate. +func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthorityRequest) (*apiv1.CreateCertificateAuthorityResponse, error) { + switch { + case req.Template == nil: + return nil, errors.New("createCertificateAuthorityRequest `template` cannot be nil") + case req.Lifetime == 0: + return nil, errors.New("createCertificateAuthorityRequest `lifetime` cannot be 0") + case req.Type == apiv1.IntermediateCA && req.Parent == nil: + return nil, errors.New("createCertificateAuthorityRequest `parent` cannot be nil") + case req.Type == apiv1.IntermediateCA && req.Parent.Certificate == nil: + return nil, errors.New("createCertificateAuthorityRequest `parent.template` cannot be nil") + case req.Type == apiv1.IntermediateCA && req.Parent.Signer == nil: + return nil, errors.New("createCertificateAuthorityRequest `parent.signer` cannot be nil") + } + + key, err := c.createKey(req.CreateKey) + if err != nil { + return nil, err + } + + signer, err := c.createSigner(&key.CreateSignerRequest) + if err != nil { + return nil, err + } + + t := now() + if req.Template.NotBefore.IsZero() { + req.Template.NotBefore = t.Add(-1 * req.Backdate) + } + if req.Template.NotAfter.IsZero() { + req.Template.NotAfter = t.Add(req.Lifetime) + } + + var cert *x509.Certificate + switch req.Type { + case apiv1.RootCA: + cert, err = x509util.CreateCertificate(req.Template, req.Template, signer.Public(), signer) + if err != nil { + return nil, err + } + case apiv1.IntermediateCA: + cert, err = x509util.CreateCertificate(req.Template, req.Parent.Certificate, signer.Public(), req.Parent.Signer) + if err != nil { + return nil, err + } + default: + return nil, errors.Errorf("createCertificateAuthorityRequest `type=%d' is invalid or not supported", req.Type) + } + + // Add the parent + var chain []*x509.Certificate + if req.Parent != nil { + chain = append(chain, req.Parent.Certificate) + for _, crt := range req.Parent.CertificateChain { + chain = append(chain, crt) + } + } + + return &apiv1.CreateCertificateAuthorityResponse{ + Name: cert.Subject.CommonName, + Certificate: cert, + CertificateChain: chain, + PublicKey: key.PublicKey, + PrivateKey: key.PrivateKey, + Signer: signer, + }, nil +} + +// initializeKeyManager initiazes the default key manager if was not given. +func (c *SoftCAS) initializeKeyManager() (err error) { + if c.KeyManager == nil { + c.KeyManager, err = kms.New(context.Background(), kmsapi.Options{ + Type: string(kmsapi.DefaultKMS), + }) + } + return +} + +// createKey uses the configured kms to create a key. +func (c *SoftCAS) createKey(req *kmsapi.CreateKeyRequest) (*kmsapi.CreateKeyResponse, error) { + if err := c.initializeKeyManager(); err != nil { + return nil, err + } + if req == nil { + req = &kmsapi.CreateKeyRequest{ + SignatureAlgorithm: kmsapi.ECDSAWithSHA256, + } + } + return c.KeyManager.CreateKey(req) +} + +// createSigner uses the configured kms to create a singer +func (c *SoftCAS) createSigner(req *kmsapi.CreateSignerRequest) (crypto.Signer, error) { + if err := c.initializeKeyManager(); err != nil { + return nil, err + } + return c.KeyManager.CreateSigner(req) +} diff --git a/commands/onboard.go b/commands/onboard.go index f5dc422b..13c32304 100644 --- a/commands/onboard.go +++ b/commands/onboard.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/pki" "github.com/urfave/cli" "go.step.sm/cli-utils/command" @@ -162,7 +163,10 @@ func onboardAction(ctx *cli.Context) error { } func onboardPKI(config onboardingConfiguration) (*authority.Config, string, error) { - p, err := pki.New() + p, err := pki.New(apiv1.Options{ + Type: apiv1.SoftCAS, + IsCreator: true, + }) if err != nil { return nil, "", err } @@ -171,13 +175,13 @@ func onboardPKI(config onboardingConfiguration) (*authority.Config, string, erro p.SetDNSNames([]string{config.DNS}) ui.Println("Generating root certificate...") - rootCrt, rootKey, err := p.GenerateRootCertificate(config.Name+" Root CA", config.password) + root, err := p.GenerateRootCertificate(config.Name, config.Name, config.Name, config.password) if err != nil { return nil, "", err } ui.Println("Generating intermediate certificate...") - err = p.GenerateIntermediateCertificate(config.Name+" Intermediate CA", rootCrt, rootKey, config.password) + err = p.GenerateIntermediateCertificate(config.Name, config.Name, config.Name, root, config.password) if err != nil { return nil, "", err } diff --git a/go.mod b/go.mod index 872e0bb7..6f905db6 100644 --- a/go.mod +++ b/go.mod @@ -20,19 +20,15 @@ require ( github.com/urfave/cli v1.22.2 go.step.sm/cli-utils v0.1.0 go.step.sm/crypto v0.6.1 - golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de - golang.org/x/net v0.0.0-20200822124328-c89045814202 + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 + golang.org/x/net v0.0.0-20201021035429-f5854403a974 google.golang.org/api v0.31.0 google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d google.golang.org/grpc v1.32.0 google.golang.org/protobuf v1.25.0 gopkg.in/square/go-jose.v2 v2.5.1 -// cloud.google.com/go/security/privateca/apiv1alpha1 v0.0.0 -// google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1 v0.0.0 ) +// replace github.com/smallstep/cli => ../cli // replace github.com/smallstep/nosql => ../nosql // replace go.step.sm/crypto => ../crypto - -// replace cloud.google.com/go/security/privateca/apiv1alpha1 => ./pkg/cloud.google.com/go/security/privateca/apiv1alpha1 -// replace google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1 => ./pkg/google.golang.org/genproto/googleapis/cloud/security/privateca/v1alpha1 diff --git a/go.sum b/go.sum index e69eb8e2..bb47a168 100644 --- a/go.sum +++ b/go.sum @@ -293,6 +293,8 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -353,6 +355,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -401,6 +405,8 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4 h1:kCCpuwSAoYJPkNc6x0xT9yTtV4oKtARo4RGBQWOfg9E= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/pki/pki.go b/pki/pki.go index 4299a1f1..05f09b02 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -5,6 +5,7 @@ import ( "crypto" "crypto/sha256" "crypto/x509" + "crypto/x509/pkix" "encoding/hex" "encoding/json" "encoding/pem" @@ -31,7 +32,6 @@ import ( "go.step.sm/crypto/jose" "go.step.sm/crypto/keyutil" "go.step.sm/crypto/pemutil" - "go.step.sm/crypto/x509util" "golang.org/x/crypto/ssh" ) @@ -148,6 +148,8 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) { // PKI represents the Public Key Infrastructure used by a certificate authority. type PKI struct { + casOptions apiv1.Options + caCreator apiv1.CertificateAuthorityCreator root, rootKey, rootFingerprint string intermediate, intermediateKey string sshHostPubKey, sshHostKey string @@ -160,11 +162,15 @@ type PKI struct { dnsNames []string caURL string enableSSH bool - authorityOptions *apiv1.Options } // New creates a new PKI configuration. -func New() (*PKI, error) { +func New(opts apiv1.Options) (*PKI, error) { + caCreator, err := cas.NewCreator(context.Background(), opts) + if err != nil { + return nil, err + } + public := GetPublicPath() private := GetSecretsPath() config := GetConfigPath() @@ -185,8 +191,9 @@ func New() (*PKI, error) { return s, errors.Wrapf(err, "error getting absolute path for %s", name) } - var err error p := &PKI{ + casOptions: opts, + caCreator: caCreator, provisioner: "step-cli", address: "127.0.0.1:9000", dnsNames: []string{"127.0.0.1"}, @@ -237,12 +244,6 @@ func (p *PKI) GetRootFingerprint() string { return p.rootFingerprint } -// SetAuthorityOptions sets the authority options object, these options are used -// to configure a registration authority. -func (p *PKI) SetAuthorityOptions(opts *apiv1.Options) { - p.authorityOptions = opts -} - // SetProvisioner sets the provisioner name of the OTT keys. func (p *PKI) SetProvisioner(s string) { p.provisioner = s @@ -275,37 +276,65 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error { return nil } -// GenerateRootCertificate generates a root certificate with the given name. -func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) { - signer, err := generateDefaultKey() +// GenerateRootCertificate generates a root certificate with the given name +// and using the default key type. +func (p *PKI) GenerateRootCertificate(name, org, resource string, pass []byte) (*apiv1.CreateCertificateAuthorityResponse, error) { + resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ + Name: resource + "-Root-CA", + Type: apiv1.RootCA, + Lifetime: 10 * 365 * 24 * time.Hour, + CreateKey: nil, // use default + Template: &x509.Certificate{ + Subject: pkix.Name{ + CommonName: name + " Root CA", + Organization: []string{org}, + }, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 1, + MaxPathLenZero: false, + }, + }) if err != nil { - return nil, nil, err + return nil, err } - cr, err := x509util.CreateCertificateRequest(name, []string{}, signer) + // PrivateKey will only be set if we have access to it (SoftCAS). + if err := p.WriteRootCertificate(resp.Certificate, resp.PrivateKey, pass); err != nil { + return nil, err + } + + return resp, nil +} + +// GenerateIntermediateCertificate generates an intermediate certificate with +// the given name and using the default key type. +func (p *PKI) GenerateIntermediateCertificate(name, org, resource string, parent *apiv1.CreateCertificateAuthorityResponse, pass []byte) error { + resp, err := p.caCreator.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ + Name: resource + "-Intermediate-CA", + Type: apiv1.IntermediateCA, + Lifetime: 10 * 365 * 24 * time.Hour, + CreateKey: nil, // use default + Template: &x509.Certificate{ + Subject: pkix.Name{ + CommonName: name + " Intermediate CA", + Organization: []string{org}, + }, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 0, + MaxPathLenZero: true, + }, + Parent: parent, + }) if err != nil { - return nil, nil, err + return err } - data := x509util.CreateTemplateData(name, []string{}) - cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data)) - if err != nil { - return nil, nil, err - } - - template := cert.GetCertificate() - template.NotBefore = time.Now() - template.NotAfter = template.NotBefore.AddDate(10, 0, 0) - rootCrt, err := x509util.CreateCertificate(template, template, signer.Public(), signer) - if err != nil { - return nil, nil, err - } - - if err := p.WriteRootCertificate(rootCrt, signer, pass); err != nil { - return nil, nil, err - } - - return rootCrt, signer, nil + p.casOptions.CertificateAuthority = resp.Name + return p.WriteIntermediateCertificate(resp.Certificate, resp.PrivateKey, pass) } // WriteRootCertificate writes to disk the given certificate and key. @@ -330,21 +359,33 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{ return nil } +// WriteIntermediateCertificate writes to disk the given certificate and key. +func (p *PKI) WriteIntermediateCertificate(crt *x509.Certificate, key interface{}, pass []byte) error { + if err := fileutil.WriteFile(p.intermediate, pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: crt.Raw, + }), 0600); err != nil { + return err + } + if key != nil { + _, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(p.intermediateKey, 0600)) + if err != nil { + return err + } + } + return nil +} + // GetCertificateAuthority attempts to load the certificate authority from the // RA. func (p *PKI) GetCertificateAuthority() error { - ca, err := cas.New(context.Background(), *p.authorityOptions) - if err != nil { - return err - } - - srv, ok := ca.(apiv1.CertificateAuthorityGetter) + srv, ok := p.caCreator.(apiv1.CertificateAuthorityGetter) if !ok { return nil } resp, err := srv.GetCertificateAuthority(&apiv1.GetCertificateAuthorityRequest{ - Name: p.authorityOptions.CertificateAuthority, + Name: p.casOptions.CertificateAuthority, }) if err != nil { return err @@ -361,51 +402,6 @@ func (p *PKI) GetCertificateAuthority() error { return nil } -// GenerateIntermediateCertificate generates an intermediate certificate with -// the given name. -func (p *PKI) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error { - key, err := generateDefaultKey() - if err != nil { - return err - } - - cr, err := x509util.CreateCertificateRequest(name, []string{}, key) - if err != nil { - return err - } - - data := x509util.CreateTemplateData(name, []string{}) - cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultIntermediateTemplate, data)) - if err != nil { - return err - } - - template := cert.GetCertificate() - template.NotBefore = rootCrt.NotBefore - template.NotAfter = rootCrt.NotAfter - intermediateCrt, err := x509util.CreateCertificate(template, rootCrt, key.Public(), rootKey.(crypto.Signer)) - if err != nil { - return err - } - - return p.WriteIntermediateCertificate(intermediateCrt, key, pass) -} - -// WriteIntermediateCertificate writes to disk the given certificate and key. -func (p *PKI) WriteIntermediateCertificate(crt *x509.Certificate, key interface{}, pass []byte) error { - if err := fileutil.WriteFile(p.intermediate, pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: crt.Raw, - }), 0600); err != nil { - return err - } - _, err := pemutil.Serialize(key, pemutil.WithPassword(pass), pemutil.ToFile(p.intermediateKey, 0600)) - if err != nil { - return err - } - return nil -} - // GenerateSSHSigningKeys generates and encrypts a private key used for signing // SSH user certificates and a private key used for signing host certificates. func (p *PKI) GenerateSSHSigningKeys(password []byte) error { @@ -457,7 +453,7 @@ func (p *PKI) TellPKI() { func (p *PKI) tellPKI() { ui.Println() - if p.authorityOptions == nil || p.authorityOptions.Is(apiv1.SoftCAS) { + if p.casOptions.Is(apiv1.SoftCAS) { ui.PrintSelected("Root certificate", p.root) ui.PrintSelected("Root private key", p.rootKey) ui.PrintSelected("Root fingerprint", p.rootFingerprint) @@ -522,6 +518,11 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { EncryptedKey: key, } + var authorityOptions *apiv1.Options + if !p.casOptions.Is(apiv1.SoftCAS) { + authorityOptions = &p.casOptions + } + config := &authority.Config{ Root: []string{p.root}, FederatedRoots: []string{}, @@ -535,7 +536,7 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { DataSource: GetDBPath(), }, AuthorityConfig: &authority.AuthConfig{ - Options: p.authorityOptions, + Options: authorityOptions, DisableIssuedAtCheck: false, Provisioners: provisioner.List{prov}, }, @@ -642,7 +643,7 @@ func (p *PKI) Save(opt ...Option) error { ui.PrintSelected("Default configuration", p.defaults) ui.PrintSelected("Certificate Authority configuration", p.config) ui.Println() - if p.authorityOptions == nil || p.authorityOptions.Is(apiv1.SoftCAS) { + if p.casOptions.Is(apiv1.SoftCAS) { ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.") } else { ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.") From 461735718d41f0d503da5e89d02839072681b507 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 26 Oct 2020 14:23:26 -0700 Subject: [PATCH 02/17] Update go.step.sm/crypto dependency. --- go.mod | 5 +++++ go.sum | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/go.mod b/go.mod index 6f905db6..4a087cdc 100644 --- a/go.mod +++ b/go.mod @@ -17,9 +17,14 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/nosql v0.3.0 +<<<<<<< HEAD github.com/urfave/cli v1.22.2 go.step.sm/cli-utils v0.1.0 go.step.sm/crypto v0.6.1 +======= + github.com/urfave/cli v1.22.4 + go.step.sm/crypto v0.7.0 +>>>>>>> Update go.step.sm/crypto dependency. golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/net v0.0.0-20201021035429-f5854403a974 google.golang.org/api v0.31.0 diff --git a/go.sum b/go.sum index bb47a168..7b4ea140 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,7 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -204,7 +205,12 @@ github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRU github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +<<<<<<< HEAD github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +======= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +>>>>>>> Update go.step.sm/crypto dependency. github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= @@ -243,6 +249,13 @@ github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5 h1:lX6ybsQW9Agn3q github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= +<<<<<<< HEAD +======= +github.com/smallstep/certificates v0.15.5/go.mod h1:T0L78wJmhdj1AyrfrNG5mPP2MG15cRM/9d1frw8NnJ0= +github.com/smallstep/certinfo v1.4.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU= +github.com/smallstep/cli v0.15.3 h1:t9LBO53PK4SEG6FSo0UOxI306m9pHb+h632oFZe+ZwA= +github.com/smallstep/cli v0.15.3/go.mod h1:KBcpj/m28/uhK+lH0/ctbncZqWQvxDFWk8T2cUyeFg0= +>>>>>>> Update go.step.sm/crypto dependency. github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U= github.com/smallstep/nosql v0.3.0/go.mod h1:QG7gNOpidifn99MjZaiNbm7HPesIyBd97F/OfacNz8Q= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -262,6 +275,16 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +<<<<<<< HEAD +======= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +>>>>>>> Update go.step.sm/crypto dependency. github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -283,6 +306,21 @@ go.step.sm/cli-utils v0.1.0 h1:uuQ73MuAh5P5Eg+3zfqwrtlTLx5DWSfGqGCrSSjYqdk= go.step.sm/cli-utils v0.1.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y= go.step.sm/crypto v0.6.1 h1:nJoRFGrGNf/mKVVMdWnfLbBfIFt/z4NdJlSL5nipQMQ= go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= +<<<<<<< HEAD +======= +go.step.sm/crypto v0.7.0 h1:azKRI4CBRzDbhHsLAnvzvGJ0aVbGI+wrh2COrPd/mks= +go.step.sm/crypto v0.7.0/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +>>>>>>> Update go.step.sm/crypto dependency. golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= From dff00a02182bc6f44a806342d2f313520099ac99 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 26 Oct 2020 16:43:02 -0700 Subject: [PATCH 03/17] Add support for local signing or cloudCAS intermediates. --- cas/cloudcas/cloudcas.go | 98 ++++++++++++++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/cas/cloudcas/cloudcas.go b/cas/cloudcas/cloudcas.go index d0519777..087febc7 100644 --- a/cas/cloudcas/cloudcas.go +++ b/cas/cloudcas/cloudcas.go @@ -15,6 +15,7 @@ import ( gax "github.com/googleapis/gax-go/v2" "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" + "go.step.sm/crypto/x509util" "google.golang.org/api/option" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" durationpb "google.golang.org/protobuf/types/known/durationpb" @@ -26,6 +27,10 @@ func init() { }) } +var now = func() time.Time { + return time.Now() +} + // The actual regular expression that matches a certificate authority is: // ^projects/[a-z][a-z0-9-]{4,28}[a-z0-9]/locations/[a-z0-9-]+/certificateAuthorities/[a-zA-Z0-9-_]+$ // But we will allow a more flexible one to fail if this changes. @@ -253,7 +258,7 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor return nil, errors.New("createCertificateAuthorityRequest `lifetime` cannot be 0") case req.Type == apiv1.IntermediateCA && req.Parent == nil: return nil, errors.New("createCertificateAuthorityRequest `parent` cannot be nil") - case req.Type == apiv1.IntermediateCA && req.Parent.Name == "": + case req.Type == apiv1.IntermediateCA && req.Parent.Name == "" && (req.Parent.Certificate == nil || req.Parent.Signer == nil): return nil, errors.New("createCertificateAuthorityRequest `parent.name` cannot be empty") } @@ -433,35 +438,71 @@ func (c *CloudCAS) signIntermediateCA(name string, req *apiv1.CreateCertificateA } // Sign the CSR with the ca. - ctx, cancel = defaultInitiatorContext() - defer cancel() + var cert *pb.Certificate + if req.Parent.Certificate != nil && req.Parent.Signer != nil { + // Using a local certificate and key. + cr, err := parseCertificateRequest(csr.PemCsr) + if err != nil { + return nil, err + } + template, err := x509util.CreateCertificateTemplate(cr) + if err != nil { + return nil, err + } - sign, err := c.client.CreateCertificate(ctx, &pb.CreateCertificateRequest{ - Parent: req.Parent.Name, - CertificateId: id, - Certificate: &pb.Certificate{ - // Name: "projects/" + c.project + "/locations/" + c.location + "/certificates/" + id, - CertificateConfig: &pb.Certificate_PemCsr{ - PemCsr: csr.PemCsr, + t := now() + template.NotBefore = t.Add(-1 * req.Backdate) + template.NotAfter = t.Add(req.Lifetime) + + // Sign certificate + crt, err := x509util.CreateCertificate(template, req.Parent.Certificate, template.PublicKey, req.Parent.Signer) + if err != nil { + return nil, err + } + + // Build pb.Certificate for activaion + chain := []string{ + encodeCertificate(req.Parent.Certificate), + } + for _, c := range req.Parent.CertificateChain { + chain = append(chain, encodeCertificate(c)) + } + cert = &pb.Certificate{ + PemCertificate: encodeCertificate(crt), + PemCertificateChain: chain, + } + } else { + // Using the parent in CloudCAS. + ctx, cancel = defaultInitiatorContext() + defer cancel() + + cert, err = c.client.CreateCertificate(ctx, &pb.CreateCertificateRequest{ + Parent: req.Parent.Name, + CertificateId: id, + Certificate: &pb.Certificate{ + CertificateConfig: &pb.Certificate_PemCsr{ + PemCsr: csr.PemCsr, + }, + Lifetime: durationpb.New(req.Lifetime), + Labels: map[string]string{}, }, - Lifetime: durationpb.New(req.Lifetime), - Labels: map[string]string{}, - }, - RequestId: req.RequestID, - }) - if err != nil { - return nil, errors.Wrap(err, "cloudCAS CreateCertificate failed") + RequestId: req.RequestID, + }) + if err != nil { + return nil, errors.Wrap(err, "cloudCAS CreateCertificate failed") + } } + // Activate the intermediate certificate. ctx, cancel = defaultInitiatorContext() defer cancel() resp, err := c.client.ActivateCertificateAuthority(ctx, &pb.ActivateCertificateAuthorityRequest{ Name: name, - PemCaCertificate: sign.PemCertificate, + PemCaCertificate: cert.PemCertificate, SubordinateConfig: &pb.SubordinateConfig{ SubordinateConfig: &pb.SubordinateConfig_PemIssuerChain{ PemIssuerChain: &pb.SubordinateConfig_SubordinateConfigChain{ - PemCertificates: sign.PemCertificateChain, + PemCertificates: cert.PemCertificateChain, }, }, }, @@ -511,6 +552,25 @@ func parseCertificate(pemCert string) (*x509.Certificate, error) { return cert, nil } +func parseCertificateRequest(pemCsr string) (*x509.CertificateRequest, error) { + block, _ := pem.Decode([]byte(pemCsr)) + if block == nil { + return nil, errors.New("error decoding certificate request: not a valid PEM encoded block") + } + cr, err := x509.ParseCertificateRequest(block.Bytes) + if err != nil { + return nil, errors.Wrap(err, "error parsing certificate request") + } + return cr, nil +} + +func encodeCertificate(cert *x509.Certificate) string { + return string(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + })) +} + func getCertificateAndChain(certpb *pb.Certificate) (*x509.Certificate, []*x509.Certificate, error) { cert, err := parseCertificate(certpb.PemCertificate) if err != nil { From 1d48f00723f13dd67db9de8c8cb92e0dbeb095be Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 26 Oct 2020 16:43:44 -0700 Subject: [PATCH 04/17] Add method to create a CertificateAuthorityResponse. --- pki/pki.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pki/pki.go b/pki/pki.go index 05f09b02..59c49ea7 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -376,6 +376,18 @@ func (p *PKI) WriteIntermediateCertificate(crt *x509.Certificate, key interface{ return nil } +// CreateCertificateAuthorityResponse returns a +// CreateCertificateAuthorityResponse that can be used as a parent of a +// CreateCertificateAuthority request. +func (p *PKI) CreateCertificateAuthorityResponse(cert *x509.Certificate, key crypto.PrivateKey) *apiv1.CreateCertificateAuthorityResponse { + signer, _ := key.(crypto.Signer) + return &apiv1.CreateCertificateAuthorityResponse{ + Certificate: cert, + PrivateKey: key, + Signer: signer, + } +} + // GetCertificateAuthority attempts to load the certificate authority from the // RA. func (p *PKI) GetCertificateAuthority() error { From 9270d432eaa435764a3e4a73a111f2907d53d496 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 26 Oct 2020 19:49:27 -0700 Subject: [PATCH 05/17] Remove unused code. --- pki/pki.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pki/pki.go b/pki/pki.go index 59c49ea7..c95ca985 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -117,18 +117,6 @@ func GetProvisioners(caURL, rootFile string) (provisioner.List, error) { } } -func generateDefaultKey() (crypto.Signer, error) { - priv, err := keyutil.GenerateDefaultKey() - if err != nil { - return nil, err - } - signer, ok := priv.(crypto.Signer) - if !ok { - return nil, errors.Errorf("type %T is not a cyrpto.Signer", priv) - } - return signer, nil -} - // GetProvisionerKey returns the encrypted provisioner key with the for the // given kid. func GetProvisionerKey(caURL, rootFile, kid string) (string, error) { From b68344ec36a230cd7e1c8f14c5db5dd2f2db5ab0 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 26 Oct 2020 19:51:29 -0700 Subject: [PATCH 06/17] Fix unexpected error. --- cas/cloudcas/certificate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cas/cloudcas/certificate.go b/cas/cloudcas/certificate.go index 51e221b4..ba823d86 100644 --- a/cas/cloudcas/certificate.go +++ b/cas/cloudcas/certificate.go @@ -354,7 +354,7 @@ func createKeyVersionSpec(alg kmsapi.SignatureAlgorithm, bits int) (*pb.Certific }, }, nil default: - return nil, fmt.Errorf("unknown or unsupported signature algorithm '%s'", bits) + return nil, fmt.Errorf("unknown or unsupported signature algorithm '%s'", alg) } } From b2ae112dd26b20764df023acd8a53166a795208d Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 26 Oct 2020 19:52:08 -0700 Subject: [PATCH 07/17] Add initial tests for CreateCertificateAuthority. --- cas/cloudcas/cloudcas_test.go | 244 +++++++++++++++++++++++++++------- cas/softcas/softcas.go | 4 +- go.mod | 1 + go.sum | 2 + 4 files changed, 198 insertions(+), 53 deletions(-) diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index 16e4386d..6aa67842 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -5,25 +5,34 @@ import ( "context" "crypto/rand" "crypto/x509" - "crypto/x509/pkix" "encoding/asn1" "io" + "net" "os" "reflect" "testing" "time" + lroauto "cloud.google.com/go/longrunning/autogen" + privateca "cloud.google.com/go/security/privateca/apiv1beta1" + gomock "github.com/golang/mock/gomock" "github.com/google/uuid" gax "github.com/googleapis/gax-go/v2" "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" + "google.golang.org/api/option" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" + longrunningpb "google.golang.org/genproto/googleapis/longrunning" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/anypb" ) var ( errTest = errors.New("test error") testAuthorityName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca" testCertificateName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca/certificates/test-certificate" + testProject = "test-project" + testLocation = "us-west1" testRootCertificate = `-----BEGIN CERTIFICATE----- MIIBhjCCAS2gAwIBAgIQLbKTuXau4+t3KFbGpJJAADAKBggqhkjOPQQDAjAiMSAw HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla @@ -72,6 +81,21 @@ ZGNhcxMkZDhkMThhNjgtNTI5Ni00YWYzLWFlNGItMmY4NzdkYTNmYmQ5MAoGCCqG SM49BAMCA0gAMEUCIGxl+pqJ50WYWUqK2l4V1FHoXSi0Nht5kwTxFxnWZu1xAiEA zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw= -----END CERTIFICATE-----` + testIntermediateCsr = `-----BEGIN CERTIFICATE REQUEST----- +MIIBIjCByQIBADAqMSgwJgYDVQQDEx9Hb29nbGUgQ0FTIFRlc3QgSW50ZXJtZWRp +YXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqoztio0c4XuaaGxHFiU7 +UBk3YRGTae9GtlKwyZJDk740hg6ZIoKcaXrzJT5taUpPiQLi7rP1eRui0dhl/bHo +o6A9MDsGCSqGSIb3DQEJDjEuMCwwKgYDVR0RBCMwIYIfR29vZ2xlIENBUyBUZXN0 +IEludGVybWVkaWF0ZSBDQTAKBggqhkjOPQQDAgNIADBFAiEAvRKBPE32scAvsMe8 +R7ecx91q58ZmeLaRdSzL7stsnJYCIEBu+vQUSTbUpKL2YQNclT9kbilips5pEMr3 +ojxK6mk3 +-----END CERTIFICATE REQUEST-----` + +// testIntermediateKey = `-----BEGIN EC PRIVATE KEY----- +// MHcCAQEEIMM+DSPChJgcYyqDWs0eRA5BctIo+VSNqRzCTL2ARYAqoAoGCCqGSM49 +// AwEHoUQDQgAEqoztio0c4XuaaGxHFiU7UBk3YRGTae9GtlKwyZJDk740hg6ZIoKc +// aXrzJT5taUpPiQLi7rP1eRui0dhl/bHoow== +// -----END EC PRIVATE KEY-----` ) type testClient struct { @@ -146,6 +170,18 @@ func (c *testClient) GetCertificateAuthority(ctx context.Context, req *pb.GetCer return c.certificateAuthority, c.err } +func (c *testClient) CreateCertificateAuthority(ctx context.Context, req *pb.CreateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.CreateCertificateAuthorityOperation, error) { + return nil, errors.New("use NewMockCertificateAuthorityClient") +} + +func (c *testClient) FetchCertificateAuthorityCsr(ctx context.Context, req *pb.FetchCertificateAuthorityCsrRequest, opts ...gax.CallOption) (*pb.FetchCertificateAuthorityCsrResponse, error) { + return nil, errors.New("use NewMockCertificateAuthorityClient") +} + +func (c *testClient) ActivateCertificateAuthority(ctx context.Context, req *pb.ActivateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.ActivateCertificateAuthorityOperation, error) { + return nil, errors.New("use NewMockCertificateAuthorityClient") +} + func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate { t.Helper() crt, err := parseCertificate(pemCert) @@ -179,17 +215,34 @@ func TestNew(t *testing.T) { }}, &CloudCAS{ client: &testClient{}, certificateAuthority: testAuthorityName, + project: testProject, + location: testLocation, }, false}, {"ok with credentials", args{context.Background(), apiv1.Options{ CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", }}, &CloudCAS{ client: &testClient{credentialsFile: "testdata/credentials.json"}, certificateAuthority: testAuthorityName, + project: testProject, + location: testLocation, + }, false}, + {"ok creator", args{context.Background(), apiv1.Options{ + IsCreator: true, Project: testProject, Location: testLocation, + }}, &CloudCAS{ + client: &testClient{}, + project: testProject, + location: testLocation, }, false}, {"fail certificate authority", args{context.Background(), apiv1.Options{}}, nil, true}, {"fail with credentials", args{context.Background(), apiv1.Options{ CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/error.json", }}, nil, true}, + {"fail creator project", args{context.Background(), apiv1.Options{ + IsCreator: true, Project: "", Location: testLocation, + }}, nil, true}, + {"fail creator location", args{context.Background(), apiv1.Options{ + IsCreator: true, Project: testProject, Location: "", + }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -217,6 +270,8 @@ func TestNew_register(t *testing.T) { want := &CloudCAS{ client: &testClient{credentialsFile: "testdata/credentials.json"}, certificateAuthority: testAuthorityName, + project: testProject, + location: testLocation, } newFn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.CloudCAS) @@ -675,61 +730,150 @@ func Test_getCertificateAndChain(t *testing.T) { } } -func TestCloudCAS(t *testing.T) { - cas, err := New(context.Background(), apiv1.Options{ - Type: "cloudCAS", - CertificateAuthority: "projects/smallstep-cas-test/locations/us-west1", - CredentialsFile: "/Users/mariano/smallstep-cas-test-8a068f3e4540.json", - }) +func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { + must := func(a, b interface{}) interface{} { + return a + } + + // client, close := mockTestClient() + // defer close() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mosCtrl := gomock.NewController(t) + defer mosCtrl.Finish() + + m := NewMockCertificateAuthorityClient(ctrl) + mos := NewMockOperationsServer(mosCtrl) + + // Create operation server + lis, err := net.Listen("tcp", "localhost:0") if err != nil { t.Fatal(err) } - // resp, err := cas.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ - // Type: apiv1.RootCA, - // Template: &x509.Certificate{ - // Subject: pkix.Name{ - // CommonName: "Test Mariano Root CA", - // }, - // BasicConstraintsValid: true, - // IsCA: true, - // MaxPathLen: 1, - // MaxPathLenZero: false, - // KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, - // }, - // Lifetime: time.Duration(30 * 24 * time.Hour), - // Project: "smallstep-cas-test", - // Location: "us-west1", - // }) - // if err != nil { - // t.Fatal(err) - // } - // debug(resp) - resp := &apiv1.CreateCertificateAuthorityResponse{ - Name: "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/9a593da4-61af-4426-a2f8-0650373b9c8e", + srv := grpc.NewServer() + longrunningpb.RegisterOperationsServer(srv, mos) + + go srv.Serve(lis) + defer srv.Stop() + + // Create fake privateca client + conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure()) + if err != nil { + t.Fatal(err) } - resp, err = cas.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ - Type: apiv1.IntermediateCA, - Template: &x509.Certificate{ - Subject: pkix.Name{ - Country: []string{"US"}, - CommonName: "Test Mariano Intermediate CA", - }, - BasicConstraintsValid: true, - IsCA: true, - MaxPathLen: 0, - MaxPathLenZero: true, - KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + client, err := lroauto.NewOperationsClient(context.Background(), option.WithGRPCConn(conn)) + if err != nil { + t.Fatal(err) + } + fake := &privateca.CertificateAuthorityClient{ + LROClient: client, + } + + // Configure mocks + any := gomock.Any() + + // ok root + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + PemCaCertificates: []string{testRootCertificate}, + })).(*anypb.Any), }, - Lifetime: time.Duration(24 * time.Hour), - Parent: resp, - Project: "smallstep-cas-test", - Location: "us-west1", - }) - if err != nil { - t.Fatal(err) + }, nil) + + // ok intermediate + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + })).(*anypb.Any), + }, + }, nil) + m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + PemCsr: testIntermediateCsr, + }, nil) + m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ + PemCertificate: testIntermediateCertificate, + PemCertificateChain: []string{testRootCertificate}, + }, nil) + m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "ActivateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + PemCaCertificates: []string{testIntermediateCertificate, testRootCertificate}, + })).(*anypb.Any), + }, + }, nil) + + rootCrt := mustParseCertificate(t, testRootCertificate) + intCrt := mustParseCertificate(t, testIntermediateCertificate) + + type fields struct { + client CertificateAuthorityClient + certificateAuthority string + project string + location string + } + type args struct { + req *apiv1.CreateCertificateAuthorityRequest + } + tests := []struct { + name string + fields fields + args args + want *apiv1.CreateCertificateAuthorityResponse + wantErr bool + }{ + {"ok root", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + }}, &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: rootCrt, + }, false}, + {"ok intermediate", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testIntermediateCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: rootCrt, + }, + }}, &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: intCrt, + CertificateChain: []*x509.Certificate{rootCrt}, + }, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &CloudCAS{ + client: tt.fields.client, + certificateAuthority: tt.fields.certificateAuthority, + project: tt.fields.project, + location: tt.fields.location, + } + got, err := c.CreateCertificateAuthority(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("CloudCAS.CreateCertificateAuthority() error = %+v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("CloudCAS.CreateCertificateAuthority() = %v, want %v", got, tt.want) + } + }) } - // debug(resp) - t.Error("foo") } diff --git a/cas/softcas/softcas.go b/cas/softcas/softcas.go index 16ae9547..4ece82d9 100644 --- a/cas/softcas/softcas.go +++ b/cas/softcas/softcas.go @@ -173,9 +173,7 @@ func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthori var chain []*x509.Certificate if req.Parent != nil { chain = append(chain, req.Parent.Certificate) - for _, crt := range req.Parent.CertificateChain { - chain = append(chain, crt) - } + chain = append(chain, req.Parent.CertificateChain...) } return &apiv1.CreateCertificateAuthorityResponse{ diff --git a/go.mod b/go.mod index 4a087cdc..dd15016c 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/aws/aws-sdk-go v1.30.29 github.com/go-chi/chi v4.0.2+incompatible github.com/go-piv/piv-go v1.6.0 + github.com/golang/mock v1.4.4 github.com/google/uuid v1.1.2 github.com/googleapis/gax-go/v2 v2.0.5 github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect diff --git a/go.sum b/go.sum index 7b4ea140..ba7ca75a 100644 --- a/go.sum +++ b/go.sum @@ -116,6 +116,7 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -134,6 +135,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= From 10c2ce307161379ebef4f6d3b5923d09762a26c9 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 26 Oct 2020 19:54:57 -0700 Subject: [PATCH 08/17] Add missing files, mocks created using mockgen. --- cas/cloudcas/mock_client_test.go | 157 ++++++++++++ cas/cloudcas/mock_operation_server_test.go | 270 +++++++++++++++++++++ 2 files changed, 427 insertions(+) create mode 100644 cas/cloudcas/mock_client_test.go create mode 100644 cas/cloudcas/mock_operation_server_test.go diff --git a/cas/cloudcas/mock_client_test.go b/cas/cloudcas/mock_client_test.go new file mode 100644 index 00000000..b81d3135 --- /dev/null +++ b/cas/cloudcas/mock_client_test.go @@ -0,0 +1,157 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./cas/cloudcas/cloudcas.go + +// Package cloudcas is a generated GoMock package. +package cloudcas + +import ( + privateca "cloud.google.com/go/security/privateca/apiv1beta1" + context "context" + gomock "github.com/golang/mock/gomock" + gax "github.com/googleapis/gax-go/v2" + privateca0 "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" + reflect "reflect" +) + +// MockCertificateAuthorityClient is a mock of CertificateAuthorityClient interface +type MockCertificateAuthorityClient struct { + ctrl *gomock.Controller + recorder *MockCertificateAuthorityClientMockRecorder +} + +// MockCertificateAuthorityClientMockRecorder is the mock recorder for MockCertificateAuthorityClient +type MockCertificateAuthorityClientMockRecorder struct { + mock *MockCertificateAuthorityClient +} + +// NewMockCertificateAuthorityClient creates a new mock instance +func NewMockCertificateAuthorityClient(ctrl *gomock.Controller) *MockCertificateAuthorityClient { + mock := &MockCertificateAuthorityClient{ctrl: ctrl} + mock.recorder = &MockCertificateAuthorityClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockCertificateAuthorityClient) EXPECT() *MockCertificateAuthorityClientMockRecorder { + return m.recorder +} + +// CreateCertificate mocks base method +func (m *MockCertificateAuthorityClient) CreateCertificate(ctx context.Context, req *privateca0.CreateCertificateRequest, opts ...gax.CallOption) (*privateca0.Certificate, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, req} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateCertificate", varargs...) + ret0, _ := ret[0].(*privateca0.Certificate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateCertificate indicates an expected call of CreateCertificate +func (mr *MockCertificateAuthorityClientMockRecorder) CreateCertificate(ctx, req interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, req}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCertificate", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).CreateCertificate), varargs...) +} + +// RevokeCertificate mocks base method +func (m *MockCertificateAuthorityClient) RevokeCertificate(ctx context.Context, req *privateca0.RevokeCertificateRequest, opts ...gax.CallOption) (*privateca0.Certificate, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, req} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RevokeCertificate", varargs...) + ret0, _ := ret[0].(*privateca0.Certificate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RevokeCertificate indicates an expected call of RevokeCertificate +func (mr *MockCertificateAuthorityClientMockRecorder) RevokeCertificate(ctx, req interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, req}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RevokeCertificate", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).RevokeCertificate), varargs...) +} + +// GetCertificateAuthority mocks base method +func (m *MockCertificateAuthorityClient) GetCertificateAuthority(ctx context.Context, req *privateca0.GetCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca0.CertificateAuthority, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, req} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetCertificateAuthority", varargs...) + ret0, _ := ret[0].(*privateca0.CertificateAuthority) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCertificateAuthority indicates an expected call of GetCertificateAuthority +func (mr *MockCertificateAuthorityClientMockRecorder) GetCertificateAuthority(ctx, req interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, req}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCertificateAuthority", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).GetCertificateAuthority), varargs...) +} + +// CreateCertificateAuthority mocks base method +func (m *MockCertificateAuthorityClient) CreateCertificateAuthority(ctx context.Context, req *privateca0.CreateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.CreateCertificateAuthorityOperation, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, req} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateCertificateAuthority", varargs...) + ret0, _ := ret[0].(*privateca.CreateCertificateAuthorityOperation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateCertificateAuthority indicates an expected call of CreateCertificateAuthority +func (mr *MockCertificateAuthorityClientMockRecorder) CreateCertificateAuthority(ctx, req interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, req}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCertificateAuthority", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).CreateCertificateAuthority), varargs...) +} + +// FetchCertificateAuthorityCsr mocks base method +func (m *MockCertificateAuthorityClient) FetchCertificateAuthorityCsr(ctx context.Context, req *privateca0.FetchCertificateAuthorityCsrRequest, opts ...gax.CallOption) (*privateca0.FetchCertificateAuthorityCsrResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, req} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "FetchCertificateAuthorityCsr", varargs...) + ret0, _ := ret[0].(*privateca0.FetchCertificateAuthorityCsrResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchCertificateAuthorityCsr indicates an expected call of FetchCertificateAuthorityCsr +func (mr *MockCertificateAuthorityClientMockRecorder) FetchCertificateAuthorityCsr(ctx, req interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, req}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchCertificateAuthorityCsr", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).FetchCertificateAuthorityCsr), varargs...) +} + +// ActivateCertificateAuthority mocks base method +func (m *MockCertificateAuthorityClient) ActivateCertificateAuthority(ctx context.Context, req *privateca0.ActivateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.ActivateCertificateAuthorityOperation, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, req} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ActivateCertificateAuthority", varargs...) + ret0, _ := ret[0].(*privateca.ActivateCertificateAuthorityOperation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ActivateCertificateAuthority indicates an expected call of ActivateCertificateAuthority +func (mr *MockCertificateAuthorityClientMockRecorder) ActivateCertificateAuthority(ctx, req interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, req}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ActivateCertificateAuthority", reflect.TypeOf((*MockCertificateAuthorityClient)(nil).ActivateCertificateAuthority), varargs...) +} diff --git a/cas/cloudcas/mock_operation_server_test.go b/cas/cloudcas/mock_operation_server_test.go new file mode 100644 index 00000000..48564cd1 --- /dev/null +++ b/cas/cloudcas/mock_operation_server_test.go @@ -0,0 +1,270 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: /Users/mariano/go/pkg/mod/google.golang.org/genproto@v0.0.0-20200904004341-0bd0a958aa1d/googleapis/longrunning/operations.pb.go + +// Package cloudcas is a generated GoMock package. +package cloudcas + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + longrunning "google.golang.org/genproto/googleapis/longrunning" + grpc "google.golang.org/grpc" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" +) + +// MockisOperation_Result is a mock of isOperation_Result interface +type MockisOperation_Result struct { + ctrl *gomock.Controller + recorder *MockisOperation_ResultMockRecorder +} + +// MockisOperation_ResultMockRecorder is the mock recorder for MockisOperation_Result +type MockisOperation_ResultMockRecorder struct { + mock *MockisOperation_Result +} + +// NewMockisOperation_Result creates a new mock instance +func NewMockisOperation_Result(ctrl *gomock.Controller) *MockisOperation_Result { + mock := &MockisOperation_Result{ctrl: ctrl} + mock.recorder = &MockisOperation_ResultMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockisOperation_Result) EXPECT() *MockisOperation_ResultMockRecorder { + return m.recorder +} + +// isOperation_Result mocks base method +func (m *MockisOperation_Result) isOperation_Result() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "isOperation_Result") +} + +// isOperation_Result indicates an expected call of isOperation_Result +func (mr *MockisOperation_ResultMockRecorder) isOperation_Result() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "isOperation_Result", reflect.TypeOf((*MockisOperation_Result)(nil).isOperation_Result)) +} + +// MockOperationsClient is a mock of OperationsClient interface +type MockOperationsClient struct { + ctrl *gomock.Controller + recorder *MockOperationsClientMockRecorder +} + +// MockOperationsClientMockRecorder is the mock recorder for MockOperationsClient +type MockOperationsClientMockRecorder struct { + mock *MockOperationsClient +} + +// NewMockOperationsClient creates a new mock instance +func NewMockOperationsClient(ctrl *gomock.Controller) *MockOperationsClient { + mock := &MockOperationsClient{ctrl: ctrl} + mock.recorder = &MockOperationsClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockOperationsClient) EXPECT() *MockOperationsClientMockRecorder { + return m.recorder +} + +// ListOperations mocks base method +func (m *MockOperationsClient) ListOperations(ctx context.Context, in *longrunning.ListOperationsRequest, opts ...grpc.CallOption) (*longrunning.ListOperationsResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListOperations", varargs...) + ret0, _ := ret[0].(*longrunning.ListOperationsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListOperations indicates an expected call of ListOperations +func (mr *MockOperationsClientMockRecorder) ListOperations(ctx, in interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOperations", reflect.TypeOf((*MockOperationsClient)(nil).ListOperations), varargs...) +} + +// GetOperation mocks base method +func (m *MockOperationsClient) GetOperation(ctx context.Context, in *longrunning.GetOperationRequest, opts ...grpc.CallOption) (*longrunning.Operation, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetOperation", varargs...) + ret0, _ := ret[0].(*longrunning.Operation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOperation indicates an expected call of GetOperation +func (mr *MockOperationsClientMockRecorder) GetOperation(ctx, in interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOperation", reflect.TypeOf((*MockOperationsClient)(nil).GetOperation), varargs...) +} + +// DeleteOperation mocks base method +func (m *MockOperationsClient) DeleteOperation(ctx context.Context, in *longrunning.DeleteOperationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteOperation", varargs...) + ret0, _ := ret[0].(*emptypb.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteOperation indicates an expected call of DeleteOperation +func (mr *MockOperationsClientMockRecorder) DeleteOperation(ctx, in interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOperation", reflect.TypeOf((*MockOperationsClient)(nil).DeleteOperation), varargs...) +} + +// CancelOperation mocks base method +func (m *MockOperationsClient) CancelOperation(ctx context.Context, in *longrunning.CancelOperationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CancelOperation", varargs...) + ret0, _ := ret[0].(*emptypb.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CancelOperation indicates an expected call of CancelOperation +func (mr *MockOperationsClientMockRecorder) CancelOperation(ctx, in interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelOperation", reflect.TypeOf((*MockOperationsClient)(nil).CancelOperation), varargs...) +} + +// WaitOperation mocks base method +func (m *MockOperationsClient) WaitOperation(ctx context.Context, in *longrunning.WaitOperationRequest, opts ...grpc.CallOption) (*longrunning.Operation, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, in} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "WaitOperation", varargs...) + ret0, _ := ret[0].(*longrunning.Operation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WaitOperation indicates an expected call of WaitOperation +func (mr *MockOperationsClientMockRecorder) WaitOperation(ctx, in interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, in}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitOperation", reflect.TypeOf((*MockOperationsClient)(nil).WaitOperation), varargs...) +} + +// MockOperationsServer is a mock of OperationsServer interface +type MockOperationsServer struct { + ctrl *gomock.Controller + recorder *MockOperationsServerMockRecorder +} + +// MockOperationsServerMockRecorder is the mock recorder for MockOperationsServer +type MockOperationsServerMockRecorder struct { + mock *MockOperationsServer +} + +// NewMockOperationsServer creates a new mock instance +func NewMockOperationsServer(ctrl *gomock.Controller) *MockOperationsServer { + mock := &MockOperationsServer{ctrl: ctrl} + mock.recorder = &MockOperationsServerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockOperationsServer) EXPECT() *MockOperationsServerMockRecorder { + return m.recorder +} + +// ListOperations mocks base method +func (m *MockOperationsServer) ListOperations(arg0 context.Context, arg1 *longrunning.ListOperationsRequest) (*longrunning.ListOperationsResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListOperations", arg0, arg1) + ret0, _ := ret[0].(*longrunning.ListOperationsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListOperations indicates an expected call of ListOperations +func (mr *MockOperationsServerMockRecorder) ListOperations(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListOperations", reflect.TypeOf((*MockOperationsServer)(nil).ListOperations), arg0, arg1) +} + +// GetOperation mocks base method +func (m *MockOperationsServer) GetOperation(arg0 context.Context, arg1 *longrunning.GetOperationRequest) (*longrunning.Operation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOperation", arg0, arg1) + ret0, _ := ret[0].(*longrunning.Operation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOperation indicates an expected call of GetOperation +func (mr *MockOperationsServerMockRecorder) GetOperation(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOperation", reflect.TypeOf((*MockOperationsServer)(nil).GetOperation), arg0, arg1) +} + +// DeleteOperation mocks base method +func (m *MockOperationsServer) DeleteOperation(arg0 context.Context, arg1 *longrunning.DeleteOperationRequest) (*emptypb.Empty, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteOperation", arg0, arg1) + ret0, _ := ret[0].(*emptypb.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteOperation indicates an expected call of DeleteOperation +func (mr *MockOperationsServerMockRecorder) DeleteOperation(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOperation", reflect.TypeOf((*MockOperationsServer)(nil).DeleteOperation), arg0, arg1) +} + +// CancelOperation mocks base method +func (m *MockOperationsServer) CancelOperation(arg0 context.Context, arg1 *longrunning.CancelOperationRequest) (*emptypb.Empty, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CancelOperation", arg0, arg1) + ret0, _ := ret[0].(*emptypb.Empty) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CancelOperation indicates an expected call of CancelOperation +func (mr *MockOperationsServerMockRecorder) CancelOperation(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelOperation", reflect.TypeOf((*MockOperationsServer)(nil).CancelOperation), arg0, arg1) +} + +// WaitOperation mocks base method +func (m *MockOperationsServer) WaitOperation(arg0 context.Context, arg1 *longrunning.WaitOperationRequest) (*longrunning.Operation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WaitOperation", arg0, arg1) + ret0, _ := ret[0].(*longrunning.Operation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WaitOperation indicates an expected call of WaitOperation +func (mr *MockOperationsServerMockRecorder) WaitOperation(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitOperation", reflect.TypeOf((*MockOperationsServer)(nil).WaitOperation), arg0, arg1) +} From b275758018653864a2cc05040003a5e7d682172d Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 27 Oct 2020 17:59:32 -0700 Subject: [PATCH 09/17] Complete CloudCAS tests. Upgrade cloud.google.com/go --- cas/cloudcas/certificate.go | 29 ++- cas/cloudcas/certificate_test.go | 74 ++++++ cas/cloudcas/cloudcas.go | 31 +-- cas/cloudcas/cloudcas_test.go | 409 ++++++++++++++++++++++++++++--- go.mod | 6 +- go.sum | 52 ++-- 6 files changed, 508 insertions(+), 93 deletions(-) diff --git a/cas/cloudcas/certificate.go b/cas/cloudcas/certificate.go index ba823d86..d7789992 100644 --- a/cas/cloudcas/certificate.go +++ b/cas/cloudcas/certificate.go @@ -343,6 +343,16 @@ func createKeyVersionSpec(alg kmsapi.SignatureAlgorithm, bits int) (*pb.Certific Algorithm: pb.CertificateAuthority_EC_P384_SHA384, }, }, nil + case kmsapi.SHA256WithRSA: + algo, err := getRSAPKCS1Algorithm(bits) + if err != nil { + return nil, err + } + return &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: algo, + }, + }, nil case kmsapi.SHA256WithRSAPSS: algo, err := getRSAPSSAlgorithm(bits) if err != nil { @@ -358,14 +368,27 @@ func createKeyVersionSpec(alg kmsapi.SignatureAlgorithm, bits int) (*pb.Certific } } +func getRSAPKCS1Algorithm(bits int) (pb.CertificateAuthority_SignHashAlgorithm, error) { + switch bits { + case 0, 3072: + return pb.CertificateAuthority_RSA_PKCS1_3072_SHA256, nil + case 2048: + return pb.CertificateAuthority_RSA_PKCS1_2048_SHA256, nil + case 4096: + return pb.CertificateAuthority_RSA_PKCS1_4096_SHA256, nil + default: + return 0, fmt.Errorf("unsupported RSA PKCS #1 key size '%d'", bits) + } +} + func getRSAPSSAlgorithm(bits int) (pb.CertificateAuthority_SignHashAlgorithm, error) { switch bits { case 0, 3072: - return pb.CertificateAuthority_RSA_PSS_3072_SHA_256, nil + return pb.CertificateAuthority_RSA_PSS_3072_SHA256, nil case 2048: - return pb.CertificateAuthority_RSA_PSS_2048_SHA_256, nil + return pb.CertificateAuthority_RSA_PSS_2048_SHA256, nil case 4096: - return pb.CertificateAuthority_RSA_PSS_4096_SHA_256, nil + return pb.CertificateAuthority_RSA_PSS_4096_SHA256, nil default: return 0, fmt.Errorf("unsupported RSA-PSS key size '%d'", bits) } diff --git a/cas/cloudcas/certificate_test.go b/cas/cloudcas/certificate_test.go index 4f30ea79..0822e4c1 100644 --- a/cas/cloudcas/certificate_test.go +++ b/cas/cloudcas/certificate_test.go @@ -14,6 +14,7 @@ import ( "reflect" "testing" + kmsapi "github.com/smallstep/certificates/kms/apiv1" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -548,3 +549,76 @@ func Test_isExtraExtension(t *testing.T) { }) } } + +func Test_createKeyVersionSpec(t *testing.T) { + type args struct { + alg kmsapi.SignatureAlgorithm + bits int + } + tests := []struct { + name string + args args + want *pb.CertificateAuthority_KeyVersionSpec + wantErr bool + }{ + {"ok P256", args{0, 0}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_EC_P256_SHA256, + }}, false}, + {"ok P256", args{kmsapi.ECDSAWithSHA256, 0}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_EC_P256_SHA256, + }}, false}, + {"ok P384", args{kmsapi.ECDSAWithSHA384, 0}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_EC_P384_SHA384, + }}, false}, + {"ok RSA default", args{kmsapi.SHA256WithRSA, 0}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_RSA_PKCS1_3072_SHA256, + }}, false}, + {"ok RSA 2048", args{kmsapi.SHA256WithRSA, 2048}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_RSA_PKCS1_2048_SHA256, + }}, false}, + {"ok RSA 3072", args{kmsapi.SHA256WithRSA, 3072}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_RSA_PKCS1_3072_SHA256, + }}, false}, + {"ok RSA 4096", args{kmsapi.SHA256WithRSA, 4096}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_RSA_PKCS1_4096_SHA256, + }}, false}, + {"ok RSA-PSS default", args{kmsapi.SHA256WithRSAPSS, 0}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_RSA_PSS_3072_SHA256, + }}, false}, + {"ok RSA-PSS 2048", args{kmsapi.SHA256WithRSAPSS, 2048}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_RSA_PSS_2048_SHA256, + }}, false}, + {"ok RSA-PSS 3072", args{kmsapi.SHA256WithRSAPSS, 3072}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_RSA_PSS_3072_SHA256, + }}, false}, + {"ok RSA-PSS 4096", args{kmsapi.SHA256WithRSAPSS, 4096}, &pb.CertificateAuthority_KeyVersionSpec{ + KeyVersion: &pb.CertificateAuthority_KeyVersionSpec_Algorithm{ + Algorithm: pb.CertificateAuthority_RSA_PSS_4096_SHA256, + }}, false}, + {"fail Ed25519", args{kmsapi.PureEd25519, 0}, nil, true}, + {"fail RSA size", args{kmsapi.SHA256WithRSA, 1024}, nil, true}, + {"fail RSA-PSS size", args{kmsapi.SHA256WithRSAPSS, 1024}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := createKeyVersionSpec(tt.args.alg, tt.args.bits) + if (err != nil) != tt.wantErr { + t.Errorf("createKeyVersionSpec() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("createKeyVersionSpec() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cas/cloudcas/cloudcas.go b/cas/cloudcas/cloudcas.go index 087febc7..4fb75b3a 100644 --- a/cas/cloudcas/cloudcas.go +++ b/cas/cloudcas/cloudcas.go @@ -262,6 +262,16 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor return nil, errors.New("createCertificateAuthorityRequest `parent.name` cannot be empty") } + var caType pb.CertificateAuthority_Type + switch req.Type { + case apiv1.RootCA: + caType = pb.CertificateAuthority_SELF_SIGNED + case apiv1.IntermediateCA: + caType = pb.CertificateAuthority_SUBORDINATE + default: + return nil, errors.Errorf("createCertificateAuthorityRequest `type=%d' is invalid or not supported", req.Type) + } + // Select key and signature algorithm to use var err error var keySpec *pb.CertificateAuthority_KeyVersionSpec @@ -276,17 +286,17 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor } // Normalize or generate id. - certificateAuthorityID := normalizeCertificateAuthorityName(req.Name) - if certificateAuthorityID == "" { + caID := normalizeCertificateAuthorityName(req.Name) + if caID == "" { id, err := createCertificateID() if err != nil { return nil, err } - certificateAuthorityID = id + caID = id } // Add CertificateAuthority extension - casExtension, err := apiv1.CreateCertificateAuthorityExtension(apiv1.CloudCAS, certificateAuthorityID) + casExtension, err := apiv1.CreateCertificateAuthorityExtension(apiv1.CloudCAS, caID) if err != nil { return nil, err } @@ -295,10 +305,10 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor // Prepare CreateCertificateAuthorityRequest pbReq := &pb.CreateCertificateAuthorityRequest{ Parent: "projects/" + c.project + "/locations/" + c.location, - CertificateAuthorityId: certificateAuthorityID, + CertificateAuthorityId: caID, RequestId: req.RequestID, CertificateAuthority: &pb.CertificateAuthority{ - Type: pb.CertificateAuthority_TYPE_UNSPECIFIED, + Type: caType, Tier: pb.CertificateAuthority_ENTERPRISE, Config: &pb.CertificateConfig{ SubjectConfig: &pb.CertificateConfig_SubjectConfig{ @@ -317,15 +327,6 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor }, } - switch req.Type { - case apiv1.RootCA: - pbReq.CertificateAuthority.Type = pb.CertificateAuthority_SELF_SIGNED - case apiv1.IntermediateCA: - pbReq.CertificateAuthority.Type = pb.CertificateAuthority_SUBORDINATE - default: - return nil, errors.Errorf("createCertificateAuthorityRequest `type=%d' is invalid or not supported", req.Type) - } - // Create certificate authority. ctx, cancel := defaultContext() defer cancel() diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index 6aa67842..2383059d 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -3,9 +3,14 @@ package cloudcas import ( "bytes" "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" "crypto/rand" "crypto/x509" "encoding/asn1" + "encoding/pem" + "fmt" "io" "net" "os" @@ -20,6 +25,7 @@ import ( gax "github.com/googleapis/gax-go/v2" "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" + kmsapi "github.com/smallstep/certificates/kms/apiv1" "google.golang.org/api/option" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" longrunningpb "google.golang.org/genproto/googleapis/longrunning" @@ -34,27 +40,25 @@ var ( testProject = "test-project" testLocation = "us-west1" testRootCertificate = `-----BEGIN CERTIFICATE----- -MIIBhjCCAS2gAwIBAgIQLbKTuXau4+t3KFbGpJJAADAKBggqhkjOPQQDAjAiMSAw -HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla -Fw0zMDA5MTIyMjQ4NDlaMCIxIDAeBgNVBAMTF0dvb2dsZSBDQVMgVGVzdCBSb290 -IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYKGgQ3/0D7+oBTc0CXoYfSC6 -M8hOqLsmzBapPZSYpfwjgEsjdNU84jdrYmW1zF1+p+MrL4c7qJv9NLo/picCuqNF -MEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE -FFVn9V7Qymd7cUJh9KAhnUDAQL5YMAoGCCqGSM49BAMCA0cAMEQCIA4LzttYoT3u -8TYgSrvFT+Z+cklfi4UrPBU6aSbcUaW2AiAPfaqbyccQT3CxMVyHg+xZZjAirZp8 -lAeA/T4FxAonHA== +MIIBeDCCAR+gAwIBAgIQcXWWjtSZ/PAyH8D1Ou4L9jAKBggqhkjOPQQDAjAbMRkw +FwYDVQQDExBDbG91ZENBUyBSb290IENBMB4XDTIwMTAyNzIyNTM1NFoXDTMwMTAy +NzIyNTM1NFowGzEZMBcGA1UEAxMQQ2xvdWRDQVMgUm9vdCBDQTBZMBMGByqGSM49 +AgEGCCqGSM49AwEHA0IABIySHA4b78Yu4LuGhZIlv/PhNwXz4ZoV1OUZQ0LrK3vj +B13O12DLZC5uj1z3kxdQzXUttSbtRv49clMpBiTpsZKjRTBDMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSZ+t9RMHbFTl5BatM3 +5bJlHPOu3DAKBggqhkjOPQQDAgNHADBEAiASah6gg0tVM3WI0meCQ4SEKk7Mjhbv ++SmhuZHWV1QlXQIgRXNyWcpVUrAoG6Uy1KQg07LDpF5dFeK9InrDxSJAkVo= -----END CERTIFICATE-----` testIntermediateCertificate = `-----BEGIN CERTIFICATE----- -MIIBsDCCAVagAwIBAgIQOb91kHxWKVzSJ9ESW1ViVzAKBggqhkjOPQQDAjAiMSAw -HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla -Fw0zMDA5MTIyMjQ4NDlaMCoxKDAmBgNVBAMTH0dvb2dsZSBDQVMgVGVzdCBJbnRl -cm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASUHN1cNyId4Ei/ -4MxD5VrZFc51P50caMUdDZVrPveidChBYCU/9IM6vnRlZHx2HLjQ0qAvqHwY3rT0 -xc7n+PfCo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAd -BgNVHQ4EFgQUSDlasiw0pRKyS7llhL0ZuVFNa9UwHwYDVR0jBBgwFoAUVWf1XtDK -Z3txQmH0oCGdQMBAvlgwCgYIKoZIzj0EAwIDSAAwRQIgMmsLcoC4KriXw+s+cZx2 -bJMf6Mx/WESj31buJJhpzY0CIQCBUa/JtvS3nyce/4DF5tK2v49/NWHREgqAaZ57 -DcYyHQ== +MIIBpDCCAUmgAwIBAgIRALLKxnxyl0GBeKevIcbx02wwCgYIKoZIzj0EAwIwGzEZ +MBcGA1UEAxMQQ2xvdWRDQVMgUm9vdCBDQTAeFw0yMDEwMjcyMjUzNTRaFw0zMDEw +MjcyMjUzNTRaMCMxITAfBgNVBAMTGENsb3VkQ0FTIEludGVybWVkaWF0ZSBDQTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABPLuqxgBY+QmaXc8zKIC8FMgjJ6dF/cL +b+Dig0XKc5GH/T1ORrhgOkRayrQcjPMu+jkjg25qn6vvp43LRtUKPXOjZjBkMA4G +A1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ8RVQI +VgXAmRNDX8qItalVpSBEGjAfBgNVHSMEGDAWgBSZ+t9RMHbFTl5BatM35bJlHPOu +3DAKBggqhkjOPQQDAgNJADBGAiEA70MVYVqjm8SBHJf5cOlWfiXXOfHUsctTJ+/F +pLsKBogCIQDJJkoQqYl9B59Dq3zydl8bpJevQxsoaa4Wqg+ZBMkvbQ== -----END CERTIFICATE-----` testLeafCertificate = `-----BEGIN CERTIFICATE----- MIIB1jCCAX2gAwIBAgIQQfOn+COMeuD8VYF1TiDkEzAKBggqhkjOPQQDAjAqMSgw @@ -82,20 +86,22 @@ SM49BAMCA0gAMEUCIGxl+pqJ50WYWUqK2l4V1FHoXSi0Nht5kwTxFxnWZu1xAiEA zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw= -----END CERTIFICATE-----` testIntermediateCsr = `-----BEGIN CERTIFICATE REQUEST----- -MIIBIjCByQIBADAqMSgwJgYDVQQDEx9Hb29nbGUgQ0FTIFRlc3QgSW50ZXJtZWRp -YXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqoztio0c4XuaaGxHFiU7 -UBk3YRGTae9GtlKwyZJDk740hg6ZIoKcaXrzJT5taUpPiQLi7rP1eRui0dhl/bHo -o6A9MDsGCSqGSIb3DQEJDjEuMCwwKgYDVR0RBCMwIYIfR29vZ2xlIENBUyBUZXN0 -IEludGVybWVkaWF0ZSBDQTAKBggqhkjOPQQDAgNIADBFAiEAvRKBPE32scAvsMe8 -R7ecx91q58ZmeLaRdSzL7stsnJYCIEBu+vQUSTbUpKL2YQNclT9kbilips5pEMr3 -ojxK6mk3 +MIHeMIGFAgEAMCMxITAfBgNVBAMTGENsb3VkQ0FTIEludGVybWVkaWF0ZSBDQTBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABPLuqxgBY+QmaXc8zKIC8FMgjJ6dF/cL +b+Dig0XKc5GH/T1ORrhgOkRayrQcjPMu+jkjg25qn6vvp43LRtUKPXOgADAKBggq +hkjOPQQDAgNIADBFAiEAn3pkYXb2OzoQZ+AExFqd7qZ7pg2nyP2kBZZ01Pl8KfcC +IHKplBXDR79/i7kjOtv1iWfgf5S/XQHrz178gXA0YQe7 -----END CERTIFICATE REQUEST-----` - -// testIntermediateKey = `-----BEGIN EC PRIVATE KEY----- -// MHcCAQEEIMM+DSPChJgcYyqDWs0eRA5BctIo+VSNqRzCTL2ARYAqoAoGCCqGSM49 -// AwEHoUQDQgAEqoztio0c4XuaaGxHFiU7UBk3YRGTae9GtlKwyZJDk740hg6ZIoKc -// aXrzJT5taUpPiQLi7rP1eRui0dhl/bHoow== -// -----END EC PRIVATE KEY-----` + testRootKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIN51Rgg6YcQVLeCRzumdw4pjM3VWqFIdCbnsV3Up1e/goAoGCCqGSM49 +AwEHoUQDQgAEjJIcDhvvxi7gu4aFkiW/8+E3BfPhmhXU5RlDQusre+MHXc7XYMtk +Lm6PXPeTF1DNdS21Ju1G/j1yUykGJOmxkg== +-----END EC PRIVATE KEY-----` + testIntermediateKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIMMX/XkXGnRDD4fYu7Z4rHACdJn/iyOy2UTwsv+oZ0C+oAoGCCqGSM49 +AwEHoUQDQgAE8u6rGAFj5CZpdzzMogLwUyCMnp0X9wtv4OKDRcpzkYf9PU5GuGA6 +RFrKtByM8y76OSODbmqfq++njctG1Qo9cw== +-----END EC PRIVATE KEY-----` ) type testClient struct { @@ -158,6 +164,29 @@ func setTeeReader(t *testing.T, w *bytes.Buffer) { rand.Reader = io.TeeReader(reader, w) } +type badSigner struct { + pub crypto.PublicKey +} + +func createBadSigner(t *testing.T) *badSigner { + t.Helper() + pub, _, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + return &badSigner{ + pub: pub, + } +} + +func (b *badSigner) Public() crypto.PublicKey { + return b.pub +} + +func (b *badSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + return nil, fmt.Errorf("💥") +} + func (c *testClient) CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) { return c.certificate, c.err } @@ -191,6 +220,19 @@ func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate { return crt } +func mustParseECKey(t *testing.T, pemKey string) *ecdsa.PrivateKey { + t.Helper() + block, _ := pem.Decode([]byte(pemKey)) + if block == nil { + t.Fatal("failed to parse key") + } + key, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + t.Fatal(err) + } + return key +} + func TestNew(t *testing.T) { tmp := newCertificateAuthorityClient newCertificateAuthorityClient = func(ctx context.Context, credentialsFile string) (CertificateAuthorityClient, error) { @@ -233,7 +275,10 @@ func TestNew(t *testing.T) { project: testProject, location: testLocation, }, false}, - {"fail certificate authority", args{context.Background(), apiv1.Options{}}, nil, true}, + {"fail certificate authority", args{context.Background(), apiv1.Options{ + CertificateAuthority: "projects/ok1234/locations/ok1234/certificateAuthorities/ok1234/bad", + }}, nil, true}, + {"fail certificate authority regex", args{context.Background(), apiv1.Options{}}, nil, true}, {"fail with credentials", args{context.Background(), apiv1.Options{ CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/error.json", }}, nil, true}, @@ -735,8 +780,6 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { return a } - // client, close := mockTestClient() - // defer close() ctrl := gomock.NewController(t) defer ctrl.Finish() mosCtrl := gomock.NewController(t) @@ -801,6 +844,31 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ PemCsr: testIntermediateCsr, }, nil) + m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "ActivateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + PemCaCertificates: []string{testIntermediateCertificate, testRootCertificate}, + })).(*anypb.Any), + }, + }, nil) + // ok intermediate local signer + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + })).(*anypb.Any), + }, + }, nil) + m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + PemCsr: testIntermediateCsr, + }, nil) m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ PemCertificate: testIntermediateCertificate, PemCertificateChain: []string{testRootCertificate}, @@ -817,6 +885,126 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { }, }, nil) + // ok create key + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + PemCaCertificates: []string{testRootCertificate}, + })).(*anypb.Any), + }, + }, nil) + + // fail CreateCertificateAuthority + m.EXPECT().CreateCertificateAuthority(any, any).Return(nil, errTest) + + // fail CreateCertificateAuthority.Wait + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(nil, errTest) + + // fail FetchCertificateAuthorityCsr + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + })).(*anypb.Any), + }, + }, nil) + m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(nil, errTest) + + // fail CreateCertificate + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + })).(*anypb.Any), + }, + }, nil) + m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + PemCsr: testIntermediateCsr, + }, nil) + m.EXPECT().CreateCertificate(any, any).Return(nil, errTest) + + // fail ActivateCertificateAuthority + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + })).(*anypb.Any), + }, + }, nil) + m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + PemCsr: testIntermediateCsr, + }, nil) + m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ + PemCertificate: testIntermediateCertificate, + PemCertificateChain: []string{testRootCertificate}, + }, nil) + m.EXPECT().ActivateCertificateAuthority(any, any).Return(nil, errTest) + + // fail ActivateCertificateAuthority.Wait + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + })).(*anypb.Any), + }, + }, nil) + m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + PemCsr: testIntermediateCsr, + }, nil) + m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{ + PemCertificate: testIntermediateCertificate, + PemCertificateChain: []string{testRootCertificate}, + }, nil) + m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(nil, errTest) + + // fail x509util.CreateCertificate + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + })).(*anypb.Any), + }, + }, nil) + m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + PemCsr: testIntermediateCsr, + }, nil) + + // fail parseCertificateRequest + m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil) + mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ + Name: "CreateCertificateAuthority", + Done: true, + Result: &longrunningpb.Operation_Response{ + Response: must(anypb.New(&pb.CertificateAuthority{ + Name: testAuthorityName, + })).(*anypb.Any), + }, + }, nil) + m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{ + PemCsr: "Not a CSR", + }, nil) + rootCrt := mustParseCertificate(t, testRootCertificate) intCrt := mustParseCertificate(t, testIntermediateCertificate) @@ -857,6 +1045,136 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { Certificate: intCrt, CertificateChain: []*x509.Certificate{rootCrt}, }, false}, + {"ok intermediate local signer", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testIntermediateCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Certificate: rootCrt, + Signer: mustParseECKey(t, testRootKey), + }, + }}, &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: intCrt, + CertificateChain: []*x509.Certificate{rootCrt}, + }, false}, + {"ok create key", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + CreateKey: &kmsapi.CreateKeyRequest{ + SignatureAlgorithm: kmsapi.ECDSAWithSHA256, + }, + }}, &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: rootCrt, + }, false}, + {"fail project", fields{m, "", "", testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail location", fields{m, "", testProject, ""}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail template", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail lifetime", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: mustParseCertificate(t, testRootCertificate), + }}, nil, true}, + {"fail parent", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail parent name", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{}, + }}, nil, true}, + {"fail type", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: 0, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail create key", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + CreateKey: &kmsapi.CreateKeyRequest{ + SignatureAlgorithm: kmsapi.PureEd25519, + }, + }}, nil, true}, + {"fail CreateCertificateAuthority", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail CreateCertificateAuthority.Wait", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: mustParseCertificate(t, testRootCertificate), + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail FetchCertificateAuthorityCsr", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testIntermediateCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: rootCrt, + }, + }}, nil, true}, + {"fail CreateCertificate", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testIntermediateCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: rootCrt, + }, + }}, nil, true}, + {"fail ActivateCertificateAuthority", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testIntermediateCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: rootCrt, + }, + }}, nil, true}, + {"fail ActivateCertificateAuthority.Wait", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testIntermediateCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Name: testAuthorityName, + Certificate: rootCrt, + }, + }}, nil, true}, + {"fail x509util.CreateCertificate", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testIntermediateCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Certificate: rootCrt, + Signer: createBadSigner(t), + }, + }}, nil, true}, + {"fail parseCertificateRequest", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: mustParseCertificate(t, testIntermediateCertificate), + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Certificate: rootCrt, + Signer: createBadSigner(t), + }, + }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -877,3 +1195,24 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { }) } } + +func Test_normalizeCertificateAuthorityName(t *testing.T) { + type args struct { + name string + } + tests := []struct { + name string + args args + want string + }{ + {"ok", args{"Test-CA-Name_1234"}, "Test-CA-Name_1234"}, + {"change", args{"💥 CA"}, "--CA"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := normalizeCertificateAuthorityName(tt.args.name); got != tt.want { + t.Errorf("normalizeCertificateAuthorityName() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/go.mod b/go.mod index dd15016c..8d48913e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/smallstep/certificates go 1.14 require ( - cloud.google.com/go v0.65.1-0.20200904011802-3c2db50b5678 + cloud.google.com/go v0.70.0 github.com/Masterminds/sprig/v3 v3.1.0 github.com/aws/aws-sdk-go v1.30.29 github.com/go-chi/chi v4.0.2+incompatible @@ -28,8 +28,8 @@ require ( >>>>>>> Update go.step.sm/crypto dependency. golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/net v0.0.0-20201021035429-f5854403a974 - google.golang.org/api v0.31.0 - google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d + google.golang.org/api v0.33.0 + google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 google.golang.org/grpc v1.32.0 google.golang.org/protobuf v1.25.0 gopkg.in/square/go-jose.v2 v2.5.1 diff --git a/go.sum b/go.sum index ba7ca75a..835b926a 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.65.1-0.20200904011802-3c2db50b5678 h1:5YqZUrIf2QELwPqw1kLpGIE0z0I++b7HhzSNKjZlIY0= cloud.google.com/go v0.65.1-0.20200904011802-3c2db50b5678/go.mod h1:Ihp2NV3Qr9BWHCDNA8LXF9fZ1HGBl6Jx1xd7KP3nxkI= +cloud.google.com/go v0.70.0 h1:ujhG1RejZYi+HYfJNlgBh3j/bVKD8DewM7AkJ5UPyBc= +cloud.google.com/go v0.70.0/go.mod h1:/UTKYRQTWjVnSe7nGvoSzxEFUELzSI/yAYd0JQT6cRo= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -136,6 +138,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -160,6 +163,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201009210932-67992a1a5a35/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -207,12 +211,7 @@ github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRU github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -<<<<<<< HEAD github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -======= -github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= ->>>>>>> Update go.step.sm/crypto dependency. github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= @@ -251,13 +250,6 @@ github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5 h1:lX6ybsQW9Agn3q github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 h1:unQFBIznI+VYD1/1fApl1A+9VcBk+9dcqGfnePY87LY= github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc= -<<<<<<< HEAD -======= -github.com/smallstep/certificates v0.15.5/go.mod h1:T0L78wJmhdj1AyrfrNG5mPP2MG15cRM/9d1frw8NnJ0= -github.com/smallstep/certinfo v1.4.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU= -github.com/smallstep/cli v0.15.3 h1:t9LBO53PK4SEG6FSo0UOxI306m9pHb+h632oFZe+ZwA= -github.com/smallstep/cli v0.15.3/go.mod h1:KBcpj/m28/uhK+lH0/ctbncZqWQvxDFWk8T2cUyeFg0= ->>>>>>> Update go.step.sm/crypto dependency. github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U= github.com/smallstep/nosql v0.3.0/go.mod h1:QG7gNOpidifn99MjZaiNbm7HPesIyBd97F/OfacNz8Q= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -277,16 +269,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -<<<<<<< HEAD -======= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= -github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= ->>>>>>> Update go.step.sm/crypto dependency. github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -308,21 +290,6 @@ go.step.sm/cli-utils v0.1.0 h1:uuQ73MuAh5P5Eg+3zfqwrtlTLx5DWSfGqGCrSSjYqdk= go.step.sm/cli-utils v0.1.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y= go.step.sm/crypto v0.6.1 h1:nJoRFGrGNf/mKVVMdWnfLbBfIFt/z4NdJlSL5nipQMQ= go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= -<<<<<<< HEAD -======= -go.step.sm/crypto v0.7.0 h1:azKRI4CBRzDbhHsLAnvzvGJ0aVbGI+wrh2COrPd/mks= -go.step.sm/crypto v0.7.0/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= ->>>>>>> Update go.step.sm/crypto dependency. golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -395,6 +362,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -404,6 +372,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0H golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -445,6 +415,7 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4 h1:kCCpuwSAoYJPkNc6x0xT9yTtV4oKtARo4RGBQWOfg9E= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -501,6 +472,9 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200903185744-af4cc2cd812e h1:RvNtqusJ+6DJ07/by/M84a6/Dd17XU6n8QvhvknjJno= golang.org/x/tools v0.0.0-20200903185744-af4cc2cd812e/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ= +golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -526,6 +500,8 @@ google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.31.0 h1:1w5Sz/puhxFo9lTtip2n47k7toB/U2nCqOKNHd3Yrbo= google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= +google.golang.org/api v0.33.0 h1:+gL0XvACeMIvpwLZ5rQZzLn5cwOsgg8dIcfJ2SYfBVw= +google.golang.org/api v0.33.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -568,6 +544,8 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 h1:bFFRpT+e8JJVY7lMMfvezL1ZIwqiwmPl2bsE2yx4HqM= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= From bb4f2aef2f5568d80f1204ad9e140088b2b141f6 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 27 Oct 2020 18:03:02 -0700 Subject: [PATCH 10/17] Fix lint error. --- cas/cloudcas/cloudcas_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index 2383059d..494763c5 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -97,6 +97,7 @@ MHcCAQEEIN51Rgg6YcQVLeCRzumdw4pjM3VWqFIdCbnsV3Up1e/goAoGCCqGSM49 AwEHoUQDQgAEjJIcDhvvxi7gu4aFkiW/8+E3BfPhmhXU5RlDQusre+MHXc7XYMtk Lm6PXPeTF1DNdS21Ju1G/j1yUykGJOmxkg== -----END EC PRIVATE KEY-----` + // nolint:unused,deadcode testIntermediateKey = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIMMX/XkXGnRDD4fYu7Z4rHACdJn/iyOy2UTwsv+oZ0C+oAoGCCqGSM49 AwEHoUQDQgAE8u6rGAFj5CZpdzzMogLwUyCMnp0X9wtv4OKDRcpzkYf9PU5GuGA6 From 7aa8a8fe1e842579017e0d06630e9196ec374c43 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 27 Oct 2020 18:45:49 -0700 Subject: [PATCH 11/17] Complete tests for softCAS. --- cas/softcas/softcas_test.go | 212 ++++++++++++++++++++++++++++++++++-- 1 file changed, 204 insertions(+), 8 deletions(-) diff --git a/cas/softcas/softcas_test.go b/cas/softcas/softcas_test.go index 1fd8248a..258ebf5c 100644 --- a/cas/softcas/softcas_test.go +++ b/cas/softcas/softcas_test.go @@ -7,16 +7,19 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" + "fmt" "io" "math/big" "reflect" "testing" "time" + "github.com/pkg/errors" + "github.com/smallstep/certificates/cas/apiv1" + "github.com/smallstep/certificates/kms" + kmsapi "github.com/smallstep/certificates/kms/apiv1" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" - - "github.com/smallstep/certificates/cas/apiv1" ) var ( @@ -36,6 +39,7 @@ MC4CAQAwBQYDK2VwBCIEII9ZckcrDKlbhZKR0jp820Uz6mOMLFsq2JhI+Tl7WJwH ) var ( + errTest = errors.New("test error") testIssuer = mustIssuer() testSigner = mustSigner() testTemplate = &x509.Certificate{ @@ -43,13 +47,83 @@ var ( DNSNames: []string{"test.smallstep.com"}, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - PublicKey: mustSigner().Public(), + PublicKey: testSigner.Public(), SerialNumber: big.NewInt(1234), } - testNow = time.Now() - testSignedTemplate = mustSign(testTemplate, testNow, testNow.Add(24*time.Hour)) + testRootTemplate = &x509.Certificate{ + Subject: pkix.Name{CommonName: "Test Root CA"}, + KeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign, + PublicKey: testSigner.Public(), + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 1, + SerialNumber: big.NewInt(1234), + } + testIntermediateTemplate = &x509.Certificate{ + Subject: pkix.Name{CommonName: "Test Intermediate CA"}, + KeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign, + PublicKey: testSigner.Public(), + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 0, + MaxPathLenZero: true, + SerialNumber: big.NewInt(1234), + } + testNow = time.Now() + testSignedTemplate = mustSign(testTemplate, testIssuer, testNow, testNow.Add(24*time.Hour)) + testSignedRootTemplate = mustSign(testRootTemplate, testRootTemplate, testNow, testNow.Add(24*time.Hour)) + testSignedIntermediateTemplate = mustSign(testIntermediateTemplate, testSignedRootTemplate, testNow, testNow.Add(24*time.Hour)) ) +type mockKeyManager struct { + signer crypto.Signer + errGetPublicKey error + errCreateKey error + errCreatesigner error + errClose error +} + +func (m *mockKeyManager) GetPublicKey(req *kmsapi.GetPublicKeyRequest) (crypto.PublicKey, error) { + signer := testSigner + if m.signer != nil { + signer = m.signer + } + return signer.Public(), m.errGetPublicKey +} + +func (m *mockKeyManager) CreateKey(req *kmsapi.CreateKeyRequest) (*kmsapi.CreateKeyResponse, error) { + signer := testSigner + if m.signer != nil { + signer = m.signer + } + return &kmsapi.CreateKeyResponse{ + PrivateKey: signer, + PublicKey: signer.Public(), + }, m.errCreateKey +} + +func (m *mockKeyManager) CreateSigner(req *kmsapi.CreateSignerRequest) (crypto.Signer, error) { + signer := testSigner + if m.signer != nil { + signer = m.signer + } + return signer, m.errCreatesigner +} + +func (m *mockKeyManager) Close() error { + return m.errClose +} + +type badSigner struct{} + +func (b *badSigner) Public() crypto.PublicKey { + return testSigner.Public() +} + +func (b *badSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + return nil, fmt.Errorf("💥") +} + func mockNow(t *testing.T) { tmp := now now = func() time.Time { @@ -76,12 +150,12 @@ func mustSigner() crypto.Signer { return v.(crypto.Signer) } -func mustSign(template *x509.Certificate, notBefore, notAfter time.Time) *x509.Certificate { +func mustSign(template, parent *x509.Certificate, notBefore, notAfter time.Time) *x509.Certificate { tmpl := *template tmpl.NotBefore = notBefore tmpl.NotAfter = notAfter - tmpl.Issuer = testIssuer.Subject - cert, err := x509util.CreateCertificate(&tmpl, testIssuer, tmpl.PublicKey, testSigner) + tmpl.Issuer = parent.Subject + cert, err := x509util.CreateCertificate(&tmpl, parent, tmpl.PublicKey, testSigner) if err != nil { panic(err) } @@ -343,3 +417,125 @@ func Test_now(t *testing.T) { t.Errorf("now() = %s, want ~%s", t1, t0) } } + +func TestSoftCAS_CreateCertificateAuthority(t *testing.T) { + mockNow(t) + + type fields struct { + Issuer *x509.Certificate + Signer crypto.Signer + KeyManager kms.KeyManager + } + type args struct { + req *apiv1.CreateCertificateAuthorityRequest + } + tests := []struct { + name string + fields fields + args args + want *apiv1.CreateCertificateAuthorityResponse + wantErr bool + }{ + {"ok root", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: testRootTemplate, + Lifetime: 24 * time.Hour, + }}, &apiv1.CreateCertificateAuthorityResponse{ + Name: "Test Root CA", + Certificate: testSignedRootTemplate, + PublicKey: testSignedRootTemplate.PublicKey, + PrivateKey: testSigner, + Signer: testSigner, + }, false}, + {"ok intermediate", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Certificate: testSignedRootTemplate, + Signer: testSigner, + }, + }}, &apiv1.CreateCertificateAuthorityResponse{ + Name: "Test Intermediate CA", + Certificate: testSignedIntermediateTemplate, + CertificateChain: []*x509.Certificate{testSignedRootTemplate}, + PublicKey: testSignedIntermediateTemplate.PublicKey, + PrivateKey: testSigner, + Signer: testSigner, + }, false}, + {"fail template", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail lifetime", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: testIntermediateTemplate, + }}, nil, true}, + {"fail type", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail parent", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail parent.certificate", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Signer: testSigner, + }, + }}, nil, true}, + {"fail parent.signer", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Certificate: testSignedRootTemplate, + }, + }}, nil, true}, + {"fail createKey", fields{nil, nil, &mockKeyManager{errCreateKey: errTest}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail createSigner", fields{nil, nil, &mockKeyManager{errCreatesigner: errTest}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail sign root", fields{nil, nil, &mockKeyManager{signer: &badSigner{}}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.RootCA, + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + }}, nil, true}, + {"fail sign intermediate", fields{nil, nil, &mockKeyManager{}}, args{&apiv1.CreateCertificateAuthorityRequest{ + Type: apiv1.IntermediateCA, + Template: testIntermediateTemplate, + Lifetime: 24 * time.Hour, + Parent: &apiv1.CreateCertificateAuthorityResponse{ + Certificate: testSignedRootTemplate, + Signer: &badSigner{}, + }, + }}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &SoftCAS{ + Issuer: tt.fields.Issuer, + Signer: tt.fields.Signer, + KeyManager: tt.fields.KeyManager, + } + got, err := c.CreateCertificateAuthority(tt.args.req) + if (err != nil) != tt.wantErr { + t.Errorf("SoftCAS.CreateCertificateAuthority() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("SoftCAS.CreateCertificateAuthority() = \n%#v, want \n%#v", got, tt.want) + } + }) + } +} From 7020011842a102126f07781a0edd8d0000486df3 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 27 Oct 2020 19:23:56 -0700 Subject: [PATCH 12/17] Add some extra tests. --- cas/cas.go | 9 ++----- cas/cas_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/cas/cas.go b/cas/cas.go index 0592fed5..8f385fb2 100644 --- a/cas/cas.go +++ b/cas/cas.go @@ -6,7 +6,6 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" - "github.com/smallstep/certificates/cas/softcas" ) // CertificateAuthorityService is the interface implemented by all the CAS. @@ -35,15 +34,12 @@ func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService, // NewCreator creates a new CertificateAuthorityCreator using the given options. func NewCreator(ctx context.Context, opts apiv1.Options) (CertificateAuthorityCreator, error) { + opts.IsCreator = true + t := apiv1.Type(strings.ToLower(opts.Type)) if t == apiv1.DefaultCAS { t = apiv1.SoftCAS } - if t == apiv1.SoftCAS { - return &softcas.SoftCAS{ - KeyManager: opts.KeyManager, - }, nil - } svc, err := New(ctx, opts) if err != nil { @@ -52,7 +48,6 @@ func NewCreator(ctx context.Context, opts apiv1.Options) (CertificateAuthorityCr creator, ok := svc.(CertificateAuthorityCreator) if !ok { - return nil, errors.Errorf("cas type '%s' does not implements CertificateAuthorityCreator", t) } diff --git a/cas/cas_test.go b/cas/cas_test.go index a01e8dab..6c4c5c41 100644 --- a/cas/cas_test.go +++ b/cas/cas_test.go @@ -5,19 +5,40 @@ import ( "crypto/ed25519" "crypto/x509" "crypto/x509/pkix" + "fmt" "reflect" "testing" - "github.com/smallstep/certificates/cas/softcas" - "github.com/smallstep/certificates/cas/apiv1" + "github.com/smallstep/certificates/cas/softcas" + "github.com/smallstep/certificates/kms" + kmsapi "github.com/smallstep/certificates/kms/apiv1" ) +type mockCAS struct{} + +func (m *mockCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { + panic("not implemented") +} + +func (m *mockCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.RenewCertificateResponse, error) { + panic("not implemented") +} + +func (m *mockCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) { + panic("not implemented") +} + func TestNew(t *testing.T) { expected := &softcas.SoftCAS{ Issuer: &x509.Certificate{Subject: pkix.Name{CommonName: "Test Issuer"}}, Signer: ed25519.PrivateKey{}, } + + apiv1.Register(apiv1.Type("nockCAS"), func(ctx context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) { + return nil, fmt.Errorf("an error") + }) + type args struct { ctx context.Context opts apiv1.Options @@ -44,6 +65,7 @@ func TestNew(t *testing.T) { }}, expected, false}, {"fail empty", args{context.Background(), apiv1.Options{}}, (*softcas.SoftCAS)(nil), true}, {"fail type", args{context.Background(), apiv1.Options{Type: "FailCAS"}}, nil, true}, + {"fail load", args{context.Background(), apiv1.Options{Type: "nockCAS"}}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -58,3 +80,48 @@ func TestNew(t *testing.T) { }) } } + +func TestNewCreator(t *testing.T) { + keyManager, err := kms.New(context.Background(), kmsapi.Options{}) + if err != nil { + t.Fatal(err) + } + + apiv1.Register(apiv1.Type("nockCAS"), func(ctx context.Context, opts apiv1.Options) (apiv1.CertificateAuthorityService, error) { + return &mockCAS{}, nil + }) + + type args struct { + ctx context.Context + opts apiv1.Options + } + tests := []struct { + name string + args args + want CertificateAuthorityCreator + wantErr bool + }{ + {"ok empty", args{context.Background(), apiv1.Options{}}, &softcas.SoftCAS{}, false}, + {"ok softcas", args{context.Background(), apiv1.Options{ + Type: "softcas", + }}, &softcas.SoftCAS{}, false}, + {"ok SoftCAS", args{context.Background(), apiv1.Options{ + Type: "SoftCAS", + KeyManager: keyManager, + }}, &softcas.SoftCAS{KeyManager: keyManager}, false}, + {"fail type", args{context.Background(), apiv1.Options{Type: "FailCAS"}}, nil, true}, + {"fail no creator", args{context.Background(), apiv1.Options{Type: "nockCAS"}}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewCreator(tt.args.ctx, tt.args.opts) + if (err != nil) != tt.wantErr { + t.Errorf("NewCreator() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewCreator() = %v, want %v", got, tt.want) + } + }) + } +} From 41a46bbd75a6faae2bef4ab98627dbc43a1f5852 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 27 Oct 2020 19:33:29 -0700 Subject: [PATCH 13/17] Enable default cas implementation. --- cas/cas.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cas/cas.go b/cas/cas.go index 8f385fb2..b564c8d2 100644 --- a/cas/cas.go +++ b/cas/cas.go @@ -6,6 +6,9 @@ import ( "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" + + // Enable default implementation + _ "github.com/smallstep/certificates/cas/softcas" ) // CertificateAuthorityService is the interface implemented by all the CAS. From 4f9200cc47f741e1f6748d0649577612034605ca Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 3 Nov 2020 10:55:39 -0800 Subject: [PATCH 14/17] Add missing docs. --- cas/apiv1/requests.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cas/apiv1/requests.go b/cas/apiv1/requests.go index 5305ff43..38db3a67 100644 --- a/cas/apiv1/requests.go +++ b/cas/apiv1/requests.go @@ -104,8 +104,8 @@ type GetCertificateAuthorityResponse struct { RootCertificate *x509.Certificate } -// CreateCertificateAuthorityRequest ... -// This is a work in progress +// CreateCertificateAuthorityRequest is the request used to generate a root or +// intermediate certificate. type CreateCertificateAuthorityRequest struct { Name string Type CertificateAuthorityType @@ -125,8 +125,9 @@ type CreateCertificateAuthorityRequest struct { CreateKey *apiv1.CreateKeyRequest } -// CreateCertificateAuthorityResponse ... -// This is a work in progress +// CreateCertificateAuthorityResponse is the response for +// CreateCertificateAuthority method and contains the root or intermedate +// certificate generated as well as the CA chain. type CreateCertificateAuthorityResponse struct { Name string Certificate *x509.Certificate From b057c6677abaf99931786072d8d654261c5fe3a1 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 3 Nov 2020 11:26:24 -0800 Subject: [PATCH 15/17] Use test/bufconn instead of a real listener. --- cas/cloudcas/cloudcas_test.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index 494763c5..eb682e28 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -12,6 +12,7 @@ import ( "encoding/pem" "fmt" "io" + "log" "net" "os" "reflect" @@ -30,6 +31,7 @@ import ( pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" longrunningpb "google.golang.org/genproto/googleapis/longrunning" "google.golang.org/grpc" + "google.golang.org/grpc/test/bufconn" "google.golang.org/protobuf/types/known/anypb" ) @@ -790,21 +792,20 @@ func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { mos := NewMockOperationsServer(mosCtrl) // Create operation server - lis, err := net.Listen("tcp", "localhost:0") - if err != nil { - t.Fatal(err) - } - srv := grpc.NewServer() longrunningpb.RegisterOperationsServer(srv, mos) + lis := bufconn.Listen(2) go srv.Serve(lis) defer srv.Stop() // Create fake privateca client - conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure()) + conn, err := grpc.DialContext(context.Background(), "", grpc.WithInsecure(), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return lis.Dial() + })) if err != nil { - t.Fatal(err) + log.Fatal(err) } client, err := lroauto.NewOperationsClient(context.Background(), option.WithGRPCConn(conn)) From a97fab4119086552e4c6fc8057bab3a766e2c46b Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 3 Nov 2020 12:48:48 -0800 Subject: [PATCH 16/17] Fix mispell. --- cas/apiv1/requests.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cas/apiv1/requests.go b/cas/apiv1/requests.go index 38db3a67..68f1cb7b 100644 --- a/cas/apiv1/requests.go +++ b/cas/apiv1/requests.go @@ -126,7 +126,7 @@ type CreateCertificateAuthorityRequest struct { } // CreateCertificateAuthorityResponse is the response for -// CreateCertificateAuthority method and contains the root or intermedate +// CreateCertificateAuthority method and contains the root or intermediate // certificate generated as well as the CA chain. type CreateCertificateAuthorityResponse struct { Name string From 736a6fb64e157aceb5809cdd24b191727a301c9c Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Tue, 3 Nov 2020 12:49:04 -0800 Subject: [PATCH 17/17] Fix rebase. --- go.mod | 8 +------- go.sum | 15 +++++++-------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 8d48913e..3781c5c3 100644 --- a/go.mod +++ b/go.mod @@ -18,14 +18,9 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/nosql v0.3.0 -<<<<<<< HEAD - github.com/urfave/cli v1.22.2 - go.step.sm/cli-utils v0.1.0 - go.step.sm/crypto v0.6.1 -======= github.com/urfave/cli v1.22.4 + go.step.sm/cli-utils v0.1.0 go.step.sm/crypto v0.7.0 ->>>>>>> Update go.step.sm/crypto dependency. golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 golang.org/x/net v0.0.0-20201021035429-f5854403a974 google.golang.org/api v0.33.0 @@ -35,6 +30,5 @@ require ( gopkg.in/square/go-jose.v2 v2.5.1 ) -// replace github.com/smallstep/cli => ../cli // replace github.com/smallstep/nosql => ../nosql // replace go.step.sm/crypto => ../crypto diff --git a/go.sum b/go.sum index 835b926a..bd170904 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.65.1-0.20200904011802-3c2db50b5678 h1:5YqZUrIf2QELwPqw1kLpGIE0z0I++b7HhzSNKjZlIY0= -cloud.google.com/go v0.65.1-0.20200904011802-3c2db50b5678/go.mod h1:Ihp2NV3Qr9BWHCDNA8LXF9fZ1HGBl6Jx1xd7KP3nxkI= cloud.google.com/go v0.70.0 h1:ujhG1RejZYi+HYfJNlgBh3j/bVKD8DewM7AkJ5UPyBc= cloud.google.com/go v0.70.0/go.mod h1:/UTKYRQTWjVnSe7nGvoSzxEFUELzSI/yAYd0JQT6cRo= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -179,6 +177,7 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -272,6 +271,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -286,10 +287,14 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.step.sm/cli-utils v0.1.0 h1:uuQ73MuAh5P5Eg+3zfqwrtlTLx5DWSfGqGCrSSjYqdk= go.step.sm/cli-utils v0.1.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y= go.step.sm/crypto v0.6.1 h1:nJoRFGrGNf/mKVVMdWnfLbBfIFt/z4NdJlSL5nipQMQ= go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= +go.step.sm/crypto v0.7.0 h1:azKRI4CBRzDbhHsLAnvzvGJ0aVbGI+wrh2COrPd/mks= +go.step.sm/crypto v0.7.0/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -469,9 +474,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200903185744-af4cc2cd812e h1:RvNtqusJ+6DJ07/by/M84a6/Dd17XU6n8QvhvknjJno= -golang.org/x/tools v0.0.0-20200903185744-af4cc2cd812e/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ= golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= @@ -498,8 +500,6 @@ google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.31.0 h1:1w5Sz/puhxFo9lTtip2n47k7toB/U2nCqOKNHd3Yrbo= -google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= google.golang.org/api v0.33.0 h1:+gL0XvACeMIvpwLZ5rQZzLn5cwOsgg8dIcfJ2SYfBVw= google.golang.org/api v0.33.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -541,7 +541,6 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154 h1:bFFRpT+e8JJVY7lMMfvezL1ZIwqiwmPl2bsE2yx4HqM=