/* vi: set sw=4 ts=4:
 *
 * Copyright (C) 2001 - 2015 Christian Hohnstaedt.
 *
 * All rights reserved.
 */


#include "pki_evp.h"
#include "pass_info.h"
#include "Passwd.h"
#include "func.h"
#include "db.h"
#include "entropy.h"
#include "widgets/PwDialog.h"

#include <openssl/rand.h>
#include <openssl/evp.h>
#include <openssl/pem.h>

#include <QProgressDialog>
#include <QApplication>
#include <QDir>

#include "openssl_compat.h"

Passwd pki_evp::passwd;
Passwd pki_evp::oldpasswd;

QString pki_evp::passHash = QString();

QPixmap *pki_evp::icon[2]= { NULL, NULL };

void pki_evp::init()
{
	class_name = "pki_evp";
	ownPass = ptCommon;
	dataVersion=2;
	pkiType=asym_key;
}

QString pki_evp::removeTypeFromIntName(QString n)
{
	if (n.right(1) != ")" )
		return n;
	n.truncate(n.length() - 6);
	return n;
}

void pki_evp::setOwnPass(enum passType x)
{
	EVP_PKEY *pk=NULL, *pk_back = key;
	int oldOwnPass = ownPass;

	if (ownPass == x || isPubKey())
		return;

	try {
		pk = decryptKey();
		if (pk == NULL)
			return;

		key = pk;
		ownPass = x;
		encryptKey();
	}
	catch (errorEx &err) {
		if (pk)
			EVP_PKEY_free(pk);
		key = pk_back;
		ownPass = oldOwnPass;
		throw(err);
	}
	EVP_PKEY_free(pk_back);
}

void pki_evp::generate(int bits, int type, QProgressBar *progress, int curve_nid)
{
	Entropy::seed_rng();

#ifdef OPENSSL_NO_EC
	(void)curve_nid;
#endif
	progress->setMinimum(0);
	progress->setMaximum(100);
	progress->setValue(50);

#if OPENSSL_VERSION_NUMBER < 0x10100000L
	BN_GENCB _bar, *bar = &_bar;
#else
	BN_GENCB *bar = BN_GENCB_new();
#endif
	BN_GENCB_set_old(bar, inc_progress_bar, progress);

	switch (type) {
	case EVP_PKEY_RSA: {
		RSA *rsakey = RSA_new();
		BIGNUM *e = BN_new();
		BN_set_word(e, 0x10001);
		if (RSA_generate_key_ex(rsakey, bits, e, bar))
			EVP_PKEY_assign_RSA(key, rsakey);
		else
			RSA_free(rsakey);
		BN_free(e);
		break;
	}
	case EVP_PKEY_DSA: {
		DSA *dsakey = DSA_new();
		progress->setMaximum(500);
		if (DSA_generate_parameters_ex(dsakey, bits, NULL, 0, NULL,
		    NULL, bar) && DSA_generate_key(dsakey))
				EVP_PKEY_assign_DSA(key, dsakey);
		else
			DSA_free(dsakey);
		break;
	}
#ifndef OPENSSL_NO_EC
	case EVP_PKEY_EC:
		EC_KEY *eckey;
		EC_GROUP *group = EC_GROUP_new_by_curve_name(curve_nid);
		if (!group)
			break;
		eckey = EC_KEY_new();
		if (eckey == NULL) {
			EC_GROUP_free(group);
			break;
		}
		EC_GROUP_set_asn1_flag(group, 1);
		if (EC_KEY_set_group(eckey, group)) {
			if (EC_KEY_generate_key(eckey)) {
				EVP_PKEY_assign_EC_KEY(key, eckey);
				EC_GROUP_free(group);
				break;
			}
		}
		EC_KEY_free(eckey);
		EC_GROUP_free(group);
		break;
#endif
	}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	BN_GENCB_free(bar);
#endif

	pki_openssl_error();
	encryptKey();
}

pki_evp::pki_evp(const pki_evp *pk)
	:pki_key(pk)
{
	init();
	pki_openssl_error();
	ownPass = pk->ownPass;
	encKey = pk->encKey;
}

pki_evp::pki_evp(const QString name, int type)
	:pki_key(name)
{
	init();
	EVP_PKEY_set_type(key, type);
	pki_openssl_error();
}

pki_evp::pki_evp(EVP_PKEY *pkey)
	:pki_key()
{
	init();
	if (key) {
		EVP_PKEY_free(key);
	}
	key = pkey;
}

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
static bool EVP_PKEY_isPrivKey(EVP_PKEY *key)
{
	const BIGNUM *b;
	int keytype = EVP_PKEY_id(key);

	switch (EVP_PKEY_type(keytype)) {
		case EVP_PKEY_RSA:
			RSA_get0_key(EVP_PKEY_get0_RSA(key), NULL, NULL, &b);
			return b ? true: false;
		case EVP_PKEY_DSA:
			DSA_get0_key(EVP_PKEY_get0_DSA(key), NULL, &b);
			return b ? true: false;
#ifndef OPENSSL_NO_EC
		case EVP_PKEY_EC:
			return EC_KEY_get0_private_key(
				EVP_PKEY_get0_EC_KEY(key)) ? true: false;
#endif
	}
	return false;
}

#else

static bool EVP_PKEY_isPrivKey(EVP_PKEY *key)
{
	int keytype;

	keytype = EVP_PKEY_id(key);

	switch (EVP_PKEY_type(keytype)) {
		case EVP_PKEY_RSA:
			return key->pkey.rsa->d ? true: false;
		case EVP_PKEY_DSA:
			return key->pkey.dsa->priv_key ? true: false;
#ifndef OPENSSL_NO_EC
		case EVP_PKEY_EC:
			return EC_KEY_get0_private_key(key->pkey.ec) ? true: false;
#endif
	}
	return false;
}
#endif

void pki_evp::openssl_pw_error(QString fname)
{
	switch (ERR_peek_error() & 0xff000fff) {
	case ERR_PACK(ERR_LIB_PEM, 0, PEM_R_BAD_DECRYPT):
	case ERR_PACK(ERR_LIB_PEM, 0, PEM_R_BAD_PASSWORD_READ):
	case ERR_PACK(ERR_LIB_EVP, 0, EVP_R_BAD_DECRYPT):
		pki_ign_openssl_error();
		throw errorEx(tr("Failed to decrypt the key (bad password) ")+
				fname, class_name, E_PASSWD);
	}
}

void pki_evp::fromPEMbyteArray(QByteArray &ba, QString name)
{
	BIO *bio = BIO_new_mem_buf(ba.data(), ba.length());
	EVP_PKEY *pkey;
	pass_info p(XCA_TITLE,
		tr("Please enter the password to decrypt the private key.") +
		" " + name);
	pkey = PEM_read_bio_PrivateKey(bio, NULL, PwDialog::pwCallback, &p);
	openssl_pw_error(name);
	if (!pkey){
		pki_ign_openssl_error();
		BIO_free(bio);
		bio = BIO_new_mem_buf(ba.data(), ba.length());
		pkey = PEM_read_bio_PUBKEY(bio, NULL, PwDialog::pwCallback, &p);
	}
	BIO_free(bio);
	if (pkey){
		if (key)
			EVP_PKEY_free(key);
		key = pkey;
		if (EVP_PKEY_isPrivKey(key))
			bogusEncryptKey();
		setIntName(rmslashdot(name));
	}
	openssl_error(name);
}

static void search_ec_oid(EVP_PKEY *pkey)
{
#ifndef OPENSSL_NO_EC
	EC_KEY *ec;
	int keytype = EVP_PKEY_id(pkey);

	if (keytype != EVP_PKEY_EC)
		return;

	ec = EVP_PKEY_get0_EC_KEY(pkey);

	const EC_GROUP *ec_group = EC_KEY_get0_group(ec);
	EC_GROUP *builtin;

	if (!ec_group)
		return;
	if (EC_GROUP_get_curve_name(ec_group))
		return;
	/* There is an EC_GROUP with a missing OID
	 * because of explicit parameters */
	foreach(builtin_curve curve, pki_key::builtinCurves) {
		builtin = EC_GROUP_new_by_curve_name(curve.nid);
		if (EC_GROUP_cmp(builtin, ec_group, NULL) == 0) {
			EC_GROUP_set_curve_name((EC_GROUP *)ec_group, curve.nid);
			EC_GROUP_set_asn1_flag((EC_GROUP *)ec_group, 1);
			EC_GROUP_free(builtin);
			break;
		}
		EC_GROUP_free(builtin);
	}
#else
	(void)pkey;
#endif
}

void pki_evp::fload(const QString fname)
{
	pass_info p(XCA_TITLE, tr("Please enter the password to decrypt the private key from file:\n%1").
		arg(compressFilename(fname)));
	pem_password_cb *cb = PwDialog::pwCallback;
	FILE *fp = fopen_read(fname);
	EVP_PKEY *pkey;

	pki_ign_openssl_error();
	if (!fp) {
		fopen_error(fname);
		return;
	}
	pkey = PEM_read_PrivateKey(fp, NULL, cb, &p);
	try {
		openssl_pw_error(fname);
	} catch (errorEx &err) {
		fclose(fp);
		throw err;
	}
	if (!pkey) {
		pki_ign_openssl_error();
		rewind(fp);
		pkey = d2i_PrivateKey_fp(fp, NULL);
	}
	if (!pkey) {
		pki_ign_openssl_error();
		rewind(fp);
		pkey = d2i_PKCS8PrivateKey_fp(fp, NULL, cb, &p);
	}
	if (!pkey) {
		PKCS8_PRIV_KEY_INFO *p8inf;
		pki_ign_openssl_error();
		rewind(fp);
		p8inf = d2i_PKCS8_PRIV_KEY_INFO_fp(fp, NULL);
		if (p8inf) {
			pkey = EVP_PKCS82PKEY(p8inf);
			PKCS8_PRIV_KEY_INFO_free(p8inf);
		}
	}
	if (!pkey) {
		pki_ign_openssl_error();
		rewind(fp);
		pkey = PEM_read_PUBKEY(fp, NULL, cb, &p);
	}
	if (!pkey) {
		pki_ign_openssl_error();
		rewind(fp);
		pkey = d2i_PUBKEY_fp(fp, NULL);
	}
	if (!pkey) {
		pki_ign_openssl_error();
                rewind(fp);
		try {
			pkey = load_ssh2_key(fp);
		} catch (errorEx &err) {
			fclose(fp);
			throw err;
		}
        }
	fclose(fp);
	if (!pkey || pki_ign_openssl_error()) {
		if (pkey)
			EVP_PKEY_free(pkey);
		throw errorEx(tr("Unable to load the private key in file %1. Tried PEM and DER private, public, PKCS#8 key types and SSH2 format.").arg(fname));
	}
	if (pkey){
		search_ec_oid(pkey);
		if (key)
			EVP_PKEY_free(key);
		key = pkey;
		if (EVP_PKEY_isPrivKey(key))
			bogusEncryptKey();
		setIntName(rmslashdot(fname));
	}
}

void pki_evp::fromData(const unsigned char *p, db_header_t *head)
{
	int version, type, size;
	void *ptr = NULL;

	if (key)
		EVP_PKEY_free(key);
	key = NULL;

	size = head->len - sizeof(db_header_t);
	version = head->version;

	QByteArray ba((const char*)p, size);

	type = db::intFromData(ba);
	ownPass = db::intFromData(ba);
	if (version < 2) {
		d2i_old(ba, type);
	} else {
		d2i(ba);
	}
	pki_openssl_error();

	if (key)
		ptr = EVP_PKEY_get0(key);
	if (!ptr)
		throw errorEx(tr("Ignoring unsupported private key"));

	encKey = ba;
}

EVP_PKEY *pki_evp::decryptKey() const
{
	unsigned char *p;
	const unsigned char *p1;
	int outl, decsize;
	unsigned char iv[EVP_MAX_IV_LENGTH];
	unsigned char ckey[EVP_MAX_KEY_LENGTH];

	EVP_PKEY *tmpkey;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
	EVP_CIPHER_CTX ctxbuf;
#endif
	EVP_CIPHER_CTX *ctx;
	const EVP_CIPHER *cipher = EVP_des_ede3_cbc();
	Passwd ownPassBuf;
	int ret;

	if (isPubKey()) {
		unsigned char *q;
		outl = i2d_PUBKEY(key, NULL);
		p = q = (unsigned char *)OPENSSL_malloc(outl);
		check_oom(q);
		i2d_PUBKEY(key, &p);
		p = q;
		tmpkey = d2i_PUBKEY(NULL, (const unsigned char**)&p, outl);
		OPENSSL_free(q);
		return tmpkey;
	}
	/* This key has its own password */
	if (ownPass == ptPrivate) {
		pass_info pi(XCA_TITLE, tr("Please enter the password to decrypt the private key: '%1'").arg(getIntName()));
		ret = PwDialog::execute(&pi, &ownPassBuf, false);
		if (ret != 1)
			throw errorEx(tr("Password input aborted"), class_name);
	} else if (ownPass == ptBogus) { // BOGUS pass
		ownPassBuf = "Bogus";
	} else {
		ownPassBuf = passwd;
		while (md5passwd(ownPassBuf) != passHash &&
			sha512passwd(ownPassBuf, passHash) != passHash)
		{
			pass_info p(XCA_TITLE, tr("Please enter the database password for decrypting the key '%1'").arg(getIntName()));
			ret = PwDialog::execute(&p, &ownPassBuf, false);
			if (ret != 1)
				throw errorEx(tr("Password input aborted"), class_name);
		}
	}
	p = (unsigned char *)OPENSSL_malloc(encKey.count());
	check_oom(p);
	pki_openssl_error();
	p1 = p;
	memset(iv, 0, EVP_MAX_IV_LENGTH);

	memcpy(iv, encKey.constData(), 8); /* recover the iv */
	/* generate the key */
	EVP_BytesToKey(cipher, EVP_sha1(), iv,
		ownPassBuf.constUchar(),
		ownPassBuf.size(), 1, ckey, NULL);
	/* we use sha1 as message digest,
	 * because an md5 version of the password is
	 * stored in the database...
	 */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	ctx = EVP_CIPHER_CTX_new();
#else
	ctx = &ctxbuf;
#endif
	EVP_CIPHER_CTX_init(ctx);
	EVP_DecryptInit(ctx, cipher, ckey, iv);
	EVP_DecryptUpdate(ctx, p , &outl,
		(const unsigned char*)encKey.constData() +8, encKey.count() -8);

	decsize = outl;
	EVP_DecryptFinal(ctx, p + decsize , &outl);
	decsize += outl;
	//printf("Decrypt decsize=%d, encKey_len=%d\n", decsize, encKey.count() -8);
	pki_openssl_error();
	tmpkey = d2i_PrivateKey(getKeyType(), NULL, &p1, decsize);
	pki_openssl_error();
	OPENSSL_free(p);
	EVP_CIPHER_CTX_cleanup(ctx);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	EVP_CIPHER_CTX_free(ctx);
#endif
	pki_openssl_error();
	if (EVP_PKEY_type(getKeyType()) == EVP_PKEY_RSA) {
		RSA *rsa = EVP_PKEY_get0_RSA(tmpkey);
		RSA_blinding_on(rsa, NULL);
	}
	return tmpkey;
}

QByteArray pki_evp::toData()
{
	QByteArray ba;

	ba += db::intToData(getKeyType());
	ba += db::intToData(ownPass);
	ba += i2d();
	ba += encKey;
	return ba;
}

EVP_PKEY *pki_evp::priv2pub(EVP_PKEY* key)
{
	int keylen;
	unsigned char *p, *p1;
	EVP_PKEY *pubkey;

	keylen = i2d_PUBKEY(key, NULL);
	p1 = p = (unsigned char *)OPENSSL_malloc(keylen);
	check_oom(p);

	/* convert rsa/dsa/ec to Pubkey */
	keylen = i2d_PUBKEY(key, &p);
	pki_openssl_error();
	p = p1;
	pubkey = d2i_PUBKEY(NULL, (const unsigned char**)&p, keylen);
	OPENSSL_free(p1);
	pki_openssl_error();
	return pubkey;
}

void pki_evp::encryptKey(const char *password)
{
	int outl, keylen;
	EVP_PKEY *pkey1 = NULL;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
	EVP_CIPHER_CTX ctxbuf;
#endif
	EVP_CIPHER_CTX *ctx;
	const EVP_CIPHER *cipher = EVP_des_ede3_cbc();
	unsigned char iv[EVP_MAX_IV_LENGTH], *punenc, *punenc1;
	unsigned char ckey[EVP_MAX_KEY_LENGTH];
	Passwd ownPassBuf;

	/* This key has its own, private password */
	if (ownPass == ptPrivate) {
		int ret;
		pass_info p(XCA_TITLE, tr("Please enter the password to protect the private key: '%1'").
			arg(getIntName()));
		ret = PwDialog::execute(&p, &ownPassBuf, true);
		if (ret != 1)
			throw errorEx("Password input aborted", class_name);
	} else if (ownPass == ptBogus) { // BOGUS password
		ownPassBuf = "Bogus";
	} else {
		if (password) {
			/* use the password parameter
			 * if this is a common password */
			ownPassBuf = password;
		} else {
			int ret = 0;
			ownPassBuf = passwd;
			pass_info p(XCA_TITLE, tr("Please enter the database password for encrypting the key"));
			while (md5passwd(ownPassBuf) != passHash &&
				sha512passwd(ownPassBuf, passHash) != passHash )
			{
				ret = PwDialog::execute(&p, &ownPassBuf, false);
				if (ret != 1)
					throw errorEx("Password input aborted", class_name);
			}
		}
	}

	/* Prepare Encryption */
	memset(iv, 0, EVP_MAX_IV_LENGTH);
	Entropy::get(iv, 8);      /* Generate a salt */
	EVP_BytesToKey(cipher, EVP_sha1(), iv,
			ownPassBuf.constUchar(),
			ownPassBuf.size(), 1, ckey, NULL);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	ctx = EVP_CIPHER_CTX_new();
#else
	ctx = &ctxbuf;
#endif
	EVP_CIPHER_CTX_init (ctx);
	pki_openssl_error();

	/* reserve space for unencrypted and encrypted key */
	keylen = i2d_PrivateKey(key, NULL);
	encKey.resize(keylen + EVP_MAX_KEY_LENGTH + 8);
	punenc1 = punenc = (unsigned char *)OPENSSL_malloc(keylen);
	check_oom(punenc);
	keylen = i2d_PrivateKey(key, &punenc1);
	pki_openssl_error();

	memcpy(encKey.data(), iv, 8); /* store the iv */
	/*
	 * Now DER version of privkey is in punenc
	 * and privkey is still in key
	 */

	/* do the encryption */
	/* store key right after the iv */
	EVP_EncryptInit(ctx, cipher, ckey, iv);
	unsigned char *penc = (unsigned char *)encKey.data() +8;
	EVP_EncryptUpdate(ctx, penc, &outl, punenc, keylen);
	int encKey_len = outl;
	EVP_EncryptFinal(ctx, penc + encKey_len, &outl);
	encKey.resize(encKey_len + outl +8);
	/* Cleanup */
	EVP_CIPHER_CTX_cleanup(ctx);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	EVP_CIPHER_CTX_free(ctx);
#endif
	/* wipe out the memory */
	memset(punenc, 0, keylen);
	OPENSSL_free(punenc);
	pki_openssl_error();

	pkey1 = priv2pub(key);
	check_oom(pkey1);
	EVP_PKEY_free(key);
	key = pkey1;
	pki_openssl_error();

	//CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF);

	//printf("Encrypt: encKey_len=%d\n", encKey_len);
	return;
}

void pki_evp::set_evp_key(EVP_PKEY *pkey)
{
	if (key)
		free(key);
	key = pkey;
}

void pki_evp::bogusEncryptKey()
{
	ownPass = ptBogus;
	encryptKey();
}

pki_evp::~pki_evp()
{
}


void pki_evp::writePKCS8(const QString fname, const EVP_CIPHER *enc,
		pem_password_cb *cb, bool pem)
{
	EVP_PKEY *pkey;
	pass_info p(XCA_TITLE, tr("Please enter the password protecting the PKCS#8 key '%1'").arg(getIntName()));
	FILE *fp = fopen_write(fname);
	if (fp != NULL) {
		if (key) {
			pkey = decryptKey();
			if (pkey) {
				if (pem)
					PEM_write_PKCS8PrivateKey(fp, pkey, enc, NULL, 0, cb, &p);
				else
					i2d_PKCS8PrivateKey_fp(fp, pkey, enc, NULL, 0, cb, &p);
				EVP_PKEY_free(pkey);
			}
		}
		fclose(fp);
		pki_openssl_error();
	} else
		fopen_error(fname);
}

static int mycb(char *buf, int size, int, void *)
{
	strncpy(buf, pki_evp::passwd, size);
	return strlen(pki_evp::passwd);
}

void pki_evp::writeDefault(const QString fname)
{
	writeKey(fname + QDir::separator() + getIntName() + ".pem",
		pki_evp::passwd[0] ? EVP_des_ede3_cbc() : NULL,
		mycb, true);
}

void pki_evp::writeKey(const QString fname, const EVP_CIPHER *enc,
			pem_password_cb *cb, bool pem)
{
	EVP_PKEY *pkey;
	int keytype;
	pass_info p(XCA_TITLE, tr("Please enter the export password for the private key '%1'").arg(getIntName()));
	if (isPubKey()) {
		writePublic(fname, pem);
		return;
	}
	FILE *fp = fopen_write(fname);
	if (!fp) {
		fopen_error(fname);
		return;
	}
	if (key){
		pkey = decryptKey();
		if (pkey) {
			if (pem) {
				keytype = EVP_PKEY_id(pkey);
				switch (keytype) {
				case EVP_PKEY_RSA:
					PEM_write_RSAPrivateKey(fp,
						EVP_PKEY_get0_RSA(pkey),
						enc, NULL, 0, cb, &p);
					break;
				case EVP_PKEY_DSA:
					PEM_write_DSAPrivateKey(fp,
						EVP_PKEY_get0_DSA(pkey),
						enc, NULL, 0, cb, &p);
					break;
#ifndef OPENSSL_NO_EC
				case EVP_PKEY_EC:
					PEM_write_ECPrivateKey(fp,
						EVP_PKEY_get0_EC_KEY(pkey),
						enc, NULL, 0, cb, &p);
					break;
#endif
				default:
					PEM_write_PrivateKey(fp,
						pkey,
						enc, NULL, 0, cb, &p);
				}
			} else {
				i2d_PrivateKey_fp(fp, pkey);
			}
			EVP_PKEY_free(pkey);
		}
	}
	fclose(fp);
	pki_openssl_error();
}

bool pki_evp::isPubKey() const
{
	if (encKey.count() == 0) {
		return true;
	}
	return false;
}

int pki_evp::verify()
{
	bool veri = false;
	return true;
	if (getKeyType() == EVP_PKEY_RSA && isPrivKey()) {
		RSA *rsa = EVP_PKEY_get0_RSA(key);
		if (RSA_check_key(rsa) == 1)
			veri = true;
	}
	if (isPrivKey())
		veri = true;
	pki_openssl_error();
	return veri;
}

const EVP_MD *pki_evp::getDefaultMD()
{
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	return EVP_sha1();
#else
	const EVP_MD *md;
	switch (getKeyType()) {
		case EVP_PKEY_RSA: md = EVP_sha1(); break;
		case EVP_PKEY_DSA: md = EVP_dss1(); break;
#ifndef OPENSSL_NO_EC
		case EVP_PKEY_EC:  md = EVP_ecdsa(); break;
#endif
		default: md = NULL; break;
	}
	return md;
#endif
}

QVariant pki_evp::getIcon(dbheader *hd)
{
	if (hd->id != HD_internal_name)
		return QVariant();
	int pixnum= isPubKey() ? 1 : 0;
	return QVariant(*icon[pixnum]);
}

QString pki_evp::md5passwd(QByteArray pass)
{

#if OPENSSL_VERSION_NUMBER < 0x10100000L
	EVP_MD_CTX mdctxbuf;
#endif
	EVP_MD_CTX *mdctx;
	QString str;
	int n;
	int j;
	unsigned char m[EVP_MAX_MD_SIZE];

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	mdctx = EVP_MD_CTX_new();
#else
	mdctx = &mdctxbuf;
#endif

	EVP_DigestInit(mdctx, EVP_md5());
	EVP_DigestUpdate(mdctx, pass.constData(), pass.size());
	EVP_DigestFinal(mdctx, m, (unsigned*)&n);

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	EVP_MD_CTX_free(mdctx);
#endif

	for (j=0; j<n; j++) {
		char zs[4];
		sprintf(zs, "%02X%c",m[j], (j+1 == n) ?'\0':':');
		str += zs;
	}
	return str;
}

QString pki_evp::sha512passwd(QByteArray pass, QString salt)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000L
	EVP_MD_CTX mdctxbuf;
#endif
	EVP_MD_CTX *mdctx;
	QString str;
	int n;
	int j;
	unsigned char m[EVP_MAX_MD_SIZE];

	if (salt.length() <5)
		abort();

	str = salt.left(5);
	pass = str.toLatin1() + pass;

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	mdctx = EVP_MD_CTX_new();
#else
	mdctx = &mdctxbuf;
#endif

	EVP_DigestInit(mdctx, EVP_sha512());
	EVP_DigestUpdate(mdctx, pass.constData(), pass.size());
	EVP_DigestFinal(mdctx, m, (unsigned*)&n);

#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	EVP_MD_CTX_free(mdctx);
#endif

	for (j=0; j<n; j++) {
		char zs[4];
		sprintf(zs, "%02X",m[j]);
		str += zs;
	}
	return str;
}

void pki_evp::veryOldFromData(unsigned char *p, int size )
{
	unsigned char *sik, *pdec, *pdec1, *sik1;
	int outl, decsize;
	unsigned char iv[EVP_MAX_IV_LENGTH];
	unsigned char ckey[EVP_MAX_KEY_LENGTH];
	memset(iv, 0, EVP_MAX_IV_LENGTH);
	RSA *rsakey;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
	EVP_CIPHER_CTX ctxbuf;
#endif
	EVP_CIPHER_CTX *ctx;
	const EVP_CIPHER *cipher = EVP_des_ede3_cbc();
	sik = (unsigned char *)OPENSSL_malloc(size);
	check_oom(sik);
	pki_openssl_error();
	pdec = (unsigned char *)OPENSSL_malloc(size);
	if (pdec == NULL ) {
		OPENSSL_free(sik);
		check_oom(pdec);
	}
	pdec1=pdec;
	sik1=sik;
	memcpy(iv, p, 8); /* recover the iv */
	/* generate the key */
	EVP_BytesToKey(cipher, EVP_sha1(), iv, oldpasswd.constUchar(),
		oldpasswd.size(), 1, ckey, NULL);
	/* we use sha1 as message digest,
	 * because an md5 version of the password is
	 * stored in the database...
	 */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	ctx = EVP_CIPHER_CTX_new();
#else
	ctx = &ctxbuf;
#endif

	EVP_CIPHER_CTX_init(ctx);
	EVP_DecryptInit(ctx, cipher, ckey, iv);
	EVP_DecryptUpdate(ctx, pdec , &outl, p + 8, size - 8);
	decsize = outl;
	EVP_DecryptFinal(ctx, pdec + decsize, &outl);
	decsize += outl;
	pki_openssl_error();
	memcpy(sik, pdec, decsize);
	if (getKeyType() == EVP_PKEY_RSA) {
		rsakey=d2i_RSAPrivateKey(NULL,(const unsigned char **)&pdec, decsize);
		if (pki_ign_openssl_error()) {
			rsakey = d2i_RSA_PUBKEY(NULL, (const unsigned char **)&sik, decsize);
		}
		pki_openssl_error();
		if (rsakey) EVP_PKEY_assign_RSA(key, rsakey);
	}
	OPENSSL_free(sik1);
	OPENSSL_free(pdec1);
	EVP_CIPHER_CTX_cleanup(ctx);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
	EVP_CIPHER_CTX_free(ctx);
#endif
	pki_openssl_error();
	encryptKey();
}

void pki_evp::oldFromData(unsigned char *p, int size )
{
	int version, type;

	QByteArray ba;

	version = intFromData(ba);
	if (version != 1) { // backward compatibility
		veryOldFromData(p, size);
		return;
	}
	if (key)
		EVP_PKEY_free(key);

	key = NULL;
	type = intFromData(ba);
	ownPass = intFromData(ba);

	d2i_old(ba, type);
	pki_openssl_error();

	encKey = ba;
}

