/**
 * FreeRDP: A Remote Desktop Protocol Client
 * Certificate Handling
 *
 * Copyright 2011 Jiten Pathy
 * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
 *
 * 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.
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>

#include <openssl/pem.h>
#include <openssl/rsa.h>

#include <freerdp/utils/file.h>

static const char certificate_store_dir[] = "certs";
static const char certificate_known_hosts_file[] = "known_hosts";

#include "certificate.h"

/**
 *
 * X.509 Certificate Structure
 *
 * Certificate ::= SEQUENCE
 * {
 * 	tbsCertificate			TBSCertificate,
 * 	signatureAlgorithm		AlgorithmIdentifier,
 * 	signatureValue			BIT_STRING
 * }
 *
 * TBSCertificate ::= SEQUENCE
 * {
 * 	version			[0]	EXPLICIT Version DEFAULT v1,
 * 	serialNumber			CertificateSerialNumber,
 * 	signature			AlgorithmIdentifier,
 * 	issuer				Name,
 * 	validity			Validity,
 * 	subject				Name,
 * 	subjectPublicKeyInfo		SubjectPublicKeyInfo,
 * 	issuerUniqueID		[1]	IMPLICIT UniqueIdentifier OPTIONAL,
 * 	subjectUniqueId		[2]	IMPLICIT UniqueIdentifier OPTIONAL,
 * 	extensions		[3]	EXPLICIT Extensions OPTIONAL
 * }
 *
 * Version ::= INTEGER { v1(0), v2(1), v3(2) }
 *
 * CertificateSerialNumber ::= INTEGER
 *
 * AlgorithmIdentifier ::= SEQUENCE
 * {
 * 	algorithm			OBJECT_IDENTIFIER,
 * 	parameters			ANY DEFINED BY algorithm OPTIONAL
 * }
 *
 * Name ::= CHOICE { RDNSequence }
 *
 * RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
 *
 * RelativeDistinguishedName ::= SET OF AttributeTypeAndValue
 *
 * AttributeTypeAndValue ::= SEQUENCE
 * {
 * 	type				AttributeType,
 * 	value				AttributeValue
 * }
 *
 * AttributeType ::= OBJECT_IDENTIFIER
 *
 * AttributeValue ::= ANY DEFINED BY AttributeType
 *
 * Validity ::= SEQUENCE
 * {
 * 	notBefore			Time,
 * 	notAfter			Time
 * }
 *
 * Time ::= CHOICE
 * {
 * 	utcTime				UTCTime,
 * 	generalTime			GeneralizedTime
 * }
 *
 * UniqueIdentifier ::= BIT_STRING
 *
 * SubjectPublicKeyInfo ::= SEQUENCE
 * {
 * 	algorithm			AlgorithmIdentifier,
 * 	subjectPublicKey		BIT_STRING
 * }
 *
 * RSAPublicKey ::= SEQUENCE
 * {
 * 	modulus				INTEGER
 * 	publicExponent			INTEGER
 * }
 *
 * Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
 *
 * Extension ::= SEQUENCE
 * {
 * 	extnID				OBJECT_IDENTIFIER
 * 	critical			BOOLEAN DEFAULT FALSE,
 * 	extnValue			OCTET_STRING
 * }
 *
 */

/**
 * Read X.509 Certificate
 * @param certificate certificate module
 * @param cert X.509 certificate
 */

void certificate_read_x509_certificate(rdpCertBlob* cert, rdpCertInfo* info)
{
	STREAM* s;
	int length;
	uint8 padding;
	uint32 version;
	int modulus_length;
	int exponent_length;

	s = stream_new(0);
	stream_attach(s, cert->data, cert->length);

	ber_read_sequence_tag(s, &length); /* Certificate (SEQUENCE) */

	ber_read_sequence_tag(s, &length); /* TBSCertificate (SEQUENCE) */

	/* Explicit Contextual Tag [0] */
	ber_read_contextual_tag(s, 0, &length, true);
	ber_read_integer(s, &version); /* version (INTEGER) */
	version++;

	/* serialNumber */
	ber_read_integer(s, NULL); /* CertificateSerialNumber (INTEGER) */

	/* signature */
	ber_read_sequence_tag(s, &length); /* AlgorithmIdentifier (SEQUENCE) */
	stream_seek(s, length);

	/* issuer */
	ber_read_sequence_tag(s, &length); /* Name (SEQUENCE) */
	stream_seek(s, length);

	/* validity */
	ber_read_sequence_tag(s, &length); /* Validity (SEQUENCE) */
	stream_seek(s, length);

	/* subject */
	ber_read_sequence_tag(s, &length); /* Name (SEQUENCE) */
	stream_seek(s, length);

	/* subjectPublicKeyInfo */
	ber_read_sequence_tag(s, &length); /* SubjectPublicKeyInfo (SEQUENCE) */

	/* subjectPublicKeyInfo::AlgorithmIdentifier */
	ber_read_sequence_tag(s, &length); /* AlgorithmIdentifier (SEQUENCE) */
	stream_seek(s, length);

	/* subjectPublicKeyInfo::subjectPublicKey */
	ber_read_bit_string(s, &length, &padding); /* BIT_STRING */

	/* RSAPublicKey (SEQUENCE) */
	ber_read_sequence_tag(s, &length); /* SEQUENCE */

	ber_read_integer_length(s, &modulus_length); /* modulus (INTEGER) */

	/* skip zero padding, if any */
	do
	{
		stream_peek_uint8(s, padding);

		if (padding == 0)
		{
			stream_seek(s, 1);
			modulus_length--;
		}
	}
	while (padding == 0);

	freerdp_blob_alloc(&info->modulus, modulus_length);
	stream_read(s, info->modulus.data, modulus_length);

	ber_read_integer_length(s, &exponent_length); /* publicExponent (INTEGER) */
	stream_read(s, &info->exponent[4 - exponent_length], exponent_length);
	crypto_reverse(info->modulus.data, modulus_length);
	crypto_reverse(info->exponent, 4);

	stream_detach(s);
	stream_free(s);
}

/**
 * Instantiate new X.509 Certificate Chain.
 * @param count certificate chain count
 * @return new X.509 certificate chain
 */

rdpX509CertChain* certificate_new_x509_certificate_chain(uint32 count)
{
	rdpX509CertChain* x509_cert_chain;

	x509_cert_chain = (rdpX509CertChain*) xmalloc(sizeof(rdpX509CertChain));

	x509_cert_chain->count = count;
	x509_cert_chain->array = (rdpCertBlob*) xzalloc(sizeof(rdpCertBlob) * count);

	return x509_cert_chain;
}

/**
 * Free X.509 Certificate Chain.
 * @param x509_cert_chain X.509 certificate chain to be freed
 */

void certificate_free_x509_certificate_chain(rdpX509CertChain* x509_cert_chain)
{
	int i;

	if (x509_cert_chain != NULL)
	{
		for (i = 0; i < (int) x509_cert_chain->count; i++)
		{
			if (x509_cert_chain->array[i].data != NULL)
				xfree(x509_cert_chain->array[i].data);
		}

		xfree(x509_cert_chain->array);
		xfree(x509_cert_chain);
	}
}

static boolean certificate_process_server_public_key(rdpCertificate* certificate, STREAM* s, uint32 length)
{
	uint8 magic[4];
	uint32 keylen;
	uint32 bitlen;
	uint32 datalen;
	uint32 modlen;

	stream_read(s, magic, 4);
	if (memcmp(magic, "RSA1", 4) != 0)
	{
		printf("gcc_process_server_public_key: magic error\n");
		return false;
	}

	stream_read_uint32(s, keylen);
	stream_read_uint32(s, bitlen);
	stream_read_uint32(s, datalen);
	stream_read(s, certificate->cert_info.exponent, 4);
	modlen = keylen - 8;
	freerdp_blob_alloc(&(certificate->cert_info.modulus), modlen);
	stream_read(s, certificate->cert_info.modulus.data, modlen);
	/* 8 bytes of zero padding */
	stream_seek(s, 8);

	return true;
}

static boolean certificate_process_server_public_signature(rdpCertificate* certificate, uint8* sigdata, int sigdatalen, STREAM* s, uint32 siglen)
{
	uint8 md5hash[CRYPTO_MD5_DIGEST_LENGTH];
	uint8 encsig[TSSK_KEY_LENGTH + 8];
	uint8 sig[TSSK_KEY_LENGTH];
	CryptoMd5 md5ctx;
	int i, sum;

	md5ctx = crypto_md5_init();
	crypto_md5_update(md5ctx, sigdata, sigdatalen);
	crypto_md5_final(md5ctx, md5hash);

	stream_read(s, encsig, siglen);

	/* Last 8 bytes shall be all zero. */

	for (sum = 0, i = sizeof(encsig) - 8; i < sizeof(encsig); i++)
		sum += encsig[i];

	if (sum != 0)
	{
		printf("certificate_process_server_public_signature: invalid signature\n");
		//return false;
	}

	siglen -= 8;

	crypto_rsa_public_decrypt(encsig, siglen, TSSK_KEY_LENGTH, tssk_modulus, tssk_exponent, sig);

	/* Verify signature. */
	if (memcmp(md5hash, sig, sizeof(md5hash)) != 0)
	{
		printf("certificate_process_server_public_signature: invalid signature\n");
		//return false;
	}

	/*
	 * Verify rest of decrypted data:
	 * The 17th byte is 0x00.
	 * The 18th through 62nd bytes are each 0xFF.
	 * The 63rd byte is 0x01.
	 */

	for (sum = 0, i = 17; i < 62; i++)
		sum += sig[i];

	if (sig[16] != 0x00 || sum != 0xFF * (62 - 17) || sig[62] != 0x01)
	{
		printf("certificate_process_server_public_signature: invalid signature\n");
		//return false;
	}

	return true;
}

/**
 * Read a Server Proprietary Certificate.\n
 * @param certificate certificate module
 * @param s stream
 */

boolean certificate_read_server_proprietary_certificate(rdpCertificate* certificate, STREAM* s)
{
	uint32 dwSigAlgId;
	uint32 dwKeyAlgId;
	uint32 wPublicKeyBlobType;
	uint32 wPublicKeyBlobLen;
	uint32 wSignatureBlobType;
	uint32 wSignatureBlobLen;
	uint8* sigdata;
	int sigdatalen;

	/* -4, because we need to include dwVersion */
	sigdata = stream_get_tail(s) - 4;
	stream_read_uint32(s, dwSigAlgId);
	stream_read_uint32(s, dwKeyAlgId);
	if (!(dwSigAlgId == SIGNATURE_ALG_RSA && dwKeyAlgId == KEY_EXCHANGE_ALG_RSA))
	{
		printf("certificate_read_server_proprietary_certificate: parse error 1\n");
		return false;
	}
	stream_read_uint16(s, wPublicKeyBlobType);
	if (wPublicKeyBlobType != BB_RSA_KEY_BLOB)
	{
		printf("certificate_read_server_proprietary_certificate: parse error 2\n");
		return false;
	}
	stream_read_uint16(s, wPublicKeyBlobLen);
	if (!certificate_process_server_public_key(certificate, s, wPublicKeyBlobLen))
	{
		printf("certificate_read_server_proprietary_certificate: parse error 3\n");
		return false;
	}
	sigdatalen = stream_get_tail(s) - sigdata;
	stream_read_uint16(s, wSignatureBlobType);
	if (wSignatureBlobType != BB_RSA_SIGNATURE_BLOB)
	{
		printf("certificate_read_server_proprietary_certificate: parse error 4\n");
		return false;
	}
	stream_read_uint16(s, wSignatureBlobLen);
	if (wSignatureBlobLen != 72)
	{
		printf("certificate_process_server_public_signature: invalid signature length (got %d, expected %d)\n", wSignatureBlobLen, 64);
		return false;
	}
	if (!certificate_process_server_public_signature(certificate, sigdata, sigdatalen, s, wSignatureBlobLen))
	{
		printf("certificate_read_server_proprietary_certificate: parse error 5\n");
		return false;
	}

	return true;
}

/**
 * Read an X.509 Certificate Chain.\n
 * @param certificate certificate module
 * @param s stream
 */

boolean certificate_read_server_x509_certificate_chain(rdpCertificate* certificate, STREAM* s)
{
	int i;
	uint32 certLength;
	uint32 numCertBlobs;

	DEBUG_CERTIFICATE("Server X.509 Certificate Chain");

	stream_read_uint32(s, numCertBlobs); /* numCertBlobs */

	certificate->x509_cert_chain = certificate_new_x509_certificate_chain(numCertBlobs);

	for (i = 0; i < (int) numCertBlobs; i++)
	{
		stream_read_uint32(s, certLength);

		DEBUG_CERTIFICATE("\nX.509 Certificate #%d, length:%d", i + 1, certLength);

		certificate->x509_cert_chain->array[i].data = (uint8*) xmalloc(certLength);
		stream_read(s, certificate->x509_cert_chain->array[i].data, certLength);
		certificate->x509_cert_chain->array[i].length = certLength;

		if (numCertBlobs - i == 2)
		{
			rdpCertInfo cert_info;
			DEBUG_CERTIFICATE("License Server Certificate");
			certificate_read_x509_certificate(&certificate->x509_cert_chain->array[i], &cert_info);
			DEBUG_LICENSE("modulus length:%d", cert_info.modulus.length);
			freerdp_blob_free(&cert_info.modulus);
		}
		else if (numCertBlobs - i == 1)
		{
			DEBUG_CERTIFICATE("Terminal Server Certificate");
			certificate_read_x509_certificate(&certificate->x509_cert_chain->array[i], &certificate->cert_info);
			DEBUG_CERTIFICATE("modulus length:%d", certificate->cert_info.modulus.length);
		}
	}

	return true;
}

/**
 * Read a Server Certificate.\n
 * @param certificate certificate module
 * @param server_cert server certificate
 * @param length certificate length
 */

boolean certificate_read_server_certificate(rdpCertificate* certificate, uint8* server_cert, int length)
{
	STREAM* s;
	uint32 dwVersion;

	s = stream_new(0);
	stream_attach(s, server_cert, length);

	if (length < 1)
	{
		printf("null server certificate\n");
		return false;
	}

	stream_read_uint32(s, dwVersion); /* dwVersion (4 bytes) */

	switch (dwVersion & CERT_CHAIN_VERSION_MASK)
	{
		case CERT_CHAIN_VERSION_1:
			certificate_read_server_proprietary_certificate(certificate, s);
			break;

		case CERT_CHAIN_VERSION_2:
			certificate_read_server_x509_certificate_chain(certificate, s);
			break;

		default:
			printf("invalid certificate chain version:%d\n", dwVersion & CERT_CHAIN_VERSION_MASK);
			break;
	}

	xfree(s);
	return true;
}

rdpKey* key_new(const char* keyfile)
{
	rdpKey* key;
	RSA *rsa;
	FILE *fp;

	key = (rdpKey*) xzalloc(sizeof(rdpKey));

	if (key == NULL)
		return NULL;

	fp = fopen(keyfile, "r");

	if (fp == NULL)
	{
		printf("unable to load RSA key from %s: %s.", keyfile, strerror(errno));
		return NULL;
	}

	rsa = PEM_read_RSAPrivateKey(fp, NULL, NULL, NULL);

	if (rsa == NULL)
	{
		ERR_print_errors_fp(stdout);
		fclose(fp);
		return NULL;
	}

	fclose(fp);

	switch (RSA_check_key(rsa))
	{
		case 0:
			RSA_free(rsa);
			printf("invalid RSA key in %s", keyfile);
			return NULL;

		case 1:
			/* Valid key. */
			break;

		default:
			ERR_print_errors_fp(stdout);
			RSA_free(rsa);
			return NULL;
	}

	if (BN_num_bytes(rsa->e) > 4)
	{
		RSA_free(rsa);
		printf("RSA public exponent too large in %s", keyfile);
		return NULL;
	}

	freerdp_blob_alloc(&key->modulus, BN_num_bytes(rsa->n));
	BN_bn2bin(rsa->n, key->modulus.data);
	crypto_reverse(key->modulus.data, key->modulus.length);
	freerdp_blob_alloc(&key->private_exponent, BN_num_bytes(rsa->d));
	BN_bn2bin(rsa->d, key->private_exponent.data);
	crypto_reverse(key->private_exponent.data, key->private_exponent.length);
	memset(key->exponent, 0, sizeof(key->exponent));
	BN_bn2bin(rsa->e, key->exponent + sizeof(key->exponent) - BN_num_bytes(rsa->e));
	crypto_reverse(key->exponent, sizeof(key->exponent));

	RSA_free(rsa);

	return key;
}

void key_free(rdpKey* key)
{
	if (key != NULL)
	{
		freerdp_blob_free(&key->modulus);
		freerdp_blob_free(&key->private_exponent);
		xfree(key);
	}
}

void certificate_store_init(rdpCertificateStore* certificate_store)
{
	char* config_path;
	rdpSettings* settings;

	settings = certificate_store->settings;

	config_path = freerdp_get_config_path(settings);
	certificate_store->path = freerdp_construct_path(config_path, (char*) certificate_store_dir);

	if (freerdp_check_file_exists(certificate_store->path) == false)
	{
		freerdp_mkdir(certificate_store->path);
		printf("creating directory %s\n", certificate_store->path);
	}

	certificate_store->file = freerdp_construct_path(config_path, (char*) certificate_known_hosts_file);

	if (freerdp_check_file_exists(certificate_store->file) == false)
	{
		certificate_store->fp = fopen((char*) certificate_store->file, "w+");

		if (certificate_store->fp == NULL)
		{
			printf("certificate_store_open: error opening [%s] for writing\n", certificate_store->file);
			return;
		}

		fflush(certificate_store->fp);
	}
	else
	{
		certificate_store->fp = fopen((char*) certificate_store->file, "r+");
	}
}

int certificate_data_match(rdpCertificateStore* certificate_store, rdpCertificateData* certificate_data)
{
	FILE* fp;
	int length;
	char* data;
	char* pline;
	int match = 1;
	long int size;

	fp = certificate_store->fp;

	if (!fp)
		return match;

	fseek(fp, 0, SEEK_END);
	size = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	if (size < 1)
		return match;

	data = (char*) xmalloc(size + 2);

	if (fread(data, size, 1, fp) != 1)
	{
		xfree(data);
		return match;
	}

	data[size] = '\n';
	data[size + 1] = '\0';
	pline = strtok(data, "\n");

	while (pline != NULL)
	{
		length = strlen(pline);

		if (length > 0)
		{
			length = strcspn(pline, " \t");
			pline[length] = '\0';

			if (strcmp(pline, certificate_data->hostname) == 0)
			{
				pline = &pline[length + 1];

				if (strcmp(pline, certificate_data->fingerprint) == 0)
					match = 0;
				else
					match = -1;
				break;
			}
		}

		pline = strtok(NULL, "\n");
	}
	xfree(data);

	return match;
}

void certificate_data_print(rdpCertificateStore* certificate_store, rdpCertificateData* certificate_data)
{
	FILE* fp;

	/* reopen in append mode */
	fp = fopen(certificate_store->file, "a");

	if (!fp)
		return;

	fprintf(fp, "%s %s\n", certificate_data->hostname, certificate_data->fingerprint);
	fclose(fp);
}

rdpCertificateData* certificate_data_new(char* hostname, char* fingerprint)
{
	rdpCertificateData* certdata;

	certdata = (rdpCertificateData*) xzalloc(sizeof(rdpCertificateData));

	if (certdata != NULL)
	{
		certdata->hostname = xstrdup(hostname);
		certdata->fingerprint = xstrdup(fingerprint);
	}

	return certdata;
}

void certificate_data_free(rdpCertificateData* certificate_data)
{
	if (certificate_data != NULL)
	{
		xfree(certificate_data->hostname);
		xfree(certificate_data->fingerprint);
		xfree(certificate_data);
	}
}

rdpCertificateStore* certificate_store_new(rdpSettings* settings)
{
	rdpCertificateStore* certificate_store;

	certificate_store = (rdpCertificateStore*) xzalloc(sizeof(rdpCertificateStore));

	if (certificate_store != NULL)
	{
		certificate_store->settings = settings;
		certificate_store_init(certificate_store);
	}

	return certificate_store;
}

void certificate_store_free(rdpCertificateStore* certstore)
{
	if (certstore != NULL)
	{
		if (certstore->fp != NULL)
			fclose(certstore->fp);

		xfree(certstore->path);
		xfree(certstore->file);
		xfree(certstore);
	}
}

/**
 * Instantiate new certificate module.\n
 * @param rdp RDP module
 * @return new certificate module
 */

rdpCertificate* certificate_new()
{
	rdpCertificate* certificate;

	certificate = (rdpCertificate*) xzalloc(sizeof(rdpCertificate));

	if (certificate != NULL)
	{
		certificate->x509_cert_chain = NULL;
	}

	return certificate;
}

/**
 * Free certificate module.
 * @param certificate certificate module to be freed
 */

void certificate_free(rdpCertificate* certificate)
{
	if (certificate != NULL)
	{
		certificate_free_x509_certificate_chain(certificate->x509_cert_chain);

		if (certificate->cert_info.modulus.data != NULL)
			freerdp_blob_free(&(certificate->cert_info.modulus));

		xfree(certificate);
	}
}
