Libkleo

dn.cpp
1/*
2 dn.cpp
3
4 This file is part of libkleopatra, the KDE keymanagement library
5 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
6 SPDX-FileCopyrightText: 2021 g10 Code GmbH
7 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
8
9 DN parsing:
10 SPDX-FileCopyrightText: 2002 g10 Code GmbH
11 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
12
13 SPDX-License-Identifier: GPL-2.0-or-later
14*/
15
16#include <config-libkleo.h>
17
18#include "dn.h"
19#include "libkleo_debug.h"
20
21#include "oidmap.h"
22
23#include <KLazyLocalizedString>
24
25#include <algorithm>
26
27#ifdef _MSC_VER
28#include <string.h>
29#define strcasecmp _stricmp
30#endif
31
32namespace
33{
34static const QStringList defaultOrder = {
35 QStringLiteral("CN"),
36 QStringLiteral("L"),
37 QStringLiteral("_X_"),
38 QStringLiteral("OU"),
39 QStringLiteral("O"),
40 QStringLiteral("C"),
41};
42
43class DNAttributeOrderStore
44{
45 DNAttributeOrderStore()
46 : mAttributeOrder{defaultOrder}
47 {
48 }
49
50public:
51 static DNAttributeOrderStore *instance()
52 {
53 static DNAttributeOrderStore *self = new DNAttributeOrderStore();
54 return self;
55 }
56
57 const QStringList &attributeOrder() const
58 {
59 return mAttributeOrder.empty() ? defaultOrder : mAttributeOrder;
60 }
61
62 void setAttributeOrder(const QStringList &order)
63 {
64 mAttributeOrder = order;
65 }
66
67private:
68 QStringList mAttributeOrder;
69};
70}
71
72class Kleo::DN::Private
73{
74public:
75 Private()
76 : mRefCount(0)
77 {
78 }
79 Private(const Private &other)
80 : attributes(other.attributes)
81 , reorderedAttributes(other.reorderedAttributes)
82 , mRefCount(0)
83 {
84 }
85
86 int ref()
87 {
88 return ++mRefCount;
89 }
90
91 int unref()
92 {
93 if (--mRefCount <= 0) {
94 delete this;
95 return 0;
96 } else {
97 return mRefCount;
98 }
99 }
100
101 int refCount() const
102 {
103 return mRefCount;
104 }
105
106 DN::Attribute::List attributes;
107 DN::Attribute::List reorderedAttributes;
108
109private:
110 int mRefCount;
111};
112
113namespace
114{
115struct DnPair {
116 char *key;
117 char *value;
118};
119}
120
121// copied from CryptPlug and adapted to work on DN::Attribute::List:
122
123#define digitp(p) (*(p) >= '0' && *(p) <= '9')
124#define hexdigitp(a) (digitp(a) || (*(a) >= 'A' && *(a) <= 'F') || (*(a) >= 'a' && *(a) <= 'f'))
125#define xtoi_1(p) (*(p) <= '9' ? (*(p) - '0') : *(p) <= 'F' ? (*(p) - 'A' + 10) : (*(p) - 'a' + 10))
126#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p) + 1))
127
128static char *trim_trailing_spaces(char *string)
129{
130 char *p;
131 char *mark;
132
133 for (mark = nullptr, p = string; *p; p++) {
134 if (isspace(*p)) {
135 if (!mark) {
136 mark = p;
137 }
138 } else {
139 mark = nullptr;
140 }
141 }
142 if (mark) {
143 *mark = '\0';
144 }
145
146 return string;
147}
148
149/* Parse a DN and return an array-ized one. This is not a validating
150 parser and it does not support any old-stylish syntax; gpgme is
151 expected to return only rfc2253 compatible strings. */
152static const unsigned char *parse_dn_part(DnPair *array, const unsigned char *string)
153{
154 const unsigned char *s;
155 const unsigned char *s1;
156 size_t n;
157 char *p;
158
159 /* parse attributeType */
160 for (s = string + 1; *s && *s != '='; s++) {
161 ;
162 }
163 if (!*s) {
164 return nullptr; /* error */
165 }
166 n = s - string;
167 if (!n) {
168 return nullptr; /* empty key */
169 }
170 p = (char *)malloc(n + 1);
171
172 memcpy(p, string, n);
173 p[n] = 0;
174 trim_trailing_spaces((char *)p);
175 // map OIDs to their names:
176 if (const char *name = Kleo::attributeNameForOID(p)) {
177 free(p);
178 p = strdup(name);
179 }
180 array->key = p;
181 string = s + 1;
182
183 if (*string == '#') {
184 /* hexstring */
185 string++;
186 for (s = string; hexdigitp(s); s++)
187 ;
188 n = s - string;
189 if (!n || (n & 1)) {
190 return nullptr; /* empty or odd number of digits */
191 }
192 n /= 2;
193 array->value = p = (char *)malloc(n + 1);
194
195 for (s1 = string; n; s1 += 2, n--) {
196 *p++ = xtoi_2(s1);
197 }
198 *p = 0;
199 } else {
200 /* regular v3 quoted string */
201 for (n = 0, s = string; *s; s++) {
202 if (*s == '\\') {
203 /* pair */
204 s++;
205 if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';' || *s == '\\' || *s == '\"' || *s == ' ') {
206 n++;
207 } else if (hexdigitp(s) && hexdigitp(s + 1)) {
208 s++;
209 n++;
210 } else {
211 return nullptr; /* invalid escape sequence */
212 }
213 } else if (*s == '\"') {
214 return nullptr; /* invalid encoding */
215 } else if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';') {
216 break;
217 } else {
218 n++;
219 }
220 }
221
222 array->value = p = (char *)malloc(n + 1);
223
224 for (s = string; n; s++, n--) {
225 if (*s == '\\') {
226 s++;
227 if (hexdigitp(s)) {
228 *p++ = xtoi_2(s);
229 s++;
230 } else {
231 *p++ = *s;
232 }
233 } else {
234 *p++ = *s;
235 }
236 }
237 *p = 0;
238 }
239 return s;
240}
241
242/* Parse a DN and return an array-ized one. This is not a validating
243 parser and it does not support any old-stylish syntax; gpgme is
244 expected to return only rfc2253 compatible strings. */
245static Kleo::DN::Attribute::List parse_dn(const unsigned char *string)
246{
247 if (!string) {
249 }
250
252 while (*string) {
253 while (*string == ' ') {
254 string++;
255 }
256 if (!*string) {
257 break; /* ready */
258 }
259
260 DnPair pair = {nullptr, nullptr};
261 string = parse_dn_part(&pair, string);
262 if (!string) {
263 goto failure;
264 }
265 if (pair.key && pair.value) {
266 result.push_back(Kleo::DN::Attribute(QString::fromUtf8(pair.key), QString::fromUtf8(pair.value)));
267 }
268 free(pair.key);
269 free(pair.value);
270
271 while (*string == ' ') {
272 string++;
273 }
274 if (*string && *string != ',' && *string != ';' && *string != '+') {
275 goto failure; /* invalid delimiter */
276 }
277 if (*string) {
278 string++;
279 }
280 }
281 return result;
282
283failure:
285}
286
287static QList<Kleo::DN::Attribute> parse_dn(const QString &dn)
288{
289 return parse_dn((const unsigned char *)dn.toUtf8().data());
290}
291
292static QString dn_escape(const QString &s)
293{
294 QString result;
295 for (int i = 0, end = s.length(); i != end; ++i) {
296 const QChar ch = s[i];
297 switch (ch.unicode()) {
298 case ',':
299 case '+':
300 case '"':
301 case '\\':
302 case '<':
303 case '>':
304 case ';':
305 result += QLatin1Char('\\');
306 // fall through
307 [[fallthrough]];
308 default:
309 result += ch;
310 }
311 }
312 return result;
313}
314
315static QString serialise(const QList<Kleo::DN::Attribute> &dn, const QString &sep)
316{
317 QStringList result;
318 for (QList<Kleo::DN::Attribute>::const_iterator it = dn.begin(); it != dn.end(); ++it) {
319 if (!(*it).name().isEmpty() && !(*it).value().isEmpty()) {
320 result.push_back((*it).name().trimmed() + QLatin1Char('=') + dn_escape((*it).value().trimmed()));
321 }
322 }
323 return result.join(sep);
324}
325
326static Kleo::DN::Attribute::List reorder_dn(const Kleo::DN::Attribute::List &dn)
327{
328 const QStringList &attrOrder = Kleo::DN::attributeOrder();
329
330 Kleo::DN::Attribute::List unknownEntries;
331 Kleo::DN::Attribute::List result;
332 unknownEntries.reserve(dn.size());
333 result.reserve(dn.size());
334
335 // find all unknown entries in their order of appearance
336 for (Kleo::DN::const_iterator it = dn.begin(); it != dn.end(); ++it) {
337 if (!attrOrder.contains((*it).name())) {
338 unknownEntries.push_back(*it);
339 }
340 }
341
342 // process the known attrs in the desired order
343 for (QStringList::const_iterator oit = attrOrder.begin(); oit != attrOrder.end(); ++oit) {
344 if (*oit == QLatin1StringView("_X_")) {
345 // insert the unknown attrs
346 std::copy(unknownEntries.begin(), unknownEntries.end(), std::back_inserter(result));
347 unknownEntries.clear(); // don't produce dup's
348 } else {
349 for (Kleo::DN::const_iterator dnit = dn.begin(); dnit != dn.end(); ++dnit) {
350 if ((*dnit).name() == *oit) {
351 result.push_back(*dnit);
352 }
353 }
354 }
355 }
356
357 return result;
358}
359
360//
361//
362// class DN
363//
364//
365
366Kleo::DN::DN()
367{
368 d = new Private();
369 d->ref();
370}
371
372Kleo::DN::DN(const QString &dn)
373{
374 d = new Private();
375 d->ref();
376 d->attributes = parse_dn(dn);
377}
378
379Kleo::DN::DN(const char *utf8DN)
380{
381 d = new Private();
382 d->ref();
383 if (utf8DN) {
384 d->attributes = parse_dn((const unsigned char *)utf8DN);
385 }
386}
387
388Kleo::DN::DN(const DN &other)
389 : d(other.d)
390{
391 if (d) {
392 d->ref();
393 }
394}
395
396Kleo::DN::~DN()
397{
398 if (d) {
399 d->unref();
400 }
401}
402
403const Kleo::DN &Kleo::DN::operator=(const DN &that)
404{
405 if (this->d == that.d) {
406 return *this;
407 }
408
409 if (that.d) {
410 that.d->ref();
411 }
412 if (this->d) {
413 this->d->unref();
414 }
415
416 this->d = that.d;
417
418 return *this;
419}
420
421// static
422QStringList Kleo::DN::attributeOrder()
423{
424 return DNAttributeOrderStore::instance()->attributeOrder();
425}
426
427// static
428void Kleo::DN::setAttributeOrder(const QStringList &order)
429{
430 DNAttributeOrderStore::instance()->setAttributeOrder(order);
431}
432
433// static
434QStringList Kleo::DN::defaultAttributeOrder()
435{
436 return defaultOrder;
437}
438
440{
441 if (!d) {
442 return QString();
443 }
444 if (d->reorderedAttributes.empty()) {
445 d->reorderedAttributes = reorder_dn(d->attributes);
446 }
447 return serialise(d->reorderedAttributes, QStringLiteral(","));
448}
449
451{
452 return d ? serialise(d->attributes, QStringLiteral(",")) : QString();
453}
454
455QString Kleo::DN::dn(const QString &sep) const
456{
457 return d ? serialise(d->attributes, sep) : QString();
458}
459
460// static
462{
463 return dn_escape(value);
464}
465
466void Kleo::DN::detach()
467{
468 if (!d) {
469 d = new Kleo::DN::Private();
470 d->ref();
471 } else if (d->refCount() > 1) {
472 Kleo::DN::Private *d_save = d;
473 d = new Kleo::DN::Private(*d);
474 d->ref();
475 d_save->unref();
476 }
477}
478
479void Kleo::DN::append(const Attribute &attr)
480{
481 detach();
482 d->attributes.push_back(attr);
483 d->reorderedAttributes.clear();
484}
485
486QString Kleo::DN::operator[](const QString &attr) const
487{
488 if (!d) {
489 return QString();
490 }
491 const QString attrUpper = attr.toUpper();
492 for (QList<Attribute>::const_iterator it = d->attributes.constBegin(); it != d->attributes.constEnd(); ++it) {
493 if ((*it).name() == attrUpper) {
494 return (*it).value();
495 }
496 }
497 return QString();
498}
499
500static QList<Kleo::DN::Attribute> empty;
501
502Kleo::DN::const_iterator Kleo::DN::begin() const
503{
504 return d ? d->attributes.constBegin() : empty.constBegin();
505}
506
507Kleo::DN::const_iterator Kleo::DN::end() const
508{
509 return d ? d->attributes.constEnd() : empty.constEnd();
510}
511
512/////////////////////
513
514namespace
515{
516static const QMap<QString, KLazyLocalizedString> attributeNamesAndLabels = {
517 // clang-format off
518 {QStringLiteral("CN"), kli18n("Common name") },
519 {QStringLiteral("SN"), kli18n("Surname") },
520 {QStringLiteral("GN"), kli18n("Given name") },
521 {QStringLiteral("L"), kli18n("Location") },
522 {QStringLiteral("T"), kli18n("Title") },
523 {QStringLiteral("OU"), kli18n("Organizational unit")},
524 {QStringLiteral("O"), kli18n("Organization") },
525 {QStringLiteral("PC"), kli18n("Postal code") },
526 {QStringLiteral("C"), kli18n("Country code") },
527 {QStringLiteral("SP"), kli18n("State or province") },
528 {QStringLiteral("DC"), kli18n("Domain component") },
529 {QStringLiteral("BC"), kli18n("Business category") },
530 {QStringLiteral("EMAIL"), kli18n("Email address") },
531 {QStringLiteral("MAIL"), kli18n("Mail address") },
532 {QStringLiteral("MOBILE"), kli18n("Mobile phone number")},
533 {QStringLiteral("TEL"), kli18n("Telephone number") },
534 {QStringLiteral("FAX"), kli18n("Fax number") },
535 {QStringLiteral("STREET"), kli18n("Street address") },
536 {QStringLiteral("UID"), kli18n("Unique ID") },
537 // clang-format on
538};
539}
540
541// static
542QStringList Kleo::DN::attributeNames()
543{
544 return attributeNamesAndLabels.keys();
545}
546
547// static
548QString Kleo::DN::attributeNameToLabel(const QString &name)
549{
550 const QString key{name.trimmed().toUpper()};
551 if (attributeNames().contains(key)) {
552 return attributeNamesAndLabels.value(key).toString();
553 }
554 qCWarning(LIBKLEO_LOG) << "Attribute " << key << " doesn't exit. Bug ?";
555 return {};
556}
DN parser and reorderer.
Definition dn.h:27
static QString escape(const QString &value)
Definition dn.cpp:461
QString prettyDN() const
Definition dn.cpp:439
QString dn() const
Definition dn.cpp:450
const QList< QKeySequence > & end()
char * data()
char16_t & unicode()
iterator begin()
bool empty() const const
iterator end()
void push_back(parameter_type value)
void reserve(qsizetype size)
QString fromUtf8(QByteArrayView str)
qsizetype length() const const
QString toUpper() const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:11 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.