1 /* vi: set sw=4 ts=4:
2  *
3  * Copyright (C) 2001 - 2020 Christian Hohnstaedt.
4  *
5  * All rights reserved.
6  */
7 
8 
9 #include "func.h"
10 #include "xfile.h"
11 #include "pki_base.h"
12 #include "exception.h"
13 #include "widgets/XcaWarning.h"
14 #include <QString>
15 #include <openssl/evp.h>
16 #include <openssl/sha.h>
17 #include <openssl/md5.h>
18 #include <typeinfo>
19 
20 pki_lookup Store;
21 
22 QRegExp pki_base::limitPattern;
23 bool pki_base::pem_comment;
24 QList<pki_base*> pki_base::allitems;
25 
pki_base(const QString & name,pki_base * p)26 pki_base::pki_base(const QString &name, pki_base *p)
27 {
28 	desc = name;
29 	parent = p;
30 	childItems.clear();
31 	pkiType=none;
32 	pkiSource=unknown;
33 	allitems << this;
34 	qDebug() << "NEW pki_base::count" << allitems.count();
35 }
36 
pki_base(const pki_base * p)37 pki_base::pki_base(const pki_base *p)
38 {
39 	desc = p->desc;
40 	parent = p->parent;
41 	childItems.clear();
42 	pkiType = p->pkiType;
43 	pkiSource = p->pkiSource;
44 	allitems << this;
45 	qDebug() << "COPY pki_base::count" << allitems.count();
46 	p->inheritFilename(this);
47 }
48 
~pki_base(void)49 pki_base::~pki_base(void)
50 {
51 	if (!allitems.removeOne(this))
52 		qDebug() << "DEL" << getIntName() << "NOT FOUND";
53 	qDebug() << "DEL pki_base::count" << allitems.count();
54 }
55 
comboText() const56 QString pki_base::comboText() const
57 {
58 	return desc;
59 }
60 
autoIntName(const QString & file)61 void pki_base::autoIntName(const QString &file)
62 {
63 	setIntName(rmslashdot(file));
64 }
65 
deleteFromToken()66 void pki_base::deleteFromToken() { }
deleteFromToken(const slotid &)67 void pki_base::deleteFromToken(const slotid &) { }
writeDefault(const QString &) const68 void pki_base::writeDefault(const QString&) const { }
fromPEM_BIO(BIO *,const QString &)69 void pki_base::fromPEM_BIO(BIO *, const QString &) { }
fload(const QString &)70 void pki_base::fload(const QString &) { }
renameOnToken(const slotid &,const QString &)71 int pki_base::renameOnToken(const slotid &, const QString &)
72 {
73 	return 0;
74 }
75 
getUnderlinedName() const76 QString pki_base::getUnderlinedName() const
77 {
78 	QString name = getIntName();
79 	QRegExp rx("^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$");
80 
81 	if (rx.indexIn(name) != -1)
82 		name += "_";
83 	return name.replace(QRegExp("[ $&;`/\\\\<>:\"/\\|?*]+"), "_");
84 }
85 
visible() const86 bool pki_base::visible() const
87 {
88 	if (limitPattern.isEmpty())
89 		return true;
90 	return getIntName().contains(limitPattern) ||
91 		comment.contains(limitPattern);
92 }
93 
PEM_file_comment(XFile & file) const94 void pki_base::PEM_file_comment(XFile &file) const
95 {
96 	if (!pem_comment)
97 		return;
98 	file.write(QString("XCA internal name: %1\n%2\n")
99 			.arg(getIntName()).arg(getComment())
100 				.toUtf8());
101 }
102 
clear()103 void pki_base::clear()
104 {
105 	childItems.clear();
106 }
107 
childVisible() const108 bool pki_base::childVisible() const
109 {
110 	foreach(pki_base *child, childItems)
111 		if (child->isVisible())
112 			return true;
113 	return false;
114 }
115 
isVisible()116 int pki_base::isVisible()
117 {
118 	if (limitPattern.isEmpty())
119 		return 1;
120 	return visible() ? 1 : childVisible() ? 2 : 0;
121 }
122 
getMsg(msg_type msg) const123 QString pki_base::getMsg(msg_type msg) const
124 {
125 	return tr("Internal error: Unexpected message: %1 %2")
126 		.arg(getClassName()).arg(msg);
127 }
128 
i2d() const129 QByteArray pki_base::i2d() const
130 {
131 	return QByteArray();
132 }
133 
pem(BioByteArray &,int)134 bool pki_base::pem(BioByteArray &, int)
135 {
136 	return false;
137 }
138 
getClassName() const139 const char *pki_base::getClassName() const
140 {
141 	return typeid(*this).name();
142 }
143 
my_error(const QString & error) const144 void pki_base::my_error(const QString &error) const
145 {
146 	if (!error.isEmpty()) {
147 		qCritical() << "Error:" << error;
148 		throw errorEx(error, getClassName());
149 	}
150 }
151 
152 
fromPEMbyteArray(const QByteArray & ba,const QString & name)153 void pki_base::fromPEMbyteArray(const QByteArray &ba, const QString &name)
154 {
155 	fromPEM_BIO(BioByteArray(ba).ro(), name);
156 	autoIntName(name);
157 	setFilename(name);
158 }
159 
rmslashdot(const QString & s)160 QString pki_base::rmslashdot(const QString &s)
161 {
162 	QByteArray a = s.toLatin1().replace("\\", "/");
163 	int r = a.lastIndexOf('.');
164 	int l = a.lastIndexOf('/');
165 	return s.mid(l+1,r-l-1);
166 }
167 
insertSql()168 QSqlError pki_base::insertSql()
169 {
170 	XSqlQuery q;
171 	QString insert;
172 	QSqlError e;
173 	insertion_date.now();
174 
175 	SQL_PREPARE(q, "SELECT MAX(id) +1 from items");
176 	q.exec();
177 	if (q.first())
178 		sqlItemId = q.value(0);
179 
180 	if (sqlItemId.toULongLong() == 0)
181 		sqlItemId = 1;
182 
183 	SQL_PREPARE(q, "INSERT INTO items "
184 		  "(id, name, type, date, source, comment) "
185 		  "VALUES (?, ?, ?, ?, ?, ?)");
186 	q.bindValue(0, sqlItemId);
187 	q.bindValue(1, getIntName());
188 	q.bindValue(2, getType());
189 	q.bindValue(3, insertion_date.toPlain());
190 	q.bindValue(4, pkiSource);
191 	q.bindValue(5, getComment());
192 	q.exec();
193 	e = q.lastError();
194 	if (!e.isValid()) {
195 		e = insertSqlData();
196 	}
197 	return e;
198 }
199 
restoreSql(const QSqlRecord & rec)200 void pki_base::restoreSql(const QSqlRecord &rec)
201 {
202 	sqlItemId = rec.value(VIEW_item_id);
203 	desc = rec.value(VIEW_item_name).toString();
204 	insertion_date.fromPlain(rec.value(VIEW_item_date).toString());
205 	comment = rec.value(VIEW_item_comment).toString();
206 	pkiSource = (enum pki_source)rec.value(VIEW_item_source).toInt();
207 }
208 
deleteSql()209 QSqlError pki_base::deleteSql()
210 {
211 	XSqlQuery q;
212 	QString insert;
213 	QSqlError e;
214 
215 	if (!sqlItemId.isValid()) {
216 		qDebug("INVALID sqlItemId (DELETE %s)", CCHAR(getIntName()));
217 		return sqlItemNotFound(QVariant());
218 	}
219 	e = deleteSqlData();
220 	if (e.isValid())
221 		return e;
222 	SQL_PREPARE(q, "UPDATE items SET del=1 WHERE id=?");
223 	q.bindValue(0, sqlItemId);
224 	q.exec();
225 	return q.lastError();
226 }
227 
sqlItemNotFound(QVariant sqlId) const228 QSqlError pki_base::sqlItemNotFound(QVariant sqlId) const
229 {
230 	return QSqlError(QString("XCA SQL database inconsistent"),
231 			QString("Item %2 not found %1")
232 				.arg(getClassName())
233 				.arg(sqlId.toString()),
234 			QSqlError::UnknownError);
235 }
236 
getParent() const237 pki_base *pki_base::getParent() const
238 {
239 	return parent;
240 }
241 
setParent(pki_base * p)242 void pki_base::setParent(pki_base *p)
243 {
244 	parent = p;
245 }
246 
child(int row)247 pki_base *pki_base::child(int row)
248 {
249 	return childItems.value(row);
250 }
251 
insert(pki_base * item)252 void pki_base::insert(pki_base *item)
253 {
254 	if (!childItems.contains(item))
255 		childItems.prepend(item);
256 }
257 
childCount() const258 int pki_base::childCount() const
259 {
260 	return childItems.size();
261 }
262 
indexOf(const pki_base * child) const263 int pki_base::indexOf(const pki_base *child) const
264 {
265 	int ret = childItems.indexOf(const_cast<pki_base *>(child));
266 	return ret >= 0 ? ret : 0;
267 }
268 
takeChild(pki_base * pki)269 void pki_base::takeChild(pki_base *pki)
270 {
271 	childItems.removeOne(pki);
272 }
273 
getChildItems() const274 QList<pki_base*> pki_base::getChildItems() const
275 {
276 	//#warning need to collect all children below folders (later)
277 	return childItems;
278 }
279 
takeFirst()280 pki_base *pki_base::takeFirst()
281 {
282 	return childItems.takeFirst();
283 }
284 
pki_source_name() const285 QString pki_base::pki_source_name() const
286 {
287 	switch (pkiSource) {
288 		default:
289 		case unknown: return tr("Unknown");
290 		case imported: return tr("Imported");
291 		case generated: return tr("Generated");
292 		case transformed: return tr("Transformed");
293 		case token: return tr("Token");
294 		case legacy_db: return tr("Legacy Database");
295 		case renewed: return tr("Renewed");
296 	}
297 	return QString("???");
298 }
299 
column_data(const dbheader * hd) const300 QVariant pki_base::column_data(const dbheader *hd) const
301 {
302 	switch (hd->id) {
303 	case HD_internal_name:
304 		return QVariant(getIntName());
305 	case HD_comment:
306 		return QVariant(comment.section('\n', 0, 0));
307 	case HD_source:
308 		return QVariant(pki_source_name());
309 	case HD_primary_key:
310 		return sqlItemId;
311 	}
312 	if (hd->type == dbheader::hd_asn1time) {
313 		a1time t = column_a1time(hd);
314 		if (!t.isUndefined())
315 			return QVariant(t.toFancy());
316 	}
317 	return QVariant();
318 }
319 
column_a1time(const dbheader * hd) const320 a1time pki_base::column_a1time(const dbheader *hd) const
321 {
322 	switch (hd->id) {
323 	case HD_creation:
324 		return insertion_date;
325 	}
326 	return a1time().setUndefined();
327 }
328 
getIcon(const dbheader * hd) const329 QVariant pki_base::getIcon(const dbheader *hd) const
330 {
331 	(void)hd;
332 	return QVariant();
333 }
334 
column_tooltip(const dbheader * hd) const335 QVariant pki_base::column_tooltip(const dbheader *hd) const
336 {
337 	switch (hd->id) {
338 	case HD_comment:
339 		return QVariant(comment);
340 	}
341 	if (hd->type == dbheader::hd_asn1time) {
342 		a1time t = column_a1time(hd);
343 		if (!t.isUndefined())
344 			return QVariant(t.toPretty());
345 	}
346 	return QVariant();
347 }
348 
compare(const pki_base * ref) const349 bool pki_base::compare(const pki_base *ref) const
350 {
351 	bool ret;
352 	ret = (i2d() == ref->i2d());
353 	pki_openssl_error();
354 	return ret;
355 }
356 
357 /* Unsigned 32 bit integer */
hash(const QByteArray & ba)358 unsigned pki_base::hash(const QByteArray &ba)
359 {
360 	unsigned char md[EVP_MAX_MD_SIZE];
361 
362 	SHA1((const unsigned char *)ba.constData(), ba.length(), md);
363 
364 	return (((unsigned)md[0]     ) | ((unsigned)md[1]<<8L) |
365 		((unsigned)md[2]<<16L) | ((unsigned)md[3]<<24L)
366 		) & 0x7fffffffL;
367 }
hash() const368 unsigned pki_base::hash() const
369 {
370 	return hash(i2d());
371 }
372 
get_dump_filename(const QString & dir,const QString & ext) const373 QString pki_base::get_dump_filename(const QString &dir,
374 				    const QString &ext) const
375 {
376 	QString ctr = "", fn;
377 	int count = 0;
378 	while (count++ < 1000) {
379 		fn = dir + "/" + getUnderlinedName() + ctr + ext;
380 		if (!QFile::exists(fn))
381 			return fn;
382 		ctr = QString("_%1").arg(count);
383 	}
384 	return fn;
385 }
386 
selfComment(QString msg)387 void pki_base::selfComment(QString msg)
388 {
389 	setComment(appendXcaComment(getComment(), msg));
390 }
391 
collect_properties(QMap<QString,QString> & prp) const392 void pki_base::collect_properties(QMap<QString, QString> &prp) const
393 {
394 	QString t;
395 	prp["Descriptor"] = getIntName();
396 	if (getComment().size() > 0)
397 		prp["Comment"] = "\n" + getComment().replace('\n', "\n    ");
398 	switch (pkiType) {
399 	case asym_key:   t = "Asymetric Key"; break;
400 	case x509_req:   t = "PKCS#10 Certificate request"; break;
401 	case x509:       t = "x.509 Certificate"; break;
402 	case revocation: t = "Certificate revocation list"; break;
403 	case tmpl:       t = "XCA Template"; break;
404 	default:         t = "Unknown"; break;
405 	}
406 	prp["Type"] = t;
407 }
408 
print(BioByteArray & bba,enum print_opt opt) const409 void pki_base::print(BioByteArray &bba, enum print_opt opt) const
410 {
411 	static const QStringList order = {
412 		"Type", "Descriptor", "Subject", "Issuer", "Serial",
413 		"Not Before", "Not After", "Verify Ok",
414 		"Unstructured Name", "Challange Password",
415 		"Last Update", "Next Update", "CA", "Self signed",
416 		"Key", "Signature", "Extensions", "Comment",
417 	};
418 	if (opt == print_coloured) {
419 		QMap<QString, QString> prp;
420 		QStringList keys;
421 		int w = 0;
422 
423 		collect_properties(prp);
424 		keys = prp.keys();
425 
426 		foreach (const QString &key, keys) {
427 			if (key.size() > w)
428 				w = key.size();
429 			if (!order.contains(key))
430 				XCA_WARN(tr("Property '%1' not listed in 'pki_base::print'").arg(key));
431 		}
432 		w = (w + 1) * -1;
433 		foreach (const QString &key, order) {
434 			if (!prp.contains(key))
435 				continue;
436 
437 			bba += QString(COL_YELL "%1" COL_RESET " %2\n")
438 				.arg(key + ":", w).arg(prp[key]).toUtf8();
439 		}
440 	}
441 }
442 
icsValue(QString s)443 static QString icsValue(QString s)
444 {
445 	int n = 60;
446 	QStringList lines;
447 
448 	QString t = s.replace(QRegExp("([,;\\\\])"), "\\\\1")
449 			.replace("\n", "\\n")
450 			.replace("\r", "\\r");
451 	qDebug() << "S:" << s;
452 	for (int j = n; !s.isEmpty(); j--) {
453 		QString sub = s.left(j);
454 		if (sub.endsWith("\\") || sub.toUtf8().length() > n)
455 			continue;
456 		s.remove(0, j);
457 		lines << sub;
458 		j = n = 74;
459 	}
460 	return lines.join("\r\n ");
461 }
462 
icsVEVENT(const a1time & expires,const QString & summary,const QString & description) const463 QStringList pki_base::icsVEVENT(const a1time &expires,
464 	const QString &summary, const QString &description) const
465 {
466 	QString uniqueid = formatHash(Digest(i2d(), EVP_sha1()), "");
467 	QString desc = icsValue(description + "\n----------\n" + comment);
468 	QString alarm = Settings["ical_expiry"];
469 	return QStringList() <<
470 
471 	"BEGIN:VEVENT" <<
472 	QString("DTSTAMP:%1").arg(a1time().toString("yyyyMMdd'T'HHmmss'Z'")) <<
473 	QString("UID:EXP-%1@xca.ovh").arg(uniqueid) <<
474 	"STATUS:CONFIRMED" <<
475 	QString("DTSTART:%1").arg(expires.toString("yyyyMMdd")) <<
476 	"DURATION:P1D" <<
477 	QString("SUMMARY:%1").arg(icsValue(summary)) <<
478 	QString("DESCRIPTION:%1").arg(desc) <<
479 	"BEGIN:VALARM" <<
480 	"ACTION:DISPLAY" <<
481 	QString("SUMMARY:%1").arg(icsValue(summary)) <<
482 	QString("DESCRIPTION:%1").arg(desc) <<
483 	QString("TRIGGER:-P%1").arg(alarm) <<
484 	"END:VALARM" <<
485 	"END:VEVENT";
486 }
487