From de8dd048e863d2355666dde79ff2460170670e7c Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 10 Apr 2022 13:18:55 +0300 Subject: [PATCH] message: add basic support for encrypted parts --- lib/message/mu-message-part.cc | 6 ++ lib/message/mu-message-part.hh | 9 ++ lib/message/mu-mime-object.cc | 160 ++++++++++++++++++++++++++++- lib/message/mu-mime-object.hh | 177 ++++++++++++++++++++++++++++----- lib/message/test-mu-message.cc | 175 +++++++++++++++++++++++++------- 5 files changed, 461 insertions(+), 66 deletions(-) diff --git a/lib/message/mu-message-part.cc b/lib/message/mu-message-part.cc index 94f914bd..b9f4cb00 100644 --- a/lib/message/mu-message-part.cc +++ b/lib/message/mu-message-part.cc @@ -143,3 +143,9 @@ MessagePart::is_signed() const noexcept { return mime_object().is_multipart_signed(); } + +bool +MessagePart::is_encrypted() const noexcept +{ + return mime_object().is_multipart_encrypted(); +} diff --git a/lib/message/mu-message-part.hh b/lib/message/mu-message-part.hh index 8d54375c..624773c1 100644 --- a/lib/message/mu-message-part.hh +++ b/lib/message/mu-message-part.hh @@ -111,6 +111,15 @@ public: */ bool is_signed() const noexcept; + + /** + * Is this part encrypted? + * + * @return true or false + */ + bool is_encrypted() const noexcept; + + /** * Write (decoded) mime-part contents to string * diff --git a/lib/message/mu-mime-object.cc b/lib/message/mu-mime-object.cc index d8f87729..830203dc 100644 --- a/lib/message/mu-mime-object.cc +++ b/lib/message/mu-mime-object.cc @@ -170,7 +170,7 @@ MimeCryptoContext::setup_gpg_test(const std::string& testpath) g_unsetenv ("DISPLAY"); g_unsetenv ("GPG_TTY"); - if (g_mkdir_with_parents((testpath + "/.gnupg").c_str(), 700) != 0) + if (g_mkdir_with_parents((testpath + "/.gnupg").c_str(), 0700) != 0) return Err(Error::Code::File, "failed to create gnupg dir; err=%d", errno); @@ -461,7 +461,6 @@ MimeMultipartSigned::verify(VerifyFlags vflags) const noexcept std::vector sigs; for (auto i = 0; i != g_mime_signature_list_length(siglist); ++i) { GMimeSignature *sig = g_mime_signature_list_get_signature(siglist, i); - g_object_ref(sig); sigs.emplace_back(MimeSignature(sig)); } @@ -470,3 +469,160 @@ MimeMultipartSigned::verify(VerifyFlags vflags) const noexcept return sigs; } + +/* + * we need to be able to pass a crypto-context to the verify(), but + * g_mime_multipart_signed_verify() doesn't offer that anymore in GMime 3.x. + * + * So, add that by reimplementing it a bit (follow the upstream impl) + */ + + +static bool +mime_types_equal (const std::string& mime_type, const std::string& official_type) +{ + if (g_ascii_strcasecmp(mime_type.c_str(), official_type.c_str())) + return true; + + const auto slash_pos = official_type.find("/"); + if (slash_pos == std::string::npos || slash_pos == 0) + return false; + + /* If the official mime-type's subtype already begins with "x-", then there's + * nothing else to check. */ + const auto subtype{official_type.substr(slash_pos + 1)}; + if (g_ascii_strncasecmp (subtype.c_str(), "x-", 2) == 0) + return false; + const auto supertype{official_type.substr(0, slash_pos - 1)}; + const auto xtype{official_type.substr(0, slash_pos - 1) + "x-" + subtype}; + + /* Check if the "x-" version of the official mime-type matches the + * supplied mime-type. For example, if the official mime-type is + * "application/pkcs7-signature", then we also want to match + * "application/x-pkcs7-signature". */ + return g_ascii_strcasecmp(mime_type.c_str(), xtype.c_str()) == 0; +} + + + + +// Result> +// MimeMultipartSigned::verify(VerifyFlags vflags, const CryptoContext& ctx) const noexcept +// { +// if (g_mime_multipart_get_count(self()) < 2) +// return Err(Error::Code::Crypto, "cannot verify, not enough subparts"); + +// const auto proto{content_type_parameter("protocol")}; +// const auto sign_proto{ctx.signature_prototol().value_or("")}; + +// if (!proto || !sign_proto || !mime_types_equal(*proto, sign_proto)) +// return Err(Error::Code::Crypto, "unsupported protocol"); + +// const auto sig{MimeObject(g_mime_multipart_get_part(self(), GMIME_MULTIPART_SIGNED_SIGNATURE))}; +// if (!sig || !mime_types_equal(sig.mime_type().value_or(""), sign_proto)) +// return Err(Error::Code::Crypto, "failed to find matching signature part"); + +// MimeObject content{g_mime_multipart_get_part(self(), GMIME_MULTIPART_SIGNED_CONTENT)}; +// if (!content) +// return Err(Error::Code::Crypto, "cannot find content part"); + +// MimeFormatOptions fopts{format_options_new()}; +// g_mime_format_options_set_newline_format(*fopts, GMIME_NEWLINE_FORMAT_DOS); + +// MimeStream stream{g_mime_stream_mem_new()}; +// g_mime_object_write_to_stream (content, *fopts, stream); +// g_mime_stream_reset (stream); + +// GMimeDataWrapper *wrapper = g_mime_part_get_content(static_cast(sig)); + + + + +// GError *err{}; +// GMimeSignatureList *siglist = g_mime_multipart_signed_verify( +// self(), +// static_cast(vflags), +// &err); + +// if (!siglist) +// return Err(Error::Code::Crypto, &err, "failed to verify"); + +// std::vector sigs; +// for (auto i = 0; i != g_mime_signature_list_length(siglist); ++i) { +// GMimeSignature *sig = g_mime_signature_list_get_signature(siglist, i); +// sigs.emplace_back(MimeSignature(sig)); +// } + +// g_object_unref(siglist); + +// return sigs; + +// } + + + + + + + + + + + + + + + +std::vector +MimeDecryptResult::recipients() const noexcept +{ + GMimeCertificateList *lst{g_mime_decrypt_result_get_recipients(self())}; + if (!lst) + return {}; + + std::vector certs; + for (int i = 0; i != g_mime_certificate_list_length(lst); ++i) + certs.emplace_back( + MimeCertificate( + g_mime_certificate_list_get_certificate(lst, i))); + + return certs; +} + +std::vector +MimeDecryptResult::signatures() const noexcept +{ + GMimeSignatureList *lst{g_mime_decrypt_result_get_signatures(self())}; + if (!lst) + return {}; + + std::vector sigs; + for (auto i = 0; i != g_mime_signature_list_length(lst); ++i) { + GMimeSignature *sig = g_mime_signature_list_get_signature(lst, i); + sigs.emplace_back(MimeSignature(sig)); + } + + return sigs; +} + + + + +Mu::Result +MimeMultipartEncrypted::decrypt(DecryptFlags flags, + const std::string& session_key) const noexcept +{ + GError *err{}; + GMimeDecryptResult *dres{}; + GMimeObject *obj = g_mime_multipart_encrypted_decrypt( + self(), + static_cast(flags), + session_key.empty() ? NULL : session_key.c_str(), + &dres, + &err); + + if (!obj) + return Err(Error::Code::Crypto, &err, "failed to decrypt"); + + return Ok(Decrypted{MimeObject{obj}, MimeDecryptResult(dres)}); +} diff --git a/lib/message/mu-mime-object.hh b/lib/message/mu-mime-object.hh index f616383a..1617c9c7 100644 --- a/lib/message/mu-mime-object.hh +++ b/lib/message/mu-mime-object.hh @@ -101,8 +101,7 @@ public: */ Object& operator=(Object&& other) noexcept { - if (this != &other) { - auto oldself = self_; + if (this != &other) { auto oldself = self_; self_ = other.self_; other.self_ = nullptr; if (oldself) @@ -130,13 +129,6 @@ public: protected: GObject* object() const { return self(); } - static Option maybe_string(const char *str) noexcept { - if (!str) - return Nothing; - else - return std::string(str); - } - private: GObject *self() const { return self_; } mutable GObject *self_{}; @@ -153,12 +145,17 @@ struct MimeContentType: public Object { if (!GMIME_IS_CONTENT_TYPE(self())) throw std::runtime_error("not a content-type"); } - std::string media_type() const { + std::string media_type() const noexcept { return g_mime_content_type_get_media_type(self()); } - std::string media_subtype() const { + std::string media_subtype() const noexcept { return g_mime_content_type_get_media_subtype(self()); } + + Option mime_type() const noexcept { + return to_option_string(g_mime_content_type_get_mime_type(self())); + } + bool is_type(const std::string& type, const std::string& subtype) const { return g_mime_content_type_is_type(self(), type.c_str(), subtype.c_str()); @@ -170,7 +167,6 @@ private: }; - /** * Thin wrapper around a GMimeStream @@ -186,6 +182,11 @@ struct MimeStream: public Object { return g_mime_stream_write(self(), buf, size); } + bool reset() { + return g_mime_stream_reset(self()) < 0 ? false : true; + } + + GMimeStream* self() const { return reinterpret_cast(object()); } @@ -282,27 +283,27 @@ struct MimeCertificate: public Object { } Option issuer_serial() const { - return maybe_string(g_mime_certificate_get_issuer_serial(self())); + return to_option_string(g_mime_certificate_get_issuer_serial(self())); } Option issuer_name() const { - return maybe_string(g_mime_certificate_get_issuer_name(self())); + return to_option_string(g_mime_certificate_get_issuer_name(self())); } Option fingerprint() const { - return maybe_string(g_mime_certificate_get_fingerprint(self())); + return to_option_string(g_mime_certificate_get_fingerprint(self())); } Option key_id() const { - return maybe_string(g_mime_certificate_get_key_id(self())); + return to_option_string(g_mime_certificate_get_key_id(self())); } Option name() const { - return maybe_string(g_mime_certificate_get_name(self())); + return to_option_string(g_mime_certificate_get_name(self())); } Option user_id() const { - return maybe_string(g_mime_certificate_get_user_id(self())); + return to_option_string(g_mime_certificate_get_user_id(self())); } Option<::time_t> created() const { @@ -477,6 +478,78 @@ static inline std::string to_string(MimeSignature::Status status) { } + + +/** +* Thin wrapper around a GMimeDecryptResult + * + */ +struct MimeDecryptResult: public Object { + MimeDecryptResult (GMimeDecryptResult *decres) : Object{G_OBJECT(decres)} { + if (!GMIME_IS_DECRYPT_RESULT(self())) + throw std::runtime_error("not a decrypt-result"); + } + + std::vector recipients() const noexcept; + std::vector signatures() const noexcept; + + enum struct CipherAlgo { + Default = GMIME_CIPHER_ALGO_DEFAULT, + Idea = GMIME_CIPHER_ALGO_IDEA, + Des3 = GMIME_CIPHER_ALGO_3DES, + Cast5 = GMIME_CIPHER_ALGO_CAST5, + Blowfish = GMIME_CIPHER_ALGO_BLOWFISH, + Aes = GMIME_CIPHER_ALGO_AES, + Aes192 = GMIME_CIPHER_ALGO_AES192, + Aes256 = GMIME_CIPHER_ALGO_AES256, + TwoFish = GMIME_CIPHER_ALGO_TWOFISH, + Camellia128 = GMIME_CIPHER_ALGO_CAMELLIA128, + Camellia192 = GMIME_CIPHER_ALGO_CAMELLIA192, + Camellia256 = GMIME_CIPHER_ALGO_CAMELLIA256 + }; + + CipherAlgo cipher() const noexcept { + return static_cast( + g_mime_decrypt_result_get_cipher(self())); + } + + using DigestAlgo = MimeCertificate::DigestAlgo; + DigestAlgo mdc() const noexcept { + return static_cast( + g_mime_decrypt_result_get_mdc(self())); + } + + Option session_key() const noexcept { + return to_option_string(g_mime_decrypt_result_get_session_key(self())); + } + + +private: + GMimeDecryptResult* self() const { + return reinterpret_cast(object()); + } +}; + +constexpr std::array, 12> +AllCipherAlgos= {{ + { MimeDecryptResult::CipherAlgo::Default , "default"}, + { MimeDecryptResult::CipherAlgo::Idea , "idea"}, + { MimeDecryptResult::CipherAlgo::Des3 , "3des"}, + { MimeDecryptResult::CipherAlgo::Cast5 , "cast5"}, + { MimeDecryptResult::CipherAlgo::Blowfish , "blowfish"}, + { MimeDecryptResult::CipherAlgo::Aes , "aes"}, + { MimeDecryptResult::CipherAlgo::Aes192 , "aes192"}, + { MimeDecryptResult::CipherAlgo::Aes256 , "aes256"}, + { MimeDecryptResult::CipherAlgo::TwoFish , "twofish"}, + { MimeDecryptResult::CipherAlgo::Camellia128, "camellia128"}, + { MimeDecryptResult::CipherAlgo::Camellia192, "camellia192"}, + { MimeDecryptResult::CipherAlgo::Camellia256, "camellia256"}, + }}; + +constexpr Option to_string_view(MimeDecryptResult::CipherAlgo algo) { + return to_string_view(AllCipherAlgos, algo); +}; + /** * Thin wrapper around a GMimeCryptoContext @@ -506,6 +579,17 @@ struct MimeCryptoContext : public Object { return Err(Error::Code::Crypto, "failed to create crypto context"); } + + Option encryption_protocol() { + return to_option_string(g_mime_crypto_context_get_encryption_protocol(self())); + } + Option signature_protocol() { + return to_option_string(g_mime_crypto_context_get_signature_protocol(self())); + } + Option key_exchange_protocol() { + return to_option_string(g_mime_crypto_context_get_key_exchange_protocol(self())); + } + /** * Imports a stream of keys/certificates contained within stream into * the key/certificate database controlled by @this. @@ -574,7 +658,7 @@ public: throw std::runtime_error("not a mime-object"); } MimeObject(GMimeObject *mobj): Object{G_OBJECT(mobj)} { - if (!GMIME_IS_OBJECT(self())) + if (mobj && !GMIME_IS_OBJECT(self())) throw std::runtime_error("not a mime-object"); } @@ -600,6 +684,24 @@ public: return MimeContentType(ct); } + Option content_type_parameter(const std::string& param) const noexcept { + return to_option_string( + g_mime_object_get_content_type_parameter(self(), param.c_str())); + } + + + using MimeFormatOptions = + deletable_unique_ptr; + + + Option write_to_stream(const MimeFormatOptions& f_opts, + MimeStream& stream) { + auto written = g_mime_object_write_to_stream(self(), f_opts.get(), stream.self()); + if (written < 0) + return Nothing; + else + return static_cast(written); + } /** * Write the object to a string. @@ -730,7 +832,7 @@ public: * @return string or nullopt */ Option message_id() const noexcept { - return maybe_string(g_mime_message_get_message_id(self())); + return to_option_string(g_mime_message_get_message_id(self())); } /** @@ -739,7 +841,7 @@ public: * @return string or nullopt */ Option subject() const noexcept { - return maybe_string(g_mime_message_get_subject(self())); + return to_option_string(g_mime_message_get_subject(self())); } /** @@ -764,7 +866,7 @@ public: * */ using ForEachFunc = std::function; + const MimeObject& part)>; /** * Recursively apply func tol all parts of this message @@ -812,7 +914,7 @@ public: * @return string or nullopt */ Option content_description() const noexcept { - return maybe_string(g_mime_part_get_content_description(self())); + return to_option_string(g_mime_part_get_content_description(self())); } /** @@ -822,7 +924,7 @@ public: * @return string or nullopt */ Option content_id() const noexcept { - return maybe_string(g_mime_part_get_content_id(self())); + return to_option_string(g_mime_part_get_content_id(self())); } /** @@ -832,7 +934,7 @@ public: * @return string or nullopt */ Option content_md5() const noexcept { - return maybe_string(g_mime_part_get_content_md5(self())); + return to_option_string(g_mime_part_get_content_md5(self())); } @@ -853,7 +955,7 @@ public: * @return string or nullopt */ Option content_location() const noexcept { - return maybe_string(g_mime_part_get_content_location(self())); + return to_option_string(g_mime_part_get_content_location(self())); } /** @@ -863,7 +965,7 @@ public: * @return string or nullopt */ Option filename() const noexcept { - return maybe_string(g_mime_part_get_filename(self())); + return to_option_string(g_mime_part_get_filename(self())); } /** @@ -1058,12 +1160,30 @@ public: throw std::runtime_error("not a mime-multipart-encrypted"); } + enum struct DecryptFlags { + None = GMIME_DECRYPT_NONE, + ExportSessionKey = GMIME_DECRYPT_EXPORT_SESSION_KEY, + NoVerify = GMIME_DECRYPT_NO_VERIFY, + EnableKeyserverLookups = GMIME_DECRYPT_ENABLE_KEYSERVER_LOOKUPS, + EnableOnlineCertificateChecks = GMIME_DECRYPT_ENABLE_ONLINE_CERTIFICATE_CHECKS + }; + + struct Decrypted { + MimeObject decrypted_object; + MimeDecryptResult decrypte_result; + }; + + Result decrypt(DecryptFlags flags=DecryptFlags::None, + const std::string& session_key = {}) const noexcept; + private: GMimeMultipartEncrypted* self() const { return reinterpret_cast(object()); } }; +MU_ENABLE_BITOPS(MimeMultipartEncrypted::DecryptFlags); + /** * Thin wrapper around a GMimeMultiPartSigned @@ -1096,6 +1216,9 @@ private: } }; + +MU_ENABLE_BITOPS(MimeMultipartSigned::VerifyFlags); + } // namespace Mu diff --git a/lib/message/test-mu-message.cc b/lib/message/test-mu-message.cc index c3638b07..53269a50 100644 --- a/lib/message/test-mu-message.cc +++ b/lib/message/test-mu-message.cc @@ -247,48 +247,50 @@ World! constexpr std::string_view pub_key = R"(-----BEGIN PGP PUBLIC KEY BLOCK----- -mDMEYkycYxYJKwYBBAHaRw8BAQdAiE6rRtXjh1u8ZNVB02k1d3divvp0qrifJSe2 -/vcCLDm0HE11IFRlc3QgPG11QGRqY2Jzb2Z0d2FyZS5ubD6IlAQTFgoAPAIbAwUL -CQgHAgMiAgEGFQoJCAsCBBYCAwECHgcCF4AWIQT0f3WQZJn/X54Jqiz74qC0xbNs -qgUCYkysVAAKCRD74qC0xbNsquJ0AP95o557wp8GoCn4Fn6RA4mX8QOVv9lGrGRu -D42pFNyFmgD+LinWJR973YEGaGSamGwjc0vKY8nXiuY21noN+RMBYQm4OARiTJxj -EgorBgEEAZdVAQUBAQdAdfnXbQAAgQ/2zDKh1kn0EeTZnzEgC0y+VCz+VQOhXDMD -AQgHiHgEGBYKACACGwwWIQT0f3WQZJn/X54Jqiz74qC0xbNsqgUCYkysvgAKCRD7 -4qC0xbNsqsQOAPkBi4cDuf0Yk6PmDb10ARuL4E8plQTO8Ehqp/+O5JeIFQD/f3mi -KTUVweCNFi/1aZ/ViQ4umui3RTmCi+M91A7bRQg= -=3Xa7 +mDMEYlbaNhYJKwYBBAHaRw8BAQdAEgxZnlN3mIwqV89zchjFlEby8OgrbrkT+yRN +hQhc+A+0LU11IFRlc3QgKG11IHRlc3Rpbmcga2V5KSA8bXVAZGpjYnNvZnR3YXJl +Lm5sPoiUBBMWCgA8FiEE/HZRT+2bPjARz29Cw7FsU49t3vAFAmJW2jYCGwMFCwkI +BwIDIgIBBhUKCQgLAgQWAgMBAh4HAheAAAoJEMOxbFOPbd7wJ2kBAIGmUDWYEPtn +qYTwhZIdZtTa4KJ3UdtTqey9AnxJ9mzAAQDRJOoVppj5wW2xRhgYP+ysN2iBUYGE +MhahOcNgxodbCLg4BGJW2jYSCisGAQQBl1UBBQEBB0D4Sp+GTVre7Cx5a8D3SwLJ +/bRAVGDwqI7PL9B/cMmCTwMBCAeIeAQYFgoAIBYhBPx2UU/tmz4wEc9vQsOxbFOP +bd7wBQJiVto2AhsMAAoJEMOxbFOPbd7w1tYA+wdfYCcwOP0QoNZZz2Yk12YkDk2R +FsRrZZpb0GKC/a2VAP4qFceeSegcUCBTQaoeFE9vq9XiUVOO98QI8r9C8QwvBw== +=jM/g -----END PGP PUBLIC KEY BLOCK----- )"; -constexpr std::string_view priv_key = +constexpr std::string_view priv_key = // "test1234" R"(-----BEGIN PGP PRIVATE KEY BLOCK----- -lIYEYkycYxYJKwYBBAHaRw8BAQdAiE6rRtXjh1u8ZNVB02k1d3divvp0qrifJSe2 -/vcCLDn+BwMCMFZr+icelQr/nHyufC4ON2PZG1WTURyap1CAXvV8Jgg8KAtG2olp -Ftp22lSko5JL791GuWe5SnQaIT2I0FNVYPJiuwtcoLxT6vCutam4GLQcTXUgVGVz -dCA8bXVAZGpjYnNvZnR3YXJlLm5sPoiUBBMWCgA8AhsDBQsJCAcCAyICAQYVCgkI -CwIEFgIDAQIeBwIXgBYhBPR/dZBkmf9fngmqLPvioLTFs2yqBQJiTKxUAAoJEPvi -oLTFs2yq4nQA/3mjnnvCnwagKfgWfpEDiZfxA5W/2UasZG4PjakU3IWaAP4uKdYl -H3vdgQZoZJqYbCNzS8pjydeK5jbWeg35EwFhCZyLBGJMnGMSCisGAQQBl1UBBQEB -B0B1+ddtAACBD/bMMqHWSfQR5NmfMSALTL5ULP5VA6FcMwMBCAf+BwMCEaDNUrOs -FLX/HOOPlvFb4zh7IkWYnpCRX1HEWheJIlhYAtzS/EU+Ebc11ricUleyM3mKIeYb -st5PE8NNcm40ep3RtBwNNMt/TGht4/iLfIh4BBgWCgAgAhsMFiEE9H91kGSZ/1+e -Caos++KgtMWzbKoFAmJMrL4ACgkQ++KgtMWzbKrEDgD5AYuHA7n9GJOj5g29dAEb -i+BPKZUEzvBIaqf/juSXiBUA/395oik1FcHgjRYv9Wmf1YkOLprot0U5govjPdQO -20UI -=hlnL +lIYEYlbaNhYJKwYBBAHaRw8BAQdAEgxZnlN3mIwqV89zchjFlEby8OgrbrkT+yRN +hQhc+A/+BwMCz6T2uBpk6a7/rXyE7C1bRbGjP6YSFcyRFz8VRV3Xlm7z6rdbdKZr +8R15AtLvXA4DOK5GiZRB2VbIxi8B9CtZ9qQx6YbQPkAmRzISGAjECrQtTXUgVGVz +dCAobXUgdGVzdGluZyBrZXkpIDxtdUBkamNic29mdHdhcmUubmw+iJQEExYKADwW +IQT8dlFP7Zs+MBHPb0LDsWxTj23e8AUCYlbaNgIbAwULCQgHAgMiAgEGFQoJCAsC +BBYCAwECHgcCF4AACgkQw7FsU49t3vAnaQEAgaZQNZgQ+2ephPCFkh1m1NrgondR +21Op7L0CfEn2bMABANEk6hWmmPnBbbFGGBg/7Kw3aIFRgYQyFqE5w2DGh1sInIsE +YlbaNhIKKwYBBAGXVQEFAQEHQPhKn4ZNWt7sLHlrwPdLAsn9tEBUYPCojs8v0H9w +yYJPAwEIB/4HAwI9MZDWcsoiJ/9oV5DRiAedeo3Ta/1M+aKfeNV36Ch1VGLwQF3E +V77qIrJlsT8CwOZHWUksUBENvG3ak3vd84awHHaHoTmoFwtISfvQrFK0iHgEGBYK +ACAWIQT8dlFP7Zs+MBHPb0LDsWxTj23e8AUCYlbaNgIbDAAKCRDDsWxTj23e8NbW +APsHX2AnMDj9EKDWWc9mJNdmJA5NkRbEa2WaW9Bigv2tlQD+KhXHnknoHFAgU0Gq +HhRPb6vV4lFTjvfECPK/QvEMLwc= +=w1Nc -----END PGP PRIVATE KEY BLOCK----- )"; + static void test_message_signed(void) { constexpr const char *msgtext = -R"(From: Mu Test -To: boo@example.com -Subject: object -Date: Thu, 07 Apr 2022 00:04:26 +0300 -Message-ID: <87bkxdyl8i.fsf@djcbsoftware.nl> +R"(Return-Path: +From: Mu Test +To: Mu Test +Subject: boo +Date: Wed, 13 Apr 2022 17:19:08 +0300 +Message-ID: <878rs9ysin.fsf@djcbsoftware.nl> MIME-Version: 1.0 Content-Type: multipart/signed; boundary="=-=-="; micalg=pgp-sha512; protocol="application/pgp-signature" @@ -303,13 +305,24 @@ Content-Type: application/pgp-signature; name="signature.asc" -----BEGIN PGP SIGNATURE----- -iIkEARYKADEWIQT0f3WQZJn/X54Jqiz74qC0xbNsqgUCYk4BBRMcbXVAZGpjYnNv -ZnR3YXJlLm5sAAoJEPvioLTFs2yqhuwBANzT0Lrex/1ohZ5t3GrAfykkbZPZUHDW -1fhWrQ9GIP+8AQCqlgXEteQjQC0VLPNuV4Iz1wOq/e+Hn0KEBNr230v9AQ== -=PeYV +iIkEARYKADEWIQT8dlFP7Zs+MBHPb0LDsWxTj23e8AUCYlbcLhMcbXVAZGpjYnNv +ZnR3YXJlLm5sAAoJEMOxbFOPbd7waIkA/jK1oY7OL8vrDoubNYxamy8HHmwtvO01 +Q46aYjxe0As6AP90bcAZ3dcn5RcTJaM0UhZssguawZ+tnriD3+5DPkMMCg== +=e32+ -----END PGP SIGNATURE----- --=-=-=-- )"; + TempDir tempdir; + auto ctx{MimeCryptoContext::make_gpg(tempdir.path())}; + g_assert_true(!!ctx); + + MimeStream stream{g_mime_stream_mem_new()}; + stream.write(pub_key.data(), pub_key.size()); + stream.reset(); + + auto imported = ctx->import_keys(stream); + g_assert_cmpuint(*imported, ==, 1); + auto message{Message::make_from_text( msgtext, "/home/test/Maildir/inbox/cur/1649279777.107710_1.mindcrime:2,RS", @@ -331,13 +344,99 @@ ZnR3YXJlLm5sAAoJEPvioLTFs2yqhuwBANzT0Lrex/1ohZ5t3GrAfykkbZPZUHDW const auto mpart{MimeMultipartSigned(mobj)}; const auto sigs{mpart.verify()}; + g_assert_true(!!sigs && sigs->size() == 1); - - g_print("status: %s\n", to_string(sigs->at(0).status()).c_str()); - ++n; } + g_assert_cmpuint(n, ==, 1); +} + + +static void +test_message_signed_encrypted(void) +{ + constexpr const char *msgtext = +R"(From: "Mu Test" +To: mu@djcbsoftware.nl +Subject: encrypted and signed +Date: Wed, 13 Apr 2022 17:32:30 +0300 +Message-ID: <87lew9xddt.fsf@djcbsoftware.nl> +MIME-Version: 1.0 +Content-Type: multipart/encrypted; boundary="=-=-="; + protocol="application/pgp-encrypted" + +--=-=-= +Content-Type: application/pgp-encrypted + +Version: 1 + +--=-=-= +Content-Type: application/octet-stream + +-----BEGIN PGP MESSAGE----- + +hF4DeEerj6WhdZASAQdAKdZwmugAlQA8c06Q5iQw4rwSADgfEWBTWlI6tDw7hEAw +0qSSeeQbA802qjG5TesaDVbFoPp1gOESt67HkJBABj9niwZLnjbzVRXKFoPTYabu +1MBWAQkCEO6kS0N73XQeJ9+nDkUacRX6sSgVM0j+nRdCGcrCQ8MOfLd9KUUBxpXy +r/rIBMpZGOIpKJnoZ2x75VsQIp/ADHLe9zzXVe0tkahXJqvLo26w3gn4NSEIEDp6 +4T/zMZImqGrENaixNmRiRSAnwPkLt95qJGOIqYhuW3X6hMRZyU4zDNwkAvnK+2Fv +Wjd+EmiFzh5tvCmPOSj556YFMV7UpFWO9VznXX/T5+f4i+95Lsm9Uotv/SiNtNQG +DPU3wiL347SzmPFXckknjlzSzDL1XbdbHdmoJs0uNnbaZxRwhkuTYbLHdpBZrBgR +C0bdoCx44QVU8HaZ2x91h3GoM/0q5bqM/rvCauwbokiJgAUrznecNPY= +=Ado7 +-----END PGP MESSAGE----- +--=-=-=-- +)"; + TempDir tempdir; + auto ctx{MimeCryptoContext::make_gpg(tempdir.path())}; + g_assert_true(!!ctx); + + /// test1234 + ctx->set_password_request_function( + [](const MimeCryptoContext& ctx, + const std::string& user_id, + const std::string& prompt, + bool reprompt, + MimeStream& response)->Result { + + return Err(Error::Code::Internal, "boo"); + + g_warning("boo!"); + + return Ok(); + }); + + { + MimeStream stream{g_mime_stream_mem_new()}; + stream.write(priv_key.data(), priv_key.size()); + stream.reset(); + g_assert_cmpint(ctx->import_keys(stream).value_or(-1),==,1); + } + + auto message{Message::make_from_text( + msgtext, + "/home/test/Maildir/inbox/cur/1649279888.107710_1.mindcrime:2,FS", + "/archive")}; + g_assert_true(!!message); + g_assert_true(message->flags() == (Flags::Encrypted|Flags::Seen|Flags::Flagged)); + + size_t n{}; + for (auto&& part: message->parts()) { + + if (!part.is_encrypted()) + continue; + + const auto& mobj{part.mime_object()}; + if (!mobj.is_multipart_encrypted()) + continue; + + const auto mpart{MimeMultipartEncrypted(mobj)}; + const auto decres = mpart.decrypt(); + g_assert_true(!!decres); + + ++n; + } g_assert_cmpuint(n, ==, 1); } @@ -355,6 +454,8 @@ main(int argc, char* argv[]) test_message_attachments); g_test_add_func("/message/message/signed", test_message_signed); + g_test_add_func("/message/message/signed-encrypted", + test_message_signed_encrypted); return g_test_run(); }