package softcas import ( "bytes" "context" "crypto" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "io" "math/big" "reflect" "testing" "time" "go.step.sm/crypto/pemutil" "go.step.sm/crypto/x509util" "github.com/smallstep/certificates/cas/apiv1" ) var ( testIntermediatePem = `-----BEGIN CERTIFICATE----- MIIBPjCB8aADAgECAhAk4aPIlsVvQg3gveApc3mIMAUGAytlcDAeMRwwGgYDVQQD ExNTbWFsbHN0ZXAgVW5pdCBUZXN0MB4XDTIwMDkxNjAyMDgwMloXDTMwMDkxNDAy MDgwMlowHjEcMBoGA1UEAxMTU21hbGxzdGVwIFVuaXQgVGVzdDAqMAUGAytlcAMh ANLs3JCzECR29biut0NDsaLnh0BGij5eJx6VkdJPfS/ko0UwQzAOBgNVHQ8BAf8E BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUup5qpZFMAFdgK7RB xNzmUaQM8YwwBQYDK2VwA0EAAwcW25E/6bchyKwp3RRK1GXiPMDCc+hsTJxuOLWy YM7ga829dU8X4pRcEEAcBndqCED/502excjEK7U9vCkFCg== -----END CERTIFICATE-----` testIntermediateKeyPem = `-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEII9ZckcrDKlbhZKR0jp820Uz6mOMLFsq2JhI+Tl7WJwH -----END PRIVATE KEY-----` ) var ( testIssuer = mustIssuer() testSigner = mustSigner() testTemplate = &x509.Certificate{ Subject: pkix.Name{CommonName: "test.smallstep.com"}, DNSNames: []string{"test.smallstep.com"}, KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, PublicKey: mustSigner().Public(), SerialNumber: big.NewInt(1234), } testNow = time.Now() testSignedTemplate = mustSign(testTemplate, testNow, testNow.Add(24*time.Hour)) ) func mockNow(t *testing.T) { tmp := now now = func() time.Time { return testNow } t.Cleanup(func() { now = tmp }) } func mustIssuer() *x509.Certificate { v, err := pemutil.Parse([]byte(testIntermediatePem)) if err != nil { panic(err) } return v.(*x509.Certificate) } func mustSigner() crypto.Signer { v, err := pemutil.Parse([]byte(testIntermediateKeyPem)) if err != nil { panic(err) } return v.(crypto.Signer) } func mustSign(template *x509.Certificate, notBefore, notAfter time.Time) *x509.Certificate { tmpl := *template tmpl.NotBefore = notBefore tmpl.NotAfter = notAfter tmpl.Issuer = testIssuer.Subject cert, err := x509util.CreateCertificate(&tmpl, testIssuer, tmpl.PublicKey, testSigner) if err != nil { panic(err) } return 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 TestNew(t *testing.T) { type args struct { ctx context.Context opts apiv1.Options } tests := []struct { name string args args want *SoftCAS wantErr bool }{ {"ok", args{context.Background(), apiv1.Options{Issuer: testIssuer, Signer: testSigner}}, &SoftCAS{Issuer: testIssuer, Signer: testSigner}, false}, {"fail no issuer", args{context.Background(), apiv1.Options{Signer: testSigner}}, nil, true}, {"fail no signer", args{context.Background(), apiv1.Options{Issuer: testIssuer}}, 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) { newFn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.SoftCAS) if !ok { t.Error("apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.SoftCAS) was not found") return } want := &SoftCAS{ Issuer: testIssuer, Signer: testSigner, } got, err := newFn(context.Background(), apiv1.Options{Issuer: testIssuer, Signer: testSigner}) if err != nil { t.Errorf("New() error = %v", err) return } if !reflect.DeepEqual(got, want) { t.Errorf("New() = %v, want %v", got, want) } } func TestSoftCAS_CreateCertificate(t *testing.T) { mockNow(t) // Set rand.Reader to EOF buf := new(bytes.Buffer) setTeeReader(t, buf) rand.Reader = buf tmplNotBefore := *testTemplate tmplNotBefore.NotBefore = testNow tmplNotAfter := *testTemplate tmplNotAfter.NotAfter = testNow.Add(24 * time.Hour) tmplWithLifetime := *testTemplate tmplWithLifetime.NotBefore = testNow tmplWithLifetime.NotAfter = testNow.Add(24 * time.Hour) tmplNoSerial := *testTemplate tmplNoSerial.SerialNumber = nil type fields struct { Issuer *x509.Certificate Signer crypto.Signer } type args struct { req *apiv1.CreateCertificateRequest } tests := []struct { name string fields fields args args want *apiv1.CreateCertificateResponse wantErr bool }{ {"ok", fields{testIssuer, testSigner}, args{&apiv1.CreateCertificateRequest{ Template: testTemplate, Lifetime: 24 * time.Hour, }}, &apiv1.CreateCertificateResponse{ Certificate: testSignedTemplate, CertificateChain: []*x509.Certificate{testIssuer}, }, false}, {"ok with notBefore", fields{testIssuer, testSigner}, args{&apiv1.CreateCertificateRequest{ Template: &tmplNotBefore, Lifetime: 24 * time.Hour, }}, &apiv1.CreateCertificateResponse{ Certificate: testSignedTemplate, CertificateChain: []*x509.Certificate{testIssuer}, }, false}, {"ok with notBefore+notAfter", fields{testIssuer, testSigner}, args{&apiv1.CreateCertificateRequest{ Template: &tmplWithLifetime, Lifetime: 24 * time.Hour, }}, &apiv1.CreateCertificateResponse{ Certificate: testSignedTemplate, CertificateChain: []*x509.Certificate{testIssuer}, }, false}, {"fail template", fields{testIssuer, testSigner}, args{&apiv1.CreateCertificateRequest{Lifetime: 24 * time.Hour}}, nil, true}, {"fail lifetime", fields{testIssuer, testSigner}, args{&apiv1.CreateCertificateRequest{Template: testTemplate}}, nil, true}, {"fail CreateCertificate", fields{testIssuer, testSigner}, args{&apiv1.CreateCertificateRequest{ Template: &tmplNoSerial, Lifetime: 24 * time.Hour, }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &SoftCAS{ Issuer: tt.fields.Issuer, Signer: tt.fields.Signer, } got, err := c.CreateCertificate(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SoftCAS.CreateCertificate() = %v, want %v", got, tt.want) } }) } } func TestSoftCAS_RenewCertificate(t *testing.T) { mockNow(t) // Set rand.Reader to EOF buf := new(bytes.Buffer) setTeeReader(t, buf) rand.Reader = buf tmplNoSerial := *testTemplate tmplNoSerial.SerialNumber = nil type fields struct { Issuer *x509.Certificate Signer crypto.Signer } type args struct { req *apiv1.RenewCertificateRequest } tests := []struct { name string fields fields args args want *apiv1.RenewCertificateResponse wantErr bool }{ {"ok", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{ Template: testTemplate, Lifetime: 24 * time.Hour, }}, &apiv1.RenewCertificateResponse{ Certificate: testSignedTemplate, CertificateChain: []*x509.Certificate{testIssuer}, }, false}, {"fail template", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{Lifetime: 24 * time.Hour}}, nil, true}, {"fail lifetime", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{Template: testTemplate}}, nil, true}, {"fail CreateCertificate", fields{testIssuer, testSigner}, args{&apiv1.RenewCertificateRequest{ Template: &tmplNoSerial, Lifetime: 24 * time.Hour, }}, nil, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &SoftCAS{ Issuer: tt.fields.Issuer, Signer: tt.fields.Signer, } got, err := c.RenewCertificate(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SoftCAS.RenewCertificate() = %v, want %v", got, tt.want) } }) } } func TestSoftCAS_RevokeCertificate(t *testing.T) { type fields struct { Issuer *x509.Certificate Signer crypto.Signer } type args struct { req *apiv1.RevokeCertificateRequest } tests := []struct { name string fields fields args args want *apiv1.RevokeCertificateResponse wantErr bool }{ {"ok", fields{testIssuer, testSigner}, args{&apiv1.RevokeCertificateRequest{ Certificate: &x509.Certificate{Subject: pkix.Name{CommonName: "fake"}}, Reason: "test reason", ReasonCode: 1, }}, &apiv1.RevokeCertificateResponse{ Certificate: &x509.Certificate{Subject: pkix.Name{CommonName: "fake"}}, CertificateChain: []*x509.Certificate{testIssuer}, }, false}, {"ok no cert", fields{testIssuer, testSigner}, args{&apiv1.RevokeCertificateRequest{ Reason: "test reason", ReasonCode: 1, }}, &apiv1.RevokeCertificateResponse{ Certificate: nil, CertificateChain: []*x509.Certificate{testIssuer}, }, false}, {"ok empty", fields{testIssuer, testSigner}, args{&apiv1.RevokeCertificateRequest{}}, &apiv1.RevokeCertificateResponse{ Certificate: nil, CertificateChain: []*x509.Certificate{testIssuer}, }, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &SoftCAS{ Issuer: tt.fields.Issuer, Signer: tt.fields.Signer, } got, err := c.RevokeCertificate(tt.args.req) if (err != nil) != tt.wantErr { t.Errorf("SoftCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { t.Errorf("SoftCAS.RevokeCertificate() = %v, want %v", got, tt.want) } }) } } func Test_now(t *testing.T) { t0 := time.Now() t1 := now() if t1.Sub(t0) > time.Second { t.Errorf("now() = %s, want ~%s", t1, t0) } }