//  gnoMint: a graphical interface for managing a certification authority
//  Copyright (C) 2006-2009 David Marín Carreño <davefx@gmail.com>
//
//  This file is part of gnoMint.
//
//  gnoMint 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 3 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

#ifndef GNOMINTCLI

#include <config.h>

#include <glib-object.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#endif

#include <glib.h>
#include <gcrypt.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include "tls.h"
#include "ca_file.h"
#include "dialog.h"
#include "pkey_manage.h"

#include <glib/gi18n.h>

#define PKEY_MANAGE_ENCRYPTED_PKCS8_HEADER "-----BEGIN ENCRYPTED PRIVATE KEY-----"
#define PKEY_MANAGE_UNCRYPTED_PKCS8_HEADER "-----BEGIN PRIVATE KEY-----"

typedef void (*__PkeyExternalFilePasswordCallback)(gchar *password, gpointer user_data);
typedef void (*__PkeyRetrieveFromFileCallback)(gchar *pem_pkey, gchar *final_filename,
                                                gboolean save_new_filename, gpointer user_data);

#ifdef GNOMINTCLI
gchar * __pkey_manage_ask_external_file_password (const gchar *cert_dn);
gchar * __pkey_retrieve_from_file (gchar **fn, gchar *cert_pem);
#else
void __pkey_manage_ask_external_file_password (const gchar *cert_dn,
                                               __PkeyExternalFilePasswordCallback cb,
                                               gpointer user_data);
void __pkey_retrieve_from_file (gchar **fn, gchar *cert_pem,
                                __PkeyRetrieveFromFileCallback cb,
                                gpointer user_data);
#endif
gchar * __pkey_manage_to_hex (const guchar *buffer, size_t len);
guchar * __pkey_manage_from_hex (const gchar *input);
guchar *__pkey_manage_create_key(const gchar *password);
gchar * __pkey_manage_aes_encrypt (const gchar *in, const gchar *password);
gchar * __pkey_manage_aes_decrypt (const gchar *string, const gchar *password);
gchar * __pkey_manage_aes_encrypt_aux (const gchar *in, const gchar *password, const guchar *iv, const guchar *ctr);
gchar * __pkey_manage_aes_decrypt_aux (const gchar *string, const gchar *password, const guchar *iv, const guchar *ctr);

#ifndef GNOMINTCLI

// CALLBACKS
G_MODULE_EXPORT gboolean pkey_manage_filechooser_file_set_cb (GtkWidget *widget, gpointer user_data);

typedef struct {
	GtkBuilder *dialog_gtkb;
	GObject *password_widget;
	gchar *message;
	__PkeyExternalFilePasswordCallback cb;
	gpointer user_data;
} __AskExternalFilePasswordCtx;

static void
__pkey_manage_ask_external_file_password_response (GtkDialog *dialog,
                                                    gint response_id,
                                                    gpointer data)
{
	__AskExternalFilePasswordCtx *ctx = (__AskExternalFilePasswordCtx *) data;
	gchar *password = NULL;

	if (response_id == GTK_RESPONSE_OK || response_id == GTK_RESPONSE_ACCEPT ||
	    response_id == GTK_RESPONSE_YES || response_id > 0) {
		password = g_strdup ((gchar *) gtk_editable_get_text (GTK_EDITABLE (ctx->password_widget)));
	}

	gtk_window_destroy (GTK_WINDOW (dialog));
	g_object_unref (G_OBJECT (ctx->dialog_gtkb));
	g_free (ctx->message);

	ctx->cb (password, ctx->user_data);
	g_free (ctx);
}

void __pkey_manage_ask_external_file_password (const gchar *cert_dn,
                                               __PkeyExternalFilePasswordCallback cb,
                                               gpointer user_data)
{
	GObject * widget = NULL, * password_widget = NULL, *remember_password_widget = NULL;
	GtkBuilder * dialog_gtkb = NULL;
	gchar *message = NULL;
	__AskExternalFilePasswordCtx *ctx;

	dialog_gtkb = gtk_builder_new();
	gtk_builder_add_from_file (dialog_gtkb,
				   g_build_filename (PACKAGE_DATA_DIR, "gnomint", "get_db_password_dialog.ui", NULL),
				   NULL);

	password_widget = gtk_builder_get_object (dialog_gtkb, "cadb_password_entry");

	remember_password_widget = gtk_builder_get_object (dialog_gtkb, "remember_password_checkbutton");
	g_object_set (G_OBJECT(remember_password_widget), "visible", FALSE, NULL);

	widget = gtk_builder_get_object (dialog_gtkb, "get_passwd_msg_label");

	message = g_strdup_printf (_("The file that holds private key for certificate '%s' is password-protected.\n\n"
				     "Please, insert the password corresponding to this file."), cert_dn);
	gtk_label_set_text (GTK_LABEL(widget), message);

	gtk_widget_grab_focus (GTK_WIDGET(password_widget));

	ctx = g_new0 (__AskExternalFilePasswordCtx, 1);
	ctx->dialog_gtkb = dialog_gtkb;
	ctx->password_widget = password_widget;
	ctx->message = message;
	ctx->cb = cb;
	ctx->user_data = user_data;

	widget = gtk_builder_get_object (dialog_gtkb, "get_db_password_dialog");
	g_signal_connect (widget, "response",
	                  G_CALLBACK (__pkey_manage_ask_external_file_password_response), ctx);
	gtk_widget_set_visible (GTK_WIDGET (widget), TRUE);
	gtk_window_set_transient_for (GTK_WINDOW (widget), dialog_get_main_window ());
	gtk_window_present (GTK_WINDOW (widget));
}


gboolean pkey_manage_filechooser_file_set_cb (GtkWidget *widget, gpointer user_data)
{
	GtkWidget *remember_filepath_widget = NULL;

	remember_filepath_widget = g_object_get_data (G_OBJECT(widget), "save_filename_checkbutton");
	g_object_set (G_OBJECT(remember_filepath_widget), "visible", TRUE, NULL);

	return FALSE;
}

#else
gchar * __pkey_manage_ask_external_file_password (const gchar *cert_dn)
{
	gchar *password;
	gchar *aux = NULL;


	printf (_("The file that holds private key for certificate\n'%s' is password-protected.\n\n"), cert_dn);

	aux = getpass ("Please, insert the password corresponding to this file:");
	
	if (!aux || aux[0] == '\0') {
		return NULL;
	} else {
		password = g_strdup (aux);
		memset (aux, 0, strlen(aux));
	}
	
	return password;
}

#endif

#ifdef GNOMINTCLI

gchar * __pkey_retrieve_from_file (gchar **fn, gchar *cert_pem)
{
	gsize file_length = 0;
	GError *error = NULL;
	gboolean cancel = FALSE;

	gboolean save_new_filename = FALSE;
	gchar *file_name = g_strdup(* fn);

	gchar *file_contents = NULL;

	gchar *pem_pkey = NULL;

	gint tls_error = 0;
	gchar *password = NULL;

	TlsCert *cert = tls_parse_cert_pem (cert_pem);

	do {
		if (g_file_test(file_name, G_FILE_TEST_EXISTS)) {
			GIOChannel *gc = g_io_channel_new_file (file_name, "r", &error);
			if (gc) {
				g_io_channel_read_to_end (gc, &file_contents, &file_length, &error);
				g_io_channel_shutdown (gc, TRUE, NULL);

				do {
					pem_pkey = tls_load_pkcs8_private_key (file_contents, password, cert->key_id, &tls_error);

					if (tls_error == TLS_INVALID_PASSWORD) {
						if (password)
							dialog_error (_("The given password doesn't match with the one used while crypting the file."));

						// We ask for a password
						password = __pkey_manage_ask_external_file_password (cert->dn);

						if (! password)
							cancel = TRUE;
					}

				} while (tls_error == TLS_INVALID_PASSWORD && ! cancel);

				g_free (password);

				if (! pem_pkey) {
					if (tls_error == TLS_NON_MATCHING_PRIVATE_KEY) {
						dialog_error (_("The file designated in database contains a private key, but it "
								   "is not the private key corresponding to the certificate."));
					} else {
						dialog_error (_("The file designated in database doesn't contain any recognized private key."));
					}
				}
			} else {
				dialog_error (_("The file designated in database couldn't be opened."));
			}
		} else {
			dialog_error (_("The file designated in database doesn't exist."));
		}

		if (! pem_pkey && ! cancel) {
                        cancel = TRUE;
		}
	} while (! pem_pkey && ! cancel);

	tls_cert_free (cert);
	g_free (file_contents);
	if (error)
		g_error_free (error);

	if (cancel) {
		g_free (file_name);
		return NULL;
	}

	if (save_new_filename) {
		g_free (*fn);
		(* fn) = file_name;
	}

	return pem_pkey;
}

#else /* GUI async __pkey_retrieve_from_file */

typedef struct {
	gchar *file_name;
	gchar *file_contents;
	gsize file_length;
	gchar *cert_pem;
	TlsCert *cert;
	gchar *password;
	gboolean save_new_filename;
	__PkeyRetrieveFromFileCallback cb;
	gpointer user_data;
} __RetrieveFromFileCtx;

static void __pkey_retrieve_try_file (__RetrieveFromFileCtx *ctx);

static void
__pkey_retrieve_external_password_cb (gchar *password, gpointer data)
{
	__RetrieveFromFileCtx *ctx = (__RetrieveFromFileCtx *) data;
	gchar *pem_pkey = NULL;
	gint tls_error = 0;

	if (!password) {
		/* User cancelled the password dialog */
		tls_cert_free (ctx->cert);
		g_free (ctx->file_contents);
		g_free (ctx->file_name);
		g_free (ctx->password);
		ctx->cb (NULL, NULL, FALSE, ctx->user_data);
		g_free (ctx);
		return;
	}

	g_free (ctx->password);
	ctx->password = password;

	pem_pkey = tls_load_pkcs8_private_key (ctx->file_contents, ctx->password,
	                                        ctx->cert->key_id, &tls_error);

	if (tls_error == TLS_INVALID_PASSWORD) {
		dialog_error (_("The given password doesn't match with the one used while crypting the file."));
		/* Re-ask for password */
		__pkey_manage_ask_external_file_password (ctx->cert->dn,
		                                          __pkey_retrieve_external_password_cb, ctx);
		return;
	}

	g_free (ctx->password);
	ctx->password = NULL;

	if (!pem_pkey) {
		if (tls_error == TLS_NON_MATCHING_PRIVATE_KEY) {
			dialog_error (_("The file designated in database contains a private key, but it "
					   "is not the private key corresponding to the certificate."));
		} else {
			dialog_error (_("The file designated in database doesn't contain any recognized private key."));
		}
		/* Show file chooser to pick a different file */
		g_free (ctx->file_contents);
		ctx->file_contents = NULL;
		__pkey_retrieve_try_file (ctx);
		return;
	}

	/* Success */
	{
		gchar *final_filename = g_strdup (ctx->file_name);
		gboolean save = ctx->save_new_filename;
		tls_cert_free (ctx->cert);
		g_free (ctx->file_contents);
		g_free (ctx->file_name);
		ctx->cb (pem_pkey, final_filename, save, ctx->user_data);
		g_free (ctx);
	}
}

static void
__pkey_browse_open_cb (GObject *source, GAsyncResult *result, gpointer user_data)
{
	GtkButton *btn = GTK_BUTTON (user_data);
	GtkFileDialog *fd = GTK_FILE_DIALOG (source);
	GError *err = NULL;
	GFile *gfile = gtk_file_dialog_open_finish (fd, result, &err);
	if (!gfile) {
		g_clear_error (&err);
		return;
	}
	gchar *path = g_file_get_path (gfile);
	g_object_unref (gfile);
	gtk_button_set_label (btn, path);
	g_object_set_data_full (G_OBJECT (btn), "selected-path", path, g_free);
}

static void
__pkey_browse_clicked (GtkButton *btn, gpointer user_data G_GNUC_UNUSED)
{
	GtkFileDialog *fd = gtk_file_dialog_new ();
	gtk_file_dialog_set_title (fd, _("Select private key file"));
	const gchar *cur = (const gchar *) g_object_get_data (G_OBJECT (btn), "selected-path");
	if (cur && cur[0]) {
		GFile *f = g_file_new_for_path (cur);
		GFile *folder = g_file_get_parent (f);
		if (folder) {
			gtk_file_dialog_set_initial_folder (fd, folder);
			g_object_unref (folder);
		}
		g_object_unref (f);
	}
	gtk_file_dialog_open (fd, GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (btn))),
	                      NULL, __pkey_browse_open_cb, btn);
	g_object_unref (fd);
}

static void
__pkey_retrieve_filechooser_response (GtkDialog *dialog, gint response_id, gpointer data)
{
	__RetrieveFromFileCtx *ctx = (__RetrieveFromFileCtx *) data;
	GtkBuilder *dialog_gtkb = g_object_get_data (G_OBJECT (dialog), "dialog_gtkb");
	GObject *filepath_widget = gtk_builder_get_object (dialog_gtkb, "pkey_filechooser");
	GObject *remember_filepath_widget = gtk_builder_get_object (dialog_gtkb, "save_filename_checkbutton");

	if (response_id != GTK_RESPONSE_OK && response_id != GTK_RESPONSE_ACCEPT &&
	    response_id != GTK_RESPONSE_YES && response_id <= 0) {
		/* Cancel */
		gtk_window_destroy (GTK_WINDOW (dialog));
		g_object_unref (G_OBJECT (dialog_gtkb));
		tls_cert_free (ctx->cert);
		g_free (ctx->file_contents);
		g_free (ctx->file_name);
		g_free (ctx->password);
		ctx->cb (NULL, NULL, FALSE, ctx->user_data);
		g_free (ctx);
		return;
	}

	const gchar *selected = (const gchar *) g_object_get_data (
	    G_OBJECT (filepath_widget), "selected-path");
	if (selected && selected[0]) {
		g_free (ctx->file_name);
		ctx->file_name = g_strdup (selected);
	}
	ctx->save_new_filename = gtk_check_button_get_active (
		GTK_CHECK_BUTTON (remember_filepath_widget));

	gtk_window_destroy (GTK_WINDOW (dialog));
	g_object_unref (G_OBJECT (dialog_gtkb));

	/* Retry with the new file */
	g_free (ctx->file_contents);
	ctx->file_contents = NULL;
	g_free (ctx->password);
	ctx->password = NULL;
	__pkey_retrieve_try_file (ctx);
}

static void
__pkey_retrieve_show_filechooser (__RetrieveFromFileCtx *ctx)
{
	GObject *widget = NULL, *filepath_widget = NULL, *remember_filepath_widget = NULL;
	GtkBuilder *dialog_gtkb = NULL;

	dialog_gtkb = gtk_builder_new ();
	gtk_builder_add_from_file (dialog_gtkb,
				   g_build_filename (PACKAGE_DATA_DIR, "gnomint", "get_pkey_dialog.ui", NULL),
				   NULL);

	filepath_widget = gtk_builder_get_object (dialog_gtkb, "pkey_filechooser");
	gtk_button_set_label (GTK_BUTTON (filepath_widget), ctx->file_name);
	g_object_set_data_full (G_OBJECT (filepath_widget), "selected-path",
	                        g_strdup (ctx->file_name), g_free);
	g_signal_connect (filepath_widget, "clicked",
	                  G_CALLBACK (__pkey_browse_clicked), NULL);

	remember_filepath_widget = gtk_builder_get_object (dialog_gtkb, "save_filename_checkbutton");
	g_object_set (G_OBJECT (remember_filepath_widget), "visible", FALSE, NULL);

	widget = gtk_builder_get_object (dialog_gtkb, "cert_dn_label");
	gtk_label_set_text (GTK_LABEL (widget), ctx->cert->dn);

	widget = gtk_builder_get_object (dialog_gtkb, "get_pkey_dialog");
	g_object_set_data (G_OBJECT (widget), "dialog_gtkb", dialog_gtkb);
	g_signal_connect (widget, "response",
	                  G_CALLBACK (__pkey_retrieve_filechooser_response), ctx);
	gtk_widget_set_visible (GTK_WIDGET (widget), TRUE);
	gtk_window_set_transient_for (GTK_WINDOW (widget), dialog_get_main_window ());
	gtk_window_present (GTK_WINDOW (widget));
}

static void
__pkey_retrieve_try_file (__RetrieveFromFileCtx *ctx)
{
	GError *error = NULL;
	gchar *pem_pkey = NULL;
	gint tls_error = 0;

	if (g_file_test (ctx->file_name, G_FILE_TEST_EXISTS)) {
		GIOChannel *gc = g_io_channel_new_file (ctx->file_name, "r", &error);
		if (gc) {
			g_io_channel_read_to_end (gc, &ctx->file_contents, &ctx->file_length, &error);
			g_io_channel_shutdown (gc, TRUE, NULL);

			pem_pkey = tls_load_pkcs8_private_key (ctx->file_contents, NULL,
			                                        ctx->cert->key_id, &tls_error);

			if (tls_error == TLS_INVALID_PASSWORD) {
				/* Need password -- ask asynchronously */
				__pkey_manage_ask_external_file_password (ctx->cert->dn,
				                                          __pkey_retrieve_external_password_cb, ctx);
				return;
			}

			if (!pem_pkey) {
				if (tls_error == TLS_NON_MATCHING_PRIVATE_KEY) {
					dialog_error (_("The file designated in database contains a private key, but it "
							   "is not the private key corresponding to the certificate."));
				} else {
					dialog_error (_("The file designated in database doesn't contain any recognized private key."));
				}
				/* Show file chooser */
				g_free (ctx->file_contents);
				ctx->file_contents = NULL;
				__pkey_retrieve_show_filechooser (ctx);
				return;
			}

			/* Success */
			{
				gchar *final_filename = g_strdup (ctx->file_name);
				gboolean save = ctx->save_new_filename;
				tls_cert_free (ctx->cert);
				g_free (ctx->file_contents);
				g_free (ctx->file_name);
				ctx->cb (pem_pkey, final_filename, save, ctx->user_data);
				g_free (ctx);
			}
			return;
		} else {
			dialog_error (_("The file designated in database couldn't be opened."));
		}
	} else {
		dialog_error (_("The file designated in database doesn't exist."));
	}

	if (error)
		g_error_free (error);

	/* File didn't exist or couldn't be opened -- show file chooser */
	__pkey_retrieve_show_filechooser (ctx);
}

void __pkey_retrieve_from_file (gchar **fn, gchar *cert_pem,
                                __PkeyRetrieveFromFileCallback cb,
                                gpointer user_data)
{
	__RetrieveFromFileCtx *ctx = g_new0 (__RetrieveFromFileCtx, 1);

	ctx->file_name = g_strdup (*fn);
	ctx->cert_pem = cert_pem;
	ctx->cert = tls_parse_cert_pem (cert_pem);
	ctx->cb = cb;
	ctx->user_data = user_data;

	__pkey_retrieve_try_file (ctx);
}

#endif /* GNOMINTCLI */


#ifdef GNOMINTCLI

PkeyManageData * pkey_manage_get_certificate_pkey (guint64 id)
{
	PkeyManageData *res = NULL;

	res = g_new0 (PkeyManageData, 1);

	if (ca_file_get_pkey_in_db_from_id (CA_FILE_ELEMENT_TYPE_CERT, id)) {
		res->pkey_data = ca_file_get_pkey_field_from_id (CA_FILE_ELEMENT_TYPE_CERT, id);
		res->is_in_db = TRUE;
		res->is_ciphered_with_db_pwd = ca_file_is_password_protected();
	} else {
		gchar *cert_pem = ca_file_get_public_pem_from_id (CA_FILE_ELEMENT_TYPE_CERT, id);
		gchar *file_name = ca_file_get_pkey_field_from_id (CA_FILE_ELEMENT_TYPE_CERT, id);
		gchar *old_filename = g_strdup (file_name);

		gchar *file_contents = __pkey_retrieve_from_file (&file_name, cert_pem);

		if (strcmp (file_name, old_filename)) {
			ca_file_set_pkey_field_for_id (CA_FILE_ELEMENT_TYPE_CERT, file_name, id);
		}

		if (file_contents) {
			res->pkey_data = file_contents;
			res->is_in_db = FALSE;
			res->external_file = file_name;
		} else {
			g_free (file_name);
			g_free (res);
			res = NULL;
		}

		g_free (old_filename);
		g_free (cert_pem);
	}

	return res;
}

#else /* GUI async pkey_manage_get_certificate_pkey */

typedef struct {
	guint64 id;
	gchar *old_filename;
	PkeyManageGetPkeyCallback cb;
	gpointer user_data;
} __GetCertPkeyCtx;

static void
__get_cert_pkey_retrieve_cb (gchar *pem_pkey, gchar *final_filename,
                             gboolean save_new_filename, gpointer data)
{
	__GetCertPkeyCtx *ctx = (__GetCertPkeyCtx *) data;
	PkeyManageData *res = NULL;

	if (pem_pkey) {
		res = g_new0 (PkeyManageData, 1);
		res->pkey_data = pem_pkey;
		res->is_in_db = FALSE;
		if (final_filename) {
			res->external_file = final_filename;
			if (save_new_filename && strcmp (final_filename, ctx->old_filename)) {
				ca_file_set_pkey_field_for_id (CA_FILE_ELEMENT_TYPE_CERT,
				                               final_filename, ctx->id);
			}
		}
	} else {
		g_free (final_filename);
	}

	g_free (ctx->old_filename);
	ctx->cb (res, ctx->user_data);
	g_free (ctx);
}

void pkey_manage_get_certificate_pkey (guint64 id,
                                       PkeyManageGetPkeyCallback cb,
                                       gpointer user_data)
{
	if (ca_file_get_pkey_in_db_from_id (CA_FILE_ELEMENT_TYPE_CERT, id)) {
		PkeyManageData *res = g_new0 (PkeyManageData, 1);
		res->pkey_data = ca_file_get_pkey_field_from_id (CA_FILE_ELEMENT_TYPE_CERT, id);
		res->is_in_db = TRUE;
		res->is_ciphered_with_db_pwd = ca_file_is_password_protected();
		cb (res, user_data);
	} else {
		gchar *cert_pem = ca_file_get_public_pem_from_id (CA_FILE_ELEMENT_TYPE_CERT, id);
		gchar *file_name = ca_file_get_pkey_field_from_id (CA_FILE_ELEMENT_TYPE_CERT, id);

		__GetCertPkeyCtx *ctx = g_new0 (__GetCertPkeyCtx, 1);
		ctx->id = id;
		ctx->old_filename = g_strdup (file_name);
		ctx->cb = cb;
		ctx->user_data = user_data;

		__pkey_retrieve_from_file (&file_name, cert_pem,
		                           __get_cert_pkey_retrieve_cb, ctx);
		g_free (file_name);
		g_free (cert_pem);
	}
}

#endif /* GNOMINTCLI */

PkeyManageData * pkey_manage_get_csr_pkey (guint64 id)
{
	PkeyManageData *res = NULL;

	res = g_new0 (PkeyManageData, 1);
	
	if (ca_file_get_pkey_in_db_from_id (CA_FILE_ELEMENT_TYPE_CSR, id)) {
		res->pkey_data = ca_file_get_pkey_field_from_id (CA_FILE_ELEMENT_TYPE_CSR, id);
		res->is_in_db = TRUE;
		res->is_ciphered_with_db_pwd = ca_file_is_password_protected();
	} else {
		// Retrieving external private keys for CSRs is not supported, as it is impossible to check if 
		// a private key corresponds to a public key/CSR.
                // However, we fill the structure with enough data for recovering the private key.
                res->pkey_data = NULL;
                res->is_in_db = FALSE;
                res->external_file = ca_file_get_pkey_field_from_id (CA_FILE_ELEMENT_TYPE_CSR, id);
	}

	return res;
}

void pkey_manage_data_free (PkeyManageData *pkeydata)
{
	if (! pkeydata)
		return;

	g_free (pkeydata->pkey_data);
	g_free (pkeydata->external_file);
}








guchar old_iv[16] =
{ 'a', 'g', 'z', 'e', 'Q', '5', 'E', '7', 'c', '+', '*', 'G', '1', 'D',
  'u', '='
};

guchar old_ctr[16] =
{ 'd', 'g', '4', 'e', 'J', '5', '3', 'l', 'c', '-', '!', 'G', 'z', 'A',
  'z', '='
};

gchar *saved_password = NULL;


gchar * __pkey_manage_to_hex (const guchar *buffer, size_t len)
{
	gchar *res = g_new0 (gchar, len*2+1);
	guint i;

	for (i=0; i<len; i++) {
		sprintf (&res[i*2], "%02X", buffer[i]);
	}
	
	return res;
}

guchar * __pkey_manage_from_hex (const gchar *input)
{
	guint i;

	guchar *res = g_new0 (guchar, (strlen(input)/2) + 1);
	
	for (i=0; i<strlen(input); i++) {
		res[i/2] = res[i/2] << 4;
		if (input[i] >= '0' && input[i] <= '9') {
			res[i/2] += (input[i] - '0');
		}
		if (input[i] >='A' && input[i] <= 'F') {
			res[i/2] += 10 + (input[i] - 'A');
		}
	}

	return res;

}

guchar *__pkey_manage_create_key(const gchar *password)
{
	guchar *key = g_new0 (guchar, 33);
	guint i, j;

	if (strlen(password) <= 32) {

		for (i=0; i<32; i=i+strlen(password)) {
			snprintf ((gchar *) &key[i], 
				  32 - i, "%s", password);
		}
	} else {
		i = 0;
		do {
			for (j=0; j<32 && j<strlen(&password[i]); j++) {
				key[j] = key[j] ^ (password[i+j]);
			}
			i += 32;
		} while (i<strlen(password));
		
	}

	return key;
}

gchar * __pkey_manage_aes_encrypt (const gchar *in, const gchar *password)
{
	gchar * result = NULL;

	gint i;
	guint in_length = strlen (in);
	guchar * random_data = NULL;
	guchar * sha256 = NULL;
	gchar * sha256_hex = NULL;

	gchar * cyphered = NULL;

	random_data = g_new0 (guchar, in_length);

	// Fill random_data with random data
	gcry_create_nonce (random_data, in_length);

	// XOR random_data with in => random_data
	for (i=0; i<in_length; i++) {
		random_data[i] = random_data[i] ^ in[i];
	}

	// SHA-2 256 random_data
	sha256 = g_new0 (guchar, gcry_md_get_algo_dlen(GCRY_MD_SHA256));
	gcry_md_hash_buffer (GCRY_MD_SHA256, sha256, random_data, in_length);
	
	sha256_hex = __pkey_manage_to_hex (sha256, 32);

	cyphered = __pkey_manage_aes_encrypt_aux (in, password, &sha256[0], &sha256[16]);
	
	result = g_strdup_printf ("gCP%s%s", sha256_hex, cyphered);

	g_free (sha256_hex);
	g_free (cyphered);

	g_free (sha256);
	g_free (random_data);

	return result;
	
}


gchar * __pkey_manage_aes_encrypt_aux (const gchar *in, const gchar *password, const guchar *iv, const guchar *ctr)
{
	guchar *key = __pkey_manage_create_key (password);
	guchar *out = (guchar *) g_strdup(in);
	gchar *res;
	gcry_error_t get;

	gcry_cipher_hd_t cry_ctxt;

	get = gcry_cipher_open (&cry_ctxt, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, 0);
	if (get) {
		fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
		return NULL;
	}

	if (! iv || !ctr) {

		get = gcry_cipher_setiv(cry_ctxt, &old_iv, 16);
		if (get) {
			fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
			return NULL;
		}

		if (! ctr)
			ctr = old_ctr;

		get = gcry_cipher_setctr(cry_ctxt, &old_ctr, 16);
		if (get) {
			fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
			return NULL;
		}

	} else {
		get = gcry_cipher_setiv(cry_ctxt, iv, 16);
		if (get) {
			fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
			return NULL;
		}

		if (! ctr)
			ctr = old_ctr;

		get = gcry_cipher_setctr(cry_ctxt, ctr, 16);
		if (get) {
			fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
			return NULL;
		}
	}
	get = gcry_cipher_setkey (cry_ctxt, key, 32);
	if (get) {
		fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
		return NULL;
	}

	get = gcry_cipher_encrypt(cry_ctxt, out, strlen(in), NULL, 0);	
	if (get) {
		fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
		return NULL;
	}

	gcry_cipher_close (cry_ctxt);	

	res = __pkey_manage_to_hex (out, strlen(in));
	
	g_free (out);

	return res;
}

gchar * __pkey_manage_aes_decrypt (const gchar *string, const gchar *password)
{
	gchar iv_hex[33];
	gchar ctr_hex[33];
	const gchar *ciphered = NULL;
	guchar *iv;
	guchar *ctr;

	gchar * result;
	
	// If it is not a new-type ciphered message, decrypt it with old static iv and ctr
	if (strncmp (string, "gCP", 3)) {
		return __pkey_manage_aes_decrypt_aux (string, password, NULL, NULL);
	}

	// It's a new-type ciphered message. 
	strncpy (iv_hex, &string[3], 32);
	strncpy (ctr_hex, &string[35], 32);
	ciphered = &string[67];

	iv = __pkey_manage_from_hex (iv_hex);
	ctr = __pkey_manage_from_hex (ctr_hex);

	result = __pkey_manage_aes_decrypt_aux (ciphered, password, iv, ctr);

	g_free (iv);
	g_free (ctr);
	
	return result;
}

gchar * __pkey_manage_aes_decrypt_aux (const gchar *string, const gchar *password, const guchar *iv, const guchar *ctr)
{
	guchar *out = __pkey_manage_from_hex(string);

	guchar *key = __pkey_manage_create_key (password);

	gcry_cipher_hd_t cry_ctxt;
	gcry_error_t get;

	get = gcry_cipher_open (&cry_ctxt, GCRY_CIPHER_AES256, GCRY_CIPHER_MODE_CTR, 0);
	if (get) {
		fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
		return NULL;
	}

	if (! iv || !ctr) {

		get = gcry_cipher_setiv(cry_ctxt, &old_iv, 16);
		if (get) {
			fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
			return NULL;
		}

		if (! ctr)
			ctr = old_ctr;

		get = gcry_cipher_setctr(cry_ctxt, &old_ctr, 16);
		if (get) {
			fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
			return NULL;
		}

	} else {
		get = gcry_cipher_setiv(cry_ctxt, iv, 16);
		if (get) {
			fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
			return NULL;
		}

		if (! ctr)
			ctr = old_ctr;

		get = gcry_cipher_setctr(cry_ctxt, ctr, 16);
		if (get) {
			fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
			return NULL;
		}
	}

	get = gcry_cipher_setkey (cry_ctxt, key, 32);
	if (get) {
		fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
		return NULL;
	}

	get = gcry_cipher_decrypt(cry_ctxt, out, strlen(string)/2, NULL, 0);	
	if (get) {
		fprintf (stderr, "ERR GCRYPT: %ud\n", gcry_err_code(get));
		return NULL;
	}

	gcry_cipher_close (cry_ctxt);	

	return (gchar *) out;
}

gchar *pkey_manage_encrypt_password (const gchar *pwd)
{
	gchar *password = NULL;
	gchar *res1, *res2;

	gchar salt[3];

#ifndef WIN32
	salt[0]= 32 + (random() % 140);
	salt[1]= 32 + (random() % 140);
	salt[2]=0;
#else
	salt[0]= 32 + (rand() % 140);
	salt[1]= 32 + (rand() % 140);
	salt[2]=0;
#endif

	password = g_strdup_printf ("%sgnoMintPassword%s", salt, pwd);

	res1 = __pkey_manage_aes_encrypt (password, password);
	res2 = g_strdup_printf ("%s%s", salt, res1);

	g_free (res1);
	g_free (password);

	return res2;
}

gboolean pkey_manage_check_password (const gchar *checking_password, const gchar *hashed_password) 
{
	
	gchar salt[3];
	gchar *password = NULL;
	gchar *cp;
	gboolean res;

	salt[0] = hashed_password[0];
	salt[1] = hashed_password[1];
	salt[2] = 0;

	password = g_strdup_printf ("%sgnoMintPassword%s", salt, checking_password);

	if (strncmp (&hashed_password[2], "gCP", 3)) {
		cp = __pkey_manage_aes_encrypt_aux (password, password, NULL, NULL);
		res = (! strcmp(cp, &hashed_password[2]));
		
		g_free (cp);
		g_free (password);
		
		return res;		
	} 

	cp = __pkey_manage_aes_decrypt (&hashed_password[2], password);

	res = (! strcmp(cp, password));

	g_free (cp);
	g_free (password);

	return res;
}

void pkey_manage_crypt_auto (gchar *password,
			     gchar **pem_private_key,
			     const gchar *pem_ca_certificate)
{
	gchar *clean_private_key = *pem_private_key;
	gchar *res = NULL;
	TlsCert *tls_cert = NULL;

	tls_cert = tls_parse_cert_pem (pem_ca_certificate);

	res = pkey_manage_crypt_w_pwd (clean_private_key, tls_cert->dn, password);

	tls_cert_free (tls_cert);

	*pem_private_key = res;

	g_free (clean_private_key);

	return;
}

#ifndef GNOMINTCLI

typedef struct {
	GtkBuilder *dialog_gtkb;
	GObject *password_widget;
	GObject *remember_password_widget;
	PkeyManagePasswordCallback cb;
	gpointer user_data;
} __AskPasswordCtx;

static void
__pkey_manage_ask_password_response (GtkDialog *dialog,
                                      gint response_id,
                                      gpointer data)
{
	__AskPasswordCtx *ctx = (__AskPasswordCtx *) data;
	gchar *password = NULL;
	gboolean remember = FALSE;

	if (response_id != GTK_RESPONSE_OK && response_id != GTK_RESPONSE_ACCEPT &&
	    response_id != GTK_RESPONSE_YES && response_id <= 0) {
		/* Cancel */
		gtk_window_destroy (GTK_WINDOW (dialog));
		g_object_unref (G_OBJECT (ctx->dialog_gtkb));
		ctx->cb (NULL, ctx->user_data);
		g_free (ctx);
		return;
	}

	password = g_strdup ((gchar *) gtk_editable_get_text (GTK_EDITABLE (ctx->password_widget)));
	remember = gtk_check_button_get_active (GTK_CHECK_BUTTON (ctx->remember_password_widget));

	if (! ca_file_check_password (password)) {
		dialog_error (_("The given password doesn't match the one used in the database"));
		g_free (password);
		/* Clear and re-present the dialog for another attempt */
		gtk_editable_set_text (GTK_EDITABLE (ctx->password_widget), "");
		gtk_widget_grab_focus (GTK_WIDGET (ctx->password_widget));
		return;
	}

	/* Password correct */
	if (remember) {
		if (saved_password)
			g_free (saved_password);
		saved_password = g_strdup (password);
	}

	gtk_window_destroy (GTK_WINDOW (dialog));
	g_object_unref (G_OBJECT (ctx->dialog_gtkb));

	ctx->cb (password, ctx->user_data);
	g_free (ctx);
}

void pkey_manage_ask_password (PkeyManagePasswordCallback cb, gpointer user_data)
{
	GObject * widget = NULL, * password_widget = NULL, *remember_password_widget = NULL;
	GtkBuilder * dialog_gtkb = NULL;
	__AskPasswordCtx *ctx;

	if (! ca_file_is_password_protected()) {
		cb (NULL, user_data);
		return;
	}

	if (saved_password && ca_file_check_password (saved_password)) {
		cb (g_strdup (saved_password), user_data);
		return;
	}

	dialog_gtkb = gtk_builder_new();
	gtk_builder_add_from_file (dialog_gtkb,
				   g_build_filename (PACKAGE_DATA_DIR, "gnomint", "get_db_password_dialog.ui", NULL),
				   NULL);

	password_widget = gtk_builder_get_object (dialog_gtkb, "cadb_password_entry");
	remember_password_widget = gtk_builder_get_object (dialog_gtkb, "remember_password_checkbutton");

	gtk_widget_grab_focus (GTK_WIDGET (password_widget));

	ctx = g_new0 (__AskPasswordCtx, 1);
	ctx->dialog_gtkb = dialog_gtkb;
	ctx->password_widget = password_widget;
	ctx->remember_password_widget = remember_password_widget;
	ctx->cb = cb;
	ctx->user_data = user_data;

	widget = gtk_builder_get_object (dialog_gtkb, "get_db_password_dialog");
	g_signal_connect (widget, "response",
	                  G_CALLBACK (__pkey_manage_ask_password_response), ctx);
	gtk_widget_set_visible (GTK_WIDGET (widget), TRUE);
	gtk_window_set_transient_for (GTK_WINDOW (widget), dialog_get_main_window ());
	gtk_window_present (GTK_WINDOW (widget));
}

#else
gchar * pkey_manage_ask_password ()
{
	gchar *password = NULL;
	gchar *pass;
	gboolean is_key_ok;

	if (! ca_file_is_password_protected())
		return NULL;

	is_key_ok = FALSE;

	while (! is_key_ok) {

		if (password) {
			g_free (password);
			password = NULL;
		}

		printf (_("This action requires using one or more private keys saved in the database.\n"));
		pass = getpass (_("Please insert the database password:"));
	
		if (! pass || pass[0] == '\0') {
			return NULL;
		} else {
			password = g_strdup (pass);
			memset (pass, 0, strlen(pass));
		}

		is_key_ok = ca_file_check_password (password);
		
		if (! is_key_ok) {
			dialog_error (_("The given password doesn't match the one used in the database"));
		}

	}


	return password;      
}
#endif

gchar * pkey_manage_crypt (const gchar *pem_private_key, const gchar *dn)
{
	gchar *res;
	gchar *password = NULL;

	if (! ca_file_is_password_protected())
		return pkey_manage_crypt_w_pwd (pem_private_key, dn, "");

	/* Use the cached password if available. In the CLI path,
	 * pkey_manage_ask_password() is called which may prompt.
	 * In the GUI path, the password should already be cached
	 * by a prior pkey_manage_ask_password() call. */
	if (saved_password && ca_file_check_password (saved_password)) {
		password = g_strdup (saved_password);
	}
#ifdef GNOMINTCLI
	else {
		password = pkey_manage_ask_password ();
	}
#endif

	if (!password)
		return NULL;

	res = pkey_manage_crypt_w_pwd (pem_private_key, dn, password);

	g_free (password);

	return res;
}

gchar * pkey_manage_crypt_w_pwd (const gchar *pem_private_key, const gchar *dn, const gchar *pwd)
{
 	gchar *res; 
	gchar *password;

	if (! ca_file_is_password_protected())
		return g_strdup(pem_private_key);
		
	password = g_strdup_printf ("gnoMintPrivateKey%s%s", pwd, dn);

	res = __pkey_manage_aes_encrypt (pem_private_key, password);

	g_free (password);

	return res;
}

#ifdef GNOMINTCLI

gchar * pkey_manage_uncrypt (PkeyManageData *pem_private_key, const gchar *dn)
{
	gchar *res = NULL;
	gchar *password = NULL;

	if (pem_private_key->is_in_db) {
                if (pem_private_key->is_ciphered_with_db_pwd) {
                        password = pkey_manage_ask_password();

                        if (password) {
                                res = pkey_manage_uncrypt_w_pwd (pem_private_key, dn, password);
                                g_free (password);
                        }

                } else {
                        res = g_strdup (pem_private_key->pkey_data);
                }
        } else {
		res = g_strdup (pem_private_key->pkey_data);
	}
	return res;
}

#else /* GUI async pkey_manage_uncrypt */

typedef struct {
	PkeyManageData *pem_private_key;
	gchar *dn;
	PkeyManageUncryptCallback cb;
	gpointer user_data;
} __UncryptCtx;

static void
__pkey_manage_uncrypt_password_cb (gchar *password, gpointer data)
{
	__UncryptCtx *ctx = (__UncryptCtx *) data;
	gchar *res = NULL;

	if (password) {
		res = pkey_manage_uncrypt_w_pwd (ctx->pem_private_key, ctx->dn, password);
		g_free (password);
	}

	g_free (ctx->dn);
	ctx->cb (res, ctx->user_data);
	g_free (ctx);
}

void pkey_manage_uncrypt (PkeyManageData *pem_private_key, const gchar *dn,
                          PkeyManageUncryptCallback cb, gpointer user_data)
{
	if (pem_private_key->is_in_db) {
		if (pem_private_key->is_ciphered_with_db_pwd) {
			__UncryptCtx *ctx = g_new0 (__UncryptCtx, 1);
			ctx->pem_private_key = pem_private_key;
			ctx->dn = g_strdup (dn);
			ctx->cb = cb;
			ctx->user_data = user_data;

			pkey_manage_ask_password (__pkey_manage_uncrypt_password_cb, ctx);
		} else {
			cb (g_strdup (pem_private_key->pkey_data), user_data);
		}
	} else {
		cb (g_strdup (pem_private_key->pkey_data), user_data);
	}
}

#endif /* GNOMINTCLI */

gchar * pkey_manage_uncrypt_w_pwd (PkeyManageData *pem_private_key, const gchar *dn, const gchar *pwd)
{
 	gchar *res; 
	gchar *password;

	if (! pem_private_key->is_in_db || ! pem_private_key->is_ciphered_with_db_pwd)
		return g_strdup(pem_private_key->pkey_data);
		
	password = g_strdup_printf ("gnoMintPrivateKey%s%s", pwd, dn);

	res = __pkey_manage_aes_decrypt (pem_private_key->pkey_data, password);

	g_free (password);

	return res;
}



