1 /* vi: set sw=4 ts=4:
2 *
3 * Copyright (C) 2001 - 2014 Christian Hohnstaedt.
4 *
5 * All rights reserved.
6 */
7
8
9 #include <unistd.h>
10 #include "func.h"
11 #include "exception.h"
12 #include "lib/asn1time.h"
13 #include "lib/settings.h"
14 #include "widgets/validity.h"
15 #include "widgets/XcaWarning.h"
16 #include <openssl/objects.h>
17 #include <openssl/sha.h>
18 #include <openssl/asn1.h>
19 #include <openssl/err.h>
20 #include <openssl/bio.h>
21 #include <openssl/buffer.h>
22
23 #if defined(Q_OS_MAC)
24 #include <IOKit/IOKitLib.h>
25 #if QT_VERSION < 0x050000
26 #include <QDesktopServices>
27 #else
28 #include <QStandardPaths>
29 #endif
30 #endif
31 #include <QDir>
32 #include <QFile>
33 #include <QFileInfo>
34 #include <QStringList>
35 #include <QLabel>
36 #include <QLineEdit>
37 #include <QComboBox>
38 #include <QMessageBox>
39 #include <QApplication>
40 #include <QPushButton>
41 #include <QProgressBar>
42 #include <QTextEdit>
43 #include <QDebug>
44 #include <stdarg.h>
45
46 #if defined(Q_OS_WIN32)
47 #include <shlobj.h>
48 #include <conio.h>
49 #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
50 #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x04
51 #endif
52 #else
53 #include <termios.h>
54 #define getch() getchar()
55 #endif
56
console_write(FILE * fp,const QByteArray & ba)57 int console_write(FILE *fp, const QByteArray &ba)
58 {
59 if (ba.size() == 0)
60 return 0;
61 #if defined(Q_OS_WIN32)
62 HANDLE con = GetStdHandle(fp == stderr ? STD_ERROR_HANDLE :
63 STD_OUTPUT_HANDLE);
64 if (con != INVALID_HANDLE_VALUE) {
65 QString string = QString::fromUtf8(ba);
66 WriteConsoleW(con, string.utf16(), string.size(), NULL, NULL);
67 //return 0;
68 }
69 #endif
70 fputs(ba.constData(), fp);
71 fflush(fp);
72 return 0;
73 }
74
readPass()75 Passwd readPass()
76 {
77 Passwd pw;
78 #if !defined(Q_OS_WIN32)
79 struct termios t, back;
80 if (tcgetattr(0, &t))
81 throw errorEx(strerror(errno));
82 back = t;
83 t.c_lflag &= ~(ECHO | ICANON);
84 if (tcsetattr(0, TCSAFLUSH, &t))
85 throw errorEx(strerror(errno));
86 #else
87 qFatal("Password input not supported");
88 #endif
89 while(1) {
90 char p = getch();
91 if (p == '\n' || p == '\r')
92 break;
93 if (p == 0x7f)
94 pw.chop(1);
95 else
96 pw += p;
97 }
98 fputc('\n', stdout);
99 #if !defined(Q_OS_WIN32)
100 if (tcsetattr(0, TCSAFLUSH, &back))
101 throw errorEx(strerror(errno));
102 #endif
103 return pw;
104 }
105
loadImg(const char * name)106 QPixmap *loadImg(const char *name )
107 {
108 return new QPixmap(QString(":") + name);
109 }
110
getLibExtensions()111 const QStringList getLibExtensions()
112 {
113 return QStringList {
114 #if defined(Q_OS_WIN32)
115 QString("*.dll"), QString("*.DLL"),
116 #elif defined(Q_OS_MAC)
117 QString("*.dylib"), QString("*.so"),
118 #else
119 QString("*.so"),
120 #endif
121 };
122 }
123
124 #if defined(Q_OS_WIN32)
xcaExeDir()125 static QString xcaExeDir()
126 {
127 QString dir;
128 wchar_t inst_dir[2048];
129 ULONG dwLength = ARRAY_SIZE(inst_dir);
130
131 dwLength = GetModuleFileNameW(0, inst_dir, dwLength - 1);
132 dir = QString::fromWCharArray(inst_dir, dwLength);
133 int bslash = dir.lastIndexOf("\\");
134 if (bslash > 0)
135 dir = dir.mid(0, bslash);
136 return QFileInfo(dir).canonicalFilePath();
137 }
138
registryInstallDir()139 static QString registryInstallDir()
140 {
141 QString dir;
142 wchar_t inst_dir[2048] = L"";
143 ULONG len = sizeof inst_dir;
144
145 if (RegGetValueW(HKEY_LOCAL_MACHINE, L"Software\\xca",
146 L"Install_Dir64", RRF_RT_REG_SZ, NULL,
147 inst_dir, &len) != ERROR_SUCCESS)
148 return dir;
149
150 /* "len" is in octets */
151 len /= sizeof inst_dir[0];
152 /* "len" includes the trailing \0\0 */
153 dir = QString::fromWCharArray(inst_dir, len -1);
154 return QFileInfo(dir).canonicalFilePath();
155 }
156 #endif
157
portable_app()158 int portable_app()
159 {
160 static int portable = -1;
161 QString f1, f2;
162 if (portable == -1) {
163 #if defined(Q_OS_WIN32)
164 f1 = registryInstallDir();
165 f2 = xcaExeDir();
166 /* f1 == f2 Registry entry of install dir exists and matches
167 * path of this xca.exe -> Installed. Not the portable app
168 */
169 portable = f1 == f2 ? 0 : 1;
170 qDebug() << "Portable:" << f1 << " != " << f2;
171 #else
172 const char *p = getenv("XCA_PORTABLE");
173 portable = p && *p;
174 #endif
175 }
176 return portable;
177 }
178
179 /* returns e.g. /usr/local/share/xca for unix systems
180 * or HKEY_LOCAL_MACHINE->Software->xca for WIN32
181 * (e.g. c:\Program Files\xca )
182 */
183
getPrefix()184 const QString getPrefix()
185 {
186 #if defined(Q_OS_WIN32)
187 static QString inst_dir;
188 QString reg_dir;
189
190 if (!inst_dir.isEmpty()) {
191 /* if we already once discovered the directory just return it */
192 return inst_dir;
193 }
194 inst_dir = xcaExeDir();
195
196 if (portable_app())
197 return QString(inst_dir);
198
199 reg_dir = registryInstallDir();
200 if (reg_dir.isEmpty())
201 XCA_WARN("Registry Key: 'HKEY_LOCAL_MACHINE->Software->xca->Install_Dir' not found");
202 else
203 inst_dir = reg_dir;
204 return inst_dir;
205
206 #elif defined(Q_OS_MAC)
207 // since this is platform-specific anyway,
208 // this is a more robust way to get the bundle directory
209 QDir bundleDir(qApp->applicationDirPath());
210 bundleDir.cdUp();
211 return bundleDir.canonicalPath() + "/Resources";
212 #else
213 #ifndef XCA_PREFIX
214 #define XCA_PREFIX PREFIX "/share/xca"
215 #endif
216 return QString(XCA_PREFIX);
217 #endif
218
219 }
220
221 #if defined(Q_OS_WIN32)
specialFolder(int csidl)222 static QString specialFolder(int csidl)
223 {
224 LPITEMIDLIST pidl = NULL;
225 wchar_t buf[MAX_PATH] = L"";
226
227 if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, csidl, &pidl)))
228 SHGetPathFromIDListW(pidl, buf);
229
230 QString f = QString::fromWCharArray(buf);
231 qDebug() << "Special Folder" << csidl << f;
232 return QFileInfo(f).canonicalFilePath();
233 }
234 #endif
235
getHomeDir()236 const QString getHomeDir()
237 {
238 #if defined(Q_OS_WIN32)
239 return portable_app() ? getPrefix() : specialFolder(CSIDL_PERSONAL);
240 #else
241 return QDir::homePath();
242 #endif
243 }
244
245 /* For portable APP remove leading file name if it is
246 * the app directory.
247 */
relativePath(QString path)248 QString relativePath(QString path)
249 {
250 QFileInfo fi_path(path);
251 QFileInfo fi_home(getHomeDir());
252
253 QString prefix = fi_home.absoluteFilePath();
254 path = fi_path.absoluteFilePath();
255
256 if (portable_app()) {
257 if (path.startsWith(prefix))
258 path = path.mid(prefix.length()+1);
259 }
260 return path;
261 }
262
getLibDir()263 const QString getLibDir()
264 {
265 #if defined(Q_OS_WIN32)
266 return specialFolder(CSIDL_SYSTEM);
267 #else
268 QString ulib = "/usr/lib/";
269 QString lib = "/lib/";
270 QString multi;
271 QString hd = ulib;
272
273 QFile f(ulib + "pkg-config.multiarch");
274 if (f.open(QIODevice::ReadOnly)) {
275 QTextStream in(&f);
276 multi = in.readLine();
277 if (!multi.isEmpty())
278 multi += "/";
279 }
280 QStringList dirs; dirs
281 << ulib + multi + "pkcs11/"
282 << lib + multi + "pkcs11/"
283 << ulib + "pkcs11/"
284 << lib + "pkcs11/"
285 << ulib + multi
286 << lib + multi
287 << ulib
288 << lib;
289 foreach(QString dir, dirs) {
290 if (QDir(dir).exists()) {
291 hd = dir;
292 break;
293 }
294 }
295 return QFileInfo(hd).canonicalFilePath();
296 #endif
297 }
298
getDocDir()299 const QString getDocDir()
300 {
301 #if defined(Q_OS_WIN32)
302 return getPrefix() + "\\html";
303 #elif defined (Q_OS_MAC)
304 return getPrefix();
305 #else
306 return QString(DOCDIR);
307 #endif
308 }
309
310 // The intent of this function is to return the proper location for
311 // user-controlled settings on the current platform
312 // i.e. PROFILE\Application Data\xca on windows, HOME/.xca on UNIX,
313 // ~/Library/Preferences/xca on Mac OS X
getUserSettingsDir()314 const QString getUserSettingsDir()
315 {
316 QString rv;
317 #if defined(Q_OS_WIN32)
318 rv = portable_app() ? getPrefix() + "/settings" :
319 specialFolder(CSIDL_APPDATA) + "/xca";
320 #elif defined(Q_OS_MAC)
321 #if QT_VERSION < 0x050000
322 rv = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
323 rv.insert(rv.count() - QCoreApplication::applicationName().count(),
324 QCoreApplication::organizationName());
325 #else
326 rv = QStandardPaths::writableLocation(
327 QStandardPaths::GenericDataLocation) + "/data/" +
328 QCoreApplication::organizationName() + "/" +
329 QCoreApplication::applicationName();
330 #endif
331 #else
332 rv = QDir::homePath() + "/.xca";
333 #endif
334 return rv;
335 }
336
getI18nDir()337 const QString getI18nDir()
338 {
339 #if defined(Q_OS_WIN32)
340 return getPrefix() + "\\i18n";
341 #else
342 return getPrefix();
343 #endif
344 }
345
346 // Qt's open and save dialogs result in some undesirable quirks.
347 // This function makes sure that a filename has the user-selected extension.
getFullFilename(const QString & filename,const QString & selectedFilter)348 QString getFullFilename(const QString & filename, const QString & selectedFilter)
349 {
350 QString rv = filename.trimmed(), ext;
351 QRegExp rx(".* \\( ?\\*(.[a-z]{1,3}) ?\\)");
352 rx.indexIn(selectedFilter);
353 ext = rx.cap(1);
354 if (!ext.isEmpty() && !rv.endsWith(ext)) {
355 rv += ext;
356 }
357 return rv;
358 }
359
hostId()360 QString hostId()
361 {
362 static QString id;
363 unsigned char guid[100] = "", md[SHA_DIGEST_LENGTH];
364
365 if (!id.isEmpty())
366 return id;
367
368 #if defined(Q_OS_WIN32)
369 #define REG_CRYPTO "SOFTWARE\\Microsoft\\Cryptography"
370 #define REG_GUID "MachineGuid"
371 ULONG dwGuid = sizeof guid;
372 HKEY hKey;
373
374 if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, REG_CRYPTO, 0,
375 KEY_READ, &hKey) != ERROR_SUCCESS) {
376 XCA_WARN("Registry Key: '" REG_CRYPTO "' not found");
377 } else {
378 if (RegQueryValueExA(hKey, REG_GUID, NULL, NULL,
379 guid, &dwGuid) != ERROR_SUCCESS) {
380 XCA_WARN("Registry Key: '" REG_CRYPTO "\\" REG_GUID
381 "' not found");
382 }
383 }
384 RegCloseKey(hKey);
385
386 #elif defined(Q_OS_MAC)
387 io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(
388 kIOMasterPortDefault, "IOService:/");
389 CFStringRef uuidCf = (CFStringRef)IORegistryEntryCreateCFProperty(
390 ioRegistryRoot, CFSTR(kIOPlatformUUIDKey),
391 kCFAllocatorDefault, 0);
392
393 snprintf((char*)guid, sizeof guid, "%s", CCHAR(
394 QString::fromUtf16(CFStringGetCharactersPtr(uuidCf))
395 ));
396
397 IOObjectRelease(ioRegistryRoot);
398 CFRelease(uuidCf);
399
400 #else
401 QString mach_id;
402 QStringList dirs; dirs <<
403 "/etc" << "/var/lib/dbus" << "/var/db/dbus";
404 foreach(QString dir, dirs) {
405 QFile file(dir + "/machine-id");
406 if (file.open(QIODevice::ReadOnly)) {
407 QTextStream in(&file);
408 mach_id = in.readLine().trimmed();
409 file.close();
410 }
411 qDebug() << "ID:" << mach_id;
412 if (!mach_id.isEmpty()) {
413 snprintf((char*)guid, sizeof guid, "%s", CCHAR(mach_id));
414 break;
415 }
416 }
417 if (mach_id.isEmpty())
418 sprintf((char*)guid, "%ld", gethostid());
419 #endif
420 guid[sizeof guid -1] = 0;
421 SHA1(guid, strlen((char*)guid), md);
422 id = QByteArray((char*)md, (int)sizeof md).toBase64().mid(0, 8);
423
424 qDebug() << "GUID:" << guid << "ID:" << id;
425
426 return id;
427 }
428
compressFilename(const QString & filename,int maxlen)429 QString compressFilename(const QString &filename, int maxlen)
430 {
431 QString fn = filename;
432 if (fn.length() >= maxlen) {
433 fn.replace("\\", "/");
434 int len, lastslash = fn.lastIndexOf('/');
435 QString base = filename.mid(lastslash);
436 len = maxlen - base.length() - 3;
437 if (len < 0) {
438 fn = "..." + base.right(maxlen -3);
439 } else {
440 fn = fn.left(len);
441 lastslash = fn.lastIndexOf('/');
442 fn = filename.left(lastslash + 1) + "..." + base;
443 }
444 }
445 return nativeSeparator(fn);
446 }
447
asn1ToQString(const ASN1_STRING * str,bool quote)448 QString asn1ToQString(const ASN1_STRING *str, bool quote)
449 {
450 QString qs;
451 unsigned short *bmp;
452 int i;
453
454 if (!str)
455 return qs;
456
457 switch (str->type) {
458 case V_ASN1_BMPSTRING:
459 bmp = (unsigned short*)str->data;
460 for (i = 0; i < str->length/2; i++) {
461 unsigned short s = xntohs(bmp[i]);
462 qs += QString::fromUtf16(&s, 1);
463 }
464 break;
465 case V_ASN1_UTF8STRING:
466 qs = QString::fromUtf8((const char*)str->data, str->length);
467 break;
468 case V_ASN1_T61STRING:
469 qs = QString::fromLocal8Bit((const char*)str->data, str->length);
470 break;
471 default:
472 qs = QString::fromLatin1((const char*)str->data, str->length);
473 }
474 #if 0
475 QString s;
476 qDebug("Convert %s (%d %d) string to '%s' len %d:",
477 ASN1_tag2str(str->type), str->type,
478 V_ASN1_UTF8STRING, CCHAR(qs), str->length);
479 for (int i=0; i< str->length; i++)
480 s += QString(" %1").arg(str->data[i], 2, 16);
481 qDebug() << s;
482 #endif
483 if (quote)
484 qs.replace('\n', "\\n\\");
485 return qs;
486 }
487
488 /* returns an encoded ASN1 string from QString for a special nid*/
QStringToAsn1(const QString s,int nid)489 ASN1_STRING *QStringToAsn1(const QString s, int nid)
490 {
491 QByteArray ba = s.toUtf8();
492 const unsigned char *utf8 = (const unsigned char *)ba.constData();
493 unsigned long global_mask = ASN1_STRING_get_default_mask();
494 unsigned long mask = DIRSTRING_TYPE & global_mask;
495 ASN1_STRING *out = NULL;
496 ASN1_STRING_TABLE *tbl;
497
498 tbl = ASN1_STRING_TABLE_get(nid);
499 if (tbl) {
500 mask = tbl->mask;
501 if (!(tbl->flags & STABLE_NO_MASK))
502 mask &= global_mask;
503 }
504 ASN1_mbstring_copy(&out, utf8, -1, MBSTRING_UTF8, mask);
505 openssl_error(QString("'%1' (%2)").arg(s).arg(OBJ_nid2ln(nid)));
506 return out;
507 }
508
OBJ_ln2sn(const char * ln)509 const char *OBJ_ln2sn(const char *ln)
510 {
511 return OBJ_nid2sn(OBJ_ln2nid(ln));
512 }
513
OBJ_sn2ln(const char * sn)514 const char *OBJ_sn2ln(const char *sn)
515 {
516 return OBJ_nid2ln(OBJ_sn2nid(sn));
517 }
518
OBJ_obj2sn(ASN1_OBJECT * a)519 const char *OBJ_obj2sn(ASN1_OBJECT *a)
520 {
521 OBJ_obj2nid(a);
522 openssl_error();
523 return OBJ_nid2sn(OBJ_obj2nid(a));
524 }
525
OBJ_obj2QString(const ASN1_OBJECT * a,int no_name)526 QString OBJ_obj2QString(const ASN1_OBJECT *a, int no_name)
527 {
528 char buf[512];
529 int len;
530
531 len = OBJ_obj2txt(buf, sizeof buf, a, no_name);
532 openssl_error();
533 return QString::fromLatin1(buf, len);
534 }
535
i2d_bytearray(int (* i2d)(const void *,unsigned char **),const void * data)536 QByteArray i2d_bytearray(int(*i2d)(const void*, unsigned char **),
537 const void *data)
538 {
539 QByteArray ba;
540
541 ba.resize(i2d(data, NULL));
542 unsigned char *p = (unsigned char*)ba.data();
543 i2d(data, &p);
544 openssl_error();
545 return ba;
546 }
547
d2i_bytearray(void * (* d2i)(void *,unsigned char **,long),QByteArray & ba)548 void *d2i_bytearray(void *(*d2i)(void *, unsigned char **, long),
549 QByteArray &ba)
550 {
551 unsigned char *p, *p1;
552 void *ret;
553 p = p1 = (unsigned char *)ba.constData();
554 ret = d2i(NULL, &p1, ba.count());
555 ba = ba.mid(p1-p);
556 openssl_error();
557 return ret;
558 }
559
_openssl_error(const QString & txt,const char * file,int line)560 void _openssl_error(const QString &txt, const char *file, int line)
561 {
562 QString error;
563
564 while (int i = ERR_get_error() ) {
565 error += QString(ERR_error_string(i, NULL)) + "\n";
566 fputs(CCHAR(QString("OpenSSL error (%1:%2) : %3\n").
567 arg(file).arg(line).arg(ERR_error_string(i, NULL))),
568 stderr);
569 }
570 if (!error.isEmpty()) {
571 if (!txt.isEmpty())
572 error = txt + "\n" + error + "\n" +
573 QString("(%1:%2)").arg(file).arg(line);
574 throw errorEx(error);
575 }
576 }
577
578 #undef PRINT_IGNORED_ANYWAY
_ign_openssl_error(const QString & txt,const char * file,int line)579 bool _ign_openssl_error(const QString &txt, const char *file, int line)
580 {
581 // ignore openssl errors
582 QString errtxt;
583 #if PRINT_IGNORED_ANYWAY
584 if (!txt.isEmpty() && ERR_peek_error())
585 qDebug() << txt;
586 #else
587 (void)txt;
588 (void)file;
589 (void)line;
590 #endif
591 while (int i = ERR_get_error() ) {
592 errtxt = ERR_error_string(i, NULL);
593 #if PRINT_IGNORED_ANYWAY
594 qDebug() << QString("IGNORED (%1:%2) : %3\n")
595 .arg(file).arg(line).arg(errtxt);
596 #endif
597 }
598 return !errtxt.isEmpty();
599 }
600
formatHash(const QByteArray & data,QString sep,int width)601 QString formatHash(const QByteArray &data, QString sep, int width)
602 {
603 return QString(data.toHex()).toUpper()
604 .replace(QRegExp(QString("(.{%1})(?=.)").arg(width)),
605 QString("\\1") + sep);
606 }
607
Digest(const QByteArray & data,const EVP_MD * type)608 QByteArray Digest(const QByteArray &data, const EVP_MD *type)
609 {
610 unsigned int n;
611 unsigned char m[EVP_MAX_MD_SIZE];
612
613 EVP_Digest(data.constData(), data.size(), m, &n, type, NULL);
614 openssl_error();
615 return QByteArray((char*)m, (int)n);
616 }
617
fingerprint(const QByteArray & data,const EVP_MD * type)618 QString fingerprint(const QByteArray &data, const EVP_MD *type)
619 {
620 return formatHash(Digest(data, type),
621 Settings["fp_separator"], Settings["fp_digits"]);
622 }
623
update_workingdir(const QString & file)624 void update_workingdir(const QString &file)
625 {
626 Settings["workingdir"] = QFileInfo(file).absolutePath();
627 }
628
629 QMap<int, QString> dn_translations;
630
dn_translations_setup()631 void dn_translations_setup()
632 {
633 QMap<int, QString> D;
634 D[NID_countryName] = QObject::tr("Country code");
635 D[NID_stateOrProvinceName] = QObject::tr("State or Province");
636 D[NID_localityName] = QObject::tr("Locality");
637 D[NID_organizationName] = QObject::tr("Organisation");
638 D[NID_organizationalUnitName] = QObject::tr("Organisational unit");
639 D[NID_commonName] = QObject::tr("Common name");
640 D[NID_pkcs9_emailAddress] = QObject::tr("E-Mail address");
641 D[NID_serialNumber] = QObject::tr("Serial number");
642 D[NID_givenName] = QObject::tr("Given name");
643 D[NID_surname] = QObject::tr("Surname");
644 D[NID_title] = QObject::tr("Title");
645 D[NID_initials] = QObject::tr("Initials");
646 D[NID_description] = QObject::tr("Description");
647 D[NID_role] = QObject::tr("Role");
648 D[NID_pseudonym] = QObject::tr("Pseudonym");
649 D[NID_generationQualifier] = QObject::tr("Generation Qualifier");
650 D[NID_x500UniqueIdentifier] = QObject::tr("x500 Unique Identifier");
651 D[NID_name] = QObject::tr("Name");
652 D[NID_dnQualifier] = QObject::tr("DN Qualifier");
653 D[NID_pkcs9_unstructuredName] = QObject::tr("Unstructured name");
654 D[NID_pkcs9_challengePassword] = QObject::tr("Challenge password");
655
656 D[NID_basic_constraints] = QObject::tr("Basic Constraints");
657 D[NID_subject_alt_name] = QObject::tr("Subject alternative name");
658 D[NID_issuer_alt_name] = QObject::tr("issuer alternative name");
659 D[NID_subject_key_identifier] = QObject::tr("Subject key identifier");
660 D[NID_authority_key_identifier] = QObject::tr("Authority key identifier");
661 D[NID_key_usage] = QObject::tr("Key usage");
662 D[NID_ext_key_usage] = QObject::tr("Extended key usage");
663 D[NID_crl_distribution_points] = QObject::tr("CRL distribution points");
664 D[NID_info_access] = QObject::tr("Authority information access");
665 D[NID_netscape_cert_type] = QObject::tr("Certificate type");
666 D[NID_netscape_base_url] = QObject::tr("Base URL");
667 D[NID_netscape_revocation_url] = QObject::tr("Revocation URL");
668 D[NID_netscape_ca_revocation_url] = QObject::tr("CA Revocation URL");
669 D[NID_netscape_renewal_url] = QObject::tr("Certificate renewal URL");
670 D[NID_netscape_ca_policy_url] = QObject::tr("CA policy URL");
671 D[NID_netscape_ssl_server_name] = QObject::tr("SSL server name");
672 D[NID_netscape_comment] = QObject::tr("Comment");
673
674 dn_translations = D;
675 }
676
appendXcaComment(QString current,QString msg)677 QString appendXcaComment(QString current, QString msg)
678 {
679 if (!current.endsWith("\n") && !current.isEmpty())
680 current += "\n";
681 return current + QString("(%1)\n").arg(msg);
682 }
683