// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/PasswordManager.h" #include "td/telegram/ConfigManager.h" #include "td/telegram/DhCache.h" #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/net/NetQueryDispatcher.h" #include "td/telegram/SuggestedAction.h" #include "td/telegram/TdDb.h" #include "td/mtproto/DhHandshake.h" #include "td/utils/BigNum.h" #include "td/utils/buffer.h" #include "td/utils/crypto.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" #include "td/utils/SliceBuilder.h" #include "td/utils/Time.h" namespace td { tl_object_ptr TempPasswordState::get_temporary_password_state_object() const { if (!has_temp_password || valid_until <= G()->unix_time()) { return make_tl_object(false, 0); } return make_tl_object(true, valid_until - G()->unix_time_cached()); } static void hash_sha256(Slice data, Slice salt, MutableSlice dest) { sha256(PSLICE() << salt << data << salt, dest); } BufferSlice PasswordManager::calc_password_hash(Slice password, Slice client_salt, Slice server_salt) { LOG(INFO) << "Begin password hash calculation"; BufferSlice buf(32); hash_sha256(password, client_salt, buf.as_slice()); hash_sha256(buf.as_slice(), server_salt, buf.as_slice()); BufferSlice hash(64); pbkdf2_sha512(buf.as_slice(), client_salt, 100000, hash.as_slice()); hash_sha256(hash.as_slice(), server_salt, buf.as_slice()); LOG(INFO) << "End password hash calculation"; return buf; } Result PasswordManager::calc_password_srp_hash(Slice password, Slice client_salt, Slice server_salt, int32 g, Slice p) { LOG(INFO) << "Begin password SRP hash calculation"; TRY_STATUS(mtproto::DhHandshake::check_config(g, p, DhCache::instance())); auto hash = calc_password_hash(password, client_salt, server_salt); auto p_bn = BigNum::from_binary(p); BigNum g_bn; g_bn.set_value(g); auto x_bn = BigNum::from_binary(hash.as_slice()); BigNumContext ctx; BigNum v_bn; BigNum::mod_exp(v_bn, g_bn, x_bn, p_bn, ctx); BufferSlice result(v_bn.to_binary(256)); LOG(INFO) << "End password SRP hash calculation"; return std::move(result); } tl_object_ptr PasswordManager::get_input_check_password( Slice password, Slice client_salt, Slice server_salt, int32 g, Slice p, Slice B, int64 id) { if (password.empty()) { return make_tl_object(); } if (mtproto::DhHandshake::check_config(g, p, DhCache::instance()).is_error()) { LOG(ERROR) << "Receive invalid config " << g << " " << format::escaped(p); return make_tl_object(); } auto p_bn = BigNum::from_binary(p); auto B_bn = BigNum::from_binary(B); auto zero = BigNum::from_decimal("0").move_as_ok(); if (BigNum::compare(zero, B_bn) != -1 || BigNum::compare(B_bn, p_bn) != -1 || B.size() < 248 || B.size() > 256) { LOG(ERROR) << "Receive invalid value of B(" << B.size() << "): " << B_bn << " " << p_bn; return make_tl_object(); } LOG(INFO) << "Begin input password SRP hash calculation"; BigNum g_bn; g_bn.set_value(g); auto g_padded = g_bn.to_binary(256); auto x = calc_password_hash(password, client_salt, server_salt); auto x_bn = BigNum::from_binary(x.as_slice()); BufferSlice a(2048 / 8); Random::secure_bytes(a.as_slice()); auto a_bn = BigNum::from_binary(a.as_slice()); BigNumContext ctx; BigNum A_bn; BigNum::mod_exp(A_bn, g_bn, a_bn, p_bn, ctx); string A = A_bn.to_binary(256); string B_pad(256 - B.size(), '\0'); string u = sha256(PSLICE() << A << B_pad << B); auto u_bn = BigNum::from_binary(u); string k = sha256(PSLICE() << p << g_padded); auto k_bn = BigNum::from_binary(k); BigNum v_bn; BigNum::mod_exp(v_bn, g_bn, x_bn, p_bn, ctx); BigNum kv_bn; BigNum::mod_mul(kv_bn, k_bn, v_bn, p_bn, ctx); BigNum t_bn; BigNum::sub(t_bn, B_bn, kv_bn); if (BigNum::compare(t_bn, zero) == -1) { BigNum::add(t_bn, t_bn, p_bn); } BigNum exp_bn; BigNum::mul(exp_bn, u_bn, x_bn, ctx); BigNum::add(exp_bn, exp_bn, a_bn); BigNum S_bn; BigNum::mod_exp(S_bn, t_bn, exp_bn, p_bn, ctx); string S = S_bn.to_binary(256); auto K = sha256(S); auto h1 = sha256(p); auto h2 = sha256(g_padded); for (size_t i = 0; i < h1.size(); i++) { h1[i] = static_cast(static_cast(h1[i]) ^ static_cast(h2[i])); } auto M = sha256(PSLICE() << h1 << sha256(client_salt) << sha256(server_salt) << A << B_pad << B << K); LOG(INFO) << "End input password SRP hash calculation"; return make_tl_object(id, BufferSlice(A), BufferSlice(M)); } tl_object_ptr PasswordManager::get_input_check_password( Slice password, const PasswordState &state) { return get_input_check_password(password, state.current_client_salt, state.current_server_salt, state.current_srp_g, state.current_srp_p, state.current_srp_B, state.current_srp_id); } void PasswordManager::get_input_check_password_srp( string password, Promise> &&promise) { do_get_state(PromiseCreator::lambda( [promise = std::move(promise), password = std::move(password)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } promise.set_value(PasswordManager::get_input_check_password(password, r_state.move_as_ok())); })); } void PasswordManager::set_password(string current_password, string new_password, string new_hint, bool set_recovery_email_address, string recovery_email_address, Promise promise) { UpdateSettings update_settings; update_settings.current_password = std::move(current_password); update_settings.update_password = true; //update_settings.update_secure_secret = true; update_settings.new_password = std::move(new_password); update_settings.new_hint = std::move(new_hint); if (set_recovery_email_address) { update_settings.update_recovery_email_address = true; update_settings.recovery_email_address = std::move(recovery_email_address); } update_password_settings(std::move(update_settings), std::move(promise)); } void PasswordManager::set_recovery_email_address(string password, string new_recovery_email_address, Promise promise) { UpdateSettings update_settings; update_settings.current_password = std::move(password); update_settings.update_recovery_email_address = true; update_settings.recovery_email_address = std::move(new_recovery_email_address); update_password_settings(std::move(update_settings), std::move(promise)); } void PasswordManager::get_secure_secret(string password, Promise promise) { return do_get_secure_secret(true, std::move(password), std::move(promise)); } void PasswordManager::do_get_secure_secret(bool allow_recursive, string password, Promise promise) { if (secret_) { return promise.set_value(secret_.value().clone()); } if (password.empty()) { return promise.set_error(Status::Error(400, "PASSWORD_HASH_INVALID")); } get_full_state( password, PromiseCreator::lambda([password, allow_recursive, promise = std::move(promise), actor_id = actor_id(this)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } auto state = r_state.move_as_ok(); if (!state.state.has_password) { return promise.set_error(Status::Error(400, "2-step verification is disabled")); } if (state.private_state.secret) { send_closure(actor_id, &PasswordManager::cache_secret, state.private_state.secret.value().clone()); return promise.set_value(std::move(state.private_state.secret.value())); } if (!allow_recursive) { return promise.set_error(Status::Error(400, "Failed to get Telegram Passport secret")); } auto new_promise = PromiseCreator::lambda([password, promise = std::move(promise), actor_id](Result r_ok) mutable { if (r_ok.is_error()) { return promise.set_error(r_ok.move_as_error()); } send_closure(actor_id, &PasswordManager::do_get_secure_secret, false, std::move(password), std::move(promise)); }); UpdateSettings update_settings; update_settings.current_password = password; update_settings.update_secure_secret = true; send_closure(actor_id, &PasswordManager::do_update_password_settings, std::move(update_settings), std::move(state), std::move(new_promise)); })); } void PasswordManager::get_temp_password_state(Promise promise) /*const*/ { promise.set_value(temp_password_state_.get_temporary_password_state_object()); } TempPasswordState PasswordManager::get_temp_password_state_sync() { auto temp_password_str = G()->td_db()->get_binlog_pmc()->get("temp_password"); TempPasswordState res; auto status = log_event_parse(res, temp_password_str); if (status.is_error() || res.valid_until <= G()->unix_time()) { res = TempPasswordState(); } return res; } void PasswordManager::create_temp_password(string password, int32 timeout, Promise promise) { if (create_temp_password_promise_) { return promise.set_error(Status::Error(400, "Another create_temp_password query is active")); } create_temp_password_promise_ = std::move(promise); auto new_promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result result) { send_closure(actor_id, &PasswordManager::on_finish_create_temp_password, std::move(result), false); }); do_get_state(PromiseCreator::lambda([password = std::move(password), timeout, promise = std::move(new_promise), actor_id = actor_id(this)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } send_closure(actor_id, &PasswordManager::do_create_temp_password, std::move(password), timeout, r_state.move_as_ok(), std::move(promise)); })); } void PasswordManager::drop_temp_password() { G()->td_db()->get_binlog_pmc()->erase("temp_password"); temp_password_state_ = TempPasswordState(); } void PasswordManager::do_create_temp_password(string password, int32 timeout, PasswordState &&password_state, Promise promise) { auto hash = get_input_check_password(password, password_state); send_with_promise(G()->net_query_creator().create(telegram_api::account_getTmpPassword(std::move(hash), timeout)), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } auto result = r_result.move_as_ok(); TempPasswordState res; res.has_temp_password = true; res.temp_password = result->tmp_password_.as_slice().str(); res.valid_until = result->valid_until_; promise.set_value(std::move(res)); })); } void PasswordManager::on_finish_create_temp_password(Result result, bool /*dummy*/) { CHECK(create_temp_password_promise_); if (result.is_error()) { drop_temp_password(); return create_temp_password_promise_.set_error(result.move_as_error()); } temp_password_state_ = result.move_as_ok(); G()->td_db()->get_binlog_pmc()->set("temp_password", log_event_store(temp_password_state_).as_slice().str()); create_temp_password_promise_.set_value(temp_password_state_.get_temporary_password_state_object()); } void PasswordManager::get_full_state(string password, Promise promise) { send_closure(G()->config_manager(), &ConfigManager::hide_suggested_action, SuggestedAction{SuggestedAction::Type::CheckPassword}); do_get_state(PromiseCreator::lambda([password = std::move(password), promise = std::move(promise), actor_id = actor_id(this)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } send_closure(actor_id, &PasswordManager::do_get_full_state, std::move(password), r_state.move_as_ok(), std::move(promise)); })); } Result PasswordManager::decrypt_secure_secret( Slice password, tl_object_ptr algo_ptr, Slice secret, int64 secret_id) { TRY_RESULT(encrypted_secret, secure_storage::EncryptedSecret::create(secret)); CHECK(algo_ptr != nullptr); BufferSlice salt; secure_storage::EnryptionAlgorithm algorithm = secure_storage::EnryptionAlgorithm::Pbkdf2; switch (algo_ptr->get_id()) { case telegram_api::securePasswordKdfAlgoUnknown::ID: return Status::Error(400, "Unsupported algorithm"); case telegram_api::securePasswordKdfAlgoSHA512::ID: { auto algo = move_tl_object_as(algo_ptr); salt = std::move(algo->salt_); algorithm = secure_storage::EnryptionAlgorithm::Sha512; break; } case telegram_api::securePasswordKdfAlgoPBKDF2HMACSHA512iter100000::ID: { auto algo = move_tl_object_as(algo_ptr); salt = std::move(algo->salt_); break; } default: UNREACHABLE(); } TRY_RESULT(result, encrypted_secret.decrypt(password, salt.as_slice(), algorithm)); if (secret_id != result.get_hash()) { return Status::Error("Secret hash mismatch"); } return result; } void PasswordManager::do_get_full_state(string password, PasswordState state, Promise promise) { if (!state.has_password) { PasswordFullState result; result.state = std::move(state); return promise.set_value(std::move(result)); } auto hash = get_input_check_password(password, state); send_with_promise(G()->net_query_creator().create(telegram_api::account_getPasswordSettings(std::move(hash))), PromiseCreator::lambda([promise = std::move(promise), state = std::move(state), password](Result r_query) mutable { promise.set_result([&]() -> Result { TRY_RESULT(result, fetch_result(std::move(r_query))); LOG(INFO) << "Receive password settings: " << to_string(result); PasswordPrivateState private_state; private_state.email = std::move(result->email_); if (result->secure_settings_ != nullptr) { auto r_secret = decrypt_secure_secret(password, std::move(result->secure_settings_->secure_algo_), result->secure_settings_->secure_secret_.as_slice(), result->secure_settings_->secure_secret_id_); if (r_secret.is_ok()) { private_state.secret = r_secret.move_as_ok(); } } return PasswordFullState{std::move(state), std::move(private_state)}; }()); })); } void PasswordManager::get_recovery_email_address(string password, Promise> promise) { get_full_state( password, PromiseCreator::lambda([password, promise = std::move(promise)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } auto state = r_state.move_as_ok(); return promise.set_value(make_tl_object(state.private_state.email)); })); } void PasswordManager::check_recovery_email_address_code(string code, Promise promise) { auto query = G()->net_query_creator().create(telegram_api::account_confirmPasswordEmail(std::move(code))); send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error() && r_result.error().message() != "EMAIL_HASH_EXPIRED" && r_result.error().message() != "CODE_INVALID") { return promise.set_error(r_result.move_as_error()); } send_closure(actor_id, &PasswordManager::get_state, std::move(promise)); })); } void PasswordManager::resend_recovery_email_address_code(Promise promise) { auto query = G()->net_query_creator().create(telegram_api::account_resendPasswordEmail()); send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error() && r_result.error().message() != "EMAIL_HASH_EXPIRED") { return promise.set_error(r_result.move_as_error()); } send_closure(actor_id, &PasswordManager::get_state, std::move(promise)); })); } void PasswordManager::send_email_address_verification_code( string email, Promise> promise) { last_verified_email_address_ = email; auto query = G()->net_query_creator().create(telegram_api::account_sendVerifyEmailCode(std::move(email))); send_with_promise( std::move(query), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } auto result = r_result.move_as_ok(); if (result->length_ < 0 || result->length_ >= 100) { LOG(ERROR) << "Receive wrong code length " << result->length_; result->length_ = 0; } return promise.set_value( make_tl_object(result->email_pattern_, result->length_)); })); } void PasswordManager::resend_email_address_verification_code( Promise> promise) { if (last_verified_email_address_.empty()) { return promise.set_error(Status::Error(400, "No email address verification was sent")); } send_email_address_verification_code(last_verified_email_address_, std::move(promise)); } void PasswordManager::check_email_address_verification_code(string code, Promise promise) { if (last_verified_email_address_.empty()) { return promise.set_error(Status::Error(400, "No email address verification was sent")); } auto query = G()->net_query_creator().create(telegram_api::account_verifyEmail(last_verified_email_address_, std::move(code))); send_with_promise(std::move(query), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } return promise.set_value(Unit()); })); } void PasswordManager::request_password_recovery( Promise> promise) { // is called only after authorization send_with_promise( G()->net_query_creator().create(telegram_api::auth_requestPasswordRecovery()), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } auto result = r_result.move_as_ok(); return promise.set_value(make_tl_object(result->email_pattern_, 0)); })); } void PasswordManager::check_password_recovery_code(string code, Promise promise) { // is called only after authorization send_with_promise(G()->net_query_creator().create(telegram_api::auth_checkRecoveryPassword(code)), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } if (!r_result.ok()) { return promise.set_error(Status::Error(400, "Invalid recovery code")); } return promise.set_value(Unit()); })); } void PasswordManager::recover_password(string code, string new_password, string new_hint, Promise promise) { // is called only after authorization if (new_password.empty()) { return do_recover_password(std::move(code), nullptr, std::move(promise)); } UpdateSettings update_settings; update_settings.update_password = true; update_settings.new_password = std::move(new_password); update_settings.new_hint = std::move(new_hint); do_get_state(PromiseCreator::lambda([actor_id = actor_id(this), code = std::move(code), update_settings = std::move(update_settings), promise = std::move(promise)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } TRY_RESULT_PROMISE( promise, new_settings, get_password_input_settings(update_settings, r_state.ok().has_password, r_state.ok().new_state, nullptr)); send_closure(actor_id, &PasswordManager::do_recover_password, std::move(code), std::move(new_settings), std::move(promise)); })); } void PasswordManager::do_recover_password(string code, PasswordInputSettings &&new_settings, Promise &&promise) { int32 flags = 0; if (new_settings != nullptr) { flags |= telegram_api::auth_recoverPassword::NEW_SETTINGS_MASK; } send_with_promise(G()->net_query_creator().create( telegram_api::auth_recoverPassword(flags, std::move(code), std::move(new_settings))), PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } send_closure(actor_id, &PasswordManager::get_state, std::move(promise)); })); } void PasswordManager::reset_password(Promise promise) { send_with_promise( G()->net_query_creator().create(telegram_api::account_resetPassword()), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } auto result_ptr = r_result.move_as_ok(); switch (result_ptr->get_id()) { case telegram_api::account_resetPasswordOk::ID: return promise.set_value(td_api::make_object()); case telegram_api::account_resetPasswordRequestedWait::ID: { auto result = move_tl_object_as(result_ptr); return promise.set_value(td_api::make_object(result->until_date_)); } case telegram_api::account_resetPasswordFailedWait::ID: { auto result = move_tl_object_as(result_ptr); return promise.set_value(td_api::make_object(result->retry_date_)); } default: UNREACHABLE(); break; } })); } void PasswordManager::cancel_password_reset(Promise promise) { send_with_promise(G()->net_query_creator().create(telegram_api::account_declinePasswordReset()), PromiseCreator::lambda([promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error() && r_result.error().message() != "RESET_REQUEST_MISSING") { return promise.set_error(r_result.move_as_error()); } return promise.set_value(Unit()); })); } void PasswordManager::update_password_settings(UpdateSettings update_settings, Promise promise) { auto result_promise = PromiseCreator::lambda( [actor_id = actor_id(this), promise = std::move(promise)](Result r_update_settings) mutable { if (r_update_settings.is_error()) { return promise.set_error(r_update_settings.move_as_error()); } if (!r_update_settings.ok()) { return promise.set_error(Status::Error(400, "account_updatePasswordSettings returned false")); } send_closure(actor_id, &PasswordManager::get_state, std::move(promise)); }); auto password = update_settings.current_password; get_full_state( std::move(password), PromiseCreator::lambda([actor_id = actor_id(this), result_promise = std::move(result_promise), update_settings = std::move(update_settings)](Result r_state) mutable { if (r_state.is_error()) { return result_promise.set_error(r_state.move_as_error()); } send_closure(actor_id, &PasswordManager::do_update_password_settings, std::move(update_settings), r_state.move_as_ok(), std::move(result_promise)); })); } static BufferSlice create_salt(Slice salt_prefix) { static constexpr size_t ADDED_SALT_SIZE = 32; BufferSlice new_salt(salt_prefix.size() + ADDED_SALT_SIZE); new_salt.as_slice().copy_from(salt_prefix); Random::secure_bytes(new_salt.as_slice().substr(salt_prefix.size())); return new_salt; } void PasswordManager::do_update_password_settings(UpdateSettings update_settings, PasswordFullState full_state, Promise promise) { // PasswordState has already been used to get PasswordPrivateState and need to be reget do_get_state(PromiseCreator::lambda([actor_id = actor_id(this), update_settings = std::move(update_settings), private_state = std::move(full_state.private_state), promise = std::move(promise)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } send_closure(actor_id, &PasswordManager::do_update_password_settings_impl, std::move(update_settings), r_state.move_as_ok(), std::move(private_state), std::move(promise)); })); } Result PasswordManager::get_password_input_settings( string new_password, string new_hint, const NewPasswordState &state) { UpdateSettings update_settings; update_settings.update_password = true; update_settings.new_password = std::move(new_password); update_settings.new_hint = std::move(new_hint); return get_password_input_settings(update_settings, true, state, nullptr); } Result PasswordManager::get_password_input_settings( const UpdateSettings &update_settings, bool has_password, const NewPasswordState &state, const PasswordPrivateState *private_state) { bool have_secret = private_state != nullptr && private_state->secret; auto update_secure_secret = update_settings.update_secure_secret; int32 flags = 0; BufferSlice new_password_hash; tl_object_ptr new_algo; string new_hint; if (update_settings.update_password) { flags |= telegram_api::account_passwordInputSettings::NEW_PASSWORD_HASH_MASK; flags |= telegram_api::account_passwordInputSettings::NEW_ALGO_MASK; flags |= telegram_api::account_passwordInputSettings::HINT_MASK; if (!update_settings.new_password.empty()) { auto new_client_salt = create_salt(state.client_salt); auto new_hash = calc_password_srp_hash(update_settings.new_password, new_client_salt.as_slice(), state.server_salt, state.srp_g, state.srp_p); if (new_hash.is_error()) { return Status::Error(400, "Unable to change password, because it may be unsafe"); } new_password_hash = new_hash.move_as_ok(); new_algo = make_tl_object( std::move(new_client_salt), BufferSlice(state.server_salt), state.srp_g, BufferSlice(state.srp_p)); new_hint = update_settings.new_hint; if (have_secret) { update_secure_secret = true; } } else { new_algo = make_tl_object(); } } // have no password and not setting one if (!update_settings.update_password && !has_password) { update_secure_secret = false; } // setting an empty password if (update_settings.update_password && update_settings.new_password.empty()) { update_secure_secret = false; } tl_object_ptr new_secure_settings; if (update_secure_secret) { auto secret = have_secret ? std::move(private_state->secret.value()) : secure_storage::Secret::create_new(); auto algorithm = make_tl_object(create_salt(state.secure_salt)); auto encrypted_secret = secret.encrypt( update_settings.update_password ? update_settings.new_password : update_settings.current_password, algorithm->salt_.as_slice(), secure_storage::EnryptionAlgorithm::Pbkdf2); flags |= telegram_api::account_passwordInputSettings::NEW_SECURE_SETTINGS_MASK; new_secure_settings = make_tl_object( std::move(algorithm), BufferSlice(encrypted_secret.as_slice()), secret.get_hash()); } if (update_settings.update_recovery_email_address) { flags |= telegram_api::account_passwordInputSettings::EMAIL_MASK; } return make_tl_object( flags, std::move(new_algo), std::move(new_password_hash), new_hint, update_settings.recovery_email_address, std::move(new_secure_settings)); } void PasswordManager::do_update_password_settings_impl(UpdateSettings update_settings, PasswordState state, PasswordPrivateState private_state, Promise promise) { TRY_RESULT_PROMISE(promise, new_settings, get_password_input_settings(update_settings, state.has_password, state.new_state, &private_state)); auto current_hash = get_input_check_password(state.has_password ? update_settings.current_password : Slice(), state); auto query = G()->net_query_creator().create( telegram_api::account_updatePasswordSettings(std::move(current_hash), std::move(new_settings))); send_with_promise(std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), promise = std::move(promise)]( Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { Slice prefix("EMAIL_UNCONFIRMED"); Slice message = r_result.error().message(); if (r_result.error().code() == 400 && begins_with(message, prefix)) { if (message.size() >= prefix.size() + 2 && message[prefix.size()] == '_') { send_closure(actor_id, &PasswordManager::on_get_code_length, to_integer(message.substr(prefix.size() + 1))); } return promise.set_value(true); } return promise.set_error(r_result.move_as_error()); } return promise.set_value(r_result.move_as_ok()); })); } void PasswordManager::on_get_code_length(int32 code_length) { if (code_length <= 0 || code_length > 100) { LOG(ERROR) << "Receive invalid code length " << code_length; return; } LOG(INFO) << "Set code length to " << code_length; last_code_length_ = code_length; } void PasswordManager::get_state(Promise promise) { do_get_state(PromiseCreator::lambda([promise = std::move(promise)](Result r_state) mutable { if (r_state.is_error()) { return promise.set_error(r_state.move_as_error()); } promise.set_value(r_state.move_as_ok().get_password_state_object()); })); } void PasswordManager::do_get_state(Promise promise) { auto query = G()->net_query_creator().create(telegram_api::account_getPassword()); send_with_promise( std::move(query), PromiseCreator::lambda([actor_id = actor_id(this), code_length = last_code_length_, promise = std::move(promise)](Result r_query) mutable { auto r_result = fetch_result(std::move(r_query)); if (r_result.is_error()) { return promise.set_error(r_result.move_as_error()); } auto password = r_result.move_as_ok(); LOG(INFO) << "Receive password info: " << to_string(password); Random::add_seed(password->secure_random_.as_slice()); PasswordState state; if (password->current_algo_ != nullptr) { state.has_password = true; switch (password->current_algo_->get_id()) { case telegram_api::passwordKdfAlgoUnknown::ID: return promise.set_error(Status::Error(400, "Please update client to continue")); case telegram_api::passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow::ID: { auto algo = move_tl_object_as( password->current_algo_); state.current_client_salt = algo->salt1_.as_slice().str(); state.current_server_salt = algo->salt2_.as_slice().str(); state.current_srp_g = algo->g_; state.current_srp_p = algo->p_.as_slice().str(); break; } default: UNREACHABLE(); } state.current_srp_B = password->srp_B_.as_slice().str(); state.current_srp_id = password->srp_id_; state.password_hint = std::move(password->hint_); state.has_recovery_email_address = password->has_recovery_; state.has_secure_values = password->has_secure_values_; } else { state.has_password = false; send_closure(actor_id, &PasswordManager::drop_cached_secret); } state.unconfirmed_recovery_email_address_pattern = std::move(password->email_unconfirmed_pattern_); state.code_length = code_length; if (password->flags_ & telegram_api::account_password::PENDING_RESET_DATE_MASK) { state.pending_reset_date = td::max(password->pending_reset_date_, 0); } auto &new_state = state.new_state; TRY_RESULT_PROMISE_ASSIGN( promise, new_state, get_new_password_state(std::move(password->new_algo_), std::move(password->new_secure_algo_))); promise.set_value(std::move(state)); })); } void PasswordManager::cache_secret(secure_storage::Secret secret) { LOG(INFO) << "Cache passport secret"; secret_ = std::move(secret); const int32 max_cache_time = 3600; secret_expire_time_ = Time::now() + max_cache_time; set_timeout_at(secret_expire_time_); } void PasswordManager::drop_cached_secret() { LOG(INFO) << "Drop passport secret"; secret_ = optional(); } void PasswordManager::timeout_expired() { if (Time::now() >= secret_expire_time_) { drop_cached_secret(); } else { set_timeout_at(secret_expire_time_); } } void PasswordManager::on_result(NetQueryPtr query) { auto token = get_link_token(); container_.extract(token).set_value(std::move(query)); } void PasswordManager::send_with_promise(NetQueryPtr query, Promise promise) { auto id = container_.create(std::move(promise)); G()->net_query_dispatcher().dispatch_with_callback(std::move(query), actor_shared(this, id)); } void PasswordManager::start_up() { temp_password_state_ = get_temp_password_state_sync(); } void PasswordManager::hangup() { container_.for_each( [](auto id, Promise &promise) { promise.set_error(Global::request_aborted_error()); }); stop(); } } // namespace td