diff options
| author | saturneric <[email protected]> | 2024-11-26 10:55:02 +0000 | 
|---|---|---|
| committer | saturneric <[email protected]> | 2024-11-26 10:55:02 +0000 | 
| commit | 37bbb836f049e03b0bbdde20ac3d837b3667647e (patch) | |
| tree | a884b5669b68f3a6dcbfa6571bf93fa5cafe29e8 /src/m_email | |
| parent | feat: support .eml file verifying (diff) | |
| download | Modules-37bbb836f049e03b0bbdde20ac3d837b3667647e.tar.gz Modules-37bbb836f049e03b0bbdde20ac3d837b3667647e.zip  | |
feat: check and provide more infos by email verification
Diffstat (limited to 'src/m_email')
| -rw-r--r-- | src/m_email/EMAilHelper.cpp | 154 | ||||
| -rw-r--r-- | src/m_email/EMailHelper.h | 100 | ||||
| -rw-r--r-- | src/m_email/EMailModule.cpp | 88 | 
3 files changed, 323 insertions, 19 deletions
diff --git a/src/m_email/EMAilHelper.cpp b/src/m_email/EMAilHelper.cpp new file mode 100644 index 0000000..ff544ab --- /dev/null +++ b/src/m_email/EMAilHelper.cpp @@ -0,0 +1,154 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include "EMailHelper.h" + +#include <QRegularExpression> + +#include "GFModuleCommonUtils.hpp" + +auto IsValidMicalgFormat(const QString& prm_micalg_value) -> bool { +  QRegularExpression regex("^pgp-(\\w+)$"); +  QRegularExpressionMatch match = regex.match(prm_micalg_value); +  return match.hasMatch(); +} + +auto FormatMailBox(const std::shared_ptr<vmime::mailbox>& m) -> QString { +  if (!m) return {"Unknown"}; + +  QString name = Q_SC(m->getName().getConvertedText(vmime::charsets::UTF_8)); +  QString address = +      Q_SC(m->getEmail().toText().getConvertedText(vmime::charsets::UTF_8)); + +  if (!name.isEmpty()) { +    return QString("%1 <%2>").arg(name, address); +  } +  return address; +} + +auto ExtractFieldValue(const vmime::shared_ptr<vmime::header>& header, +                       const QString& field_name) -> QString { +  auto field = header->getField(field_name.toStdString()); +  if (!field) { +    FLOG_WARN("cannot get '%1' Field from header", field_name); +    return {}; +  } + +  auto field_value = field->getValue(); +  if (!field_value) { +    FLOG_WARN("cannot get '%s' Field Value from header", field_name); +    return {}; +  } + +  return Q_SC(field_value->generate()); +} + +auto ExtractFieldValueMailBox(const vmime::shared_ptr<vmime::header>& header, +                              const QString& field_name) -> QString { +  auto field = header->getField(field_name.toStdString()); +  if (!field) { +    FLOG_WARN("cannot get '%1' Field from header", field_name); +    return {}; +  } + +  auto field_value = field->getValue<vmime::mailbox>(); +  if (!field_value) { +    FLOG_WARN("cannot get '%s' Field Value from header", field_name); +    return {}; +  } + +  return FormatMailBox(field_value); +} + +auto ExtractFieldValueAddressList( +    const vmime::shared_ptr<vmime::header>& header, +    const QString& field_name) -> QString { +  auto field = header->getField(field_name.toStdString()); +  if (!field) { +    FLOG_WARN("cannot get '%1' Field from header", field_name); +    return {}; +  } + +  auto field_value = field->getValue<vmime::addressList>(); +  if (!field_value) { +    FLOG_WARN("cannot get '%s' Field Value from header", field_name); +    return {}; +  } + +  QStringList mailbox_list_strings; +  for (const auto& mailbox : field_value->toMailboxList()->getMailboxList()) { +    QString formatted_mailbox = FormatMailBox(mailbox); +    mailbox_list_strings.append(formatted_mailbox); +  } + +  return mailbox_list_strings.join(", "); +} + +auto ExtractFieldValueText(const vmime::shared_ptr<vmime::header>& header, +                           const QString& field_name) -> QString { +  auto field = header->getField(field_name.toStdString()); +  if (!field) { +    FLOG_WARN("cannot get '%1' Field from header", field_name); +    return {}; +  } + +  auto field_value = field->getValue<vmime::text>(); +  if (!field_value) { +    FLOG_WARN("cannot get '%s' Field Value from header", field_name); +    return {}; +  } + +  return Q_SC(field_value->getConvertedText(vmime::charsets::UTF_8)); +} + +auto ExtractFieldValueDateTime(const vmime::shared_ptr<vmime::header>& header, +                               const QString& field_name) -> QDateTime { +  auto field = header->getField(field_name.toStdString()); +  if (!field) { +    FLOG_WARN("cannot get '%1' Field from header", field_name); +    return {}; +  } + +  auto field_value = field->getValue<vmime::datetime>(); +  if (!field_value) { +    FLOG_WARN("cannot get '%s' Field Value from header", field_name); +    return {}; +  } + +  QDate date(field_value->getYear(), field_value->getMonth(), +             field_value->getDay()); +  QTime time(field_value->getHour(), field_value->getMinute(), +             field_value->getSecond()); +  field_value->getZone(); + +  auto zone = field_value->getZone(); +  QDateTime datetime(date, time); +  datetime.setOffsetFromUtc(zone * 60); + +  return datetime; +} diff --git a/src/m_email/EMailHelper.h b/src/m_email/EMailHelper.h new file mode 100644 index 0000000..3a70d0b --- /dev/null +++ b/src/m_email/EMailHelper.h @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2021-2024 Saturneric <[email protected]> + * + * This file is part of GpgFrontend. + * + * GpgFrontend is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GpgFrontend 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GpgFrontend. If not, see <https://www.gnu.org/licenses/>. + * + * The initial version of the source code is inherited from + * the gpg4usb project, which is under GPL-3.0-or-later. + * + * All the source code of GpgFrontend was modified and released by + * Saturneric <[email protected]> starting on May 12, 2021. + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#pragma once + +#include <QDateTime> +#include <QString> + +// vmime +#define VMIME_STATIC +#include <vmime/vmime.hpp> + +auto inline Q_SC(const std::string& s) -> QString { +  return QString::fromStdString(s); +} + +/** + * @brief + * + * @param prm_micalg_value + * @return true + * @return false + */ +auto IsValidMicalgFormat(const QString& prm_micalg_value) -> bool; + +/** + * @brief + * + * @param header + * @param field_name + * @return QString + */ +auto ExtractFieldValue(const vmime::shared_ptr<vmime::header>& header, +                       const QString& field_name) -> QString; + +/** + * @brief + * + * @param header + * @param field_name + * @return QString + */ +auto ExtractFieldValueText(const vmime::shared_ptr<vmime::header>& header, +                           const QString& field_name) -> QString; + +/** + * @brief + * + * @param header + * @param field_name + * @return QString + */ +auto ExtractFieldValueMailBox(const vmime::shared_ptr<vmime::header>& header, +                              const QString& field_name) -> QString; + +/** + * @brief + * + * @param header + * @param field_name + * @return QString + */ +auto ExtractFieldValueAddressList( +    const vmime::shared_ptr<vmime::header>& header, +    const QString& field_name) -> QString; + +/** + * @brief + * + * @param header + * @param field_name + * @return QDateTime + */ +auto ExtractFieldValueDateTime(const vmime::shared_ptr<vmime::header>& header, +                               const QString& field_name) -> QDateTime; diff --git a/src/m_email/EMailModule.cpp b/src/m_email/EMailModule.cpp index e12ca54..f2747c7 100644 --- a/src/m_email/EMailModule.cpp +++ b/src/m_email/EMailModule.cpp @@ -49,15 +49,14 @@  #include "GFModuleCommonUtils.hpp"  #include "GFModuleDefine.h" +// +#include "EMailHelper.h" +  GF_MODULE_API_DEFINE_V2("com.bktus.gpgfrontend.module.email", "Email", "1.0.0",                          "Everything related to E-Mails.", "Saturneric")  DEFINE_TRANSLATIONS_STRUCTURE(ModuleEMail); -auto inline Q_SC(const std::string& s) -> QString { -  return QString::fromStdString(s); -} -  auto GFRegisterModule() -> int {    MLogDebug("email module registering..."); @@ -72,9 +71,6 @@ REGISTER_EVENT_HANDLER(EMAIL_VERIFY_EML_DATA, [](const MEvent& event) -> int {    if (event["eml_data"].isEmpty()) CB_ERR(event, -1, "eml_data is empty");    auto data = QByteArray::fromBase64(QString(event["eml_data"]).toLatin1()); -  auto hash = QCryptographicHash::hash(data, QCryptographicHash::Sha1); -  FLOG_DEBUG("E-Mail Raw Data SHA-1 Hash: %1", hash.toHex()); -    vmime::string vmime_data(data.constData(), data.size());    auto message = vmime::make_shared<vmime::message>(); @@ -89,39 +85,85 @@ REGISTER_EVENT_HANDLER(EMAIL_VERIFY_EML_DATA, [](const MEvent& event) -> int {    auto content_type_field =        header->getField<vmime::contentTypeField>(vmime::fields::CONTENT_TYPE); +  if (!content_type_field) { +    CB_ERR(event, -2, "cannot get 'Content-Type' Field from header"); +  }    auto content_type_value =        Q_SC(content_type_field->getValue()->generate()).trimmed(); +  auto prm_protocol = content_type_field->getParameter("protocol"); +  if (!prm_protocol) { +    CB_ERR(event, -2, "cannot get 'protocol' from 'Content-Type'"); +  } +    /* -   * OpenPGP signed messages are denoted by the "multipart/signed" content type. +   * OpenPGP signed messages are denoted by the "multipart/signed" content +   * type.     */    if (content_type_value != "multipart/signed") -    CB_ERR(event, -2, "Content-Type must be multipart/signed"); - -  auto prm_protocol = content_type_field->getParameter("protocol"); -  auto prm_protocol_value = Q_SC(prm_protocol->getValue().generate()); +    CB_ERR(event, -2, +           "OpenPGP signed messages are denoted by the 'multipart/signed' " +           "content type");    /*     * with a "protocol" parameter which MUST have a value of     * "application/pgp-signature"     */ +  auto prm_protocol_value = Q_SC(prm_protocol->getValue().generate());    if (prm_protocol_value != "application/pgp-signature")      CB_ERR(event, -2, -           "protocol of Content-Type MUST be application/pgp-signature"); +           "'protocol' parameter which MUST have a value of " +           "'application/pgp-signature' (MUST be quoted)"); + +  auto prm_micalg = content_type_field->getParameter("micalg"); +  if (!prm_micalg) { +    CB_ERR(event, -2, "cannot get 'micalg' from 'Content-Type'"); +  } + +  /* +   * The "micalg" parameter for the "application/pgp-signature" protocol +   * MUST contain exactly one hash-symbol of the format "pgp-<hash- +   * identifier>", where <hash-identifier> identifies the Message +   * Integrity Check (MIC) algorithm used to generate the signature. +   */ +  auto prm_micalg_value = Q_SC(prm_micalg->getValue().generate()); +  FLOG_DEBUG("micalg value: %1", prm_micalg_value); +  if (!IsValidMicalgFormat(prm_micalg_value)) { +    CB_ERR(event, -2, +           "'micalg' MUST contain exactly one hash-symbol of the format " +           "'pgp-<hash-identifier>'"); +  } + +  auto from_field_value_text = +      ExtractFieldValueMailBox(header, vmime::fields::FROM); +  auto to_field_value_text = +      ExtractFieldValueAddressList(header, vmime::fields::TO); +  auto cc_field_value_text = +      ExtractFieldValueAddressList(header, vmime::fields::CC); +  auto bcc_field_value_text = +      ExtractFieldValueAddressList(header, vmime::fields::BCC); +  auto date_field_value = +      ExtractFieldValueDateTime(header, vmime::fields::DATE); +  auto subject_field_value_text = +      ExtractFieldValueText(header, vmime::fields::SUBJECT); +  auto reply_to_field_value_text = +      ExtractFieldValueMailBox(header, vmime::fields::REPLY_TO); +  auto organization_text = +      ExtractFieldValueText(header, vmime::fields::ORGANIZATION);    auto body = message->getBody();    auto content_type = body->getContentType();    auto part_count = body->getPartCount(); -  FLOG_DEBUG("body content type: %1", content_type.generate());    FLOG_DEBUG("body page count: %1", part_count);    /*     * The multipart/signed body MUST consist of exactly two parts.     */    if (part_count != 2) -    CB_ERR(event, -2, "body MUST consist of exactly two parts"); +    CB_ERR(event, -2, +           "The multipart/signed body MUST consist of exactly two parts");    /*      The first part contains the signed data in MIME canonical format, @@ -150,7 +192,7 @@ REGISTER_EVENT_HANDLER(EMAIL_VERIFY_EML_DATA, [](const MEvent& event) -> int {      CB_ERR(event, -2, "mime raw data part is empty");    /* -   * The second body MUST contain the OpenPGP digital signature.  It MUST +   * The second body MUST contain the OpenPGP digital signature. It MUST     * be labeled with a content type of "application/pgp-signature"     */    auto part_sign = body->getPartAt(1); @@ -160,9 +202,9 @@ REGISTER_EVENT_HANDLER(EMAIL_VERIFY_EML_DATA, [](const MEvent& event) -> int {        Q_SC(part_sign_content_type->getValue()->generate());    if (part_sign_content_type_value != "application/pgp-signature") -    CB_ERR( -        event, -2, -        "signature part must have a Content-Type of application/pgp-signature"); +    CB_ERR(event, -2, +           "The second body MUST be labeled with a content type of " +           "'application/pgp-signature'");    auto part_sign_body_content =        QByteArray::fromStdString(part_sign->getBody()->generate()); @@ -176,7 +218,15 @@ REGISTER_EVENT_HANDLER(EMAIL_VERIFY_EML_DATA, [](const MEvent& event) -> int {       {           {"ret", QString::number(0)},           {"mime", QString::fromLatin1(part_mime_content_text.toBase64())}, +         {"mime_hash", part_mime_content_hash},           {"signature", QString::fromLatin1(part_sign_body_content.toBase64())}, +         {"from", from_field_value_text}, +         {"to", to_field_value_text}, +         {"cc", cc_field_value_text}, +         {"bcc", bcc_field_value_text}, +         {"subject", subject_field_value_text}, +         {"datetime", QString::number(date_field_value.toMSecsSinceEpoch())}, +         {"micalg", prm_micalg_value},       });    return 0;  | 
