diff --git a/include/session/session_encrypt.h b/include/session/session_encrypt.h index b91628e3..4b6399de 100644 --- a/include/session/session_encrypt.h +++ b/include/session/session_encrypt.h @@ -170,12 +170,11 @@ LIBSESSION_EXPORT bool session_decrypt_for_blinded_recipient( /// This function attempts to decrypt an ONS response. /// /// Inputs: -/// - `lowercase_name_in` -- [in] Pointer to a buffer containing the lowercase name used to trigger -/// the response. -/// - `name_len` -- [in] Length of `name_in`. +/// - `lowercase_name_in` -- [in] Pointer to a NULL-terminated buffer containing the lowercase name +/// used to trigger the response. /// - `ciphertext_in` -- [in] Pointer to a data buffer containing the encrypted data. /// - `ciphertext_len` -- [in] Length of `ciphertext_in`. -/// - `nonce_in` -- [in] Pointer to a data buffer containing the nonce (24 bytes). +/// - `nonce_in` -- [in, optional] Pointer to a data buffer containing the nonce (24 bytes) or NULL. /// - `session_id_out` -- [out] pointer to a buffer of at least 67 bytes where the null-terminated, /// hex-encoded session_id will be written if decryption was successful. /// @@ -183,10 +182,9 @@ LIBSESSION_EXPORT bool session_decrypt_for_blinded_recipient( /// - `bool` -- True if the session ID was successfully decrypted, false if decryption failed. LIBSESSION_EXPORT bool session_decrypt_ons_response( const char* lowercase_name_in, - size_t name_len, const unsigned char* ciphertext_in, size_t ciphertext_len, - const unsigned char* nonce_in, /* 24 bytes */ + const unsigned char* nonce_in, /* 24 bytes or NULL */ char* session_id_out /* 67 byte output buffer */); /// API: crypto/session_decrypt_push_notification diff --git a/include/session/session_encrypt.hpp b/include/session/session_encrypt.hpp index 6a185a73..e1381f7b 100644 --- a/include/session/session_encrypt.hpp +++ b/include/session/session_encrypt.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "types.hpp" // Helper functions for the "Session Protocol" encryption mechanism. This is the encryption used @@ -235,13 +237,15 @@ std::pair decrypt_from_blinded_recipient( /// Inputs: /// - `lowercase_name` -- the lowercase name which was looked to up to retrieve this response. /// - `ciphertext` -- ciphertext returned from the server. -/// - `nonce` -- the nonce returned from the server +/// - `nonce` -- the nonce returned from the server if provided. /// /// Outputs: /// - `std::string` -- the session ID (in hex) returned from the server, *if* the server returned /// a session ID. Throws on error/failure. std::string decrypt_ons_response( - std::string_view lowercase_name, ustring_view ciphertext, ustring_view nonce); + std::string_view lowercase_name, + ustring_view ciphertext, + std::optional nonce); /// API: crypto/decrypt_push_notification /// diff --git a/src/session_encrypt.cpp b/src/session_encrypt.cpp index fdb831f6..24ef71a3 100644 --- a/src/session_encrypt.cpp +++ b/src/session_encrypt.cpp @@ -6,8 +6,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -508,10 +510,44 @@ std::pair decrypt_from_blinded_recipient( } std::string decrypt_ons_response( - std::string_view lowercase_name, ustring_view ciphertext, ustring_view nonce) { + std::string_view lowercase_name, + ustring_view ciphertext, + std::optional nonce) { + // Handle old Argon2-based encryption used before HF16 + if (!nonce) { + if (ciphertext.size() < crypto_secretbox_MACBYTES) + throw std::invalid_argument{"Invalid ciphertext: expected to be greater than 16 bytes"}; + + uc32 key; + std::array salt = {0}; + + if (0 != crypto_pwhash( + key.data(), + key.size(), + lowercase_name.data(), + lowercase_name.size(), + salt.data(), + crypto_pwhash_OPSLIMIT_MODERATE, + crypto_pwhash_MEMLIMIT_MODERATE, + crypto_pwhash_ALG_ARGON2ID13)) + throw std::runtime_error{"Failed to generate key"}; + + ustring msg; + msg.resize(ciphertext.size() - crypto_secretbox_MACBYTES); + std::array nonce = {0}; + + if (0 != + crypto_secretbox_open_easy( + msg.data(), ciphertext.data(), ciphertext.size(), nonce.data(), key.data())) + throw std::runtime_error{"Failed to decrypt"}; + + std::string session_id = oxenc::to_hex(msg.begin(), msg.end()); + return session_id; + } + if (ciphertext.size() < crypto_aead_xchacha20poly1305_ietf_ABYTES) throw std::invalid_argument{"Invalid ciphertext: expected to be greater than 16 bytes"}; - if (nonce.size() != crypto_aead_xchacha20poly1305_ietf_NPUBBYTES) + if (nonce->size() != crypto_aead_xchacha20poly1305_ietf_NPUBBYTES) throw std::invalid_argument{"Invalid nonce: expected to be 24 bytes"}; // Hash the ONS name using BLAKE2b @@ -543,7 +579,7 @@ std::string decrypt_ons_response( ciphertext.size(), nullptr, 0, - nonce.data(), + nonce->data(), key.data())) throw std::runtime_error{"Failed to decrypt"}; @@ -718,16 +754,17 @@ LIBSESSION_C_API bool session_decrypt_for_blinded_recipient( LIBSESSION_C_API bool session_decrypt_ons_response( const char* name_in, - size_t name_len, const unsigned char* ciphertext_in, size_t ciphertext_len, const unsigned char* nonce_in, char* session_id_out) { try { + std::optional nonce; + if (nonce_in) + nonce = ustring{nonce_in, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES}; + auto session_id = session::decrypt_ons_response( - std::string_view{name_in, name_len}, - ustring_view{ciphertext_in, ciphertext_len}, - ustring_view{nonce_in, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES}); + name_in, ustring_view{ciphertext_in, ciphertext_len}, nonce); std::memcpy(session_id_out, session_id.c_str(), session_id.size() + 1); return true; diff --git a/tests/test_session_encrypt.cpp b/tests/test_session_encrypt.cpp index 8c286b69..2a0f6eca 100644 --- a/tests/test_session_encrypt.cpp +++ b/tests/test_session_encrypt.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -418,16 +419,40 @@ TEST_CASE("Session ONS response decryption", "[session-ons][decrypt]") { auto ciphertext = "3575802dd9bfea72672a208840f37ca289ceade5d3ffacabe2d231f109d204329fc33e28c33" "1580d9a8c9b8a64cacfec97"_hexbytes; + auto ciphertext_legacy = + "dbd4bc89bd2c9e5322fd9f4cadcaa66a0c38f15d0c927a86cc36e895fe1f3c532a3958d972563f52ca858e94eec22dc360"_hexbytes; auto nonce = "00112233445566778899aabbccddeeff00ffeeddccbbaa99"_hexbytes; - ustring sid_data = - "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"_hexbytes; CHECK(decrypt_ons_response(name, ciphertext, nonce) == "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); + CHECK(decrypt_ons_response(name, ciphertext_legacy, std::nullopt) == + "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"); CHECK_THROWS(decrypt_ons_response(name, to_unsigned_sv("invalid"), nonce)); CHECK_THROWS(decrypt_ons_response(name, ciphertext, to_unsigned_sv("invalid"))); } +TEST_CASE("Session ONS response decryption C API", "[session-ons][session_decrypt_ons_response]") { + using namespace session; + + auto name = "test\0"; + auto ciphertext = + "3575802dd9bfea72672a208840f37ca289ceade5d3ffacabe2d231f109d204329fc33e28c33" + "1580d9a8c9b8a64cacfec97"_hexbytes; + auto ciphertext_legacy = + "dbd4bc89bd2c9e5322fd9f4cadcaa66a0c38f15d0c927a86cc36e895fe1f3c532a3958d972563f52ca858e94eec22dc360"_hexbytes; + auto nonce = "00112233445566778899aabbccddeeff00ffeeddccbbaa99"_hexbytes; + + char ons1[67]; + CHECK(session_decrypt_ons_response( + name, ciphertext.data(), ciphertext.size(), nonce.data(), ons1)); + CHECK(ons1 == "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"sv); + + char ons2[67]; + CHECK(session_decrypt_ons_response( + name, ciphertext_legacy.data(), ciphertext_legacy.size(), nullptr, ons2)); + CHECK(ons2 == "05d2ad010eeb72d72e561d9de7bd7b6989af77dcabffa03a5111a6c859ae5c3a72"sv); +} + TEST_CASE("Session push notification decryption", "[session-notification][decrypt]") { using namespace session;