Add most of cloudcas unit tests and minor fixes.
This commit is contained in:
parent
8eff4e77a8
commit
01e6495f43
5 changed files with 902 additions and 23 deletions
56
cas/apiv1/extension_test.go
Normal file
56
cas/apiv1/extension_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
213
cas/cloudcas/certificate_test.go
Normal file
213
cas/cloudcas/certificate_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
581
cas/cloudcas/cloudcas_test.go
Normal file
581
cas/cloudcas/cloudcas_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue