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


#include "pki_evp.h"
#include "pass_info.h"
#include "func.h"
#include "entropy.h"
#include "BioByteArray.h"
#include "XcaProgress.h"
#include "openssl_compat.h"

#include "PwDialogCore.h"
#include "XcaWarningCore.h"

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

Passwd pki_evp::passwd;

QString pki_evp::passHash = QString();

void pki_evp::init()
{
	ownPass = ptCommon;
	pkiType = asym_key;
}

void pki_evp::setOwnPass(enum passType x)
{
	EVP_PKEY *pk=NULL, *pk_back = key;
	enum passType 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);
	}
}

bool pki_evp::sqlUpdatePrivateKey()
{
	Transaction;
	if (!TransBegin())
		return false;
	XSqlQuery q;
	SQL_PREPARE(q, "UPDATE private_keys SET private=?, ownPass=? "
		"WHERE item=?");
	q.bindValue(0, encKey_b64());
	q.bindValue(1, ownPass);
	q.bindValue(2, sqlItemId);
	AffectedItems(sqlItemId);
	q.exec();

	encKey.fill(0);
	encKey.clear();

	if (!q.lastError().isValid() && q.numRowsAffected() == 1) {
		TransCommit();
		return true;
	}
	return false;
}

void pki_evp::generate(const keyjob &task)
{
	Entropy::seed_rng();
	XcaProgress progress;

	BN_GENCB *bar = BN_GENCB_new();
	BN_GENCB_set_old(bar, XcaProgress::inc, &progress);

	switch (task.ktype.type) {
	case EVP_PKEY_RSA: {
		RSA *rsakey = RSA_new();
		BIGNUM *e = BN_new();
		BN_set_word(e, 0x10001);
		if (RSA_generate_key_ex(rsakey, task.size, e, bar))
			EVP_PKEY_assign_RSA(key, rsakey);
		else
			RSA_free(rsakey);
		BN_free(e);
		break;
	}
	case EVP_PKEY_DSA: {
		DSA *dsakey = DSA_new();
		if (DSA_generate_parameters_ex(dsakey, task.size, 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(task.ec_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;
	}
#ifdef EVP_PKEY_ED25519
	case EVP_PKEY_ED25519: {
		EVP_PKEY *pkey = NULL;
		EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
		Q_CHECK_PTR(pctx);
		EVP_PKEY_keygen_init(pctx);
		EVP_PKEY_keygen(pctx, &pkey);
		EVP_PKEY_CTX_free(pctx);
		EVP_PKEY_free(key);
		key = pkey;
	}
#endif
#endif
	}
	BN_GENCB_free(bar);
	isPub = false;
	pkiSource = generated;
	pki_openssl_error();
	encryptKey();
}

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

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

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;
#ifdef EVP_PKEY_ED25519
		case EVP_PKEY_ED25519: {
			unsigned char buf[ED25519_KEYLEN];
			size_t len = sizeof buf;
			int ret = EVP_PKEY_get_raw_private_key(key, buf, &len);
			ign_openssl_error();
			return ret && len == ED25519_KEYLEN;
		}
#endif
#endif
	}
	return false;
}

pki_evp::pki_evp(EVP_PKEY *pkey)
	:pki_key()
{
	init();
	set_EVP_PKEY(pkey);
}

bool pki_evp::openssl_pw_error() const
{
	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();
		return true;
	}
	return false;
}

void pki_evp::fromPEMbyteArray(const QByteArray &ba, const QString &name)
{
	EVP_PKEY *pkey;
	pass_info p(XCA_TITLE,
		tr("Please enter the password to decrypt the private key %1.")
			.arg(name));
	pkey = load_ssh_ed25519_privatekey(ba, p);

	while (!pkey) {
		pkey = PEM_read_bio_PrivateKey(BioByteArray(ba).ro(), NULL,
						PwDialogCore::pwCallback, &p);
		if (openssl_pw_error())
			XCA_PASSWD_ERROR();
		if (p.getResult() != pw_ok)
			throw p.getResult();
		if (pki_ign_openssl_error())
			break;
	}
	if (!pkey) {
		pki_ign_openssl_error();
		pkey = PEM_read_bio_PUBKEY(BioByteArray(ba).ro(), NULL, NULL,0);
	}
	pki_openssl_error();
	set_EVP_PKEY(pkey, name);
}

static void search_ec_oid(EVP_PKEY *pkey)
{
#ifndef OPENSSL_NO_EC
	EC_GROUP *builtin;
	const EC_KEY *ec;
	const EC_GROUP *ec_group;

	int keytype = EVP_PKEY_id(pkey);

	if (keytype != EVP_PKEY_EC)
		return;

	ec = EVP_PKEY_get0_EC_KEY(pkey);
	if (!ec)
		return;

	ec_group = EC_KEY_get0_group(ec);
	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, 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::set_EVP_PKEY(EVP_PKEY *pkey, QString name)
{
	if (!pkey)
		return;
	if (!verify(pkey)) {
		pki_ign_openssl_error();
		EVP_PKEY_free(pkey);
		throw errorEx(tr("The key from file '%1' is incomplete or inconsistent.").arg(name));
	}
	if (key)
		EVP_PKEY_free(key);
	key = pkey;
	isPub = !EVP_PKEY_isPrivKey(key);
	if (!isPub)
		bogusEncryptKey();
	search_ec_oid(pkey);

	autoIntName(name);
	setFilename(name);
	pki_openssl_error();
}

EVP_PKEY *pki_evp::load_ssh_ed25519_privatekey(const QByteArray &ba,
						const pass_info &p)
{
	EVP_PKEY *pkey = NULL;
	unsigned char *pdata;
	long plen;
	QByteArray chunk, enc_algo, kdfname, kdf, pub, priv;

	(void)p; // Will be used later for decryption
	if (!PEM_bytes_read_bio(&pdata, &plen, NULL, PEM_STRING_OPENSSH_KEY,
				BioByteArray(ba).ro(), NULL, NULL))
		return NULL;

	QByteArray content((const char*)pdata, plen);
	OPENSSL_free(pdata);

	if (!content.startsWith("openssh-key-v1") ||
	     // also check trailing \0
	     content.constData()[sizeof "openssh-key-v1" -1])
		return NULL;

	content.remove(0, sizeof "openssh-key-v1");
	// encryption: "none", "aes256-ctr"
	enc_algo = ssh_key_next_chunk(&content);
	// KDFName "bcrypt"
	kdfname = ssh_key_next_chunk(&content);
	kdf = ssh_key_next_chunk(&content);

	if (enc_algo != "none" || kdfname != "none") {
		qCritical("Encrypted SSH ED25519 keys not supported, yet");
		return NULL;
	}
	// check bytes 00 00 00 01
	const char *d = content.constData();
	if (d[0] || d[1] || d[2] || d[3] != 1)
		return NULL;
	content.remove(0, 4);
	// Handle first occurance of the public key
	pub = ssh_key_next_chunk(&content);
	ssh_key_check_chunk(&pub, "ssh-ed25519");
	pub = ssh_key_next_chunk(&pub);
	if (pub.size() != ED25519_KEYLEN)
		return NULL;

	// Followed by the private key
	priv = ssh_key_next_chunk(&content);
	// Drop 64bit random nonce
	priv.remove(0, 8);

	ssh_key_check_chunk(&priv, "ssh-ed25519");
	// The first pubkey must match the second occurance
	// in front of the private one
	if (pub != ssh_key_next_chunk(&priv))
		return NULL;
	priv = ssh_key_next_chunk(&priv);
	// The private key is concatenated by the public key in one chunk
	if (priv.size() != 2 * ED25519_KEYLEN)
		return NULL;
	// The last ED25519_KEYLEN bytes must match the public key
	if (pub != priv.mid(ED25519_KEYLEN))
		return NULL;
	// The first ED25519_KEYLEN octets are the private key
#ifndef OPENSSL_NO_EC
#ifdef EVP_PKEY_ED25519
	pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
		(const unsigned char *)priv.constData(), ED25519_KEYLEN);
#endif
#endif
	pki_openssl_error();
	return pkey;
}

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 = PwDialogCore::pwCallback;

	pki_ign_openssl_error();
	XFile file(fname);
	file.open_read();
	QByteArray ba = file.readAll();
	EVP_PKEY *pkey;

	do {
		pkey = PEM_read_bio_PrivateKey(BioByteArray(ba).ro(),
						NULL, cb, &p);
		if (openssl_pw_error())
			XCA_PASSWD_ERROR();
		if (p.getResult() != pw_ok)
			throw p.getResult();
		if (pki_ign_openssl_error())
			break;
	} while (!pkey);

	if (!pkey) {
		pki_ign_openssl_error();
		pkey = d2i_PrivateKey_bio(BioByteArray(ba).ro(), NULL);
	}
	if (!pkey) {
		pki_ign_openssl_error();
		pkey = d2i_PKCS8PrivateKey_bio(BioByteArray(ba).ro(),
						NULL, cb, &p);
	}
	if (!pkey) {
		PKCS8_PRIV_KEY_INFO *p8inf;
		pki_ign_openssl_error();
		p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(BioByteArray(ba).ro(),
							NULL);
		if (p8inf) {
			pkey = EVP_PKCS82PKEY(p8inf);
			PKCS8_PRIV_KEY_INFO_free(p8inf);
		}
	}
	if (!pkey) {
		pki_ign_openssl_error();
		pkey = b2i_PVK_bio(BioByteArray(ba).ro(), cb, &p);
	}
	if (!pkey) {
		pki_ign_openssl_error();
		pkey = load_ssh_ed25519_privatekey(ba, p);
	}
	if (!pkey) {
		pki_ign_openssl_error();
		pkey = PEM_read_bio_PUBKEY(BioByteArray(ba).ro(), NULL, cb, &p);
	}
	if (!pkey) {
		pki_ign_openssl_error();
		pkey = d2i_PUBKEY_bio(BioByteArray(ba).ro(), NULL);
	}
	if (!pkey) {
		pki_ign_openssl_error();
		pkey = load_ssh2_key(ba);
	}
	if (!pkey) {
		pki_ign_openssl_error();
		pkey = b2i_PublicKey_bio(BioByteArray(ba).ro());
	}
	if (pki_ign_openssl_error() || !pkey) {
		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));
	}
	set_EVP_PKEY(pkey, fname);
}

EVP_PKEY *pki_evp::decryptKey() const
{
	Passwd ownPassBuf;
	int ret;

	if (isPubKey()) {
		QByteArray ba = i2d_bytearray(I2D_VOID(i2d_PUBKEY), key);
		return (EVP_PKEY*)d2i_bytearray(D2I_VOID(d2i_PUBKEY), ba);
	}
	/* 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 = PwDialogCore::execute(&pi, &ownPassBuf, false);
		if (ret != 1)
			throw errorEx(tr("Password input aborted"),
					getClassName());
	} else if (ownPass == ptBogus) { // BOGUS pass
		ownPassBuf = "Bogus";
	} else {
		ownPassBuf = passwd;
		while (passHash.isEmpty() ||
			(sha512passwT(ownPassBuf, passHash) != passHash &&
			 sha512passwd(ownPassBuf, passHash) != passHash))
		{
			pass_info p(XCA_TITLE, tr("Please enter the database password for decrypting the key '%1'").arg(getIntName()));
			ret = PwDialogCore::execute(&p, &ownPassBuf,
							passHash.isEmpty());
			if (ret != 1)
				throw errorEx(tr("Password input aborted"),
						getClassName());
		}
	}
	QByteArray myencKey = getEncKey();
	qDebug() << "myencKey.size()"<<myencKey.size();
	if (myencKey.size() == 0)
		return NULL;
	EVP_PKEY *priv = NULL;
	X509_SIG *p8 = d2i_PKCS8_bio(BioByteArray(myencKey).ro(), NULL);
	if (p8) {
		PKCS8_PRIV_KEY_INFO *p8inf = PKCS8_decrypt(p8,
				ownPassBuf.constData(), ownPassBuf.size());
		if (p8inf) {
			priv = EVP_PKCS82PKEY(p8inf);
			PKCS8_PRIV_KEY_INFO_free(p8inf);
		}
		X509_SIG_free(p8);
	}
	pki_ign_openssl_error();
	return priv;
}

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);
	Q_CHECK_PTR(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)
{
	Passwd ownPassBuf;

	pki_openssl_error();
	/* 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 = PwDialogCore::execute(&p, &ownPassBuf, true);
		if (ret != 1)
			throw errorEx("Password input aborted", getClassName());
		pki_openssl_error();
	} else if (ownPass == ptBogus) { // BOGUS password
		ownPassBuf = "Bogus";
		pki_openssl_error();
	} else {
		if (password) {
			/* use the password parameter
			 * if this is a common password */
			ownPassBuf = password;
			pki_openssl_error();
		} else {
			int ret = 0;
			ownPassBuf = passwd;
			pass_info p(XCA_TITLE, tr("Please enter the database password for encrypting the key"));
			while (passHash.isEmpty() ||
				(sha512passwT(ownPassBuf, passHash) != passHash &&
				 sha512passwd(ownPassBuf, passHash) != passHash))
			{
				ret = PwDialogCore::execute(&p, &ownPassBuf,
							passHash.isEmpty());
				if (ret != 1)
					throw errorEx("Password input aborted",
							getClassName());
			}
		}
	}

	/* Convert private key to DER(PKCS8-aes) */
	BioByteArray bba;
	i2d_PKCS8PrivateKey_bio(bba, key, EVP_aes_256_cbc(),
		ownPassBuf.data(), ownPassBuf.size(), NULL, 0);
	pki_openssl_error();
	encKey = bba;

	/* Replace private key by public key and
	   have the encrypted private in "encKey"
	 */
	EVP_PKEY *pkey1 = priv2pub(key);
	Q_CHECK_PTR(pkey1);
	EVP_PKEY_free(key);
	key = pkey1;
	pki_openssl_error();
}

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

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

pki_evp::~pki_evp()
{
	encKey.fill(0);
}

QSqlError pki_evp::insertSqlData()
{
	XSqlQuery q;
	QSqlError e = pki_key::insertSqlData();
	if (e.isValid())
		return e;
	if (isPubKey())
		return QSqlError();

	SQL_PREPARE(q, "INSERT INTO private_keys (item, ownPass, private) "
		  "VALUES (?, ?, ?)");
	q.bindValue(0, sqlItemId);
	q.bindValue(1, ownPass);
	q.bindValue(2, encKey_b64());
	q.exec();
	encKey.fill(0);
	encKey.clear();
	return q.lastError();
}

void pki_evp::restoreSql(const QSqlRecord &rec)
{
	pki_key::restoreSql(rec);
	isPub = rec.isNull(VIEW_private_ownpass);
	if (!isPub)
		ownPass =(enum passType)rec.value(VIEW_private_ownpass).toInt();
}

QByteArray pki_evp::getEncKey() const
{
	XSqlQuery q;
	QSqlError e;
	QByteArray ba;

	if (encKey.size() > 0 || !sqlItemId.isValid())
		return encKey;

	SQL_PREPARE(q, "SELECT private FROM private_keys WHERE item=?");
	q.bindValue(0, sqlItemId);
	q.exec();
	e = q.lastError();
	if (e.isValid() || !q.first())
		return QByteArray();
	return QByteArray::fromBase64(q.value(0).toByteArray().trimmed());
}

QSqlError pki_evp::deleteSqlData()
{
	XSqlQuery q;
	QSqlError e = pki_key::deleteSqlData();
	if (e.isValid())
		return e;
	SQL_PREPARE(q, "DELETE FROM private_keys WHERE item=?");
	q.bindValue(0, sqlItemId);
	q.exec();
	return q.lastError();
}

bool pki_evp::pem(BioByteArray &b)
{
	EVP_PKEY *pkey;
	int keytype;
	const pki_export *xport = pki_export::by_id(Settings["KeyFormat"]);

	if (xport->match_all(F_PEM | F_PRIVATE)) {
		pkey = decryptKey();
		keytype = EVP_PKEY_id(pkey);
		switch (keytype) {
		case EVP_PKEY_RSA:
			PEM_write_bio_RSAPrivateKey(b,
				EVP_PKEY_get0_RSA(pkey),
				NULL, NULL, 0, NULL, NULL);
			break;
		case EVP_PKEY_DSA:
			PEM_write_bio_DSAPrivateKey(b,
				EVP_PKEY_get0_DSA(pkey),
				NULL, NULL, 0, NULL, NULL);
			break;
#ifndef OPENSSL_NO_EC
		case EVP_PKEY_EC:
			PEM_write_bio_ECPrivateKey(b,
				EVP_PKEY_get0_EC_KEY(pkey),
				NULL, NULL, 0, NULL, NULL);
			break;
#ifdef EVP_PKEY_ED25519
		case EVP_PKEY_ED25519:
			 if (xport->match_all(F_PRIVATE))
				return false;
			write_SSH2_ed25519_private(b, pkey, NULL);
			break;
#endif
#endif
		}
		EVP_PKEY_free(pkey);
	} else if (xport->match_all(F_PKCS8 | F_PRIVATE)) {
		const EVP_CIPHER *algo = xport->match_all(F_CRYPT) ?
			EVP_aes_256_cbc() : NULL;
		pkey = decryptKey();
        PEM_write_bio_PrivateKey(b, pkey, algo,
					 passwd.constUchar(), passwd.size(),
					 NULL, NULL);
		EVP_PKEY_free(pkey);
	} else
		return pki_key::pem(b);

	return true;
}
void pki_evp::writePKCS8(XFile &file, const EVP_CIPHER *enc,
		pem_password_cb *cb, bool pem) const
{
	pass_info p(XCA_TITLE,
		tr("Please enter the password protecting the PKCS#8 key '%1'")
			.arg(getIntName()));
	EVP_PKEY *pkey = decryptKey();
	if (!pkey) {
		pki_openssl_error();
		return;
	}
	BioByteArray b;
	if (pem) {
		b += PEM_comment();
		PEM_write_bio_PKCS8PrivateKey(b, pkey, enc, NULL, 0, cb, &p);
	} else {
		i2d_PKCS8PrivateKey_bio(b, pkey, enc, NULL, 0, cb, &p);
	}
	EVP_PKEY_free(pkey);
	file.write(b);
}

void pki_evp::writePVKprivate(XFile &file, pem_password_cb *cb) const
{
	pass_info p(XCA_TITLE, tr("Please enter the password protecting the Microsoft PVK key '%1'").arg(getIntName()));

	int enc = cb ? 2 /* pvk-strong */ : 0 /* pvk-none */;
	EVP_PKEY *pkey = decryptKey();
	if (!pkey) {
		pki_openssl_error();
		return;
	}
	/* In case of success! the error
	 *   PEMerr(PEM_F_I2B_PVK_BIO, PEM_R_BIO_WRITE_FAILURE)
	 * is set. Workaround this behavior */
	BioByteArray b;
	if (i2b_PVK_bio(b, pkey, enc, cb, &p) == -1) {
		pki_openssl_error();
		PEMerr(PEM_F_I2B_PVK_BIO, PEM_R_BIO_WRITE_FAILURE);
		pki_openssl_error();
	}
	ign_openssl_error();
	EVP_PKEY_free(pkey);
	file.write(b);
}

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 &dirname) const
{
	XFile file(get_dump_filename(dirname, ".pem"));
	file.open_key();
	writeKey(file, pki_evp::passwd.isEmpty() ? NULL : EVP_des_ede3_cbc(),
			mycb, true);
}

#ifndef LIBRESSL_VERSION_NUMBER
int PEM_write_bio_PrivateKey_traditional(BIO *bp, EVP_PKEY *x,
                                         const EVP_CIPHER *enc,
                                         unsigned char *kstr, int klen,
                                         pem_password_cb *cb, void *u)
{
	QString pem = keytype::byPKEY(x).traditionalPemName();

	return PEM_ASN1_write_bio((i2d_of_void *)i2d_PrivateKey,
			pem.toLatin1(), bp, (char*)x, enc, kstr, klen, cb, u);
}
#endif

void pki_evp::writeKey(XFile &file, const EVP_CIPHER *enc,
			pem_password_cb *cb, bool pem) const
{
	pass_info p(XCA_TITLE,
		tr("Please enter the export password for the private key '%1'")
			.arg(getIntName()));

	if (isPubKey()) {
		writePublic(file, pem);
		return;
	}
	EVP_PKEY *pkey = key ? decryptKey() : NULL;
	if (!pkey) {
	        pki_openssl_error();
		return;
	}
	BioByteArray b;
	if (pem) {
		b += PEM_comment();
		PEM_write_bio_PrivateKey_traditional(b, pkey, enc,
						NULL, 0, cb, &p);
	} else {
		i2d_PrivateKey_bio(b, pkey);
	}
	EVP_PKEY_free(pkey);
	pki_openssl_error();
	file.write(b);
}

bool pki_evp::verify(EVP_PKEY *pkey) const
{
	if (!EVP_PKEY_isPrivKey(pkey))
		return pki_key::verify(pkey);

	EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
	Q_CHECK_PTR(ctx);
	int verify = EVP_PKEY_check(ctx);
	EVP_PKEY_CTX_free(ctx);
	if (verify == -2) {
		// Operation not supported assume true
		pki_ign_openssl_error();
	}
	pki_openssl_error();
	return verify;
}

QVariant pki_evp::getIcon(const dbheader *hd) const
{
	if (hd->id != HD_internal_name)
		return QVariant();

	return QVariant(QPixmap(isPubKey() ? ":pubkeyIco" : ":keyIco"));
}

QString pki_evp::md5passwd(QByteArray pass)
{
	return formatHash(Digest(pass, EVP_md5()));
}

QString pki_evp::_sha512passwd(QByteArray pass, QString salt,
				int size, int repeat)
{
	if (salt.length() < size)
		return QString();

	salt = salt.left(size);
	pass = salt.toLatin1() + pass;

	while (repeat--)
		pass = Digest(pass, EVP_sha512());

	return salt + formatHash(pass, "");
}

QString pki_evp::sha512passwd(QByteArray pass, QString salt)
{
	return _sha512passwd(pass, salt, 5, 1);
}

QString pki_evp::sha512passwT(QByteArray pass, QString salt)
{
	return _sha512passwd(pass, salt, 17, 8000);
}
