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

#include "x509v3ext.h"
#include "x509name.h"
#include "asn1int.h"
#include "func.h"
#include "exception.h"
#include <openssl/x509v3.h>
#include <openssl/stack.h>
#include <QStringList>
#include <QRegularExpression>
#include <QDebug>
#include "base.h"
#include "BioByteArray.h"

x509v3ext::x509v3ext()
{
}

x509v3ext::x509v3ext(const X509_EXTENSION *n)
{
	if (n) {
		ext = X509_EXTENSION_dup((X509_EXTENSION *)n);
		Q_CHECK_PTR(ext);
	}
}

x509v3ext::x509v3ext(const x509v3ext &n)
{
	set(n.ext);
}

x509v3ext::x509v3ext(int nid, const QString &et, X509V3_CTX *ctx)
{
	create(nid, et, ctx);
}

x509v3ext::~x509v3ext()
{
	if (ext)
		X509_EXTENSION_free(ext);
}

x509v3ext &x509v3ext::set(const X509_EXTENSION *n)
{
	if (n) {
		ASN1_OCTET_STRING *str = X509_EXTENSION_get_data((X509_EXTENSION *)n);
		if (!str || !str->length)
			n = nullptr;
	}
	if (ext != nullptr)
		X509_EXTENSION_free(ext);
	ext = n ? X509_EXTENSION_dup((X509_EXTENSION *)n) : nullptr;
	openssl_error();
	return *this;
}

x509v3ext &x509v3ext::create(int nid, const QString &et, X509V3_CTX *ctx)
{
	if (ext) {
		X509_EXTENSION_free(ext);
		ext = nullptr;
	}
	openssl_error();
	if (!et.isEmpty()) {
		QString etext = et;
		if (et.contains("DNS:copycn") && ctx && ctx->subject_cert &&
		    nid == NID_subject_alt_name)
		{
			x509name xn(X509_get_subject_name(ctx->subject_cert));
			QStringList new_san;
			int i, max = xn.entryCount();
			for (i=0; i < max; i++) {
				if (xn.nid(i) == NID_commonName)
					new_san << QString("DNS:%1").arg(xn.getEntry(i));
			}
			qDebug() << "COUNT" << new_san.size() << new_san.join(",");
			if (new_san.size() > 0)
				etext.replace(QString("DNS:copycn"), new_san.join(","));
		}
		if (nid == NID_subject_alt_name || nid == NID_issuer_alt_name || nid == NID_name_constraints) {
			QStringList sl = etext.split(",");
			QRegularExpression match("([a-z]+;)*UPN:");
			for (int i=0; i<sl.size(); i++) {
				QString s = sl[i].trimmed();
				sl[i] = s.replace(match, "\\1otherName:msUPN;UTF8:");
			}
			etext = sl.join(",");
		}
		QByteArray ba = etext.toLocal8Bit();
		ext = X509V3_EXT_conf_nid(NULL, ctx, nid, ba.data());
	}
	if (ext) {
		if (ctx && ctx->subject_cert) {
			X509_add_ext(ctx->subject_cert, ext, -1);
		}
	}
	ign_openssl_error();
	return *this;
}

x509v3ext &x509v3ext::create_ia5(int nid, const QString &et, X509V3_CTX *ctx)
{
	QByteArray ba = et.toLocal8Bit();
	for (int i=0; i<ba.size(); i++) {
		if (ba[i] & 0x80)
			throw errorEx(QObject::tr("String '%1' for '%2' contains invalid characters").arg(et).arg(OBJ_nid2ln(nid)));
	}
	return create(nid, et, ctx);
}

const ASN1_OBJECT *x509v3ext::object() const
{
	ASN1_OBJECT *obj = nullptr;
	if (ext) {
		obj = X509_EXTENSION_get_object(ext);
		ign_openssl_error();
	}
	return obj;
}

int x509v3ext::nid() const
{
	const ASN1_OBJECT *obj = object();
	if (!obj)
		return NID_undef;
	int nid = OBJ_obj2nid(obj);
	if (nid == NID_undef) {
		QString o = OBJ_obj2QString(obj, 1);
		if (!o.isEmpty())
			nid = OBJ_create(CCHAR(o), CCHAR(o), CCHAR(o));
		openssl_error();
	}
	return nid;
}

void *x509v3ext::d2i() const
{
	if (!ext)
		return nullptr;

	void *r = X509V3_EXT_d2i(ext);
	ign_openssl_error();
	return r;
}

x509v3ext &x509v3ext::operator = (const x509v3ext &x)
{
	set(x.ext);
	return *this;
}

QString x509v3ext::getObject() const
{
	const ASN1_OBJECT *obj = object();
	return obj ? OBJ_obj2QString(obj) : QString("INVALID");
}

int x509v3ext::getCritical() const
{
	return ext ? X509_EXTENSION_get_critical(ext) : 0;
}

ASN1_OCTET_STRING *x509v3ext::getData() const
{
	return ext ? X509_EXTENSION_get_data(ext) : nullptr;;
}

QString x509v3ext::getValue() const
{
	BioByteArray bba;
	if (!isValid())
		return QString();
	int ret = X509V3_EXT_print(bba, ext, X509V3_EXT_DEFAULT, 0);
	if (ign_openssl_error() || !ret)
		ret = ASN1_STRING_print(bba, (ASN1_STRING *)getData());
	if (ign_openssl_error() || !ret)
		return QString();
	return bba.qstring().trimmed();
}

QString x509v3ext::getHtmlValue() const
{
	QString text = getValue();
	text.replace(QRegularExpression("&"), "&amp;");
	text.replace(QRegularExpression("<"), "&lt;");
	text.replace(QRegularExpression(">"), "&gt;");
	text.replace(QRegularExpression("\n"), "<br>\n");
	return text;
}

QString x509v3ext::getConsoleValue(const QString &indent) const
{
	QString text = getValue();
	text.replace(QRegularExpression("\n"), QString("\n") + indent);
	return text;
}

static QString vlist2Section(QStringList vlist, QString tag, QString *sect)
{
	/* Check for commas in the text */
	if (!vlist.join("").contains(","))
		return vlist.join(", ");

	*sect += QString("\n[%1_sect]\n").arg(tag);

	for (int i=0; i<vlist.count(); i++) {
		QString s = vlist[i];
		int eq = s.indexOf(":");
		*sect += QString("%1.%2=%3\n").arg(s.left(eq)).
			arg(i).arg(s.mid(eq+1));
	}
	return QString("@%1_sect\n").arg(tag);
}

static QString obj2SnOid(const ASN1_OBJECT *a)
{
	QString obj;
	int nid = OBJ_obj2nid(a);

	if (nid == NID_undef)
		obj = OBJ_obj2QString(a);
	else
		obj = OBJ_nid2sn(nid);

	return obj;
}

static const char *asn1Type2Name(int type)
{
#define ASN1_GEN_STR(x,y) { x,y }
	struct {
		const char *strnam;
		int tag;
	} tags[] = {
		ASN1_GEN_STR("BOOL", V_ASN1_BOOLEAN),
		ASN1_GEN_STR("NULL", V_ASN1_NULL),
		ASN1_GEN_STR("INT", V_ASN1_INTEGER),
		ASN1_GEN_STR("ENUM", V_ASN1_ENUMERATED),
		ASN1_GEN_STR("OID", V_ASN1_OBJECT),
		ASN1_GEN_STR("UTC", V_ASN1_UTCTIME),
		ASN1_GEN_STR("GENTIME", V_ASN1_GENERALIZEDTIME),
		ASN1_GEN_STR("OCT", V_ASN1_OCTET_STRING),
		ASN1_GEN_STR("BITSTR", V_ASN1_BIT_STRING),
		ASN1_GEN_STR("UNIV", V_ASN1_UNIVERSALSTRING),
		ASN1_GEN_STR("IA5", V_ASN1_IA5STRING),
		ASN1_GEN_STR("UTF8", V_ASN1_UTF8STRING),
		ASN1_GEN_STR("BMP", V_ASN1_BMPSTRING),
		ASN1_GEN_STR("VISIBLE", V_ASN1_VISIBLESTRING),
		ASN1_GEN_STR("PRINTABLE", V_ASN1_PRINTABLESTRING),
		ASN1_GEN_STR("T61", V_ASN1_T61STRING),
		ASN1_GEN_STR("GENSTR", V_ASN1_GENERALSTRING),
		ASN1_GEN_STR("NUMERIC", V_ASN1_NUMERICSTRING),
	};
	for (unsigned i=0; i< ARRAY_SIZE(tags); i++) {
		if (tags[i].tag == type)
			return tags[i].strnam;
	}
	return "UNKNOWN";
}

static bool asn1TypePrintable(int type)
{
	switch (type) {
	case V_ASN1_IA5STRING:
	case V_ASN1_UTF8STRING:
	case V_ASN1_BMPSTRING:
	case V_ASN1_VISIBLESTRING:
	case V_ASN1_PRINTABLESTRING:
	case V_ASN1_T61STRING:
	case V_ASN1_GENERALSTRING:
		return true;
	}
	return false;
}

static QString ipv6_from_binary(const unsigned char *p)
{
	QString ip;
	int i, skip =0, skiplen = 0, skippos =0;

	/* find largest gap */
	for (i = 0; i < 17; i += 2) {
		if (i==16 || (p[i] | p[i +1])) {
			if (skiplen < skip) {
				skiplen = skip;
				skippos = i - skip;
			}
			skip = 0;
		} else {
			skip += 2;
		}
	}
	for (i = 0, skip = 0; i < 16; i += 2) {
		int x = p[i] << 8 | p[i+1];
		skip += skippos == i;
		switch (!x*4 + skip) {
		case 5: // skip first 0
			skip = 2;
			ip += ":";
		case 6: // skip next 0
			break;
		default: // no reduction
			skip = 0;
			ip += QString("%1%2").arg(i? ":" : "").arg(x,0,16);
		}
	}
	if (skip == 2)
		ip += ":";
	return ip;
}

static bool
genName2conf(GENERAL_NAME *gen, QString tag, QString *single, QString *sect)
{
	unsigned char *p;
	QString ret;

	switch (gen->type) {
	case GEN_EMAIL: ret = "email:%1"; break;
	case GEN_DNS:   ret = "DNS:%1"; break;
	case GEN_URI:   ret = "URI:%1"; break;

	case GEN_DIRNAME: {
		tag += "_dirname";
		x509name xn(gen->d.dirn);
		*sect += QString("\n[%1]\n"). arg(tag);
		*sect += xn.taggedValues();
		*single = QString("dirName:") + tag;
		return true;
	}
	case GEN_IPADD:
		p = gen->d.ip->data;
		if (gen->d.ip->length == 4) {
			*single = QString("IP:%1.%2.%3.%4").
				arg(p[0]).arg(p[1]).arg(p[2]).arg(p[3]);
			return true;
		} else if(gen->d.ip->length == 8) {
			*single = QString("IP:%1.%2.%3.%4/%5.%6.%7.%8").
			                    arg(p[0]).arg(p[1]).arg(p[2]).arg(p[3]).
			                    arg(p[4]).arg(p[5]).arg(p[6]).arg(p[7]);
			return true;
		} else if(gen->d.ip->length == 16) {
			*single = "IP:" + ipv6_from_binary(gen->d.ip->data);
			return true;
		} else if(gen->d.ip->length == 32) {
			*single = "IP:" + ipv6_from_binary(gen->d.ip->data) +
				"/" + ipv6_from_binary(gen->d.ip->data +16);
			return true;
		}
		return false;

	case GEN_RID:
		*single = QString("RID:%1").
				arg(obj2SnOid(gen->d.rid));
		return true;
	case GEN_OTHERNAME: {
		int type = gen->d.otherName->value->type;
		ASN1_STRING *a;
		a = gen->d.otherName->value->value.asn1_string;
		if (asn1TypePrintable(type)) {
			*single = QString("otherName:%1;%2:%3").
			arg(obj2SnOid(gen->d.otherName->type_id)).
			arg(asn1Type2Name(type)).
			arg(asn1ToQString(a, true));
		} else {
			*single = QString("otherName:%1;FORMAT:HEX,%2").
			arg(obj2SnOid(gen->d.otherName->type_id)).
			arg(asn1Type2Name(type));
			for (int i=0; i<a->length; i++) {
				*single += QString(":%1").
				arg((int)(a->data[i]), 2, 16, QChar('0'));
			}
		}
		return true;
	}
	default:
		return false;
	}
	if (!ret.isEmpty())
		*single = ret.arg(asn1ToQString(gen->d.ia5, true));
	return true;
}

static bool genNameStack2conf(STACK_OF(GENERAL_NAME) *gens, QString tag,
				QString *single, QString *sect)
{
	int i;
	QStringList sl;
	for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) {
		QString one;
		if (!genName2conf(sk_GENERAL_NAME_value(gens, i),
			QString("%1_%2").arg(tag).arg(i), &one, sect))
		{
			return false;
		}
		sl << one;
	}
	*single = vlist2Section(sl, tag, sect);
	return true;
}

QString x509v3ext::parse_critical() const
{
	return QString(getCritical() ? "critical," : "");
}

#define TEXTS (\
	B_ASN1_TIME | B_ASN1_DIRECTORYSTRING | B_ASN1_DISPLAYTEXT | \
	B_ASN1_NUMERICSTRING | B_ASN1_T61STRING | B_ASN1_UNIVERSALSTRING)

bool x509v3ext::parse_ia5(QString *single, QString *adv) const
{
	ASN1_STRING *str = (ASN1_STRING *)d2i();
	QString ret;

	if (!isValid())
		return false;

	if (!str) {
		const unsigned char *p = getData()->data;
		str = d2i_ASN1_OCTET_STRING(NULL, &p, getData()->length);
		if (ign_openssl_error() || !str)
			return false;
		ret = QString("<ERROR: NOT IA5 but %1>%2").
			arg(asn1Type2Name(str->type)).
			arg(QString(asn1ToQString(str)));
	} else {
		ret = QString(asn1ToQString(str));
	}
	if (single)
		*single = ret;
	else if (adv)
		*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).arg(ret) +*adv;

	ASN1_STRING_free(str);
	return true;
}

bool x509v3ext::parse_generalName(QString *single, QString *adv) const
{
	bool retval = true;
	QString sect, ret;
	STACK_OF(GENERAL_NAME) *gens = (STACK_OF(GENERAL_NAME) *)d2i();

	if (!gens)
		return false;

	QString tag = OBJ_nid2sn(nid());

	if (!genNameStack2conf(gens, tag, &ret, &sect))
		retval = false;
	else if (sect.isEmpty() && single) {
		*single = parse_critical() + ret;
	} else if (adv) {
		*adv = QString("%1=%2\n").arg(tag).
			arg(parse_critical() +ret) + *adv + sect;
	}
	sk_GENERAL_NAME_free(gens);
	return retval;
}

bool x509v3ext::parse_eku(QString *single, QString *adv) const
{
	EXTENDED_KEY_USAGE *eku = (EXTENDED_KEY_USAGE *)d2i();
	QStringList sl;
	int i;

	if (!eku)
		return false;

	for (i = 0; i < sk_ASN1_OBJECT_num(eku); i++) {
		sl << QString(OBJ_obj2sn(sk_ASN1_OBJECT_value(eku, i)));
	}
	QString r = parse_critical() + sl.join(", ");
	if (single)
		*single = r;
	else if (adv)
		*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).arg(r) + *adv;

	EXTENDED_KEY_USAGE_free(eku);
	return true;
}

bool x509v3ext::parse_ainfo(QString *single, QString *adv) const
{
	bool retval = true;
	QString sect, ret;
	QString tag = OBJ_nid2sn(nid());
	QStringList sl;
	int i;

	AUTHORITY_INFO_ACCESS *ainfo = (AUTHORITY_INFO_ACCESS *)d2i();

	if (!ainfo)
		return false;

	for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ainfo); i++) {
		QString one;
		ACCESS_DESCRIPTION *desc = sk_ACCESS_DESCRIPTION_value(ainfo, i);
		if (!genName2conf(desc->location,
			QString("%1_%2").arg(tag).arg(i), &one, &sect))
		{
			retval = false;
			break;
		}
		sl << QString("%1;%2").arg(OBJ_obj2sn(desc->method)).arg(one);
	}
	if (retval) {
		ret = vlist2Section(sl, tag, &sect);
		if (sect.isEmpty() && sk_ACCESS_DESCRIPTION_num(ainfo) == 1 && single) {
			*single = parse_critical() + ret;
		} else if (adv) {
			*adv = QString("%1=%2\n").arg(tag).
				arg(parse_critical() + ret) + *adv + sect;
		}
	}
	AUTHORITY_INFO_ACCESS_free(ainfo);
	return retval;
}

static const BIT_STRING_BITNAME reason_flags[] = {
{0, "", "unused"},
{1, "", "keyCompromise"},
{2, "", "CACompromise"},
{3, "", "affiliationChanged"},
{4, "", "superseded"},
{5, "", "cessationOfOperation"},
{6, "", "certificateHold"},
{7, "", "privilegeWithdrawn"},
{8, "", "AACompromise"},
{-1, NULL, NULL}
};

static QString parse_bits(const BIT_STRING_BITNAME *flags,
				ASN1_BIT_STRING *str)
{
	const BIT_STRING_BITNAME *pbn;
	QStringList r;
	for (pbn = flags; pbn->sname; pbn++) {
		if (ASN1_BIT_STRING_get_bit(str, pbn->bitnum))
			r << QString(pbn->sname);
	}
	return r.join(", ");
}

bool x509v3ext::parse_Crldp(QString *single, QString *adv) const
{
	QString othersect;
	QStringList crldps;
	const char *sn = OBJ_nid2sn(nid());

	STACK_OF(DIST_POINT) *crld = (STACK_OF(DIST_POINT)*)d2i();

	if (!crld)
		return false;

	if (sk_DIST_POINT_num(crld) == 1 && single) {
		DIST_POINT *point = sk_DIST_POINT_value(crld, 0);
		if (point->distpoint && !point->reasons && !point->CRLissuer &&
		    !point->distpoint->type)
		{
			QString sect, ret;
			if (!genNameStack2conf(point->distpoint->name.fullname,
						"", &ret, &sect))
				goto could_not_parse;

			if (sect.isEmpty()) {
				if (single)
					*single = parse_critical() +ret;
				else if (adv)
					*adv = QString("%1=%2\n").arg(sn).
					       arg(parse_critical() +ret) +*adv;
				return true;
			}
		}
	}
	for(int i = 0; i < sk_DIST_POINT_num(crld); i++) {
		DIST_POINT *point = sk_DIST_POINT_value(crld, i);
		QString tag = QString("crlDistributionPoint%1_sect").arg(i);
		QString crldpsect = QString("\n[%1]\n").arg(tag);
		if (point->distpoint) {
			if (!point->distpoint->type) {
				QString ret;
				if (!genNameStack2conf(point->distpoint->name.fullname,
						tag + "_fullname", &ret, &othersect))
					goto could_not_parse;

				crldpsect += "fullname=" + ret +"\n";
			} else {
				QString mysect = tag + "_relativename";
				x509name xn(point->distpoint->name.relativename);
				crldpsect += "relativename=" + mysect + "\n";
				othersect += QString("\n[%1]\n").arg(mysect) +
						xn.taggedValues();
			}
		}
		if (point->reasons) {
			crldpsect += QString("reasons=%1\n").
				arg(parse_bits(reason_flags, point->reasons));
		}
		if (point->CRLissuer) {
			QString ret;
			if (genNameStack2conf(point->CRLissuer,
					tag +"_crlissuer", &ret, &othersect))
				goto could_not_parse;
			crldpsect += "CRLissuer=" + ret + "\n";
		}
		crldps << tag;
		othersect = crldpsect + othersect;
	}
	sk_DIST_POINT_free(crld);
	if (crldps.size() == 0)
		return true;
	if (adv) {
		*adv = QString("%1=%2\n").arg(sn).
			arg(parse_critical() + crldps.join(", ")) +
			*adv + othersect;
	}
	return true;

could_not_parse:
	sk_DIST_POINT_free(crld);
	return false;
}

static void gen_cpol_notice(QString tag, USERNOTICE *notice, QString *adv)
{
	*adv += QString("\n[%1]\n").arg(tag);
	if (notice->exptext) {
		*adv += QString("explicitText=%1\n").
				arg(asn1ToQString(notice->exptext, true));
	}
	if (notice->noticeref) {
		NOTICEREF *ref = notice->noticeref;
		QStringList sl;
		int i;
		*adv += QString("organization=%1\n").
		          arg(asn1ToQString(ref->organization, true));
		for (i = 0; i < sk_ASN1_INTEGER_num(ref->noticenos); i++) {
			a1int num(sk_ASN1_INTEGER_value(ref->noticenos, i));
			sl << num.toDec();
		}
		if (sl.size())
			*adv += QString("noticeNumbers=%1\n").
					arg(sl.join(", "));
	}
}

static bool gen_cpol_qual_sect(QString tag, POLICYINFO *pinfo, QString *adv)
{
	QString polsect = QString("\n[%1]\n").arg(tag);
	QString noticetag, _adv;
	STACK_OF(POLICYQUALINFO) *quals = pinfo->qualifiers;
	int i;

	if (!quals)
		return false;

	if (!adv)
		adv = &_adv;

	polsect += QString("policyIdentifier=%1\n").
			arg(obj2SnOid(pinfo->policyid));

	for (i = 0; i < sk_POLICYQUALINFO_num(quals); i++) {
		POLICYQUALINFO *qualinfo = sk_POLICYQUALINFO_value(quals, i);
		switch (OBJ_obj2nid(qualinfo->pqualid)) {
		case NID_id_qt_cps:
			polsect += QString("CPS.%1=%2\n").arg(i).
					arg(asn1ToQString(qualinfo->d.cpsuri, true));
			break;
		case NID_id_qt_unotice:
			noticetag = QString("%1_notice%2_sect").arg(tag).arg(i);
			polsect += QString("userNotice.%1=@%2\n").arg(i).
					arg(noticetag);
			gen_cpol_notice(noticetag, qualinfo->d.usernotice, adv);
			break;
		default:
			return false;
		}
	}
	*adv = polsect + *adv;
	return true;
}


bool x509v3ext::parse_certpol(QString *, QString *adv) const
{
	bool retval = true;
	QStringList pols;
	QString myadv;
	int i;
	STACK_OF(POLICYINFO) *pol = (STACK_OF(POLICYINFO) *)d2i();

	if (!pol)
		return false;

	for (i = 0; i < sk_POLICYINFO_num(pol); i++) {
		POLICYINFO *pinfo = sk_POLICYINFO_value(pol, i);
		if (!pinfo->qualifiers) {
			pols << obj2SnOid(pinfo->policyid);
			continue;
		}
		QString tag = QString("certpol%1_sect").arg(i);
		pols << QString("@") + tag;
		if (!gen_cpol_qual_sect(tag, pinfo, &myadv)) {
			retval = false;
			break;
		}
	}
	if (retval && adv)
		*adv = QString("certificatePolicies=%1ia5org,%2\n").
		arg(parse_critical()).arg(pols.join(", ")) + *adv + myadv;
	sk_POLICYINFO_free(pol);
	return retval;
}

bool x509v3ext::parse_bc(QString *single, QString *adv) const
{
	BASIC_CONSTRAINTS *bc = (BASIC_CONSTRAINTS *)d2i();

	if (!bc)
		return false;

	QString ret = a1int(bc->pathlen).toDec();
	if (!ret.isEmpty())
		ret = ",pathlen:" + ret;
	ret = parse_critical() + (bc->ca ? "CA:TRUE" : "CA:FALSE") + ret;
	if (single)
		*single = ret;
	else if (adv)
		*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).arg(ret) +*adv;
	BASIC_CONSTRAINTS_free(bc);
	return true;
}

static const BIT_STRING_BITNAME key_usage_type_table[] = {
{0, "Digital Signature", "digitalSignature"},
{1, "Non Repudiation", "nonRepudiation"},
{2, "Key Encipherment", "keyEncipherment"},
{3, "Data Encipherment", "dataEncipherment"},
{4, "Key Agreement", "keyAgreement"},
{5, "Certificate Sign", "keyCertSign"},
{6, "CRL Sign", "cRLSign"},
{7, "Encipher Only", "encipherOnly"},
{8, "Decipher Only", "decipherOnly"},
{-1, NULL, NULL}
};

static const BIT_STRING_BITNAME ns_cert_type_table[] = {
{0, "SSL Client", "client"},
{1, "SSL Server", "server"},
{2, "S/MIME", "email"},
{3, "Object Signing", "objsign"},
{4, "Unused", "reserved"},
{5, "SSL CA", "sslCA"},
{6, "S/MIME CA", "emailCA"},
{7, "Object Signing CA", "objCA"},
{-1, NULL, NULL}
};

bool x509v3ext::parse_bitstring(QString *single, QString *adv) const
{
	ASN1_BIT_STRING *bs;
	const BIT_STRING_BITNAME *bnames;
	int n = nid();

	switch (n) {
	case NID_key_usage: bnames = key_usage_type_table; break;
	case NID_netscape_cert_type: bnames = ns_cert_type_table; break;
	default: return false;
	}
	bs = (ASN1_BIT_STRING *)d2i();

	if (!bs)
		return false;

	QString ret = parse_critical() + parse_bits(bnames, bs);
	if (single)
		*single = ret;
	else if (adv)
		*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).arg(ret) +*adv;
	ASN1_BIT_STRING_free(bs);
	return true;
}

bool x509v3ext::parse_sKeyId(QString *, QString *adv) const
{
	if (adv)
		*adv = QString("%1=hash\n").arg(OBJ_nid2sn(nid())) + *adv;
	return true;
}

bool x509v3ext::parse_aKeyId(QString *, QString *adv) const
{
	QStringList ret;
	AUTHORITY_KEYID *akeyid = (AUTHORITY_KEYID *)d2i();
	ign_openssl_error();

	if (!akeyid)
		return false;

	if (akeyid->keyid)
		ret << "keyid";
	if (akeyid->issuer)
		ret << "issuer:always";
	if (adv)
		*adv = QString("%1=%2\n").arg(OBJ_nid2sn(nid())).
			arg(ret.join(", ")) + *adv;
	AUTHORITY_KEYID_free(akeyid);
	return true;
}

bool x509v3ext::parse_generic(QString *, QString *adv) const
{
	if (!isValid())
		return false;

	const ASN1_OBJECT *o = object();
	QString der, obj = o ? obj2SnOid(o) : QString("INVALID");
	ASN1_OCTET_STRING *v = getData();

	for (int i=0; v && i < v->length; i++)
		der += QString(":%1").arg((int)(v->data[i]), 2, 16, QChar('0'));

	if (adv)
		*adv = QString("%1=%2DER%3\n").arg(obj).
				arg(parse_critical()).arg(der) + *adv;
	return true;
}

bool x509v3ext::parse_inhibitAnyPolicy(QString *, QString *adv) const
{
	ASN1_INTEGER *a = (ASN1_INTEGER *)d2i();
	ign_openssl_error();

	if (!a)
		return false;

	a1int val(a);
	if (adv) {
		*adv = QString("%1=%2%3\n").arg(OBJ_nid2sn(nid())).
			arg(parse_critical()).arg(val.toDec()) + *adv;
	}
	ASN1_INTEGER_free(a);
	return true;
}

bool x509v3ext::parse_policyConstraints(QString *, QString *adv) const
{
	QStringList v;
	a1int a1null(0L), a;
	POLICY_CONSTRAINTS *pol = (POLICY_CONSTRAINTS *)d2i();
	ign_openssl_error();

	if (!pol)
		return false;

	a = a1int(pol->requireExplicitPolicy);
	if (a != a1null)
		v << QString("requireExplicitPolicy:%1").arg(a.toDec());

	a = a1int(pol->inhibitPolicyMapping);
	if (a != a1null)
		v << QString("inhibitPolicyMapping:%1").arg(a.toDec());

	if (adv)
		*adv = QString("%1=%2%3\n").arg(OBJ_nid2sn(nid())).
			arg(parse_critical()).arg(v.join(", ")) + *adv;
	POLICY_CONSTRAINTS_free(pol);
	return true;

}

bool x509v3ext::parse_policyMappings(QString *, QString *adv) const
{
	bool retval = true;
	QStringList polMaps;
	QString myadv;
	POLICY_MAPPINGS *pmaps = (POLICY_MAPPINGS *)d2i();
	ign_openssl_error();

	if (!pmaps)
		return false;

	for (int i = 0; i < sk_POLICY_MAPPING_num(pmaps); i++) {
		POLICY_MAPPING *pmap = sk_POLICY_MAPPING_value(pmaps, i);
		polMaps << QString("%1 = %2").
			arg(obj2SnOid(pmap->issuerDomainPolicy)).
			arg(obj2SnOid(pmap->subjectDomainPolicy));
	}
	if (polMaps.size() > 0 && adv) {
		*adv = QString("policyMappings=%1@policyMappings_sect\n").
		arg(parse_critical()) + *adv +
		QString("[policyMappings_sect]\n") + polMaps.join("\n");
	}
	sk_POLICY_MAPPING_free(pmaps);
	return retval;
}

static bool nameConstraint(STACK_OF(GENERAL_SUBTREE) *trees,
		QString prefix, QString tag, QString *single, QString *sect)
{
	QStringList sl;
	for (int i = 0; i < sk_GENERAL_SUBTREE_num(trees); i++) {
		QString one;
		GENERAL_SUBTREE *tree = sk_GENERAL_SUBTREE_value(trees, i);
		if (!genName2conf(tree->base,
			QString("%1_%2").arg(tag).arg(i), &one, sect))
		{
			return false;
		}
		qDebug("%s: %d '%s'\n", __func__, i, CCHAR(one));
		sl << prefix + ";" + one;
	}
	*single = vlist2Section(sl, tag+prefix, sect);
	qDebug("Single: '%s'\n", CCHAR(*single));
	return true;
}

bool x509v3ext::parse_nameConstraints(QString *single, QString *adv) const
{
	bool retval = true;
	QString sect, ret;
	QStringList permEx;
	QString tag = OBJ_nid2sn(nid());
	NAME_CONSTRAINTS *cons = (NAME_CONSTRAINTS *)d2i();
	ign_openssl_error();

	if (!cons)
		return false;

	if (!nameConstraint(cons->permittedSubtrees, "permitted",
				tag, &ret, &sect))
		retval = false;
	if (ret.size() > 0)
		permEx << ret;
	if (!nameConstraint(cons->excludedSubtrees, "excluded",
				tag, &ret, &sect))
		retval = false;
	if (ret.size() > 0)
		permEx << ret;

	if (retval && permEx.size() > 0) {
		ret = permEx.join(", ");
		qDebug() << retval << ret;
		if (single)
			*single = ret;
		else if (adv)
			*adv = QString("%1=%2\n").arg(tag).
				arg(parse_critical() +ret) + *adv + sect;
	}
	NAME_CONSTRAINTS_free(cons);
	return retval;
}

bool x509v3ext::genConf(QString *single, QString *adv) const
{
	int n = nid();
	switch (n) {
	case NID_crl_distribution_points:
		return parse_Crldp(single, adv);
	case NID_subject_alt_name:
	case NID_issuer_alt_name:
		return parse_generalName(single, adv);
	case NID_info_access:
		return parse_ainfo(single, adv);
	case NID_ext_key_usage:
		return parse_eku(single, adv);
	case NID_certificate_policies:
		return parse_certpol(single, adv);
	case NID_netscape_comment:
	case NID_netscape_base_url:
	case NID_netscape_revocation_url:
	case NID_netscape_ca_revocation_url:
	case NID_netscape_renewal_url:
	case NID_netscape_ca_policy_url:
	case NID_netscape_ssl_server_name:
		return parse_ia5(single, adv);
	case NID_basic_constraints:
		return parse_bc(single, adv);
	case NID_key_usage:
	case NID_netscape_cert_type:
		return parse_bitstring(single, adv);
	case NID_subject_key_identifier:
		return parse_sKeyId(single, adv);
	case NID_authority_key_identifier:
		return parse_aKeyId(single, adv);
	case NID_inhibit_any_policy:
		return parse_inhibitAnyPolicy(single, adv);
	case NID_policy_constraints:
		return parse_policyConstraints(single, adv);
	case NID_policy_mappings:
		return parse_policyMappings(single, adv);
	case NID_name_constraints:
		return parse_nameConstraints(single, adv);
	case NID_id_pkix_OCSP_noCheck:
		if (adv)
			*adv = "noCheck = ignored\n" + *adv;
		return true;
	default:
		return parse_generic(single, adv);
	}
	return false;
}

QString x509v3ext::getHtml() const
{
	QString html;
	html = "<b><u>" + getObject();
	if (getCritical() != 0)
		html += " <font color=\"red\">critical</font>";
	html += ":</u></b><br><tt>" + getHtmlValue() + "</tt>";
	return html;
}

QString x509v3ext::getConsole(const QString &indent) const
{
	QString text, twoind = indent + indent;
	text = indent + COL_BOLD COL_UNDER + getObject();
	if (getCritical() != 0)
		text += " " COL_RED COL_UNDER "[critical]";
	text += COL_RESET "\n" + twoind + getConsoleValue(twoind);
	return text;
}

X509_EXTENSION *x509v3ext::get() const
{
	return ext ? X509_EXTENSION_dup(ext) : nullptr;
}

bool x509v3ext::isValid() const
{
	return ext && getData() && getData()->length > 0 &&
		OBJ_obj2nid(X509_EXTENSION_get_object(ext)) != NID_undef;
}

/*************************************************************/

bool extList::genConf(int nid, QString *single, QString *adv)
{
	int i = idxByNid(nid);
	if (i != -1) {
		if (at(i).genConf(single, adv))
			removeAt(i);
		ign_openssl_error();
		return true;
	}
	return false;
}

void extList::genGenericConf(QString *adv)
{
	for (int i=0; i< size();) {
		if (at(i).genConf(NULL, adv) || at(i).parse_generic(NULL, adv))
			removeAt(i);
		else
			i++;
		ign_openssl_error();
	}
}

void extList::setStack(const STACK_OF(X509_EXTENSION) *st, int start)
{
	clear();
	int cnt = sk_X509_EXTENSION_num(st);
	x509v3ext e;
	for (int i=start; i<cnt; i++) {
		e.set(sk_X509_EXTENSION_value(st,i));
		append(e);
	}
}

STACK_OF(X509_EXTENSION) *extList::getStack() const
{
	STACK_OF(X509_EXTENSION) *sk = sk_X509_EXTENSION_new_null();
	Q_CHECK_PTR(sk);
	foreach(const x509v3ext &e, *this)
		sk_X509_EXTENSION_push(sk, e.get());
	return sk;
}

bool extList::search(const QRegularExpression &pattern) const
{
	foreach(const x509v3ext &e, *this)
		if (e.getValue().contains(pattern))
			return true;
	return false;
}
QString extList::getHtml(const QString &sep) const
{
	x509v3ext e;
	QStringList s;
	foreach(const x509v3ext &e, *this)
		s << e.getHtml();
	QString a = s.join(sep);
	return a;
}

QString extList::getConsole(const QString &indent) const
{
	x509v3ext e;
	QStringList s;
	foreach(const x509v3ext &e, *this)
		s << e.getConsole(indent);
	QString a = "\n" + s.join("\n");
	return a;
}


bool extList::delByNid(int nid)
{
	for(int i = 0; i< size(); i++) {
		if (at(i).nid() == nid) {
			removeAt(i);
			return true;
		}
	}
	return false;
}

int extList::idxByNid(int nid) const
{
	for(int i = 0; i< size(); i++) {
		if (at(i).nid() == nid) {
			return i;
		}
	}
	return -1;
}

int extList::delInvalid(void)
{
	int removed=0;
	QMutableListIterator<x509v3ext> i(*this);
	while (i.hasNext()) {
		if (!i.next().isValid()) {
			i.remove();
			removed=1;
		}
	}
	return removed;
}
