From 4060a42634fed80f1c517ca57ea3594a92c32525 Mon Sep 17 00:00:00 2001 From: sha512sum Date: Sat, 14 Sep 2024 16:38:45 +0000 Subject: [PATCH] Move all encryption to separate file --- library/include/larra/client/client.hpp | 136 ++------------ library/include/larra/encryption.hpp | 98 ++++++++++ library/src/encryption.cpp | 228 ++++++++++++++++++++++++ 3 files changed, 337 insertions(+), 125 deletions(-) create mode 100644 library/include/larra/encryption.hpp create mode 100644 library/src/encryption.cpp diff --git a/library/include/larra/client/client.hpp b/library/include/larra/client/client.hpp index 1eca4ee..e49c9db 100644 --- a/library/include/larra/client/client.hpp +++ b/library/include/larra/client/client.hpp @@ -1,13 +1,4 @@ #pragma once -#include -#include -#include -#include - -#include -#include -#include -#include #include #include #include @@ -17,10 +8,10 @@ #include #include #include +#include #include #include #include -#include #include namespace larra::xmpp { @@ -51,76 +42,6 @@ struct ServerRequiresStartTls : std::exception { namespace impl { -auto DecodeBase64(std::string_view val) -> std::string { - using namespace boost::archive::iterators; // NOLINT - using It = transform_width, 8, 6>; // NOLINT - return boost::algorithm::trim_right_copy_if(std::string(It(std::begin(val)), It(std::end(val))), [](char c) { - return c == '\0'; - }); -} - -auto EncodeBase64(std::string_view val) -> std::string { - using namespace boost::archive::iterators; // NOLINT - using It = base64_from_binary>; // NOLINT - auto tmp = std::string(It(std::begin(val)), It(std::end(val))); - return tmp.append((3 - val.size() % 3) % 3, '='); -} - -struct CharTrait { - using char_type = unsigned char; - static constexpr auto assign(char_type& c1, const char_type& c2) -> void { - c1 = c2; - } - static constexpr auto assign(char_type* c1, std::size_t n, const char_type c2) -> char_type* { - std::ranges::fill_n(c1, static_cast>(n), c2); - return c1; - } - static constexpr auto copy(char_type* c1, const char_type* c2, std::size_t n) -> char_type* { - std::ranges::copy_n(c2, static_cast>(n), c1); - return c1; - } -}; - -inline auto GenerateNonce(std::size_t length = 24) -> std::string { // NOLINT - constexpr std::string_view characters = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789"; - - std::random_device rd; - std::mt19937 generator(rd()); - std::uniform_int_distribution<> distribution(0, characters.size() - 1); - - std::ostringstream nonceStream; - for(size_t i = 0; i < length; ++i) { - nonceStream << characters[distribution(generator)]; - } - - return std::move(nonceStream.str()); -} - -template // NOLINT -inline auto Pbkdf2(std::string_view password, - std::basic_string_view salt, - int iterations) -> std::array { - std::array key; - PKCS5_PBKDF2_HMAC(password.data(), password.length(), salt.data(), salt.size(), iterations, F(), 64, key.data()); // NOLINT - return key; -} -template -inline auto SHA(std::basic_string_view input) -> std::array { - std::array response; - F(input.data(), input.size(), response.data()); - return response; -} - -template -inline auto HMAC(std::string_view key, - std::basic_string_view message) -> std::basic_string { - unsigned char* result = HMAC(F(), key.data(), static_cast(key.size()), message.data(), message.size(), nullptr, nullptr); - return {result, Size}; -} - inline auto StartStream(const BareJid& from, auto& connection) -> boost::asio::awaitable { auto stream = UserStream{}.To(from.server).From(std::move(from)).Version("1.0").XmlLang("en"); auto buffer = "" + ToString(stream); @@ -144,44 +65,6 @@ inline auto ToInt(std::string_view input) -> std::optional { return result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range ? std::nullopt : std::optional{out}; } -auto ToCharStringView(std::ranges::range auto& str) -> std::string_view { - return {new(&*str.begin()) char[str.size()], str.size()}; -} - -inline auto ToUnsignedCharStringView(std::string& str) -> std::basic_string_view { - return {new(str.data()) unsigned char[str.size()], str.size()}; -} - -auto Xor(auto str1, auto str2) -> auto { - if(str1.length() != str2.length()) { - throw std::invalid_argument("Strings must be of equal length for XOR."); - } - return std::views::iota(std::size_t{}, str1.size()) | std::views::transform([str1 = std::move(str1), str2 = std::move(str2)](auto i) { - return str1[i] ^ str2[i]; - }); -} - -template -auto GenerateAuthScramMessage(std::string_view password, - std::string salt, - std::string_view serverNonce, - std::string_view firstServerMessage, - std::string_view initialMessage, - int iterations) -> std::string { - auto clientFinalMessageBare = std::format("c=biws,r={}", serverNonce); - auto saltedPassword = Pbkdf2(password, ToUnsignedCharStringView(salt), iterations); - std::string clientKeyStr = "Client Key"; // NOLINT - auto clientKey = HMAC(ToCharStringView(saltedPassword), ToUnsignedCharStringView(clientKeyStr)); - auto storedKey = SHA(clientKey); - auto authMessage = std::format("{},{},{}", initialMessage, firstServerMessage, clientFinalMessageBare); - auto clientSignature = HMAC(ToCharStringView(storedKey), ToUnsignedCharStringView(authMessage)); - auto clientProof = Xor(clientKey, clientSignature) | std::ranges::to(); - std::string serverKeyStr = "Server Key"; - auto serverKey = HMAC(ToCharStringView(saltedPassword), ToUnsignedCharStringView(serverKeyStr)); - auto serverSignature = HMAC(ToCharStringView(serverKey), ToUnsignedCharStringView(authMessage)); - return std::format("{},p={}", clientFinalMessageBare, EncodeBase64(ToCharStringView(clientProof))); -} - inline auto ParseChallenge(std::string_view str) { return std::views::split(str, ',') | std::views::transform([](auto param) { return std::string_view{param}; @@ -221,8 +104,10 @@ struct ClientCreateVisitor { co_await boost::asio::async_read_until(socket, boost::asio::dynamic_buffer(response), '>', boost::asio::use_awaitable); } - template - auto ScramAuth(std::string_view methodName, const EncryptionUserAccount& account, auto& socket) -> boost::asio::awaitable { + auto ScramAuth(std::string_view methodName, + const EncryptionUserAccount& account, + auto& socket, + auto tag) -> boost::asio::awaitable { pugi::xml_document doc; auto auth = doc.append_child("auth"); auth.append_attribute("xmlns") = "urn:ietf:params:xml:ns:xmpp-sasl"; @@ -252,8 +137,9 @@ struct ClientCreateVisitor { auto success = doc.append_child("response"); success.append_attribute("xmlns") = "urn:ietf:params:xml:ns:xmpp-sasl"; success.text().set( - EncodeBase64(GenerateAuthScramMessage( - account.password, DecodeBase64(params["s"]), serverNonce, decoded, initialMessage, ToInt(params["i"]).value())) + EncodeBase64( + GenerateScramAuthMessage( + account.password, DecodeBase64(params["s"]), serverNonce, decoded, initialMessage, ToInt(params["i"]).value(), tag)) .c_str()); std::ostringstream strstream2; doc.print(strstream2, @@ -277,13 +163,13 @@ struct ClientCreateVisitor { ServerToUserStream stream) -> boost::asio::awaitable { // NOLINTBEGIN if(std::ranges::contains(features.saslMechanisms.mechanisms, "SCRAM-SHA-512")) { - co_return co_await ScramAuth("SCRAM-SHA-512", account, socket); + co_return co_await ScramAuth("SCRAM-SHA-512", account, socket, sha512sum::EncryptionTag{}); } if(std::ranges::contains(features.saslMechanisms.mechanisms, "SCRAM-SHA-256")) { - co_return co_await ScramAuth("SCRAM-SHA-256", account, socket); + co_return co_await ScramAuth("SCRAM-SHA-256", account, socket, sha256sum::EncryptionTag{}); } if(std::ranges::contains(features.saslMechanisms.mechanisms, "SCRAM-SHA-1")) { - co_return co_await ScramAuth("SCRAM-SHA-1", account, socket); + co_return co_await ScramAuth("SCRAM-SHA-1", account, socket, sha1sum::EncryptionTag{}); } // NOLINTEND throw std::runtime_error("Server not support SCRAM SHA 1 or SCRAM SHA 256 or SCRAM SHA 512 auth"); diff --git a/library/include/larra/encryption.hpp b/library/include/larra/encryption.hpp new file mode 100644 index 0000000..4c87fb9 --- /dev/null +++ b/library/include/larra/encryption.hpp @@ -0,0 +1,98 @@ +#pragma once +#include +#include + +namespace larra::xmpp { + +constexpr std::size_t kSha512ResultSize = 64; + +constexpr std::size_t kSha256ResultSize = 32; + +constexpr std::size_t kSha1ResultSize = 20; + +struct UnsignedCharTrait { + using char_type = unsigned char; + static constexpr auto assign(char_type& c1, const char_type& c2) -> void { + c1 = c2; + } + static constexpr auto assign(char_type* c1, std::size_t n, const char_type c2) -> char_type* { + std::ranges::fill_n(c1, static_cast>(n), c2); + return c1; + } + static constexpr auto copy(char_type* c1, const char_type* c2, std::size_t n) -> char_type* { + std::ranges::copy_n(c2, static_cast>(n), c1); + return c1; + } +}; + +using UnsignedStringView = std::basic_string_view; + +using UnsignedString = std::basic_string; + +auto EncodeBase64(std::string_view) -> std::string; + +auto DecodeBase64(std::string_view) -> std::string; + +auto GenerateNonce(std::size_t length = 24) -> std::string; // NOLINT + +namespace sha512sum { + +struct EncryptionTag {}; + +auto Pbdkf2(std::string_view password, UnsignedStringView salt, int iterations, EncryptionTag = {}) -> UnsignedString; + +auto Hash(UnsignedStringView data, EncryptionTag = {}) -> UnsignedString; + +auto Hmac(std::string_view key, UnsignedStringView message, EncryptionTag = {}) -> UnsignedString; + +auto GenerateScramAuthMessage(std::string_view password, + std::string salt, + std::string_view serverNonce, + std::string_view firstServerMessage, + std::string_view initialMessage, + int iterations, + EncryptionTag = {}) -> std::string; + +} // namespace sha512sum + +namespace sha256sum { + +struct EncryptionTag {}; + +auto Pbdkf2(std::string_view password, UnsignedStringView salt, int iterations, EncryptionTag = {}) -> UnsignedString; + +auto Hash(UnsignedStringView data, EncryptionTag = {}) -> UnsignedString; + +auto Hmac(std::string_view key, UnsignedStringView message, EncryptionTag = {}) -> UnsignedString; + +auto GenerateScramAuthMessage(std::string_view password, + std::string salt, + std::string_view serverNonce, + std::string_view firstServerMessage, + std::string_view initialMessage, + int iterations, + EncryptionTag = {}) -> std::string; + +} // namespace sha256sum + +namespace sha1sum { + +struct EncryptionTag {}; + +auto Pbdkf2(std::string_view password, UnsignedStringView salt, int iterations, EncryptionTag = {}) -> UnsignedString; + +auto Hash(UnsignedStringView data, EncryptionTag = {}) -> UnsignedString; + +auto Hmac(std::string_view key, UnsignedStringView message, EncryptionTag = {}) -> UnsignedString; + +auto GenerateScramAuthMessage(std::string_view password, + std::string salt, + std::string_view serverNonce, + std::string_view firstServerMessage, + std::string_view initialMessage, + int iterations, + EncryptionTag = {}) -> std::string; + +} // namespace sha1sum + +} // namespace larra::xmpp diff --git a/library/src/encryption.cpp b/library/src/encryption.cpp new file mode 100644 index 0000000..fd06cd0 --- /dev/null +++ b/library/src/encryption.cpp @@ -0,0 +1,228 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace larra::xmpp { + +auto EncodeBase64(std::string_view val) -> std::string { + using namespace boost::archive::iterators; // NOLINT + using It = base64_from_binary>; // NOLINT + auto tmp = std::string(It(std::begin(val)), It(std::end(val))); + return tmp.append((3 - val.size() % 3) % 3, '='); +} + +auto DecodeBase64(std::string_view val) -> std::string { + using namespace boost::archive::iterators; // NOLINT + using It = transform_width, 8, 6>; // NOLINT + return boost::algorithm::trim_right_copy_if(std::string(It(std::begin(val)), It(std::end(val))), [](char c) { + return c == '\0'; + }); +} + +auto GenerateNonce(std::size_t length) -> std::string { // NOLINT + constexpr std::string_view characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789"; + + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution<> distribution(0, characters.size() - 1); + + return std::views::iota(std::size_t{}, length) | std::views::transform([&](...) { + return characters[distribution(generator)]; + }) | + std::ranges::to(); +} + +template +struct DataHolder { + A first; + B second; + C third; +}; + +consteval auto GetDataForTag(sha512sum::EncryptionTag) { + return DataHolder{[] { + return EVP_sha512(); + }, + kSha512ResultSize, + [](auto... args) { + return SHA512(args...); + }}; +} + +consteval auto GetDataForTag(sha256sum::EncryptionTag) { + return DataHolder{[] { + return EVP_sha256(); + }, + kSha256ResultSize, + [](auto... args) { + return SHA256(args...); + }}; +} + +consteval auto GetDataForTag(sha1sum::EncryptionTag) { + return DataHolder{[] { + return EVP_sha256(); + }, + kSha1ResultSize, + [](auto... args) { + return SHA1(args...); + }}; +} + +template +inline auto HashImpl(UnsignedStringView input, TagType) -> UnsignedString { + constexpr auto Size = GetDataForTag(TagType{}).second; + constexpr auto F = GetDataForTag(TagType{}).third; + UnsignedString response; + response.resize(Size); + F(input.data(), input.size(), response.data()); + return response; +} + +template +inline auto HmacImpl(std::string_view key, UnsignedStringView message, TagType) -> UnsignedString { + constexpr auto Size = GetDataForTag(TagType{}).second; + constexpr auto F = GetDataForTag(TagType{}).first; + UnsignedString result; + result.resize(Size); + unsigned int result_len{}; + HMAC(F(), key.data(), static_cast(key.size()), message.data(), message.size(), result.data(), &result_len); + result.resize(result_len); + return result; +} + +template +inline auto Pbkdf2Impl(std::string_view password, UnsignedStringView salt, int iterations, TagType) -> UnsignedString { + constexpr auto Size = GetDataForTag(TagType{}).second; + constexpr auto F = GetDataForTag(TagType{}).first; + UnsignedString response; + response.resize(Size); + PKCS5_PBKDF2_HMAC(password.data(), password.length(), salt.data(), salt.size(), iterations, F(), Size, response.data()); // NOLINT + return response; +} + +inline auto ToCharStringView(std::ranges::range auto& str) -> std::string_view { + return {new(&*str.begin()) char[str.size()], str.size()}; +} + +inline auto ToUnsignedCharStringView(std::ranges::range auto& str) -> UnsignedStringView { + return {new(str.data()) unsigned char[str.size()], str.size()}; +} + +template +inline auto GenerateAuthScramMessageImpl(std::string_view password, + std::string salt, + std::string_view serverNonce, + std::string_view firstServerMessage, + std::string_view initialMessage, + int iterations, + TagType tag) -> std::string { + auto clientFinalMessageBare = std::format("c=biws,r={}", serverNonce); + auto saltedPassword = Pbkdf2Impl(password, ToUnsignedCharStringView(salt), iterations, tag); + std::string clientKeyStr = "Client Key"; // NOLINT + auto clientKey = HmacImpl(ToCharStringView(saltedPassword), ToUnsignedCharStringView(clientKeyStr), tag); + auto storedKey = HashImpl(clientKey, tag); + auto authMessage = std::format("{},{},{}", initialMessage, firstServerMessage, clientFinalMessageBare); + auto clientSignature = HmacImpl(ToCharStringView(storedKey), ToUnsignedCharStringView(authMessage), tag); + auto clientProof = std::views::iota(std::size_t{}, clientKey.size()) | // No std::views::enumerate in libc++ + std::views::transform([&](auto i) { + return clientKey[i] ^ clientSignature[i]; + }) | + std::ranges::to(); + std::string serverKeyStr = "Server Key"; + auto serverKey = HmacImpl(ToCharStringView(saltedPassword), ToUnsignedCharStringView(serverKeyStr), tag); + auto serverSignature = HmacImpl(ToCharStringView(serverKey), ToUnsignedCharStringView(authMessage), tag); + return std::format("{},p={}", clientFinalMessageBare, EncodeBase64(ToCharStringView(clientProof))); +} + +namespace sha512sum { + +auto Pbdkf2(std::string_view password, UnsignedStringView salt, int iterations, EncryptionTag tag) -> UnsignedString { + return Pbkdf2Impl(password, salt, iterations, tag); +} + +auto Hash(UnsignedStringView data, EncryptionTag tag) -> UnsignedString { + return HashImpl(data, tag); +} + +auto Hmac(std::string_view key, UnsignedStringView message, EncryptionTag tag) -> UnsignedString { + return HmacImpl(key, message, tag); +} + +auto GenerateScramAuthMessage(std::string_view password, + std::string salt, + std::string_view serverNonce, + std::string_view firstServerMessage, + std::string_view initialMessage, + int iterations, + EncryptionTag tag) -> std::string { + return GenerateAuthScramMessageImpl(password, std::move(salt), serverNonce, firstServerMessage, initialMessage, iterations, tag); +} + +} // namespace sha512sum + +namespace sha256sum { + +auto Pbdkf2(std::string_view password, UnsignedStringView salt, int iterations, EncryptionTag tag) -> UnsignedString { + return Pbkdf2Impl(password, salt, iterations, tag); +} + +auto Hash(UnsignedStringView data, EncryptionTag tag) -> UnsignedString { + return HashImpl(data, tag); +} + +auto Hmac(std::string_view key, UnsignedStringView message, EncryptionTag tag) -> UnsignedString { + return HmacImpl(key, message, tag); +} + +auto GenerateScramAuthMessage(std::string_view password, + std::string salt, + std::string_view serverNonce, + std::string_view firstServerMessage, + std::string_view initialMessage, + int iterations, + EncryptionTag tag) -> std::string { + return GenerateAuthScramMessageImpl(password, std::move(salt), serverNonce, firstServerMessage, initialMessage, iterations, tag); +} + +} // namespace sha256sum + +namespace sha1sum { + +auto Pbdkf2(std::string_view password, UnsignedStringView salt, int iterations, EncryptionTag tag) -> UnsignedString { + return Pbkdf2Impl(password, salt, iterations, tag); +} + +auto Hash(UnsignedStringView data, EncryptionTag tag) -> UnsignedString { + return HashImpl(data, tag); +} + +auto Hmac(std::string_view key, UnsignedStringView message, EncryptionTag tag) -> UnsignedString { + return HmacImpl(key, message, tag); +} + +auto GenerateScramAuthMessage(std::string_view password, + std::string salt, + std::string_view serverNonce, + std::string_view firstServerMessage, + std::string_view initialMessage, + int iterations, + EncryptionTag tag) -> std::string { + return GenerateAuthScramMessageImpl(password, std::move(salt), serverNonce, firstServerMessage, initialMessage, iterations, tag); +} + +} // namespace sha1sum + +} // namespace larra::xmpp