Merge pull request #409 from smallstep/cloudcas-init

Add CreateCertificateAuthority
This commit is contained in:
Mariano Cano 2020-11-03 16:28:50 -08:00 committed by GitHub
commit 98a5aa5916
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 2119 additions and 169 deletions

View file

@ -5,6 +5,7 @@ import (
"crypto/x509" "crypto/x509"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/kms"
) )
// Options represents the configuration options used to select and configure the // 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. // They are configured in ca.json crt and key properties.
Issuer *x509.Certificate `json:"-"` Issuer *x509.Certificate `json:"-"`
Signer crypto.Signer `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. // Validate checks the fields in Options.

View file

@ -1,8 +1,53 @@
package apiv1 package apiv1
import ( import (
"crypto"
"crypto/x509" "crypto/x509"
"time" "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. // CreateCertificateRequest is the request used to sign a new certificate.
@ -58,3 +103,36 @@ type GetCertificateAuthorityRequest struct {
type GetCertificateAuthorityResponse struct { type GetCertificateAuthorityResponse struct {
RootCertificate *x509.Certificate RootCertificate *x509.Certificate
} }
// CreateCertificateAuthorityRequest is the request used to generate a root or
// intermediate certificate.
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 is the response for
// CreateCertificateAuthority method and contains the root or intermediate
// certificate generated as well as the CA chain.
type CreateCertificateAuthorityResponse struct {
Name string
Certificate *x509.Certificate
CertificateChain []*x509.Certificate
PublicKey crypto.PublicKey
PrivateKey crypto.PrivateKey
Signer crypto.Signer
}

View file

@ -18,6 +18,13 @@ type CertificateAuthorityGetter interface {
GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error) 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 represents the CAS type used.
type Type string type Type string

View file

@ -14,6 +14,10 @@ import (
// CertificateAuthorityService is the interface implemented by all the CAS. // CertificateAuthorityService is the interface implemented by all the CAS.
type CertificateAuthorityService = apiv1.CertificateAuthorityService 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) { func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService, error) {
if err := opts.Validate(); err != nil { if err := opts.Validate(); err != nil {
return nil, err return nil, err
@ -26,7 +30,29 @@ func New(ctx context.Context, opts apiv1.Options) (CertificateAuthorityService,
fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(t) fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(t)
if !ok { if !ok {
return nil, errors.Errorf("unsupported kms type '%s'", t) return nil, errors.Errorf("unsupported cas type '%s'", t)
} }
return fn(ctx, opts) return fn(ctx, opts)
} }
// 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
}
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
}

View file

@ -5,19 +5,40 @@ import (
"crypto/ed25519" "crypto/ed25519"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"fmt"
"reflect" "reflect"
"testing" "testing"
"github.com/smallstep/certificates/cas/softcas"
"github.com/smallstep/certificates/cas/apiv1" "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) { func TestNew(t *testing.T) {
expected := &softcas.SoftCAS{ expected := &softcas.SoftCAS{
Issuer: &x509.Certificate{Subject: pkix.Name{CommonName: "Test Issuer"}}, Issuer: &x509.Certificate{Subject: pkix.Name{CommonName: "Test Issuer"}},
Signer: ed25519.PrivateKey{}, 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 { type args struct {
ctx context.Context ctx context.Context
opts apiv1.Options opts apiv1.Options
@ -44,6 +65,7 @@ func TestNew(t *testing.T) {
}}, expected, false}, }}, expected, false},
{"fail empty", args{context.Background(), apiv1.Options{}}, (*softcas.SoftCAS)(nil), true}, {"fail empty", args{context.Background(), apiv1.Options{}}, (*softcas.SoftCAS)(nil), true},
{"fail type", args{context.Background(), apiv1.Options{Type: "FailCAS"}}, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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)
}
})
}
}

View file

@ -8,8 +8,10 @@ import (
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
kmsapi "github.com/smallstep/certificates/kms/apiv1"
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
) )
@ -326,3 +328,68 @@ func findExtraExtension(cert *x509.Certificate, oid asn1.ObjectIdentifier) (pkix
} }
return pkix.Extension{}, false 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.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 {
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'", alg)
}
}
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_SHA256, nil
case 2048:
return pb.CertificateAuthority_RSA_PSS_2048_SHA256, nil
case 4096:
return pb.CertificateAuthority_RSA_PSS_4096_SHA256, nil
default:
return 0, fmt.Errorf("unsupported RSA-PSS key size '%d'", bits)
}
}

View file

@ -14,6 +14,7 @@ import (
"reflect" "reflect"
"testing" "testing"
kmsapi "github.com/smallstep/certificates/kms/apiv1"
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" 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)
}
})
}
}

View file

@ -6,6 +6,8 @@ import (
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/pem" "encoding/pem"
"regexp"
"strings"
"time" "time"
privateca "cloud.google.com/go/security/privateca/apiv1beta1" privateca "cloud.google.com/go/security/privateca/apiv1beta1"
@ -13,6 +15,7 @@ import (
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/apiv1"
"go.step.sm/crypto/x509util"
"google.golang.org/api/option" "google.golang.org/api/option"
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
durationpb "google.golang.org/protobuf/types/known/durationpb" durationpb "google.golang.org/protobuf/types/known/durationpb"
@ -24,12 +27,24 @@ 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.
var caRegexp = regexp.MustCompile("^projects/[^/]+/locations/[^/]+/certificateAuthorities/[^/]+$")
// CertificateAuthorityClient is the interface implemented by the Google CAS // CertificateAuthorityClient is the interface implemented by the Google CAS
// client. // client.
type CertificateAuthorityClient interface { type CertificateAuthorityClient interface {
CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) 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) 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) 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 // recocationCodeMap maps revocation reason codes from RFC 5280, to Google CAS
@ -51,6 +66,8 @@ var revocationCodeMap = map[int]pb.RevocationReason{
type CloudCAS struct { type CloudCAS struct {
client CertificateAuthorityClient client CertificateAuthorityClient
certificateAuthority string certificateAuthority string
project string
location string
} }
// newCertificateAuthorityClient creates the certificate authority client. This // newCertificateAuthorityClient creates the certificate authority client. This
@ -70,8 +87,29 @@ var newCertificateAuthorityClient = func(ctx context.Context, credentialsFile st
// New creates a new CertificateAuthorityService implementation using Google // New creates a new CertificateAuthorityService implementation using Google
// Cloud CAS. // Cloud CAS.
func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) { func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
if opts.CertificateAuthority == "" { if opts.IsCreator {
return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty") 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) client, err := newCertificateAuthorityClient(ctx, opts.CredentialsFile)
@ -82,6 +120,8 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
return &CloudCAS{ return &CloudCAS{
client: client, client: client,
certificateAuthority: opts.CertificateAuthority, certificateAuthority: opts.CertificateAuthority,
project: opts.Project,
location: opts.Location,
}, nil }, nil
} }
@ -101,6 +141,7 @@ func (c *CloudCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityReq
Name: name, Name: name,
}) })
if err != nil { if err != nil {
println(name)
return nil, errors.Wrap(err, "cloudCAS GetCertificateAuthority failed") return nil, errors.Wrap(err, "cloudCAS GetCertificateAuthority failed")
} }
if len(resp.PemCaCertificates) == 0 { if len(resp.PemCaCertificates) == 0 {
@ -160,7 +201,7 @@ func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.
}, nil }, 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) { func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1.RevokeCertificateResponse, error) {
reason, ok := revocationCodeMap[req.ReasonCode] reason, ok := revocationCodeMap[req.ReasonCode]
switch { switch {
@ -203,6 +244,141 @@ func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv
}, nil }, 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 == "" && (req.Parent.Certificate == nil || req.Parent.Signer == nil):
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
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.
caID := normalizeCertificateAuthorityName(req.Name)
if caID == "" {
id, err := createCertificateID()
if err != nil {
return nil, err
}
caID = id
}
// Add CertificateAuthority extension
casExtension, err := apiv1.CreateCertificateAuthorityExtension(apiv1.CloudCAS, caID)
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: caID,
RequestId: req.RequestID,
CertificateAuthority: &pb.CertificateAuthority{
Type: caType,
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{},
},
}
// 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) { func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Duration, requestID string) (*x509.Certificate, []*x509.Certificate, error) {
// Removes the CAS extension if it exists. // Removes the CAS extension if it exists.
apiv1.RemoveCertificateAuthorityExtension(tpl) apiv1.RemoveCertificateAuthorityExtension(tpl)
@ -245,10 +421,118 @@ func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Durati
return getCertificateAndChain(cert) 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.
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
}
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{},
},
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: cert.PemCertificate,
SubordinateConfig: &pb.SubordinateConfig{
SubordinateConfig: &pb.SubordinateConfig_PemIssuerChain{
PemIssuerChain: &pb.SubordinateConfig_SubordinateConfigChain{
PemCertificates: cert.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) { func defaultContext() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), 15*time.Second) 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) { func createCertificateID() (string, error) {
id, err := uuid.NewRandomFromReader(rand.Reader) id, err := uuid.NewRandomFromReader(rand.Reader)
if err != nil { if err != nil {
@ -269,6 +553,25 @@ func parseCertificate(pemCert string) (*x509.Certificate, error) {
return cert, nil 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) { func getCertificateAndChain(certpb *pb.Certificate) (*x509.Certificate, []*x509.Certificate, error) {
cert, err := parseCertificate(certpb.PemCertificate) cert, err := parseCertificate(certpb.PemCertificate)
if err != nil { if err != nil {
@ -287,3 +590,23 @@ func getCertificateAndChain(certpb *pb.Certificate) (*x509.Certificate, []*x509.
return cert, chain, nil 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)
}

View file

@ -3,48 +3,64 @@ package cloudcas
import ( import (
"bytes" "bytes"
"context" "context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/pem"
"fmt"
"io" "io"
"log"
"net"
"os" "os"
"reflect" "reflect"
"testing" "testing"
"time" "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" "github.com/google/uuid"
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/cas/apiv1" "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" 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"
) )
var ( var (
errTest = errors.New("test error") errTest = errors.New("test error")
testAuthorityName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca" testAuthorityName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca"
testCertificateName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca/certificates/test-certificate" testCertificateName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca/certificates/test-certificate"
testProject = "test-project"
testLocation = "us-west1"
testRootCertificate = `-----BEGIN CERTIFICATE----- testRootCertificate = `-----BEGIN CERTIFICATE-----
MIIBhjCCAS2gAwIBAgIQLbKTuXau4+t3KFbGpJJAADAKBggqhkjOPQQDAjAiMSAw MIIBeDCCAR+gAwIBAgIQcXWWjtSZ/PAyH8D1Ou4L9jAKBggqhkjOPQQDAjAbMRkw
HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla FwYDVQQDExBDbG91ZENBUyBSb290IENBMB4XDTIwMTAyNzIyNTM1NFoXDTMwMTAy
Fw0zMDA5MTIyMjQ4NDlaMCIxIDAeBgNVBAMTF0dvb2dsZSBDQVMgVGVzdCBSb290 NzIyNTM1NFowGzEZMBcGA1UEAxMQQ2xvdWRDQVMgUm9vdCBDQTBZMBMGByqGSM49
IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYKGgQ3/0D7+oBTc0CXoYfSC6 AgEGCCqGSM49AwEHA0IABIySHA4b78Yu4LuGhZIlv/PhNwXz4ZoV1OUZQ0LrK3vj
M8hOqLsmzBapPZSYpfwjgEsjdNU84jdrYmW1zF1+p+MrL4c7qJv9NLo/picCuqNF B13O12DLZC5uj1z3kxdQzXUttSbtRv49clMpBiTpsZKjRTBDMA4GA1UdDwEB/wQE
MEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSZ+t9RMHbFTl5BatM3
FFVn9V7Qymd7cUJh9KAhnUDAQL5YMAoGCCqGSM49BAMCA0cAMEQCIA4LzttYoT3u 5bJlHPOu3DAKBggqhkjOPQQDAgNHADBEAiASah6gg0tVM3WI0meCQ4SEKk7Mjhbv
8TYgSrvFT+Z+cklfi4UrPBU6aSbcUaW2AiAPfaqbyccQT3CxMVyHg+xZZjAirZp8 +SmhuZHWV1QlXQIgRXNyWcpVUrAoG6Uy1KQg07LDpF5dFeK9InrDxSJAkVo=
lAeA/T4FxAonHA==
-----END CERTIFICATE-----` -----END CERTIFICATE-----`
testIntermediateCertificate = `-----BEGIN CERTIFICATE----- testIntermediateCertificate = `-----BEGIN CERTIFICATE-----
MIIBsDCCAVagAwIBAgIQOb91kHxWKVzSJ9ESW1ViVzAKBggqhkjOPQQDAjAiMSAw MIIBpDCCAUmgAwIBAgIRALLKxnxyl0GBeKevIcbx02wwCgYIKoZIzj0EAwIwGzEZ
HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla MBcGA1UEAxMQQ2xvdWRDQVMgUm9vdCBDQTAeFw0yMDEwMjcyMjUzNTRaFw0zMDEw
Fw0zMDA5MTIyMjQ4NDlaMCoxKDAmBgNVBAMTH0dvb2dsZSBDQVMgVGVzdCBJbnRl MjcyMjUzNTRaMCMxITAfBgNVBAMTGENsb3VkQ0FTIEludGVybWVkaWF0ZSBDQTBZ
cm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASUHN1cNyId4Ei/ MBMGByqGSM49AgEGCCqGSM49AwEHA0IABPLuqxgBY+QmaXc8zKIC8FMgjJ6dF/cL
4MxD5VrZFc51P50caMUdDZVrPveidChBYCU/9IM6vnRlZHx2HLjQ0qAvqHwY3rT0 b+Dig0XKc5GH/T1ORrhgOkRayrQcjPMu+jkjg25qn6vvp43LRtUKPXOjZjBkMA4G
xc7n+PfCo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAd A1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQ8RVQI
BgNVHQ4EFgQUSDlasiw0pRKyS7llhL0ZuVFNa9UwHwYDVR0jBBgwFoAUVWf1XtDK VgXAmRNDX8qItalVpSBEGjAfBgNVHSMEGDAWgBSZ+t9RMHbFTl5BatM35bJlHPOu
Z3txQmH0oCGdQMBAvlgwCgYIKoZIzj0EAwIDSAAwRQIgMmsLcoC4KriXw+s+cZx2 3DAKBggqhkjOPQQDAgNJADBGAiEA70MVYVqjm8SBHJf5cOlWfiXXOfHUsctTJ+/F
bJMf6Mx/WESj31buJJhpzY0CIQCBUa/JtvS3nyce/4DF5tK2v49/NWHREgqAaZ57 pLsKBogCIQDJJkoQqYl9B59Dq3zydl8bpJevQxsoaa4Wqg+ZBMkvbQ==
DcYyHQ==
-----END CERTIFICATE-----` -----END CERTIFICATE-----`
testLeafCertificate = `-----BEGIN CERTIFICATE----- testLeafCertificate = `-----BEGIN CERTIFICATE-----
MIIB1jCCAX2gAwIBAgIQQfOn+COMeuD8VYF1TiDkEzAKBggqhkjOPQQDAjAqMSgw MIIB1jCCAX2gAwIBAgIQQfOn+COMeuD8VYF1TiDkEzAKBggqhkjOPQQDAjAqMSgw
@ -71,6 +87,24 @@ ZGNhcxMkZDhkMThhNjgtNTI5Ni00YWYzLWFlNGItMmY4NzdkYTNmYmQ5MAoGCCqG
SM49BAMCA0gAMEUCIGxl+pqJ50WYWUqK2l4V1FHoXSi0Nht5kwTxFxnWZu1xAiEA SM49BAMCA0gAMEUCIGxl+pqJ50WYWUqK2l4V1FHoXSi0Nht5kwTxFxnWZu1xAiEA
zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw= zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw=
-----END CERTIFICATE-----` -----END CERTIFICATE-----`
testIntermediateCsr = `-----BEGIN CERTIFICATE REQUEST-----
MIHeMIGFAgEAMCMxITAfBgNVBAMTGENsb3VkQ0FTIEludGVybWVkaWF0ZSBDQTBZ
MBMGByqGSM49AgEGCCqGSM49AwEHA0IABPLuqxgBY+QmaXc8zKIC8FMgjJ6dF/cL
b+Dig0XKc5GH/T1ORrhgOkRayrQcjPMu+jkjg25qn6vvp43LRtUKPXOgADAKBggq
hkjOPQQDAgNIADBFAiEAn3pkYXb2OzoQZ+AExFqd7qZ7pg2nyP2kBZZ01Pl8KfcC
IHKplBXDR79/i7kjOtv1iWfgf5S/XQHrz178gXA0YQe7
-----END CERTIFICATE REQUEST-----`
testRootKey = `-----BEGIN EC PRIVATE KEY-----
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
RFrKtByM8y76OSODbmqfq++njctG1Qo9cw==
-----END EC PRIVATE KEY-----`
) )
type testClient struct { type testClient struct {
@ -133,6 +167,29 @@ func setTeeReader(t *testing.T, w *bytes.Buffer) {
rand.Reader = io.TeeReader(reader, w) 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) { func (c *testClient) CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) {
return c.certificate, c.err return c.certificate, c.err
} }
@ -145,6 +202,18 @@ func (c *testClient) GetCertificateAuthority(ctx context.Context, req *pb.GetCer
return c.certificateAuthority, c.err 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 { func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate {
t.Helper() t.Helper()
crt, err := parseCertificate(pemCert) crt, err := parseCertificate(pemCert)
@ -154,6 +223,19 @@ func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate {
return crt 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) { func TestNew(t *testing.T) {
tmp := newCertificateAuthorityClient tmp := newCertificateAuthorityClient
newCertificateAuthorityClient = func(ctx context.Context, credentialsFile string) (CertificateAuthorityClient, error) { newCertificateAuthorityClient = func(ctx context.Context, credentialsFile string) (CertificateAuthorityClient, error) {
@ -178,17 +260,37 @@ func TestNew(t *testing.T) {
}}, &CloudCAS{ }}, &CloudCAS{
client: &testClient{}, client: &testClient{},
certificateAuthority: testAuthorityName, certificateAuthority: testAuthorityName,
project: testProject,
location: testLocation,
}, false}, }, false},
{"ok with credentials", args{context.Background(), apiv1.Options{ {"ok with credentials", args{context.Background(), apiv1.Options{
CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json",
}}, &CloudCAS{ }}, &CloudCAS{
client: &testClient{credentialsFile: "testdata/credentials.json"}, client: &testClient{credentialsFile: "testdata/credentials.json"},
certificateAuthority: testAuthorityName, certificateAuthority: testAuthorityName,
project: testProject,
location: testLocation,
}, false}, }, false},
{"fail certificate authority", args{context.Background(), apiv1.Options{}}, nil, true}, {"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{
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{ {"fail with credentials", args{context.Background(), apiv1.Options{
CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/error.json", CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/error.json",
}}, nil, true}, }}, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -216,6 +318,8 @@ func TestNew_register(t *testing.T) {
want := &CloudCAS{ want := &CloudCAS{
client: &testClient{credentialsFile: "testdata/credentials.json"}, client: &testClient{credentialsFile: "testdata/credentials.json"},
certificateAuthority: testAuthorityName, certificateAuthority: testAuthorityName,
project: testProject,
location: testLocation,
} }
newFn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.CloudCAS) newFn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.CloudCAS)
@ -673,3 +777,444 @@ func Test_getCertificateAndChain(t *testing.T) {
}) })
} }
} }
func TestCloudCAS_CreateCertificateAuthority(t *testing.T) {
must := func(a, b interface{}) interface{} {
return a
}
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mosCtrl := gomock.NewController(t)
defer mosCtrl.Finish()
m := NewMockCertificateAuthorityClient(ctrl)
mos := NewMockOperationsServer(mosCtrl)
// Create operation server
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.DialContext(context.Background(), "", grpc.WithInsecure(),
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return lis.Dial()
}))
if err != nil {
log.Fatal(err)
}
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),
},
}, 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().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},
}, 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 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)
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},
{"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) {
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)
}
})
}
}
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)
}
})
}
}

View file

@ -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...)
}

View file

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

View file

@ -4,10 +4,12 @@ import (
"context" "context"
"crypto" "crypto"
"crypto/x509" "crypto/x509"
"errors"
"time" "time"
"github.com/pkg/errors"
"github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/kms"
kmsapi "github.com/smallstep/certificates/kms/apiv1"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
) )
@ -24,22 +26,26 @@ var now = func() time.Time {
// SoftCAS implements a Certificate Authority Service using Golang or KMS // SoftCAS implements a Certificate Authority Service using Golang or KMS
// crypto. This is the default CAS used in step-ca. // crypto. This is the default CAS used in step-ca.
type SoftCAS struct { type SoftCAS struct {
Issuer *x509.Certificate Issuer *x509.Certificate
Signer crypto.Signer Signer crypto.Signer
KeyManager kms.KeyManager
} }
// New creates a new CertificateAuthorityService implementation using Golang or KMS // New creates a new CertificateAuthorityService implementation using Golang or KMS
// crypto. // crypto.
func New(ctx context.Context, opts apiv1.Options) (*SoftCAS, error) { func New(ctx context.Context, opts apiv1.Options) (*SoftCAS, error) {
switch { if !opts.IsCreator {
case opts.Issuer == nil: switch {
return nil, errors.New("softCAS 'issuer' cannot be nil") case opts.Issuer == nil:
case opts.Signer == nil: return nil, errors.New("softCAS 'issuer' cannot be nil")
return nil, errors.New("softCAS 'signer' cannot be nil") case opts.Signer == nil:
return nil, errors.New("softCAS 'signer' cannot be nil")
}
} }
return &SoftCAS{ return &SoftCAS{
Issuer: opts.Issuer, Issuer: opts.Issuer,
Signer: opts.Signer, Signer: opts.Signer,
KeyManager: opts.KeyManager,
}, nil }, nil
} }
@ -113,3 +119,100 @@ func (c *SoftCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv1
}, },
}, nil }, 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)
chain = append(chain, req.Parent.CertificateChain...)
}
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)
}

View file

@ -7,16 +7,19 @@ import (
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"fmt"
"io" "io"
"math/big" "math/big"
"reflect" "reflect"
"testing" "testing"
"time" "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/pemutil"
"go.step.sm/crypto/x509util" "go.step.sm/crypto/x509util"
"github.com/smallstep/certificates/cas/apiv1"
) )
var ( var (
@ -36,6 +39,7 @@ MC4CAQAwBQYDK2VwBCIEII9ZckcrDKlbhZKR0jp820Uz6mOMLFsq2JhI+Tl7WJwH
) )
var ( var (
errTest = errors.New("test error")
testIssuer = mustIssuer() testIssuer = mustIssuer()
testSigner = mustSigner() testSigner = mustSigner()
testTemplate = &x509.Certificate{ testTemplate = &x509.Certificate{
@ -43,13 +47,83 @@ var (
DNSNames: []string{"test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"},
KeyUsage: x509.KeyUsageDigitalSignature, KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
PublicKey: mustSigner().Public(), PublicKey: testSigner.Public(),
SerialNumber: big.NewInt(1234), SerialNumber: big.NewInt(1234),
} }
testNow = time.Now() testRootTemplate = &x509.Certificate{
testSignedTemplate = mustSign(testTemplate, testNow, testNow.Add(24*time.Hour)) 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) { func mockNow(t *testing.T) {
tmp := now tmp := now
now = func() time.Time { now = func() time.Time {
@ -76,12 +150,12 @@ func mustSigner() crypto.Signer {
return v.(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 := *template
tmpl.NotBefore = notBefore tmpl.NotBefore = notBefore
tmpl.NotAfter = notAfter tmpl.NotAfter = notAfter
tmpl.Issuer = testIssuer.Subject tmpl.Issuer = parent.Subject
cert, err := x509util.CreateCertificate(&tmpl, testIssuer, tmpl.PublicKey, testSigner) cert, err := x509util.CreateCertificate(&tmpl, parent, tmpl.PublicKey, testSigner)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -343,3 +417,125 @@ func Test_now(t *testing.T) {
t.Errorf("now() = %s, want ~%s", t1, t0) 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)
}
})
}
}

View file

@ -11,6 +11,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority"
"github.com/smallstep/certificates/ca" "github.com/smallstep/certificates/ca"
"github.com/smallstep/certificates/cas/apiv1"
"github.com/smallstep/certificates/pki" "github.com/smallstep/certificates/pki"
"github.com/urfave/cli" "github.com/urfave/cli"
"go.step.sm/cli-utils/command" "go.step.sm/cli-utils/command"
@ -162,7 +163,10 @@ func onboardAction(ctx *cli.Context) error {
} }
func onboardPKI(config onboardingConfiguration) (*authority.Config, string, 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 { if err != nil {
return nil, "", err return nil, "", err
} }
@ -171,13 +175,13 @@ func onboardPKI(config onboardingConfiguration) (*authority.Config, string, erro
p.SetDNSNames([]string{config.DNS}) p.SetDNSNames([]string{config.DNS})
ui.Println("Generating root certificate...") 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 { if err != nil {
return nil, "", err return nil, "", err
} }
ui.Println("Generating intermediate certificate...") 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 { if err != nil {
return nil, "", err return nil, "", err
} }

20
go.mod
View file

@ -3,11 +3,12 @@ module github.com/smallstep/certificates
go 1.14 go 1.14
require ( 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/Masterminds/sprig/v3 v3.1.0
github.com/aws/aws-sdk-go v1.30.29 github.com/aws/aws-sdk-go v1.30.29
github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/chi v4.0.2+incompatible
github.com/go-piv/piv-go v1.6.0 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/google/uuid v1.1.2
github.com/googleapis/gax-go/v2 v2.0.5 github.com/googleapis/gax-go/v2 v2.0.5
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
@ -17,22 +18,17 @@ require (
github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus v1.4.2
github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262 github.com/smallstep/assert v0.0.0-20200723003110-82e2b9b3b262
github.com/smallstep/nosql v0.3.0 github.com/smallstep/nosql v0.3.0
github.com/urfave/cli v1.22.2 github.com/urfave/cli v1.22.4
go.step.sm/cli-utils v0.1.0 go.step.sm/cli-utils v0.1.0
go.step.sm/crypto v0.6.1 go.step.sm/crypto v0.7.0
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/net v0.0.0-20201021035429-f5854403a974
google.golang.org/api v0.31.0 google.golang.org/api v0.33.0
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154
google.golang.org/grpc v1.32.0 google.golang.org/grpc v1.32.0
google.golang.org/protobuf v1.25.0 google.golang.org/protobuf v1.25.0
gopkg.in/square/go-jose.v2 v2.5.1 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/nosql => ../nosql // replace github.com/smallstep/nosql => ../nosql
// replace go.step.sm/crypto => ../crypto // 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

39
go.sum
View file

@ -14,8 +14,8 @@ 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.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 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 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.70.0 h1:ujhG1RejZYi+HYfJNlgBh3j/bVKD8DewM7AkJ5UPyBc=
cloud.google.com/go v0.65.1-0.20200904011802-3c2db50b5678/go.mod h1:Ihp2NV3Qr9BWHCDNA8LXF9fZ1HGBl6Jx1xd7KP3nxkI= 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.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.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -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/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 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 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-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 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@ -115,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.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.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.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/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 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -133,6 +135,8 @@ 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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 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.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 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -157,6 +161,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-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-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-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/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 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -172,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 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 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-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 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -265,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/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 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 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/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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -279,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.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= 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.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 h1:uuQ73MuAh5P5Eg+3zfqwrtlTLx5DWSfGqGCrSSjYqdk=
go.step.sm/cli-utils v0.1.0/go.mod h1:+t4qCp5NO+080DdGkJxEh3xL5S4TcYC2JTPLMM72b6Y= 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 h1:nJoRFGrGNf/mKVVMdWnfLbBfIFt/z4NdJlSL5nipQMQ=
go.step.sm/crypto v0.6.1/go.mod h1:AKS4yMZVZD4EGjpSkY4eibuMenrvKCscb+BpWMet8c0= 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-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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -293,6 +305,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-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 h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -353,6 +367,9 @@ 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-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 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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= 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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -360,6 +377,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-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 h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -401,6 +420,9 @@ 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-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 h1:kCCpuwSAoYJPkNc6x0xT9yTtV4oKtARo4RGBQWOfg9E=
golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -452,9 +474,9 @@ 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-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 h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200903185744-af4cc2cd812e h1:RvNtqusJ+6DJ07/by/M84a6/Dd17XU6n8QvhvknjJno= golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ=
golang.org/x/tools v0.0.0-20200903185744-af4cc2cd812e/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 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-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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -478,8 +500,8 @@ 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.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= 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.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.31.0 h1:1w5Sz/puhxFo9lTtip2n47k7toB/U2nCqOKNHd3Yrbo= google.golang.org/api v0.33.0 h1:+gL0XvACeMIvpwLZ5rQZzLn5cwOsgg8dIcfJ2SYfBVw=
google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= 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.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.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -519,9 +541,10 @@ 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-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 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 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 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-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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=

View file

@ -5,6 +5,7 @@ import (
"crypto" "crypto"
"crypto/sha256" "crypto/sha256"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"encoding/pem" "encoding/pem"
@ -31,7 +32,6 @@ import (
"go.step.sm/crypto/jose" "go.step.sm/crypto/jose"
"go.step.sm/crypto/keyutil" "go.step.sm/crypto/keyutil"
"go.step.sm/crypto/pemutil" "go.step.sm/crypto/pemutil"
"go.step.sm/crypto/x509util"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
@ -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 // GetProvisionerKey returns the encrypted provisioner key with the for the
// given kid. // given kid.
func GetProvisionerKey(caURL, rootFile, kid string) (string, error) { func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
@ -148,6 +136,8 @@ func GetProvisionerKey(caURL, rootFile, kid string) (string, error) {
// PKI represents the Public Key Infrastructure used by a certificate authority. // PKI represents the Public Key Infrastructure used by a certificate authority.
type PKI struct { type PKI struct {
casOptions apiv1.Options
caCreator apiv1.CertificateAuthorityCreator
root, rootKey, rootFingerprint string root, rootKey, rootFingerprint string
intermediate, intermediateKey string intermediate, intermediateKey string
sshHostPubKey, sshHostKey string sshHostPubKey, sshHostKey string
@ -160,11 +150,15 @@ type PKI struct {
dnsNames []string dnsNames []string
caURL string caURL string
enableSSH bool enableSSH bool
authorityOptions *apiv1.Options
} }
// New creates a new PKI configuration. // 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() public := GetPublicPath()
private := GetSecretsPath() private := GetSecretsPath()
config := GetConfigPath() config := GetConfigPath()
@ -185,8 +179,9 @@ func New() (*PKI, error) {
return s, errors.Wrapf(err, "error getting absolute path for %s", name) return s, errors.Wrapf(err, "error getting absolute path for %s", name)
} }
var err error
p := &PKI{ p := &PKI{
casOptions: opts,
caCreator: caCreator,
provisioner: "step-cli", provisioner: "step-cli",
address: "127.0.0.1:9000", address: "127.0.0.1:9000",
dnsNames: []string{"127.0.0.1"}, dnsNames: []string{"127.0.0.1"},
@ -237,12 +232,6 @@ func (p *PKI) GetRootFingerprint() string {
return p.rootFingerprint 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. // SetProvisioner sets the provisioner name of the OTT keys.
func (p *PKI) SetProvisioner(s string) { func (p *PKI) SetProvisioner(s string) {
p.provisioner = s p.provisioner = s
@ -275,37 +264,65 @@ func (p *PKI) GenerateKeyPairs(pass []byte) error {
return nil return nil
} }
// GenerateRootCertificate generates a root certificate with the given name. // GenerateRootCertificate generates a root certificate with the given name
func (p *PKI) GenerateRootCertificate(name string, pass []byte) (*x509.Certificate, interface{}, error) { // and using the default key type.
signer, err := generateDefaultKey() 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 { 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 { if err != nil {
return nil, nil, err return err
} }
data := x509util.CreateTemplateData(name, []string{}) p.casOptions.CertificateAuthority = resp.Name
cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(x509util.DefaultRootTemplate, data)) return p.WriteIntermediateCertificate(resp.Certificate, resp.PrivateKey, pass)
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
} }
// WriteRootCertificate writes to disk the given certificate and key. // WriteRootCertificate writes to disk the given certificate and key.
@ -330,21 +347,45 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{
return nil 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
}
// 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 // GetCertificateAuthority attempts to load the certificate authority from the
// RA. // RA.
func (p *PKI) GetCertificateAuthority() error { func (p *PKI) GetCertificateAuthority() error {
ca, err := cas.New(context.Background(), *p.authorityOptions) srv, ok := p.caCreator.(apiv1.CertificateAuthorityGetter)
if err != nil {
return err
}
srv, ok := ca.(apiv1.CertificateAuthorityGetter)
if !ok { if !ok {
return nil return nil
} }
resp, err := srv.GetCertificateAuthority(&apiv1.GetCertificateAuthorityRequest{ resp, err := srv.GetCertificateAuthority(&apiv1.GetCertificateAuthorityRequest{
Name: p.authorityOptions.CertificateAuthority, Name: p.casOptions.CertificateAuthority,
}) })
if err != nil { if err != nil {
return err return err
@ -361,51 +402,6 @@ func (p *PKI) GetCertificateAuthority() error {
return nil 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 // GenerateSSHSigningKeys generates and encrypts a private key used for signing
// SSH user certificates and a private key used for signing host certificates. // SSH user certificates and a private key used for signing host certificates.
func (p *PKI) GenerateSSHSigningKeys(password []byte) error { func (p *PKI) GenerateSSHSigningKeys(password []byte) error {
@ -457,7 +453,7 @@ func (p *PKI) TellPKI() {
func (p *PKI) tellPKI() { func (p *PKI) tellPKI() {
ui.Println() 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 certificate", p.root)
ui.PrintSelected("Root private key", p.rootKey) ui.PrintSelected("Root private key", p.rootKey)
ui.PrintSelected("Root fingerprint", p.rootFingerprint) ui.PrintSelected("Root fingerprint", p.rootFingerprint)
@ -522,6 +518,11 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
EncryptedKey: key, EncryptedKey: key,
} }
var authorityOptions *apiv1.Options
if !p.casOptions.Is(apiv1.SoftCAS) {
authorityOptions = &p.casOptions
}
config := &authority.Config{ config := &authority.Config{
Root: []string{p.root}, Root: []string{p.root},
FederatedRoots: []string{}, FederatedRoots: []string{},
@ -535,7 +536,7 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) {
DataSource: GetDBPath(), DataSource: GetDBPath(),
}, },
AuthorityConfig: &authority.AuthConfig{ AuthorityConfig: &authority.AuthConfig{
Options: p.authorityOptions, Options: authorityOptions,
DisableIssuedAtCheck: false, DisableIssuedAtCheck: false,
Provisioners: provisioner.List{prov}, Provisioners: provisioner.List{prov},
}, },
@ -642,7 +643,7 @@ func (p *PKI) Save(opt ...Option) error {
ui.PrintSelected("Default configuration", p.defaults) ui.PrintSelected("Default configuration", p.defaults)
ui.PrintSelected("Certificate Authority configuration", p.config) ui.PrintSelected("Certificate Authority configuration", p.config)
ui.Println() 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'.") ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.")
} else { } else {
ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.") ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.")