#include <stdio.h>
#include <stdlib.h>

#include "vk_constants.h"
#include "vk_error.h"
#include "vk_log.h"
#include "crypto/vk_crypto_cert.h"
#include "openssl/evp.h"
#include "openssl/x509.h"

int vk_crypto_check_server_cert_chain(const unsigned char* ca_pubkey, unsigned int ca_pubkey_size,
										const unsigned char* rawServiceRootCA_cert, unsigned int rawServiceRootCA_cert_size,
										unsigned char* cert, unsigned int cert_len)
{
	int ret = VK_ERR_VERIFY_CERT_CHAIN;

	const unsigned char* pCa_pubkey = ca_pubkey;
	RSA* pRootRsaPubKey = NULL;
	EVP_PKEY* pRootPubKey = NULL;

	const unsigned char* pRawServiceCert = rawServiceRootCA_cert;
	X509* pServiceCACert = NULL;
	EVP_PKEY* pServiceCAPubkey = NULL;

	const unsigned char* pCert = cert;
	X509* pServerCert = NULL;

	if (cert == NULL) {
		LOGE("%s: cert is null\n", __func__);
		return VK_ERR_INVALID_ARGUMENT;
	}

	if (cert_len <= 0) {
		LOGE("%s: cert_len isn't correct(%d)\n", __func__, cert_len);
		return VK_ERR_INVALID_ARGUMENT;
	}

	// Read root CA public key.
	if ((pRootRsaPubKey = d2i_RSA_PUBKEY(NULL, &pCa_pubkey, ca_pubkey_size)) == NULL) {
		LOGE("%s: Failed to read root CA pubkey\n", __func__);
		ret = VK_ERR_VERIFY_CERT_CHAIN;
		goto out;
	}

	// Allocate memory for root key.
	if ((pRootPubKey = EVP_PKEY_new()) == NULL) {
		LOGE("%s: Failed to initialize root pubkey\n", __func__);
		ret = VK_ERR_VERIFY_CERT_CHAIN;
		goto out;
	}

	// Set RSA property.
	if (!EVP_PKEY_set1_RSA(pRootPubKey, pRootRsaPubKey)) {
		LOGE("%s: Failed to set property for root pubkey\n", __func__);
		ret = VK_ERR_VERIFY_CERT_CHAIN;
		goto out;
	}

	// Read Service CA cert.
	if ((pServiceCACert = d2i_X509(NULL, &pRawServiceCert, rawServiceRootCA_cert_size)) == NULL) {
		LOGE("%s: Failed to read service_ca cert\n", __func__);
		ret = VK_ERR_VERIFY_CERT_CHAIN;
		goto out;
	}

	switch (X509_verify(pServiceCACert, pRootPubKey)) {
		case 1:
			ret = VK_SUCCESS;
			break;

		default:
			LOGE("%s: Failed to verify service CA cert\n", __func__);
			ret = VK_ERR_VERIFY_CERT_CHAIN;
			goto out;
	}

	// Read server cert's public key.
	if ((pServiceCAPubkey = X509_get_pubkey(pServiceCACert)) == NULL) {
		LOGE("%s: Failed to read server pubkey\n", __func__);
		ret = VK_ERR_VERIFY_CERT_CHAIN;
		goto out;
	}

	// Read server cert.
	if ((pServerCert = d2i_X509(NULL, &pCert, cert_len)) == NULL) {
		LOGE("%s: Failed to read server cert\n", __func__);
		ret = VK_ERR_VERIFY_CERT_CHAIN;
		goto out;
	}

	switch (X509_verify(pServerCert, pServiceCAPubkey)) {
		case 1:
			ret = VK_SUCCESS;
			break;

		default:
			LOGE("%s: Failed to verify cert\n", __func__);
			ret = VK_ERR_VERIFY_CERT_CHAIN;
			goto out;
	}

out:
	if (pRootPubKey) {
		EVP_PKEY_free(pRootPubKey);
		pRootPubKey = NULL;
	}

	if (pServiceCAPubkey ) {
		EVP_PKEY_free(pServiceCAPubkey);
		pServiceCAPubkey = NULL;
	}

	if (pRootRsaPubKey) {
		RSA_free(pRootRsaPubKey);
		pRootRsaPubKey = NULL;
	}

	if (pServerCert) {
		X509_free(pServerCert);
		pServerCert = NULL;
	}

	if (pServiceCACert) {
		X509_free(pServiceCACert);
		pServiceCACert = NULL;
	}

	return ret;
}
