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


#include "pki_scard.h"
#include "pass_info.h"
#include "pk11_attribute.h"
#include "exception.h"
#include "db_base.h"
#include "pkcs11.h"
#include "x509name.h"
#include "func.h"
#include "XcaProgress.h"

#include "XcaWarningCore.h"

#include <QThread>
#include <QInputDialog>

void pki_scard::init(void)
{
	ownPass = ptPin;
	pkiType = smartCard;
	isPub = false;
}

pki_scard::pki_scard(const QString &name)
	:pki_key(name)
{
	init();
}

QString pki_scard::getMsg(msg_type msg, int n) const
{
	/*
	 * We do not construct english sentences from fragments
	 * to allow proper translations.
	 *
	 * %1 will be replaced by the name of the smartcard
	 */
	switch (msg) {
	case msg_import: return tr("Successfully imported the token key '%1'");
	case msg_delete: return tr("Delete the %n token key(s): '%1'?", "", n);
	case msg_create: return tr("Successfully created the token key '%1'");
	}
	return pki_base::getMsg(msg);
}

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

	SQL_PREPARE(q, "INSERT INTO tokens (item, card_manufacturer, card_serial, "
					"card_model, card_label, slot_label, "
					"object_id) "
		  "VALUES (?, ?, ?, ?, ?, ?, ?)");
	q.bindValue(0, sqlItemId);
	q.bindValue(1, card_manufacturer);
	q.bindValue(2, card_serial);
	q.bindValue(3, card_model);
	q.bindValue(4, card_label);
	q.bindValue(5, slot_label);
	q.bindValue(6, object_id);
	q.exec();
	e = q.lastError();
	if (e.isValid())
		return e;
	SQL_PREPARE(q, "INSERT INTO token_mechanism (item, mechanism) "
		  "VALUES (?, ?)");
	q.bindValue(0, sqlItemId);
	foreach(CK_MECHANISM_TYPE m, mech_list) {
		q.bindValue(1, QVariant((uint)m));
		q.exec();
	}
	return q.lastError();
}

void pki_scard::restoreSql(const QSqlRecord &rec)
{
	pki_key::restoreSql(rec);
	card_manufacturer = rec.value(VIEW_tokens_card_manufacturer).toString();
	card_serial = rec.value(VIEW_tokens_card_serial).toString();
	card_model = rec.value(VIEW_tokens_card_model).toString();
	card_label = rec.value(VIEW_tokens_card_label).toString();
	slot_label = rec.value(VIEW_tokens_slot_label).toString();
	object_id = rec.value(VIEW_tokens_object_id).toString();
	card_manufacturer = rec.value(VIEW_tokens_card_manufacturer).toString();
	isPub = false;
	qDebug() << card_manufacturer <<card_serial<<card_model<<card_label<<slot_label<<object_id;
}

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

EVP_PKEY *pki_scard::load_pubkey(pkcs11 &p11, CK_OBJECT_HANDLE object) const
{
	unsigned long keytype;
	EVP_PKEY *pkey = NULL;

	pk11_attr_ulong type(CKA_KEY_TYPE);
	p11.loadAttribute(type, object);
	keytype = type.getValue();

	switch (keytype) {
	case CKK_RSA: {
		RSA *rsa = RSA_new();

		pk11_attr_data n(CKA_MODULUS);
		p11.loadAttribute(n, object);

		pk11_attr_data e(CKA_PUBLIC_EXPONENT);
		p11.loadAttribute(e, object);

		RSA_set0_key(rsa, n.getBignum(), e.getBignum(), NULL);

		pkey = EVP_PKEY_new();
		EVP_PKEY_assign_RSA(pkey, rsa);
		break;
	}
	case CKK_DSA: {
		DSA *dsa = DSA_new();

		pk11_attr_data p(CKA_PRIME);
		p11.loadAttribute(p, object);

		pk11_attr_data q(CKA_SUBPRIME);
		p11.loadAttribute(q, object);

		pk11_attr_data g(CKA_BASE);
		p11.loadAttribute(g, object);

		pk11_attr_data pub(CKA_VALUE);
		p11.loadAttribute(pub, object);

		DSA_set0_pqg(dsa, p.getBignum(), q.getBignum(), g.getBignum());
		DSA_set0_key(dsa, pub.getBignum(), NULL);

		pkey = EVP_PKEY_new();
		EVP_PKEY_assign_DSA(pkey, dsa);
		break;
	}
#ifndef OPENSSL_NO_EC
	case CKK_EC: {
		QByteArray ba;
		EC_GROUP *group;
		ASN1_OCTET_STRING *os;

		EC_KEY *ec = EC_KEY_new();

		pk11_attr_data grp(CKA_EC_PARAMS);
		p11.loadAttribute(grp, object);
		ba = grp.getData();
		group = (EC_GROUP *)
			d2i_bytearray(D2I_VOID(d2i_ECPKParameters), ba);
		pki_openssl_error();

		EC_GROUP_set_asn1_flag(group, 1);
		EC_KEY_set_group(ec, group);
		pki_openssl_error();

		pk11_attr_data pt(CKA_EC_POINT);
		p11.loadAttribute(pt, object);
		ba = pt.getData();
		os = (ASN1_OCTET_STRING *)
			d2i_bytearray(D2I_VOID(d2i_ASN1_OCTET_STRING), ba);
		pki_openssl_error();

		BIGNUM *bn = BN_bin2bn(os->data, os->length, NULL);
		pki_openssl_error();

		EC_POINT *point = EC_POINT_bn2point(group, bn, NULL, NULL);
		BN_free(bn);
		ASN1_OCTET_STRING_free(os);
		pki_openssl_error();

		EC_KEY_set_public_key(ec, point);
		pki_openssl_error();

		pkey = EVP_PKEY_new();
		EVP_PKEY_assign_EC_KEY(pkey, ec);
		break;
	}
#ifdef EVP_PKEY_ED25519
	case CKK_EC_EDWARDS: {
		QByteArray ba;
		ASN1_OCTET_STRING *os;
		pk11_attr_data grp(CKA_EC_PARAMS);
		p11.loadAttribute(grp, object);
		pk11_attr_data pt(CKA_EC_POINT);
		p11.loadAttribute(pt, object);
		ba = pt.getData();
		os = (ASN1_OCTET_STRING *)
			d2i_bytearray(D2I_VOID(d2i_ASN1_OCTET_STRING), ba);
		pki_openssl_error();
		pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
			(const uint8_t *)os->data,
			os->length);
		pki_openssl_error();
		ASN1_OCTET_STRING_free(os);
		pki_openssl_error();
		break;
	}
#endif
#endif
	default:
		throw errorEx(QString("Unsupported CKA_KEY_TYPE: %1\n").arg(keytype));
	}

	pki_openssl_error();
	return pkey;
}

void pki_scard::load_token(pkcs11 &p11, CK_OBJECT_HANDLE object)
{
	tkInfo ti = p11.tokenInfo();
	card_label = ti.label();
	card_manufacturer = ti.manufacturerID();
	card_serial = ti.serial();
	card_model = ti.model();
	pkiSource = token;
	isPub = false;

	pk11_attr_data id(CKA_ID);
	p11.loadAttribute(id, object);
	if (id.getAttribute()->ulValueLen > 0) {
		object_id = QString(id.getData().toHex());
	}

	try {
		pk11_attr_data label(CKA_LABEL);
		p11.loadAttribute(label, object);
		slot_label = label.getText();
	} catch (errorEx &err) {
		qDebug() << "No PubKey Label:" << err.getString();
		// ignore
	}
	if (slot_label.isEmpty()) {
		try{
			x509name xn;

			pk11_attr_data subj(CKA_SUBJECT);
			p11.loadAttribute(subj, object);
			QByteArray der = subj.getData();
			xn.d2i(der);
			slot_label = xn.getMostPopular();
			pki_openssl_error();
		} catch (errorEx &err) {
			qDebug() << "No Pubkey Subject:" << err.getString();
			// ignore
		}
	}
	EVP_PKEY *pkey = load_pubkey(p11, object);
	if (pkey) {
		if (key)
			EVP_PKEY_free(key);
		key = pkey;
	}
	setIntName(slot_label);
	pki_openssl_error();
}

pk11_attr_data pki_scard::getIdAttr() const
{
	pk11_attr_data id(CKA_ID);
	if (object_id.isEmpty())
		return id;

	QByteArray val = QByteArray::fromHex(object_id.toLocal8Bit());
	id.setValue(reinterpret_cast<const unsigned char*>(val.constData()), val.length());
	return id;
}

void pki_scard::deleteFromToken()
{
	slotid slot;

	if (!prepare_card(&slot))
		return;
	deleteFromToken(slot);
}

pk11_attlist pki_scard::objectAttributesNoId(EVP_PKEY *pk, bool priv) const
{
	QByteArray ba;
	const RSA *rsa;
	const DSA *dsa;
#ifndef OPENSSL_NO_EC
	const EC_KEY *ec;
#endif
	const BIGNUM *n = NULL;
	const BIGNUM *e = NULL;
	const BIGNUM *p = NULL;
	const BIGNUM *q = NULL;
	const BIGNUM *g = NULL;

	pk11_attlist attrs(pk11_attr_ulong(CKA_CLASS,
			priv ? CKO_PRIVATE_KEY : CKO_PUBLIC_KEY));

	switch (EVP_PKEY_type(EVP_PKEY_id(pk))) {
	case EVP_PKEY_RSA:
		rsa = EVP_PKEY_get0_RSA(pk);
		RSA_get0_key(rsa, &n, &e, NULL);
		attrs << pk11_attr_ulong(CKA_KEY_TYPE, CKK_RSA) <<
			pk11_attr_data(CKA_MODULUS, n) <<
			pk11_attr_data(CKA_PUBLIC_EXPONENT, e);
		break;
	case EVP_PKEY_DSA:
		dsa = EVP_PKEY_get0_DSA(pk);
		DSA_get0_pqg(dsa, &p, &q, &g);
		attrs << pk11_attr_ulong(CKA_KEY_TYPE, CKK_DSA) <<
			pk11_attr_data(CKA_PRIME, p) <<
			pk11_attr_data(CKA_SUBPRIME, q) <<
			pk11_attr_data(CKA_BASE, g);
		break;
#ifndef OPENSSL_NO_EC
	case EVP_PKEY_EC:
		ec = EVP_PKEY_get0_EC_KEY(pk);
		ba = i2d_bytearray(I2D_VOID(i2d_ECPKParameters),
				EC_KEY_get0_group(ec));

		attrs << pk11_attr_ulong(CKA_KEY_TYPE, CKK_EC) <<
			pk11_attr_data(CKA_EC_PARAMS, ba);
		break;
#ifdef EVP_PKEY_ED25519
	case EVP_PKEY_ED25519:
		attrs << pk11_attr_ulong(CKA_KEY_TYPE, CKK_EC_EDWARDS);
		// should it also return params, somehow?
		break;
#endif
#endif
	default:
		throw errorEx(QString("Unknown Keytype %d")
				.arg(EVP_PKEY_type(EVP_PKEY_id(pk))));

	}
	return attrs;
}

pk11_attlist pki_scard::objectAttributes(bool priv) const
{

	pk11_attlist attrs = objectAttributesNoId(key, priv);
	attrs << getIdAttr();
	return attrs;
}

void pki_scard::deleteFromToken(const slotid &slot)
{
	pkcs11 p11;
	p11.startSession(slot, true);

	tkInfo ti = p11.tokenInfo();
	if (!XCA_YESNO(tr("Delete the private key '%1' from the token '%2 (#%3)' ?").
			arg(getIntName()).arg(ti.label()).arg(ti.serial())))
		return;

	if (!p11.tokenLoginForModification())
		return;

	pk11_attlist atts = objectAttributes(true);
	QList<CK_OBJECT_HANDLE> priv_objects = p11.objectList(atts);
	atts = objectAttributes(false);
	QList<CK_OBJECT_HANDLE> pub_objects = p11.objectList(atts);

	p11.deleteObjects(priv_objects);
	p11.deleteObjects(pub_objects);
}

int pki_scard::renameOnToken(const slotid &slot, const QString &name)
{
	pkcs11 p11;
	p11.startSession(slot, true);
	QList<CK_OBJECT_HANDLE> objs;

	if (!p11.tokenLoginForModification())
		return 0;
	pk11_attr_data label(CKA_LABEL, name.toUtf8());

	/* Private key */
	pk11_attlist attrs = objectAttributes(true);

	objs = p11.objectList(attrs);
	if (!objs.count())
		return 0;
	p11.storeAttribute(label, objs[0]);

	/* Public key */
	attrs = objectAttributes(false);
	objs = p11.objectList(attrs);
	if (objs.count())
		p11.storeAttribute(label, objs[0]);

	return 1;
}

void pki_scard::store_token(const slotid &slot, EVP_PKEY *pkey)
{
	QByteArray ba;
	const RSA *rsa;
	const DSA *dsa;
#ifndef OPENSSL_NO_EC
	const EC_KEY *ec;
#endif
	pk11_attlist pub_atts;
	pk11_attlist priv_atts;
	QList<CK_OBJECT_HANDLE> objects;
	const BIGNUM *d = NULL;
	const BIGNUM *p = NULL;
	const BIGNUM *q = NULL;
	const BIGNUM *dmp1 = NULL;
	const BIGNUM *dmq1 = NULL;
	const BIGNUM *iqmp = NULL;
	const BIGNUM *priv_key = NULL;
	const BIGNUM *pub_key = NULL;

	pub_atts = objectAttributesNoId(pkey, false);
	priv_atts = objectAttributesNoId(pkey, true);

	pkcs11 p11;
	p11.startSession(slot, true);

	QList<CK_OBJECT_HANDLE> objs = p11.objectList(pub_atts);
	if (objs.count() == 0)
		objs = p11.objectList(priv_atts);
	if (objs.count() != 0) {
		XCA_INFO(tr("This Key is already on the token"));
		load_token(p11, objs[0]);
		return;
	}
	pk11_attr_data new_id = select_id(p11);

	pub_atts << new_id <<
		pk11_attr_bool(CKA_TOKEN, true) <<
		pk11_attr_data(CKA_LABEL, getIntName().toUtf8()) <<
		pk11_attr_bool(CKA_PRIVATE, false) <<
		pk11_attr_bool(CKA_WRAP, true) <<
		pk11_attr_bool(CKA_ENCRYPT, true) <<
		pk11_attr_bool(CKA_VERIFY, true);

	priv_atts << new_id <<
		pk11_attr_bool(CKA_TOKEN, true) <<
		pk11_attr_data(CKA_LABEL, desc.toUtf8()) <<
		pk11_attr_bool(CKA_PRIVATE, true) <<
		pk11_attr_bool(CKA_UNWRAP, true) <<
		pk11_attr_bool(CKA_DECRYPT, true) <<
		pk11_attr_bool(CKA_SIGN, true);

	switch (EVP_PKEY_type(EVP_PKEY_id(pkey))) {
	case EVP_PKEY_RSA:
		rsa = EVP_PKEY_get0_RSA(pkey);
		RSA_get0_key(rsa, NULL, NULL, &d);
		RSA_get0_factors(rsa, &p, &q);
		RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp);

		priv_atts <<
		pk11_attr_data(CKA_PRIVATE_EXPONENT, d) <<
		pk11_attr_data(CKA_PRIME_1, p) <<
		pk11_attr_data(CKA_PRIME_2, q) <<
		pk11_attr_data(CKA_EXPONENT_1, dmp1) <<
		pk11_attr_data(CKA_EXPONENT_2, dmq1) <<
		pk11_attr_data(CKA_COEFFICIENT, iqmp);
		break;
	case EVP_PKEY_DSA:
		dsa = EVP_PKEY_get0_DSA(pkey);
		DSA_get0_key(dsa, &pub_key, &priv_key);

		priv_atts << pk11_attr_data(CKA_VALUE, priv_key);
		pub_atts << pk11_attr_data(CKA_VALUE, pub_key);
		break;
#ifndef OPENSSL_NO_EC
	case EVP_PKEY_EC: {
		/* Public Key */
		BIGNUM *point;
		int size;
		unsigned char *buf;
		ASN1_OCTET_STRING *os;

		ec = EVP_PKEY_get0_EC_KEY(pkey);
		point = EC_POINT_point2bn(EC_KEY_get0_group(ec),
			EC_KEY_get0_public_key(ec),
			EC_KEY_get_conv_form(ec), NULL, NULL);

		pki_openssl_error();
		size = BN_num_bytes(point);
		buf = (unsigned char *)OPENSSL_malloc(size);
		Q_CHECK_PTR(buf);
		BN_bn2bin(point, buf);
		os = ASN1_OCTET_STRING_new();
		/* set0 -> ASN1_OCTET_STRING_free() also free()s buf */
		ASN1_STRING_set0(os, buf, size);
		ba = i2d_bytearray(I2D_VOID(i2d_ASN1_OCTET_STRING), os);
		ASN1_OCTET_STRING_free(os);
		BN_free(point);
		pki_openssl_error();
		pub_atts << pk11_attr_data(CKA_EC_POINT, ba);

		/* Private key */
		priv_atts << pk11_attr_data(CKA_VALUE,
					EC_KEY_get0_private_key(ec));
		break;
	}
#endif
	default:
		throw errorEx(QString("Unknown Keytype %d")
				.arg(EVP_PKEY_id(pkey)));

	}

	if (!p11.tokenLoginForModification())
		throw errorEx(tr("PIN input aborted"));

	try {
		p11.createObject(pub_atts);
	} catch (errorEx &e) {
		XCA_ERROR(e);
	}
	p11.createObject(priv_atts);

	pub_atts.reset();

	pub_atts = objectAttributesNoId(pkey, false);
	pub_atts << new_id;

	objs = p11.objectList(pub_atts);
	if (objs.count() == 0)
		throw errorEx(tr("Unable to find copied key on the token"));

	load_token(p11, objs[0]);
}

QList<int> pki_scard::possibleHashNids()
{
	QList<int> nids;

	if (!Settings["only_token_hashes"])
		return pki_key::possibleHashNids();

	foreach(CK_MECHANISM_TYPE mechanism, mech_list) {
		switch (EVP_PKEY_type(getKeyType())) {
		case EVP_PKEY_RSA:
			switch (mechanism) {
			case CKM_MD5_RSA_PKCS:    nids << NID_md5; break;
			case CKM_RIPEMD160_RSA_PKCS: nids << NID_ripemd160; break;
			case CKM_SHA1_RSA_PKCS:   nids << NID_sha1; break;
			case CKM_SHA256_RSA_PKCS: nids << NID_sha256; break;
			case CKM_SHA384_RSA_PKCS: nids << NID_sha384; break;
			case CKM_SHA512_RSA_PKCS: nids << NID_sha512; break;
			}
			break;
		case EVP_PKEY_DSA:
			switch (mechanism) {
			case CKM_DSA_SHA1:        nids << NID_sha1; break;
			}
			break;
#ifndef OPENSSL_NO_EC
		case EVP_PKEY_EC:
			switch (mechanism) {
			case CKM_ECDSA_SHA1:      nids << NID_sha1; break;
			}
			break;
#endif
		}
	}
	if (nids.count() == 0) {
		switch (EVP_PKEY_type(getKeyType())) {
		case EVP_PKEY_RSA:
			nids << NID_md5 << NID_sha1 << NID_sha256 <<
				NID_sha384 << NID_sha512 << NID_ripemd160;
			break;
		case EVP_PKEY_DSA:
#ifndef OPENSSL_NO_EC
		case EVP_PKEY_EC:
#endif
			nids << NID_sha1;
			break;
		}
	}
	return nids;
}

bool pki_scard::find_key_on_card(slotid *slot) const
{
	pkcs11 p11;
	slotid sl;

	pk11_attlist cls(pk11_attr_ulong(CKA_CLASS, CKO_PUBLIC_KEY));
	cls << getIdAttr();

	foreach(sl, p11.getSlotList()) {
		pkcs11 p11sess;
		p11sess.startSession(sl);

		foreach(CK_OBJECT_HANDLE object, p11sess.objectList(cls)) {
			EVP_PKEY *pkey = load_pubkey(p11sess, object);
			bool match = EVP_PKEY_cmp(key, pkey) == 1;
			EVP_PKEY_free(pkey);

			if (match) {
				*slot = sl;
				return true;
			}
		}
	}
	return false;
}

/* Assures the correct card is inserted and
 * returns the slot ID in slot true on success */
bool pki_scard::prepare_card(slotid *slot) const
{
	if (!pkcs11::libraries.loaded())
		return false;

	QString msg = tr("Please insert card: %1 %2 [%3] with Serial: %4").
			arg(card_manufacturer).arg(card_model).
			arg(card_label).arg(card_serial);
	do {
		try {
			if (find_key_on_card(slot))
				return true;
		} catch (errorEx &err) {
			qDebug() << "find_key_on_card:" << err.getString();
		} catch (...) {
			qDebug() << "find_key_on_card exception";
		}
	} while (XCA_OKCANCEL(msg));
	return false;
}

class keygenThread: public QThread
{
public:
	errorEx err;
	const keyjob task;
	QString name;
	pkcs11 *p11;
	pk11_attr_data id;

	keygenThread(const keyjob &t, const QString &n, pkcs11 *_p11,
				const pk11_attr_data &_id)
		: QThread(), task(t), name(n), p11(_p11), id(_id) { }

	void run()
	{
		try {
			id = p11->generateKey(name, task.ktype.mech, task.size,
						task.ec_nid, id);
		} catch (errorEx &e) {
			err = e;
		}
	}
};

pk11_attr_data pki_scard::select_id(const pkcs11 &p11) const
{
	tkInfo ti = p11.tokenInfo();
	pk11_attr_data new_id(CKA_ID);

	QList<QStringList> fixed_ids = ti.fixed_ids();
	if (fixed_ids.size() > 0) {
		QMap<QString, unsigned long> map;
		QStringList items;
		for (QStringList item : fixed_ids) {
			items << item[0];
			map[item[0]] = item[1].toULong();
		}
		QString idname = QInputDialog::getItem(nullptr, XCA_TITLE,
			tr("Select Slot of %1").arg(ti.model()),
			items, 0, false);
		if (map.contains(idname))
			new_id.setULong(map[idname]);
	} else {
		new_id = p11.findUniqueID(CKO_PUBLIC_KEY);
	}
	return new_id;
}

void pki_scard::generate(const keyjob &task)
{
	pk11_attlist atts;

	pkcs11 p11;
	p11.startSession(task.slot, true);
	p11.getRandom();

	pk11_attr_data new_id = select_id(p11);

	if (!p11.tokenLoginForModification())
		return;

	XcaProgress progress;
	keygenThread kt(task, getIntName(), &p11, new_id);
	kt.start();
	while (!kt.wait(20)) {
		progress.increment();
	}
	if (!kt.err.isEmpty())
		throw errorEx(kt.err);

	atts << pk11_attr_ulong(CKA_CLASS, CKO_PUBLIC_KEY) << kt.id;
	QList<CK_OBJECT_HANDLE> objects = p11.objectList(atts);
	if (objects.count() != 1)
		qCritical() << "OBJECTS found:" << objects.count();

	if (objects.count() == 0)
		throw errorEx(tr("Unable to find generated key on card"));

	load_token(p11, objects[0]);
}

pki_scard::~pki_scard()
{
}

QString pki_scard::getTypeString(void) const
{
	return tr("Token %1").arg(pki_key::getTypeString());
}

EVP_PKEY *pki_scard::decryptKey() const
{
	slotid slot_id;
	QString key_id;

	if (!prepare_card(&slot_id))
		throw errorEx(tr("Failed to find the key on the token"));

	pkcs11 *p11 = new pkcs11();
	p11->startSession(slot_id);
	if (p11->tokenLogin(card_label, false).isNull()) {
		delete p11;
		throw errorEx(tr("Invalid Pin for the token"));
	}
	pk11_attlist atts = objectAttributes(true);
	QList<CK_OBJECT_HANDLE> priv_objects = p11->objectList(atts);
	if (priv_objects.count() != 1) {
		delete p11;
		throw errorEx(tr("Failed to find the key on the token"));
	}
	EVP_PKEY *pkey = p11->getPrivateKey(key, priv_objects[0]);

	if (!pkey) {
		delete p11;
		throw errorEx(tr("Failed to initialize the key on the token"));
	}
	pki_openssl_error();
	return pkey;
}

void pki_scard::changePin()
{
	slotid slot;

	if (!prepare_card(&slot))
		return;

	pkcs11 p11;
	p11.changePin(slot, false);
}

void pki_scard::changeSoPin()
{
	slotid slot;

	if (!prepare_card(&slot))
		return;

	pkcs11 p11;
	p11.changePin(slot, true);
}

void pki_scard::initPin()
{
	slotid slot;

	if (!prepare_card(&slot))
		return;

	pkcs11 p11;
	p11.initPin(slot);
}

bool pki_scard::isToken()
{
	return true;
}

QVariant pki_scard::getIcon(const dbheader *hd) const
{
	return hd->id == HD_internal_name ?
		QVariant(QPixmap(":scardIco")) : QVariant();
}

bool pki_scard::visible() const
{
	QStringList sl;
	if (pki_base::visible())
		return true;

	sl << card_serial << card_manufacturer << card_model <<
		card_label << slot_label << object_id;
	foreach(QString s, sl) {
		if (s.contains(limitPattern))
			return true;
	}
	return false;
}

void pki_scard::updateLabel(QString label)
{
	XSqlQuery q;
	Transaction;

	if (slot_label == label)
		return;
	if (!TransBegin())
		return;
	slot_label = label;

	SQL_PREPARE(q, "UPDATE tokens SET slot_label=? WHERE item=?");
	q.bindValue(0, slot_label);
	q.bindValue(1, sqlItemId);
	q.exec();
	AffectedItems(sqlItemId);
	TransCommit();
}
