Move all encryption to separate file

This commit is contained in:
sha512sum 2024-09-14 16:38:45 +00:00
parent 3b78412da4
commit 4060a42634
3 changed files with 337 additions and 125 deletions

View file

@ -1,13 +1,4 @@
#pragma once #pragma once
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <boost/algorithm/string.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/asio/awaitable.hpp> #include <boost/asio/awaitable.hpp>
#include <boost/asio/connect.hpp> #include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
@ -17,10 +8,10 @@
#include <boost/asio/use_awaitable.hpp> #include <boost/asio/use_awaitable.hpp>
#include <charconv> #include <charconv>
#include <larra/client/options.hpp> #include <larra/client/options.hpp>
#include <larra/encryption.hpp>
#include <larra/features.hpp> #include <larra/features.hpp>
#include <larra/stream.hpp> #include <larra/stream.hpp>
#include <larra/user_account.hpp> #include <larra/user_account.hpp>
#include <random>
#include <ranges> #include <ranges>
namespace larra::xmpp { namespace larra::xmpp {
@ -51,76 +42,6 @@ struct ServerRequiresStartTls : std::exception {
namespace impl { namespace impl {
auto DecodeBase64(std::string_view val) -> std::string {
using namespace boost::archive::iterators; // NOLINT
using It = transform_width<binary_from_base64<std::string_view::const_iterator>, 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<transform_width<std::string_view::const_iterator, 6, 8>>; // 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<std::iter_difference_t<unsigned char>>(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<std::iter_difference_t<unsigned char>>(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 <auto F = EVP_sha512, std::size_t Size = 64> // NOLINT
inline auto Pbkdf2(std::string_view password,
std::basic_string_view<unsigned char, CharTrait> salt,
int iterations) -> std::array<unsigned char, Size> {
std::array<unsigned char, Size> key;
PKCS5_PBKDF2_HMAC(password.data(), password.length(), salt.data(), salt.size(), iterations, F(), 64, key.data()); // NOLINT
return key;
}
template <auto F = SHA512, std::size_t Size = 64>
inline auto SHA(std::basic_string_view<unsigned char, CharTrait> input) -> std::array<unsigned char, Size> {
std::array<unsigned char, Size> response;
F(input.data(), input.size(), response.data());
return response;
}
template <auto F = EVP_sha512, std::size_t Size = 64>
inline auto HMAC(std::string_view key,
std::basic_string_view<unsigned char, CharTrait> message) -> std::basic_string<unsigned char, CharTrait> {
unsigned char* result = HMAC(F(), key.data(), static_cast<int>(key.size()), message.data(), message.size(), nullptr, nullptr);
return {result, Size};
}
inline auto StartStream(const BareJid& from, auto& connection) -> boost::asio::awaitable<void> { inline auto StartStream(const BareJid& from, auto& connection) -> boost::asio::awaitable<void> {
auto stream = UserStream{}.To(from.server).From(std::move(from)).Version("1.0").XmlLang("en"); auto stream = UserStream{}.To(from.server).From(std::move(from)).Version("1.0").XmlLang("en");
auto buffer = "<?xml version='1.0'?>" + ToString(stream); auto buffer = "<?xml version='1.0'?>" + ToString(stream);
@ -144,44 +65,6 @@ inline auto ToInt(std::string_view input) -> std::optional<int> {
return result.ec == std::errc::invalid_argument || result.ec == std::errc::result_out_of_range ? std::nullopt : std::optional{out}; 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<unsigned char, CharTrait> {
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 F = EVP_sha512, auto F2 = SHA512, std::size_t Size = 64>
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<F, Size>(password, ToUnsignedCharStringView(salt), iterations);
std::string clientKeyStr = "Client Key"; // NOLINT
auto clientKey = HMAC<F, Size>(ToCharStringView(saltedPassword), ToUnsignedCharStringView(clientKeyStr));
auto storedKey = SHA<F2, Size>(clientKey);
auto authMessage = std::format("{},{},{}", initialMessage, firstServerMessage, clientFinalMessageBare);
auto clientSignature = HMAC<F, Size>(ToCharStringView(storedKey), ToUnsignedCharStringView(authMessage));
auto clientProof = Xor(clientKey, clientSignature) | std::ranges::to<std::string>();
std::string serverKeyStr = "Server Key";
auto serverKey = HMAC<F, Size>(ToCharStringView(saltedPassword), ToUnsignedCharStringView(serverKeyStr));
auto serverSignature = HMAC<F, Size>(ToCharStringView(serverKey), ToUnsignedCharStringView(authMessage));
return std::format("{},p={}", clientFinalMessageBare, EncodeBase64(ToCharStringView(clientProof)));
}
inline auto ParseChallenge(std::string_view str) { inline auto ParseChallenge(std::string_view str) {
return std::views::split(str, ',') | std::views::transform([](auto param) { return std::views::split(str, ',') | std::views::transform([](auto param) {
return std::string_view{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); co_await boost::asio::async_read_until(socket, boost::asio::dynamic_buffer(response), '>', boost::asio::use_awaitable);
} }
template <auto F, auto F2, std::size_t Size> auto ScramAuth(std::string_view methodName,
auto ScramAuth(std::string_view methodName, const EncryptionUserAccount& account, auto& socket) -> boost::asio::awaitable<void> { const EncryptionUserAccount& account,
auto& socket,
auto tag) -> boost::asio::awaitable<void> {
pugi::xml_document doc; pugi::xml_document doc;
auto auth = doc.append_child("auth"); auto auth = doc.append_child("auth");
auth.append_attribute("xmlns") = "urn:ietf:params:xml:ns:xmpp-sasl"; auth.append_attribute("xmlns") = "urn:ietf:params:xml:ns:xmpp-sasl";
@ -252,8 +137,9 @@ struct ClientCreateVisitor {
auto success = doc.append_child("response"); auto success = doc.append_child("response");
success.append_attribute("xmlns") = "urn:ietf:params:xml:ns:xmpp-sasl"; success.append_attribute("xmlns") = "urn:ietf:params:xml:ns:xmpp-sasl";
success.text().set( success.text().set(
EncodeBase64(GenerateAuthScramMessage<F, F2, Size>( EncodeBase64(
account.password, DecodeBase64(params["s"]), serverNonce, decoded, initialMessage, ToInt(params["i"]).value())) GenerateScramAuthMessage(
account.password, DecodeBase64(params["s"]), serverNonce, decoded, initialMessage, ToInt(params["i"]).value(), tag))
.c_str()); .c_str());
std::ostringstream strstream2; std::ostringstream strstream2;
doc.print(strstream2, doc.print(strstream2,
@ -277,13 +163,13 @@ struct ClientCreateVisitor {
ServerToUserStream stream) -> boost::asio::awaitable<void> { ServerToUserStream stream) -> boost::asio::awaitable<void> {
// NOLINTBEGIN // NOLINTBEGIN
if(std::ranges::contains(features.saslMechanisms.mechanisms, "SCRAM-SHA-512")) { if(std::ranges::contains(features.saslMechanisms.mechanisms, "SCRAM-SHA-512")) {
co_return co_await ScramAuth<EVP_sha512, SHA512, 64>("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")) { if(std::ranges::contains(features.saslMechanisms.mechanisms, "SCRAM-SHA-256")) {
co_return co_await ScramAuth<EVP_sha256, SHA256, 32>("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")) { if(std::ranges::contains(features.saslMechanisms.mechanisms, "SCRAM-SHA-1")) {
co_return co_await ScramAuth<EVP_sha1, SHA1, 20>("SCRAM-SHA-1", account, socket); co_return co_await ScramAuth("SCRAM-SHA-1", account, socket, sha1sum::EncryptionTag{});
} }
// NOLINTEND // NOLINTEND
throw std::runtime_error("Server not support SCRAM SHA 1 or SCRAM SHA 256 or SCRAM SHA 512 auth"); throw std::runtime_error("Server not support SCRAM SHA 1 or SCRAM SHA 256 or SCRAM SHA 512 auth");

View file

@ -0,0 +1,98 @@
#pragma once
#include <algorithm>
#include <string>
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<std::iter_difference_t<unsigned char>>(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<std::iter_difference_t<unsigned char>>(n), c1);
return c1;
}
};
using UnsignedStringView = std::basic_string_view<unsigned char, UnsignedCharTrait>;
using UnsignedString = std::basic_string<unsigned char, UnsignedCharTrait>;
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

228
library/src/encryption.cpp Normal file
View file

@ -0,0 +1,228 @@
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <boost/algorithm/string.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <larra/encryption.hpp>
#include <random>
#include <ranges>
namespace larra::xmpp {
auto EncodeBase64(std::string_view val) -> std::string {
using namespace boost::archive::iterators; // NOLINT
using It = base64_from_binary<transform_width<std::string_view::const_iterator, 6, 8>>; // 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<binary_from_base64<std::string_view::const_iterator>, 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<std::string>();
}
template <typename A, typename B, typename C>
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 <typename TagType>
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 <typename TagType>
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<int>(key.size()), message.data(), message.size(), result.data(), &result_len);
result.resize(result_len);
return result;
}
template <typename TagType>
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 <typename TagType>
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>();
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