/**
 * Copyright (C) 2006 International Business Machines Corp.
 * Author(s): Trevor S. Highland <trevor.highland@gmail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/err.h>
#include <openssl/engine.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "../include/ecryptfs.h"

#define RSA_KEY_SIZE_BYTES 128

struct pki_openssl {
	char *path;
	char *password;
	char *signature;
};

static int get_pki_data(unsigned char *data, struct pki_openssl *pki_data)
{
	size_t path_length;
	size_t pass_length;
	size_t i = 0;

	path_length = data[i++] % 256;
	path_length += data[i++] << 8;
	i += path_length;
	pass_length = data[i++] % 256;
	pass_length += data[i++] << 8;
	pki_data->path = data + 2;
	pki_data->password = pki_data->path + path_length + 2;
	pki_data->signature = pki_data->password + pass_length + 2;
	return 0;
}

static int generate_signature(RSA *key, char *sig)
{
	int len, nbits, ebits, i;
	int nbytes, ebytes;
	char *hash;
	char *data = NULL;
	int rc = 0;

	hash = malloc(SHA_DIGEST_LENGTH);
	if (!hash) {
		syslog(LOG_ERR, "Out of memory\n");
		rc = -ENOMEM;
		goto out;
	}
	nbits = BN_num_bits(key->n);
	nbytes = nbits / 8;
	if (nbits % 8)
		nbytes++;
	ebits = BN_num_bits(key->e);
	ebytes = ebits / 8;
	if (ebits % 8)
		ebytes++;
	len = 10 + nbytes + ebytes;
	data = malloc(3 + len);
	if (!data) {
		syslog(LOG_ERR, "Out of memory\n");
		rc = -ENOMEM;
		goto out;
	}
	i = 0;
	data[i++] = '\x99';
	data[i++] = (char)(len >> 8);
	data[i++] = (char)len;
	data[i++] = '\x04';
	data[i++] = '\00';
	data[i++] = '\00';
	data[i++] = '\00';
	data[i++] = '\00';
	data[i++] = '\02';
	data[i++] = (char)(nbits >> 8);
	data[i++] = (char)nbits;
	BN_bn2bin(key->n, &(data[i]));
	i += nbytes;
	data[i++] = (char)(ebits >> 8);
	data[i++] = (char)ebits;
	BN_bn2bin(key->e, &(data[i]));
	i += ebytes;
	SHA1(data, len + 3, hash);
	to_hex(sig, hash, ECRYPTFS_SIG_SIZE);
	sig[ECRYPTFS_SIG_SIZE_HEX] = '\0';
out:
	free(data);
	free(hash);
	return rc;
}

static int write_key_to_file(RSA *rsa, char *filename, char *pass)
{
	BIO *out;
	const EVP_CIPHER *enc = EVP_aes_256_cbc();
	int rc = 0;

	if ((out = BIO_new(BIO_s_file())) == NULL) {
		syslog(LOG_ERR, "Unable to create BIO for output\n");
		rc= -EIO;
		goto out;
	}
	if (BIO_write_filename(out, filename) <= 0) {
		syslog(LOG_ERR, "Failed to open file for reading\n");
		rc = -EIO;
		goto out;
	}
	if (!PEM_write_bio_RSAPrivateKey(out, rsa, enc, NULL, 0, NULL, pass)) {
		rc = -1;
		syslog(LOG_ERR, "Failed to write key to file\n");
		goto out;
	}
out:
	if (out)
		BIO_free_all(out);
	return rc;
}

static int read_key(RSA **rsa, unsigned char *private_key_data)
{
	struct pki_openssl *pki_data = NULL;
	char *read_key_sig = NULL;
	BIO *in = NULL;
	int rc;

	CRYPTO_malloc_init();
	ERR_load_crypto_strings();
	OpenSSL_add_all_algorithms();
	ENGINE_load_builtin_engines();
	pki_data = malloc(sizeof(struct pki_openssl));
	if (!pki_data) {
		syslog(LOG_ERR, "Out of memory\n");
		rc = -ENOMEM;
		goto out;
	}
	get_pki_data(private_key_data, pki_data);
	if ((in = BIO_new(BIO_s_file())) == NULL) {
		syslog(LOG_ERR, "Unable to create BIO for output\n");
		rc = -EIO;
		goto out;
	}
	if (BIO_read_filename(in, pki_data->path) <= 0) {
		syslog(LOG_ERR, "Unable to read filename [%s]\n",
		       pki_data->path);
		rc = -EIO;
		goto out;
	}
	if ((*rsa = PEM_read_bio_RSAPrivateKey(in, NULL, NULL,
					       pki_data->password )) == NULL) {
		syslog(LOG_ERR, "Unable to read private key from file [%s]\n",
		       pki_data->password);
		rc = -EIO;
		goto out;
	}
	read_key_sig = malloc(ECRYPTFS_SIG_SIZE_HEX);
	if (!read_key_sig) {
		syslog(LOG_ERR, "Out of memory\n");
		rc = -ENOMEM;
		goto out;
	}
	rc = generate_signature(*rsa, read_key_sig);
	if (strcmp(pki_data->signature, read_key_sig)) {
		syslog(LOG_ERR, "The loaded key has incorrect signature\n");
		rc = -EINVAL;
	}
out:
	free(read_key_sig);
	free(pki_data);
	BIO_free_all(in);
	EVP_cleanup();
	CRYPTO_cleanup_all_ex_data();
	ERR_remove_state(0);
	ERR_free_strings();
	return rc;
}

/* TODO: If sig == NULL, just return *length; the caller can use this
 * information to allocate memory and call again. There should also be
 * a max_length parameter. -Halcrow */
int set_key_data(unsigned char *private_key_data, char *sig, int *length)
{
	struct pki_openssl *pki_data = NULL;
	BIO *in = NULL;
	RSA *rsa = NULL;
	int rc;

	CRYPTO_malloc_init();
	ERR_load_crypto_strings();
	OpenSSL_add_all_algorithms();
	ENGINE_load_builtin_engines();
	pki_data = malloc(sizeof(pki_data));
	if (!pki_data) {
		syslog(LOG_ERR, "Out of memory\n");
		rc = -ENOMEM;
		goto out;
	}
	get_pki_data(private_key_data, pki_data);
	if ((in = BIO_new(BIO_s_file())) == NULL) {
		syslog(LOG_ERR, "Unable to create BIO for output\n");
		rc = -EIO;
		goto out;
	}
	if (BIO_read_filename(in, pki_data->path) <= 0) {
		syslog(LOG_ERR, "Unable to read filename [%s]\n",
		       pki_data->path);
		rc = -EIO;
		goto out;
	}
	if ((rsa = PEM_read_bio_RSAPrivateKey(in, NULL, NULL,
					      pki_data->password )) == NULL) {
		syslog(LOG_ERR, "Unable to read private key from file\n");
		rc = -EIO;
		goto out;
	}
	*length = RSA_size(rsa);
	rc = generate_signature(rsa, pki_data->signature);
	memcpy(sig, pki_data->signature, ECRYPTFS_SIG_SIZE_HEX + 1);
out:
	free(pki_data);
	RSA_free(rsa);
	BIO_free_all(in);
	EVP_cleanup();
	CRYPTO_cleanup_all_ex_data();
	ERR_remove_state(0);
	ERR_free_strings();
	return rc;
}

int ecryptfs_generate_key(char *filename)
{
	RSA *rsa;
	BIGNUM *e;
	int rc = 0;

	rsa = RSA_new();
	if (!rsa) {
		rc = -ENOMEM;
		goto out;
	}
	e = BN_new();
	if (!rsa) {
		rc = -ENOMEM;
		goto out;
	}
	/* Set e to 65537 */
	if (BN_set_bit(e, 0) == 0 || BN_set_bit(e, 16) == 0) {
		rc = -EINVAL;
		goto out;
	}
	rsa = RSA_generate_key(1024, 65537, NULL, NULL);
	if (write_key_to_file(rsa, filename, NULL))
		rc = -EIO;
out:
	BN_free(e);
	RSA_free(rsa);
	if (rc)
		syslog(LOG_ERR, "Failed to write key file rc = [%x]\n", rc);
	return rc;
}

int ecryptfs_encrypt(int size, char *from, char *to,
		     unsigned char *private_key_data)
{
	RSA *rsa = NULL;
	int rc;

	rc = read_key(&rsa,  private_key_data);
	if (rc) {
		rc = -(int)ERR_get_error();
		syslog(LOG_ERR, "Error attempting to read RSA key from file;"
		       " rc = [%d]\n", rc);
		goto out;
	}
	rc = RSA_public_encrypt(size, from, to, rsa,
				RSA_PKCS1_OAEP_PADDING);
	if (rc == -1) {
		rc = -(int)ERR_get_error();
		syslog(LOG_ERR, "Error attempting to perform RSA public key "
		       "encryption; rc = [%d]\n", rc);
	} else {
		rc = 0;
	}
out:
	RSA_free(rsa);
	return rc;
}

/* currently assumes that a single block is passed in for decryption */
int decrypt(char *from, char *to, size_t *decrypted_key_size,
	    unsigned char *private_key_data)
{
	RSA *rsa = NULL;
	int rc;

	rc = read_key(&rsa, private_key_data);
	if (rc) {
		rc = -(int)ERR_get_error();
		syslog(LOG_ERR, "Error attempting to read RSA key from file;"
		       " rc = [%d]\n", rc);
		goto out;
	}
	rc = RSA_private_decrypt(RSA_size(rsa), from, to,
				 rsa, RSA_PKCS1_OAEP_PADDING);
	if (rc == -1) {
		rc = -(int)ERR_get_error();
		syslog(LOG_ERR, "Error attempting to perform RSA public key "
		       "decryption; rc = [%d]\n", rc);
	} else {
		*decrypted_key_size = rc;
		rc = 0;
	}
out:
	RSA_free(rsa);
	return 0;
}

/* TODO: This seems a bit magical. Should it be a NULL mode in a
 * serialize function, perhaps? -Halcrow */
int get_pki_data_length(struct ecryptfs_name_val_pair *pair)
{
	size_t length;
	char *password;
	char *path;

	while (pair) {
		if (!pair->name)
			;
		else if (!strcmp(pair->name, "passphrase"))
			password = pair->value;
		else if (!strcmp(pair->name, "path"))
			path = pair->value;
		pair = pair->next;
	}
	if (path) 
		length = (strlen(path) + 3);
	else
		return -1;
	if (password)
		length += (strlen(password) + 3);
	else
		return -1;
	length += (2 + (ECRYPTFS_SIG_SIZE_HEX + 1));
	return length;
}

struct pki_nvp_map_elem {
	char *name;
	uint32_t flags;
};

static struct pki_nvp_map_elem pki_nvp_map[] = {
	{"path", ECRYPTFS_ECHO | ECRYPTFS_DEFAULT_VALUE_SET},
	{"passphrase", ECRYPTFS_MLOCK},
	{NULL, 0}
};

int generate_name_val_list(struct ecryptfs_name_val_pair *head)
{
	int i = 0;
	int rc = 0;
	struct stat buf;
	uid_t id = getuid();
	struct passwd *pw = getpwuid(id);

	while (pki_nvp_map[i].name) {
		head->next = malloc(sizeof(struct ecryptfs_name_val_pair));
		if (!head->next) {
			rc = -ENOMEM;
			goto out;
		}
		head = head->next;
		head->name = pki_nvp_map[i].name;
		head->flags = pki_nvp_map[i].flags;
		if (!strcmp(head->name, "path")) {
			asprintf(&head->value,
				 "%s/.ecryptfs/pki/openssl/key.pem",
				 pw->pw_dir);
			if (stat(head->value, &buf))
				head->flags &= ~ECRYPTFS_DEFAULT_VALUE_SET;
		}
		i++;
	}
	head->next = NULL;
out:
	return rc;
}

/* TODO: In general, we should be passing the head of the lists
 * around; the head's only job is to provide a pointer to the first
 * real element in the list. Please update this function to reflect
 * that. -Halcrow */
int set_pki_data(struct ecryptfs_name_val_pair *pair, unsigned char *data)
{
	size_t password_length;
	size_t path_length;
	char *password;
	char *path;
	size_t i = 0;

	while (pair) {
		if (!pair->name)
			;
		else if (!strcmp(pair->name, "passphrase"))
			password = pair->value;
		else if (!strcmp(pair->name, "path"))
			path = pair->value;
		pair = pair->next;
	}
	if (path) {
		path_length = strlen(path) + 1;
		data[i++] = path_length % 256;
		data[i++] = path_length >> 8;
		memcpy(&data[i], path, path_length);
		i += path_length;
	} else {
		return -1;
	}
	if (password) {
		password_length = strlen(password) + 1;
		data[i++] = password_length % 256;
		data[i++] = password_length >> 8;
		memcpy(&data[i], password, password_length);
		i += password_length;
	} else {
		return -1;
	}
	return 0;
}

static int ssl_sig(struct ecryptfs_pki_elem *pki, struct val_node **head)
{
	struct ecryptfs_name_val_pair *openssl_nvp;
	char *sig;
	char *param;
	int rc;

	pki->nvp_head.next = malloc(sizeof(struct ecryptfs_name_val_pair));
	if (!pki->nvp_head.next) {
		rc = -ENOMEM;
		goto out;
	}
	openssl_nvp = pki->nvp_head.next;
	openssl_nvp->name = "passphrase";
	stack_pop_val(head, (void *)&(openssl_nvp->value));
	openssl_nvp->next = malloc(sizeof(struct ecryptfs_name_val_pair));
	if (!openssl_nvp->next) {
		rc = -ENOMEM;
		goto out;
	}
	openssl_nvp = openssl_nvp->next;
	openssl_nvp->name = "path";
	stack_pop_val(head, (void *)&(openssl_nvp->value));
	openssl_nvp->next = NULL;
	sig = malloc(ECRYPTFS_SIG_SIZE_HEX + 1);
	if (!sig) {
		rc = -ENOMEM;
		goto out;
	}
	rc = add_public_key_key_to_keyring(sig, pki);
	if (rc < 0)
		goto out;
	else
		rc = 0;
	asprintf(&param, "ecryptfs_sig=%s", sig);
	free(sig);
	stack_push(head, param);
out:
	if (rc)
		return MOUNT_ERROR;
	return DEFAULT_TOK;
}

static int tf_ssl_file(struct ecryptfs_ctx *ctx, struct param_node *node,
		       struct val_node **head, void **foo)
{
	stack_push(head, node->val);
	node->val = NULL;
	return DEFAULT_TOK;
}

static int tf_ssl_passwd(struct ecryptfs_ctx *ctx, struct param_node *node,
			 struct val_node **head, void **foo)
{
	stack_push(head, node->val);
	node->val = NULL;
	return ssl_sig((struct ecryptfs_pki_elem *)*foo, head);
}

static int tf_ssl_passfile(struct ecryptfs_ctx *ctx, struct param_node *node,
			   struct val_node **head, void **foo)
{
	int rc = 0;
	char *tmp_val = NULL;
	int fd;
	struct ecryptfs_name_val_pair *file_head = NULL;
	struct ecryptfs_name_val_pair *walker = NULL;

	file_head = malloc(sizeof(struct ecryptfs_name_val_pair));
	if (!file_head) {
		rc = -ENOMEM;
		goto out;
	}
	memset(file_head, 0, sizeof(struct ecryptfs_name_val_pair));
	if (strcmp(node->mnt_opt_names[0], "passfile") == 0)
		fd = open(node->val, O_RDONLY);
	else if (strcmp(node->mnt_opt_names[0], "passfd") == 0)
		fd = strtol(node->val, NULL, 0);
	else {
		rc = MOUNT_ERROR;
		goto out;
	}
	rc = parse_options_file(fd, file_head);
	if (rc) {
		rc = MOUNT_ERROR;
		goto out;
	}
	close(fd);
	walker = file_head->next;
	while (walker) {
		if (strcmp(walker->name, "passwd") == 0) {
			asprintf(&tmp_val, "%s", walker->value);
			stack_push(head, tmp_val);
			break;
		}
		walker = walker->next;
	}
	if (!walker) {
		rc = MOUNT_ERROR;
		goto out;
	}
	free_name_val_pairs(file_head);
	file_head = NULL;
	walker = NULL;
	rc = ssl_sig((struct ecryptfs_pki_elem *)*foo, head);
out:
	free(node->val);
	node->val = NULL;
	return rc;
}

#define OPENSSL_TOK 0
#define SSL_FILE_TOK 1
#define SSL_PASSWD_TOK 2
#define SSL_PASS_FILE_TOK 3
#define SSL_PASS_ENV_TOK 4
#define SSL_PASS_FD_TOK 5
#define SSL_PASS_STDIN_TOK 6
#define SSL_DEFAULT_PASS_TOK 7
static struct param_node ssl_param_nodes[] = {
	{.num_mnt_opt_names = 1,
	 .mnt_opt_names = {"keyformat"},
	 .prompt = "Key format",
	 .val_type = VAL_STR,
	 .val = NULL,
	 .display_opts = NULL,
	 .default_val = "keyfile",
	 .flags = NO_VALUE,
	 .num_transitions = 1,
	 .tl = {{.val = "default",
		 .pretty_val = "OpenSSL Key File",
		 .next_token = &ssl_param_nodes[SSL_FILE_TOK],
		 .trans_func = NULL}}},

	{.num_mnt_opt_names = 1,
	 .mnt_opt_names = {"keyfile"},
	 .prompt = "SSL key file",
	 .val_type = VAL_STR,
	 .val = NULL,
	 .display_opts = NULL,
	 .default_val = NULL,
	 .flags = ECHO_INPUT,
	 .num_transitions = 6,
	 .tl = {{.val = "passwd",
		 .pretty_val = "",
		 .next_token = &ssl_param_nodes[SSL_PASSWD_TOK],
		 .trans_func = tf_ssl_file},
	 	{.val = "passfile",
		 .pretty_val = "Passphrase File",
		 .next_token = &ssl_param_nodes[SSL_PASS_FILE_TOK],
		 .trans_func = tf_ssl_file},
	 	{.val = "passenv",
		 .pretty_val = "Passphrase ENV",
		 .next_token = &ssl_param_nodes[SSL_PASS_ENV_TOK],
		 .trans_func = tf_ssl_file},
	 	{.val = "passfd",
		 .pretty_val = "Passphrase File Descriptor",
		 .next_token = &ssl_param_nodes[SSL_PASS_FD_TOK],
		 .trans_func = tf_ssl_file},
	 	{.val = "passstdin",
		 .pretty_val = "Passphrase STDIN",
		 .next_token = &ssl_param_nodes[SSL_PASS_STDIN_TOK],
		 .trans_func = tf_ssl_file},
	 	{.val = "default",
		 .pretty_val = "Passphrase",
		 .next_token = &ssl_param_nodes[SSL_DEFAULT_PASS_TOK],
		 .trans_func = tf_ssl_file}}},

	{.num_mnt_opt_names = 1,
	 .mnt_opt_names = {"passwd"},
	 .prompt = "Passphrase",
	 .val_type = VAL_STR,
	 .val = NULL,
	 .display_opts = NULL,
	 .default_val = NULL,
	 .flags = MASK_OUTPUT,
	 .num_transitions = 1,
	 .tl = {{.val = NULL,
		 .pretty_val = NULL,
		 .next_token = NULL,
		 .trans_func = tf_ssl_passwd}}},

	{.num_mnt_opt_names = 1,
	 .mnt_opt_names = {"passfile"},
	 .prompt = "Passphrase",
	 .val_type = VAL_STR,
	 .val = NULL,
	 .display_opts = NULL,
	 .default_val = NULL,
	 .flags = MASK_OUTPUT,
	 .num_transitions = 1,
	 .tl = {{.val = NULL,
		 .pretty_val = NULL,
		 .next_token = NULL,
		 .trans_func = tf_ssl_passfile}}},

	{.num_mnt_opt_names = 1,
	 .mnt_opt_names = {"passenv"},
	 .prompt = "Passphrase",
	 .val_type = VAL_STR,
	 .val = NULL,
	 .display_opts = NULL,
	 .default_val = NULL,
	 .flags = MASK_OUTPUT,
	 .num_transitions = 1,
	 .tl = {{.val = NULL,
		 .pretty_val = NULL,
		 .next_token = NULL,
		 .trans_func = tf_ssl_passwd}}},

	{.num_mnt_opt_names = 1,
	 .mnt_opt_names = {"passfd"},
	 .prompt = "Passphrase",
	 .val_type = VAL_STR,
	 .val = NULL,
	 .display_opts = NULL,
	 .default_val = NULL,
	 .flags = MASK_OUTPUT,
	 .num_transitions = 1,
	 .tl = {{.val = NULL,
		 .pretty_val = NULL,
		 .next_token = NULL,
		 .trans_func = tf_ssl_passfile}}},

	{.num_mnt_opt_names = 1,
	 .mnt_opt_names = {"passstdin"},
	 .prompt = "Passphrase",
	 .val_type = VAL_STR,
	 .val = NULL,
	 .display_opts = NULL,
	 .default_val = NULL,
	 .flags = VERIFY_VALUE | STDIN_REQUIRED,
	 .num_transitions = 1,
	 .tl = {{.val = NULL,
		 .pretty_val = NULL,
		 .next_token = NULL,
		 .trans_func = tf_ssl_passwd}}},

	{.num_mnt_opt_names = 1,
	 .mnt_opt_names = {"defaultpass"},
	 .prompt = "Passphrase",
	 .val_type = VAL_STR,
	 .val = NULL,
	 .display_opts = NULL,
	 .default_val = NULL,
	 .flags = VERIFY_VALUE | STDIN_REQUIRED,
	 .num_transitions = 1,
	 .tl = {{.val = NULL,
		 .pretty_val = NULL,
		 .next_token = NULL,
		 .trans_func = tf_ssl_passwd}}},
};

static int tf_openssl_enter(struct ecryptfs_ctx *ctx, struct param_node *node,
			    struct val_node **head, void **foo)
{
	int rc;
	rc = ecryptfs_find_pki(ctx, node->val,
			       (struct ecryptfs_pki_elem **)foo);
	return rc;
}

struct transition_node openssl_transition = {
	.val = "openssl",
	.pretty_val = "OpenSSL module",
	.next_token = &(ssl_param_nodes[0]),
	.trans_func = tf_openssl_enter
};

static int get_param_subgraph_trans_node(struct transition_node **trans,
					 uint32_t version)
{
	if ((version & ECRYPTFS_VERSIONING_PUBKEY) == 0)
		return -1;
	*trans = &openssl_transition;
	return 0;
}

int init_pki(char **pki_name, struct ecryptfs_pki_elem *pki)
{
	int rc;

	rc = generate_name_val_list(&pki->nvp_head);
	if (rc)
		goto out;
	if (asprintf(pki_name, "openssl") == -1) {
		ecryptfs_syslog(LOG_ERR, "Out of memory\n");
		goto out;
	}
	struct ecryptfs_pki_ops ops = {
		&set_key_data,
		&ecryptfs_generate_key,
		&ecryptfs_encrypt,
		&decrypt,
		&get_pki_data_length,
		&set_pki_data,
		&get_param_subgraph_trans_node
	};
	memcpy(&pki->ops, &ops, sizeof(struct ecryptfs_pki_ops));
out:
	return 0;
}
