package stepcas

import (
	"bytes"
	"context"
	"crypto"
	"crypto/ed25519"
	"crypto/rand"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"testing"
	"time"

	"github.com/smallstep/certificates/api"
	"github.com/smallstep/certificates/authority/provisioner"
	"github.com/smallstep/certificates/ca"
	"github.com/smallstep/certificates/cas/apiv1"
	"go.step.sm/crypto/jose"
	"go.step.sm/crypto/pemutil"
	"go.step.sm/crypto/randutil"
	"go.step.sm/crypto/x509util"
)

var (
	testRootCrt                   *x509.Certificate
	testRootKey                   crypto.Signer
	testRootPath, testRootKeyPath string
	testRootFingerprint           string

	testIssCrt                  *x509.Certificate
	testIssKey                  crypto.Signer
	testIssPath, testIssKeyPath string

	testX5CCrt                         *x509.Certificate
	testX5CKey                         crypto.Signer
	testX5CPath, testX5CKeyPath        string
	testPassword, testEncryptedKeyPath string
	testKeyID, testEncryptedJWKKey     string

	testCR     *x509.CertificateRequest
	testCrt    *x509.Certificate
	testKey    crypto.Signer
	testFailCR *x509.CertificateRequest
)

func mustSignCertificate(subject string, sans []string, template string, parent *x509.Certificate, signer crypto.Signer) (*x509.Certificate, crypto.Signer) {
	pub, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		panic(err)
	}
	cr, err := x509util.CreateCertificateRequest(subject, sans, priv)
	if err != nil {
		panic(err)
	}
	cert, err := x509util.NewCertificate(cr, x509util.WithTemplate(template, x509util.CreateTemplateData(subject, sans)))
	if err != nil {
		panic(err)
	}

	crt := cert.GetCertificate()
	crt.NotBefore = time.Now()
	crt.NotAfter = crt.NotBefore.Add(time.Hour)
	if parent == nil {
		parent = crt
	}
	if signer == nil {
		signer = priv
	}
	if crt, err = x509util.CreateCertificate(crt, parent, pub, signer); err != nil {
		panic(err)
	}
	return crt, priv
}

func mustSerializeCrt(filename string, certs ...*x509.Certificate) {
	buf := new(bytes.Buffer)
	for _, c := range certs {
		if err := pem.Encode(buf, &pem.Block{
			Type:  "CERTIFICATE",
			Bytes: c.Raw,
		}); err != nil {
			panic(err)
		}
	}
	if err := os.WriteFile(filename, buf.Bytes(), 0600); err != nil {
		panic(err)
	}
}

func mustSerializeKey(filename string, key crypto.Signer) {
	b, err := x509.MarshalPKCS8PrivateKey(key)
	if err != nil {
		panic(err)
	}
	b = pem.EncodeToMemory(&pem.Block{
		Type:  "PRIVATE KEY",
		Bytes: b,
	})
	if err := os.WriteFile(filename, b, 0600); err != nil {
		panic(err)
	}
}

func mustEncryptKey(filename string, key crypto.Signer) {
	_, err := pemutil.Serialize(key,
		pemutil.ToFile(filename, 0600),
		pemutil.WithPKCS8(true),
		pemutil.WithPassword([]byte(testPassword)))
	if err != nil {
		panic(err)
	}
}

func testCAHelper(t *testing.T) (*url.URL, *ca.Client) {
	t.Helper()

	writeJSON := func(w http.ResponseWriter, v interface{}) {
		_ = json.NewEncoder(w).Encode(v)
	}
	parseJSON := func(r *http.Request, v interface{}) {
		_ = json.NewDecoder(r.Body).Decode(v)
	}
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.RequestURI == "/root/"+testRootFingerprint:
			w.WriteHeader(http.StatusOK)
			writeJSON(w, api.RootResponse{
				RootPEM: api.NewCertificate(testRootCrt),
			})
		case r.RequestURI == "/sign":
			var msg api.SignRequest
			parseJSON(r, &msg)
			if msg.CsrPEM.DNSNames[0] == "fail.doe.org" {
				w.WriteHeader(http.StatusBadRequest)
				fmt.Fprintf(w, `{"error":"fail","message":"fail"}`)
				return
			}
			w.WriteHeader(http.StatusOK)
			writeJSON(w, api.SignResponse{
				CertChainPEM: []api.Certificate{api.NewCertificate(testCrt), api.NewCertificate(testIssCrt)},
			})
		case r.RequestURI == "/revoke":
			var msg api.RevokeRequest
			parseJSON(r, &msg)
			if msg.Serial == "fail" {
				w.WriteHeader(http.StatusBadRequest)
				fmt.Fprintf(w, `{"error":"fail","message":"fail"}`)
				return
			}
			w.WriteHeader(http.StatusOK)
			writeJSON(w, api.RevokeResponse{
				Status: "ok",
			})
		case r.RequestURI == "/provisioners":
			w.WriteHeader(http.StatusOK)
			writeJSON(w, api.ProvisionersResponse{
				NextCursor: "cursor",
				Provisioners: []provisioner.Interface{
					&provisioner.JWK{
						Type:         "JWK",
						Name:         "ra@doe.org",
						Key:          &jose.JSONWebKey{KeyID: testKeyID, Key: testX5CKey.Public()},
						EncryptedKey: testEncryptedJWKKey,
					},
					&provisioner.JWK{
						Type: "JWK",
						Name: "empty@doe.org",
						Key:  &jose.JSONWebKey{KeyID: testKeyID, Key: testX5CKey.Public()},
					},
				},
			})
		case r.RequestURI == "/provisioners?cursor=cursor":
			w.WriteHeader(http.StatusOK)
			writeJSON(w, api.ProvisionersResponse{})
		default:
			w.WriteHeader(http.StatusNotFound)
			fmt.Fprintf(w, `{"error":"not found"}`)
		}
	}))
	t.Cleanup(func() {
		srv.Close()
	})
	u, err := url.Parse(srv.URL)
	if err != nil {
		srv.Close()
		t.Fatal(err)
	}

	client, err := ca.NewClient(srv.URL, ca.WithTransport(http.DefaultTransport))
	if err != nil {
		srv.Close()
		t.Fatal(err)
	}

	return u, client
}

func testX5CIssuer(t *testing.T, caURL *url.URL, password string) *x5cIssuer {
	t.Helper()
	key, givenPassword := testX5CKeyPath, password
	if password != "" {
		key = testEncryptedKeyPath
		password = testPassword
	}
	x5c, err := newX5CIssuer(caURL, &apiv1.CertificateIssuer{
		Type:        "x5c",
		Provisioner: "X5C",
		Certificate: testX5CPath,
		Key:         key,
		Password:    password,
	})
	if err != nil {
		t.Fatal(err)
	}
	x5c.password = givenPassword
	return x5c
}

func testJWKIssuer(t *testing.T, caURL *url.URL, password string) *jwkIssuer {
	t.Helper()
	client, err := ca.NewClient(caURL.String(), ca.WithTransport(http.DefaultTransport))
	if err != nil {
		t.Fatal(err)
	}
	key := testX5CKeyPath
	if password != "" {
		key = testEncryptedKeyPath
		password = testPassword
	}
	jwk, err := newJWKIssuer(caURL, client, &apiv1.CertificateIssuer{
		Type:        "jwk",
		Provisioner: "ra@doe.org",
		Key:         key,
		Password:    password,
	})
	if err != nil {
		t.Fatal(err)
	}

	return jwk
}

func TestMain(m *testing.M) {
	testRootCrt, testRootKey = mustSignCertificate("Test Root Certificate", nil, x509util.DefaultRootTemplate, nil, nil)
	testIssCrt, testIssKey = mustSignCertificate("Test Intermediate Certificate", nil, x509util.DefaultIntermediateTemplate, testRootCrt, testRootKey)
	testX5CCrt, testX5CKey = mustSignCertificate("Test X5C Certificate", nil, x509util.DefaultLeafTemplate, testIssCrt, testIssKey)
	testRootFingerprint = x509util.Fingerprint(testRootCrt)

	// Final certificate.
	var err error
	sans := []string{"doe.org", "jane@doe.org", "127.0.0.1", "::1", "localhost", "uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6;name=value"}
	testCrt, testKey = mustSignCertificate("Test Certificate", sans, x509util.DefaultLeafTemplate, testIssCrt, testIssKey)
	testCR, err = x509util.CreateCertificateRequest("Test Certificate", sans, testKey)
	if err != nil {
		panic(err)
	}

	// CR used in errors.
	testFailCR, err = x509util.CreateCertificateRequest("", []string{"fail.doe.org"}, testKey)
	if err != nil {
		panic(err)
	}

	// Password used to encrypt the key.
	testPassword, err = randutil.Hex(32)
	if err != nil {
		panic(err)
	}

	// Encrypted JWK key used when the key is downloaded from the CA.
	jwe, err := jose.EncryptJWK(&jose.JSONWebKey{Key: testX5CKey}, []byte(testPassword))
	if err != nil {
		panic(err)
	}
	testEncryptedJWKKey, err = jwe.CompactSerialize()
	if err != nil {
		panic(err)
	}
	testKeyID, err = jose.Thumbprint(&jose.JSONWebKey{Key: testX5CKey})
	if err != nil {
		panic(err)
	}

	// Create test files.
	path, err := os.MkdirTemp(os.TempDir(), "stepcas")
	if err != nil {
		panic(err)
	}

	testRootPath = filepath.Join(path, "root_ca.crt")
	testRootKeyPath = filepath.Join(path, "root_ca.key")
	mustSerializeCrt(testRootPath, testRootCrt)
	mustSerializeKey(testRootKeyPath, testRootKey)

	testIssPath = filepath.Join(path, "intermediate_ca.crt")
	testIssKeyPath = filepath.Join(path, "intermediate_ca.key")
	mustSerializeCrt(testIssPath, testIssCrt)
	mustSerializeKey(testIssKeyPath, testIssKey)

	testX5CPath = filepath.Join(path, "x5c.crt")
	testX5CKeyPath = filepath.Join(path, "x5c.key")
	mustSerializeCrt(testX5CPath, testX5CCrt, testIssCrt)
	mustSerializeKey(testX5CKeyPath, testX5CKey)

	testEncryptedKeyPath = filepath.Join(path, "x5c.enc.key")
	mustEncryptKey(testEncryptedKeyPath, testX5CKey)

	code := m.Run()
	if err := os.RemoveAll(path); err != nil {
		panic(err)
	}
	os.Exit(code)
}

func Test_init(t *testing.T) {
	caURL, _ := testCAHelper(t)

	fn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.StepCAS)
	if !ok {
		t.Errorf("apiv1.Register() ok = %v, want true", ok)
		return
	}
	fn(context.Background(), apiv1.Options{
		CertificateAuthority:            caURL.String(),
		CertificateAuthorityFingerprint: testRootFingerprint,
		CertificateIssuer: &apiv1.CertificateIssuer{
			Type:        "x5c",
			Provisioner: "X5C",
			Certificate: testX5CPath,
			Key:         testX5CKeyPath,
		},
	})
}

func TestNew(t *testing.T) {
	caURL, client := testCAHelper(t)
	signer, err := newJWKSignerFromEncryptedKey(testKeyID, testEncryptedJWKKey, testPassword)
	if err != nil {
		t.Fatal(err)
	}

	type args struct {
		ctx  context.Context
		opts apiv1.Options
	}
	tests := []struct {
		name    string
		args    args
		want    *StepCAS
		wantErr bool
	}{
		{"ok", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, &StepCAS{
			iss: &x5cIssuer{
				caURL:    caURL,
				certFile: testX5CPath,
				keyFile:  testX5CKeyPath,
				issuer:   "X5C",
			},
			client:      client,
			fingerprint: testRootFingerprint,
		}, false},
		{"ok jwk", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "ra@doe.org",
				Key:         testX5CKeyPath,
			},
		}}, &StepCAS{
			iss: &jwkIssuer{
				caURL:  caURL,
				issuer: "ra@doe.org",
				signer: signer,
			},
			client:      client,
			fingerprint: testRootFingerprint,
		}, false},
		{"ok jwk provisioners", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "ra@doe.org",
				Password:    testPassword,
			},
		}}, &StepCAS{
			iss: &jwkIssuer{
				caURL:  caURL,
				issuer: "ra@doe.org",
				signer: signer,
			},
			client:      client,
			fingerprint: testRootFingerprint,
		}, false},
		{"ok ca getter", args{context.TODO(), apiv1.Options{
			IsCAGetter:                      true,
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "ra@doe.org",
			},
		}}, &StepCAS{
			iss:         nil,
			client:      client,
			fingerprint: testRootFingerprint,
		}, false},
		{"fail authority", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            "",
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail fingerprint", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: "",
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail type", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail provisioner", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail provisioner jwk", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "",
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail provisioner not found", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "notfound@doe.org",
				Password:    testPassword,
			},
		}}, nil, true},
		{"fail invalid password", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "ra@doe.org",
				Password:    "bad-password",
			},
		}}, nil, true},
		{"fail no key", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "empty@doe.org",
				Password:    testPassword,
			},
		}}, nil, true},
		{"fail certificate", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: "",
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail key", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         "",
			},
		}}, nil, true},
		{"fail key jwk", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "ra@smallstep.com",
				Key:         "",
			},
		}}, nil, true},
		{"bad authority", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            "https://foobar",
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail parse url", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            "::failparse",
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail new client", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: "foobar",
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail new x5c issuer", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "x5c",
				Provisioner: "X5C",
				Certificate: testX5CPath + ".missing",
				Key:         testX5CKeyPath,
			},
		}}, nil, true},
		{"fail new jwk issuer", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "jwk",
				Provisioner: "ra@doe.org",
				Key:         testX5CKeyPath + ".missing",
			},
		}}, nil, true},
		{"bad issuer", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer:               nil}}, nil, true},
		{"bad issuer type", args{context.TODO(), apiv1.Options{
			CertificateAuthority:            caURL.String(),
			CertificateAuthorityFingerprint: testRootFingerprint,
			CertificateIssuer: &apiv1.CertificateIssuer{
				Type:        "fail",
				Provisioner: "X5C",
				Certificate: testX5CPath,
				Key:         testX5CKeyPath,
			},
		}}, 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
			}
			// We cannot compare neither the client nor the signer.
			if got != nil && tt.want != nil {
				got.client = tt.want.client
				if jwk, ok := got.iss.(*jwkIssuer); ok {
					jwk.signer = signer
				}
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("New() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestStepCAS_CreateCertificate(t *testing.T) {
	caURL, client := testCAHelper(t)
	x5c := testX5CIssuer(t, caURL, "")
	jwk := testJWKIssuer(t, caURL, "")
	x5cEnc := testX5CIssuer(t, caURL, testPassword)
	jwkEnc := testJWKIssuer(t, caURL, testPassword)
	x5cBad := testX5CIssuer(t, caURL, "bad-password")

	type fields struct {
		iss         stepIssuer
		client      *ca.Client
		fingerprint string
	}
	type args struct {
		req *apiv1.CreateCertificateRequest
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *apiv1.CreateCertificateResponse
		wantErr bool
	}{
		{"ok", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      testCR,
			Lifetime: time.Hour,
		}}, &apiv1.CreateCertificateResponse{
			Certificate:      testCrt,
			CertificateChain: []*x509.Certificate{testIssCrt},
		}, false},
		{"ok with password", fields{x5cEnc, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      testCR,
			Lifetime: time.Hour,
		}}, &apiv1.CreateCertificateResponse{
			Certificate:      testCrt,
			CertificateChain: []*x509.Certificate{testIssCrt},
		}, false},
		{"ok jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      testCR,
			Lifetime: time.Hour,
		}}, &apiv1.CreateCertificateResponse{
			Certificate:      testCrt,
			CertificateChain: []*x509.Certificate{testIssCrt},
		}, false},
		{"ok jwk with password", fields{jwkEnc, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      testCR,
			Lifetime: time.Hour,
		}}, &apiv1.CreateCertificateResponse{
			Certificate:      testCrt,
			CertificateChain: []*x509.Certificate{testIssCrt},
		}, false},
		{"ok with provisioner", fields{jwk, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:         testCR,
			Lifetime:    time.Hour,
			Provisioner: &apiv1.ProvisionerInfo{ID: "provisioner-id", Type: "ACME"},
		}}, &apiv1.CreateCertificateResponse{
			Certificate:      testCrt,
			CertificateChain: []*x509.Certificate{testIssCrt},
		}, false},
		{"ok with server cert", fields{jwk, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:            testCR,
			Lifetime:       time.Hour,
			IsCAServerCert: true,
		}}, &apiv1.CreateCertificateResponse{
			Certificate:      testCrt,
			CertificateChain: []*x509.Certificate{testIssCrt},
		}, false},
		{"fail CSR", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      nil,
			Lifetime: time.Hour,
		}}, nil, true},
		{"fail lifetime", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      testCR,
			Lifetime: 0,
		}}, nil, true},
		{"fail sign token", fields{mockErrIssuer{}, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      testCR,
			Lifetime: time.Hour,
		}}, nil, true},
		{"fail client sign", fields{x5c, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      testFailCR,
			Lifetime: time.Hour,
		}}, nil, true},
		{"fail password", fields{x5cBad, client, testRootFingerprint}, args{&apiv1.CreateCertificateRequest{
			CSR:      testCR,
			Lifetime: time.Hour,
		}}, nil, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := &StepCAS{
				iss:         tt.fields.iss,
				client:      tt.fields.client,
				authorityID: "authority-id",
				fingerprint: tt.fields.fingerprint,
			}
			got, err := s.CreateCertificate(tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("StepCAS.CreateCertificate() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("StepCAS.CreateCertificate() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestStepCAS_RenewCertificate(t *testing.T) {
	caURL, client := testCAHelper(t)
	x5c := testX5CIssuer(t, caURL, "")
	jwk := testJWKIssuer(t, caURL, "")

	type fields struct {
		iss         stepIssuer
		client      *ca.Client
		fingerprint string
	}
	type args struct {
		req *apiv1.RenewCertificateRequest
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *apiv1.RenewCertificateResponse
		wantErr bool
	}{
		{"not implemented", fields{x5c, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{
			CSR:      testCR,
			Lifetime: time.Hour,
		}}, nil, true},
		{"not implemented jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RenewCertificateRequest{
			CSR:      testCR,
			Lifetime: time.Hour,
		}}, nil, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := &StepCAS{
				iss:         tt.fields.iss,
				client:      tt.fields.client,
				fingerprint: tt.fields.fingerprint,
			}
			got, err := s.RenewCertificate(tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("StepCAS.RenewCertificate() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("StepCAS.RenewCertificate() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestStepCAS_RevokeCertificate(t *testing.T) {
	caURL, client := testCAHelper(t)
	x5c := testX5CIssuer(t, caURL, "")
	jwk := testJWKIssuer(t, caURL, "")
	x5cEnc := testX5CIssuer(t, caURL, testPassword)
	jwkEnc := testJWKIssuer(t, caURL, testPassword)
	x5cBad := testX5CIssuer(t, caURL, "bad-password")

	type fields struct {
		iss         stepIssuer
		client      *ca.Client
		fingerprint string
	}
	type args struct {
		req *apiv1.RevokeCertificateRequest
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *apiv1.RevokeCertificateResponse
		wantErr bool
	}{
		{"ok serial number", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "ok",
			Certificate:  nil,
		}}, &apiv1.RevokeCertificateResponse{}, false},
		{"ok certificate", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "",
			Certificate:  testCrt,
		}}, &apiv1.RevokeCertificateResponse{
			Certificate: testCrt,
		}, false},
		{"ok both", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "ok",
			Certificate:  testCrt,
		}}, &apiv1.RevokeCertificateResponse{
			Certificate: testCrt,
		}, false},
		{"ok with password", fields{x5cEnc, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "ok",
			Certificate:  nil,
		}}, &apiv1.RevokeCertificateResponse{}, false},
		{"ok serial number jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "ok",
			Certificate:  nil,
		}}, &apiv1.RevokeCertificateResponse{}, false},
		{"ok certificate jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "",
			Certificate:  testCrt,
		}}, &apiv1.RevokeCertificateResponse{
			Certificate: testCrt,
		}, false},
		{"ok both jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "ok",
			Certificate:  testCrt,
		}}, &apiv1.RevokeCertificateResponse{
			Certificate: testCrt,
		}, false},
		{"ok jwk with password", fields{jwkEnc, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "ok",
			Certificate:  nil,
		}}, &apiv1.RevokeCertificateResponse{}, false},
		{"fail request", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "",
			Certificate:  nil,
		}}, nil, true},
		{"fail revoke token", fields{mockErrIssuer{}, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "ok",
		}}, nil, true},
		{"fail client revoke", fields{x5c, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "fail",
		}}, nil, true},
		{"fail password", fields{x5cBad, client, testRootFingerprint}, args{&apiv1.RevokeCertificateRequest{
			SerialNumber: "ok",
			Certificate:  nil,
		}}, nil, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := &StepCAS{
				iss:         tt.fields.iss,
				client:      tt.fields.client,
				fingerprint: tt.fields.fingerprint,
			}
			got, err := s.RevokeCertificate(tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("StepCAS.RevokeCertificate() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("StepCAS.RevokeCertificate() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestStepCAS_GetCertificateAuthority(t *testing.T) {
	caURL, client := testCAHelper(t)
	x5c := testX5CIssuer(t, caURL, "")
	jwk := testJWKIssuer(t, caURL, "")

	type fields struct {
		iss         stepIssuer
		client      *ca.Client
		fingerprint string
	}
	type args struct {
		req *apiv1.GetCertificateAuthorityRequest
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    *apiv1.GetCertificateAuthorityResponse
		wantErr bool
	}{
		{"ok", fields{x5c, client, testRootFingerprint}, args{&apiv1.GetCertificateAuthorityRequest{
			Name: caURL.String(),
		}}, &apiv1.GetCertificateAuthorityResponse{
			RootCertificate: testRootCrt,
		}, false},
		{"ok jwk", fields{jwk, client, testRootFingerprint}, args{&apiv1.GetCertificateAuthorityRequest{
			Name: caURL.String(),
		}}, &apiv1.GetCertificateAuthorityResponse{
			RootCertificate: testRootCrt,
		}, false},
		{"fail fingerprint", fields{x5c, client, "fail"}, args{&apiv1.GetCertificateAuthorityRequest{
			Name: caURL.String(),
		}}, nil, true},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			s := &StepCAS{
				iss:         tt.fields.iss,
				client:      tt.fields.client,
				fingerprint: tt.fields.fingerprint,
			}
			got, err := s.GetCertificateAuthority(tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("StepCAS.GetCertificateAuthority() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("StepCAS.GetCertificateAuthority() = %v, want %v", got, tt.want)
			}
		})
	}
}
