forked from TrueCloudLab/certificates
Merge pull request #409 from smallstep/cloudcas-init
Add CreateCertificateAuthority
This commit is contained in:
commit
98a5aa5916
17 changed files with 2119 additions and 169 deletions
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
28
cas/cas.go
28
cas/cas.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
157
cas/cloudcas/mock_client_test.go
Normal file
157
cas/cloudcas/mock_client_test.go
Normal 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...)
|
||||||
|
}
|
270
cas/cloudcas/mock_operation_server_test.go
Normal file
270
cas/cloudcas/mock_operation_server_test.go
Normal 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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
20
go.mod
|
@ -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
39
go.sum
|
@ -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=
|
||||||
|
|
205
pki/pki.go
205
pki/pki.go
|
@ -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'.")
|
||||||
|
|
Loading…
Reference in a new issue