// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
	"crypto"
	"crypto/ecdsa"
	"crypto/ed25519"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"reflect"
	"strings"
	"testing"
)

const (
	csr = `
-----BEGIN CERTIFICATE REQUEST-----
MIIBoTCCAQoCAQAwEzERMA8GA1UEChMISnVqdSBvcmcwgZ8wDQYJKoZIhvcNAQEB
BQADgY0AMIGJAoGBANFf06eqiDx0+qD/xBAR5aMwwgaBOn6TPfSy96vOxLTsfkTg
ir/vb8UG+F5hO6yxF+z2BgzD8LwcbKnxahoPq/aWGLw3Umcqm4wxgWKHxvtYSQDG
w4zpmKOqgkagxbx32JXDlMpi6adUVHNvB838CiUys6IkVB0obGHnre8zmCLdAgMB
AAGgTjBMBgkqhkiG9w0BCQ4xPzA9MDsGA1UdEQQ0MDKGMHNwaWZmZTovL3Rlc3Qu
Y29tL25hbWVzcGFjZS9ucy9zZXJ2aWNlYWNjb3VudC9zYTANBgkqhkiG9w0BAQsF
AAOBgQCw9dL6xRQSjdYKt7exqlTJliuNEhw/xDVGlNUbDZnT0uL3zXI//Z8tsejn
8IFzrDtm0Z2j4BmBzNMvYBKL/4JPZ8DFywOyQqTYnGtHIkt41CNjGfqJRk8pIqVC
hKldzzeCKNgztEvsUKVqltFZ3ZYnkj/8/Cg8zUtTkOhHOjvuig==
-----END CERTIFICATE REQUEST-----`

	keyRSA = `
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAw/OBAAhDu58f0HkJlJBtb42Jp9EECC+WYEOVEdM/Y9fqcoSF
b19NxztVqy0r/aW8pCO3DZ2EYIA3Y9pYasDfhsIl9lhQkvEwk/05iL6oNrZ45Bgs
iSK+R5OlO9pXtj6HF948qFTDYbYVqki3rAWSSYeGpQ+/s/xcIIIKH5ozKs7DTqR8
svQ6t7Hxg0vYSUCHfJo25yIvoo8XGZxrFWOZDXfHHC22q8kuuxT82bdQo7KzYhgn
uujyzIZYqgG9BuUmB6UYdvuDRRDz4HDfERSFFxZbTAaMPNgCRvQnkPS0DJO0XZW2
T9m3bQvaqTgFI/capuhhgRcP0UrStJKZO7LVHQIDAQABAoIBAFLw0v2Mgf78j57S
XLfBmlDJfCbIVgiQ+/mrIYH2BLLiRZ5LcZ9+m5FlEBHwgNpQONTROT5OGiYun0No
vFwTX4nOy/rFzvUjmghJ+vxilxjxi6IgiVlSl2/8ksgO12mQdeYob0xg9IJ7bBgz
x2rMwOrWrqtXSzGH9AbehCJ0RowrUUjTujnow8WrDVS0cjPIl9c1eQDIDlHCUskC
iGMYYfJtB1nkdw1Kkp1YVmCYqwzVENi6+Hx66j/oOtGteTelwFmclc7JIK7liKEZ
xnDbgVTkIp9nSszpHStWikwh/srCWI7trC/k0viViZQLOd/64CyJ/sf8uZDzsG7f
hoiK3pECgYEAxF/d7QcDWaXCBkw4yC8ozqp6KTcA8thVuBK0Adtpzuf9t8h+B7V2
wlkSEs4A3YiUnxolEh1XOT+0u3uGfawlhFgXxEEHeBq+Lz6PIbWuVSl4PiWo8vtj
9MoBYRPtJelhHkBfjunqqaFwdRQQvXjmCsQfx4UAhBxdXvc2kTR/y08CgYEA/3K8
DKXldQliScqkG+Acj6nNgugecLYjCHgmAglHX6jwDuTVqNF3cv3DF5bZ4/nIbDsk
WooVhS4AEFYWceqmTveGsJNuDMoRSWNwDFRBu5Iq6LxneKiXp9vuZ4U4xZNejrgx
la7w1hQs92qCloY4Jxw9Ls3zKub4vC26CfJwzdMCgYBGw0T1ZNGQPGruWgkcGeJa
lpPuxiNRXyOEcTjscmRuaqrCzzybCokA/5fDrvgg3Fax/nndTTVhK9O0u457Os1K
I3RtBAHtBbYC0EhDnXR0u7zYqDl5VZ1vWFum38dVIgQdIpVMqn4lIkej6Ncfb7F1
r7bD7umAsbfzwKGpMYHbgQKBgQDL03vzR6hIc71mjffWekOv6lieXKJ1Yw+fIWeK
dmbqEH3EFJnbg5AhRBSYTPj9bICcw7AlQksbonHQlzB/ozEij2V8nZbRQ6b5fQuZ
+t0cUuxEGpkhcLzZ5qZbGbUMCaQIkzaVbiqjVyPuI6Ghg+VoZ6L2JsUh9XyBgqcQ
as/RmwKBgGPB8PHYHyz0km8LxM/GPstcoO4Ls5coS3MX2EBDKGqWOIOtLKz0azc7
R4beF5BJE6ulhLig4fkOWH4CIvw2Y1/22GJE/fYjUTRMD57ZdYuKqSyMNxwqiolw
xGSDfnFvR13RCqeUdlQofVYpolqrSobOyOVfQv2ksnPPsC87NISM
-----END RSA PRIVATE KEY-----`

	keyInvalidRSA = `
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----`

	keyECDSA = `
-----BEGIN EC PRIVATE KEY-----
MGgCAQEEHBMUyVWFKTW4TwtwCmIAxdpsBFn0MV7tGeSA32CgBwYFK4EEACGhPAM6
AATCkAx7whb2k3xWm+UjlFWFiV11oYmIdYgXqiAQkiz7fEq6QFhsjjCizeGzAlhT
TmngRSxv/dSvGA==
-----END EC PRIVATE KEY-----`

	keyInvalidECDSA = `
-----BEGIN EC PRIVATE KEY-----
-----END EC PRIVATE KEY-----`

	keyPKCS8RSA = `
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5iMw+Cvt0ZAnj
l5piKgyoFIlCAD2FeKd0CQaUA9sHk7XuWAXUAHWQrNXRIG/d9uZBedOwFQ/D8GCR
m5uQTTZC/4cww6pXvrVvX3DYe7H3hF8g3cDsHoYuR51LHzFR1lAe/y0XFaRw18Vm
WNFLnUozEeq1nT0CAyw0iXGa22ptZA3M1G9JJjDrENT6adI/N/19XH9fMiQOOOrT
zerC9DRVouXgxDsXUz2H25XT3YWu7nVs/ic57BRc3JU4JfYbVR8Br2+lAgPFvLFs
/HfX2BuvHC64sseIOVfXL7a/EaOVQhD9NKL7K+5faX21JAfN40f+qwfNLJrrUjHu
7UMpZbJVAgMBAAECggEBAJQ6RarfzUuMzRXGvjHlFF2IoqxXUs96uJYMy/OfLPNd
wIEeU/GvOD4Qx3afqqA0LHttIIHSIdlSB2TtZBiih1J5ogGEoWge1geXwalDEckF
OZchc4txS5RX5MPqtNWEGljZV6XUxZ7d1DjThssZa/lnPBRC/kXIUR3cHSYyXFHt
vA4vY4tVmH1TDUY51A64qKO5dg/Jupn1AHVnoU8eGGD31pISmlZdy64rBMdx9Ac5
u30rGP4RWR9YCSZRFtoxL2d0VoEdKAx1qN0/rPYPNUK1EIFO8kW4voNlUfyGDTLX
TdYBX5Stmm3ws6MAoJJKdpBLWmiJgqOjaiYlf/7y+zUCgYEA5mogVNiba2KRP85O
QOH2ugY1dPiiJLm6QTub7Aygy8lc6WQGa2TXBUpqGYp4rs0wjBnkhtIoErZM8cxu
ENj8dGBikfEJq95NcOMx6wsQYKRWX9JwDzNrLyUCtKK+B3kO8daHzHWTPB7mNkHF
Y91PZLmzSCdAsqxI5VHo0mzStMsCgYEAziLizDE4Q+IpNvzpGo+TxOJILTYdf1HC
OdoaWMKU1Hl6TyWGXon0/ZfAI+jxjT9sI4/Slr5ED86FT0iaIOcSgvl8PmbcvMrM
m3Nj/ZVM3EPzo8EW/LNFlZreheIuvuSwUvnzlWaWZdWJ5/z0M7Is05NsF2v5QlON
OVd/b3pVcV8CgYEAhIDTRvepqP9t9/t0FOvdLu0TIMk6tVP5QDo/WGeKsKaDv9O9
vVSoMmqwyS9QZ3WoTWk2ejGwydH8PbEKOrYNt/8VsEelACk+74Q32KrsKCdZZJFn
z9YJ9XqbK7XLAhEj/v8X6QRUP2aljN4V3XAPkCUabIvmMNnSsc2AzkG2ijECgYB6
BtTTo9928A8N6jHj81K6nmmzufFESZX8wUwPd0C7dx4cdE5S8MACzy6DE4bK4tyV
QLKdYgzQfqUUBhqXl7KxrhcKqcHKURNGgsySdSuGyQMV0VxWQ5nRslhAUWDyyFZJ
CIZVzuEBb6OvnWLCp5s5tG+sfdKUnPlhFJbv2y9xaQKBgCp1g3NtljkH1Olk+kFF
LwxpTxIxS/8jZIM6MpfcmLPyZVRrpUORgkphXvLVXub+anNqGbK0Cbc30KQGnFKD
PFsekZAmhgetPqL16MQSEZbXRSnGiklqQtew79S/yQDwZVCer8n1ABp5eZ2wsLgu
92ik2sTgTEhef6AgLeHcT5ne
-----END PRIVATE KEY-----`

	keyInvalidPKCS8 = `
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----`

	certRSA = `
-----BEGIN CERTIFICATE-----
MIIC+zCCAeOgAwIBAgIQQ0vFSayWg4FQBBr1EpI5rzANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhKdWp1IG9yZzAeFw0xNzAzMTEwNjA0MDJaFw0xODAzMTEwNjA0
MDJaMBMxETAPBgNVBAoTCEp1anUgb3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAw/OBAAhDu58f0HkJlJBtb42Jp9EECC+WYEOVEdM/Y9fqcoSFb19N
xztVqy0r/aW8pCO3DZ2EYIA3Y9pYasDfhsIl9lhQkvEwk/05iL6oNrZ45BgsiSK+
R5OlO9pXtj6HF948qFTDYbYVqki3rAWSSYeGpQ+/s/xcIIIKH5ozKs7DTqR8svQ6
t7Hxg0vYSUCHfJo25yIvoo8XGZxrFWOZDXfHHC22q8kuuxT82bdQo7KzYhgnuujy
zIZYqgG9BuUmB6UYdvuDRRDz4HDfERSFFxZbTAaMPNgCRvQnkPS0DJO0XZW2T9m3
bQvaqTgFI/capuhhgRcP0UrStJKZO7LVHQIDAQABo0swSTAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTAL
gglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAITDuqOhN1jwiA72qSWzOwuy
bMHPkUTUw2JfICtPS0AlfNNVJXREUi4KoX81ju126PGQeOTApWvS5Kkd6PbNqVH9
g3myAKrkyjewTfFtK5OOOQGzQT6lCEhKdZJusdqfAMl1heFJGnZ6GAi38ftdz2Z8
0LPyyIaVBvexNnTPrqoBqdtWyzjYIdMnsSNWJnldmWjwA76sW+vvlLvTONiT4unM
8ia4GGIw7GK4E/7qxl27q6pXdZkZgG53XItYiUJGAKeBJ2nQfXq0qSmtpHkF17Cu
hw25X3FJpzRq62JxTx5q6+M2c07g4dkbfMDp/TO7vF4SWruU6JBZj5MVDYn4PEA=
-----END CERTIFICATE-----`

	certECDSA = `
-----BEGIN CERTIFICATE-----
MIIBSzCB+qADAgECAhAzJszEACNBOHrsfSUJMPsHMAoGCCqGSM49BAMCMAsxCTAH
BgNVBAoTADAeFw0xNzAzMTMwNTE2NThaFw0xNzAzMTMwNTE2NThaMAsxCTAHBgNV
BAoTADBOMBAGByqGSM49AgEGBSuBBAAhAzoABMKQDHvCFvaTfFab5SOUVYWJXXWh
iYh1iBeqIBCSLPt8SrpAWGyOMKLN4bMCWFNOaeBFLG/91K8Yo0swSTAOBgNVHQ8B
Af8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNV
HREEDTALgglsb2NhbGhvc3QwCgYIKoZIzj0EAwIDQAAwPQIcY8lgBAAtFWtxmk9k
BB6nORpwdv4LVt/BFgLwWQIdAKvHn7cxBJ+aAC25rIumRNKDzP7PkV0HDbxtX+M=
-----END CERTIFICATE-----`
)

var certChainValid = loadPEMFile("../testdata/cert-chain.pem")

func TestParsePemEncodedCertificate(t *testing.T) {
	testCases := map[string]struct {
		errMsg        string
		pem           string
		publicKeyAlgo x509.PublicKeyAlgorithm
	}{
		"Invalid PEM string": {
			errMsg: "invalid PEM encoded certificate",
			pem:    "invalid pem string",
		},
		"Invalid certificate string": {
			errMsg: "failed to parse X.509 certificate",
			pem:    keyECDSA,
		},
		"Parse RSA certificate": {
			publicKeyAlgo: x509.RSA,
			pem:           certRSA,
		},
		"Parse ECDSA certificate": {
			publicKeyAlgo: x509.ECDSA,
			pem:           certECDSA,
		},
	}

	for id, c := range testCases {
		cert, err := ParsePemEncodedCertificate([]byte(c.pem))
		if c.errMsg != "" {
			if err == nil {
				t.Errorf("%s: no error is returned", id)
			} else if c.errMsg != err.Error() {
				t.Errorf(`%s: Unexpected error message: expected "%s" but got "%s"`, id, c.errMsg, err.Error())
			}
		} else if cert.PublicKeyAlgorithm != c.publicKeyAlgo {
			t.Errorf("%s: Unexpected public key algorithm: want %d but got %d", id, c.publicKeyAlgo, cert.PublicKeyAlgorithm)
		}
	}
}

func TestParsePemEncodedCertificateChain(t *testing.T) {
	testCases := map[string]struct {
		errMsg string
		pem    string
	}{
		"Parse Certificate Chain": {
			pem: certChainValid,
		},
		"Invalid PEM string": {
			pem:    "Invalid PEM string",
			errMsg: "invalid PEM encoded certificate",
		},
		"Invalid certificate string": {
			pem:    keyRSA,
			errMsg: "failed to parse X.509 certificate",
		},
	}

	for id, c := range testCases {
		_, rootCertByte, err := ParsePemEncodedCertificateChain([]byte(c.pem))
		if c.errMsg != "" {
			if err == nil {
				t.Errorf("%s: no error is returned", id)
			} else if c.errMsg != err.Error() {
				t.Errorf(`%s: Unexpected error message: expected "%s" but got "%s"`, id, c.errMsg, err.Error())
			}
		} else if len(rootCertByte) == 0 {
			t.Errorf("%s: rootCertByte is nil", id)
		}
	}
}

func TestParsePemEncodedCSR(t *testing.T) {
	testCases := map[string]struct {
		algo   x509.PublicKeyAlgorithm
		errMsg string
		pem    string
	}{
		"Invalid PEM string": {
			errMsg: "certificate signing request is not properly encoded",
			pem:    "bad pem string",
		},
		"Invalid CSR string": {
			errMsg: "failed to parse X.509 certificate signing request",
			pem:    certECDSA,
		},
		"Parse CSR": {
			algo: x509.RSA,
			pem:  csr,
		},
	}

	for id, c := range testCases {
		_, err := ParsePemEncodedCSR([]byte(c.pem))
		if c.errMsg != "" {
			if err == nil {
				t.Errorf(`%s: no error is returned, expected "%s"`, id, c.errMsg)
			} else if c.errMsg != err.Error() {
				t.Errorf(`%s: Unexpected error message: want "%s" but got "%s"`, id, c.errMsg, err.Error())
			}
		} else if err != nil {
			t.Errorf(`%s: Unexpected error: "%s"`, id, err)
		}
	}
}

func TestParsePemEncodedKey(t *testing.T) {
	testCases := map[string]struct {
		pem     string
		keyType reflect.Type
		errMsg  string
	}{
		"Invalid PEM string": {
			pem:    "Invalid PEM string",
			errMsg: "invalid PEM-encoded key",
		},
		"Invalid PEM block type": {
			pem:    certRSA,
			errMsg: "unsupported PEM block type for a private key: CERTIFICATE",
		},
		"Parse RSA key": {
			pem:     keyRSA,
			keyType: reflect.TypeOf(&rsa.PrivateKey{}),
		},
		"Parse invalid RSA key": {
			pem:    keyInvalidRSA,
			errMsg: "failed to parse the RSA private key",
		},
		"Parse ECDSA key": {
			pem:     keyECDSA,
			keyType: reflect.TypeOf(&ecdsa.PrivateKey{}),
		},
		"Parse invalid ECDSA key": {
			pem:    keyInvalidECDSA,
			errMsg: "failed to parse the ECDSA private key",
		},
		"Parse PKCS8 key using RSA algorithm": {
			pem:     keyPKCS8RSA,
			keyType: reflect.TypeOf(&rsa.PrivateKey{}),
		},
		"Parse invalid PKCS8 key": {
			pem:    keyInvalidPKCS8,
			errMsg: "failed to parse the PKCS8 private key",
		},
	}

	for id, c := range testCases {
		key, err := ParsePemEncodedKey([]byte(c.pem))
		if c.errMsg != "" {
			if err == nil {
				t.Errorf(`%s: no error is returned, expected "%s"`, id, c.errMsg)
			} else if c.errMsg != err.Error() {
				t.Errorf(`%s: Unexpected error message: expected "%s" but got "%s"`, id, c.errMsg, err.Error())
			}
		} else if err != nil {
			t.Errorf(`%s: Unexpected error: "%s"`, id, err)
		} else if keyType := reflect.TypeOf(key); keyType != c.keyType {
			t.Errorf(`%s: Unmatched key type: expected "%v" but got "%v"`, id, c.keyType, keyType)
		}
	}
}

func TestGetRSAKeySize(t *testing.T) {
	testCases := map[string]struct {
		pem    string
		size   int
		errMsg string
	}{
		"Success with RSA key": {
			pem:  keyRSA,
			size: 2048,
		},
		"Success with PKCS8RSA key": {
			pem:  keyPKCS8RSA,
			size: 2048,
		},
		"Failure with non-RSA key": {
			pem:    keyECDSA,
			errMsg: "key type is not RSA: *ecdsa.PrivateKey",
		},
	}

	for id, c := range testCases {
		key, err := ParsePemEncodedKey([]byte(c.pem))
		if err != nil {
			t.Errorf("%s: failed to parse the Pem key.", id)
		}
		size, err := GetRSAKeySize(key)
		if c.errMsg != "" {
			if err == nil {
				t.Errorf(`%s: no error is returned, expected error: "%s"`, id, c.errMsg)
			} else if c.errMsg != err.Error() {
				t.Errorf(`%s: Unexpected error message: expected "%s" but got "%s"`, id, c.errMsg, err.Error())
			}
		} else if err != nil {
			t.Errorf(`%s: Unexpected error: "%s"`, id, err)
		} else if size != c.size {
			t.Errorf(`%s: Unmatched key size: expected %v but got "%v"`, id, c.size, size)
		}
	}
}

func TestIsSupportedECPrivateKey(t *testing.T) {
	_, ed25519PrivKey, _ := ed25519.GenerateKey(nil)
	ecdsaPrivKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

	cases := map[string]struct {
		key         crypto.PrivateKey
		isSupported bool
	}{
		"ECDSA": {
			key:         ecdsaPrivKey,
			isSupported: true,
		},
		"ED25519": {
			key:         ed25519PrivKey,
			isSupported: false,
		},
	}

	for id, tc := range cases {
		if IsSupportedECPrivateKey(&tc.key) != tc.isSupported {
			t.Errorf("%s: does not match expected support level for EC signature algorithms", id)
		}
	}
}

func TestPemCertBytestoString(t *testing.T) {
	// empty check
	if len(PemCertBytestoString([]byte{})) != 0 {
		t.Errorf("Empty call fails!")
	}

	certBytes := []byte(certECDSA)
	certBytes = AppendCertByte(certBytes, []byte(certRSA))
	result := PemCertBytestoString(certBytes)
	cert1 := strings.TrimSuffix(strings.TrimPrefix(certECDSA, "\n"), "\n")
	cert2 := strings.TrimSuffix(strings.TrimPrefix(certRSA, "\n"), "\n")
	if !reflect.DeepEqual(result, []string{cert1, cert2}) {
		t.Errorf("Basic comparison fails!")
	}

	// check only first string passed if second is bogus
	certBytes = []byte(certRSA)
	certBytes = AppendCertByte(certBytes, []byte("Bogus"))
	result = PemCertBytestoString(certBytes)
	cert1 = strings.TrimSuffix(strings.TrimPrefix(certRSA, "\n"), "\n")
	if !reflect.DeepEqual(result, []string{cert1}) {
		t.Errorf("Bogus comparison fails!")
	}
}
