File indexing completed on 2025-03-09 04:54:29

0001 /*
0002    SPDX-FileCopyrightText: 2018-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "dkiminfo.h"
0008 #include "dkimutil.h"
0009 #include "messageviewer_dkimcheckerdebug.h"
0010 
0011 using namespace MessageViewer;
0012 
0013 DKIMInfo::DKIMInfo() = default;
0014 
0015 bool DKIMInfo::parseDKIM(const QString &header)
0016 {
0017     if (header.isEmpty()) {
0018         qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Error: trying to parse empty header";
0019         return false;
0020     }
0021     QString newHeaders = header;
0022     newHeaders.replace(QLatin1StringView("; "), QLatin1StringView(";"));
0023     const QStringList items = newHeaders.split(QLatin1Char(';'), Qt::SkipEmptyParts);
0024     bool foundCanonizations = false;
0025     for (int i = 0; i < items.count(); ++i) {
0026         const QString elem = items.at(i).trimmed();
0027         if (elem.startsWith(QLatin1StringView("v="))) {
0028             mVersion = QStringView(elem).right(elem.length() - 2).toInt();
0029             if (mVersion != 1) {
0030                 qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Version is not correct " << mVersion;
0031             }
0032         } else if (elem.startsWith(QLatin1StringView("a="))) {
0033             // Parse it as "algorithm.signature-algorithm.hash
0034             parseAlgorithm(elem.right(elem.length() - 2));
0035         } else if (elem.startsWith(QLatin1StringView("t="))) {
0036             mSignatureTimeStamp = elem.right(elem.length() - 2).toLong();
0037         } else if (elem.startsWith(QLatin1StringView("c="))) {
0038             // Parse header/body canonicalization (example c=relaxed/simple) only relaxed and simple.
0039             parseCanonicalization(elem.right(elem.length() - 2));
0040             foundCanonizations = true;
0041         } else if (elem.startsWith(QLatin1StringView("bh="))) {
0042             mBodyHash = elem.right(elem.length() - 3).remove(QLatin1Char(' '));
0043         } else if (elem.startsWith(QLatin1StringView("l="))) {
0044             mBodyLengthCount = QStringView(elem).right(elem.length() - 2).toInt();
0045         } else if (elem.startsWith(QLatin1StringView("i="))) {
0046             mAgentOrUserIdentifier = elem.right(elem.length() - 2);
0047         } else if (elem.startsWith(QLatin1StringView("q="))) {
0048             mQuery = elem.right(elem.length() - 2);
0049             if (mQuery != QLatin1StringView("dns/txt")) {
0050                 qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Query is not correct and not supported " << mQuery;
0051             }
0052         } else if (elem.startsWith(QLatin1StringView("d="))) {
0053             mDomain = elem.right(elem.length() - 2).trimmed();
0054         } else if (elem.startsWith(QLatin1StringView("s="))) {
0055             mSelector = elem.right(elem.length() - 2).trimmed();
0056         } else if (elem.startsWith(QLatin1StringView("b="))) {
0057             mSignature = elem.right(elem.length() - 2);
0058         } else if (elem.startsWith(QLatin1StringView("h="))) {
0059             const QString str = MessageViewer::DKIMUtil::cleanString(elem.right(elem.length() - 2));
0060             mListSignedHeader = str.split(QLatin1Char(':'));
0061         } else if (elem.startsWith(QLatin1StringView("x="))) {
0062             mExpireTime = elem.right(elem.length() - 2).toLong();
0063         } else if (elem.startsWith(QLatin1StringView("z="))) {
0064             mCopiedHeaderField = elem.right(elem.length() - 2).split(QLatin1Char(':'));
0065         } else {
0066             qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << " Unknown element type" << elem << " : items : " << items;
0067         }
0068     }
0069     if (!foundCanonizations) { // Default
0070         mHeaderCanonization = Simple;
0071         mBodyCanonization = Simple;
0072     }
0073     if (mVersion == -1) {
0074         mVersion = 1;
0075     }
0076     if (mQuery.isEmpty()) {
0077         mQuery = QStringLiteral("dns/txt");
0078     }
0079     if (mAgentOrUserIdentifier.isEmpty()) {
0080         mAgentOrUserIdentifier = QLatin1Char('@') + mDomain;
0081         mIDomain = mDomain;
0082     } else {
0083         const QStringList lst = mAgentOrUserIdentifier.split(QLatin1Char('@'));
0084         if (lst.count() == 2) {
0085             if (mAgentOrUserIdentifier.isEmpty()) {
0086                 mAgentOrUserIdentifier = QLatin1Char('@') + mDomain;
0087             }
0088             mIDomain = lst.at(1);
0089         }
0090     }
0091     return true;
0092 }
0093 
0094 void DKIMInfo::parseAlgorithm(const QString &str)
0095 {
0096     // currently only "rsa-sha1" or "rsa-sha256"
0097     const QStringList lst = str.split(QLatin1Char('-'));
0098     if (lst.count() != 2) {
0099         qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "algorithm is invalid " << str;
0100         // Error
0101     } else {
0102         mSigningAlgorithm = lst.at(0);
0103         const QString hashStr = lst.at(1);
0104         if (hashStr == QLatin1StringView("sha1")) {
0105             mHashingAlgorithm = HashingAlgorithmType::Sha1;
0106         } else if (hashStr == QLatin1StringView("sha256")) {
0107             mHashingAlgorithm = HashingAlgorithmType::Sha256;
0108         } else {
0109             mHashingAlgorithm = HashingAlgorithmType::Unknown;
0110         }
0111     }
0112 }
0113 
0114 QString DKIMInfo::iDomain() const
0115 {
0116     return mIDomain;
0117 }
0118 
0119 void DKIMInfo::setIDomain(const QString &iDomain)
0120 {
0121     mIDomain = iDomain;
0122 }
0123 
0124 void DKIMInfo::parseCanonicalization(const QString &str)
0125 {
0126     if (!str.isEmpty()) {
0127         const QStringList canonicalization = str.split(QLatin1Char('/'));
0128         // qDebug() << " canonicalization "<< canonicalization;
0129         if (canonicalization.count() >= 1) {
0130             if (canonicalization.at(0) == QLatin1StringView("relaxed")) {
0131                 mHeaderCanonization = DKIMInfo::Relaxed;
0132             } else if (canonicalization.at(0) == QLatin1StringView("simple")) {
0133                 mHeaderCanonization = DKIMInfo::Simple;
0134             } else {
0135                 qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "canonicalization for header unknown " << canonicalization.at(0);
0136                 mHeaderCanonization = DKIMInfo::Unknown;
0137                 return;
0138             }
0139             if (canonicalization.count() == 1) {
0140                 mBodyCanonization = DKIMInfo::Simple;
0141             } else if (canonicalization.count() == 2) {
0142                 if (canonicalization.at(1) == QLatin1StringView("relaxed")) {
0143                     mBodyCanonization = DKIMInfo::Relaxed;
0144                 } else if (canonicalization.at(1) == QLatin1StringView("simple")) {
0145                     mBodyCanonization = DKIMInfo::Simple;
0146                 } else {
0147                     qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "canonicalization for body unknown " << canonicalization.at(1);
0148                     mBodyCanonization = DKIMInfo::Unknown;
0149                     return;
0150                 }
0151             } else {
0152                 qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << " Problem during parsing canonicalization " << str;
0153                 mHeaderCanonization = DKIMInfo::Unknown;
0154                 mBodyCanonization = DKIMInfo::Unknown;
0155             }
0156         }
0157     }
0158 }
0159 
0160 QStringList DKIMInfo::copiedHeaderField() const
0161 {
0162     return mCopiedHeaderField;
0163 }
0164 
0165 void DKIMInfo::setCopiedHeaderField(const QStringList &copiedHeaderField)
0166 {
0167     mCopiedHeaderField = copiedHeaderField;
0168 }
0169 
0170 DKIMInfo::CanonicalizationType DKIMInfo::bodyCanonization() const
0171 {
0172     return mBodyCanonization;
0173 }
0174 
0175 void DKIMInfo::setBodyCanonization(CanonicalizationType bodyCanonization)
0176 {
0177     mBodyCanonization = bodyCanonization;
0178 }
0179 
0180 bool DKIMInfo::operator==(const DKIMInfo &other) const
0181 {
0182     return mVersion == other.version() && mHashingAlgorithm == other.hashingAlgorithm() && mSigningAlgorithm == other.signingAlgorithm()
0183         && mDomain == other.domain() && mSelector == other.selector() && mBodyHash == other.bodyHash() && mSignatureTimeStamp == other.signatureTimeStamp()
0184         && mExpireTime == other.expireTime() && mQuery == other.query() && mSignature == other.signature()
0185         && mAgentOrUserIdentifier == other.agentOrUserIdentifier() && mBodyLengthCount == other.bodyLengthCount()
0186         && mListSignedHeader == other.listSignedHeader() && mHeaderCanonization == other.headerCanonization() && mBodyCanonization == other.bodyCanonization()
0187         && mIDomain == other.iDomain();
0188 }
0189 
0190 DKIMInfo::CanonicalizationType DKIMInfo::headerCanonization() const
0191 {
0192     return mHeaderCanonization;
0193 }
0194 
0195 void DKIMInfo::setHeaderCanonization(CanonicalizationType headerCanonization)
0196 {
0197     mHeaderCanonization = headerCanonization;
0198 }
0199 
0200 int DKIMInfo::version() const
0201 {
0202     return mVersion;
0203 }
0204 
0205 void DKIMInfo::setVersion(int version)
0206 {
0207     mVersion = version;
0208 }
0209 
0210 DKIMInfo::HashingAlgorithmType DKIMInfo::hashingAlgorithm() const
0211 {
0212     return mHashingAlgorithm;
0213 }
0214 
0215 void DKIMInfo::setHashingAlgorithm(DKIMInfo::HashingAlgorithmType hashingAlgorithm)
0216 {
0217     mHashingAlgorithm = hashingAlgorithm;
0218 }
0219 
0220 QString DKIMInfo::domain() const
0221 {
0222     return mDomain;
0223 }
0224 
0225 void DKIMInfo::setDomain(const QString &domain)
0226 {
0227     mDomain = domain;
0228 }
0229 
0230 QString DKIMInfo::selector() const
0231 {
0232     return mSelector;
0233 }
0234 
0235 void DKIMInfo::setSelector(const QString &selector)
0236 {
0237     mSelector = selector;
0238 }
0239 
0240 QString DKIMInfo::bodyHash() const
0241 {
0242     return mBodyHash;
0243 }
0244 
0245 void DKIMInfo::setBodyHash(const QString &bodyHash)
0246 {
0247     mBodyHash = bodyHash;
0248 }
0249 
0250 bool DKIMInfo::isValid() const
0251 {
0252     if (mBodyCanonization == DKIMInfo::Unknown || mHeaderCanonization == DKIMInfo::Unknown) {
0253         return false;
0254     }
0255 
0256     return !mSelector.isEmpty() && !mDomain.isEmpty() && !mBodyHash.isEmpty()
0257         && ((mHashingAlgorithm == HashingAlgorithmType::Sha1) || mHashingAlgorithm == HashingAlgorithmType::Sha256);
0258 }
0259 
0260 QStringList DKIMInfo::listSignedHeader() const
0261 {
0262     return mListSignedHeader;
0263 }
0264 
0265 void DKIMInfo::setListSignedHeader(const QStringList &listSignedHeader)
0266 {
0267     mListSignedHeader = listSignedHeader;
0268 }
0269 
0270 QString DKIMInfo::signingAlgorithm() const
0271 {
0272     return mSigningAlgorithm;
0273 }
0274 
0275 void DKIMInfo::setSigningAlgorithm(const QString &signingAlgorithm)
0276 {
0277     mSigningAlgorithm = signingAlgorithm;
0278 }
0279 
0280 qint64 DKIMInfo::signatureTimeStamp() const
0281 {
0282     return mSignatureTimeStamp;
0283 }
0284 
0285 void DKIMInfo::setSignatureTimeStamp(qint64 signatureTimeStamp)
0286 {
0287     mSignatureTimeStamp = signatureTimeStamp;
0288 }
0289 
0290 QString DKIMInfo::query() const
0291 {
0292     return mQuery;
0293 }
0294 
0295 void DKIMInfo::setQuery(const QString &query)
0296 {
0297     mQuery = query;
0298 }
0299 
0300 qint64 DKIMInfo::expireTime() const
0301 {
0302     return mExpireTime;
0303 }
0304 
0305 void DKIMInfo::setExpireTime(qint64 expireTime)
0306 {
0307     mExpireTime = expireTime;
0308 }
0309 
0310 QString DKIMInfo::signature() const
0311 {
0312     return mSignature;
0313 }
0314 
0315 void DKIMInfo::setSignature(const QString &signature)
0316 {
0317     mSignature = signature;
0318 }
0319 
0320 QString DKIMInfo::agentOrUserIdentifier() const
0321 {
0322     return mAgentOrUserIdentifier;
0323 }
0324 
0325 void DKIMInfo::setAgentOrUserIdentifier(const QString &userAgent)
0326 {
0327     mAgentOrUserIdentifier = userAgent;
0328 }
0329 
0330 int DKIMInfo::bodyLengthCount() const
0331 {
0332     return mBodyLengthCount;
0333 }
0334 
0335 void DKIMInfo::setBodyLengthCount(int bodyLengthCount)
0336 {
0337     mBodyLengthCount = bodyLengthCount;
0338 }
0339 
0340 QDebug operator<<(QDebug d, const DKIMInfo &t)
0341 {
0342     d << "mVersion " << t.version();
0343     d << "mHashingAlgorithm " << t.hashingAlgorithm();
0344     d << "mSigningAlgorithm " << t.signingAlgorithm();
0345     d << "mDomain " << t.domain();
0346     d << "mSelector " << t.selector();
0347     d << "mBodyHash " << t.bodyHash();
0348     d << "mSignatureTimeStamp " << t.signatureTimeStamp();
0349     d << "mExpireTime " << t.expireTime();
0350     d << "mQuery " << t.query();
0351     d << "mSignature " << t.signature();
0352     d << "mAgentOrUserIdentifier " << t.agentOrUserIdentifier();
0353     d << "mBodyLengthCount " << t.bodyLengthCount();
0354     d << "mListSignedHeader " << t.listSignedHeader();
0355     d << "mHeaderCanonization " << t.headerCanonization();
0356     d << "mBodyCanonization " << t.bodyCanonization();
0357     d << "mIdomain " << t.iDomain();
0358     return d;
0359 }
0360 
0361 #include "moc_dkiminfo.cpp"