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 }