diff --git a/messageviewer/src/dkim-verify/autotests/dkiminfotest.cpp b/messageviewer/src/dkim-verify/autotests/dkiminfotest.cpp index 9a7c24b5..1577381f 100644 --- a/messageviewer/src/dkim-verify/autotests/dkiminfotest.cpp +++ b/messageviewer/src/dkim-verify/autotests/dkiminfotest.cpp @@ -1,143 +1,143 @@ /* Copyright (C) 2018-2019 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dkiminfotest.h" #include "dkim-verify/dkiminfo.h" #include #include QTEST_GUILESS_MAIN(DKIMInfoTest) DKIMInfoTest::DKIMInfoTest(QObject *parent) : QObject(parent) { } void DKIMInfoTest::shouldHaveDefaultValue() { MessageViewer::DKIMInfo info; QCOMPARE(info.version(), -1); - QVERIFY(info.hashingAlgorithm().isEmpty()); + QCOMPARE(info.hashingAlgorithm(), MessageViewer::DKIMInfo::HashingAlgorithmType::Any); QVERIFY(info.signingAlgorithm().isEmpty()); QVERIFY(info.domain().isEmpty()); QVERIFY(info.selector().isEmpty()); QVERIFY(info.bodyHash().isEmpty()); QVERIFY(info.listSignedHeader().isEmpty()); QVERIFY(info.query().isEmpty()); QCOMPARE(info.signatureTimeStamp(), -1); QCOMPARE(info.expireTime(), -1); QVERIFY(info.signature().isEmpty()); QVERIFY(info.agentOrUserIdentifier().isEmpty()); QCOMPARE(info.bodyLengthCount(), -1); QCOMPARE(info.headerCanonization(), MessageViewer::DKIMInfo::Unknown); QCOMPARE(info.bodyCanonization(), MessageViewer::DKIMInfo::Unknown); QVERIFY(info.copiedHeaderField().isEmpty()); QVERIFY(info.iDomain().isEmpty()); } void DKIMInfoTest::shouldTestExtractDkimInfo_data() { QTest::addColumn("dkimstr"); QTest::addColumn("dkiminforesult"); QTest::addColumn("isValid"); QTest::addRow("empty") << QString() << MessageViewer::DKIMInfo() << false; QString val = QStringLiteral("a=rsa-sha1; q=dns; d=example.com; i=user@eng.example.com; s=jun2005.eng; c=relaxed/simple; t=1117574938; x=1118006938; h=from:to:subject:date; b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZVoG4ZHRNiYzR"); MessageViewer::DKIMInfo info1; info1.setVersion(1); info1.setQuery(QStringLiteral("dns")); info1.setDomain(QStringLiteral("example.com")); info1.setSigningAlgorithm(QStringLiteral("rsa")); - info1.setHashingAlgorithm(QStringLiteral("sha1")); + info1.setHashingAlgorithm(MessageViewer::DKIMInfo::HashingAlgorithmType::Sha1); info1.setBodyCanonization(MessageViewer::DKIMInfo::Simple); info1.setHeaderCanonization(MessageViewer::DKIMInfo::Relaxed); info1.setSignatureTimeStamp(1117574938); info1.setExpireTime(1118006938); info1.setSelector(QStringLiteral("jun2005.eng")); info1.setAgentOrUserIdentifier(QStringLiteral("user")); info1.setIDomain(QStringLiteral("eng.example.com")); info1.setSignature(QStringLiteral("dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZVoG4ZHRNiYzR")); info1.setListSignedHeader(QStringList({QStringLiteral("from"), QStringLiteral("to"), QStringLiteral("subject"), QStringLiteral("date")})); QTest::addRow("test1") << val << info1 << true; val = QStringLiteral("v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; h=message-id:content-type:content-type:user-agent:content-disposition:content-transfer-encoding:mime-version:reply-to:references:in-reply-to:subject:subject" ":list-unsubscribe:list-id:from:from:date:date; s=dkim; t=1569945303; x=1570809304; bh=/TC3U+LlxLH3YGxhC7qIHK8PzGl6Zx/8P6OGhDWrcWs=; b=w4GIjJb/+yEZvzvlw9yIPiuk/eKxAGSKL4WNmgC4D3V9fNyuVOV" "IH06PzqCuU/NwLas3SdAvd3VbTCObAb38KXeXkO7meeyCoR+kDsFzHpSAUg7+IRkeDR+RmarFjXwZAtoX3OMsB8euEprhS9fgGupxCWxwu6VGKJgt3Yu3/cI="); MessageViewer::DKIMInfo info2; info2.setVersion(1); info2.setQuery(QStringLiteral("dns/txt")); info2.setDomain(QStringLiteral("example.com")); info2.setIDomain(QStringLiteral("example.com")); info2.setAgentOrUserIdentifier(QStringLiteral("@example.com")); info2.setSigningAlgorithm(QStringLiteral("rsa")); - info2.setHashingAlgorithm(QStringLiteral("sha256")); + info2.setHashingAlgorithm(MessageViewer::DKIMInfo::HashingAlgorithmType::Sha256); info2.setBodyCanonization(MessageViewer::DKIMInfo::Relaxed); info2.setHeaderCanonization(MessageViewer::DKIMInfo::Relaxed); info2.setSignatureTimeStamp(1569945303); info2.setExpireTime(1570809304); info2.setSelector(QStringLiteral("dkim")); info2.setSignature(QStringLiteral("w4GIjJb/+yEZvzvlw9yIPiuk/eKxAGSKL4WNmgC4D3V9fNyuVOVIH06PzqCuU/NwLas3SdAvd3VbTCObAb38KXeXkO7meeyCoR+kDsFzHpSAUg7+IRkeDR+RmarFjXwZAtoX3OMsB8euEprhS9fgGupxCWxwu6VGKJgt3Yu3/cI=")); info2.setBodyHash(QStringLiteral("/TC3U+LlxLH3YGxhC7qIHK8PzGl6Zx/8P6OGhDWrcWs=")); info2.setListSignedHeader(QStringList({QStringLiteral("message-id"), QStringLiteral("content-type"), QStringLiteral("content-type"), QStringLiteral("user-agent"), QStringLiteral("content-disposition"), QStringLiteral("content-transfer-encoding"), QStringLiteral("mime-version"), QStringLiteral("reply-to"), QStringLiteral("references"), QStringLiteral("in-reply-to"), QStringLiteral("subject"), QStringLiteral("subject"), QStringLiteral("list-unsubscribe"), QStringLiteral("list-id"), QStringLiteral("from"), QStringLiteral("from"), QStringLiteral("date"), QStringLiteral("date")})); QTest::addRow("test2") << val << info2 << true; val = QStringLiteral( "v=1; a=rsa-sha1; c=relaxed; d=abonnement.radins.com; h=message-id:list-unsubscribe:from:to:reply-to:content-type:subject:content-transfer-encoding:mime-version:date; s=selector1; bh=vyAg5eFfq019WlDt9csu4bJMC54=; b=ABKgPqPe/MOGdgR2TJuiVNTLugsL8q/+ky/JxOxwZxnsPbtFnyJ+Y7Gk8bfcBL9myKPNqe7bU6Uy4IiNptn+v34rhVApm6ccoc44UXe/2A5D+6CPJHjFyf/ggjgF/BtQGYoMeQwj2+F4+QRxHSPldAcWqLCwlcRN25nPgiSAvWg="); MessageViewer::DKIMInfo info3; info3.setVersion(1); info3.setQuery(QStringLiteral("dns/txt")); info3.setDomain(QStringLiteral("abonnement.radins.com")); info3.setIDomain(QStringLiteral("abonnement.radins.com")); info3.setAgentOrUserIdentifier(QStringLiteral("@abonnement.radins.com")); info3.setSigningAlgorithm(QStringLiteral("rsa")); - info3.setHashingAlgorithm(QStringLiteral("sha1")); + info3.setHashingAlgorithm(MessageViewer::DKIMInfo::HashingAlgorithmType::Sha1); info3.setBodyCanonization(MessageViewer::DKIMInfo::Simple); info3.setHeaderCanonization(MessageViewer::DKIMInfo::Relaxed); info3.setSelector(QStringLiteral("selector1")); info3.setSignature(QStringLiteral("ABKgPqPe/MOGdgR2TJuiVNTLugsL8q/+ky/JxOxwZxnsPbtFnyJ+Y7Gk8bfcBL9myKPNqe7bU6Uy4IiNptn+v34rhVApm6ccoc44UXe/2A5D+6CPJHjFyf/ggjgF/BtQGYoMeQwj2+F4+QRxHSPldAcWqLCwlcRN25nPgiSAvWg=")); info3.setBodyHash(QStringLiteral("vyAg5eFfq019WlDt9csu4bJMC54=")); info3.setListSignedHeader(QStringList({QStringLiteral("message-id"), QStringLiteral("list-unsubscribe"), QStringLiteral("from"), QStringLiteral("to"), QStringLiteral("reply-to"), QStringLiteral("content-type"), QStringLiteral("subject"), QStringLiteral("content-transfer-encoding"), QStringLiteral("mime-version"), QStringLiteral("date")})); QTest::addRow("test3") << val << info3 << true; } void DKIMInfoTest::shouldTestExtractDkimInfo() { QFETCH(QString, dkimstr); QFETCH(MessageViewer::DKIMInfo, dkiminforesult); QFETCH(bool, isValid); MessageViewer::DKIMInfo info; QCOMPARE(info.parseDKIM(dkimstr), isValid); if (isValid) { const bool isEqual = (info == dkiminforesult); if (!isEqual) { qDebug() << " info" << info; qDebug() << " dkiminforesult" << dkiminforesult; } QVERIFY(isEqual); } } diff --git a/messageviewer/src/dkim-verify/dkimchecksignaturejob.cpp b/messageviewer/src/dkim-verify/dkimchecksignaturejob.cpp index e41eb0ee..37f5d332 100644 --- a/messageviewer/src/dkim-verify/dkimchecksignaturejob.cpp +++ b/messageviewer/src/dkim-verify/dkimchecksignaturejob.cpp @@ -1,652 +1,663 @@ /* Copyright (C) 2018-2019 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dkimchecksignaturejob.h" #include "dkimdownloadkeyjob.h" #include "dkimmanagerkey.h" #include "dkiminfo.h" #include "dkimutil.h" #include "dkimkeyrecord.h" #include "messageviewer_dkimcheckerdebug.h" #include "dkimheaderparser.h" #include #include #include #include //see https://tools.ietf.org/html/rfc6376 using namespace MessageViewer; DKIMCheckSignatureJob::DKIMCheckSignatureJob(QObject *parent) : QObject(parent) { } DKIMCheckSignatureJob::~DKIMCheckSignatureJob() { } MessageViewer::DKIMCheckSignatureJob::CheckSignatureResult DKIMCheckSignatureJob::createCheckResult() { MessageViewer::DKIMCheckSignatureJob::CheckSignatureResult result; result.error = mError; result.warning = mWarning; result.status = mStatus; result.item = mMessageItem; return result; } QString DKIMCheckSignatureJob::bodyCanonizationResult() const { return mBodyCanonizationResult; } QString DKIMCheckSignatureJob::headerCanonizationResult() const { return mHeaderCanonizationResult; } void DKIMCheckSignatureJob::start() { if (mMessageItem.isValid() && !mMessage) { if (mMessageItem.hasPayload()) { mMessage = mMessageItem.payload(); } } else if (mMessage) { //Nothing } else { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Item has not a message"; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } if (auto hrd = mMessage->headerByType("DKIM-Signature")) { mDkimValue = hrd->asUnicodeString(); } if (mDkimValue.isEmpty()) { mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::EmailNotSigned; Q_EMIT result(createCheckResult()); deleteLater(); return; } if (!mDkimInfo.parseDKIM(mDkimValue)) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Impossible to parse header" << mDkimValue; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } const MessageViewer::DKIMCheckSignatureJob::DKIMStatus status = checkSignature(mDkimInfo); if (status != MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Valid) { mStatus = status; Q_EMIT result(createCheckResult()); deleteLater(); return; } //ComputeBodyHash now. switch (mDkimInfo.bodyCanonization()) { case MessageViewer::DKIMInfo::CanonicalizationType::Unknown: mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::InvalidBodyCanonicalization; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; case MessageViewer::DKIMInfo::CanonicalizationType::Simple: mBodyCanonizationResult = bodyCanonizationSimple(); break; case MessageViewer::DKIMInfo::CanonicalizationType::Relaxed: mBodyCanonizationResult = bodyCanonizationRelaxed(); break; } //qDebug() << " bodyCanonizationResult "<< mBodyCanonizationResult << " algorithm " << mDkimInfo.hashingAlgorithm() << mDkimInfo.bodyHash(); if (mDkimInfo.bodyLengthCount() != -1) { //Verify it. if (mDkimInfo.bodyLengthCount() < mBodyCanonizationResult.length()) { // length tag exceeds body size mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::SignatureTooLarge; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } else if (mDkimInfo.bodyLengthCount() > mBodyCanonizationResult.length()) { mWarning = MessageViewer::DKIMCheckSignatureJob::DKIMWarning::SignatureTooSmall; } // truncated body to the length specified in the "l=" tag mBodyCanonizationResult = mBodyCanonizationResult.left(mDkimInfo.bodyLengthCount()); } if (mBodyCanonizationResult.startsWith(QLatin1Literal("\r\n"))) { //Remove it from start mBodyCanonizationResult = mBodyCanonizationResult.right(mBodyCanonizationResult.length() -2 ); } #if 0 QFile caFile(QStringLiteral("/tmp/bodycanon-kmail.txt")); caFile.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream outStream(&caFile); outStream << mBodyCanonizationResult; caFile.close(); #endif QByteArray resultHash; - if (mDkimInfo.hashingAlgorithm() == QLatin1String("sha1")) { + switch (mDkimInfo.hashingAlgorithm()) { + case DKIMInfo::HashingAlgorithmType::Sha1: resultHash = MessageViewer::DKIMUtil::generateHash(mBodyCanonizationResult.toLatin1(), QCryptographicHash::Sha1); - } else if (mDkimInfo.hashingAlgorithm() == QLatin1String("sha256")) { + break; + case DKIMInfo::HashingAlgorithmType::Sha256: resultHash = MessageViewer::DKIMUtil::generateHash(mBodyCanonizationResult.toLatin1(), QCryptographicHash::Sha256); - } else { + break; + case DKIMInfo::HashingAlgorithmType::Any: + case DKIMInfo::HashingAlgorithmType::Unknown: mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::InsupportedHashAlgorithm; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } // compare body hash qDebug() << "resultHash " << resultHash << "mDkimInfo.bodyHash()" << mDkimInfo.bodyHash(); if (resultHash != mDkimInfo.bodyHash().toLatin1()) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << " Corrupted body hash"; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::CorruptedBodyHash; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } //Compute Hash Header switch (mDkimInfo.headerCanonization()) { case MessageViewer::DKIMInfo::CanonicalizationType::Unknown: mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::InvalidHeaderCanonicalization; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; case MessageViewer::DKIMInfo::CanonicalizationType::Simple: mHeaderCanonizationResult = headerCanonizationSimple(); break; case MessageViewer::DKIMInfo::CanonicalizationType::Relaxed: mHeaderCanonizationResult = headerCanonizationRelaxed(); break; } // In hash step 2, the Signer/Verifier MUST pass the following to the // hash algorithm in the indicated order. // 1. The header fields specified by the "h=" tag, in the order // specified in that tag, and canonicalized using the header // canonicalization algorithm specified in the "c=" tag. Each // header field MUST be terminated with a single CRLF. // 2. The DKIM-Signature header field that exists (verifying) or will // be inserted (signing) in the message, with the value of the "b=" // tag (including all surrounding whitespace) deleted (i.e., treated // as the empty string), canonicalized using the header // canonicalization algorithm specified in the "c=" tag, and without // a trailing CRLF. // add DKIM-Signature header to the hash input // with the value of the "b=" tag (including all surrounding whitespace) deleted //Add dkim-signature as lowercase QString dkimValue = mDkimValue; dkimValue = dkimValue.left(dkimValue.indexOf(QLatin1String("b=")) + 2); switch (mDkimInfo.headerCanonization()) { case MessageViewer::DKIMInfo::CanonicalizationType::Unknown: return; case MessageViewer::DKIMInfo::CanonicalizationType::Simple: mHeaderCanonizationResult += QLatin1String("\r\n") + MessageViewer::DKIMUtil::headerCanonizationSimple(QLatin1String("dkim-signature"), dkimValue); break; case MessageViewer::DKIMInfo::CanonicalizationType::Relaxed: mHeaderCanonizationResult += QLatin1String("\r\n") + MessageViewer::DKIMUtil::headerCanonizationRelaxed(QLatin1String("dkim-signature"), dkimValue); break; } if (mSaveKey) { const QString keyValue = MessageViewer::DKIMManagerKey::self()->keyValue(mDkimInfo.selector(), mDkimInfo.domain()); if (keyValue.isEmpty()) { downloadKey(mDkimInfo); } else { parseDKIMKeyRecord(keyValue, mDkimInfo.domain(), mDkimInfo.selector(), false); } } else { downloadKey(mDkimInfo); } } QString DKIMCheckSignatureJob::bodyCanonizationSimple() const { /* * canonicalize the body using the simple algorithm * specified in Section 3.4.3 of RFC 6376 */ // The "simple" body canonicalization algorithm ignores all empty lines // at the end of the message body. An empty line is a line of zero // length after removal of the line terminator. If there is no body or // no trailing CRLF on the message body, a CRLF is added. It makes no // other changes to the message body. In more formal terms, the // "simple" body canonicalization algorithm converts "*CRLF" at the end // of the body to a single "CRLF". // Note that a completely empty or missing body is canonicalized as a // single "CRLF"; that is, the canonicalized length will be 2 octets. return MessageViewer::DKIMUtil::bodyCanonizationSimple(QString::fromLatin1(mMessage->encodedBody())); } QString DKIMCheckSignatureJob::bodyCanonizationRelaxed() const { /* * canonicalize the body using the relaxed algorithm * specified in Section 3.4.4 of RFC 6376 */ /* a. Reduce whitespace: * Ignore all whitespace at the end of lines. Implementations MUST NOT remove the CRLF at the end of the line. * Reduce all sequences of WSP within a line to a single SP character. b. Ignore all empty lines at the end of the message body. "Empty line" is defined in Section 3.4.3. If the body is non-empty but does not end with a CRLF, a CRLF is added. (For email, this is only possible when using extensions to SMTP or non-SMTP transport mechanisms.) */ const QString returnValue = MessageViewer::DKIMUtil::bodyCanonizationRelaxed(QString::fromUtf8(mMessage->encodedBody())); return returnValue; } QString DKIMCheckSignatureJob::headerCanonizationSimple() const { QString headers; DKIMHeaderParser parser; parser.setHead(mMessage->head()); parser.parse(); for (const QString &header : mDkimInfo.listSignedHeader()) { const QString str = parser.headerType(header.toLower()); if (!str.isEmpty()) { if (!headers.isEmpty()) { headers += QLatin1String("\r\n"); } headers += MessageViewer::DKIMUtil::headerCanonizationSimple(header, str); } } return headers; } QString DKIMCheckSignatureJob::headerCanonizationRelaxed() const { // The "relaxed" header canonicalization algorithm MUST apply the // following steps in order: // o Convert all header field names (not the header field values) to // lowercase. For example, convert "SUBJect: AbC" to "subject: AbC". // o Unfold all header field continuation lines as described in // [RFC5322]; in particular, lines with terminators embedded in // continued header field values (that is, CRLF sequences followed by // WSP) MUST be interpreted without the CRLF. Implementations MUST // NOT remove the CRLF at the end of the header field value. // o Convert all sequences of one or more WSP characters to a single SP // character. WSP characters here include those before and after a // line folding boundary. // o Delete all WSP characters at the end of each unfolded header field // value. // o Delete any WSP characters remaining before and after the colon // separating the header field name from the header field value. The // colon separator MUST be retained. QString headers; DKIMHeaderParser parser; parser.setHead(mMessage->head()); parser.parse(); for (const QString &header : mDkimInfo.listSignedHeader()) { #if 1 const QString str = parser.headerType(header.toLower()); // qDebug() << " str " << str << " header search " << header; // auto hrd = mMessage->headerByType(header.toLatin1().constData()); // if (hrd) { // qDebug() << " hrd " << hrd->asUnicodeString(); // } if (!str.isEmpty()) { if (!headers.isEmpty()) { headers += QLatin1String("\r\n"); } headers += MessageViewer::DKIMUtil::headerCanonizationRelaxed(header, str); } #else qDebug() << " header" << header; if (headerAlreadyAdded.contains(header)) { continue; } if (auto hrd = mMessage->headerByType(header.toLatin1().constData())) { QString str; const QByteArray Allheaders = mMessage->head(); //qDebug() << " all headers " << Allheaders; if (header == QLatin1String("date")) { int index = Allheaders.indexOf("Date:"); if (index == -1) { index = Allheaders.indexOf("date:"); if (index == -1) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Impossible to find \'date:\'"; continue; } } index += 5; const int end = Allheaders.indexOf("\n", index); if (end != -1) { str = QString::fromLatin1(Allheaders.mid(index, (end - index))); if (str.startsWith(QLatin1Char(' '))) { str = str.right(str.length() - 1); } } } else if (header == QLatin1String("from:")) { int index = Allheaders.indexOf("from:"); if (index == -1) { index = Allheaders.indexOf("From:"); if (index == -1) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Impossible to find \'from:\'"; continue; } } index += 5; const int end = Allheaders.indexOf("\n", index); if (end != -1) { str = QString::fromLatin1(Allheaders.mid(index, (end - index))); if (str.startsWith(QLatin1Char(' '))) { str = str.right(str.length() - 1); } } qDebug() <<" FROM !!!!!!!!!!!!!!!!!!!" << str; } else { str = hrd->asUnicodeString(); } headerAlreadyAdded << header; if (!headers.isEmpty()) { headers += QLatin1String("\r\n"); } headers += MessageViewer::DKIMUtil::headerCanonizationRelaxed(header, str); } #endif } return headers; } void DKIMCheckSignatureJob::downloadKey(const DKIMInfo &info) { DKIMDownloadKeyJob *job = new DKIMDownloadKeyJob(this); job->setDomainName(info.domain()); job->setSelectorName(info.selector()); connect(job, &DKIMDownloadKeyJob::error, this, [this](const QString &errorString) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Impossible to start downloadkey: error returned: " << errorString; deleteLater(); }); connect(job, &DKIMDownloadKeyJob::success, this, &DKIMCheckSignatureJob::slotDownloadKeyDone); if (!job->start()) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Impossible to start downloadkey"; deleteLater(); } } void DKIMCheckSignatureJob::slotDownloadKeyDone(const QList &lst, const QString &domain, const QString &selector) { QByteArray ba; if (lst.count() != 1) { for (const QByteArray &b : lst) { ba += b; } qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Key result has more that 1 element" << lst; } else { ba = lst.at(0); } parseDKIMKeyRecord(QString::fromLocal8Bit(ba), domain, selector, true); } void DKIMCheckSignatureJob::parseDKIMKeyRecord(const QString &str, const QString &domain, const QString &selector, bool storeKeyValue) { //qDebug() << "void DKIMCheckSignatureJob::parseDKIMKeyRecord(const QString &str, const QString &domain, const QString &selector, bool storeKeyValue) " << str; if (!mDkimKeyRecord.parseKey(str)) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Impossible to parse key record " << str; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } if (mDkimKeyRecord.keyType() != QLatin1String("rsa")) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "mDkimKeyRecord key type is unknown " << mDkimKeyRecord.keyType() << " str " << str; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } // if s flag is set in DKIM key record // AUID must be from the same domain as SDID (and not a subdomain) if (mDkimKeyRecord.flags().contains(QLatin1String("s"))) { // s Any DKIM-Signature header fields using the "i=" tag MUST have // the same domain value on the right-hand side of the "@" in the // "i=" tag and the value of the "d=" tag. That is, the "i=" // domain MUST NOT be a subdomain of "d=". Use of this flag is // RECOMMENDED unless subdomaining is required. if (mDkimInfo.iDomain() != mDkimInfo.domain()) { mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::DomainI; Q_EMIT result(createCheckResult()); deleteLater(); return; } } // check that the testing flag is not set if (mDkimKeyRecord.flags().contains(QLatin1String("y"))) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Testing mode!"; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::TestKeyMode; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } if (mDkimKeyRecord.publicKey().isEmpty()) { // empty value means that this public key has been revoked qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "mDkimKeyRecord public key is empty. It was revoked "; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::PublicKeyWasRevoked; mStatus = MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; Q_EMIT result(createCheckResult()); deleteLater(); return; } if (storeKeyValue) { Q_EMIT storeKey(str, domain, selector); } verifyRSASignature(); } void DKIMCheckSignatureJob::verifyRSASignature() { QCA::ConvertResult conversionResult; //qDebug() << "mDkimKeyRecord.publicKey() " < currentDate) { mWarning = DKIMCheckSignatureJob::DKIMWarning::SignatureCreatedInFuture; } if (info.signature().isEmpty()) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Signature doesn't exist"; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::MissingSignature; return MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; } if (!info.listSignedHeader().contains(QLatin1String("from"), Qt::CaseInsensitive)) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "From is not include in headers list"; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::MissingFrom; return MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; } if (info.domain().isEmpty()) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Domain is not defined."; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::DomainNotExist; return MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; } if (info.query() != QLatin1String("dns/txt")) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Query is incorrect: " << info.query(); mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::InvalidQueryMethod; return MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; } - if (info.hashingAlgorithm().isEmpty()) { + if ((info.hashingAlgorithm() == MessageViewer::DKIMInfo::HashingAlgorithmType::Any) || (info.hashingAlgorithm() == MessageViewer::DKIMInfo::HashingAlgorithmType::Unknown)) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "body header algorithm is empty"; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::InvalidBodyHashAlgorithm; return MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; } if (info.signingAlgorithm().isEmpty()) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "signature algorithm is empty"; mError = MessageViewer::DKIMCheckSignatureJob::DKIMError::InvalidSignAlgorithm; return MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Invalid; } - if (info.hashingAlgorithm() == QLatin1String("sha1")) { + if (info.hashingAlgorithm() == DKIMInfo::HashingAlgorithmType::Sha1) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "hash algorithm is not secure sha1"; mWarning = MessageViewer::DKIMCheckSignatureJob::DKIMWarning::HashAlgorithmUnsafe; } //Add more test //TODO check if info is valid return MessageViewer::DKIMCheckSignatureJob::DKIMStatus::Valid; } DKIMCheckSignatureJob::DKIMError DKIMCheckSignatureJob::error() const { return mError; } DKIMCheckSignatureJob::DKIMStatus DKIMCheckSignatureJob::status() const { return mStatus; } void DKIMCheckSignatureJob::setStatus(const DKIMCheckSignatureJob::DKIMStatus &status) { mStatus = status; } QString DKIMCheckSignatureJob::dkimValue() const { return mDkimValue; } diff --git a/messageviewer/src/dkim-verify/dkiminfo.cpp b/messageviewer/src/dkim-verify/dkiminfo.cpp index c1e2091b..cff6c817 100644 --- a/messageviewer/src/dkim-verify/dkiminfo.cpp +++ b/messageviewer/src/dkim-verify/dkiminfo.cpp @@ -1,376 +1,384 @@ /* Copyright (C) 2018-2019 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dkiminfo.h" #include "dkimutil.h" #include "messageviewer_dkimcheckerdebug.h" #include #include #include "messageviewer_debug.h" using namespace MessageViewer; DKIMInfo::DKIMInfo() { } bool DKIMInfo::parseDKIM(const QString &header) { if (header.isEmpty()) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Error: trying to parse empty header"; return false; } const QStringList items = header.split(QLatin1String("; ")); bool foundCanonizations = false; for (int i = 0; i < items.count(); ++i) { const QString elem = items.at(i).trimmed(); if (elem.startsWith(QLatin1String("v="))) { mVersion = elem.right(elem.length() - 2).toInt(); if (mVersion != 1) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Version is not correct " << mVersion; } } else if (elem.startsWith(QLatin1String("a="))) { //Parse it as "algorithm.signature-algorithm.hash parseAlgorithm(elem.right(elem.length() - 2)); } else if (elem.startsWith(QLatin1String("t="))) { mSignatureTimeStamp = elem.right(elem.length() - 2).toLong(); } else if (elem.startsWith(QLatin1String("c="))) { //Parse header/body canonicalization (example c=relaxed/simple) only relaxed and simple. parseCanonicalization(elem.right(elem.length() - 2)); foundCanonizations = true; } else if (elem.startsWith(QLatin1String("bh="))) { mBodyHash = elem.right(elem.length() - 3).replace(QStringLiteral(" "), QString()); } else if (elem.startsWith(QLatin1String("l="))) { mBodyLengthCount = elem.right(elem.length() - 2).toInt(); } else if (elem.startsWith(QLatin1String("i="))) { mAgentOrUserIdentifier = elem.right(elem.length() - 2); } else if (elem.startsWith(QLatin1String("q="))) { mQuery = elem.right(elem.length() - 2); if (mQuery != QLatin1String("dns/txt")) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "Query is not correct and not supported " << mQuery; } } else if (elem.startsWith(QLatin1String("d="))) { mDomain = elem.right(elem.length() - 2); } else if (elem.startsWith(QLatin1String("s="))) { mSelector = elem.right(elem.length() - 2); } else if (elem.startsWith(QLatin1String("b="))) { mSignature = elem.right(elem.length() - 2); } else if (elem.startsWith(QLatin1String("h="))) { const QString str = MessageViewer::DKIMUtil::cleanString(elem.right(elem.length() - 2)); mListSignedHeader = str.split(QLatin1Char(':')); } else if (elem.startsWith(QLatin1String("x="))) { mExpireTime = elem.right(elem.length() - 2).toLong(); } else if (elem.startsWith(QLatin1String("z="))) { mCopiedHeaderField = elem.right(elem.length() - 2).split(QLatin1Char(':')); } else { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << " Unknown element type" << elem; } } if (!foundCanonizations) { //Default mHeaderCanonization = Simple; mBodyCanonization = Simple; } if (mVersion == -1) { mVersion = 1; } if (mQuery.isEmpty()) { mQuery = QLatin1String("dns/txt"); } if (mAgentOrUserIdentifier.isEmpty()) { mAgentOrUserIdentifier = QLatin1Char('@') + mDomain; mIDomain = mDomain; } else { const QStringList lst = mAgentOrUserIdentifier.split(QLatin1Char('@')); if (lst.count() == 2) { mAgentOrUserIdentifier = lst.at(0); mIDomain = lst.at(1); } } return true; } void DKIMInfo::parseAlgorithm(const QString &str) { // currently only "rsa-sha1" or "rsa-sha256" //FIXME const QStringList lst = str.split(QLatin1Char('-')); if (lst.count() != 2) { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "algorithm is invalid " << str; //Error } else { mSigningAlgorithm = lst.at(0); - mHashingAlgorithm = lst.at(1); + const QString hashStr = lst.at(1); + if (hashStr == QLatin1String("sha1")) { + mHashingAlgorithm = HashingAlgorithmType::Sha1; + } else if (hashStr == QLatin1String("sha256")) { + mHashingAlgorithm = HashingAlgorithmType::Sha256; + } else { + mHashingAlgorithm = HashingAlgorithmType::Unknown; + } } } QString DKIMInfo::iDomain() const { return mIDomain; } void DKIMInfo::setIDomain(const QString &iDomain) { mIDomain = iDomain; } void DKIMInfo::parseCanonicalization(const QString &str) { if (!str.isEmpty()) { const QStringList canonicalizations = str.split(QLatin1Char('/')); //qDebug() << " canonicalizations "<< canonicalizations; if (canonicalizations.count() >= 1) { if (canonicalizations.at(0) == QLatin1String("relaxed")) { mHeaderCanonization = DKIMInfo::Relaxed; } else if (canonicalizations.at(0) == QLatin1String("simple")) { mHeaderCanonization = DKIMInfo::Simple; } else { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "canonicalizations for header unknown " << canonicalizations.at(0); mHeaderCanonization = DKIMInfo::Unknown; return; } if (canonicalizations.count() == 1) { mBodyCanonization = DKIMInfo::Simple; } else if (canonicalizations.count() == 2) { if (canonicalizations.at(1) == QLatin1String("relaxed")) { mBodyCanonization = DKIMInfo::Relaxed; } else if (canonicalizations.at(1) == QLatin1String("simple")) { mBodyCanonization = DKIMInfo::Simple; } else { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << "canonicalizations for body unknown " << canonicalizations.at(1); mBodyCanonization = DKIMInfo::Unknown; return; } } else { qCWarning(MESSAGEVIEWER_DKIMCHECKER_LOG) << " Problem during parsing canonicalizations " << str; mHeaderCanonization = DKIMInfo::Unknown; mBodyCanonization = DKIMInfo::Unknown; } } } } QStringList DKIMInfo::copiedHeaderField() const { return mCopiedHeaderField; } void DKIMInfo::setCopiedHeaderField(const QStringList &copiedHeaderField) { mCopiedHeaderField = copiedHeaderField; } DKIMInfo::CanonicalizationType DKIMInfo::bodyCanonization() const { return mBodyCanonization; } void DKIMInfo::setBodyCanonization(const CanonicalizationType &bodyCanonization) { mBodyCanonization = bodyCanonization; } bool DKIMInfo::operator==(const DKIMInfo &other) const { return mVersion == other.version() && mHashingAlgorithm == other.hashingAlgorithm() && mSigningAlgorithm == other.signingAlgorithm() && mDomain == other.domain() && mSelector == other.selector() && mBodyHash == other.bodyHash() && mSignatureTimeStamp == other.signatureTimeStamp() && mExpireTime == other.expireTime() && mQuery == other.query() && mSignature == other.signature() && mAgentOrUserIdentifier == other.agentOrUserIdentifier() && mBodyLengthCount == other.bodyLengthCount() && mListSignedHeader == other.listSignedHeader() && mHeaderCanonization == other.headerCanonization() && mBodyCanonization == other.bodyCanonization() && mIDomain == other.iDomain(); } DKIMInfo::CanonicalizationType DKIMInfo::headerCanonization() const { return mHeaderCanonization; } void DKIMInfo::setHeaderCanonization(const CanonicalizationType &headerCanonization) { mHeaderCanonization = headerCanonization; } int DKIMInfo::version() const { return mVersion; } void DKIMInfo::setVersion(int version) { mVersion = version; } -QString DKIMInfo::hashingAlgorithm() const +DKIMInfo::HashingAlgorithmType DKIMInfo::hashingAlgorithm() const { return mHashingAlgorithm; } -void DKIMInfo::setHashingAlgorithm(const QString &hashingAlgorithm) +void DKIMInfo::setHashingAlgorithm( DKIMInfo::HashingAlgorithmType hashingAlgorithm) { mHashingAlgorithm = hashingAlgorithm; } QString DKIMInfo::domain() const { return mDomain; } void DKIMInfo::setDomain(const QString &domain) { mDomain = domain; } QString DKIMInfo::selector() const { return mSelector; } void DKIMInfo::setSelector(const QString &selector) { mSelector = selector; } QString DKIMInfo::bodyHash() const { return mBodyHash; } void DKIMInfo::setBodyHash(const QString &bodyHash) { mBodyHash = bodyHash; } bool DKIMInfo::isValid() const { if (mBodyCanonization == DKIMInfo::Unknown || mHeaderCanonization == DKIMInfo::Unknown) { return false; } - return !mSelector.isEmpty() && !mDomain.isEmpty() && !mBodyHash.isEmpty() && !mHashingAlgorithm.isEmpty(); + return !mSelector.isEmpty() && !mDomain.isEmpty() && !mBodyHash.isEmpty() + && ((mHashingAlgorithm == HashingAlgorithmType::Sha1) || mHashingAlgorithm == HashingAlgorithmType::Sha256); } QStringList DKIMInfo::listSignedHeader() const { return mListSignedHeader; } void DKIMInfo::setListSignedHeader(const QStringList &listSignedHeader) { mListSignedHeader = listSignedHeader; } QString DKIMInfo::signingAlgorithm() const { return mSigningAlgorithm; } void DKIMInfo::setSigningAlgorithm(const QString &signingAlgorithm) { mSigningAlgorithm = signingAlgorithm; } qint64 DKIMInfo::signatureTimeStamp() const { return mSignatureTimeStamp; } void DKIMInfo::setSignatureTimeStamp(qint64 signatureTimeStamp) { mSignatureTimeStamp = signatureTimeStamp; } QString DKIMInfo::query() const { return mQuery; } void DKIMInfo::setQuery(const QString &query) { mQuery = query; } qint64 DKIMInfo::expireTime() const { return mExpireTime; } void DKIMInfo::setExpireTime(qint64 expireTime) { mExpireTime = expireTime; } QString DKIMInfo::signature() const { return mSignature; } void DKIMInfo::setSignature(const QString &signature) { mSignature = signature; } QString DKIMInfo::agentOrUserIdentifier() const { return mAgentOrUserIdentifier; } void DKIMInfo::setAgentOrUserIdentifier(const QString &userAgent) { mAgentOrUserIdentifier = userAgent; } int DKIMInfo::bodyLengthCount() const { return mBodyLengthCount; } void DKIMInfo::setBodyLengthCount(int bodyLengthCount) { mBodyLengthCount = bodyLengthCount; } QDebug operator <<(QDebug d, const DKIMInfo &t) { d << "mVersion " << t.version(); d << "mHashingAlgorithm " << t.hashingAlgorithm(); d << "mSigningAlgorithm " << t.signingAlgorithm(); d << "mDomain " << t.domain(); d << "mSelector " << t.selector(); d << "mBodyHash " << t.bodyHash(); d << "mSignatureTimeStamp " << t.signatureTimeStamp(); d << "mExpireTime " << t.expireTime(); d << "mQuery " << t.query(); d << "mSignature " << t.signature(); d << "mAgentOrUserIdentifier " << t.agentOrUserIdentifier(); d << "mBodyLengthCount " << t.bodyLengthCount(); d << "mListSignedHeader " << t.listSignedHeader(); d << "mHeaderCanonization " << t.headerCanonization(); d << "mBodyCanonization " << t.bodyCanonization(); d << "mIdomain " << t.iDomain(); return d; } diff --git a/messageviewer/src/dkim-verify/dkiminfo.h b/messageviewer/src/dkim-verify/dkiminfo.h index ecef0cdd..4b42c13c 100644 --- a/messageviewer/src/dkim-verify/dkiminfo.h +++ b/messageviewer/src/dkim-verify/dkiminfo.h @@ -1,120 +1,128 @@ /* Copyright (C) 2018-2019 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DKIMINFO_H #define DKIMINFO_H #include "messageviewer_export.h" #include #include #include namespace MessageViewer { class MESSAGEVIEWER_EXPORT DKIMInfo { + Q_GADGET public: DKIMInfo(); enum CanonicalizationType { Unknown, Simple, Relaxed, }; + enum class HashingAlgorithmType { + Any, + Sha1, + Sha256, + Unknown, + }; + Q_ENUM(HashingAlgorithmType) Q_REQUIRED_RESULT bool parseDKIM(const QString &header); Q_REQUIRED_RESULT int version() const; void setVersion(int version); - Q_REQUIRED_RESULT QString hashingAlgorithm() const; - void setHashingAlgorithm(const QString &hashingAlgorithm); + Q_REQUIRED_RESULT HashingAlgorithmType hashingAlgorithm() const; + void setHashingAlgorithm(DKIMInfo::HashingAlgorithmType type); Q_REQUIRED_RESULT QString domain() const; void setDomain(const QString &domain); Q_REQUIRED_RESULT QString selector() const; void setSelector(const QString &selector); Q_REQUIRED_RESULT QString bodyHash() const; void setBodyHash(const QString &bodyHash); Q_REQUIRED_RESULT bool isValid() const; Q_REQUIRED_RESULT QStringList listSignedHeader() const; void setListSignedHeader(const QStringList &listSignedHeader); Q_REQUIRED_RESULT QString signingAlgorithm() const; void setSigningAlgorithm(const QString &signingAlgorithm); Q_REQUIRED_RESULT qint64 signatureTimeStamp() const; void setSignatureTimeStamp(qint64 signatureTimeStamp); Q_REQUIRED_RESULT QString query() const; void setQuery(const QString &query); Q_REQUIRED_RESULT qint64 expireTime() const; void setExpireTime(qint64 expireTime); Q_REQUIRED_RESULT QString signature() const; void setSignature(const QString &signature); Q_REQUIRED_RESULT QString agentOrUserIdentifier() const; void setAgentOrUserIdentifier(const QString &agentOrUserIdentifier); Q_REQUIRED_RESULT int bodyLengthCount() const; void setBodyLengthCount(int bodyLengthCount); Q_REQUIRED_RESULT CanonicalizationType headerCanonization() const; void setHeaderCanonization(const CanonicalizationType &headerCanonization); Q_REQUIRED_RESULT CanonicalizationType bodyCanonization() const; void setBodyCanonization(const CanonicalizationType &bodyCanonization); Q_REQUIRED_RESULT bool operator==(const DKIMInfo &other) const; Q_REQUIRED_RESULT QStringList copiedHeaderField() const; void setCopiedHeaderField(const QStringList &copiedHeaderField); Q_REQUIRED_RESULT QString iDomain() const; void setIDomain(const QString &iDomain); private: void parseCanonicalization(const QString &str); void parseAlgorithm(const QString &str); - QString mHashingAlgorithm; + HashingAlgorithmType mHashingAlgorithm = HashingAlgorithmType::Any; QString mSigningAlgorithm; QString mDomain; QString mSelector; QString mBodyHash; QString mQuery; QString mSignature; QString mAgentOrUserIdentifier; QString mIDomain; QStringList mListSignedHeader; QStringList mCopiedHeaderField; //Default is empty qint64 mSignatureTimeStamp = -1; qint64 mExpireTime = -1; int mVersion = -1; int mBodyLengthCount = -1; CanonicalizationType mHeaderCanonization = Unknown; CanonicalizationType mBodyCanonization = Unknown; }; } Q_DECLARE_METATYPE(MessageViewer::DKIMInfo) MESSAGEVIEWER_EXPORT QDebug operator <<(QDebug d, const MessageViewer::DKIMInfo &t); #endif // DKIMINFO_H