mirror of
https://github.com/chris2511/xca.git
synced 2026-09-12 19:51:05 +05:00
Introduced with 7f5bdb7dec
Templates now export again completely and
not only the internal name and comment.
Also protect against an empty internal name
by setting it to "Pasted" as last resort.
561 lines
14 KiB
C++
561 lines
14 KiB
C++
/* vi: set sw=4 ts=4:
|
|
*
|
|
* Copyright (C) 2001 - 2020 Christian Hohnstaedt.
|
|
*
|
|
* All rights reserved.
|
|
*/
|
|
|
|
#include "pki_temp.h"
|
|
#include "pki_x509.h"
|
|
#include "func.h"
|
|
#include "oid.h"
|
|
#include "exception.h"
|
|
#include <QDir>
|
|
#include <QBuffer>
|
|
#include <QDataStream>
|
|
#include <QRegularExpression>
|
|
|
|
#define TEMPLATE_DS_VERSION (QDataStream::Qt_4_2)
|
|
|
|
namespace db {
|
|
|
|
QByteArray intToData(uint32_t val)
|
|
{
|
|
uint32_t v = xhtonl(val);
|
|
return QByteArray((char*)&v, sizeof(uint32_t));
|
|
}
|
|
uint32_t intFromData(QByteArray &ba)
|
|
{
|
|
uint32_t ret;
|
|
if ((unsigned)(ba.size()) < sizeof(uint32_t)) {
|
|
throw errorEx(QObject::tr("Out of data"));
|
|
}
|
|
memcpy(&ret, ba.constData(), sizeof(uint32_t));
|
|
ba = ba.mid(sizeof(uint32_t));
|
|
return xntohl(ret);
|
|
}
|
|
bool boolFromData(QByteArray &ba)
|
|
{
|
|
unsigned char c;
|
|
if (ba.size() < 1)
|
|
throw errorEx(QObject::tr("Out of data"));
|
|
|
|
c = ba.constData()[0];
|
|
ba = ba.mid(1);
|
|
return c ? true : false;
|
|
}
|
|
QString stringFromData(QByteArray &ba)
|
|
{
|
|
int idx = ba.indexOf('\0');
|
|
|
|
if (idx == -1)
|
|
throw errorEx(QObject::tr("Error finding endmarker of string"));
|
|
|
|
QString ret = QString::fromUtf8(ba.constData(), idx);
|
|
ba = ba.mid(idx+1);
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
const QList<QString> pki_temp::tmpl_keys = {
|
|
"nameCons",
|
|
"subAltName",
|
|
"issAltName",
|
|
"crlDist",
|
|
"authInfAcc",
|
|
"nsCertType",
|
|
"nsComment",
|
|
"nsBaseUrl",
|
|
"nsRevocationUrl",
|
|
"nsCARevocationUrl",
|
|
"nsRenewalUrl",
|
|
"nsCaPolicyUrl",
|
|
"nsSslServerName",
|
|
"ca",
|
|
"bcCritical",
|
|
"ekuCritical",
|
|
"kuCritical",
|
|
"subKey",
|
|
"authKey",
|
|
"basicPath",
|
|
"validN",
|
|
"validM",
|
|
"validMidn",
|
|
"keyUse",
|
|
"eKeyUse",
|
|
"adv_ext",
|
|
"noWellDefinedExpDate",
|
|
"OCSPstaple",
|
|
};
|
|
|
|
pki_temp::pki_temp(const pki_temp *pk)
|
|
:pki_x509name(pk->getIntName()), xname(pk->xname), settings(pk->settings)
|
|
{
|
|
}
|
|
|
|
pki_temp::pki_temp(const QString &d)
|
|
:pki_x509name(d)
|
|
{
|
|
pkiType = tmpl;
|
|
|
|
foreach(QString key, tmpl_keys) {
|
|
settings[key] = QString();
|
|
}
|
|
settings["nsComment"] = "xca certificate";
|
|
settings["validN"] = "365";
|
|
}
|
|
|
|
QString pki_temp::comboText() const
|
|
{
|
|
return pre_defined ? QString("[default] ") + pki_base::comboText() :
|
|
pki_base::comboText();
|
|
}
|
|
|
|
QSqlError pki_temp::insertSqlData()
|
|
{
|
|
XSqlQuery q;
|
|
QSqlError e = pki_x509name::insertSqlData();
|
|
if (e.isValid())
|
|
return e;
|
|
SQL_PREPARE(q, "INSERT INTO templates (item, version, template) "
|
|
"VALUES (?, ?, ?)");
|
|
q.bindValue(0, sqlItemId);
|
|
q.bindValue(1, TMPL_VERSION);
|
|
q.bindValue(2, toB64Data());
|
|
q.exec();
|
|
return q.lastError();
|
|
}
|
|
|
|
void pki_temp::restoreSql(const QSqlRecord &rec)
|
|
{
|
|
pki_base::restoreSql(rec);
|
|
int version = rec.value(VIEW_temp_version).toInt();
|
|
QByteArray ba = QByteArray::fromBase64(
|
|
rec.value(VIEW_temp_template).toByteArray());
|
|
fromData(ba, version);
|
|
}
|
|
|
|
QSqlError pki_temp::deleteSqlData()
|
|
{
|
|
XSqlQuery q;
|
|
QSqlError e = pki_x509name::deleteSqlData();
|
|
if (e.isValid())
|
|
return e;
|
|
SQL_PREPARE(q, "DELETE FROM templates WHERE item=?");
|
|
q.bindValue(0, sqlItemId);
|
|
q.exec();
|
|
return q.lastError();
|
|
}
|
|
|
|
QString pki_temp::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 internal name of the template
|
|
*/
|
|
switch (msg) {
|
|
case msg_import: return tr("Successfully imported the XCA template '%1'");
|
|
case msg_delete: return tr("Delete the %n XCA template(s): '%1'?", "", n);
|
|
case msg_create: return tr("Successfully created the XCA template '%1'");
|
|
}
|
|
return pki_base::getMsg(msg);
|
|
}
|
|
|
|
x509name pki_temp::getSubject() const
|
|
{
|
|
return xname;
|
|
}
|
|
|
|
static int bitsToInt(extList &el, int nid, bool *crit)
|
|
{
|
|
int ret = 0, i = el.idxByNid(nid);
|
|
|
|
if (i != -1) {
|
|
if (crit)
|
|
*crit = el[i].getCritical();
|
|
ASN1_BIT_STRING *bits;
|
|
bits = (ASN1_BIT_STRING *)el[i].d2i();
|
|
|
|
for (int j=0; j<9; j++) {
|
|
if (ASN1_BIT_STRING_get_bit(bits, j))
|
|
ret |= 1 << j;
|
|
}
|
|
el.removeAt(i);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void pki_temp::fromExtList(extList *el, int nid, const char *item)
|
|
{
|
|
QString target;
|
|
el->genConf(nid, &target, &adv_ext);
|
|
if (nid == NID_subject_alt_name || nid == NID_issuer_alt_name ||
|
|
nid == NID_name_constraints)
|
|
{
|
|
QStringList sl = target.split(",");
|
|
QRegularExpression match("([a-z]+;)*otherName:msUPN;UTF8:");
|
|
for (int i=0; i<sl.size(); i++) {
|
|
QString s = sl[i].trimmed();
|
|
sl[i] = s.replace(match, "\\1UPN:");
|
|
}
|
|
target = sl.join(",");
|
|
}
|
|
settings[item] = target;
|
|
}
|
|
|
|
extList pki_temp::fromCert(pki_x509super *cert_or_req)
|
|
{
|
|
x509name n;
|
|
extList el = cert_or_req->getV3ext();
|
|
adv_ext.clear();
|
|
|
|
settings["nsComment"] = "";
|
|
|
|
n = cert_or_req->getSubject();
|
|
foreach(QString sn, Settings["explicit_dn"].split(",")) {
|
|
int nid = OBJ_sn2nid(CCHAR(sn));
|
|
QString ne = n.popEntryByNid(nid);
|
|
if (!ne.isNull())
|
|
xname.addEntryByNid(nid, ne);
|
|
}
|
|
for (int i=0; i<n.entryCount(); i++) {
|
|
int nid = n.nid(i);
|
|
if (nid != NID_undef)
|
|
xname.addEntryByNid(nid, n.getEntry(i));
|
|
}
|
|
|
|
fromExtList(&el, NID_name_constraints, "nameCons");
|
|
fromExtList(&el, NID_subject_alt_name, "subAltName");
|
|
fromExtList(&el, NID_issuer_alt_name, "issAltName");
|
|
fromExtList(&el, NID_crl_distribution_points, "crlDist");
|
|
fromExtList(&el, NID_info_access, "authInfAcc");
|
|
fromExtList(&el, NID_netscape_comment, "nsComment");
|
|
fromExtList(&el, NID_netscape_base_url, "nsBaseUrl");
|
|
fromExtList(&el, NID_netscape_revocation_url, "nsRevocationUrl");
|
|
fromExtList(&el, NID_netscape_ca_revocation_url, "nsCARevocationUrl");
|
|
fromExtList(&el, NID_netscape_renewal_url, "nsRenewalUrl");
|
|
fromExtList(&el, NID_netscape_ca_policy_url, "nsCaPolicyUrl");
|
|
fromExtList(&el, NID_netscape_ssl_server_name, "nsSslServerName");
|
|
|
|
QString r;
|
|
if (el.genConf(NID_basic_constraints, &r)) {
|
|
QStringList sl = r.split(",");
|
|
if (sl.contains("critical"))
|
|
settings["bcCritical"] = "1";
|
|
settings["ca"] = sl.contains("CA:TRUE") ? "1" : "2";
|
|
settings["basicPath"]=sl.filter("pathlen:").join("") .mid(8,-1);
|
|
} else {
|
|
settings["bcCritical"] = "";
|
|
settings["ca"] = "";
|
|
settings["basicPath"] = "";
|
|
}
|
|
settings["authKey"] = el.delByNid(NID_authority_key_identifier) ? "1" : "0";
|
|
settings["subKey"] = el.delByNid(NID_subject_key_identifier) ? "1" : "0";
|
|
settings["OCSPstaple"] = el.delByNid(NID_tlsfeature) ? "1" : "0";
|
|
|
|
int nsCT = bitsToInt(el, NID_netscape_cert_type, NULL);
|
|
/* bit 4 is unused. Move higher bits down. */
|
|
settings["nsCertType"] = QString::number(
|
|
(nsCT & 0xf) | ((nsCT & 0xf0) >> 1));
|
|
|
|
bool kuCritical;
|
|
settings["keyUse"] = QString::number(
|
|
bitsToInt(el, NID_key_usage, &kuCritical));
|
|
|
|
settings["kuCritical"] = kuCritical ? "1" : "0";
|
|
fromExtList(&el, NID_ext_key_usage, "eKeyUse");
|
|
QStringList eKeyUse = settings["eKeyUse"]
|
|
.split(QRegularExpression(",\\s*"));
|
|
settings["ekuCritical"] = "0";
|
|
if (eKeyUse.contains("critical")) {
|
|
eKeyUse.removeOne("critical");
|
|
settings["eKeyUse"] = eKeyUse.join(", ");
|
|
settings["ekuCritical"] = "1";
|
|
}
|
|
qDebug() << "eKeyUse" << settings["kuCritical"] << settings["eKeyUse"];
|
|
|
|
el.genGenericConf(&adv_ext);
|
|
settings["adv_ext"] = adv_ext;
|
|
|
|
if (cert_or_req->getType() == x509) {
|
|
pki_x509 *cert = (pki_x509*)cert_or_req;
|
|
if (cert->getNotAfter().isUndefined()) {
|
|
settings["noWellDefinedExpDate"] = "1";
|
|
} else {
|
|
a1time notBefore = cert->getNotBefore();
|
|
a1time notAfter = cert->getNotAfter();
|
|
|
|
if (notBefore.toPlain().endsWith("000000Z") &&
|
|
notAfter.toPlain().endsWith("235959Z"))
|
|
{
|
|
settings["validMidn"] = "1";
|
|
}
|
|
|
|
int diff = notBefore.daysTo(notAfter);
|
|
settings["validM"] = "0";
|
|
if (diff > 60) {
|
|
settings["validM"] = "1";
|
|
diff /= 30;
|
|
if (diff > 24) {
|
|
settings["validM"] = "2";
|
|
diff /= 12;
|
|
}
|
|
}
|
|
settings["validN"] = QString::number(diff);
|
|
}
|
|
}
|
|
return el;
|
|
}
|
|
|
|
static QString old_eKeyUse2QString(int old)
|
|
{
|
|
QStringList sl;
|
|
|
|
for (int i = 0; i < extkeyuse_nid.size(); i++) {
|
|
if (old & (1<<i)) {
|
|
sl << OBJ_nid2sn(extkeyuse_nid[i]);
|
|
}
|
|
}
|
|
return sl.join(", ");
|
|
}
|
|
|
|
void pki_temp::old_fromData(const unsigned char *p, int size, int version)
|
|
{
|
|
QByteArray ba((const char*)p, size);
|
|
|
|
/* destination = */ db::stringFromData(ba);
|
|
settings["bcCritical"] = QString::number(db::boolFromData(ba));
|
|
settings["kuCritical"] = QString::number(db::boolFromData(ba));
|
|
settings["ekuCritical"] = QString::number(db::boolFromData(ba));
|
|
settings["subKey"] = QString::number(db::boolFromData(ba));
|
|
settings["authKey"] = QString::number(db::boolFromData(ba));
|
|
settings["ca"] = QString::number(db::intFromData(ba));
|
|
if (version > 5) {
|
|
settings["basicPath"] = db::stringFromData(ba);
|
|
} else {
|
|
settings["basicPath"] = QString::number(db::intFromData(ba));
|
|
if (settings["basicPath"] == "0")
|
|
settings["basicPath"] = "";
|
|
}
|
|
settings["validN"] = QString::number(db::intFromData(ba));
|
|
settings["validM"] = QString::number(db::intFromData(ba));
|
|
settings["keyUse"] = QString::number(db::intFromData(ba));
|
|
if (version > 4) {
|
|
settings["eKeyUse"] = db::stringFromData(ba);
|
|
} else {
|
|
int old = db::intFromData(ba);
|
|
settings["eKeyUse"] = old_eKeyUse2QString(old);
|
|
}
|
|
settings["nsCertType"] = QString::number(db::intFromData(ba));
|
|
settings["subAltName"] = db::stringFromData(ba);
|
|
settings["issAltName"] = db::stringFromData(ba);
|
|
settings["crlDist"] = db::stringFromData(ba);
|
|
settings["nsComment"] = db::stringFromData(ba);
|
|
settings["nsBaseUrl"] = db::stringFromData(ba);
|
|
settings["nsRevocationUrl"] = db::stringFromData(ba);
|
|
settings["nsCARevocationUrl"] = db::stringFromData(ba);
|
|
settings["nsRenewalUrl"] = db::stringFromData(ba);
|
|
settings["nsCaPolicyUrl"] = db::stringFromData(ba);
|
|
settings["nsSslServerName"] = db::stringFromData(ba);
|
|
xname.d2i(ba);
|
|
settings["authInfAcc"] = db::stringFromData(ba);
|
|
/* certPol = */ db::stringFromData(ba);
|
|
settings["validMidn"] = QString::number(db::boolFromData(ba));
|
|
if (version>2)
|
|
settings["adv_ext"] = db::stringFromData(ba);
|
|
if (version>3)
|
|
settings["noWellDefinedExpDate"] =
|
|
QString::number(db::boolFromData(ba));
|
|
|
|
if (ba.size() > 0) {
|
|
my_error(tr("Wrong Size %1").arg(ba.size()));
|
|
}
|
|
}
|
|
|
|
QByteArray pki_temp::toData(bool for_export) const
|
|
{
|
|
QByteArray ba(xname.i2d());
|
|
|
|
QBuffer buf(&ba);
|
|
buf.open(QIODevice::WriteOnly | QIODevice::Append);
|
|
QDataStream out(&buf);
|
|
out.setVersion(TEMPLATE_DS_VERSION);
|
|
QMap<QString, QString> export_settings = settings;
|
|
if (for_export) {
|
|
export_settings["internal_name"] = getIntName();
|
|
export_settings["internal_comment"] = getComment();
|
|
}
|
|
out << export_settings;
|
|
buf.close();
|
|
return ba;
|
|
}
|
|
|
|
void pki_temp::fromData(QByteArray &ba, int version)
|
|
{
|
|
xname.d2i(ba);
|
|
QBuffer buf(&ba);
|
|
buf.open(QIODevice::ReadOnly);
|
|
QDataStream in(&buf);
|
|
in.setVersion(TEMPLATE_DS_VERSION);
|
|
in >> settings;
|
|
QMap<QString, QString> translate;
|
|
translate["eKyUseCritical"] = "ekuCritical";
|
|
translate["keyUseCritical"] ="kuCritical";
|
|
|
|
for (const QString &key : translate.keys()) {
|
|
if (settings.contains(key))
|
|
settings[translate[key]] = settings.take(key);
|
|
}
|
|
if (settings.contains("internal_name")) {
|
|
qDebug() << "Import internal_name" << settings["internal_name"];
|
|
setIntName(settings.take("internal_name"));
|
|
}
|
|
if (settings.contains("internal_comment")) {
|
|
qDebug() << "Import internal_comment" << settings["internal_comment"];
|
|
setComment(settings.take("internal_comment"));
|
|
}
|
|
buf.close();
|
|
(void)version;
|
|
//if (version < 11) ....
|
|
}
|
|
|
|
void pki_temp::fromData(const unsigned char *p, int size, int version)
|
|
{
|
|
if (version < 10) {
|
|
old_fromData(p, size, version);
|
|
} else {
|
|
QByteArray ba((const char*)p, size);
|
|
fromData(ba, version);
|
|
}
|
|
}
|
|
|
|
QByteArray pki_temp::toExportData() const
|
|
{
|
|
QByteArray data, header;
|
|
BioByteArray b;
|
|
|
|
data = toData(true);
|
|
header = db::intToData(data.size());
|
|
header += db::intToData(TMPL_VERSION);
|
|
header += data;
|
|
PEM_write_bio(b, PEM_STRING_XCA_TEMPLATE, (char*)"",
|
|
(unsigned char*)(header.data()), header.size());
|
|
return b.byteArray();
|
|
}
|
|
|
|
void pki_temp::writeTemp(XFile &file) const
|
|
{
|
|
file.write(PEM_comment() + toExportData());
|
|
}
|
|
|
|
void pki_temp::writeDefault(const QString &dirname) const
|
|
{
|
|
XFile file(get_dump_filename(dirname, ".xca"));
|
|
file.open_write();
|
|
writeTemp(file);
|
|
}
|
|
|
|
bool pki_temp::pem(BioByteArray &b)
|
|
{
|
|
b += toExportData();
|
|
return true;
|
|
}
|
|
|
|
void pki_temp::fromExportData(QByteArray data)
|
|
{
|
|
int version;
|
|
|
|
if (data.size() < (int)sizeof(uint32_t))
|
|
my_error(tr("Template file content error (too small)"));
|
|
|
|
db::intFromData(data);
|
|
version = db::intFromData(data);
|
|
fromData((const unsigned char*)data.constData(),
|
|
data.size(), version);
|
|
}
|
|
|
|
void pki_temp::try_fload(XFile &file)
|
|
{
|
|
QByteArray ba = file.read(4096*1024);
|
|
try {
|
|
fromPEM_BIO(BioByteArray(ba).ro(), file.fileName());
|
|
} catch (errorEx &) {
|
|
fromExportData(ba);
|
|
}
|
|
pki_openssl_error();
|
|
}
|
|
|
|
void pki_temp::fload(const QString &fname)
|
|
{
|
|
try {
|
|
XFile file(fname);
|
|
file.open_read();
|
|
try_fload(file);
|
|
#if defined(Q_OS_WIN32)
|
|
} catch (errorEx &) {
|
|
/* Try again in ascii mode on Windows
|
|
* to support pre 1.1.0 template exports */
|
|
XFile file(fname);
|
|
file.open(QIODevice::ReadOnly | QIODevice::QIODevice::Text);
|
|
try_fload(file);
|
|
#else
|
|
} catch (errorEx &err) {
|
|
throw err;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void pki_temp::fromPEM_BIO(BIO *bio, const QString &)
|
|
{
|
|
QByteArray ba;
|
|
QString msg;
|
|
char *nm = NULL, *header = NULL;
|
|
unsigned char *data = NULL;
|
|
long len;
|
|
|
|
PEM_read_bio(bio, &nm, &header, &data, &len);
|
|
|
|
if (ign_openssl_error())
|
|
throw errorEx(tr("Not a PEM encoded XCA Template"),
|
|
getClassName());
|
|
|
|
if (!strcmp(nm, PEM_STRING_XCA_TEMPLATE)) {
|
|
ba = QByteArray::fromRawData((char*)data, len);
|
|
fromExportData(ba);
|
|
} else {
|
|
msg = tr("Not an XCA Template, but '%1'").arg(nm);
|
|
}
|
|
OPENSSL_free(nm);
|
|
OPENSSL_free(header);
|
|
OPENSSL_free(data);
|
|
if (!msg.isEmpty())
|
|
my_error(msg);
|
|
}
|
|
|
|
pki_temp::~pki_temp()
|
|
{
|
|
}
|
|
|
|
bool pki_temp::compare(const pki_base *) const
|
|
{
|
|
// we don't care if templates with identical contents
|
|
// are stored in the database ...
|
|
return false;
|
|
}
|
|
|
|
QVariant pki_temp::getIcon(const dbheader *hd) const
|
|
{
|
|
return hd->id == HD_internal_name ?
|
|
QVariant(QPixmap(":templateIco")) : QVariant();
|
|
}
|
|
|
|
void pki_temp::autoIntName(const QString &file)
|
|
{
|
|
if (getIntName().isEmpty())
|
|
setIntName(rmslashdot(file));
|
|
if (getIntName().isEmpty())
|
|
pki_x509name::autoIntName(file);
|
|
}
|