diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl index b570aa79e..587d753cd 100644 --- a/Telegram/Resources/scheme.tl +++ b/Telegram/Resources/scheme.tl @@ -590,7 +590,7 @@ authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string s account.authorizations#1250abde authorizations:Vector = account.Authorizations; -account.password#68873ba5 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password; +account.password#ad2641f8 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password; account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings; @@ -1022,7 +1022,7 @@ savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date: account.takeout#4dba4501 id:long = account.Takeout; passwordKdfAlgoUnknown#d45ab096 = PasswordKdfAlgo; -passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000#b6425eaa salt1:bytes salt2:bytes = PasswordKdfAlgo; +passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow#3a912d4a salt1:bytes salt2:bytes g:int p:bytes = PasswordKdfAlgo; securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo; securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo; @@ -1030,6 +1030,9 @@ securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo; secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings; +inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP; +inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1049,7 +1052,7 @@ auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization; auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization; -auth.checkPassword#a63011e password_hash:bytes = auth.Authorization; +auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.recoverPassword#4ea56e92 code:string = auth.Authorization; auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; @@ -1078,11 +1081,11 @@ account.updateDeviceLocked#38df3532 period:int = Bool; account.getAuthorizations#e320c158 = account.Authorizations; account.resetAuthorization#df77f3bc hash:long = Bool; account.getPassword#548a30f5 = account.Password; -account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.PasswordSettings; -account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings:account.PasswordInputSettings = Bool; +account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings; +account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_settings:account.PasswordInputSettings = Bool; account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode; account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool; -account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword; +account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword; account.getWebAuthorizations#182e6d6f = account.WebAuthorizations; account.resetWebAuthorization#2d01b9ef hash:long = Bool; account.resetWebAuthorizations#682d2594 = Bool; @@ -1318,4 +1321,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector = Vector; -// LAYER 83 +// LAYER 84 diff --git a/Telegram/SourceFiles/base/bytes.h b/Telegram/SourceFiles/base/bytes.h index 667e27ecd..070f3dcb1 100644 --- a/Telegram/SourceFiles/base/bytes.h +++ b/Telegram/SourceFiles/base/bytes.h @@ -12,9 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace bytes { -using span = gsl::span; -using const_span = gsl::span; -using vector = std::vector; +using type = gsl::byte; +using span = gsl::span; +using const_span = gsl::span; +using vector = std::vector; + +template +using array = std::array; template < typename Container, @@ -48,6 +52,16 @@ inline const_span make_span(const Type *value, std::size_t count) { return gsl::as_bytes(gsl::make_span(value, count)); } +template +inline span object_as_span(Type *value) { + return bytes::make_span(value, 1); +} + +template +inline const_span object_as_span(const Type *value) { + return bytes::make_span(value, 1); +} + template inline vector make_vector(const Container &container) { const auto buffer = bytes::make_span(container); diff --git a/Telegram/SourceFiles/base/openssl_help.h b/Telegram/SourceFiles/base/openssl_help.h index 3ca53719e..0ad4916be 100644 --- a/Telegram/SourceFiles/base/openssl_help.h +++ b/Telegram/SourceFiles/base/openssl_help.h @@ -87,18 +87,11 @@ public: _failed = true; } } - void setModExp( - const BigNum &a, - const BigNum &p, - const BigNum &m, - const Context &context = Context()) { - if (a.failed() || p.failed() || m.failed()) { + + void setAdd(const BigNum &a, const BigNum &b) { + if (a.failed() || b.failed()) { _failed = true; - } else if (a.isNegative() || p.isNegative() || m.isNegative()) { - _failed = true; - } else if (!BN_mod_exp(raw(), a.raw(), p.raw(), m.raw(), context.raw())) { - _failed = true; - } else if (isNegative()) { + } else if (!BN_add(raw(), a.raw(), b.raw())) { _failed = true; } } @@ -116,6 +109,16 @@ public: _failed = true; } } + void setMul( + const BigNum &a, + const BigNum &b, + const Context &context = Context()) { + if (a.failed() || b.failed()) { + _failed = true; + } else if (!BN_mul(raw(), a.raw(), b.raw(), context.raw())) { + _failed = true; + } + } BN_ULONG setDivWord(BN_ULONG word) { Expects(word != 0); if (failed()) { @@ -128,6 +131,51 @@ public: } return result; } + void setModSub( + const BigNum &a, + const BigNum &b, + const BigNum &m, + const Context &context = Context()) { + if (a.failed() || b.failed() || m.failed()) { + _failed = true; + } else if (a.isNegative() || b.isNegative() || m.isNegative()) { + _failed = true; + } else if (!BN_mod_sub(raw(), a.raw(), b.raw(), m.raw(), context.raw())) { + _failed = true; + } else if (isNegative()) { + _failed = true; + } + } + void setModMul( + const BigNum &a, + const BigNum &b, + const BigNum &m, + const Context &context = Context()) { + if (a.failed() || b.failed() || m.failed()) { + _failed = true; + } else if (a.isNegative() || b.isNegative() || m.isNegative()) { + _failed = true; + } else if (!BN_mod_mul(raw(), a.raw(), b.raw(), m.raw(), context.raw())) { + _failed = true; + } else if (isNegative()) { + _failed = true; + } + } + void setModExp( + const BigNum &base, + const BigNum &power, + const BigNum &m, + const Context &context = Context()) { + if (base.failed() || power.failed() || m.failed()) { + _failed = true; + } else if (base.isNegative() || power.isNegative() || m.isNegative()) { + _failed = true; + } else if (!BN_mod_exp(raw(), base.raw(), power.raw(), m.raw(), context.raw())) { + _failed = true; + } else if (isNegative()) { + _failed = true; + } + } bool isNegative() const { return failed() ? false : BN_is_negative(raw()); @@ -198,9 +246,54 @@ public: return _failed; } - static BigNum ModExp(const BigNum &base, const BigNum &power, const openssl::BigNum &mod) { + static BigNum Add(const BigNum &a, const BigNum &b) { BigNum result; - result.setModExp(base, power, mod); + result.setAdd(a, b); + return result; + } + static BigNum Sub(const BigNum &a, const BigNum &b) { + BigNum result; + result.setSub(a, b); + return result; + } + static BigNum Mul( + const BigNum &a, + const BigNum &b, + const Context &context = Context()) { + BigNum result; + result.setMul(a, b, context); + return result; + } + static BigNum ModSub( + const BigNum &a, + const BigNum &b, + const BigNum &mod, + const Context &context = Context()) { + BigNum result; + result.setModSub(a, b, mod, context); + return result; + } + static BigNum ModMul( + const BigNum &a, + const BigNum &b, + const BigNum &mod, + const Context &context = Context()) { + BigNum result; + result.setModMul(a, b, mod, context); + return result; + } + static BigNum ModExp( + const BigNum &base, + const BigNum &power, + const BigNum &mod, + const Context &context = Context()) { + BigNum result; + result.setModExp(base, power, mod, context); + return result; + } + static BigNum Failed() { + BigNum result; + result._failed = true; return result; } @@ -210,12 +303,6 @@ private: }; -inline BigNum operator-(const BigNum &a, const BigNum &b) { - BigNum result; - result.setSub(a, b); - return result; -} - namespace details { template diff --git a/Telegram/SourceFiles/boxes/passcode_box.cpp b/Telegram/SourceFiles/boxes/passcode_box.cpp index 64c15bebf..637c1aeb1 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.cpp +++ b/Telegram/SourceFiles/boxes/passcode_box.cpp @@ -30,7 +30,7 @@ PasscodeBox::PasscodeBox(QWidget*, bool turningOff) PasscodeBox::PasscodeBox( QWidget*, - const Core::CloudPasswordAlgo &curAlgo, + const Core::CloudPasswordCheckRequest &curRequest, const Core::CloudPasswordAlgo &newAlgo, bool hasRecovery, bool notEmptyPassport, @@ -39,19 +39,19 @@ PasscodeBox::PasscodeBox( bool turningOff) : _turningOff(turningOff) , _cloudPwd(true) -, _curAlgo(curAlgo) +, _curRequest(curRequest) , _newAlgo(newAlgo) , _newSecureSecretAlgo(newSecureSecretAlgo) , _hasRecovery(hasRecovery) , _notEmptyPassport(notEmptyPassport) , _about(st::boxWidth - st::boxPadding.left() * 1.5) , _oldPasscode(this, st::defaultInputField, langFactory(lng_cloud_password_enter_old)) -, _newPasscode(this, st::defaultInputField, langFactory(curAlgo ? lng_cloud_password_enter_new : lng_cloud_password_enter_first)) +, _newPasscode(this, st::defaultInputField, langFactory(curRequest ? lng_cloud_password_enter_new : lng_cloud_password_enter_first)) , _reenterPasscode(this, st::defaultInputField, langFactory(lng_cloud_password_confirm_new)) -, _passwordHint(this, st::defaultInputField, langFactory(curAlgo ? lng_cloud_password_change_hint : lng_cloud_password_hint)) +, _passwordHint(this, st::defaultInputField, langFactory(curRequest ? lng_cloud_password_change_hint : lng_cloud_password_hint)) , _recoverEmail(this, st::defaultInputField, langFactory(lng_cloud_password_email)) , _recover(this, lang(lng_signin_recover)) { - Expects(!_turningOff || curAlgo.has_value()); + Expects(!_turningOff || curRequest); if (!hint.isEmpty()) _hintText.setText(st::passcodeTextStyle, lng_signin_hint(lt_password_hint, hint)); } @@ -65,7 +65,7 @@ rpl::producer<> PasscodeBox::passwordReloadNeeded() const { } bool PasscodeBox::currentlyHave() const { - return _cloudPwd ? _curAlgo.has_value() : Global::LocalPasscode(); + return _cloudPwd ? (!!_curRequest) : Global::LocalPasscode(); } void PasscodeBox::prepare() { @@ -225,10 +225,8 @@ void PasscodeBox::closeReplacedBy() { } } -bool PasscodeBox::setPasswordFail(const RPCError &error) { +void PasscodeBox::setPasswordFail(const RPCError &error) { if (MTP::isFloodError(error)) { - if (_oldPasscode->isHidden()) return false; - closeReplacedBy(); _setRequest = 0; @@ -240,45 +238,36 @@ bool PasscodeBox::setPasswordFail(const RPCError &error) { _recover->hide(); } update(); - return true; + return; } - if (MTP::isDefaultHandledError(error)) return false; closeReplacedBy(); _setRequest = 0; - QString err = error.type(); - if (err == qstr("PASSWORD_HASH_INVALID")) { + const auto err = error.type(); + if (err == qstr("PASSWORD_HASH_INVALID") + || err == qstr("SRP_PASSWORD_CHANGED")) { if (_oldPasscode->isHidden()) { _passwordReloadNeeded.fire({}); closeBox(); } else { badOldPasscode(); } - } else if (err == qstr("NEW_PASSWORD_BAD")) { - _newPasscode->setFocus(); - _newPasscode->showError(); - _newError = lang(lng_cloud_password_bad); - update(); - } else if (err == qstr("NEW_SALT_INVALID")) { - _passwordReloadNeeded.fire({}); - closeBox(); + } else if (error.type() == qstr("SRP_ID_INVALID")) { + handleSrpIdInvalid(); + //} else if (err == qstr("NEW_PASSWORD_BAD")) { + //} else if (err == qstr("NEW_SALT_INVALID")) { } else if (err == qstr("EMAIL_INVALID")) { _emailError = lang(lng_cloud_password_bad_email); _recoverEmail->setFocus(); _recoverEmail->showError(); update(); } - return true; } -bool PasscodeBox::setPasswordFail( +void PasscodeBox::setPasswordFail( const QByteArray &newPasswordBytes, const RPCError &error) { - if (MTP::isFloodError(error)) { - return setPasswordFail(error); - } else if (MTP::isDefaultHandledError(error)) { - return setPasswordFail(error); - } else if (error.type() == qstr("EMAIL_UNCONFIRMED")) { + if (error.type() == qstr("EMAIL_UNCONFIRMED")) { closeReplacedBy(); _setRequest = 0; @@ -286,9 +275,21 @@ bool PasscodeBox::setPasswordFail( getDelegate()->show( Box(lang(lng_cloud_password_almost)), LayerOption::CloseOther); - return true; } else { - return setPasswordFail(error); + setPasswordFail(error); + } +} + +void PasscodeBox::handleSrpIdInvalid() { + const auto now = getms(true); + if (_lastSrpIdInvalidTime > 0 + && now - _lastSrpIdInvalidTime < Core::kHandleSrpIdInvalidTimeout) { + _curRequest.id = 0; + _oldError = Lang::Hard::ServerError(); + update(); + } else { + _lastSrpIdInvalidTime = now; + requestPasswordData(); } } @@ -391,10 +392,68 @@ void PasscodeBox::clearCloudPassword(const QString &oldPassword) { } void PasscodeBox::sendClearCloudPassword(const QString &oldPassword) { + checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) { + sendClearCloudPassword(check); + }); +} + +void PasscodeBox::checkPassword( + const QString &oldPassword, + CheckPasswordCallback callback) { const auto passwordUtf = oldPassword.toUtf8(); - const auto oldPasswordHash = Core::ComputeCloudPasswordHash( - _curAlgo, + _checkPasswordHash = Core::ComputeCloudPasswordHash( + _curRequest.algo, bytes::make_span(passwordUtf)); + checkPasswordHash(std::move(callback)); +} + +void PasscodeBox::checkPasswordHash(CheckPasswordCallback callback) { + _checkPasswordCallback = std::move(callback); + if (_curRequest.id) { + passwordChecked(); + } else { + requestPasswordData(); + } +} + +void PasscodeBox::passwordChecked() { + if (!_curRequest || !_curRequest.id || !_checkPasswordCallback) { + return serverError(); + } + const auto check = Core::ComputeCloudPasswordCheck( + _curRequest, + _checkPasswordHash); + if (!check) { + return serverError(); + } + _curRequest.id = 0; + _checkPasswordCallback(check); +} + +void PasscodeBox::requestPasswordData() { + if (!_checkPasswordCallback) { + return serverError(); + } + + request(base::take(_setRequest)).cancel(); + _setRequest = request( + MTPaccount_GetPassword() + ).done([=](const MTPaccount_Password &result) { + _setRequest = 0; + result.match([&](const MTPDaccount_password &data) { + _curRequest = Core::ParseCloudPasswordCheckRequest(data); + passwordChecked(); + }); + }).send(); +} + +void PasscodeBox::serverError() { + getDelegate()->show(Box(Lang::Hard::ServerError())); + closeBox(); +} + +void PasscodeBox::sendClearCloudPassword( + const Core::CloudPasswordResult &check) { const auto newPasswordData = QByteArray(); const auto newPasswordHash = QByteArray(); const auto hint = QString(); @@ -404,40 +463,42 @@ void PasscodeBox::sendClearCloudPassword(const QString &oldPassword) { | MTPDaccount_passwordInputSettings::Flag::f_hint | MTPDaccount_passwordInputSettings::Flag::f_email; _setRequest = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(oldPasswordHash), + check.result, MTP_account_passwordInputSettings( MTP_flags(flags), Core::PrepareCloudPasswordAlgo(_newAlgo), - MTP_bytes(newPasswordHash), + MTP_bytes(QByteArray()), // new_password_hash MTP_string(hint), MTP_string(email), MTPSecureSecretSettings()) )).done([=](const MTPBool &result) { setPasswordDone({}); - }).fail([=](const RPCError &error) { + }).handleFloodErrors().fail([=](const RPCError &error) mutable { setPasswordFail({}, error); }).send(); } void PasscodeBox::setNewCloudPassword(const QString &newPassword) { const auto newPasswordBytes = newPassword.toUtf8(); - const auto newPasswordHash = Core::ComputeCloudPasswordHash( + const auto newPasswordHash = Core::ComputeCloudPasswordDigest( _newAlgo, bytes::make_span(newPasswordBytes)); - const auto oldPasswordData = QByteArray(); - const auto oldPasswordHash = QByteArray(); + if (newPasswordHash.modpow.empty()) { + return serverError(); + } const auto hint = _passwordHint->getLastText(); const auto email = _recoverEmail->getLastText().trimmed(); const auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash | MTPDaccount_passwordInputSettings::Flag::f_hint | MTPDaccount_passwordInputSettings::Flag::f_email; + _checkPasswordCallback = nullptr; _setRequest = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(oldPasswordHash), + MTP_inputCheckPasswordEmpty(), MTP_account_passwordInputSettings( MTP_flags(flags), Core::PrepareCloudPasswordAlgo(_newAlgo), - MTP_bytes(newPasswordHash), + MTP_bytes(newPasswordHash.modpow), MTP_string(hint), MTP_string(email), MTPSecureSecretSettings()) @@ -451,12 +512,17 @@ void PasscodeBox::setNewCloudPassword(const QString &newPassword) { void PasscodeBox::changeCloudPassword( const QString &oldPassword, const QString &newPassword) { - const auto passwordUtf = oldPassword.toUtf8(); - const auto oldPasswordHash = Core::ComputeCloudPasswordHash( - _curAlgo, - bytes::make_span(passwordUtf)); + checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) { + changeCloudPassword(oldPassword, check, newPassword); + }); +} + +void PasscodeBox::changeCloudPassword( + const QString &oldPassword, + const Core::CloudPasswordResult &check, + const QString &newPassword) { _setRequest = request(MTPaccount_GetPasswordSettings( - MTP_bytes(oldPasswordHash) + check.result )).done([=](const MTPaccount_PasswordSettings &result) { _setRequest = 0; @@ -464,12 +530,15 @@ void PasscodeBox::changeCloudPassword( const auto &data = result.c_account_passwordSettings(); if (!data.has_secure_settings()) { - const auto empty = QByteArray(); - sendChangeCloudPassword(oldPasswordHash, newPassword, empty); + checkPasswordHash([=](const Core::CloudPasswordResult &check) { + const auto empty = QByteArray(); + sendChangeCloudPassword(check, newPassword, empty); + }); return; } const auto &wrapped = data.vsecure_settings; const auto &settings = wrapped.c_secureSecretSettings(); + const auto passwordUtf = oldPassword.toUtf8(); const auto secret = Passport::DecryptSecureSecret( bytes::make_span(settings.vsecure_secret.v), Core::ComputeSecureSecretHash( @@ -477,67 +546,84 @@ void PasscodeBox::changeCloudPassword( bytes::make_span(passwordUtf))); if (secret.empty()) { LOG(("API Error: Failed to decrypt secure secret.")); - suggestSecretReset(oldPasswordHash, newPassword); + suggestSecretReset(newPassword); } else if (Passport::CountSecureSecretId(secret) != settings.vsecure_secret_id.v) { LOG(("API Error: Wrong secure secret id.")); - suggestSecretReset(oldPasswordHash, newPassword); + suggestSecretReset(newPassword); } else { - sendChangeCloudPassword( - oldPasswordHash, - newPassword, - QByteArray::fromRawData( - reinterpret_cast(secret.data()), - secret.size())); + const auto secureSecret = QByteArray( + reinterpret_cast(secret.data()), + secret.size()); + checkPasswordHash([=](const Core::CloudPasswordResult &check) { + sendChangeCloudPassword(check, newPassword, secureSecret); + }); } - }).fail([=](const RPCError &error) { + }).handleFloodErrors().fail([=](const RPCError &error) { setPasswordFail(error); }).send(); } -void PasscodeBox::suggestSecretReset( - const bytes::vector &oldPasswordHash, - const QString &newPassword) { +void PasscodeBox::suggestSecretReset(const QString &newPassword) { const auto box = std::make_shared>(); const auto resetSecretAndSave = [=] { - using Flag = MTPDaccount_passwordInputSettings::Flag; - _setRequest = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(oldPasswordHash), - MTP_account_passwordInputSettings( - MTP_flags(Flag::f_new_secure_settings), - MTPPasswordKdfAlgo(), // new_algo - MTPbytes(), // new_password_hash - MTPstring(), // hint - MTPstring(), // email - MTP_secureSecretSettings( - MTP_securePasswordKdfAlgoUnknown(), // secure_algo - MTP_bytes(QByteArray()), // secure_secret - MTP_long(0))) // secure_secret_id - )).done([=](const MTPBool &result) { - _setRequest = 0; - const auto empty = QByteArray(); - if (*box) { - (*box)->closeBox(); - } - sendChangeCloudPassword(oldPasswordHash, newPassword, empty); - }).fail([=](const RPCError &error) { - _setRequest = 0; - }).send(); + checkPasswordHash([=](const Core::CloudPasswordResult &check) { + resetSecret(check, newPassword, [=] { + if (*box) { + (*box)->closeBox(); + } + }); + }); }; *box = getDelegate()->show(Box( - Lang::Hard::PassportCorrupted(), + Lang::Hard::PassportCorruptedChange(), Lang::Hard::PassportCorruptedReset(), [=] { resetSecretAndSave(); })); } +void PasscodeBox::resetSecret( + const Core::CloudPasswordResult &check, + const QString &newPassword, + Fn callback) { + using Flag = MTPDaccount_passwordInputSettings::Flag; + _setRequest = request(MTPaccount_UpdatePasswordSettings( + check.result, + MTP_account_passwordInputSettings( + MTP_flags(Flag::f_new_secure_settings), + MTPPasswordKdfAlgo(), // new_algo + MTPbytes(), // new_password_hash + MTPstring(), // hint + MTPstring(), // email + MTP_secureSecretSettings( + MTP_securePasswordKdfAlgoUnknown(), // secure_algo + MTP_bytes(QByteArray()), // secure_secret + MTP_long(0))) // secure_secret_id + )).done([=](const MTPBool &result) { + _setRequest = 0; + callback(); + checkPasswordHash([=](const Core::CloudPasswordResult &check) { + const auto empty = QByteArray(); + sendChangeCloudPassword(check, newPassword, empty); + }); + }).fail([=](const RPCError &error) { + _setRequest = 0; + if (error.type() == qstr("SRP_ID_INVALID")) { + handleSrpIdInvalid(); + } + }).send(); +} + void PasscodeBox::sendChangeCloudPassword( - const bytes::vector &oldPasswordHash, + const Core::CloudPasswordResult &check, const QString &newPassword, const QByteArray &secureSecret) { const auto newPasswordBytes = newPassword.toUtf8(); - const auto newPasswordHash = Core::ComputeCloudPasswordHash( + const auto newPasswordHash = Core::ComputeCloudPasswordDigest( _newAlgo, bytes::make_span(newPasswordBytes)); + if (newPasswordHash.modpow.empty()) { + return serverError(); + } const auto hint = _passwordHint->getLastText(); auto flags = MTPDaccount_passwordInputSettings::Flag::f_new_algo | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash @@ -555,11 +641,11 @@ void PasscodeBox::sendChangeCloudPassword( bytes::make_span(newPasswordBytes))); } _setRequest = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(oldPasswordHash), + check.result, MTP_account_passwordInputSettings( MTP_flags(flags), Core::PrepareCloudPasswordAlgo(_newAlgo), - MTP_bytes(newPasswordHash), + MTP_bytes(newPasswordHash.modpow), MTP_string(hint), MTPstring(), // email is not changing MTP_secureSecretSettings( @@ -568,7 +654,7 @@ void PasscodeBox::sendChangeCloudPassword( MTP_long(newSecureSecretId))) )).done([=](const MTPBool &result) { setPasswordDone(newPasswordBytes); - }).fail([=](const RPCError &error) { + }).handleFloodErrors().fail([=](const RPCError &error) { setPasswordFail(newPasswordBytes, error); }).send(); } @@ -651,12 +737,9 @@ void PasscodeBox::recoverStarted(const MTPauth_PasswordRecovery &result) { recover(); } -bool PasscodeBox::recoverStartFail(const RPCError &error) { - if (MTP::isDefaultHandledError(error)) return false; - +void PasscodeBox::recoverStartFail(const RPCError &error) { _pattern = QString(); closeBox(); - return true; } RecoverBox::RecoverBox( diff --git a/Telegram/SourceFiles/boxes/passcode_box.h b/Telegram/SourceFiles/boxes/passcode_box.h index 78f43d6d8..918b459b5 100644 --- a/Telegram/SourceFiles/boxes/passcode_box.h +++ b/Telegram/SourceFiles/boxes/passcode_box.h @@ -22,7 +22,7 @@ public: PasscodeBox(QWidget*, bool turningOff); PasscodeBox( QWidget*, - const Core::CloudPasswordAlgo &curAlgo, + const Core::CloudPasswordCheckRequest &curRequest, const Core::CloudPasswordAlgo &newAlgo, bool hasRecovery, bool notEmptyPassport, @@ -41,6 +41,9 @@ protected: void resizeEvent(QResizeEvent *e) override; private: + using CheckPasswordCallback = Fn; + void submit(); void closeReplacedBy(); void oldChanged(); @@ -53,31 +56,51 @@ private: bool currentlyHave() const; void setPasswordDone(const QByteArray &newPasswordBytes); - bool setPasswordFail(const RPCError &error); - bool setPasswordFail( + void setPasswordFail(const RPCError &error); + void setPasswordFail( const QByteArray &newPasswordBytes, const RPCError &error); void recoverStarted(const MTPauth_PasswordRecovery &result); - bool recoverStartFail(const RPCError &error); + void recoverStartFail(const RPCError &error); void recover(); void clearCloudPassword(const QString &oldPassword); void setNewCloudPassword(const QString &newPassword); + + void checkPassword( + const QString &oldPassword, + CheckPasswordCallback callback); + void checkPasswordHash(CheckPasswordCallback callback); + void changeCloudPassword( const QString &oldPassword, const QString &newPassword); + void changeCloudPassword( + const QString &oldPassword, + const Core::CloudPasswordResult &check, + const QString &newPassword); + void sendChangeCloudPassword( - const bytes::vector &oldPasswordHash, + const Core::CloudPasswordResult &check, const QString &newPassword, const QByteArray &secureSecret); - void suggestSecretReset( - const bytes::vector &oldPasswordHash, - const QString &newPassword); + void suggestSecretReset(const QString &newPassword); + void resetSecret( + const Core::CloudPasswordResult &check, + const QString &newPassword, + Fn callback); void resetSecretAndChangePassword( const bytes::vector &oldPasswordHash, const QString &newPassword); + void sendClearCloudPassword(const QString &oldPassword); + void sendClearCloudPassword(const Core::CloudPasswordResult &check); + + void handleSrpIdInvalid(); + void requestPasswordData(); + void passwordChecked(); + void serverError(); QString _pattern; @@ -86,12 +109,15 @@ private: bool _cloudPwd = false; mtpRequestId _setRequest = 0; - Core::CloudPasswordAlgo _curAlgo; + Core::CloudPasswordCheckRequest _curRequest; + TimeMs _lastSrpIdInvalidTime = 0; Core::CloudPasswordAlgo _newAlgo; Core::SecureSecretAlgo _newSecureSecretAlgo; bool _hasRecovery = false; bool _notEmptyPassport = false; bool _skipEmailWarning = false; + CheckPasswordCallback _checkPasswordCallback; + bytes::vector _checkPasswordHash; int _aboutHeight = 0; diff --git a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py index 1cb7e6df7..12eb36008 100644 --- a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py +++ b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py @@ -55,12 +55,13 @@ addChildParentFlags('MTPDchannelForbidden', 'MTPDchannel'); parentFlagsCheck = {}; countedTypeIdExceptions = {}; -for i in range(77, 84): - countedTypeIdExceptions[i] = {} - countedTypeIdExceptions[i]['channel'] = True -countedTypeIdExceptions['ipPortSecret'] = True -countedTypeIdExceptions['accessPointRule'] = True -countedTypeIdExceptions['help_configSimple'] = True +countedTypeIdExceptions['channel#c88974ac'] = True +countedTypeIdExceptions['ipPortSecret#37982646'] = True +countedTypeIdExceptions['accessPointRule#4679b65f'] = True +countedTypeIdExceptions['help.configSimple#5a592a6c'] = True + +renamedTypes = {}; +renamedTypes['passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow'] = 'passwordKdfAlgoModPow'; lines = []; layer = ''; @@ -119,7 +120,10 @@ for line in lines: sys.exit(1); continue; - name = nametype.group(1); + originalname = nametype.group(1); + name = originalname; + if (name in renamedTypes): + name = renamedTypes[name]; nameInd = name.find('.'); if (nameInd >= 0): Name = name[0:nameInd] + '_' + name[nameInd + 1:nameInd + 2].upper() + name[nameInd + 2:]; @@ -144,17 +148,19 @@ for line in lines: countTypeId = binascii.crc32(binascii.a2b_qp(cleanline)); if (countTypeId < 0): countTypeId += 2 ** 32; - countTypeId = '0x' + re.sub(r'^0x|L$', '', hex(countTypeId)); + countTypeId = re.sub(r'^0x|L$', '', hex(countTypeId)); if (typeid and len(typeid) > 0): - typeid = '0x' + typeid; + typeid = typeid; if (typeid != countTypeId): - if (not layerIndex in countedTypeIdExceptions or not name in countedTypeIdExceptions[layerIndex]): - if (not name in countedTypeIdExceptions): - print('Warning: counted ' + countTypeId + ' mismatch with provided ' + typeid + ' (' + cleanline + ')'); + key = originalname + '#' + typeid; + if (not key in countedTypeIdExceptions): + print('Warning: counted ' + countTypeId + ' mismatch with provided ' + typeid + ' (' + key + ', ' + cleanline + ')'); continue; else: typeid = countTypeId; + typeid = '0x' + typeid; + params = nametype.group(3); restype = nametype.group(4); if (restype.find('<') >= 0): @@ -216,7 +222,7 @@ for line in lines: if (templ): hasTemplate = templ.group(1); continue; - pnametype = re.match(r'([a-z_][a-z0-9_]*):([A-Za-z0-9<>\._]+|![a-zA-Z]+|\#|[a-z_][a-z0-9_]*\.[0-9]+\?[A-Za-z0-9<>\._]+)$', param); + pnametype = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*):([A-Za-z0-9<>\._]+|![a-zA-Z]+|\#|[a-z_][a-z0-9_]*\.[0-9]+\?[A-Za-z0-9<>\._]+)$', param); if (not pnametype): print('Bad param found: "' + param + '" in line: ' + line); sys.exit(1); diff --git a/Telegram/SourceFiles/core/core_cloud_password.cpp b/Telegram/SourceFiles/core/core_cloud_password.cpp index b8da67f69..446a5abb2 100644 --- a/Telegram/SourceFiles/core/core_cloud_password.cpp +++ b/Telegram/SourceFiles/core/core_cloud_password.cpp @@ -8,59 +8,204 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/core_cloud_password.h" #include "base/openssl_help.h" +#include "mtproto/connection.h" namespace Core { namespace { -constexpr auto kAdditionalSalt = size_type(8); +using namespace openssl; + +constexpr auto kAdditionalSalt = size_type(32); + +constexpr auto kSizeForHash = 256; +bytes::vector NumBytesForHash(bytes::const_span number) { + const auto fill = kSizeForHash - number.size(); + if (!fill) { + return bytes::make_vector(number); + } + auto result = bytes::vector(kSizeForHash); + const auto storage = bytes::make_span(result); + bytes::set_with_const(storage.subspan(0, fill), bytes::type(0)); + bytes::copy(storage.subspan(fill), number); + return result; +} + +bytes::vector BigNumForHash(const BigNum &number) { + auto result = number.getBytes(); + if (result.size() == kSizeForHash) { + return result; + } + return NumBytesForHash(result); +} + +bool IsPositive(const BigNum &number) { + return !number.isNegative() && (number.bitsSize() > 0); +} + +bool IsGoodLarge(const BigNum &number, const BigNum &p) { + return IsPositive(number) && IsPositive(BigNum::Sub(p, number)); +} + +bytes::vector Xor(bytes::const_span a, bytes::const_span b) { + Expects(a.size() == b.size()); + + auto result = bytes::vector(a.size()); + for (auto i = index_type(); i != a.size(); ++i) { + result[i] = a[i] ^ b[i]; + } + return result; +}; + +bytes::vector ComputeHash( + const CloudPasswordAlgoModPow &algo, + bytes::const_span password) { + const auto hash1 = Sha256(algo.salt1, password, algo.salt1); + const auto hash2 = Sha256(algo.salt2, hash1, algo.salt2); + const auto hash3 = Pbkdf2Sha512(hash2, algo.salt1, algo.kIterations); + return Sha256(algo.salt2, hash3, algo.salt2); +} + +CloudPasswordDigest ComputeDigest( + const CloudPasswordAlgoModPow &algo, + bytes::const_span password) { + if (!MTP::IsPrimeAndGood(algo.p, algo.g)) { + LOG(("API Error: Bad p/g in cloud password creation!")); + return {}; + } + const auto value = BigNum::ModExp( + BigNum(algo.g), + BigNum(ComputeHash(algo, password)), + BigNum(algo.p)); + if (value.failed()) { + LOG(("API Error: Failed to count g_x in cloud password creation!")); + return {}; + } + return { BigNumForHash(value) }; +} + +CloudPasswordResult ComputeCheck( + const CloudPasswordCheckRequest &request, + const CloudPasswordAlgoModPow &algo, + bytes::const_span hash) { + const auto failed = [] { + return CloudPasswordResult{ MTP_inputCheckPasswordEmpty() }; + }; + + const auto p = BigNum(algo.p); + const auto g = BigNum(algo.g); + const auto B = BigNum(request.B); + if (!MTP::IsPrimeAndGood(algo.p, algo.g)) { + LOG(("API Error: Bad p/g in cloud password creation!")); + return failed(); + } else if (!IsGoodLarge(B, p)) { + LOG(("API Error: Bad B in cloud password check!")); + return failed(); + } + + const auto context = Context(); + const auto x = BigNum(hash); + const auto pForHash = NumBytesForHash(algo.p); + const auto gForHash = BigNumForHash(g); + const auto BForHash = NumBytesForHash(request.B); + const auto g_x = BigNum::ModExp(g, x, p, context); + const auto k = BigNum(Sha256(pForHash, gForHash)); + const auto kg_x = BigNum::ModMul(k, g_x, p, context); + + const auto GenerateAndCheckRandom = [&] { + constexpr auto kRandomSize = 256; + while (true) { + auto random = bytes::vector(kRandomSize); + bytes::set_random(random); + const auto a = BigNum(random); + const auto A = BigNum::ModExp(g, a, p, context); + if (MTP::IsGoodModExpFirst(A, p)) { + auto AForHash = BigNumForHash(A); + const auto u = BigNum(Sha256(AForHash, BForHash)); + if (IsPositive(u)) { + return std::make_tuple(a, std::move(AForHash), u); + } + } + } + }; + + const auto [a, AForHash, u] = GenerateAndCheckRandom(); + const auto g_b = BigNum::ModSub(B, kg_x, p, context); + if (!MTP::IsGoodModExpFirst(g_b, p)) { + LOG(("API Error: Bad g_b in cloud password check!")); + return failed(); + } + const auto ux = BigNum::Mul(u, x, context); + const auto a_ux = BigNum::Add(a, ux); + const auto S = BigNum::ModExp(g_b, a_ux, p, context); + if (S.failed()) { + LOG(("API Error: Failed to count S in cloud password check!")); + return failed(); + } + const auto K = Sha256(BigNumForHash(S)); + const auto M1 = Sha256( + Xor(Sha256(pForHash), Sha256(gForHash)), + Sha256(algo.salt1), + Sha256(algo.salt2), + AForHash, + BForHash, + K); + return CloudPasswordResult{ MTP_inputCheckPasswordSRP( + MTP_long(request.id), + MTP_bytes(AForHash), + MTP_bytes(M1)) + }; +} bytes::vector ComputeHash( base::none_type, bytes::const_span password) { - Unexpected("Bad cloud password algorithm."); -} - -bytes::vector ComputeHash( - const CloudPasswordAlgoPBKDF2 &algo, - bytes::const_span password) { - const auto hash1 = openssl::Sha256(algo.salt1, password, algo.salt1); - const auto hash2 = openssl::Sha256(algo.salt2, hash1, algo.salt2); - return openssl::Pbkdf2Sha512( - hash2, - bytes::make_span(algo.salt1), - algo.kIterations); + Unexpected("Bad secure secret algorithm."); } bytes::vector ComputeHash( const SecureSecretAlgoSHA512 &algo, bytes::const_span password) { - return openssl::Sha512(algo.salt, password, algo.salt); + return Sha512(algo.salt, password, algo.salt); } bytes::vector ComputeHash( const SecureSecretAlgoPBKDF2 &algo, bytes::const_span password) { - return openssl::Pbkdf2Sha512(password, algo.salt, algo.kIterations); + return Pbkdf2Sha512(password, algo.salt, algo.kIterations); } } // namespace CloudPasswordAlgo ParseCloudPasswordAlgo(const MTPPasswordKdfAlgo &data) { - return data.match([]( - const MTPDpasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000 &data) { - return CloudPasswordAlgo(CloudPasswordAlgoPBKDF2{ + return data.match([](const MTPDpasswordKdfAlgoModPow &data) { + return CloudPasswordAlgo(CloudPasswordAlgoModPow{ bytes::make_vector(data.vsalt1.v), - bytes::make_vector(data.vsalt2.v) }); + bytes::make_vector(data.vsalt2.v), + data.vg.v, + bytes::make_vector(data.vp.v) }); }, [](const MTPDpasswordKdfAlgoUnknown &data) { return CloudPasswordAlgo(); }); } +CloudPasswordCheckRequest ParseCloudPasswordCheckRequest( + const MTPDaccount_password &data) { + return CloudPasswordCheckRequest{ + (data.has_srp_id() ? data.vsrp_id.v : uint64()), + (data.has_srp_B() + ? bytes::make_vector(data.vsrp_B.v) + : bytes::vector()), + (data.has_current_algo() + ? ParseCloudPasswordAlgo(data.vcurrent_algo) + : CloudPasswordAlgo()) + }; +} + CloudPasswordAlgo ValidateNewCloudPasswordAlgo(CloudPasswordAlgo &&parsed) { - if (!parsed.is()) { + if (!parsed.is()) { return base::none; } - auto &value = parsed.get_unchecked(); + auto &value = parsed.get_unchecked(); const auto already = value.salt1.size(); value.salt1.resize(already + kAdditionalSalt); bytes::set_random(bytes::make_span(value.salt1).subspan(already)); @@ -68,20 +213,48 @@ CloudPasswordAlgo ValidateNewCloudPasswordAlgo(CloudPasswordAlgo &&parsed) { } MTPPasswordKdfAlgo PrepareCloudPasswordAlgo(const CloudPasswordAlgo &data) { - return data.match([](const CloudPasswordAlgoPBKDF2 &data) { - return MTP_passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000( + return data.match([](const CloudPasswordAlgoModPow &data) { + return MTP_passwordKdfAlgoModPow( MTP_bytes(data.salt1), - MTP_bytes(data.salt2)); + MTP_bytes(data.salt2), + MTP_int(data.g), + MTP_bytes(data.p)); }, [](base::none_type) { return MTP_passwordKdfAlgoUnknown(); }); } +CloudPasswordResult::operator bool() const { + return (result.type() != mtpc_inputCheckPasswordEmpty); +} + bytes::vector ComputeCloudPasswordHash( const CloudPasswordAlgo &algo, bytes::const_span password) { - return algo.match([&](const auto &data) { + return algo.match([&](const CloudPasswordAlgoModPow &data) { return ComputeHash(data, password); + }, [](base::none_type) -> bytes::vector { + Unexpected("Bad cloud password algorithm."); + }); +} + +CloudPasswordDigest ComputeCloudPasswordDigest( + const CloudPasswordAlgo &algo, + bytes::const_span password) { + return algo.match([&](const CloudPasswordAlgoModPow &data) { + return ComputeDigest(data, password); + }, [](base::none_type) -> CloudPasswordDigest { + Unexpected("Bad cloud password algorithm."); + }); +} + +CloudPasswordResult ComputeCloudPasswordCheck( + const CloudPasswordCheckRequest &request, + bytes::const_span hash) { + return request.algo.match([&](const CloudPasswordAlgoModPow &data) { + return ComputeCheck(request, data, hash); + }, [](base::none_type) -> CloudPasswordResult { + Unexpected("Bad cloud password algorithm."); }); } diff --git a/Telegram/SourceFiles/core/core_cloud_password.h b/Telegram/SourceFiles/core/core_cloud_password.h index 6856eeff1..5f9754308 100644 --- a/Telegram/SourceFiles/core/core_cloud_password.h +++ b/Telegram/SourceFiles/core/core_cloud_password.h @@ -11,29 +11,79 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Core { -struct CloudPasswordAlgoPBKDF2 { +constexpr auto kHandleSrpIdInvalidTimeout = 60 * TimeMs(1000); + +struct CloudPasswordAlgoModPow { static constexpr auto kIterations = 100000; bytes::vector salt1; bytes::vector salt2; + int g = 0; + bytes::vector p; }; inline bool operator==( - const CloudPasswordAlgoPBKDF2 &a, - const CloudPasswordAlgoPBKDF2 &b) { - return (a.salt1 == b.salt1) && (a.salt2 == b.salt2); + const CloudPasswordAlgoModPow &a, + const CloudPasswordAlgoModPow &b) { + return (a.salt1 == b.salt1) + && (a.salt2 == b.salt2) + && (a.g == b.g) + && (a.p == b.p); } -using CloudPasswordAlgo = base::optional_variant; +using CloudPasswordAlgo = base::optional_variant; CloudPasswordAlgo ParseCloudPasswordAlgo(const MTPPasswordKdfAlgo &data); CloudPasswordAlgo ValidateNewCloudPasswordAlgo(CloudPasswordAlgo &&parsed); MTPPasswordKdfAlgo PrepareCloudPasswordAlgo(const CloudPasswordAlgo &data); +struct CloudPasswordCheckRequest { + uint64 id = 0; + bytes::vector B; + CloudPasswordAlgo algo; + + explicit operator bool() const { + return !!algo; + } +}; + +inline bool operator==( + const CloudPasswordCheckRequest &a, + const CloudPasswordCheckRequest &b) { + return (a.id == b.id) && (a.B == b.B) && (a.algo == b.algo); +} + +inline bool operator!=( + const CloudPasswordCheckRequest &a, + const CloudPasswordCheckRequest &b) { + return !(a == b); +} + +CloudPasswordCheckRequest ParseCloudPasswordCheckRequest( + const MTPDaccount_password &data); + +struct CloudPasswordResult { + MTPInputCheckPasswordSRP result; + + explicit operator bool() const; +}; + +struct CloudPasswordDigest { + bytes::vector modpow; +}; + bytes::vector ComputeCloudPasswordHash( const CloudPasswordAlgo &algo, bytes::const_span password); +CloudPasswordDigest ComputeCloudPasswordDigest( + const CloudPasswordAlgo &algo, + bytes::const_span password); + +CloudPasswordResult ComputeCloudPasswordCheck( + const CloudPasswordCheckRequest &request, + bytes::const_span hash); + struct SecureSecretAlgoSHA512 { bytes::vector salt; }; diff --git a/Telegram/SourceFiles/intro/introcode.cpp b/Telegram/SourceFiles/intro/introcode.cpp index 7b62e3322..979bc0819 100644 --- a/Telegram/SourceFiles/intro/introcode.cpp +++ b/Telegram/SourceFiles/intro/introcode.cpp @@ -293,14 +293,12 @@ void CodeWidget::gotPassword(const MTPaccount_Password &result) { stopCheck(); _sentRequest = 0; const auto &d = result.c_account_password(); - getData()->pwdAlgo = d.has_current_algo() - ? Core::ParseCloudPasswordAlgo(d.vcurrent_algo) - : Core::CloudPasswordAlgo(); - if (!d.has_current_algo()) { + getData()->pwdRequest = Core::ParseCloudPasswordCheckRequest(d); + if (!d.has_current_algo() || !d.has_srp_id() || !d.has_srp_B()) { LOG(("API Error: No current password received on login.")); _code->setFocus(); return; - } else if (!getData()->pwdAlgo) { + } else if (!getData()->pwdRequest) { const auto box = std::make_shared>(); const auto callback = [=] { Core::UpdateApplication(); @@ -326,7 +324,7 @@ void CodeWidget::submit() { _checkRequest->start(1000); _sentCode = _code->getLastText(); - getData()->pwdAlgo = Core::CloudPasswordAlgo(); + getData()->pwdRequest = Core::CloudPasswordCheckRequest(); getData()->hasRecovery = false; getData()->pwdHint = QString(); getData()->pwdNotEmptyPassport = false; diff --git a/Telegram/SourceFiles/intro/intropwdcheck.cpp b/Telegram/SourceFiles/intro/intropwdcheck.cpp index eb6bd446b..e378940cf 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.cpp +++ b/Telegram/SourceFiles/intro/intropwdcheck.cpp @@ -26,7 +26,7 @@ PwdCheckWidget::PwdCheckWidget( QWidget *parent, Widget::Data *data) : Step(parent, data) -, _algo(getData()->pwdAlgo) +, _request(getData()->pwdRequest) , _hasRecovery(getData()->hasRecovery) , _notEmptyPassport(getData()->pwdNotEmptyPassport) , _hint(getData()->pwdHint) @@ -36,7 +36,7 @@ PwdCheckWidget::PwdCheckWidget( , _toRecover(this, lang(lng_signin_recover)) , _toPassword(this, lang(lng_signin_try_password)) , _checkRequest(this) { - Expects(_algo.has_value()); + Expects(!!_request); subscribe(Lang::Current().updated(), [this] { refreshLang(); }); @@ -103,7 +103,7 @@ void PwdCheckWidget::activate() { } void PwdCheckWidget::cancelled() { - MTP::cancel(base::take(_sentRequest)); + request(base::take(_sentRequest)).cancel(); } void PwdCheckWidget::stopCheck() { @@ -115,7 +115,7 @@ void PwdCheckWidget::onCheckRequest() { if (status < 0) { auto leftms = -status; if (leftms >= 1000) { - MTP::cancel(base::take(_sentRequest)); + request(base::take(_sentRequest)).cancel(); } } if (!_sentRequest && status == MTP::RequestSent) { @@ -137,72 +137,125 @@ void PwdCheckWidget::pwdSubmitDone(bool recover, const MTPauth_Authorization &re finish(d.vuser); } -bool PwdCheckWidget::pwdSubmitFail(const RPCError &error) { +void PwdCheckWidget::pwdSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { _sentRequest = 0; stopCheck(); showError(langFactory(lng_flood_error)); _pwdField->showError(); - return true; + return; } - if (MTP::isDefaultHandledError(error)) return false; _sentRequest = 0; stopCheck(); auto &err = error.type(); - if (err == qstr("PASSWORD_HASH_INVALID")) { + if (err == qstr("PASSWORD_HASH_INVALID") + || err == qstr("SRP_PASSWORD_CHANGED")) { showError(langFactory(lng_signin_bad_password)); _pwdField->selectAll(); _pwdField->showError(); - return true; } else if (err == qstr("PASSWORD_EMPTY")) { goBack(); - } - if (Logs::DebugEnabled()) { // internal server error - auto text = err + ": " + error.description(); - showError([text] { return text; }); + } else if (err == qstr("SRP_ID_INVALID")) { + handleSrpIdInvalid(); } else { - showError(&Lang::Hard::ServerError); + if (Logs::DebugEnabled()) { // internal server error + auto text = err + ": " + error.description(); + showError([text] { return text; }); + } else { + showError(&Lang::Hard::ServerError); + } + _pwdField->setFocus(); } - _pwdField->setFocus(); - return false; } -bool PwdCheckWidget::codeSubmitFail(const RPCError &error) { +void PwdCheckWidget::handleSrpIdInvalid() { + const auto now = getms(true); + if (_lastSrpIdInvalidTime > 0 + && now - _lastSrpIdInvalidTime < Core::kHandleSrpIdInvalidTimeout) { + _request.id = 0; + showError(&Lang::Hard::ServerError); + } else { + _lastSrpIdInvalidTime = now; + requestPasswordData(); + } +} + +void PwdCheckWidget::checkPasswordHash() { + if (_request.id) { + passwordChecked(); + } else { + requestPasswordData(); + } +} + +void PwdCheckWidget::requestPasswordData() { + request(base::take(_sentRequest)).cancel(); + _sentRequest = request( + MTPaccount_GetPassword() + ).done([=](const MTPaccount_Password &result) { + _sentRequest = 0; + result.match([&](const MTPDaccount_password &data) { + _request = Core::ParseCloudPasswordCheckRequest(data); + passwordChecked(); + }); + }).send(); +} + +void PwdCheckWidget::passwordChecked() { + if (!_request || !_request.id) { + return serverError(); + } + const auto check = Core::ComputeCloudPasswordCheck( + _request, + _passwordHash); + if (!check) { + return serverError(); + } + _request.id = 0; + _sentRequest = request( + MTPauth_CheckPassword(check.result) + ).done([=](const MTPauth_Authorization &result) { + pwdSubmitDone(false, result); + }).handleFloodErrors().fail([=](const RPCError &error) { + pwdSubmitFail(error); + }).send(); +} + +void PwdCheckWidget::serverError() { + showError(&Lang::Hard::ServerError); +} + +void PwdCheckWidget::codeSubmitFail(const RPCError &error) { if (MTP::isFloodError(error)) { showError(langFactory(lng_flood_error)); _codeField->showError(); - return true; + return; } - if (MTP::isDefaultHandledError(error)) return false; _sentRequest = 0; stopCheck(); const QString &err = error.type(); if (err == qstr("PASSWORD_EMPTY")) { goBack(); - return true; } else if (err == qstr("PASSWORD_RECOVERY_NA")) { recoverStartFail(error); - return true; } else if (err == qstr("PASSWORD_RECOVERY_EXPIRED")) { _emailPattern = QString(); onToPassword(); - return true; } else if (err == qstr("CODE_INVALID")) { showError(langFactory(lng_signin_wrong_code)); _codeField->selectAll(); _codeField->showError(); - return true; - } - if (Logs::DebugEnabled()) { // internal server error - auto text = err + ": " + error.description(); - showError([text] { return text; }); } else { - showError(&Lang::Hard::ServerError); + if (Logs::DebugEnabled()) { // internal server error + auto text = err + ": " + error.description(); + showError([text] { return text; }); + } else { + showError(&Lang::Hard::ServerError); + } + _codeField->setFocus(); } - _codeField->setFocus(); - return false; } void PwdCheckWidget::recoverStarted(const MTPauth_PasswordRecovery &result) { @@ -210,7 +263,7 @@ void PwdCheckWidget::recoverStarted(const MTPauth_PasswordRecovery &result) { updateDescriptionText(); } -bool PwdCheckWidget::recoverStartFail(const RPCError &error) { +void PwdCheckWidget::recoverStartFail(const RPCError &error) { stopCheck(); _pwdField->show(); _pwdHint->show(); @@ -219,13 +272,12 @@ bool PwdCheckWidget::recoverStartFail(const RPCError &error) { updateDescriptionText(); update(); hideError(); - return true; } void PwdCheckWidget::onToRecover() { if (_hasRecovery) { if (_sentRequest) { - MTP::cancel(base::take(_sentRequest)); + request(base::take(_sentRequest)).cancel(); } hideError(); _toRecover->hide(); @@ -237,7 +289,13 @@ void PwdCheckWidget::onToRecover() { _codeField->setFocus(); updateDescriptionText(); if (_emailPattern.isEmpty()) { - MTP::send(MTPauth_RequestPasswordRecovery(), rpcDone(&PwdCheckWidget::recoverStarted), rpcFail(&PwdCheckWidget::recoverStartFail)); + request( + MTPauth_RequestPasswordRecovery() + ).done([=](const MTPauth_PasswordRecovery &result) { + recoverStarted(result); + }).fail([=](const RPCError &error) { + recoverStartFail(error); + }).send(); } } else { Ui::show(Box(lang(lng_signin_no_email_forgot), [this] { showReset(); })); @@ -250,7 +308,7 @@ void PwdCheckWidget::onToPassword() { void PwdCheckWidget::showReset() { if (_sentRequest) { - MTP::cancel(base::take(_sentRequest)); + request(base::take(_sentRequest)).cancel(); } _toRecover->show(); _toPassword->hide(); @@ -285,10 +343,13 @@ void PwdCheckWidget::submit() { return; } const auto send = crl::guard(this, [=] { - _sentRequest = MTP::send( - MTPauth_RecoverPassword(MTP_string(code)), - rpcDone(&PwdCheckWidget::pwdSubmitDone, true), - rpcFail(&PwdCheckWidget::codeSubmitFail)); + _sentRequest = request( + MTPauth_RecoverPassword(MTP_string(code)) + ).done([=](const MTPauth_Authorization &result) { + pwdSubmitDone(true, result); + }).handleFloodErrors().fail([=](const RPCError &error) { + codeSubmitFail(error); + }).send(); }); if (_notEmptyPassport) { @@ -310,13 +371,10 @@ void PwdCheckWidget::submit() { hideError(); const auto password = _pwdField->getLastText().toUtf8(); - const auto hash = Core::ComputeCloudPasswordHash( - _algo, + _passwordHash = Core::ComputeCloudPasswordHash( + _request.algo, bytes::make_span(password)); - _sentRequest = MTP::send( - MTPauth_CheckPassword(MTP_bytes(hash)), - rpcDone(&PwdCheckWidget::pwdSubmitDone, false), - rpcFail(&PwdCheckWidget::pwdSubmitFail)); + checkPasswordHash(); } } diff --git a/Telegram/SourceFiles/intro/intropwdcheck.h b/Telegram/SourceFiles/intro/intropwdcheck.h index 8ca541b17..61e38519b 100644 --- a/Telegram/SourceFiles/intro/intropwdcheck.h +++ b/Telegram/SourceFiles/intro/intropwdcheck.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "intro/introwidget.h" +#include "mtproto/sender.h" namespace Ui { class InputField; @@ -18,7 +19,7 @@ class LinkButton; namespace Intro { -class PwdCheckWidget : public Widget::Step { +class PwdCheckWidget : public Widget::Step, private MTP::Sender { Q_OBJECT public: @@ -45,16 +46,23 @@ private: void updateControlsGeometry(); void pwdSubmitDone(bool recover, const MTPauth_Authorization &result); - bool pwdSubmitFail(const RPCError &error); - bool codeSubmitFail(const RPCError &error); - bool recoverStartFail(const RPCError &error); + void pwdSubmitFail(const RPCError &error); + void codeSubmitFail(const RPCError &error); + void recoverStartFail(const RPCError &error); void recoverStarted(const MTPauth_PasswordRecovery &result); void updateDescriptionText(); void stopCheck(); + void handleSrpIdInvalid(); + void requestPasswordData(); + void checkPasswordHash(); + void passwordChecked(); + void serverError(); - Core::CloudPasswordAlgo _algo; + Core::CloudPasswordCheckRequest _request; + TimeMs _lastSrpIdInvalidTime = 0; + bytes::vector _passwordHash; bool _hasRecovery = false; bool _notEmptyPassport = false; QString _hint, _emailPattern; diff --git a/Telegram/SourceFiles/intro/introwidget.h b/Telegram/SourceFiles/intro/introwidget.h index 7effa1207..2857667c8 100644 --- a/Telegram/SourceFiles/intro/introwidget.h +++ b/Telegram/SourceFiles/intro/introwidget.h @@ -73,7 +73,7 @@ public: int codeLength = 5; bool codeByTelegram = false; - Core::CloudPasswordAlgo pwdAlgo; + Core::CloudPasswordCheckRequest pwdRequest; bool hasRecovery = false; QString pwdHint; bool pwdNotEmptyPassport = false; diff --git a/Telegram/SourceFiles/lang/lang_hardcoded.h b/Telegram/SourceFiles/lang/lang_hardcoded.h index 9ad20a585..7ca7ebcc5 100644 --- a/Telegram/SourceFiles/lang/lang_hardcoded.h +++ b/Telegram/SourceFiles/lang/lang_hardcoded.h @@ -46,6 +46,10 @@ inline QString PassportCorrupted() { return qsl("It seems your Telegram Passport data was corrupted.\n\nYou can reset your Telegram Passport and restart this authorization."); } +inline QString PassportCorruptedChange() { + return qsl("It seems your Telegram Passport data was corrupted.\n\nYou can reset your Telegram Passport and change your cloud password."); +} + inline QString PassportCorruptedReset() { return qsl("Reset"); } diff --git a/Telegram/SourceFiles/mtproto/connection.cpp b/Telegram/SourceFiles/mtproto/connection.cpp index f3c0b56fc..b27cb877c 100644 --- a/Telegram/SourceFiles/mtproto/connection.cpp +++ b/Telegram/SourceFiles/mtproto/connection.cpp @@ -61,16 +61,20 @@ QString LogIdsVector(const QVector &ids) { return idsStr + "]"; } -bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime) { - auto diff = prime - modexp; +bool IsGoodModExpFirst( + const openssl::BigNum &modexp, + const openssl::BigNum &prime) { + const auto diff = openssl::BigNum::Sub(prime, modexp); if (modexp.failed() || prime.failed() || diff.failed()) { return false; } constexpr auto kMinDiffBitsCount = 2048 - 64; - if (diff.isNegative() || diff.bitsSize() < kMinDiffBitsCount || modexp.bitsSize() < kMinDiffBitsCount) { + if (diff.isNegative() + || diff.bitsSize() < kMinDiffBitsCount + || modexp.bitsSize() < kMinDiffBitsCount + || modexp.bytesSize() > kMaxModExpSize) { return false; } - Assert(modexp.bytesSize() <= kMaxModExpSize); return true; } @@ -194,20 +198,21 @@ ModExpFirst CreateModExp( BigNum prime(primeBytes); auto result = ModExpFirst(); - constexpr auto kMaxModExpFirstTries = 5; result.randomPower.resize(ModExpFirst::kRandomPowerSize); - for (auto tries = 0; tries != kMaxModExpFirstTries; ++tries) { + while (true) { bytes::set_random(result.randomPower); for (auto i = 0; i != ModExpFirst::kRandomPowerSize; ++i) { result.randomPower[i] ^= randomSeed[i]; } - auto modexp = BigNum::ModExp(BigNum(g), BigNum(result.randomPower), prime); + const auto modexp = BigNum::ModExp( + BigNum(g), + BigNum(result.randomPower), + prime); if (IsGoodModExpFirst(modexp, prime)) { result.modexp = modexp.getBytes(); - break; + return result; } } - return result; } void wrapInvokeAfter(SecureRequest &to, const SecureRequest &from, const RequestMap &haveSent, int32 skipBeforeRequest = 0) { @@ -3243,6 +3248,10 @@ bool IsPrimeAndGood(bytes::const_span primeBytes, int g) { return internal::IsPrimeAndGood(primeBytes, g); } +bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime) { + return internal::IsGoodModExpFirst(modexp, prime); +} + ModExpFirst CreateModExp(int g, bytes::const_span primeBytes, bytes::const_span randomSeed) { return internal::CreateModExp(g, primeBytes, randomSeed); } diff --git a/Telegram/SourceFiles/mtproto/connection.h b/Telegram/SourceFiles/mtproto/connection.h index 10a7ffe7c..feb2f6106 100644 --- a/Telegram/SourceFiles/mtproto/connection.h +++ b/Telegram/SourceFiles/mtproto/connection.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/auth_key.h" #include "mtproto/dc_options.h" #include "mtproto/connection_abstract.h" +#include "base/openssl_help.h" #include "base/timer.h" namespace MTP { @@ -23,6 +24,7 @@ struct ModExpFirst { bytes::vector modexp; bytes::vector randomPower; }; +bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime); ModExpFirst CreateModExp(int g, bytes::const_span primeBytes, bytes::const_span randomSeed); bytes::vector CreateAuthKey(bytes::const_span firstBytes, bytes::const_span randomBytes, bytes::const_span primeBytes); diff --git a/Telegram/SourceFiles/passport/passport_form_controller.cpp b/Telegram/SourceFiles/passport/passport_form_controller.cpp index 22ca4cf9d..8624ce8ef 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_form_controller.cpp @@ -321,7 +321,7 @@ QString FormController::privacyPolicyUrl() const { bytes::vector FormController::passwordHashForAuth( bytes::const_span password) const { - return Core::ComputeCloudPasswordHash(_password.algo, password); + return Core::ComputeCloudPasswordHash(_password.request.algo, password); } auto FormController::prepareFinalData() -> FinalData { @@ -442,8 +442,52 @@ std::vector> FormController::submitGetErrors() { return {}; } +void FormController::checkPasswordHash( + mtpRequestId &guard, + bytes::vector hash, + PasswordCheckCallback callback) { + _passwordCheckHash = std::move(hash); + _passwordCheckCallback = std::move(callback); + if (_password.request.id) { + passwordChecked(); + } else { + requestPasswordData(guard); + } +} + +void FormController::passwordChecked() { + if (!_password.request || !_password.request.id) { + return passwordServerError(); + } + const auto check = Core::ComputeCloudPasswordCheck( + _password.request, + _passwordCheckHash); + if (!check) { + return passwordServerError(); + } + _password.request.id = 0; + _passwordCheckCallback(check); +} + +void FormController::requestPasswordData(mtpRequestId &guard) { + if (!_passwordCheckCallback) { + return passwordServerError(); + } + + request(base::take(guard)).cancel(); + guard = request( + MTPaccount_GetPassword() + ).done([=, &guard](const MTPaccount_Password &result) { + guard = 0; + result.match([&](const MTPDaccount_password &data) { + _password.request = Core::ParseCloudPasswordCheckRequest(data); + passwordChecked(); + }); + }).send(); +} + void FormController::submitPassword(const QByteArray &password) { - Expects(_password.algo.has_value()); + Expects(!!_password.request); const auto submitSaved = !base::take(_savedPasswordValue).isEmpty(); if (_passwordCheckRequestId) { @@ -452,16 +496,27 @@ void FormController::submitPassword(const QByteArray &password) { _passwordError.fire(QString()); return; } + const auto callback = [=](const Core::CloudPasswordResult &check) { + submitPassword(check, password, submitSaved); + }; + checkPasswordHash( + _passwordCheckRequestId, + passwordHashForAuth(bytes::make_span(password)), + callback); +} + +void FormController::submitPassword( + const Core::CloudPasswordResult &check, + const QByteArray &password, + bool submitSaved) { _passwordCheckRequestId = request(MTPaccount_GetPasswordSettings( - MTP_bytes(passwordHashForAuth(bytes::make_span(password))) + check.result )).handleFloodErrors( ).done([=](const MTPaccount_PasswordSettings &result) { Expects(result.type() == mtpc_account_passwordSettings); _passwordCheckRequestId = 0; _savedPasswordValue = QByteArray(); - const auto hashForAuth = passwordHashForAuth( - bytes::make_span(password)); const auto &data = result.c_account_passwordSettings(); _password.confirmedEmail = qs(data.vemail); if (data.has_secure_settings()) { @@ -483,7 +538,7 @@ void FormController::submitPassword(const QByteArray &password) { settings.vsecure_secret_id.v); if (!_secret.empty()) { auto saved = SavedCredentials(); - saved.hashForAuth = hashForAuth; + saved.hashForAuth = base::take(_passwordCheckHash); saved.hashForSecret = hashForSecret; saved.secretId = _secretId; Auth().data().rememberPassportCredentials( @@ -499,13 +554,16 @@ void FormController::submitPassword(const QByteArray &password) { } }).fail([=](const RPCError &error) { _passwordCheckRequestId = 0; - if (submitSaved) { + if (error.type() == qstr("SRP_ID_INVALID")) { + handleSrpIdInvalid(_passwordCheckRequestId); + } else if (submitSaved) { // Force reload and show form. _password = PasswordSettings(); reloadPassword(); } else if (MTP::isFloodError(error)) { _passwordError.fire(lang(lng_flood_error)); - } else if (error.type() == qstr("PASSWORD_HASH_INVALID")) { + } else if (error.type() == qstr("PASSWORD_HASH_INVALID") + || error.type() == qstr("SRP_PASSWORD_CHANGED")) { _passwordError.fire(lang(lng_passport_password_wrong)); } else { _passwordError.fire_copy(error.type()); @@ -513,10 +571,40 @@ void FormController::submitPassword(const QByteArray &password) { }).send(); } +bool FormController::handleSrpIdInvalid(mtpRequestId &guard) { + const auto now = getms(true); + if (_lastSrpIdInvalidTime > 0 + && now - _lastSrpIdInvalidTime < Core::kHandleSrpIdInvalidTimeout) { + _password.request.id = 0; + _passwordError.fire(Lang::Hard::ServerError()); + return false; + } else { + _lastSrpIdInvalidTime = now; + requestPasswordData(guard); + return true; + } +} + +void FormController::passwordServerError() { + _view->showCriticalError(Lang::Hard::ServerError()); +} + void FormController::checkSavedPasswordSettings( const SavedCredentials &credentials) { + const auto callback = [=](const Core::CloudPasswordResult &check) { + checkSavedPasswordSettings(check, credentials); + }; + checkPasswordHash( + _passwordCheckRequestId, + credentials.hashForAuth, + callback); +} + +void FormController::checkSavedPasswordSettings( + const Core::CloudPasswordResult &check, + const SavedCredentials &credentials) { _passwordCheckRequestId = request(MTPaccount_GetPasswordSettings( - MTP_bytes(credentials.hashForAuth) + check.result )).done([=](const MTPaccount_PasswordSettings &result) { Expects(result.type() == mtpc_account_passwordSettings); @@ -546,8 +634,12 @@ void FormController::checkSavedPasswordSettings( } }).fail([=](const RPCError &error) { _passwordCheckRequestId = 0; - Auth().data().forgetPassportCredentials(); - showForm(); + if (error.type() != qstr("SRP_ID_INVALID") + || !handleSrpIdInvalid(_passwordCheckRequestId)) { + } else { + Auth().data().forgetPassportCredentials(); + showForm(); + } }).send(); } @@ -601,7 +693,7 @@ void FormController::cancelPassword() { return; } _passwordRequestId = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(QByteArray()), + MTP_inputCheckPasswordEmpty(), MTP_account_passwordInputSettings( MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email), MTP_passwordKdfAlgoUnknown(), // new_algo @@ -662,30 +754,45 @@ void FormController::suggestReset(bytes::vector password) { // } } _view->suggestReset([=] { - using Flag = MTPDaccount_passwordInputSettings::Flag; - _saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(passwordHashForAuth(password)), - MTP_account_passwordInputSettings( - MTP_flags(Flag::f_new_secure_settings), - MTPPasswordKdfAlgo(), // new_algo - MTPbytes(), // new_password_hash - MTPstring(), // hint - MTPstring(), // email - MTP_secureSecretSettings( - MTP_securePasswordKdfAlgoUnknown(), // secure_algo - MTP_bytes(QByteArray()), // secure_secret - MTP_long(0))) // secure_secret_id - )).done([=](const MTPBool &result) { - _saveSecretRequestId = 0; - generateSecret(password); - }).fail([=](const RPCError &error) { - _saveSecretRequestId = 0; - formFail(error.type()); - }).send(); + const auto callback = [=](const Core::CloudPasswordResult &check) { + resetSecret(check, password); + }; + checkPasswordHash( + _saveSecretRequestId, + passwordHashForAuth(bytes::make_span(password)), + callback); _secretReady.fire({}); }); } +void FormController::resetSecret( + const Core::CloudPasswordResult &check, + const bytes::vector &password) { + using Flag = MTPDaccount_passwordInputSettings::Flag; + _saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings( + check.result, + MTP_account_passwordInputSettings( + MTP_flags(Flag::f_new_secure_settings), + MTPPasswordKdfAlgo(), // new_algo + MTPbytes(), // new_password_hash + MTPstring(), // hint + MTPstring(), // email + MTP_secureSecretSettings( + MTP_securePasswordKdfAlgoUnknown(), // secure_algo + MTP_bytes(QByteArray()), // secure_secret + MTP_long(0))) // secure_secret_id + )).done([=](const MTPBool &result) { + _saveSecretRequestId = 0; + generateSecret(password); + }).fail([=](const RPCError &error) { + _saveSecretRequestId = 0; + if (error.type() != qstr("SRP_ID_INVALID") + || !handleSrpIdInvalid(_saveSecretRequestId)) { + formFail(error.type()); + } + }).send(); +} + void FormController::decryptValues() { Expects(!_secret.empty()); @@ -1792,19 +1899,29 @@ void FormController::generateSecret(bytes::const_span password) { auto secret = GenerateSecretBytes(); auto saved = SavedCredentials(); - saved.hashForAuth = passwordHashForAuth(password); + saved.hashForAuth = _passwordCheckHash; saved.hashForSecret = Core::ComputeSecureSecretHash( _password.newSecureAlgo, password); saved.secretId = CountSecureSecretId(secret); - auto encryptedSecret = EncryptSecureSecret( + const auto callback = [=](const Core::CloudPasswordResult &check) { + saveSecret(check, saved, secret); + }; + checkPasswordHash(_saveSecretRequestId, saved.hashForAuth, callback); +} + +void FormController::saveSecret( + const Core::CloudPasswordResult &check, + const SavedCredentials &saved, + const bytes::vector &secret) { + const auto encryptedSecret = EncryptSecureSecret( secret, saved.hashForSecret); using Flag = MTPDaccount_passwordInputSettings::Flag; _saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings( - MTP_bytes(saved.hashForAuth), + check.result, MTP_account_passwordInputSettings( MTP_flags(Flag::f_new_secure_settings), MTPPasswordKdfAlgo(), // new_algo @@ -1829,7 +1946,10 @@ void FormController::generateSecret(bytes::const_span password) { } }).fail([=](const RPCError &error) { _saveSecretRequestId = 0; - suggestRestart(); + if (error.type() != qstr("SRP_ID_INVALID") + || !handleSrpIdInvalid(_saveSecretRequestId)) { + suggestRestart(); + } }).send(); } @@ -2126,7 +2246,7 @@ void FormController::showForm() { || !_password.newSecureAlgo) { _view->showUpdateAppBox(); return; - } else if (_password.algo) { + } else if (_password.request) { if (!_savedPasswordValue.isEmpty()) { submitPassword(base::duplicate(_savedPasswordValue)); } else if (const auto saved = Auth().data().passportCredentials()) { @@ -2144,11 +2264,9 @@ bool FormController::applyPassword(const MTPDaccount_password &result) { settings.hint = qs(result.vhint); settings.hasRecovery = result.is_has_recovery(); settings.notEmptyPassport = result.is_has_secure_values(); - settings.algo = result.has_current_algo() - ? Core::ParseCloudPasswordAlgo(result.vcurrent_algo) - : Core::CloudPasswordAlgo(); + settings.request = Core::ParseCloudPasswordCheckRequest(result); settings.unknownAlgo = result.has_current_algo() - && !settings.algo; + && !settings.request; settings.unconfirmedPattern = result.has_email_unconfirmed_pattern() ? qs(result.vemail_unconfirmed_pattern) : QString(); diff --git a/Telegram/SourceFiles/passport/passport_form_controller.h b/Telegram/SourceFiles/passport/passport_form_controller.h index ec7626443..a709456a9 100644 --- a/Telegram/SourceFiles/passport/passport_form_controller.h +++ b/Telegram/SourceFiles/passport/passport_form_controller.h @@ -196,7 +196,7 @@ struct Form { }; struct PasswordSettings { - Core::CloudPasswordAlgo algo; + Core::CloudPasswordCheckRequest request; Core::CloudPasswordAlgo newAlgo; Core::SecureSecretAlgo newSecureAlgo; QString hint; @@ -207,9 +207,14 @@ struct PasswordSettings { bool unknownAlgo = false; bool operator==(const PasswordSettings &other) const { - return (algo == other.algo) - && (newAlgo == other.newAlgo) - && (newSecureAlgo == other.newSecureAlgo) + return (request == other.request) +// newAlgo and newSecureAlgo are always different, because they have +// different random parts added on the client to the server salts. +// && (newAlgo == other.newAlgo) +// && (newSecureAlgo == other.newSecureAlgo) + && ((!newAlgo && !other.newAlgo) || (newAlgo && other.newAlgo)) + && ((!newSecureAlgo && !other.newSecureAlgo) + || (newSecureAlgo && other.newSecureAlgo)) && (hint == other.hint) && (unconfirmedPattern == other.unconfirmedPattern) && (confirmedEmail == other.confirmedEmail) @@ -310,6 +315,9 @@ public: ~FormController(); private: + using PasswordCheckCallback = Fn; + struct FinalData { QVector hashes; QByteArray credentials; @@ -340,11 +348,26 @@ private: File &destination, const std::vector &source) const; + void submitPassword( + const Core::CloudPasswordResult &check, + const QByteArray &password, + bool submitSaved); + void checkPasswordHash( + mtpRequestId &guard, + bytes::vector hash, + PasswordCheckCallback callback); + bool handleSrpIdInvalid(mtpRequestId &guard); + void requestPasswordData(mtpRequestId &guard); + void passwordChecked(); + void passwordServerError(); void passwordDone(const MTPaccount_Password &result); bool applyPassword(const MTPDaccount_password &settings); bool applyPassword(PasswordSettings &&settings); bytes::vector passwordHashForAuth(bytes::const_span password) const; void checkSavedPasswordSettings(const SavedCredentials &credentials); + void checkSavedPasswordSettings( + const Core::CloudPasswordResult &check, + const SavedCredentials &credentials); void validateSecureSecret( bytes::const_span encryptedSecret, bytes::const_span passwordHashForSecret, @@ -361,6 +384,10 @@ private: void fileLoadProgress(FileKey key, int offset); void fileLoadFail(FileKey key); void generateSecret(bytes::const_span password); + void saveSecret( + const Core::CloudPasswordResult &check, + const SavedCredentials &saved, + const bytes::vector &secret); void subscribeToUploader(); void encryptFile( @@ -410,6 +437,9 @@ private: FinalData prepareFinalData(); void suggestReset(bytes::vector password); + void resetSecret( + const Core::CloudPasswordResult &check, + const bytes::vector &password); void suggestRestart(); void cancelAbort(); void shortPollEmailConfirmation(); @@ -423,6 +453,9 @@ private: mtpRequestId _passwordCheckRequestId = 0; PasswordSettings _password; + TimeMs _lastSrpIdInvalidTime = 0; + bytes::vector _passwordCheckHash; + PasswordCheckCallback _passwordCheckCallback; QByteArray _savedPasswordValue; Form _form; bool _cancelled = false; diff --git a/Telegram/SourceFiles/passport/passport_panel_controller.cpp b/Telegram/SourceFiles/passport/passport_panel_controller.cpp index 43123e6c7..c7519e2be 100644 --- a/Telegram/SourceFiles/passport/passport_panel_controller.cpp +++ b/Telegram/SourceFiles/passport/passport_panel_controller.cpp @@ -472,7 +472,7 @@ void PanelController::setupPassword() { || !settings.newSecureAlgo) { showUpdateAppBox(); return; - } else if (settings.algo) { + } else if (settings.request) { showAskPassword(); return; } @@ -481,7 +481,7 @@ void PanelController::setupPassword() { const auto notEmptyPassport = false; const auto hint = QString(); auto box = show(Box( - Core::CloudPasswordAlgo(), // current algo + Core::CloudPasswordCheckRequest(), // current settings.newAlgo, hasRecovery, notEmptyPassport, diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp index 5ad4385de..7f40b0cef 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_widget.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_widget.cpp @@ -93,7 +93,7 @@ void CloudPasswordState::onEdit() { return; } auto box = Ui::show(Box( - _curPasswordAlgo, + _curPasswordRequest, _newPasswordAlgo, _hasPasswordRecovery, _notEmptyPassport, @@ -122,12 +122,12 @@ void CloudPasswordState::onTurnOff() { callback)); return; } - if (!_curPasswordAlgo) { + if (!_curPasswordRequest) { _turnOff->hide(); MTP::send( MTPaccount_UpdatePasswordSettings( - MTP_bytes(QByteArray()), + MTP_inputCheckPasswordEmpty(), MTP_account_passwordInputSettings( MTP_flags( MTPDaccount_passwordInputSettings::Flag::f_email), @@ -140,7 +140,7 @@ void CloudPasswordState::onTurnOff() { rpcFail(&CloudPasswordState::offPasswordFail)); } else { auto box = Ui::show(Box( - _curPasswordAlgo, + _curPasswordRequest, _newPasswordAlgo, _hasPasswordRecovery, _notEmptyPassport, @@ -177,10 +177,8 @@ void CloudPasswordState::getPasswordDone(const MTPaccount_Password &result) { _waitingConfirm = QString(); const auto &d = result.c_account_password(); - _curPasswordAlgo = d.has_current_algo() - ? Core::ParseCloudPasswordAlgo(d.vcurrent_algo) - : Core::CloudPasswordAlgo(); - _unknownPasswordAlgo = d.has_current_algo() && !_curPasswordAlgo; + _curPasswordRequest = Core::ParseCloudPasswordCheckRequest(d); + _unknownPasswordAlgo = d.has_current_algo() && !_curPasswordRequest; _hasPasswordRecovery = d.is_has_recovery(); _notEmptyPassport = d.is_has_secure_values(); _curPasswordHint = qs(d.vhint); @@ -205,7 +203,7 @@ void CloudPasswordState::getPasswordDone(const MTPaccount_Password &result) { } bool CloudPasswordState::hasCloudPassword() const { - return (_curPasswordAlgo || _unknownPasswordAlgo); + return (_curPasswordRequest || _unknownPasswordAlgo); } bool CloudPasswordState::getPasswordFail(const RPCError &error) { diff --git a/Telegram/SourceFiles/settings/settings_privacy_widget.h b/Telegram/SourceFiles/settings/settings_privacy_widget.h index 15c42fea8..7d212c377 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_widget.h +++ b/Telegram/SourceFiles/settings/settings_privacy_widget.h @@ -66,7 +66,7 @@ private: object_ptr _turnOff; QString _waitingConfirm; - Core::CloudPasswordAlgo _curPasswordAlgo; + Core::CloudPasswordCheckRequest _curPasswordRequest; bool _unknownPasswordAlgo = false; bool _hasPasswordRecovery = false; bool _notEmptyPassport = false;