diff --git a/CMakeLists.txt b/CMakeLists.txt index 71058f033724ffa06175ef8723c507dc7087046d..945b4478621f159441a1eaa1b52977c53edffa0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,10 +106,11 @@ set_package_properties(Boost PROPERTIES TYPE REQUIRED ) -add_library(matrix_client +add_library(matrix_client SHARED lib/http/client.cpp lib/http/session.cpp lib/crypto/client.cpp + lib/crypto/utils.cpp lib/utils.cpp lib/structs/common.cpp lib/structs/errors.cpp diff --git a/include/mtxclient/crypto/types.hpp b/include/mtxclient/crypto/types.hpp index 675e2b1acbad65a40b24c2fc204a949956bab769..52f52cb2131d536eda591134b22f4699263fb005 100644 --- a/include/mtxclient/crypto/types.hpp +++ b/include/mtxclient/crypto/types.hpp @@ -80,13 +80,18 @@ from_json(const nlohmann::json &obj, ExportedSession &s) inline void to_json(nlohmann::json &obj, const ExportedSessionKeys &keys) { - obj["sessions"] = keys.sessions; + obj = keys.sessions; } inline void from_json(const nlohmann::json &obj, ExportedSessionKeys &keys) { - keys.sessions = obj.at("sessions").get<std::vector<ExportedSession>>(); + try { + keys.sessions = obj.get<std::vector<ExportedSession>>(); + // might be the old format. + } catch (const nlohmann::json::exception&) { + keys.sessions = obj.at("sessions").get<std::vector<ExportedSession>>(); + } } inline void diff --git a/include/mtxclient/crypto/utils.hpp b/include/mtxclient/crypto/utils.hpp index fb9c374485585cfa6a8ded316e25336418deb4a5..257e97b180772790ed758dc0791ba2e7d5fc9a8d 100644 --- a/include/mtxclient/crypto/utils.hpp +++ b/include/mtxclient/crypto/utils.hpp @@ -3,12 +3,14 @@ #include <string> #include <vector> +#include <algorithm> #include <openssl/aes.h> #include <openssl/evp.h> #include <openssl/hmac.h> #include <openssl/sha.h> +#include <boost/algorithm/string.hpp> namespace mtx { namespace crypto { @@ -16,13 +18,28 @@ namespace crypto { //! Data representation used to interact with libolm. using BinaryBuf = std::vector<uint8_t>; +const std::string HEADER_LINE ( "-----BEGIN MEGOLM SESSION DATA-----"); +const std::string TRAILER_LINE ( "-----END MEGOLM SESSION DATA-----"); + //! Simple wrapper around the OpenSSL PKCS5_PBKDF2_HMAC function BinaryBuf PBKDF2_HMAC_SHA_512(const std::string pass, const BinaryBuf salt, uint32_t iterations); -BinaryBuf AES_CTR_256(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv); +BinaryBuf AES_CTR_256_Encrypt(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv); + +BinaryBuf AES_CTR_256_Decrypt(const std::string ciphertext, const BinaryBuf aes256Key, BinaryBuf iv); BinaryBuf HMAC_SHA256(const BinaryBuf hmacKey, const BinaryBuf data); +//! Translates the data back into the binary buffer, taking care +//! to remove the header and footer elements. +std::string unpack_key_file(const std::string &data); + +template<typename T> +void remove_substrs(std::basic_string<T>& s, + const std::basic_string<T>& p); + +void uint8_to_uint32(uint8_t b[4], uint32_t &u32); + void uint32_to_uint8 (uint8_t b[4], uint32_t u32); void print_binary_buf(const BinaryBuf buf) ; diff --git a/lib/crypto/client.cpp b/lib/crypto/client.cpp index fe3bd50d1378b439314d13a268e1e16a3427a804..70a58c621c5b15993e9c7d2446c31fac855ad7db 100644 --- a/lib/crypto/client.cpp +++ b/lib/crypto/client.cpp @@ -561,7 +561,7 @@ mtx::crypto::encrypt_exported_sessions(const mtx::crypto::ExportedSessionKeys &k BinaryBuf hmac256 = BinaryBuf(buf.begin() + 32, buf.begin() + (2 * 32)); - auto ciphertext = mtx::crypto::AES_CTR_256(plaintext, + auto ciphertext = mtx::crypto::AES_CTR_256_Encrypt(plaintext, aes256, nonce); @@ -594,31 +594,87 @@ mtx::crypto::encrypt_exported_sessions(const mtx::crypto::ExportedSessionKeys &k mtx::crypto::ExportedSessionKeys mtx::crypto::decrypt_exported_sessions(const std::string &data, std::string pass) { - std::cout << "decrypt_exported_sessions data: " << data << std::endl; - if (data.size() < - crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES + crypto_pwhash_SALTBYTES) - throw sodium_exception{"decrypt_exported_sessions", "ciphertext too small"}; + // Parse the data into a base64 string without the header and footer + std::string unpacked = mtx::crypto::unpack_key_file(data); - const auto nonce_start = data.begin(); - const auto nonce_end = nonce_start + crypto_secretbox_NONCEBYTES; - auto nonce = BinaryBuf(nonce_start, nonce_end); + std::string binary_str = base642bin(unpacked); - const auto salt_end = nonce_end + crypto_pwhash_SALTBYTES; - auto salt = BinaryBuf(nonce_end, salt_end); + const auto binary_start = binary_str.begin(); + const auto binary_end = binary_str.end(); - auto ciphertext = BinaryBuf(salt_end, data.end()); - auto decrypted = create_buffer(ciphertext.size() - crypto_secretbox_MACBYTES); + // Format version 0x01, 1 byte + const auto format_end = binary_start + 1; + auto format = BinaryBuf(binary_start, format_end); - auto key = derive_key(pass, salt); + // Salt, 16 bytes + const auto salt_end = format_end + crypto_pwhash_SALTBYTES; + auto salt = BinaryBuf(format_end, salt_end); - if (crypto_secretbox_open_easy(decrypted.data(), - reinterpret_cast<const unsigned char *>(ciphertext.data()), - ciphertext.size(), - nonce.data(), - reinterpret_cast<const unsigned char *>(key.data())) != 0) - throw sodium_exception{"crypto_secretbox_open_easy", "failed to decrypt"}; + // IV, 16 bytes + const auto iv_end = salt_end + AES_BLOCK_SIZE; + auto iv = BinaryBuf(salt_end, iv_end); - return json::parse(std::string(decrypted.begin(), decrypted.end())); + // Number of rounds, 4 bytes + const auto rounds_end = iv_end + sizeof(uint32_t); + auto rounds_buff = BinaryBuf(iv_end, rounds_end); + uint8_t rounds_arr[4]; + std::copy(rounds_buff.begin(), rounds_buff.end(), rounds_arr); + uint32_t rounds; + mtx::crypto::uint8_to_uint32(rounds_arr, rounds); + + // Variable-length JSON object... + const auto json_end = binary_end - SHA256_DIGEST_LENGTH; + auto json = BinaryBuf(rounds_end, json_end); + + // HMAC of the above, 32 bytes + auto hmac = BinaryBuf(json_end, binary_end); + + // derive the keys + auto buf = mtx::crypto::PBKDF2_HMAC_SHA_512(pass, + salt, + rounds); + + BinaryBuf aes256 = BinaryBuf(buf.begin(), buf.begin() + 32); + + BinaryBuf hmac256 = BinaryBuf(buf.begin() + 32, buf.begin() + (2 * 32)); + + // get hmac and verify they match + auto hmacSha256 = mtx::crypto::HMAC_SHA256(hmac256, BinaryBuf(binary_start, json_end)); + + if (hmacSha256 != hmac) { + throw sodium_exception{"decrypt_exported_sessions", "HMAC doesn't match"}; + } + + const std::string ciphertext(json.begin(), json.end()); + auto decrypted = mtx::crypto::AES_CTR_256_Decrypt(ciphertext, aes256, iv); + + // TODO: Move this to a helper function for the 'old' format that + // nheko enabled pre-e2e key spec. + // std::cout << "decrypt_exported_sessions data: " << data << std::endl; + // if (data.size() < + // crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES + crypto_pwhash_SALTBYTES) + // throw sodium_exception{"decrypt_exported_sessions", "ciphertext too small"}; + + // const auto nonce_start = data.begin(); + // const auto nonce_end = nonce_start + crypto_secretbox_NONCEBYTES; + // auto nonce = BinaryBuf(nonce_start, nonce_end); + + // const auto salt_end = nonce_end + crypto_pwhash_SALTBYTES; + // auto salt = BinaryBuf(nonce_end, salt_end); + + // auto ciphertext = BinaryBuf(salt_end, data.end()); + // auto decrypted = create_buffer(ciphertext.size() - crypto_secretbox_MACBYTES); + + // auto key = derive_key(pass, salt); + + // if (crypto_secretbox_open_easy(decrypted.data(), + // reinterpret_cast<const unsigned char *>(ciphertext.data()), + // ciphertext.size(), + // nonce.data(), + // reinterpret_cast<const unsigned char *>(key.data())) != 0) + // throw sodium_exception{"crypto_secretbox_open_easy", "failed to decrypt"}; + std::string plaintext(decrypted.begin(), decrypted.end()); + return json::parse(plaintext); } std::string diff --git a/lib/crypto/utils.cpp b/lib/crypto/utils.cpp index 8800a0fe8b287ec0f77aee04fefa707798f1adcf..254c07d2b789d130c5cb133ee18b0eb2bdb08979 100644 --- a/lib/crypto/utils.cpp +++ b/lib/crypto/utils.cpp @@ -16,14 +16,14 @@ PBKDF2_HMAC_SHA_512(const std::string pass, const BinaryBuf salt, uint32_t itera } BinaryBuf -AES_CTR_256(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv) { +AES_CTR_256_Encrypt(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv) { EVP_CIPHER_CTX *ctx; int len; int ciphertext_len; - unsigned char *cipher; + unsigned char *cipher = new unsigned char[plaintext.size()]; uint8_t *iv_data = iv.data(); // need to set bit 63 to 0 @@ -58,10 +58,85 @@ AES_CTR_256(const std::string plaintext, const BinaryBuf aes256Key, BinaryBuf iv /* Clean up */ EVP_CIPHER_CTX_free(ctx); - return BinaryBuf(reinterpret_cast<uint8_t *>(cipher), cipher + ciphertext_len); + BinaryBuf encrypted(reinterpret_cast<uint8_t *>(cipher), cipher + ciphertext_len); + delete [] cipher; + return encrypted; } +BinaryBuf +AES_CTR_256_Decrypt(const std::string ciphertext, const BinaryBuf aes256Key, BinaryBuf iv) +{ + EVP_CIPHER_CTX *ctx; + + int len; + + int plaintext_len; + + unsigned char *plaintext = new unsigned char[ciphertext.size()]; + + + /* Create and initialise the context */ + if (!(ctx = EVP_CIPHER_CTX_new())) { + // handleErrors(); + } + + /* Initialise the decryption operation. IMPORTANT - ensure you use a key + * and IV size appropriate for your cipher + * In this example we are using 256 bit AES (i.e. a 256 bit key). The + * IV size for *most* modes is the same as the block size. For AES this + * is 128 bits */ + if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_ctr(), NULL, aes256Key.data(), iv.data())) { + // handleErrors(); + } + + /* Provide the message to be decrypted, and obtain the plaintext output. + * EVP_DecryptUpdate can be called multiple times if necessary + */ + if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, reinterpret_cast<const unsigned char*>(&ciphertext.data()[0]), ciphertext.size())) { + // handleErrors(); + } + plaintext_len = len; + + /* Finalise the decryption. Further plaintext bytes may be written at + * this stage. + */ + if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) { + // handleErrors(); + } + plaintext_len += len; + + /* Clean up */ + EVP_CIPHER_CTX_free(ctx); + + BinaryBuf decrypted(reinterpret_cast<uint8_t *>(plaintext), plaintext + plaintext_len); + delete[] plaintext; + return decrypted; +} + +template<typename T> +void remove_substrs(std::basic_string<T>& s, + const std::basic_string<T>& p) { + auto n = p.length(); + + for (auto i = s.find(p); + i != std::basic_string<T>::npos; + i = s.find(p)) + s.erase(i, n); +} + +std::string +unpack_key_file(const std::string &data) { + std::string unpacked(data); + remove_substrs(unpacked, HEADER_LINE); + + remove_substrs(unpacked, TRAILER_LINE); + + remove_substrs(unpacked, std::string("\n")); + + return unpacked; +} + BinaryBuf HMAC_SHA256 (const BinaryBuf hmacKey, const BinaryBuf data) { unsigned int len = SHA256_DIGEST_LENGTH; @@ -79,6 +154,10 @@ print_binary_buf(const BinaryBuf buf) { std::cout << std::endl; } +void uint8_to_uint32(uint8_t b[4], uint32_t &u32) { + u32 = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; +} + void uint32_to_uint8 (uint8_t b[4], uint32_t u32) { b[3] = (uint8_t)u32; b[2] = (uint8_t)(u32>>=8);