Add most of cloudcas unit tests and minor fixes.

This commit is contained in:
Mariano Cano 2020-09-14 19:13:40 -07:00
parent 8eff4e77a8
commit 01e6495f43
5 changed files with 902 additions and 23 deletions

View file

@ -0,0 +1,56 @@
package apiv1
import (
"crypto/x509/pkix"
"fmt"
"reflect"
"testing"
)
func TestCreateCertificateAuthorityExtension(t *testing.T) {
type args struct {
typ Type
certificateID string
keyValuePairs []string
}
tests := []struct {
name string
args args
want pkix.Extension
wantErr bool
}{
{"ok", args{Type(CloudCAS), "1ac75689-cd3f-482e-a695-8a13daf39dc4", nil}, pkix.Extension{
Id: oidStepCertificateAuthority,
Critical: false,
Value: []byte{
0x30, 0x30, 0x13, 0x08, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x63, 0x61, 0x73, 0x13, 0x24, 0x31, 0x61,
0x63, 0x37, 0x35, 0x36, 0x38, 0x39, 0x2d, 0x63, 0x64, 0x33, 0x66, 0x2d, 0x34, 0x38, 0x32, 0x65,
0x2d, 0x61, 0x36, 0x39, 0x35, 0x2d, 0x38, 0x61, 0x31, 0x33, 0x64, 0x61, 0x66, 0x33, 0x39, 0x64,
0x63, 0x34,
},
}, false},
{"ok", args{Type(CloudCAS), "1ac75689-cd3f-482e-a695-8a13daf39dc4", []string{"foo", "bar"}}, pkix.Extension{
Id: oidStepCertificateAuthority,
Critical: false,
Value: []byte{
0x30, 0x3c, 0x13, 0x08, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x63, 0x61, 0x73, 0x13, 0x24, 0x31, 0x61,
0x63, 0x37, 0x35, 0x36, 0x38, 0x39, 0x2d, 0x63, 0x64, 0x33, 0x66, 0x2d, 0x34, 0x38, 0x32, 0x65,
0x2d, 0x61, 0x36, 0x39, 0x35, 0x2d, 0x38, 0x61, 0x31, 0x33, 0x64, 0x61, 0x66, 0x33, 0x39, 0x64,
0x63, 0x34, 0x30, 0x0a, 0x13, 0x03, 0x66, 0x6f, 0x6f, 0x13, 0x03, 0x62, 0x61, 0x72,
},
}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := CreateCertificateAuthorityExtension(tt.args.typ, tt.args.certificateID, tt.args.keyValuePairs...)
if (err != nil) != tt.wantErr {
t.Errorf("CreateCertificateAuthorityExtension() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CreateCertificateAuthorityExtension() = %v, want %v", got, tt.want)
fmt.Printf("%x\n", got.Value)
}
})
}
}

View file

@ -70,7 +70,7 @@ func createPublicKey(key crypto.PublicKey) (*pb.PublicKey, error) {
return nil, errors.Wrap(err, "error marshaling public key") return nil, errors.Wrap(err, "error marshaling public key")
} }
return &pb.PublicKey{ return &pb.PublicKey{
Type: pb.PublicKey_PEM_RSA_KEY, Type: pb.PublicKey_PEM_EC_KEY,
Key: pem.EncodeToMemory(&pem.Block{ Key: pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY", Type: "PUBLIC KEY",
Bytes: asn1Bytes, Bytes: asn1Bytes,
@ -215,9 +215,9 @@ func createReusableConfig(cert *x509.Certificate) *pb.ReusableConfigWrapper {
unknownEKUs = append(unknownEKUs, createObjectID(oid)) unknownEKUs = append(unknownEKUs, createObjectID(oid))
} }
policyIDs := make([]*pb.ObjectId, len(cert.PolicyIdentifiers)) var policyIDs []*pb.ObjectId
for i, oid := range cert.PolicyIdentifiers { for _, oid := range cert.PolicyIdentifiers {
policyIDs[i] = createObjectID(oid) policyIDs = append(policyIDs, createObjectID(oid))
} }
var caOptions *pb.ReusableConfigValues_CaOptions var caOptions *pb.ReusableConfigValues_CaOptions

View file

@ -0,0 +1,213 @@
package cloudcas
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"reflect"
"testing"
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
)
var (
testLeafPrivateKey = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAdUSRBrpgHFilN4eaGlNnX2+xfjX
a1Iwk2/+AensjFTXJi1UAIB0e+4pqi7Sen5E2QVBhntEHCrA3xOf7czgPw==
-----END PUBLIC KEY-----
`
testRSACertificate = `-----BEGIN CERTIFICATE-----
MIICozCCAkmgAwIBAgIRANNhMpODj7ThgviZCoF6kj8wCgYIKoZIzj0EAwIwKjEo
MCYGA1UEAxMfR29vZ2xlIENBUyBUZXN0IEludGVybWVkaWF0ZSBDQTAeFw0yMDA5
MTUwMTUxMDdaFw0zMDA5MTMwMTUxMDNaMB0xGzAZBgNVBAMTEnRlc3Quc21hbGxz
dGVwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANPRjuIlsP5Z
672syAsHlbILFabG/xmrlsO0UdcLo4Yjf9WPAFA+7q+CsVDFh4dQbMv96fsHtdYP
E9wlWyMqYG+5E8QT2i0WNFEoYcXOGZuXdyD/TA5Aucu1RuYLrZXQrXWDnvaWOgvr
EZ6s9VsPCzzkL8KBejIMQIMY0KXEJfB/HgXZNn8V2trZkWT5CzxbcOF3s3UC1Z6F
Ja6zjpxhSyRkqgknJxv6yK4t7HEwdhrDI8uyxJYHPQWKNRjWecHWE9E+MtoS7D08
mTh8qlAKoBbkGolR2nJSXffU09F3vSg+MIfjPiRqjf6394cQ3T9D5yZK//rCrxWU
8KKBQMEmdKcCAwEAAaOBkTCBjjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYI
KwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQffuoYvH1+IF1cipl35gXJxSJE
SjAfBgNVHSMEGDAWgBRIOVqyLDSlErJLuWWEvRm5UU1r1TAdBgNVHREEFjAUghJ0
ZXN0LnNtYWxsc3RlcC5jb20wCgYIKoZIzj0EAwIDSAAwRQIhAL9AAw/LVLvvxBkM
sJnHd+RIk7ZblkgcArwpIS2+Z5xNAiBtUED4zyimz9b4aQiXdw4IMd2CKxVyW8eE
6x1vSZMvzQ==
-----END CERTIFICATE-----`
testRSAPublicKey = `-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA09GO4iWw/lnrvazICweVsgsVpsb/GauWw7RR1wujhiN/1Y8AUD7u
r4KxUMWHh1Bsy/3p+we11g8T3CVbIypgb7kTxBPaLRY0UShhxc4Zm5d3IP9MDkC5
y7VG5gutldCtdYOe9pY6C+sRnqz1Ww8LPOQvwoF6MgxAgxjQpcQl8H8eBdk2fxXa
2tmRZPkLPFtw4XezdQLVnoUlrrOOnGFLJGSqCScnG/rIri3scTB2GsMjy7LElgc9
BYo1GNZ5wdYT0T4y2hLsPTyZOHyqUAqgFuQaiVHaclJd99TT0Xe9KD4wh+M+JGqN
/rf3hxDdP0PnJkr/+sKvFZTwooFAwSZ0pwIDAQAB
-----END RSA PUBLIC KEY-----
`
)
func Test_createCertificateConfig(t *testing.T) {
cert := mustParseCertificate(t, testLeafCertificate)
type args struct {
tpl *x509.Certificate
}
tests := []struct {
name string
args args
want *pb.Certificate_Config
wantErr bool
}{
{"ok", args{cert}, &pb.Certificate_Config{
Config: &pb.CertificateConfig{
SubjectConfig: &pb.CertificateConfig_SubjectConfig{
Subject: &pb.Subject{},
CommonName: "test.smallstep.com",
SubjectAltName: &pb.SubjectAltNames{
DnsNames: []string{"test.smallstep.com"},
},
},
ReusableConfig: &pb.ReusableConfigWrapper{
ConfigValues: &pb.ReusableConfigWrapper_ReusableConfigValues{
ReusableConfigValues: &pb.ReusableConfigValues{
KeyUsage: &pb.KeyUsage{
BaseKeyUsage: &pb.KeyUsage_KeyUsageOptions{
DigitalSignature: true,
},
ExtendedKeyUsage: &pb.KeyUsage_ExtendedKeyUsageOptions{
ClientAuth: true,
ServerAuth: true,
},
},
},
},
},
PublicKey: &pb.PublicKey{
Type: pb.PublicKey_PEM_EC_KEY,
Key: []byte(testLeafPrivateKey),
},
},
}, false},
{"fail", args{&x509.Certificate{}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := createCertificateConfig(tt.args.tpl)
if (err != nil) != tt.wantErr {
t.Errorf("createCertificateConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("createCertificateConfig() = %v, want %v", got.Config.ReusableConfig, tt.want.Config.ReusableConfig)
}
})
}
}
func Test_createPublicKey(t *testing.T) {
edpub, _, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
ecCert := mustParseCertificate(t, testLeafCertificate)
rsaCert := mustParseCertificate(t, testRSACertificate)
type args struct {
key crypto.PublicKey
}
tests := []struct {
name string
args args
want *pb.PublicKey
wantErr bool
}{
{"ok ec", args{ecCert.PublicKey}, &pb.PublicKey{
Type: pb.PublicKey_PEM_EC_KEY,
Key: []byte(testLeafPrivateKey),
}, false},
{"ok rsa", args{rsaCert.PublicKey}, &pb.PublicKey{
Type: pb.PublicKey_PEM_RSA_KEY,
Key: []byte(testRSAPublicKey),
}, false},
{"fail ed25519", args{edpub}, nil, true},
{"fail ec marshal", args{&ecdsa.PublicKey{
Curve: &elliptic.CurveParams{Name: "FOO", BitSize: 256},
X: ecCert.PublicKey.(*ecdsa.PublicKey).X,
Y: ecCert.PublicKey.(*ecdsa.PublicKey).Y,
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := createPublicKey(tt.args.key)
if (err != nil) != tt.wantErr {
t.Errorf("createPublicKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("createPublicKey() = %v, want %v", got, tt.want)
}
})
}
}
func Test_createSubject(t *testing.T) {
type args struct {
cert *x509.Certificate
}
tests := []struct {
name string
args args
want *pb.Subject
}{
{"ok empty", args{&x509.Certificate{}}, &pb.Subject{}},
{"ok all", args{&x509.Certificate{
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"Smallstep Labs"},
OrganizationalUnit: []string{"Engineering"},
Locality: []string{"San Francisco"},
Province: []string{"California"},
StreetAddress: []string{"1 A St."},
PostalCode: []string{"12345"},
SerialNumber: "1234567890",
CommonName: "test.smallstep.com",
},
}}, &pb.Subject{
CountryCode: "US",
Organization: "Smallstep Labs",
OrganizationalUnit: "Engineering",
Locality: "San Francisco",
Province: "California",
StreetAddress: "1 A St.",
PostalCode: "12345",
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := createSubject(tt.args.cert); !reflect.DeepEqual(got, tt.want) {
t.Errorf("createSubject() = %v, want %v", got, tt.want)
}
})
}
}
func Test_createSubjectAlternativeNames(t *testing.T) {
type args struct {
cert *x509.Certificate
}
tests := []struct {
name string
args args
want *pb.SubjectAltNames
}{
{"ok empty", args{&x509.Certificate{}}, &pb.SubjectAltNames{}},
// TODO
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := createSubjectAlternativeNames(tt.args.cert); !reflect.DeepEqual(got, tt.want) {
t.Errorf("createSubjectAlternativeNames() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -2,15 +2,15 @@ package cloudcas
import ( import (
"context" "context"
"crypto/rand"
"crypto/x509" "crypto/x509"
"encoding/asn1" "encoding/asn1"
"encoding/json"
"encoding/pem" "encoding/pem"
"fmt"
"time" "time"
privateca "cloud.google.com/go/security/privateca/apiv1beta1" privateca "cloud.google.com/go/security/privateca/apiv1beta1"
"github.com/google/uuid" "github.com/google/uuid"
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"
"google.golang.org/api/option" "google.golang.org/api/option"
@ -24,9 +24,11 @@ func init() {
}) })
} }
func debug(v interface{}) { // CertificateAuthorityClient is the interface implemented by the Google CAS
b, _ := json.MarshalIndent(v, "", " ") // client.
fmt.Println(string(b)) type CertificateAuthorityClient interface {
CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error)
RevokeCertificate(ctx context.Context, req *pb.RevokeCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error)
} }
var ( var (
@ -34,13 +36,40 @@ var (
stepOIDCertificateAuthority = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 2)...) stepOIDCertificateAuthority = append(asn1.ObjectIdentifier(nil), append(stepOIDRoot, 2)...)
) )
// recocationCodeMap maps revocation reason codes from RFC 5280, to Google CAS
// revocation reasons. Revocation reason 7 is not used, and revocation reason 8
// (removeFromCRL) is not supported by Google CAS.
var revocationCodeMap = map[int]pb.RevocationReason{
0: pb.RevocationReason_REVOCATION_REASON_UNSPECIFIED,
1: pb.RevocationReason_KEY_COMPROMISE,
2: pb.RevocationReason_CERTIFICATE_AUTHORITY_COMPROMISE,
3: pb.RevocationReason_AFFILIATION_CHANGED,
4: pb.RevocationReason_SUPERSEDED,
5: pb.RevocationReason_CESSATION_OF_OPERATION,
6: pb.RevocationReason_CERTIFICATE_HOLD,
9: pb.RevocationReason_PRIVILEGE_WITHDRAWN,
10: pb.RevocationReason_ATTRIBUTE_AUTHORITY_COMPROMISE,
}
// CloudCAS implements a Certificate Authority Service using Google Cloud CAS. // CloudCAS implements a Certificate Authority Service using Google Cloud CAS.
type CloudCAS struct { type CloudCAS struct {
client *privateca.CertificateAuthorityClient client CertificateAuthorityClient
certificateAuthority string certificateAuthority string
} }
type caClient interface{} // newCertificateAuthorityClient creates the certificate authority client. This
// function is used for testing purposes.
var newCertificateAuthorityClient = func(ctx context.Context, credentialsFile string) (CertificateAuthorityClient, error) {
var cloudOpts []option.ClientOption
if credentialsFile != "" {
cloudOpts = append(cloudOpts, option.WithCredentialsFile(credentialsFile))
}
client, err := privateca.NewCertificateAuthorityClient(ctx, cloudOpts...)
if err != nil {
return nil, errors.Wrap(err, "error creating client")
}
return client, nil
}
// New creates a new CertificateAuthorityService implementation using Google // New creates a new CertificateAuthorityService implementation using Google
// Cloud CAS. // Cloud CAS.
@ -49,14 +78,9 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty") return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty")
} }
var cloudOpts []option.ClientOption client, err := newCertificateAuthorityClient(ctx, opts.CredentialsFile)
if opts.CredentialsFile != "" {
cloudOpts = append(cloudOpts, option.WithCredentialsFile(opts.CredentialsFile))
}
client, err := privateca.NewCertificateAuthorityClient(ctx, cloudOpts...)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error creating client") return nil, err
} }
return &CloudCAS{ return &CloudCAS{
@ -109,7 +133,11 @@ func (c *CloudCAS) RenewCertificate(req *apiv1.RenewCertificateRequest) (*apiv1.
// RevokeCertificate a certificate using Google Cloud CAS. // RevokeCertificate 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) {
if req.Certificate == nil { reason, ok := revocationCodeMap[req.ReasonCode]
switch {
case !ok:
return nil, errors.Errorf("revokeCertificate 'reasonCode=%d' is invalid or not supported", req.ReasonCode)
case req.Certificate == nil:
return nil, errors.New("revokeCertificateRequest `certificate` cannot be nil") return nil, errors.New("revokeCertificateRequest `certificate` cannot be nil")
} }
@ -119,7 +147,7 @@ func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv
} }
var cae apiv1.CertificateAuthorityExtension var cae apiv1.CertificateAuthorityExtension
if _, err := asn1.Unmarshal(ext.Value, &ext); err != nil { if _, err := asn1.Unmarshal(ext.Value, &cae); err != nil {
return nil, errors.Wrap(err, "error unmarshaling certificate authority extension") return nil, errors.Wrap(err, "error unmarshaling certificate authority extension")
} }
@ -127,8 +155,9 @@ func (c *CloudCAS) RevokeCertificate(req *apiv1.RevokeCertificateRequest) (*apiv
defer cancel() defer cancel()
certpb, err := c.client.RevokeCertificate(ctx, &pb.RevokeCertificateRequest{ certpb, err := c.client.RevokeCertificate(ctx, &pb.RevokeCertificateRequest{
Name: c.certificateAuthority + "/certificates/" + cae.CertificateID, Name: c.certificateAuthority + "/certificates/" + cae.CertificateID,
Reason: pb.RevocationReason_REVOCATION_REASON_UNSPECIFIED, Reason: reason,
RequestId: req.RequestID,
}) })
if err != nil { if err != nil {
return nil, errors.Wrap(err, "cloudCAS RevokeCertificate failed") return nil, errors.Wrap(err, "cloudCAS RevokeCertificate failed")
@ -192,7 +221,7 @@ func defaultContext() (context.Context, context.CancelFunc) {
} }
func createCertificateID() (string, error) { func createCertificateID() (string, error) {
id, err := uuid.NewRandom() id, err := uuid.NewRandomFromReader(rand.Reader)
if err != nil { if err != nil {
return "", errors.Wrap(err, "error creating certificate id") return "", errors.Wrap(err, "error creating certificate id")
} }

View file

@ -0,0 +1,581 @@
package cloudcas
import (
"bytes"
"context"
"crypto/rand"
"crypto/x509"
"encoding/asn1"
"io"
"os"
"reflect"
"testing"
"time"
"github.com/google/uuid"
gax "github.com/googleapis/gax-go/v2"
"github.com/pkg/errors"
"github.com/smallstep/certificates/cas/apiv1"
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
)
var (
errTest = errors.New("test error")
testAuthorityName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca"
testCertificateName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca/certificates/test-certificate"
testRootCertificate = `-----BEGIN CERTIFICATE-----
MIIBhjCCAS2gAwIBAgIQLbKTuXau4+t3KFbGpJJAADAKBggqhkjOPQQDAjAiMSAw
HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla
Fw0zMDA5MTIyMjQ4NDlaMCIxIDAeBgNVBAMTF0dvb2dsZSBDQVMgVGVzdCBSb290
IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYKGgQ3/0D7+oBTc0CXoYfSC6
M8hOqLsmzBapPZSYpfwjgEsjdNU84jdrYmW1zF1+p+MrL4c7qJv9NLo/picCuqNF
MEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
FFVn9V7Qymd7cUJh9KAhnUDAQL5YMAoGCCqGSM49BAMCA0cAMEQCIA4LzttYoT3u
8TYgSrvFT+Z+cklfi4UrPBU6aSbcUaW2AiAPfaqbyccQT3CxMVyHg+xZZjAirZp8
lAeA/T4FxAonHA==
-----END CERTIFICATE-----`
testIntermediateCertificate = `-----BEGIN CERTIFICATE-----
MIIBsDCCAVagAwIBAgIQOb91kHxWKVzSJ9ESW1ViVzAKBggqhkjOPQQDAjAiMSAw
HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla
Fw0zMDA5MTIyMjQ4NDlaMCoxKDAmBgNVBAMTH0dvb2dsZSBDQVMgVGVzdCBJbnRl
cm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASUHN1cNyId4Ei/
4MxD5VrZFc51P50caMUdDZVrPveidChBYCU/9IM6vnRlZHx2HLjQ0qAvqHwY3rT0
xc7n+PfCo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAd
BgNVHQ4EFgQUSDlasiw0pRKyS7llhL0ZuVFNa9UwHwYDVR0jBBgwFoAUVWf1XtDK
Z3txQmH0oCGdQMBAvlgwCgYIKoZIzj0EAwIDSAAwRQIgMmsLcoC4KriXw+s+cZx2
bJMf6Mx/WESj31buJJhpzY0CIQCBUa/JtvS3nyce/4DF5tK2v49/NWHREgqAaZ57
DcYyHQ==
-----END CERTIFICATE-----`
testLeafCertificate = `-----BEGIN CERTIFICATE-----
MIIB1jCCAX2gAwIBAgIQQfOn+COMeuD8VYF1TiDkEzAKBggqhkjOPQQDAjAqMSgw
JgYDVQQDEx9Hb29nbGUgQ0FTIFRlc3QgSW50ZXJtZWRpYXRlIENBMB4XDTIwMDkx
NDIyNTE1NVoXDTMwMDkxMjIyNTE1MlowHTEbMBkGA1UEAxMSdGVzdC5zbWFsbHN0
ZXAuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAdUSRBrpgHFilN4eaGlN
nX2+xfjXa1Iwk2/+AensjFTXJi1UAIB0e+4pqi7Sen5E2QVBhntEHCrA3xOf7czg
P6OBkTCBjjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
AQUFBwMCMB0GA1UdDgQWBBSYPbu4Tmm7Zze/hCePeZH1Avoj+jAfBgNVHSMEGDAW
gBRIOVqyLDSlErJLuWWEvRm5UU1r1TAdBgNVHREEFjAUghJ0ZXN0LnNtYWxsc3Rl
cC5jb20wCgYIKoZIzj0EAwIDRwAwRAIgY+nTc+RHn31/BOhht4JpxCmJPHxqFT3S
ojnictBudV0CIB87ipY5HV3c8FLVEzTA0wFwdDZvQraQYsthwbg2kQFb
-----END CERTIFICATE-----`
testSignedCertificate = `-----BEGIN CERTIFICATE-----
MIIB/DCCAaKgAwIBAgIQHHFuGMz0cClfde5kqP5prTAKBggqhkjOPQQDAjAqMSgw
JgYDVQQDEx9Hb29nbGUgQ0FTIFRlc3QgSW50ZXJtZWRpYXRlIENBMB4XDTIwMDkx
NTAwMDQ0M1oXDTMwMDkxMzAwMDQ0MFowHTEbMBkGA1UEAxMSdGVzdC5zbWFsbHN0
ZXAuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMqNCiXMvbn74LsHzRv+8
17m9vEzH6RHrg3m82e0uEc36+fZWV/zJ9SKuONmnl5VP79LsjL5SVH0RDj73U2XO
DKOBtjCBszAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
AQUFBwMCMB0GA1UdDgQWBBRTA2cTs7PCNjnps/+T0dS8diqv0DAfBgNVHSMEGDAW
gBRIOVqyLDSlErJLuWWEvRm5UU1r1TBCBgwrBgEEAYKkZMYoQAIEMjAwEwhjbG91
ZGNhcxMkZDhkMThhNjgtNTI5Ni00YWYzLWFlNGItMmY4NzdkYTNmYmQ5MAoGCCqG
SM49BAMCA0gAMEUCIGxl+pqJ50WYWUqK2l4V1FHoXSi0Nht5kwTxFxnWZu1xAiEA
zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw=
-----END CERTIFICATE-----`
)
type testClient struct {
credentialsFile string
certificate *pb.Certificate
err error
}
func newTestClient(credentialsFile string) (CertificateAuthorityClient, error) {
if credentialsFile == "testdata/error.json" {
return nil, errTest
}
return &testClient{
credentialsFile: credentialsFile,
}, nil
}
func okTestClient() *testClient {
return &testClient{
credentialsFile: "testdata/credentials.json",
certificate: &pb.Certificate{
Name: testCertificateName,
PemCertificate: testSignedCertificate,
PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate},
},
}
}
func failTestClient() *testClient {
return &testClient{
credentialsFile: "testdata/credentials.json",
err: errTest,
}
}
func badTestClient() *testClient {
return &testClient{
credentialsFile: "testdata/credentials.json",
certificate: &pb.Certificate{
Name: testCertificateName,
PemCertificate: "not a pem cert",
PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate},
},
}
}
func setTeeReader(t *testing.T, w *bytes.Buffer) {
t.Helper()
reader := rand.Reader
t.Cleanup(func() {
rand.Reader = reader
})
rand.Reader = io.TeeReader(reader, w)
}
func (c *testClient) CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) {
return c.certificate, c.err
}
func (c *testClient) RevokeCertificate(ctx context.Context, req *pb.RevokeCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) {
return c.certificate, c.err
}
func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate {
t.Helper()
crt, err := parseCertificate(pemCert)
if err != nil {
t.Fatal(err)
}
return crt
}
func TestNew(t *testing.T) {
tmp := newCertificateAuthorityClient
newCertificateAuthorityClient = func(ctx context.Context, credentialsFile string) (CertificateAuthorityClient, error) {
return newTestClient(credentialsFile)
}
t.Cleanup(func() {
newCertificateAuthorityClient = tmp
})
type args struct {
ctx context.Context
opts apiv1.Options
}
tests := []struct {
name string
args args
want *CloudCAS
wantErr bool
}{
{"ok", args{context.Background(), apiv1.Options{
Certificateauthority: testAuthorityName,
}}, &CloudCAS{
client: &testClient{},
certificateAuthority: testAuthorityName,
}, false},
{"ok with credentials", args{context.Background(), apiv1.Options{
Certificateauthority: testAuthorityName, CredentialsFile: "testdata/credentials.json",
}}, &CloudCAS{
client: &testClient{credentialsFile: "testdata/credentials.json"},
certificateAuthority: testAuthorityName,
}, false},
{"fail certificate authority", args{context.Background(), apiv1.Options{}}, nil, true},
{"fail with credentials", args{context.Background(), apiv1.Options{
Certificateauthority: testAuthorityName, CredentialsFile: "testdata/error.json",
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := New(tt.args.ctx, tt.args.opts)
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("New() = %v, want %v", got, tt.want)
}
})
}
}
func TestNew_real(t *testing.T) {
if v, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS"); ok {
os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS")
t.Cleanup(func() {
os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", v)
})
}
type args struct {
ctx context.Context
opts apiv1.Options
}
tests := []struct {
name string
args args
wantErr bool
}{
{"fail default credentials", args{context.Background(), apiv1.Options{Certificateauthority: testAuthorityName}}, true},
{"fail certificate authority", args{context.Background(), apiv1.Options{}}, true},
{"fail with credentials", args{context.Background(), apiv1.Options{
Certificateauthority: testAuthorityName, CredentialsFile: "testdata/missing.json",
}}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := New(tt.args.ctx, tt.args.opts)
if (err != nil) != tt.wantErr {
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestCloudCAS_CreateCertificate(t *testing.T) {
type fields struct {
client CertificateAuthorityClient
certificateAuthority string
}
type args struct {
req *apiv1.CreateCertificateRequest
}
tests := []struct {
name string
fields fields
args args
want *apiv1.CreateCertificateResponse
wantErr bool
}{
{"ok", fields{okTestClient(), testCertificateName}, args{&apiv1.CreateCertificateRequest{
Template: mustParseCertificate(t, testLeafCertificate),
Lifetime: 24 * time.Hour,
}}, &apiv1.CreateCertificateResponse{
Certificate: mustParseCertificate(t, testSignedCertificate),
CertificateChain: []*x509.Certificate{mustParseCertificate(t, testIntermediateCertificate)},
}, false},
{"fail Template", fields{okTestClient(), testCertificateName}, args{&apiv1.CreateCertificateRequest{
Lifetime: 24 * time.Hour,
}}, nil, true},
{"fail Lifetime", fields{okTestClient(), testCertificateName}, args{&apiv1.CreateCertificateRequest{
Template: mustParseCertificate(t, testLeafCertificate),
}}, nil, true},
{"fail CreateCertificate", fields{failTestClient(), testCertificateName}, args{&apiv1.CreateCertificateRequest{
Template: mustParseCertificate(t, testLeafCertificate),
Lifetime: 24 * time.Hour,
}}, nil, true},
{"fail Certificate", fields{badTestClient(), testCertificateName}, args{&apiv1.CreateCertificateRequest{
Template: mustParseCertificate(t, testLeafCertificate),
Lifetime: 24 * time.Hour,
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CloudCAS{
client: tt.fields.client,
certificateAuthority: tt.fields.certificateAuthority,
}
got, err := c.CreateCertificate(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("CloudCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudCAS.CreateCertificate() = %v, want %v", got, tt.want)
}
})
}
}
func TestCloudCAS_createCertificate(t *testing.T) {
leaf := mustParseCertificate(t, testLeafCertificate)
signed := mustParseCertificate(t, testSignedCertificate)
chain := []*x509.Certificate{mustParseCertificate(t, testIntermediateCertificate)}
type fields struct {
client CertificateAuthorityClient
certificateAuthority string
}
type args struct {
tpl *x509.Certificate
lifetime time.Duration
requestID string
}
tests := []struct {
name string
fields fields
args args
want *x509.Certificate
want1 []*x509.Certificate
wantErr bool
}{
{"ok", fields{okTestClient(), testAuthorityName}, args{leaf, 24 * time.Hour, "request-id"}, signed, chain, false},
{"fail CertificateConfig", fields{okTestClient(), testAuthorityName}, args{&x509.Certificate{}, 24 * time.Hour, "request-id"}, nil, nil, true},
{"fail CreateCertificate", fields{failTestClient(), testAuthorityName}, args{leaf, 24 * time.Hour, "request-id"}, nil, nil, true},
{"fail ParseCertificates", fields{badTestClient(), testAuthorityName}, args{leaf, 24 * time.Hour, "request-id"}, nil, nil, true},
{"fail create id", fields{okTestClient(), testAuthorityName}, args{leaf, 24 * time.Hour, "request-id"}, nil, nil, true},
}
// Pre-calulate rand.Random
buf := new(bytes.Buffer)
setTeeReader(t, buf)
for i := 0; i < len(tests)-1; i++ {
_, err := uuid.NewRandomFromReader(rand.Reader)
if err != nil {
t.Fatal(err)
}
}
rand.Reader = buf
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CloudCAS{
client: tt.fields.client,
certificateAuthority: tt.fields.certificateAuthority,
}
got, got1, err := c.createCertificate(tt.args.tpl, tt.args.lifetime, tt.args.requestID)
if (err != nil) != tt.wantErr {
t.Errorf("CloudCAS.createCertificate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudCAS.createCertificate() got = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got1, tt.want1) {
t.Errorf("CloudCAS.createCertificate() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
func TestCloudCAS_RenewCertificate(t *testing.T) {
type fields struct {
client CertificateAuthorityClient
certificateAuthority string
}
type args struct {
req *apiv1.RenewCertificateRequest
}
tests := []struct {
name string
fields fields
args args
want *apiv1.RenewCertificateResponse
wantErr bool
}{
{"ok", fields{okTestClient(), testCertificateName}, args{&apiv1.RenewCertificateRequest{
Template: mustParseCertificate(t, testLeafCertificate),
Lifetime: 24 * time.Hour,
}}, &apiv1.RenewCertificateResponse{
Certificate: mustParseCertificate(t, testSignedCertificate),
CertificateChain: []*x509.Certificate{mustParseCertificate(t, testIntermediateCertificate)},
}, false},
{"fail Template", fields{okTestClient(), testCertificateName}, args{&apiv1.RenewCertificateRequest{
Lifetime: 24 * time.Hour,
}}, nil, true},
{"fail Lifetime", fields{okTestClient(), testCertificateName}, args{&apiv1.RenewCertificateRequest{
Template: mustParseCertificate(t, testLeafCertificate),
}}, nil, true},
{"fail CreateCertificate", fields{failTestClient(), testCertificateName}, args{&apiv1.RenewCertificateRequest{
Template: mustParseCertificate(t, testLeafCertificate),
Lifetime: 24 * time.Hour,
}}, nil, true},
{"fail Certificate", fields{badTestClient(), testCertificateName}, args{&apiv1.RenewCertificateRequest{
Template: mustParseCertificate(t, testLeafCertificate),
Lifetime: 24 * time.Hour,
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CloudCAS{
client: tt.fields.client,
certificateAuthority: tt.fields.certificateAuthority,
}
got, err := c.RenewCertificate(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("CloudCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudCAS.RenewCertificate() = %v, want %v", got, tt.want)
}
})
}
}
func TestCloudCAS_RevokeCertificate(t *testing.T) {
badExtensionCert := mustParseCertificate(t, testSignedCertificate)
for i, ext := range badExtensionCert.Extensions {
if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 37476, 9000, 64, 2}) {
badExtensionCert.Extensions[i].Value = []byte("bad-data")
}
}
type fields struct {
client CertificateAuthorityClient
certificateAuthority string
}
type args struct {
req *apiv1.RevokeCertificateRequest
}
tests := []struct {
name string
fields fields
args args
want *apiv1.RevokeCertificateResponse
wantErr bool
}{
{"ok", fields{okTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
Certificate: mustParseCertificate(t, testSignedCertificate),
ReasonCode: 1,
}}, &apiv1.RevokeCertificateResponse{
Certificate: mustParseCertificate(t, testSignedCertificate),
CertificateChain: []*x509.Certificate{mustParseCertificate(t, testIntermediateCertificate)},
}, false},
{"fail Extension", fields{okTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
Certificate: mustParseCertificate(t, testLeafCertificate),
ReasonCode: 1,
}}, nil, true},
{"fail Extension Value", fields{okTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
Certificate: badExtensionCert,
ReasonCode: 1,
}}, nil, true},
{"fail Certificate", fields{okTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
ReasonCode: 2,
}}, nil, true},
{"fail ReasonCode", fields{okTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
Certificate: mustParseCertificate(t, testSignedCertificate),
ReasonCode: 100,
}}, nil, true},
{"fail ReasonCode 7", fields{okTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
Certificate: mustParseCertificate(t, testSignedCertificate),
ReasonCode: 7,
}}, nil, true},
{"fail ReasonCode 8", fields{okTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
Certificate: mustParseCertificate(t, testSignedCertificate),
ReasonCode: 8,
}}, nil, true},
{"fail RevokeCertificate", fields{failTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
Certificate: mustParseCertificate(t, testSignedCertificate),
ReasonCode: 1,
}}, nil, true},
{"fail ParseCertificate", fields{badTestClient(), testCertificateName}, args{&apiv1.RevokeCertificateRequest{
Certificate: mustParseCertificate(t, testSignedCertificate),
ReasonCode: 1,
}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CloudCAS{
client: tt.fields.client,
certificateAuthority: tt.fields.certificateAuthority,
}
got, err := c.RevokeCertificate(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("CloudCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudCAS.RevokeCertificate() = %v, want %v", got, tt.want)
}
})
}
}
func Test_createCertificateID(t *testing.T) {
buf := new(bytes.Buffer)
setTeeReader(t, buf)
uuid, err := uuid.NewRandomFromReader(rand.Reader)
if err != nil {
t.Fatal(err)
}
rand.Reader = buf
tests := []struct {
name string
want string
wantErr bool
}{
{"ok", uuid.String(), false},
{"fail", "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := createCertificateID()
if (err != nil) != tt.wantErr {
t.Errorf("createCertificateID() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("createCertificateID() = %v, want %v", got, tt.want)
}
})
}
}
func Test_parseCertificate(t *testing.T) {
type args struct {
pemCert string
}
tests := []struct {
name string
args args
want *x509.Certificate
wantErr bool
}{
{"ok", args{testLeafCertificate}, mustParseCertificate(t, testLeafCertificate), false},
{"ok intermediate", args{testIntermediateCertificate}, mustParseCertificate(t, testIntermediateCertificate), false},
{"fail pem", args{"not pem"}, nil, true},
{"fail parseCertificate", args{"-----BEGIN CERTIFICATE-----\nZm9vYmFyCg==\n-----END CERTIFICATE-----\n"}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseCertificate(tt.args.pemCert)
if (err != nil) != tt.wantErr {
t.Errorf("parseCertificate() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseCertificate() = %v, want %v", got, tt.want)
}
})
}
}
func Test_getCertificateAndChain(t *testing.T) {
type args struct {
certpb *pb.Certificate
}
tests := []struct {
name string
args args
want *x509.Certificate
want1 []*x509.Certificate
wantErr bool
}{
{"ok", args{&pb.Certificate{
Name: testCertificateName,
PemCertificate: testSignedCertificate,
PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate},
}}, mustParseCertificate(t, testSignedCertificate), []*x509.Certificate{mustParseCertificate(t, testIntermediateCertificate)}, false},
{"fail PemCertificate", args{&pb.Certificate{
Name: testCertificateName,
PemCertificate: "foobar",
PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate},
}}, nil, nil, true},
{"fail PemCertificateChain", args{&pb.Certificate{
Name: testCertificateName,
PemCertificate: testSignedCertificate,
PemCertificateChain: []string{"foobar", testRootCertificate},
}}, nil, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1, err := getCertificateAndChain(tt.args.certpb)
if (err != nil) != tt.wantErr {
t.Errorf("getCertificateAndChain() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getCertificateAndChain() got = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got1, tt.want1) {
t.Errorf("getCertificateAndChain() got1 = %v, want %v", got1, tt.want1)
}
})
}
}