package certutil

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"math/big"
	mathrand "math/rand"
	"reflect"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/fatih/structs"
)

// Tests converting back and forth between a CertBundle and a ParsedCertBundle.
//
// Also tests the GetSubjKeyID, GetHexFormatted, ParseHexFormatted and
// ParsedCertBundle.getSigner functions.
func TestCertBundleConversion(t *testing.T) {
	cbuts := []*CertBundle{
		refreshRSACertBundle(),
		refreshRSACertBundleWithChain(),
		refreshRSA8CertBundle(),
		refreshRSA8CertBundleWithChain(),
		refreshECCertBundle(),
		refreshECCertBundleWithChain(),
		refreshEC8CertBundle(),
		refreshEC8CertBundleWithChain(),
		refreshEd255198CertBundle(),
		refreshEd255198CertBundleWithChain(),
	}

	for i, cbut := range cbuts {
		pcbut, err := cbut.ToParsedCertBundle()
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Errorf("Error converting to parsed cert bundle: %s", err)
			continue
		}

		err = compareCertBundleToParsedCertBundle(cbut, pcbut)
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Errorf(err.Error())
		}

		cbut, err := pcbut.ToCertBundle()
		if err != nil {
			t.Fatalf("Error converting to cert bundle: %s", err)
		}

		err = compareCertBundleToParsedCertBundle(cbut, pcbut)
		if err != nil {
			t.Fatalf(err.Error())
		}
	}
}

func BenchmarkCertBundleParsing(b *testing.B) {
	for i := 0; i < b.N; i++ {
		cbuts := []*CertBundle{
			refreshRSACertBundle(),
			refreshRSACertBundleWithChain(),
			refreshRSA8CertBundle(),
			refreshRSA8CertBundleWithChain(),
			refreshECCertBundle(),
			refreshECCertBundleWithChain(),
			refreshEC8CertBundle(),
			refreshEC8CertBundleWithChain(),
			refreshEd255198CertBundle(),
			refreshEd255198CertBundleWithChain(),
		}

		for i, cbut := range cbuts {
			pcbut, err := cbut.ToParsedCertBundle()
			if err != nil {
				b.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
				b.Errorf("Error converting to parsed cert bundle: %s", err)
				continue
			}

			cbut, err = pcbut.ToCertBundle()
			if err != nil {
				b.Fatalf("Error converting to cert bundle: %s", err)
			}
		}
	}
}

func TestCertBundleParsing(t *testing.T) {
	cbuts := []*CertBundle{
		refreshRSACertBundle(),
		refreshRSACertBundleWithChain(),
		refreshRSA8CertBundle(),
		refreshRSA8CertBundleWithChain(),
		refreshECCertBundle(),
		refreshECCertBundleWithChain(),
		refreshEC8CertBundle(),
		refreshEC8CertBundleWithChain(),
		refreshEd255198CertBundle(),
		refreshEd255198CertBundleWithChain(),
	}

	for i, cbut := range cbuts {
		jsonString, err := json.Marshal(cbut)
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Fatalf("Error marshaling testing certbundle to JSON: %s", err)
		}
		pcbut, err := ParsePKIJSON(jsonString)
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Fatalf("Error during JSON bundle handling: %s", err)
		}
		err = compareCertBundleToParsedCertBundle(cbut, pcbut)
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Fatalf(err.Error())
		}

		dataMap := structs.New(cbut).Map()
		pcbut, err = ParsePKIMap(dataMap)
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Fatalf("Error during JSON bundle handling: %s", err)
		}
		err = compareCertBundleToParsedCertBundle(cbut, pcbut)
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Fatalf(err.Error())
		}

		pcbut, err = ParsePEMBundle(cbut.ToPEMBundle())
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Fatalf("Error during JSON bundle handling: %s", err)
		}
		err = compareCertBundleToParsedCertBundle(cbut, pcbut)
		if err != nil {
			t.Logf("Error occurred with bundle %d in test array (index %d).\n", i+1, i)
			t.Fatalf(err.Error())
		}
	}
}

func compareCertBundleToParsedCertBundle(cbut *CertBundle, pcbut *ParsedCertBundle) error {
	if cbut == nil {
		return fmt.Errorf("got nil bundle")
	}
	if pcbut == nil {
		return fmt.Errorf("got nil parsed bundle")
	}

	switch {
	case pcbut.Certificate == nil:
		return fmt.Errorf("parsed bundle has nil certificate")
	case pcbut.PrivateKey == nil:
		return fmt.Errorf("parsed bundle has nil private key")
	}

	switch cbut.PrivateKey {
	case privRSAKeyPem:
		if pcbut.PrivateKeyType != RSAPrivateKey {
			return fmt.Errorf("parsed bundle has wrong private key type: %v, should be 'rsa' (%v)", pcbut.PrivateKeyType, RSAPrivateKey)
		}
	case privRSA8KeyPem:
		if pcbut.PrivateKeyType != RSAPrivateKey {
			return fmt.Errorf("parsed bundle has wrong pkcs8 private key type: %v, should be 'rsa' (%v)", pcbut.PrivateKeyType, RSAPrivateKey)
		}
	case privECKeyPem:
		if pcbut.PrivateKeyType != ECPrivateKey {
			return fmt.Errorf("parsed bundle has wrong private key type: %v, should be 'ec' (%v)", pcbut.PrivateKeyType, ECPrivateKey)
		}
	case privEC8KeyPem:
		if pcbut.PrivateKeyType != ECPrivateKey {
			return fmt.Errorf("parsed bundle has wrong pkcs8 private key type: %v, should be 'ec' (%v)", pcbut.PrivateKeyType, ECPrivateKey)
		}
	case privEd255198KeyPem:
		if pcbut.PrivateKeyType != Ed25519PrivateKey {
			return fmt.Errorf("parsed bundle has wrong pkcs8 private key type: %v, should be 'ed25519' (%v)", pcbut.PrivateKeyType, ECPrivateKey)
		}
	default:
		return fmt.Errorf("parsed bundle has unknown private key type")
	}

	subjKeyID, err := GetSubjKeyID(pcbut.PrivateKey)
	if err != nil {
		return fmt.Errorf("error when getting subject key id: %s", err)
	}
	if bytes.Compare(subjKeyID, pcbut.Certificate.SubjectKeyId) != 0 {
		return fmt.Errorf("parsed bundle private key does not match subject key id\nGot\n%#v\nExpected\n%#v\nCert\n%#v", subjKeyID, pcbut.Certificate.SubjectKeyId, *pcbut.Certificate)
	}

	switch {
	case len(pcbut.CAChain) > 0 && len(cbut.CAChain) == 0:
		return fmt.Errorf("parsed bundle ca chain has certs when cert bundle does not")
	case len(pcbut.CAChain) == 0 && len(cbut.CAChain) > 0:
		return fmt.Errorf("cert bundle ca chain has certs when parsed cert bundle does not")
	}

	cb, err := pcbut.ToCertBundle()
	if err != nil {
		return fmt.Errorf("thrown error during parsed bundle conversion: %s\n\nInput was: %#v", err, *pcbut)
	}

	switch {
	case len(cb.Certificate) == 0:
		return fmt.Errorf("bundle has nil certificate")
	case len(cb.PrivateKey) == 0:
		return fmt.Errorf("bundle has nil private key")
	case len(cb.CAChain[0]) == 0:
		return fmt.Errorf("bundle has nil issuing CA")
	}

	switch pcbut.PrivateKeyType {
	case RSAPrivateKey:
		if cb.PrivateKey != privRSAKeyPem && cb.PrivateKey != privRSA8KeyPem {
			return fmt.Errorf("bundle private key does not match")
		}
	case ECPrivateKey:
		if cb.PrivateKey != privECKeyPem && cb.PrivateKey != privEC8KeyPem {
			return fmt.Errorf("bundle private key does not match")
		}
	case Ed25519PrivateKey:
		if cb.PrivateKey != privEd255198KeyPem {
			return fmt.Errorf("bundle private key does not match")
		}
	default:
		return fmt.Errorf("certBundle has unknown private key type")
	}

	if cb.SerialNumber != GetHexFormatted(pcbut.Certificate.SerialNumber.Bytes(), ":") {
		return fmt.Errorf("bundle serial number does not match")
	}

	if !bytes.Equal(pcbut.Certificate.SerialNumber.Bytes(), ParseHexFormatted(cb.SerialNumber, ":")) {
		return fmt.Errorf("failed re-parsing hex formatted number %s", cb.SerialNumber)
	}

	switch {
	case len(pcbut.CAChain) > 0 && len(cb.CAChain) == 0:
		return fmt.Errorf("parsed bundle ca chain has certs when cert bundle does not")
	case len(pcbut.CAChain) == 0 && len(cb.CAChain) > 0:
		return fmt.Errorf("cert bundle ca chain has certs when parsed cert bundle does not")
	case !reflect.DeepEqual(cbut.CAChain, cb.CAChain):
		return fmt.Errorf("cert bundle ca chain does not match: %#v\n\n%#v", cbut.CAChain, cb.CAChain)
	}

	return nil
}

func TestCSRBundleConversion(t *testing.T) {
	csrbuts := []*CSRBundle{
		refreshRSACSRBundle(),
		refreshECCSRBundle(),
		refreshEd25519CSRBundle(),
	}

	for _, csrbut := range csrbuts {
		pcsrbut, err := csrbut.ToParsedCSRBundle()
		if err != nil {
			t.Fatalf("Error converting to parsed CSR bundle: %v", err)
		}

		err = compareCSRBundleToParsedCSRBundle(csrbut, pcsrbut)
		if err != nil {
			t.Fatalf(err.Error())
		}

		csrbut, err = pcsrbut.ToCSRBundle()
		if err != nil {
			t.Fatalf("Error converting to CSR bundle: %v", err)
		}

		err = compareCSRBundleToParsedCSRBundle(csrbut, pcsrbut)
		if err != nil {
			t.Fatalf(err.Error())
		}
	}
}

func compareCSRBundleToParsedCSRBundle(csrbut *CSRBundle, pcsrbut *ParsedCSRBundle) error {
	if csrbut == nil {
		return fmt.Errorf("got nil bundle")
	}
	if pcsrbut == nil {
		return fmt.Errorf("got nil parsed bundle")
	}

	switch {
	case pcsrbut.CSR == nil:
		return fmt.Errorf("parsed bundle has nil csr")
	case pcsrbut.PrivateKey == nil:
		return fmt.Errorf("parsed bundle has nil private key")
	}

	switch csrbut.PrivateKey {
	case privRSAKeyPem:
		if pcsrbut.PrivateKeyType != RSAPrivateKey {
			return fmt.Errorf("parsed bundle has wrong private key type")
		}
	case privECKeyPem:
		if pcsrbut.PrivateKeyType != ECPrivateKey {
			return fmt.Errorf("parsed bundle has wrong private key type")
		}
	case privEd255198KeyPem:
		if pcsrbut.PrivateKeyType != Ed25519PrivateKey {
			return fmt.Errorf("parsed bundle has wrong private key type")
		}
	default:
		return fmt.Errorf("parsed bundle has unknown private key type")
	}

	csrb, err := pcsrbut.ToCSRBundle()
	if err != nil {
		return fmt.Errorf("Thrown error during parsed bundle conversion: %s\n\nInput was: %#v", err, *pcsrbut)
	}

	switch {
	case len(csrb.CSR) == 0:
		return fmt.Errorf("bundle has nil certificate")
	case len(csrb.PrivateKey) == 0:
		return fmt.Errorf("bundle has nil private key")
	}

	switch csrb.PrivateKeyType {
	case "rsa":
		if pcsrbut.PrivateKeyType != RSAPrivateKey {
			return fmt.Errorf("bundle has wrong private key type")
		}
		if csrb.PrivateKey != privRSAKeyPem {
			return fmt.Errorf("bundle rsa private key does not match\nGot\n%#v\nExpected\n%#v", csrb.PrivateKey, privRSAKeyPem)
		}
	case "ec":
		if pcsrbut.PrivateKeyType != ECPrivateKey {
			return fmt.Errorf("bundle has wrong private key type")
		}
		if csrb.PrivateKey != privECKeyPem {
			return fmt.Errorf("bundle ec private key does not match")
		}
	case "ed25519":
		if pcsrbut.PrivateKeyType != Ed25519PrivateKey {
			return fmt.Errorf("bundle has wrong private key type")
		}
		if csrb.PrivateKey != privEd255198KeyPem {
			return fmt.Errorf("bundle ed25519 private key does not match")
		}
	default:
		return fmt.Errorf("bundle has unknown private key type")
	}

	return nil
}

func TestTLSConfig(t *testing.T) {
	cbut := refreshRSACertBundle()

	pcbut, err := cbut.ToParsedCertBundle()
	if err != nil {
		t.Fatalf("Error getting parsed cert bundle: %s", err)
	}

	usages := []TLSUsage{
		TLSUnknown,
		TLSClient,
		TLSServer,
		TLSClient | TLSServer,
	}

	for _, usage := range usages {
		tlsConfig, err := pcbut.GetTLSConfig(usage)
		if err != nil {
			t.Fatalf("Error getting tls config: %s", err)
		}
		if tlsConfig == nil {
			t.Fatalf("Got nil tls.Config")
		}

		if len(tlsConfig.Certificates) != 1 {
			t.Fatalf("Unexpected length in config.Certificates")
		}

		// Length should be 2, since we passed in a CA
		if len(tlsConfig.Certificates[0].Certificate) != 2 {
			t.Fatalf("Did not find both certificates in config.Certificates.Certificate")
		}

		if tlsConfig.Certificates[0].Leaf != pcbut.Certificate {
			t.Fatalf("Leaf certificate does not match parsed bundle's certificate")
		}

		if tlsConfig.Certificates[0].PrivateKey != pcbut.PrivateKey {
			t.Fatalf("Config's private key does not match parsed bundle's private key")
		}

		switch usage {
		case TLSServer | TLSClient:
			if len(tlsConfig.ClientCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.ClientCAs.Subjects()[0], pcbut.CAChain[0].Certificate.RawSubject) != 0 {
				t.Fatalf("CA certificate not in client cert pool as expected")
			}
			if len(tlsConfig.RootCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.RootCAs.Subjects()[0], pcbut.CAChain[0].Certificate.RawSubject) != 0 {
				t.Fatalf("CA certificate not in root cert pool as expected")
			}
		case TLSServer:
			if len(tlsConfig.ClientCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.ClientCAs.Subjects()[0], pcbut.CAChain[0].Certificate.RawSubject) != 0 {
				t.Fatalf("CA certificate not in client cert pool as expected")
			}
			if tlsConfig.RootCAs != nil {
				t.Fatalf("Found root pools in config object when not expected")
			}
		case TLSClient:
			if len(tlsConfig.RootCAs.Subjects()) != 1 || bytes.Compare(tlsConfig.RootCAs.Subjects()[0], pcbut.CAChain[0].Certificate.RawSubject) != 0 {
				t.Fatalf("CA certificate not in root cert pool as expected")
			}
			if tlsConfig.ClientCAs != nil {
				t.Fatalf("Found root pools in config object when not expected")
			}
		default:
			if tlsConfig.RootCAs != nil || tlsConfig.ClientCAs != nil {
				t.Fatalf("Found root pools in config object when not expected")
			}
		}
	}
}

func TestNewCertPool(t *testing.T) {
	caExample := `-----BEGIN CERTIFICATE-----
MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTE5MTIxMDIzMDUxOVoXDTI5MTIwODIzMDUxOVowFTETMBEGA1UE
AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANFi
/RIdMHd865X6JygTb9riX01DA3QnR+RoXDXNnj8D3LziLG2n8ItXMJvWbU3sxxyy
nX9HxJ0SIeexj1cYzdQBtJDjO1/PeuKc4CZ7zCukCAtHz8mC7BDPOU7F7pggpcQ0
/t/pa2m22hmCu8aDF9WlUYHtJpYATnI/A5vz/VFLR9daxmkl59Qo3oHITj7vAzSx
/75r9cibpQyJ+FhiHOZHQWYY2JYw2g4v5hm5hg5SFM9yFcZ75ISI9ebyFFIl9iBY
zAk9jqv1mXvLr0Q39AVwMTamvGuap1oocjM9NIhQvaFL/DNqF1ouDQjCf5u2imLc
TraO1/2KO8fqwOZCOrMCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQBtVZCwCPqUUUpIClAlE9nc2fo2bTs9gsjXRmqdQ5oaSomSLE93
aJWYFuAhxPXtlApbLYZfW2m1sM3mTVQN60y0uE4e1jdSN1ErYQ9slJdYDAMaEmOh
iSexj+Nd1scUiMHV9lf3ps5J8sYeCpwZX3sPmw7lqZojTS12pANBDcigsaj5RRyN
9GyP3WkSQUsTpWlDb9Fd+KNdkCVw7nClIpBPA2KW4BQKw/rNSvOFD61mbzc89lo0
Q9IFGQFFF8jO18lbyWqnRBGXcS4/G7jQ3S7C121d14YLUeAYOM7pJykI1g4CLx9y
vitin0L6nprauWkKO38XgM4T75qKZpqtiOcT
-----END CERTIFICATE-----
`
	if _, err := NewCertPool(bytes.NewReader([]byte(caExample))); err != nil {
		t.Fatal(err)
	}
}

func TestGetPublicKeySize(t *testing.T) {
	rsa, err := rsa.GenerateKey(rand.Reader, 3072)
	if err != nil {
		t.Fatal(err)
	}
	if GetPublicKeySize(&rsa.PublicKey) != 3072 {
		t.Fatal("unexpected rsa key size")
	}
	ecdsa, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	if GetPublicKeySize(&ecdsa.PublicKey) != 384 {
		t.Fatal("unexpected ecdsa key size")
	}
	ed25519, _, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	if GetPublicKeySize(ed25519) != 256 {
		t.Fatal("unexpected ed25519 key size")
	}
	// Skipping DSA as too slow
}

func refreshRSA8CertBundle() *CertBundle {
	initTest.Do(setCerts)
	return &CertBundle{
		Certificate: certRSAPem,
		PrivateKey:  privRSA8KeyPem,
		CAChain:     []string{issuingCaChainPem[0]},
	}
}

func refreshRSA8CertBundleWithChain() *CertBundle {
	initTest.Do(setCerts)
	ret := refreshRSA8CertBundle()
	ret.CAChain = issuingCaChainPem
	return ret
}

func refreshRSACertBundle() *CertBundle {
	initTest.Do(setCerts)
	return &CertBundle{
		Certificate: certRSAPem,
		CAChain:     []string{issuingCaChainPem[0]},
		PrivateKey:  privRSAKeyPem,
	}
}

func refreshRSACertBundleWithChain() *CertBundle {
	initTest.Do(setCerts)
	ret := refreshRSACertBundle()
	ret.CAChain = issuingCaChainPem
	return ret
}

func refreshECCertBundle() *CertBundle {
	initTest.Do(setCerts)
	return &CertBundle{
		Certificate: certECPem,
		CAChain:     []string{issuingCaChainPem[0]},
		PrivateKey:  privECKeyPem,
	}
}

func refreshECCertBundleWithChain() *CertBundle {
	initTest.Do(setCerts)
	ret := refreshECCertBundle()
	ret.CAChain = issuingCaChainPem
	return ret
}

func refreshEd255198CertBundle() *CertBundle {
	initTest.Do(setCerts)
	return &CertBundle{
		Certificate: certEd25519Pem,
		PrivateKey:  privEd255198KeyPem,
		CAChain:     []string{issuingCaChainPem[0]},
	}
}

func refreshEd255198CertBundleWithChain() *CertBundle {
	initTest.Do(setCerts)
	ret := refreshEd255198CertBundle()
	ret.CAChain = issuingCaChainPem
	return ret
}

func refreshEd25519CSRBundle() *CSRBundle {
	initTest.Do(setCerts)
	return &CSRBundle{
		CSR:        csrEd25519Pem,
		PrivateKey: privEd255198KeyPem,
	}
}

func refreshRSACSRBundle() *CSRBundle {
	initTest.Do(setCerts)
	return &CSRBundle{
		CSR:        csrRSAPem,
		PrivateKey: privRSAKeyPem,
	}
}

func refreshECCSRBundle() *CSRBundle {
	initTest.Do(setCerts)
	return &CSRBundle{
		CSR:        csrECPem,
		PrivateKey: privECKeyPem,
	}
}

func refreshEC8CertBundle() *CertBundle {
	initTest.Do(setCerts)
	return &CertBundle{
		Certificate: certECPem,
		PrivateKey:  privEC8KeyPem,
		CAChain:     []string{issuingCaChainPem[0]},
	}
}

func refreshEC8CertBundleWithChain() *CertBundle {
	initTest.Do(setCerts)
	ret := refreshEC8CertBundle()
	ret.CAChain = issuingCaChainPem
	return ret
}

func setCerts() {
	caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic(err)
	}
	subjKeyID, err := GetSubjKeyID(caKey)
	if err != nil {
		panic(err)
	}
	caCertTemplate := &x509.Certificate{
		Subject: pkix.Name{
			CommonName: "root.localhost",
		},
		SubjectKeyId:          subjKeyID,
		DNSNames:              []string{"root.localhost"},
		KeyUsage:              x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
		SerialNumber:          big.NewInt(mathrand.Int63()),
		NotBefore:             time.Now().Add(-30 * time.Second),
		NotAfter:              time.Now().Add(262980 * time.Hour),
		BasicConstraintsValid: true,
		IsCA:                  true,
	}
	caBytes, err := x509.CreateCertificate(rand.Reader, caCertTemplate, caCertTemplate, caKey.Public(), caKey)
	if err != nil {
		panic(err)
	}
	caCert, err := x509.ParseCertificate(caBytes)
	if err != nil {
		panic(err)
	}
	caCertPEMBlock := &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: caBytes,
	}
	caCertPEM := strings.TrimSpace(string(pem.EncodeToMemory(caCertPEMBlock)))

	intKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		panic(err)
	}
	subjKeyID, err = GetSubjKeyID(intKey)
	if err != nil {
		panic(err)
	}
	intCertTemplate := &x509.Certificate{
		Subject: pkix.Name{
			CommonName: "int.localhost",
		},
		SubjectKeyId:          subjKeyID,
		DNSNames:              []string{"int.localhost"},
		KeyUsage:              x509.KeyUsage(x509.KeyUsageCertSign | x509.KeyUsageCRLSign),
		SerialNumber:          big.NewInt(mathrand.Int63()),
		NotBefore:             time.Now().Add(-30 * time.Second),
		NotAfter:              time.Now().Add(262980 * time.Hour),
		BasicConstraintsValid: true,
		IsCA:                  true,
	}
	intBytes, err := x509.CreateCertificate(rand.Reader, intCertTemplate, caCert, intKey.Public(), caKey)
	if err != nil {
		panic(err)
	}
	intCert, err := x509.ParseCertificate(intBytes)
	if err != nil {
		panic(err)
	}
	intCertPEMBlock := &pem.Block{
		Type:  "CERTIFICATE",
		Bytes: intBytes,
	}
	intCertPEM := strings.TrimSpace(string(pem.EncodeToMemory(intCertPEMBlock)))

	// EC generation
	{
		key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
		if err != nil {
			panic(err)
		}
		subjKeyID, err := GetSubjKeyID(key)
		if err != nil {
			panic(err)
		}
		certTemplate := &x509.Certificate{
			Subject: pkix.Name{
				CommonName: "localhost",
			},
			SubjectKeyId: subjKeyID,
			DNSNames:     []string{"localhost"},
			ExtKeyUsage: []x509.ExtKeyUsage{
				x509.ExtKeyUsageServerAuth,
				x509.ExtKeyUsageClientAuth,
			},
			KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
			SerialNumber: big.NewInt(mathrand.Int63()),
			NotBefore:    time.Now().Add(-30 * time.Second),
			NotAfter:     time.Now().Add(262980 * time.Hour),
		}
		csrTemplate := &x509.CertificateRequest{
			Subject: pkix.Name{
				CommonName: "localhost",
			},
			DNSNames: []string{"localhost"},
		}
		csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, key)
		if err != nil {
			panic(err)
		}
		csrPEMBlock := &pem.Block{
			Type:  "CERTIFICATE REQUEST",
			Bytes: csrBytes,
		}
		csrECPem = strings.TrimSpace(string(pem.EncodeToMemory(csrPEMBlock)))
		certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, intCert, key.Public(), intKey)
		if err != nil {
			panic(err)
		}
		certPEMBlock := &pem.Block{
			Type:  "CERTIFICATE",
			Bytes: certBytes,
		}
		certECPem = strings.TrimSpace(string(pem.EncodeToMemory(certPEMBlock)))
		marshaledKey, err := x509.MarshalECPrivateKey(key)
		if err != nil {
			panic(err)
		}
		keyPEMBlock := &pem.Block{
			Type:  "EC PRIVATE KEY",
			Bytes: marshaledKey,
		}
		privECKeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
		marshaledKey, err = x509.MarshalPKCS8PrivateKey(key)
		if err != nil {
			panic(err)
		}
		keyPEMBlock = &pem.Block{
			Type:  "PRIVATE KEY",
			Bytes: marshaledKey,
		}
		privEC8KeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
	}

	// RSA generation
	{
		key, err := rsa.GenerateKey(rand.Reader, 2048)
		if err != nil {
			panic(err)
		}
		subjKeyID, err := GetSubjKeyID(key)
		if err != nil {
			panic(err)
		}
		certTemplate := &x509.Certificate{
			Subject: pkix.Name{
				CommonName: "localhost",
			},
			SubjectKeyId: subjKeyID,
			DNSNames:     []string{"localhost"},
			ExtKeyUsage: []x509.ExtKeyUsage{
				x509.ExtKeyUsageServerAuth,
				x509.ExtKeyUsageClientAuth,
			},
			KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
			SerialNumber: big.NewInt(mathrand.Int63()),
			NotBefore:    time.Now().Add(-30 * time.Second),
			NotAfter:     time.Now().Add(262980 * time.Hour),
		}
		csrTemplate := &x509.CertificateRequest{
			Subject: pkix.Name{
				CommonName: "localhost",
			},
			DNSNames: []string{"localhost"},
		}
		csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, key)
		if err != nil {
			panic(err)
		}
		csrPEMBlock := &pem.Block{
			Type:  "CERTIFICATE REQUEST",
			Bytes: csrBytes,
		}
		csrRSAPem = strings.TrimSpace(string(pem.EncodeToMemory(csrPEMBlock)))
		certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, intCert, key.Public(), intKey)
		if err != nil {
			panic(err)
		}
		certPEMBlock := &pem.Block{
			Type:  "CERTIFICATE",
			Bytes: certBytes,
		}
		certRSAPem = strings.TrimSpace(string(pem.EncodeToMemory(certPEMBlock)))
		marshaledKey := x509.MarshalPKCS1PrivateKey(key)
		keyPEMBlock := &pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: marshaledKey,
		}
		privRSAKeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
		marshaledKey, err = x509.MarshalPKCS8PrivateKey(key)
		if err != nil {
			panic(err)
		}
		keyPEMBlock = &pem.Block{
			Type:  "PRIVATE KEY",
			Bytes: marshaledKey,
		}
		privRSA8KeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
	}

	// Ed25519 generation
	{
		pubkey, privkey, err := ed25519.GenerateKey(rand.Reader)
		if err != nil {
			panic(err)
		}
		subjKeyID, err := GetSubjKeyID(privkey)
		if err != nil {
			panic(err)
		}
		certTemplate := &x509.Certificate{
			Subject: pkix.Name{
				CommonName: "localhost",
			},
			SubjectKeyId: subjKeyID,
			DNSNames:     []string{"localhost"},
			ExtKeyUsage: []x509.ExtKeyUsage{
				x509.ExtKeyUsageServerAuth,
				x509.ExtKeyUsageClientAuth,
			},
			KeyUsage:     x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement,
			SerialNumber: big.NewInt(mathrand.Int63()),
			NotBefore:    time.Now().Add(-30 * time.Second),
			NotAfter:     time.Now().Add(262980 * time.Hour),
		}
		csrTemplate := &x509.CertificateRequest{
			Subject: pkix.Name{
				CommonName: "localhost",
			},
			DNSNames: []string{"localhost"},
		}
		csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csrTemplate, privkey)
		if err != nil {
			panic(err)
		}
		csrPEMBlock := &pem.Block{
			Type:  "CERTIFICATE REQUEST",
			Bytes: csrBytes,
		}
		csrEd25519Pem = strings.TrimSpace(string(pem.EncodeToMemory(csrPEMBlock)))
		certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, intCert, pubkey, intKey)
		if err != nil {
			panic(err)
		}
		certPEMBlock := &pem.Block{
			Type:  "CERTIFICATE",
			Bytes: certBytes,
		}
		certEd25519Pem = strings.TrimSpace(string(pem.EncodeToMemory(certPEMBlock)))
		marshaledKey, err := x509.MarshalPKCS8PrivateKey(privkey)
		if err != nil {
			panic(err)
		}
		keyPEMBlock := &pem.Block{
			Type:  "PRIVATE KEY",
			Bytes: marshaledKey,
		}
		privEd255198KeyPem = strings.TrimSpace(string(pem.EncodeToMemory(keyPEMBlock)))
	}

	issuingCaChainPem = []string{intCertPEM, caCertPEM}
}

func TestComparePublicKeysAndType(t *testing.T) {
	rsa1 := genRsaKey(t).Public()
	rsa2 := genRsaKey(t).Public()
	eddsa1 := genEdDSA(t).Public()
	eddsa2 := genEdDSA(t).Public()
	ed25519_1, _ := genEd25519Key(t)
	ed25519_2, _ := genEd25519Key(t)

	type args struct {
		key1Iface crypto.PublicKey
		key2Iface crypto.PublicKey
	}
	tests := []struct {
		name    string
		args    args
		want    bool
		wantErr bool
	}{
		{name: "RSA_Equal", args: args{key1Iface: rsa1, key2Iface: rsa1}, want: true, wantErr: false},
		{name: "RSA_NotEqual", args: args{key1Iface: rsa1, key2Iface: rsa2}, want: false, wantErr: false},
		{name: "EDDSA_Equal", args: args{key1Iface: eddsa1, key2Iface: eddsa1}, want: true, wantErr: false},
		{name: "EDDSA_NotEqual", args: args{key1Iface: eddsa1, key2Iface: eddsa2}, want: false, wantErr: false},
		{name: "ED25519_Equal", args: args{key1Iface: ed25519_1, key2Iface: ed25519_1}, want: true, wantErr: false},
		{name: "ED25519_NotEqual", args: args{key1Iface: ed25519_1, key2Iface: ed25519_2}, want: false, wantErr: false},
		{name: "Mismatched_RSA", args: args{key1Iface: rsa1, key2Iface: ed25519_2}, want: false, wantErr: false},
		{name: "Mismatched_EDDSA", args: args{key1Iface: ed25519_1, key2Iface: rsa1}, want: false, wantErr: false},
		{name: "Mismatched_ED25519", args: args{key1Iface: ed25519_1, key2Iface: rsa1}, want: false, wantErr: false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := ComparePublicKeysAndType(tt.args.key1Iface, tt.args.key2Iface)
			if (err != nil) != tt.wantErr {
				t.Errorf("ComparePublicKeysAndType() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got != tt.want {
				t.Errorf("ComparePublicKeysAndType() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestNotAfterValues(t *testing.T) {
	if ErrNotAfterBehavior != 0 {
		t.Fatalf("Expected ErrNotAfterBehavior=%v to have value 0", ErrNotAfterBehavior)
	}

	if TruncateNotAfterBehavior != 1 {
		t.Fatalf("Expected TruncateNotAfterBehavior=%v to have value 1", TruncateNotAfterBehavior)
	}

	if PermitNotAfterBehavior != 2 {
		t.Fatalf("Expected PermitNotAfterBehavior=%v to have value 2", PermitNotAfterBehavior)
	}
}

func TestSignatureAlgorithmRoundTripping(t *testing.T) {
	for leftName, value := range SignatureAlgorithmNames {
		if leftName == "pureed25519" && value == x509.PureEd25519 {
			continue
		}

		rightName, present := InvSignatureAlgorithmNames[value]
		if !present {
			t.Fatalf("%v=%v is present in SignatureAlgorithmNames but not in InvSignatureAlgorithmNames", leftName, value)
		}

		if strings.ToLower(rightName) != leftName {
			t.Fatalf("%v=%v is present in SignatureAlgorithmNames but inverse for %v has different name: %v", leftName, value, value, rightName)
		}
	}

	for leftValue, name := range InvSignatureAlgorithmNames {
		rightValue, present := SignatureAlgorithmNames[strings.ToLower(name)]
		if !present {
			t.Fatalf("%v=%v is present in InvSignatureAlgorithmNames but not in SignatureAlgorithmNames", leftValue, name)
		}

		if rightValue != leftValue {
			t.Fatalf("%v=%v is present in InvSignatureAlgorithmNames but forwards for %v has different value: %v", leftValue, name, name, rightValue)
		}
	}
}

func genRsaKey(t *testing.T) *rsa.PrivateKey {
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatal(err)
	}
	return key
}

func genEdDSA(t *testing.T) *ecdsa.PrivateKey {
	key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	return key
}

func genEd25519Key(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) {
	key, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		t.Fatal(err)
	}
	return key, priv
}

var (
	initTest           sync.Once
	privRSA8KeyPem     string
	privRSAKeyPem      string
	csrRSAPem          string
	certRSAPem         string
	privEd255198KeyPem string
	csrEd25519Pem      string
	certEd25519Pem     string
	privECKeyPem       string
	csrECPem           string
	privEC8KeyPem      string
	certECPem          string
	issuingCaChainPem  []string
)
