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


#include <typeinfo>

#include "pki_x509.h"
#include "pki_x509req.h"
#include "pki_evp.h"
#include "func.h"
#include "db_base.h"
#include "x509name.h"
#include "exception.h"
#include <openssl/bio.h>

pki_x509req::pki_x509req(const QString &name)
	: pki_x509super(name)
{
	request = X509_REQ_new();
	pki_openssl_error();
	pkiType=x509_req;
	done = false;
	resetX509count();
}

pki_x509req::pki_x509req(const pki_x509req *req)
	: pki_x509super(req)
{
	request = X509_REQ_dup(request);
	done = req->done;
	resetX509count();
	pki_openssl_error();
}

pki_x509req::~pki_x509req()
{
	if (request)
		X509_REQ_free(request);
}

QSqlError pki_x509req::insertSqlData()
{
	XSqlQuery q;
	QSqlError e = pki_x509super::insertSqlData();
	if (e.isValid())
		return e;
	SQL_PREPARE(q, "INSERT INTO requests (item, hash, signed, request) "
		  "VALUES (?, ?, ?, ?)");
	q.bindValue(0, sqlItemId);
	q.bindValue(1, hash());
	q.bindValue(2, done ? 1 : 0);
	q.bindValue(3, i2d_b64());
	q.exec();
	return q.lastError();
}

void pki_x509req::markSigned(bool signe)
{
	XSqlQuery q;
	Transaction;
	TransThrow();

	SQL_PREPARE(q, "UPDATE requests SET signed=? WHERE item=?");
	q.bindValue(0, signe ? 1 : 0);
	q.bindValue(1, sqlItemId);
	q.exec();

	if (q.lastError().isValid())
		return;
	done = signe;
	AffectedItems(sqlItemId);
	TransCommit();
}

void pki_x509req::restoreSql(const QSqlRecord &rec)
{
	pki_x509super::restoreSql(rec);
	QByteArray ba = QByteArray::fromBase64(
				rec.value(VIEW_x509req_request).toByteArray());
	d2i(ba);
	done = rec.value(VIEW_x509req_signed).toBool();
}

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

void pki_x509req::createReq(pki_key *key, const x509name &dn,
				const digest &digest, extList el)
{
	QList<int> bad_nids; bad_nids << NID_authority_key_identifier <<
		NID_issuer_alt_name << NID_undef;

	EVP_PKEY *privkey = NULL;

	if (key->isPubKey()) {
		my_error(tr("Signing key not valid (public key)"));
		return;
	}

	X509_REQ_set_version(request, 0L);
	X509_REQ_set_pubkey(request, key->getPubKey());
	setSubject(dn);
	pki_openssl_error();

	foreach(int nid , bad_nids)
		el.delByNid(nid);

	el.delInvalid();

	if (el.count() > 0) {
		STACK_OF(X509_EXTENSION) *sk;
		sk = el.getStack();
		X509_REQ_add_extensions(request, sk);
		sk_X509_EXTENSION_pop_free(sk, X509_EXTENSION_free);
	}
	pki_openssl_error();

	privkey = key->decryptKey();
	X509_REQ_sign(request, privkey, digest.MD());
	pki_openssl_error();
	EVP_PKEY_free(privkey);
}

QString pki_x509req::getMsg(msg_type msg) const
{
	/*
	 * We do not construct english sentences from fragments
	 * to allow proper translations.
	 * The drawback are all the slightly different duplicated messages
	 *
	 * %1 will be replaced by either "SPKAC" or "PKCS#10"
	 * %2 will be replaced by the internal name of the request
	 */

	QString type = "PKCS#10";

	switch (msg) {
	case msg_import: return tr("Successfully imported the %1 certificate request '%2'").arg(type);
	case msg_delete: return tr("Delete the %1 certificate request '%2'?").arg(type);
	case msg_create: return tr("Successfully created the %1 certificate request '%2'").arg(type);
	/* %1: Number of requests; %2: list of request names */
	case msg_delete_multi: return tr("Delete the %1 certificate requests: %2?");
	}
	return pki_base::getMsg(msg);
}

void pki_x509req::fromPEM_BIO(BIO *bio, const QString &name)
{
	X509_REQ *req;
	req = PEM_read_bio_X509_REQ(bio, NULL, NULL, NULL);
	openssl_error_msg(name);
	if (!req)
		throw errorEx();
	X509_REQ_free(request);
	request = req;
}

void pki_x509req::fload(const QString &fname)
{
	X509_REQ *_req;
	XFile file(fname);
	file.open_read();
	QByteArray ba(file.readAll());

	_req = PEM_read_bio_X509_REQ(BioByteArray(ba).ro(), NULL, NULL, NULL);
	if (!_req) {
		pki_ign_openssl_error();
		_req = d2i_X509_REQ_bio(BioByteArray(ba).ro(), NULL);
	}
	if (pki_ign_openssl_error() || !_req) {
		if (_req)
			X509_REQ_free(_req);
		throw errorEx(tr("Unable to load the certificate request in file %1. Tried PEM, DER and SPKAC format.").arg(fname));
	}

	X509_REQ_free(request);
	request = _req;
}

void pki_x509req::d2i(QByteArray &ba)
{
	X509_REQ *r= (X509_REQ*)d2i_bytearray(D2I_VOID(d2i_X509_REQ), ba);
	if (r) {
		X509_REQ_free(request);
		request = r;
	}
}

QByteArray pki_x509req::i2d() const
{
	return i2d_bytearray(I2D_VOID(i2d_X509_REQ), request);
}

void pki_x509req::addAttribute(int nid, QString content)
{
	if (content.isEmpty())
		return;

	ASN1_STRING *a = QStringToAsn1(content, nid);
	X509_REQ_add1_attr_by_NID(request, nid, a->type, a->data, a->length);
	ASN1_STRING_free(a);
	openssl_error_msg(QString("'%1' (%2)").arg(content).arg(OBJ_nid2ln(nid)));
}

x509name pki_x509req::getSubject() const
{
	x509name x(X509_REQ_get_subject_name(request));
	pki_openssl_error();
	return x;
}

int pki_x509req::sigAlg() const
{
	return X509_REQ_get_signature_nid(request);
}

void pki_x509req::setSubject(const x509name &n)
{
	X509_REQ_set_subject_name(request, n.get());
}

void pki_x509req::writeDefault(const QString &dirname) const
{
	XFile file(get_dump_filename(dirname, ".csr"));
	file.open_write();
	writeReq(file, true);
}

void pki_x509req::writeReq(XFile &file, bool pem) const
{
	BioByteArray b;
	if (!request)
		return;
	if (pem) {
		b += PEM_comment();
		PEM_write_bio_X509_REQ(b, request);
	} else {
		i2d_X509_REQ_bio(b, request);
	}
	pki_openssl_error();
	file.write(b);
}

bool pki_x509req::pem(BioByteArray &b)
{
	return PEM_write_bio_X509_REQ(b, request);
}

bool pki_x509req::verify() const
{
	EVP_PKEY *pkey = X509_REQ_get_pubkey(request);
	bool x = X509_REQ_verify(request,pkey) > 0;
	pki_ign_openssl_error();
	EVP_PKEY_free(pkey);
	return x;
}

pki_key *pki_x509req::getPubKey() const
{
	 EVP_PKEY *pkey = X509_REQ_get_pubkey(request);
	 pki_ign_openssl_error();
	 if (pkey == NULL)
		 return NULL;
	 pki_evp *key = new pki_evp(pkey);
	 pki_openssl_error();
	 return key;
}

extList pki_x509req::getV3ext() const
{
	extList el;
	STACK_OF(X509_EXTENSION) *sk;
	sk = X509_REQ_get_extensions(request);
	el.setStack(sk);
	sk_X509_EXTENSION_pop_free(sk, X509_EXTENSION_free);
	return el;
}

QString pki_x509req::getAttribute(int nid) const
{
	int n;
	int count;
	QStringList ret;

	n = X509_REQ_get_attr_by_NID(request, nid, -1);
	if (n == -1)
		return QString("");
	X509_ATTRIBUTE *att = X509_REQ_get_attr(request, n);
	if (!att)
		return QString("");
	count = X509_ATTRIBUTE_count(att);
	for (int j = 0; j < count; j++)
		ret << asn1ToQString(X509_ATTRIBUTE_get0_type(att, j)->
				             value.asn1_string);
	return ret.join(", ");
}

int pki_x509req::issuedCerts() const
{
	XSqlQuery q;
	int count = 0;

	if (x509count != -1)
		return x509count;

	pki_key *k = getPubKey();
	if (!k)
		return 0;
	QList<pki_x509 *> certs = Store.sqlSELECTpki<pki_x509>(
		"SELECT item FROM x509super LEFT JOIN items ON items.id = x509super.item "
		"WHERE key_hash=? AND items.type=?",
		QList<QVariant>() << QVariant(pubHash()) << QVariant(x509));

	foreach(pki_x509 *x, certs) {
		if (x->compareRefKey(k))
			count++;

		qDebug() << "Req:" << getIntName() << "Cert with hash"
			 << x->getIntName() << count;
	}
	delete k;
	x509count = count;
	return count;
}

void pki_x509req::collect_properties(QMap<QString, QString> &prp) const
{
	QString s = getAttribute(NID_pkcs9_unstructuredName);
	if (!s.isEmpty())
		prp["Unstructured Name"] = s;

	s = getAttribute(NID_pkcs9_challengePassword);
	if (!s.isEmpty())
		prp["Challange Password"] = s;

	pki_x509super::collect_properties(prp);
	prp["Verify Ok"] = verify() ? "Yes" : "No";
}

void pki_x509req::print(BioByteArray &bba, enum print_opt opt) const
{
	pki_x509super::print(bba, opt);
	switch (opt) {
	case print_openssl_txt:
		X509_REQ_print(bba, request);
		break;
	case print_pem:
		PEM_write_bio_X509_REQ(bba, request);
		break;
	case print_coloured:
		break;
	}
}

QVariant pki_x509req::column_data(const dbheader *hd) const
{
	switch (hd->id) {
	case HD_req_signed:
		return QVariant(done ? tr("Signed") : tr("Unhandled"));
	case HD_req_unstr_name:
		return getAttribute(NID_pkcs9_unstructuredName);
	case HD_req_chall_pass:
		return getAttribute(NID_pkcs9_challengePassword);
	case HD_req_certs:
		return QVariant(issuedCerts());
	}
	return pki_x509super::column_data(hd);
}

QVariant pki_x509req::getIcon(const dbheader *hd) const
{
	switch (hd->id) {
	case HD_internal_name:
		return QVariant(QPixmap(hasPrivKey() ? ":reqkeyIco" : ":reqIco"));
	case HD_req_signed:
		if (done)
			return QVariant(QPixmap(":doneIco"));
		break;
	default:
		return pki_x509super::getIcon(hd);
	}
	return QVariant();
}

bool pki_x509req::visible() const
{
	if (pki_x509super::visible())
		return true;
	if (getAttribute(NID_pkcs9_unstructuredName).contains(limitPattern))
		return true;
	if (getAttribute(NID_pkcs9_challengePassword).contains(limitPattern))
		return true;
	return false;
}
