File indexing completed on 2024-06-09 05:17:16
0001 /* 0002 dn.cpp 0003 0004 This file is part of libkleopatra, the KDE keymanagement library 0005 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB 0006 SPDX-FileCopyrightText: 2021 g10 Code GmbH 0007 SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de> 0008 0009 DN parsing: 0010 SPDX-FileCopyrightText: 2002 g10 Code GmbH 0011 SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB 0012 0013 SPDX-License-Identifier: GPL-2.0-or-later 0014 */ 0015 0016 #include <config-libkleo.h> 0017 0018 #include "dn.h" 0019 #include "libkleo_debug.h" 0020 0021 #include "oidmap.h" 0022 0023 #include <KLazyLocalizedString> 0024 0025 #include <algorithm> 0026 0027 #ifdef _MSC_VER 0028 #include <string.h> 0029 #define strcasecmp _stricmp 0030 #endif 0031 0032 namespace 0033 { 0034 static const QStringList defaultOrder = { 0035 QStringLiteral("CN"), 0036 QStringLiteral("L"), 0037 QStringLiteral("_X_"), 0038 QStringLiteral("OU"), 0039 QStringLiteral("O"), 0040 QStringLiteral("C"), 0041 }; 0042 0043 class DNAttributeOrderStore 0044 { 0045 DNAttributeOrderStore() 0046 : mAttributeOrder{defaultOrder} 0047 { 0048 } 0049 0050 public: 0051 static DNAttributeOrderStore *instance() 0052 { 0053 static DNAttributeOrderStore *self = new DNAttributeOrderStore(); 0054 return self; 0055 } 0056 0057 const QStringList &attributeOrder() const 0058 { 0059 return mAttributeOrder.empty() ? defaultOrder : mAttributeOrder; 0060 } 0061 0062 void setAttributeOrder(const QStringList &order) 0063 { 0064 mAttributeOrder = order; 0065 } 0066 0067 private: 0068 QStringList mAttributeOrder; 0069 }; 0070 } 0071 0072 class Kleo::DN::Private 0073 { 0074 public: 0075 Private() 0076 : mRefCount(0) 0077 { 0078 } 0079 Private(const Private &other) 0080 : attributes(other.attributes) 0081 , reorderedAttributes(other.reorderedAttributes) 0082 , mRefCount(0) 0083 { 0084 } 0085 0086 int ref() 0087 { 0088 return ++mRefCount; 0089 } 0090 0091 int unref() 0092 { 0093 if (--mRefCount <= 0) { 0094 delete this; 0095 return 0; 0096 } else { 0097 return mRefCount; 0098 } 0099 } 0100 0101 int refCount() const 0102 { 0103 return mRefCount; 0104 } 0105 0106 DN::Attribute::List attributes; 0107 DN::Attribute::List reorderedAttributes; 0108 0109 private: 0110 int mRefCount; 0111 }; 0112 0113 namespace 0114 { 0115 struct DnPair { 0116 char *key; 0117 char *value; 0118 }; 0119 } 0120 0121 // copied from CryptPlug and adapted to work on DN::Attribute::List: 0122 0123 #define digitp(p) (*(p) >= '0' && *(p) <= '9') 0124 #define hexdigitp(a) (digitp(a) || (*(a) >= 'A' && *(a) <= 'F') || (*(a) >= 'a' && *(a) <= 'f')) 0125 #define xtoi_1(p) (*(p) <= '9' ? (*(p) - '0') : *(p) <= 'F' ? (*(p) - 'A' + 10) : (*(p) - 'a' + 10)) 0126 #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p) + 1)) 0127 0128 static char *trim_trailing_spaces(char *string) 0129 { 0130 char *p; 0131 char *mark; 0132 0133 for (mark = nullptr, p = string; *p; p++) { 0134 if (isspace(*p)) { 0135 if (!mark) { 0136 mark = p; 0137 } 0138 } else { 0139 mark = nullptr; 0140 } 0141 } 0142 if (mark) { 0143 *mark = '\0'; 0144 } 0145 0146 return string; 0147 } 0148 0149 /* Parse a DN and return an array-ized one. This is not a validating 0150 parser and it does not support any old-stylish syntax; gpgme is 0151 expected to return only rfc2253 compatible strings. */ 0152 static const unsigned char *parse_dn_part(DnPair *array, const unsigned char *string) 0153 { 0154 const unsigned char *s; 0155 const unsigned char *s1; 0156 size_t n; 0157 char *p; 0158 0159 /* parse attributeType */ 0160 for (s = string + 1; *s && *s != '='; s++) { 0161 ; 0162 } 0163 if (!*s) { 0164 return nullptr; /* error */ 0165 } 0166 n = s - string; 0167 if (!n) { 0168 return nullptr; /* empty key */ 0169 } 0170 p = (char *)malloc(n + 1); 0171 0172 memcpy(p, string, n); 0173 p[n] = 0; 0174 trim_trailing_spaces((char *)p); 0175 // map OIDs to their names: 0176 if (const char *name = Kleo::attributeNameForOID(p)) { 0177 free(p); 0178 p = strdup(name); 0179 } 0180 array->key = p; 0181 string = s + 1; 0182 0183 if (*string == '#') { 0184 /* hexstring */ 0185 string++; 0186 for (s = string; hexdigitp(s); s++) 0187 ; 0188 n = s - string; 0189 if (!n || (n & 1)) { 0190 return nullptr; /* empty or odd number of digits */ 0191 } 0192 n /= 2; 0193 array->value = p = (char *)malloc(n + 1); 0194 0195 for (s1 = string; n; s1 += 2, n--) { 0196 *p++ = xtoi_2(s1); 0197 } 0198 *p = 0; 0199 } else { 0200 /* regular v3 quoted string */ 0201 for (n = 0, s = string; *s; s++) { 0202 if (*s == '\\') { 0203 /* pair */ 0204 s++; 0205 if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';' || *s == '\\' || *s == '\"' || *s == ' ') { 0206 n++; 0207 } else if (hexdigitp(s) && hexdigitp(s + 1)) { 0208 s++; 0209 n++; 0210 } else { 0211 return nullptr; /* invalid escape sequence */ 0212 } 0213 } else if (*s == '\"') { 0214 return nullptr; /* invalid encoding */ 0215 } else if (*s == ',' || *s == '=' || *s == '+' || *s == '<' || *s == '>' || *s == '#' || *s == ';') { 0216 break; 0217 } else { 0218 n++; 0219 } 0220 } 0221 0222 array->value = p = (char *)malloc(n + 1); 0223 0224 for (s = string; n; s++, n--) { 0225 if (*s == '\\') { 0226 s++; 0227 if (hexdigitp(s)) { 0228 *p++ = xtoi_2(s); 0229 s++; 0230 } else { 0231 *p++ = *s; 0232 } 0233 } else { 0234 *p++ = *s; 0235 } 0236 } 0237 *p = 0; 0238 } 0239 return s; 0240 } 0241 0242 /* Parse a DN and return an array-ized one. This is not a validating 0243 parser and it does not support any old-stylish syntax; gpgme is 0244 expected to return only rfc2253 compatible strings. */ 0245 static Kleo::DN::Attribute::List parse_dn(const unsigned char *string) 0246 { 0247 if (!string) { 0248 return QList<Kleo::DN::Attribute>(); 0249 } 0250 0251 QList<Kleo::DN::Attribute> result; 0252 while (*string) { 0253 while (*string == ' ') { 0254 string++; 0255 } 0256 if (!*string) { 0257 break; /* ready */ 0258 } 0259 0260 DnPair pair = {nullptr, nullptr}; 0261 string = parse_dn_part(&pair, string); 0262 if (!string) { 0263 goto failure; 0264 } 0265 if (pair.key && pair.value) { 0266 result.push_back(Kleo::DN::Attribute(QString::fromUtf8(pair.key), QString::fromUtf8(pair.value))); 0267 } 0268 free(pair.key); 0269 free(pair.value); 0270 0271 while (*string == ' ') { 0272 string++; 0273 } 0274 if (*string && *string != ',' && *string != ';' && *string != '+') { 0275 goto failure; /* invalid delimiter */ 0276 } 0277 if (*string) { 0278 string++; 0279 } 0280 } 0281 return result; 0282 0283 failure: 0284 return QList<Kleo::DN::Attribute>(); 0285 } 0286 0287 static QList<Kleo::DN::Attribute> parse_dn(const QString &dn) 0288 { 0289 return parse_dn((const unsigned char *)dn.toUtf8().data()); 0290 } 0291 0292 static QString dn_escape(const QString &s) 0293 { 0294 QString result; 0295 for (int i = 0, end = s.length(); i != end; ++i) { 0296 const QChar ch = s[i]; 0297 switch (ch.unicode()) { 0298 case ',': 0299 case '+': 0300 case '"': 0301 case '\\': 0302 case '<': 0303 case '>': 0304 case ';': 0305 result += QLatin1Char('\\'); 0306 // fall through 0307 [[fallthrough]]; 0308 default: 0309 result += ch; 0310 } 0311 } 0312 return result; 0313 } 0314 0315 static QString serialise(const QList<Kleo::DN::Attribute> &dn, const QString &sep) 0316 { 0317 QStringList result; 0318 for (QList<Kleo::DN::Attribute>::const_iterator it = dn.begin(); it != dn.end(); ++it) { 0319 if (!(*it).name().isEmpty() && !(*it).value().isEmpty()) { 0320 result.push_back((*it).name().trimmed() + QLatin1Char('=') + dn_escape((*it).value().trimmed())); 0321 } 0322 } 0323 return result.join(sep); 0324 } 0325 0326 static Kleo::DN::Attribute::List reorder_dn(const Kleo::DN::Attribute::List &dn) 0327 { 0328 const QStringList &attrOrder = Kleo::DN::attributeOrder(); 0329 0330 Kleo::DN::Attribute::List unknownEntries; 0331 Kleo::DN::Attribute::List result; 0332 unknownEntries.reserve(dn.size()); 0333 result.reserve(dn.size()); 0334 0335 // find all unknown entries in their order of appearance 0336 for (Kleo::DN::const_iterator it = dn.begin(); it != dn.end(); ++it) { 0337 if (!attrOrder.contains((*it).name())) { 0338 unknownEntries.push_back(*it); 0339 } 0340 } 0341 0342 // process the known attrs in the desired order 0343 for (QStringList::const_iterator oit = attrOrder.begin(); oit != attrOrder.end(); ++oit) { 0344 if (*oit == QLatin1StringView("_X_")) { 0345 // insert the unknown attrs 0346 std::copy(unknownEntries.begin(), unknownEntries.end(), std::back_inserter(result)); 0347 unknownEntries.clear(); // don't produce dup's 0348 } else { 0349 for (Kleo::DN::const_iterator dnit = dn.begin(); dnit != dn.end(); ++dnit) { 0350 if ((*dnit).name() == *oit) { 0351 result.push_back(*dnit); 0352 } 0353 } 0354 } 0355 } 0356 0357 return result; 0358 } 0359 0360 // 0361 // 0362 // class DN 0363 // 0364 // 0365 0366 Kleo::DN::DN() 0367 { 0368 d = new Private(); 0369 d->ref(); 0370 } 0371 0372 Kleo::DN::DN(const QString &dn) 0373 { 0374 d = new Private(); 0375 d->ref(); 0376 d->attributes = parse_dn(dn); 0377 } 0378 0379 Kleo::DN::DN(const char *utf8DN) 0380 { 0381 d = new Private(); 0382 d->ref(); 0383 if (utf8DN) { 0384 d->attributes = parse_dn((const unsigned char *)utf8DN); 0385 } 0386 } 0387 0388 Kleo::DN::DN(const DN &other) 0389 : d(other.d) 0390 { 0391 if (d) { 0392 d->ref(); 0393 } 0394 } 0395 0396 Kleo::DN::~DN() 0397 { 0398 if (d) { 0399 d->unref(); 0400 } 0401 } 0402 0403 const Kleo::DN &Kleo::DN::operator=(const DN &that) 0404 { 0405 if (this->d == that.d) { 0406 return *this; 0407 } 0408 0409 if (that.d) { 0410 that.d->ref(); 0411 } 0412 if (this->d) { 0413 this->d->unref(); 0414 } 0415 0416 this->d = that.d; 0417 0418 return *this; 0419 } 0420 0421 // static 0422 QStringList Kleo::DN::attributeOrder() 0423 { 0424 return DNAttributeOrderStore::instance()->attributeOrder(); 0425 } 0426 0427 // static 0428 void Kleo::DN::setAttributeOrder(const QStringList &order) 0429 { 0430 DNAttributeOrderStore::instance()->setAttributeOrder(order); 0431 } 0432 0433 // static 0434 QStringList Kleo::DN::defaultAttributeOrder() 0435 { 0436 return defaultOrder; 0437 } 0438 0439 QString Kleo::DN::prettyDN() const 0440 { 0441 if (!d) { 0442 return QString(); 0443 } 0444 if (d->reorderedAttributes.empty()) { 0445 d->reorderedAttributes = reorder_dn(d->attributes); 0446 } 0447 return serialise(d->reorderedAttributes, QStringLiteral(",")); 0448 } 0449 0450 QString Kleo::DN::dn() const 0451 { 0452 return d ? serialise(d->attributes, QStringLiteral(",")) : QString(); 0453 } 0454 0455 QString Kleo::DN::dn(const QString &sep) const 0456 { 0457 return d ? serialise(d->attributes, sep) : QString(); 0458 } 0459 0460 // static 0461 QString Kleo::DN::escape(const QString &value) 0462 { 0463 return dn_escape(value); 0464 } 0465 0466 void Kleo::DN::detach() 0467 { 0468 if (!d) { 0469 d = new Kleo::DN::Private(); 0470 d->ref(); 0471 } else if (d->refCount() > 1) { 0472 Kleo::DN::Private *d_save = d; 0473 d = new Kleo::DN::Private(*d); 0474 d->ref(); 0475 d_save->unref(); 0476 } 0477 } 0478 0479 void Kleo::DN::append(const Attribute &attr) 0480 { 0481 detach(); 0482 d->attributes.push_back(attr); 0483 d->reorderedAttributes.clear(); 0484 } 0485 0486 QString Kleo::DN::operator[](const QString &attr) const 0487 { 0488 if (!d) { 0489 return QString(); 0490 } 0491 const QString attrUpper = attr.toUpper(); 0492 for (QList<Attribute>::const_iterator it = d->attributes.constBegin(); it != d->attributes.constEnd(); ++it) { 0493 if ((*it).name() == attrUpper) { 0494 return (*it).value(); 0495 } 0496 } 0497 return QString(); 0498 } 0499 0500 static QList<Kleo::DN::Attribute> empty; 0501 0502 Kleo::DN::const_iterator Kleo::DN::begin() const 0503 { 0504 return d ? d->attributes.constBegin() : empty.constBegin(); 0505 } 0506 0507 Kleo::DN::const_iterator Kleo::DN::end() const 0508 { 0509 return d ? d->attributes.constEnd() : empty.constEnd(); 0510 } 0511 0512 ///////////////////// 0513 0514 namespace 0515 { 0516 static const QMap<QString, KLazyLocalizedString> attributeNamesAndLabels = { 0517 // clang-format off 0518 {QStringLiteral("CN"), kli18n("Common name") }, 0519 {QStringLiteral("SN"), kli18n("Surname") }, 0520 {QStringLiteral("GN"), kli18n("Given name") }, 0521 {QStringLiteral("L"), kli18n("Location") }, 0522 {QStringLiteral("T"), kli18n("Title") }, 0523 {QStringLiteral("OU"), kli18n("Organizational unit")}, 0524 {QStringLiteral("O"), kli18n("Organization") }, 0525 {QStringLiteral("PC"), kli18n("Postal code") }, 0526 {QStringLiteral("C"), kli18n("Country code") }, 0527 {QStringLiteral("SP"), kli18n("State or province") }, 0528 {QStringLiteral("DC"), kli18n("Domain component") }, 0529 {QStringLiteral("BC"), kli18n("Business category") }, 0530 {QStringLiteral("EMAIL"), kli18n("Email address") }, 0531 {QStringLiteral("MAIL"), kli18n("Mail address") }, 0532 {QStringLiteral("MOBILE"), kli18n("Mobile phone number")}, 0533 {QStringLiteral("TEL"), kli18n("Telephone number") }, 0534 {QStringLiteral("FAX"), kli18n("Fax number") }, 0535 {QStringLiteral("STREET"), kli18n("Street address") }, 0536 {QStringLiteral("UID"), kli18n("Unique ID") }, 0537 // clang-format on 0538 }; 0539 } 0540 0541 // static 0542 QStringList Kleo::DN::attributeNames() 0543 { 0544 return attributeNamesAndLabels.keys(); 0545 } 0546 0547 // static 0548 QString Kleo::DN::attributeNameToLabel(const QString &name) 0549 { 0550 const QString key{name.trimmed().toUpper()}; 0551 if (attributeNames().contains(key)) { 0552 return attributeNamesAndLabels.value(key).toString(); 0553 } 0554 qCWarning(LIBKLEO_LOG) << "Attribute " << key << " doesn't exit. Bug ?"; 0555 return {}; 0556 }