/* 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 #include #include #include #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 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; igetV3ext(); 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> 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< 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 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 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); }