package cloudcas import ( "bytes" "context" "crypto/rand" "crypto/x509" "encoding/asn1" "io" "net" "os" "reflect" "testing" "time" lroauto "cloud.google.com/go/longrunning/autogen" privateca "cloud.google.com/go/security/privateca/apiv1beta1" gomock "github.com/golang/mock/gomock" "github.com/google/uuid" gax "github.com/googleapis/gax-go/v2" "github.com/pkg/errors" "github.com/smallstep/certificates/cas/apiv1" "google.golang.org/api/option" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" longrunningpb "google.golang.org/genproto/googleapis/longrunning" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/anypb" ) var ( errTest = errors.New("test error") testAuthorityName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca" testCertificateName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca/certificates/test-certificate" testProject = "test-project" testLocation = "us-west1" testRootCertificate = `-----BEGIN CERTIFICATE----- MIIBhjCCAS2gAwIBAgIQLbKTuXau4+t3KFbGpJJAADAKBggqhkjOPQQDAjAiMSAw HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla 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-----` testIntermediateCsr = `-----BEGIN CERTIFICATE REQUEST----- MIIBIjCByQIBADAqMSgwJgYDVQQDEx9Hb29nbGUgQ0FTIFRlc3QgSW50ZXJtZWRp YXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqoztio0c4XuaaGxHFiU7 UBk3YRGTae9GtlKwyZJDk740hg6ZIoKcaXrzJT5taUpPiQLi7rP1eRui0dhl/bHo o6A9MDsGCSqGSIb3DQEJDjEuMCwwKgYDVR0RBCMwIYIfR29vZ2xlIENBUyBUZXN0 IEludGVybWVkaWF0ZSBDQTAKBggqhkjOPQQDAgNIADBFAiEAvRKBPE32scAvsMe8 R7ecx91q58ZmeLaRdSzL7stsnJYCIEBu+vQUSTbUpKL2YQNclT9kbilips5pEMr3 ojxK6mk3 -----END CERTIFICATE REQUEST-----` // testIntermediateKey = `-----BEGIN EC PRIVATE KEY----- // MHcCAQEEIMM+DSPChJgcYyqDWs0eRA5BctIo+VSNqRzCTL2ARYAqoAoGCCqGSM49 // AwEHoUQDQgAEqoztio0c4XuaaGxHFiU7UBk3YRGTae9GtlKwyZJDk740hg6ZIoKc // aXrzJT5taUpPiQLi7rP1eRui0dhl/bHoow== // -----END EC PRIVATE KEY-----` ) type testClient struct { credentialsFile string certificate *pb.Certificate certificateAuthority *pb.CertificateAuthority 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}, }, certificateAuthority: &pb.CertificateAuthority{ PemCaCertificates: []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}, }, certificateAuthority: &pb.CertificateAuthority{ PemCaCertificates: []string{testIntermediateCertificate, "not a pem cert"}, }, } } 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 (c *testClient) GetCertificateAuthority(ctx context.Context, req *pb.GetCertificateAuthorityRequest, opts ...gax.CallOption) (*pb.CertificateAuthority, error) { return c.certificateAuthority, c.err } func (c *testClient) CreateCertificateAuthority(ctx context.Context, req *pb.CreateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.CreateCertificateAuthorityOperation, error) { return nil, errors.New("use NewMockCertificateAuthorityClient") } func (c *testClient) FetchCertificateAuthorityCsr(ctx context.Context, req *pb.FetchCertificateAuthorityCsrRequest, opts ...gax.CallOption) (*pb.FetchCertificateAuthorityCsrResponse, error) { return nil, errors.New("use NewMockCertificateAuthorityClient") } func (c *testClient) ActivateCertificateAuthority(ctx context.Context, req *pb.ActivateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.ActivateCertificateAuthorityOperation, error) { return nil, errors.New("use NewMockCertificateAuthorityClient") } func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate { t.Helper() crt, err := parseCertificate(pemCert) 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, project: testProject, location: testLocation, }, false}, {"ok with credentials", args{context.Background(), apiv1.Options{ CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", }}, &CloudCAS{ client: &testClient{credentialsFile: "testdata/credentials.json"}, certificateAuthority: testAuthorityName, project: testProject, location: testLocation, }, false}, {"ok creator", args{context.Background(), apiv1.Options{ IsCreator: true, Project: testProject, Location: testLocation, }}, &CloudCAS{ client: &testClient{}, project: testProject, location: testLocation, }, false}, {"fail certificate authority", args{context.Background(), apiv1.Options{}}, nil, true}, {"fail with credentials", args{context.Background(), apiv1.Options{ CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/error.json", }}, nil, true}, {"fail creator project", args{context.Background(), apiv1.Options{ IsCreator: true, Project: "", Location: testLocation, }}, nil, true}, {"fail creator location", args{context.Background(), apiv1.Options{ IsCreator: true, Project: testProject, Location: "", }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { 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_register(t *testing.T) { tmp := newCertificateAuthorityClient newCertificateAuthorityClient = func(ctx context.Context, credentialsFile string) (CertificateAuthorityClient, error) { return newTestClient(credentialsFile) } t.Cleanup(func() { newCertificateAuthorityClient = tmp }) want := &CloudCAS{ client: &testClient{credentialsFile: "testdata/credentials.json"}, certificateAuthority: testAuthorityName, project: testProject, location: testLocation, } newFn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.CloudCAS) if !ok { t.Error("apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.CloudCAS) was not found") return } got, err := newFn(context.Background(), apiv1.Options{ CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", }) if err != nil { t.Errorf("New() error = %v", err) return } if !reflect.DeepEqual(got, want) { t.Errorf("New() = %v, want %v", got, 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 skipOnCI bool args args wantErr bool }{ {"fail default credentials", true, args{context.Background(), apiv1.Options{CertificateAuthority: testAuthorityName}}, true}, {"fail certificate authority", false, args{context.Background(), apiv1.Options{}}, true}, {"fail with credentials", false, args{context.Background(), apiv1.Options{ CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/missing.json", }}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.skipOnCI && os.Getenv("CI") == "true" { t.SkipNow() } _, 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_GetCertificateAuthority(t *testing.T) { root := mustParseCertificate(t, testRootCertificate) type fields struct { client CertificateAuthorityClient certificateAuthority string } type args struct { req *apiv1.GetCertificateAuthorityRequest } tests := []struct { name string fields fields args args want *apiv1.GetCertificateAuthorityResponse wantErr bool }{ {"ok", fields{okTestClient(), testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{}}, &apiv1.GetCertificateAuthorityResponse{ RootCertificate: root, }, false}, {"ok with name", fields{okTestClient(), testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{ Name: testCertificateName, }}, &apiv1.GetCertificateAuthorityResponse{ RootCertificate: root, }, false}, {"fail GetCertificateAuthority", fields{failTestClient(), testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{}}, nil, true}, {"fail bad root", fields{badTestClient(), testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{}}, nil, true}, {"fail no pems", fields{&testClient{certificateAuthority: &pb.CertificateAuthority{}}, testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{}}, 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.GetCertificateAuthority(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("CloudCAS.GetCertificateAuthority() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CloudCAS.GetCertificateAuthority() = %v, want %v", got, tt.want) } }) } } 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-calculate 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) } }) } } func TestCloudCAS_CreateCertificateAuthority(t *testing.T) { must := func(a, b interface{}) interface{} { return a } // client, close := mockTestClient() // defer close() ctrl := gomock.NewController(t) defer ctrl.Finish() mosCtrl := gomock.NewController(t) defer mosCtrl.Finish() m := NewMockCertificateAuthorityClient(ctrl) mos := NewMockOperationsServer(mosCtrl) // Create operation server lis, err := net.Listen("tcp", "localhost:0") if err != nil { t.Fatal(err) } srv := grpc.NewServer() longrunningpb.RegisterOperationsServer(srv, mos) go srv.Serve(lis) defer srv.Stop() // Create fake privateca client conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure()) if err != nil { t.Fatal(err) } 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().CreateCertificate(any, any).Return(&pb.Certificate{ PemCertificate: testIntermediateCertificate, PemCertificateChain: []string{testRootCertificate}, }, nil) m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil) mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{ Name: "ActivateCertificateAuthority", Done: true, Result: &longrunningpb.Operation_Response{ Response: must(anypb.New(&pb.CertificateAuthority{ Name: testAuthorityName, PemCaCertificates: []string{testIntermediateCertificate, testRootCertificate}, })).(*anypb.Any), }, }, nil) rootCrt := mustParseCertificate(t, testRootCertificate) intCrt := mustParseCertificate(t, testIntermediateCertificate) type fields struct { client CertificateAuthorityClient certificateAuthority string project string location string } type args struct { req *apiv1.CreateCertificateAuthorityRequest } tests := []struct { name string fields fields args args want *apiv1.CreateCertificateAuthorityResponse wantErr bool }{ {"ok root", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ Type: apiv1.RootCA, Template: mustParseCertificate(t, testRootCertificate), Lifetime: 24 * time.Hour, }}, &apiv1.CreateCertificateAuthorityResponse{ Name: testAuthorityName, Certificate: rootCrt, }, false}, {"ok intermediate", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{ Type: apiv1.IntermediateCA, Template: mustParseCertificate(t, testIntermediateCertificate), Lifetime: 24 * time.Hour, Parent: &apiv1.CreateCertificateAuthorityResponse{ Name: testAuthorityName, Certificate: rootCrt, }, }}, &apiv1.CreateCertificateAuthorityResponse{ Name: testAuthorityName, Certificate: intCrt, CertificateChain: []*x509.Certificate{rootCrt}, }, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &CloudCAS{ client: tt.fields.client, certificateAuthority: tt.fields.certificateAuthority, project: tt.fields.project, location: tt.fields.location, } got, err := c.CreateCertificateAuthority(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("CloudCAS.CreateCertificateAuthority() error = %+v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("CloudCAS.CreateCertificateAuthority() = %v, want %v", got, tt.want) } }) } }