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