mirror of https://github.com/procxx/kepka.git
Update API scheme.
This commit is contained in:
parent
1392e05ab1
commit
083b520eee
|
@ -281,6 +281,8 @@ messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDis
|
|||
messageActionScreenshotTaken#4792929b = MessageAction;
|
||||
messageActionCustomAction#fae69f56 message:string = MessageAction;
|
||||
messageActionBotAllowed#abe9affe domain:string = MessageAction;
|
||||
messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
|
||||
messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
|
||||
|
||||
dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
|
||||
|
||||
|
@ -584,12 +586,12 @@ authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string s
|
|||
|
||||
account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations;
|
||||
|
||||
account.noPassword#c86371bd new_salt:bytes secret_random:bytes email_unconfirmed_pattern:string = account.Password;
|
||||
account.password#49efccf5 current_salt:bytes new_salt:bytes secret_random:bytes hint:string has_recovery:Bool email_unconfirmed_pattern:string = account.Password;
|
||||
account.noPassword#5ea182f6 new_salt:bytes new_secure_salt:bytes secure_random:bytes email_unconfirmed_pattern:string = account.Password;
|
||||
account.password#d06c5fc3 current_salt:bytes new_salt:bytes new_secure_salt:bytes secure_random:bytes hint:string has_recovery:Bool email_unconfirmed_pattern:string = account.Password;
|
||||
|
||||
account.passwordSettings#f8ebfc3f email:string secure_secret:bytes = account.PasswordSettings;
|
||||
account.passwordSettings#48ec1750 email:string secure_salt:bytes secure_secret:bytes secure_secret_hash:long = account.PasswordSettings;
|
||||
|
||||
account.passwordInputSettings#20ce9e59 flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_secret:flags.2?bytes = account.PasswordInputSettings;
|
||||
account.passwordInputSettings#e553a6cf flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_salt:flags.2?bytes new_secure_secret:flags.2?bytes new_secure_secret_hash:flags.2?long = account.PasswordInputSettings;
|
||||
|
||||
auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
|
||||
|
||||
|
@ -965,36 +967,40 @@ help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vect
|
|||
help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate;
|
||||
help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate;
|
||||
|
||||
inputSecureFileUploaded#c53ed020 id:long parts:int md5_checksum:string file_hash:string = InputSecureFile;
|
||||
inputSecureFileUploaded#c53ed020 id:long parts:int md5_checksum:string file_hash:bytes = InputSecureFile;
|
||||
inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;
|
||||
|
||||
secureFileEmpty#64199744 = SecureFile;
|
||||
secureFile#40ad69ba id:long access_hash:long size:int dc_id:int file_hash:string = SecureFile;
|
||||
secureFile#40ad69ba id:long access_hash:long size:int dc_id:int file_hash:bytes = SecureFile;
|
||||
|
||||
secureValueEmpty#f0cec6e0 name:string = SecureValue;
|
||||
secureValueData#6c3bf275 name:string data:bytes hash:string secret:bytes = SecureValue;
|
||||
secureValueFile#a145a158 name:string file:Vector<SecureFile> hash:string secret:bytes = SecureValue;
|
||||
secureValueText#1bb37ce3 name:string text:string hash:string = SecureValue;
|
||||
secureData#262b0134 data:bytes data_hash:bytes = SecureData;
|
||||
|
||||
inputSecureValueData#627bcf0d name:string data:bytes hash:string secret:bytes = InputSecureValue;
|
||||
inputSecureValueFile#e24f9466 name:string file:Vector<InputSecureFile> hash:string secret:bytes = InputSecureValue;
|
||||
inputSecureValueText#955a14bb name:string text:string hash:string = InputSecureValue;
|
||||
secureValueVerified#612d86df date:int provider:string = SecureValueVerified;
|
||||
|
||||
secureValueHash#e4c2677 name:string hash:string = SecureValueHash;
|
||||
secureValueTypeIdentity#37da58ca = SecureValueType;
|
||||
secureValueTypeAddress#cbe31e26 = SecureValueType;
|
||||
secureValueTypePhone#b320aadb = SecureValueType;
|
||||
secureValueTypeEmail#8e3ca7ee = SecureValueType;
|
||||
|
||||
authFieldTypeIdentity#4413b5d = AuthFieldType;
|
||||
authFieldTypeAddress#63bbccf3 = AuthFieldType;
|
||||
authFieldTypePhone#b56120ff = AuthFieldType;
|
||||
authFieldTypeEmail#4cc9fd06 = AuthFieldType;
|
||||
secureValueIdentity#a6c927ad flags:# data:SecureData files:Vector<SecureFile> secret:bytes hash:bytes verified:flags.0?SecureValueVerified = SecureValue;
|
||||
secureValueAddress#74a0d79c flags:# data:SecureData files:Vector<SecureFile> secret:bytes hash:bytes verified:flags.0?SecureValueVerified = SecureValue;
|
||||
secureValuePhone#a1ca84fe flags:# phone:string hash:bytes verified:flags.0?SecureValueVerified = SecureValue;
|
||||
secureValueEmail#c4db6579 flags:# email:string hash:bytes verified:flags.0?SecureValueVerified = SecureValue;
|
||||
|
||||
authField#ed81c3dc flags:# type:AuthFieldType data:SecureValue document:flags.0?SecureValue = AuthField;
|
||||
inputSecureValueIdentity#ae27d606 data:SecureData files:Vector<InputSecureFile> secret:bytes hash:bytes = InputSecureValue;
|
||||
inputSecureValueAddress#c1f733e5 data:SecureData files:Vector<InputSecureFile> secret:bytes hash:bytes = InputSecureValue;
|
||||
inputSecureValuePhone#141e00b8 phone:string hash:bytes = InputSecureValue;
|
||||
inputSecureValueEmail#2dc15b9a email:string hash:bytes = InputSecureValue;
|
||||
|
||||
account.secureValueResultSaved#f4e0ab11 = account.SecureValueResult;
|
||||
account.secureValueVerificationNeeded#9192a1db code_hash:string = account.SecureValueResult;
|
||||
secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
|
||||
|
||||
account.authorizationForm#40997b71 flags:# accepted:flags.0?true request_write:flags.1?true bot_id:int fields:Vector<AuthField> accepted_fields:Vector<AuthFieldType> users:Vector<User> = account.AuthorizationForm;
|
||||
secureValueSaved#c9147049 files:Vector<SecureFile> = SecureValueSaved;
|
||||
|
||||
account.authorizationResult#836727ce user_data:DataJSON accepted_fields:Vector<AuthFieldType> = account.AuthorizationResult;
|
||||
secureCredentialsEncrypted#628fe12a data:bytes secret:bytes hash:bytes = SecureCredentialsEncrypted;
|
||||
|
||||
account.authorizationForm#8d9dddeb flags:# accepted:flags.0?true required_types:Vector<SecureValueType> values:Vector<SecureValue> users:Vector<User> = account.AuthorizationForm;
|
||||
|
||||
account.sentEmailCode#28b1633b email_pattern:string = account.SentEmailCode;
|
||||
|
||||
---functions---
|
||||
|
||||
|
@ -1051,11 +1057,15 @@ account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPass
|
|||
account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
|
||||
account.resetWebAuthorization#2d01b9ef hash:long = Bool;
|
||||
account.resetWebAuthorizations#682d2594 = Bool;
|
||||
account.getSecureValue#d86fa683 user_id:InputUser name:Vector<string> = Vector<SecureValue>;
|
||||
account.saveSecureValue#9fc2a624 data:InputSecureValue = account.SecureValueResult;
|
||||
account.verifySecureValue#68367bed name:string code_hash:string code:string = Bool;
|
||||
account.getAuthorizationForm#2f003837 flags:# bot_id:int scope:Vector<string> origin:flags.0?string package_name:flags.1?string bundle_id:flags.2?string public_key:flags.3?string = account.AuthorizationForm;
|
||||
account.acceptAuthorization#df98a3f5 flags:# request_write:flags.4?true bot_id:int scope:Vector<string> origin:flags.0?string package_name:flags.1?string bundle_id:flags.2?string public_key:flags.3?string accepted_fields:Vector<AuthFieldType> value_hashes:Vector<secureValueHash> = account.AuthorizationResult;
|
||||
account.getSecureValue#d97e77cb user_id:InputUser types:Vector<SecureValueType> = Vector<SecureValue>;
|
||||
account.saveSecureValue#842659a5 value:InputSecureValue secure_secret_hash:long = SecureValueSaved;
|
||||
account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool;
|
||||
account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm;
|
||||
account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector<SecureValueHash> credentials:SecureCredentialsEncrypted = Bool;
|
||||
account.sendVerifyPhoneCode#823380b4 flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode;
|
||||
account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
|
||||
account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode;
|
||||
account.verifyEmail#ecba39db email:string code:string = Bool;
|
||||
|
||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||
|
|
|
@ -331,6 +331,8 @@ void PasscodeBox::save(bool force) {
|
|||
flags |= MTPDaccount_passwordInputSettings::Flag::f_email;
|
||||
}
|
||||
const auto newSecureSecret = bytes::vector();
|
||||
const auto newSecureSalt = bytes::vector();
|
||||
const auto newSecureSecretHash = 0ULL;
|
||||
_setRequest = MTP::send(
|
||||
MTPaccount_UpdatePasswordSettings(
|
||||
MTP_bytes(oldPasswordHash),
|
||||
|
@ -340,7 +342,9 @@ void PasscodeBox::save(bool force) {
|
|||
MTP_bytes(newPasswordHash),
|
||||
MTP_string(hint),
|
||||
MTP_string(email),
|
||||
MTP_bytes(newSecureSecret))),
|
||||
MTP_bytes(newSecureSalt),
|
||||
MTP_bytes(newSecureSecret),
|
||||
MTP_long(newSecureSecretHash))),
|
||||
rpcDone(&PasscodeBox::setPasswordDone),
|
||||
rpcFail(&PasscodeBox::setPasswordFail));
|
||||
}
|
||||
|
|
|
@ -166,6 +166,11 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
|||
return result;
|
||||
};
|
||||
|
||||
auto prepareSecureValuesSent = [&](const MTPDmessageActionSecureValuesSent &action) {
|
||||
// #TODO passport
|
||||
return PreparedText{ QString("Secure values sent.") };
|
||||
};
|
||||
|
||||
auto messageText = PreparedText {};
|
||||
|
||||
switch (action.type()) {
|
||||
|
@ -187,6 +192,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
|
|||
case mtpc_messageActionScreenshotTaken: messageText = prepareScreenshotTaken(); break;
|
||||
case mtpc_messageActionCustomAction: messageText = prepareCustomAction(action.c_messageActionCustomAction()); break;
|
||||
case mtpc_messageActionBotAllowed: messageText = prepareBotAllowed(action.c_messageActionBotAllowed()); break;
|
||||
case mtpc_messageActionSecureValuesSent: messageText = prepareSecureValuesSent(action.c_messageActionSecureValuesSent()); break;
|
||||
default: messageText.text = lang(lng_message_empty); break;
|
||||
}
|
||||
|
||||
|
|
|
@ -914,7 +914,7 @@ bool Messenger::openLocalUrl(const QString &url) {
|
|||
authMatch->captured(1),
|
||||
UrlParamNameTransform::ToLower);
|
||||
if (const auto botId = params.value("bot_id", QString()).toInt()) {
|
||||
const auto scope = params.value("scope", QString()).split(',');
|
||||
const auto scope = params.value("scope", QString());
|
||||
const auto callback = params.value("callback_url", QString());
|
||||
const auto publicKey = params.value("public_key", QString());
|
||||
if (const auto window = App::wnd()) {
|
||||
|
|
|
@ -146,13 +146,19 @@ bytes::vector EncryptSecretBytes(
|
|||
return Encrypt(secret, std::move(params));
|
||||
}
|
||||
|
||||
bytes::vector Concatenate(
|
||||
bytes::const_span a,
|
||||
bytes::const_span b) {
|
||||
auto result = bytes::vector(a.size() + b.size());
|
||||
bytes::copy(result, a);
|
||||
bytes::copy(gsl::make_span(result).subspan(a.size()), b);
|
||||
return result;
|
||||
bytes::vector DecryptSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span password) {
|
||||
Expects(!salt.empty());
|
||||
Expects(!encryptedSecret.empty());
|
||||
Expects(!password.empty());
|
||||
|
||||
const auto hash = openssl::Sha512(bytes::concatenate(
|
||||
salt,
|
||||
password,
|
||||
salt));
|
||||
return DecryptSecretBytes(encryptedSecret, hash);
|
||||
}
|
||||
|
||||
bytes::vector SerializeData(const std::map<QString, QString> &data) {
|
||||
|
@ -246,8 +252,9 @@ EncryptedData EncryptData(
|
|||
gsl::make_span(unencrypted).subspan(padding),
|
||||
bytes);
|
||||
const auto dataHash = openssl::Sha256(unencrypted);
|
||||
const auto dataSecretHash = openssl::Sha512(
|
||||
Concatenate(dataSecret, dataHash));
|
||||
const auto dataSecretHash = openssl::Sha512(bytes::concatenate(
|
||||
dataSecret,
|
||||
dataHash));
|
||||
|
||||
auto params = PrepareAesParams(dataSecretHash);
|
||||
return {
|
||||
|
@ -272,8 +279,9 @@ bytes::vector DecryptData(
|
|||
return {};
|
||||
}
|
||||
|
||||
const auto dataSecretHash = openssl::Sha512(
|
||||
Concatenate(dataSecret, dataHash));
|
||||
const auto dataSecretHash = openssl::Sha512(bytes::concatenate(
|
||||
dataSecret,
|
||||
dataHash));
|
||||
auto params = PrepareAesParams(dataSecretHash);
|
||||
const auto decrypted = Decrypt(encrypted, std::move(params));
|
||||
if (bytes::compare(openssl::Sha256(decrypted), dataHash) != 0) {
|
||||
|
@ -294,22 +302,15 @@ bytes::vector DecryptData(
|
|||
bytes::vector PrepareValueHash(
|
||||
bytes::const_span dataHash,
|
||||
bytes::const_span valueSecret) {
|
||||
const auto result = openssl::Sha256(Concatenate(dataHash, valueSecret));
|
||||
return { result.begin(), result.end() };
|
||||
return openssl::Sha256(bytes::concatenate(dataHash, valueSecret));
|
||||
}
|
||||
|
||||
bytes::vector PrepareFilesHash(
|
||||
gsl::span<bytes::const_span> fileHashes,
|
||||
bytes::const_span valueSecret) {
|
||||
auto resultInner = bytes::vector{
|
||||
valueSecret.begin(),
|
||||
valueSecret.end()
|
||||
};
|
||||
for (const auto &hash : base::reversed(fileHashes)) {
|
||||
resultInner = Concatenate(hash, resultInner);
|
||||
}
|
||||
const auto result = openssl::Sha256(resultInner);
|
||||
return { result.begin(), result.end() };
|
||||
return openssl::Sha256(bytes::concatenate(
|
||||
bytes::concatenate(fileHashes),
|
||||
valueSecret));
|
||||
}
|
||||
|
||||
bytes::vector EncryptValueSecret(
|
||||
|
@ -317,7 +318,7 @@ bytes::vector EncryptValueSecret(
|
|||
bytes::const_span secret,
|
||||
bytes::const_span valueHash) {
|
||||
const auto valueSecretHash = openssl::Sha512(
|
||||
Concatenate(secret, valueHash));
|
||||
bytes::concatenate(secret, valueHash));
|
||||
return EncryptSecretBytes(valueSecret, valueSecretHash);
|
||||
}
|
||||
|
||||
|
@ -325,9 +326,18 @@ bytes::vector DecryptValueSecret(
|
|||
bytes::const_span encrypted,
|
||||
bytes::const_span secret,
|
||||
bytes::const_span valueHash) {
|
||||
const auto valueSecretHash = openssl::Sha512(
|
||||
Concatenate(secret, valueHash));
|
||||
const auto valueSecretHash = openssl::Sha512(bytes::concatenate(
|
||||
secret,
|
||||
valueHash));
|
||||
return DecryptSecretBytes(encrypted, valueSecretHash);
|
||||
}
|
||||
|
||||
uint64 CountSecureSecretHash(bytes::const_span secret) {
|
||||
const auto full = openssl::Sha256(secret);
|
||||
const auto part = bytes::make_span(full).subspan(
|
||||
full.size() - sizeof(uint64),
|
||||
sizeof(uint64));
|
||||
return *reinterpret_cast<const uint64*>(part.data());
|
||||
}
|
||||
|
||||
} // namespace Passport
|
||||
|
|
|
@ -18,6 +18,11 @@ bytes::vector DecryptSecretBytes(
|
|||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span passwordHashForSecret);
|
||||
|
||||
bytes::vector DecryptSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span password);
|
||||
|
||||
bytes::vector PasswordHashForSecret(bytes::const_span passwordUtf8);
|
||||
|
||||
bytes::vector SerializeData(const std::map<QString, QString> &data);
|
||||
|
@ -58,4 +63,6 @@ bytes::vector PrepareFilesHash(
|
|||
gsl::span<bytes::const_span> fileHashes,
|
||||
bytes::const_span valueSecret);
|
||||
|
||||
uint64 CountSecureSecretHash(bytes::const_span secret);
|
||||
|
||||
} // namespace Passport
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace Passport {
|
|||
|
||||
FormRequest::FormRequest(
|
||||
UserId botId,
|
||||
const QStringList &scope,
|
||||
const QString &scope,
|
||||
const QString &callbackUrl,
|
||||
const QString &publicKey)
|
||||
: botId(botId)
|
||||
|
@ -65,8 +65,6 @@ FormController::FormController(
|
|||
const FormRequest &request)
|
||||
: _controller(controller)
|
||||
, _request(request) {
|
||||
const auto url = QUrl(_request.callbackUrl);
|
||||
_origin = url.scheme() + "://" + url.host();
|
||||
}
|
||||
|
||||
void FormController::show() {
|
||||
|
@ -74,8 +72,16 @@ void FormController::show() {
|
|||
requestPassword();
|
||||
}
|
||||
|
||||
bytes::vector FormController::passwordHashForAuth(
|
||||
bytes::const_span password) const {
|
||||
return openssl::Sha256(bytes::concatenate(
|
||||
_password.salt,
|
||||
password,
|
||||
_password.salt));
|
||||
}
|
||||
|
||||
void FormController::submitPassword(const QString &password) {
|
||||
Expects(!_password.salt.isEmpty());
|
||||
Expects(!_password.salt.empty());
|
||||
|
||||
if (_passwordCheckRequestId) {
|
||||
return;
|
||||
|
@ -83,41 +89,19 @@ void FormController::submitPassword(const QString &password) {
|
|||
_passwordError.fire(QString());
|
||||
}
|
||||
const auto passwordBytes = password.toUtf8();
|
||||
_passwordHashForAuth = openssl::Sha256(bytes::concatenate(
|
||||
bytes::make_span(_password.salt),
|
||||
bytes::make_span(passwordBytes),
|
||||
bytes::make_span(_password.salt)));
|
||||
_passwordCheckRequestId = request(MTPaccount_GetPasswordSettings(
|
||||
MTP_bytes(_passwordHashForAuth)
|
||||
MTP_bytes(passwordHashForAuth(bytes::make_span(passwordBytes)))
|
||||
)).handleFloodErrors(
|
||||
).done([=](const MTPaccount_PasswordSettings &result) {
|
||||
Expects(result.type() == mtpc_account_passwordSettings);
|
||||
|
||||
_passwordCheckRequestId = 0;
|
||||
const auto &data = result.c_account_passwordSettings();
|
||||
_passwordEmail = qs(data.vemail);
|
||||
_passwordHashForSecret = openssl::Sha512(bytes::make_span(passwordBytes));
|
||||
_secret = DecryptSecretBytes(
|
||||
_password.confirmedEmail = qs(data.vemail);
|
||||
validateSecureSecret(
|
||||
bytes::make_span(data.vsecure_salt.v),
|
||||
bytes::make_span(data.vsecure_secret.v),
|
||||
_passwordHashForSecret);
|
||||
for (auto &field : _form.fields) {
|
||||
field.data.values = fillData(field.data);
|
||||
if (auto &document = field.document) {
|
||||
const auto filesHash = bytes::make_span(document->filesHash);
|
||||
document->filesSecret = DecryptValueSecret(
|
||||
bytes::make_span(document->filesSecretEncrypted),
|
||||
_secret,
|
||||
filesHash);
|
||||
if (document->filesSecret.empty()
|
||||
&& !document->files.empty()) {
|
||||
LOG(("API Error: Empty decrypted files secret. "
|
||||
"Forgetting files."));
|
||||
document->files.clear();
|
||||
document->filesHash.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
_secretReady.fire({});
|
||||
bytes::make_span(passwordBytes));
|
||||
}).fail([=](const RPCError &error) {
|
||||
_passwordCheckRequestId = 0;
|
||||
if (MTP::isFloodError(error)) {
|
||||
|
@ -130,6 +114,87 @@ void FormController::submitPassword(const QString &password) {
|
|||
}).send();
|
||||
}
|
||||
|
||||
void FormController::validateSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span password) {
|
||||
if (!salt.empty() && !encryptedSecret.empty()) {
|
||||
_secret = DecryptSecureSecret(salt, encryptedSecret, password);
|
||||
if (_secret.empty()) {
|
||||
LOG(("API Error: Failed to decrypt secure secret. "
|
||||
"Forgetting all files and data :("));
|
||||
for (auto &field : _form.fields) {
|
||||
if (!field.encryptedSecret.empty()) {
|
||||
resetField(field);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
decryptFields();
|
||||
}
|
||||
}
|
||||
if (_secret.empty()) {
|
||||
generateSecret(password);
|
||||
}
|
||||
_secretReady.fire({});
|
||||
}
|
||||
|
||||
void FormController::decryptFields() {
|
||||
Expects(!_secret.empty());
|
||||
|
||||
for (auto &field : _form.fields) {
|
||||
if (field.encryptedSecret.empty()) {
|
||||
continue;
|
||||
}
|
||||
decryptField(field);
|
||||
}
|
||||
}
|
||||
|
||||
void FormController::decryptField(Field &field) {
|
||||
Expects(!_secret.empty());
|
||||
Expects(!field.encryptedSecret.empty());
|
||||
|
||||
if (!validateFieldSecret(field)) {
|
||||
resetField(field);
|
||||
return;
|
||||
}
|
||||
const auto dataHash = bytes::make_span(field.dataHash);
|
||||
field.parsedData = DeserializeData(DecryptData(
|
||||
bytes::make_span(field.originalData),
|
||||
field.dataHash,
|
||||
field.secret));
|
||||
}
|
||||
|
||||
bool FormController::validateFieldSecret(Field &field) {
|
||||
field.secret = DecryptValueSecret(
|
||||
field.encryptedSecret,
|
||||
_secret,
|
||||
field.hash);
|
||||
if (field.secret.empty()) {
|
||||
LOG(("API Error: Could not decrypt value secret. "
|
||||
"Forgetting files and data :("));
|
||||
return false;
|
||||
}
|
||||
const auto fileHashes = ranges::view::all(
|
||||
field.files
|
||||
) | ranges::view::transform([](File &file) {
|
||||
return bytes::make_span(file.fileHash);
|
||||
});
|
||||
const auto countedHash = openssl::Sha256(bytes::concatenate(
|
||||
field.dataHash,
|
||||
bytes::concatenate(fileHashes),
|
||||
field.secret));
|
||||
if (field.hash != countedHash) {
|
||||
LOG(("API Error: Wrong hash after decrypting value secret. "
|
||||
"Forgetting files and data :("));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FormController::resetField(Field &field) {
|
||||
field = Field(field.type);
|
||||
}
|
||||
|
||||
rpl::producer<QString> FormController::passwordError() const {
|
||||
return _passwordError.events();
|
||||
}
|
||||
|
@ -141,22 +206,21 @@ QString FormController::passwordHint() const {
|
|||
void FormController::uploadScan(int index, QByteArray &&content) {
|
||||
Expects(_editBox != nullptr);
|
||||
Expects(index >= 0 && index < _form.fields.size());
|
||||
Expects(_form.fields[index].document.has_value());
|
||||
|
||||
auto &document = *_form.fields[index].document;
|
||||
if (document.filesSecret.empty()) {
|
||||
document.filesSecret = GenerateSecretBytes();
|
||||
auto &field = _form.fields[index];
|
||||
if (field.secret.empty()) {
|
||||
field.secret = GenerateSecretBytes();
|
||||
}
|
||||
|
||||
const auto weak = _editBox;
|
||||
crl::async([
|
||||
=,
|
||||
bytes = std::move(content),
|
||||
filesSecret = document.filesSecret
|
||||
valueSecret = field.secret
|
||||
] {
|
||||
auto data = EncryptData(
|
||||
bytes::make_span(bytes),
|
||||
filesSecret);
|
||||
valueSecret);
|
||||
auto result = UploadedScan();
|
||||
result.fileId = rand_value<uint64>();
|
||||
result.hash = std::move(data.hash);
|
||||
|
@ -193,14 +257,13 @@ void FormController::subscribeToUploader() {
|
|||
void FormController::uploadEncryptedScan(int index, UploadedScan &&data) {
|
||||
Expects(_editBox != nullptr);
|
||||
Expects(index >= 0 && index < _form.fields.size());
|
||||
Expects(_form.fields[index].document.has_value());
|
||||
|
||||
subscribeToUploader();
|
||||
|
||||
_form.fields[index].document->filesInEdit.emplace_back(
|
||||
_form.fields[index].filesInEdit.emplace_back(
|
||||
File(),
|
||||
std::make_unique<UploadedScan>(std::move(data)));
|
||||
auto &file = _form.fields[index].document->filesInEdit.back();
|
||||
auto &file = _form.fields[index].filesInEdit.back();
|
||||
|
||||
auto uploaded = std::make_shared<FileLoadResult>(
|
||||
TaskId(),
|
||||
|
@ -238,7 +301,7 @@ rpl::producer<> FormController::secretReadyEvents() const {
|
|||
}
|
||||
|
||||
QString FormController::defaultEmail() const {
|
||||
return _passwordEmail;
|
||||
return _password.confirmedEmail;
|
||||
}
|
||||
|
||||
QString FormController::defaultPhoneNumber() const {
|
||||
|
@ -294,13 +357,9 @@ void FormController::editField(int index) {
|
|||
auto &field = _form.fields[index];
|
||||
switch (field.type) {
|
||||
case Field::Type::Identity:
|
||||
if (field.document) {
|
||||
loadFiles(field.document->files);
|
||||
} else {
|
||||
field.document = Value();
|
||||
}
|
||||
field.document->filesInEdit = ranges::view::all(
|
||||
field.document->files
|
||||
loadFiles(field.files);
|
||||
field.filesInEdit = ranges::view::all(
|
||||
field.files
|
||||
) | ranges::view::transform([](const File &file) {
|
||||
return EditFile(file, nullptr);
|
||||
}) | ranges::to_vector;
|
||||
|
@ -353,7 +412,7 @@ void FormController::fileLoaded(FileKey key, const QByteArray &bytes) {
|
|||
const auto decrypted = DecryptData(
|
||||
bytes::make_span(bytes),
|
||||
file->fileHash,
|
||||
field->document->filesSecret);
|
||||
field->secret);
|
||||
auto image = App::readImage(QByteArray::fromRawData(
|
||||
reinterpret_cast<const char*>(decrypted.data()),
|
||||
decrypted.size()));
|
||||
|
@ -368,7 +427,7 @@ void FormController::fileLoaded(FileKey key, const QByteArray &bytes) {
|
|||
}
|
||||
|
||||
IdentityData FormController::fieldDataIdentity(const Field &field) const {
|
||||
const auto &map = field.data.values;
|
||||
const auto &map = field.parsedData;
|
||||
auto result = IdentityData();
|
||||
if (const auto i = map.find(qsl("first_name")); i != map.cend()) {
|
||||
result.name = i->second;
|
||||
|
@ -381,10 +440,8 @@ IdentityData FormController::fieldDataIdentity(const Field &field) const {
|
|||
|
||||
std::vector<ScanInfo> FormController::fieldFilesIdentity(
|
||||
const Field &field) const {
|
||||
Expects(field.document.has_value());
|
||||
|
||||
auto result = std::vector<ScanInfo>();
|
||||
for (const auto &file : field.document->filesInEdit) {
|
||||
for (const auto &file : field.filesInEdit) {
|
||||
result.push_back({
|
||||
FileKey{ file.fields.id, file.fields.dcId },
|
||||
langDateTime(QDateTime::currentDateTime()),
|
||||
|
@ -400,178 +457,119 @@ void FormController::saveFieldIdentity(
|
|||
Expects(index >= 0 && index < _form.fields.size());
|
||||
Expects(_form.fields[index].type == Field::Type::Identity);
|
||||
|
||||
_form.fields[index].data.values[qsl("first_name")] = data.name;
|
||||
_form.fields[index].data.values[qsl("last_name")] = data.surname;
|
||||
_form.fields[index].parsedData[qsl("first_name")] = data.name;
|
||||
_form.fields[index].parsedData[qsl("last_name")] = data.surname;
|
||||
|
||||
saveData(index);
|
||||
saveFiles(index);
|
||||
|
||||
_editBox->closeBox();
|
||||
saveIdentity(index);
|
||||
}
|
||||
|
||||
std::map<QString, QString> FormController::fillData(
|
||||
const Value &from) const {
|
||||
if (from.dataEncrypted.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
const auto valueHash = bytes::make_span(from.dataHash);
|
||||
const auto valueSecret = DecryptValueSecret(
|
||||
bytes::make_span(from.dataSecretEncrypted),
|
||||
_secret,
|
||||
valueHash);
|
||||
return DeserializeData(DecryptData(
|
||||
bytes::make_span(from.dataEncrypted),
|
||||
valueHash,
|
||||
valueSecret));
|
||||
}
|
||||
|
||||
void FormController::saveData(int index) {
|
||||
void FormController::saveIdentity(int index) {
|
||||
Expects(index >= 0 && index < _form.fields.size());
|
||||
Expects(_form.fields[index].type == Field::Type::Identity);
|
||||
|
||||
if (_secret.empty()) {
|
||||
generateSecret([=] {
|
||||
saveData(index);
|
||||
_secretCallbacks.push_back([=] {
|
||||
saveIdentity(index);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const auto &data = _form.fields[index].data;
|
||||
const auto encrypted = EncryptData(SerializeData(data.values));
|
||||
|
||||
// #TODO file_hash + file_hash + ...
|
||||
// PrepareValueHash(encrypted.hash, valueSecret);
|
||||
const auto valueHash = encrypted.hash;
|
||||
auto valueHashString = QString();
|
||||
valueHashString.reserve(valueHash.size() * 2);
|
||||
const auto hex = [](uchar value) -> QChar {
|
||||
return (value >= 10) ? ('a' + (value - 10)) : ('0' + value);
|
||||
};
|
||||
for (const auto byte : valueHash) {
|
||||
const auto value = uchar(byte);
|
||||
const auto high = uchar(value / 16);
|
||||
const auto low = uchar(value % 16);
|
||||
valueHashString.append(hex(high)).append(hex(low));
|
||||
_editBox->closeBox();
|
||||
|
||||
auto &field = _form.fields[index];
|
||||
|
||||
auto inputFiles = QVector<MTPInputSecureFile>();
|
||||
inputFiles.reserve(field.filesInEdit.size());
|
||||
for (const auto &file : field.filesInEdit) {
|
||||
if (const auto uploaded = file.uploaded.get()) {
|
||||
inputFiles.push_back(MTP_inputSecureFileUploaded(
|
||||
MTP_long(file.fields.id),
|
||||
MTP_int(uploaded->partsCount),
|
||||
MTP_bytes(uploaded->md5checksum),
|
||||
MTP_bytes(file.fields.fileHash)));
|
||||
} else {
|
||||
inputFiles.push_back(MTP_inputSecureFile(
|
||||
MTP_long(file.fields.id),
|
||||
MTP_long(file.fields.accessHash)));
|
||||
}
|
||||
}
|
||||
if (field.secret.empty()) {
|
||||
field.secret = GenerateSecretBytes();
|
||||
}
|
||||
const auto encryptedData = EncryptData(
|
||||
SerializeData(field.parsedData),
|
||||
field.secret);
|
||||
const auto fileHashes = ranges::view::all(
|
||||
field.filesInEdit
|
||||
) | ranges::view::transform([](const EditFile &file) {
|
||||
return bytes::make_span(file.fields.fileHash);
|
||||
});
|
||||
const auto valueHash = openssl::Sha256(bytes::concatenate(
|
||||
encryptedData.hash,
|
||||
bytes::concatenate(fileHashes),
|
||||
field.secret));
|
||||
|
||||
const auto encryptedValueSecret = EncryptValueSecret(
|
||||
encrypted.secret,
|
||||
field.encryptedSecret = EncryptValueSecret(
|
||||
field.secret,
|
||||
_secret,
|
||||
valueHash);
|
||||
request(MTPaccount_SaveSecureValue(MTP_inputSecureValueData(
|
||||
MTP_string(data.name),
|
||||
MTP_bytes(encrypted.bytes),
|
||||
MTP_string(valueHashString),
|
||||
MTP_bytes(encryptedValueSecret)
|
||||
))).done([=](const MTPaccount_SecureValueResult &result) {
|
||||
if (result.type() == mtpc_account_secureValueResultSaved) {
|
||||
Ui::show(Box<InformBox>("Saved"), LayerOption::KeepOther);
|
||||
} else if (result.type() == mtpc_account_secureValueVerificationNeeded) {
|
||||
Ui::show(Box<InformBox>("Verification needed :("), LayerOption::KeepOther);
|
||||
}
|
||||
request(MTPaccount_SaveSecureValue(
|
||||
MTP_inputSecureValueIdentity(
|
||||
MTP_secureData(
|
||||
MTP_bytes(encryptedData.bytes),
|
||||
MTP_bytes(encryptedData.hash)),
|
||||
MTP_vector<MTPInputSecureFile>(inputFiles),
|
||||
MTP_bytes(field.encryptedSecret),
|
||||
MTP_bytes(valueHash)),
|
||||
MTP_long(CountSecureSecretHash(_secret))
|
||||
)).done([=](const MTPSecureValueSaved &result) {
|
||||
Ui::show(Box<InformBox>("Saved"), LayerOption::KeepOther);
|
||||
}).fail([=](const RPCError &error) {
|
||||
Ui::show(Box<InformBox>("Error saving value."));
|
||||
}).send();
|
||||
}
|
||||
|
||||
void FormController::saveFiles(int index) {
|
||||
Expects(index >= 0 && index < _form.fields.size());
|
||||
Expects(_form.fields[index].document.has_value());
|
||||
|
||||
if (_secret.empty()) {
|
||||
generateSecret([=] {
|
||||
saveFiles(index);
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto &document = *_form.fields[index].document;
|
||||
if (document.filesSecret.empty()) {
|
||||
Assert(document.filesInEdit.empty());
|
||||
return;
|
||||
}
|
||||
auto filesHash = computeFilesHash(
|
||||
ranges::view::all(
|
||||
document.filesInEdit
|
||||
) | ranges::view::transform([=](const EditFile &file) {
|
||||
return bytes::make_span(file.fields.fileHash);
|
||||
}),
|
||||
document.filesSecret);
|
||||
|
||||
auto filesHashString = QString();
|
||||
filesHashString.reserve(filesHash.size() * 2);
|
||||
const auto hex = [](uchar value) -> QChar {
|
||||
return (value >= 10) ? ('a' + (value - 10)) : ('0' + value);
|
||||
};
|
||||
for (const auto byte : filesHash) {
|
||||
const auto value = uchar(byte);
|
||||
const auto high = uchar(value / 16);
|
||||
const auto low = uchar(value % 16);
|
||||
filesHashString.append(hex(high)).append(hex(low));
|
||||
}
|
||||
|
||||
auto files = QVector<MTPInputSecureFile>();
|
||||
files.reserve(document.filesInEdit.size());
|
||||
for (const auto &file : document.filesInEdit) {
|
||||
if (const auto uploaded = file.uploaded.get()) {
|
||||
auto fileHashString = QString();
|
||||
for (const auto byte : file.fields.fileHash) {
|
||||
const auto value = uchar(byte);
|
||||
const auto high = uchar(value / 16);
|
||||
const auto low = uchar(value % 16);
|
||||
fileHashString.append(hex(high)).append(hex(low));
|
||||
}
|
||||
files.push_back(MTP_inputSecureFileUploaded(
|
||||
MTP_long(file.fields.id),
|
||||
MTP_int(uploaded->partsCount),
|
||||
MTP_bytes(uploaded->md5checksum),
|
||||
MTP_string(fileHashString)));
|
||||
} else {
|
||||
files.push_back(MTP_inputSecureFile(
|
||||
MTP_long(file.fields.id),
|
||||
MTP_long(file.fields.accessHash)));
|
||||
}
|
||||
}
|
||||
|
||||
const auto encryptedFilesSecret = EncryptValueSecret(
|
||||
document.filesSecret,
|
||||
_secret,
|
||||
filesHash);
|
||||
request(MTPaccount_SaveSecureValue(MTP_inputSecureValueFile(
|
||||
MTP_string(document.name),
|
||||
MTP_vector<MTPInputSecureFile>(files),
|
||||
MTP_string(filesHashString),
|
||||
MTP_bytes(encryptedFilesSecret)
|
||||
))).done([=](const MTPaccount_SecureValueResult &result) {
|
||||
if (result.type() == mtpc_account_secureValueResultSaved) {
|
||||
Ui::show(Box<InformBox>("Files Saved"), LayerOption::KeepOther);
|
||||
} else if (result.type() == mtpc_account_secureValueVerificationNeeded) {
|
||||
Ui::show(Box<InformBox>("Verification needed :("), LayerOption::KeepOther);
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
Ui::show(Box<InformBox>("Error saving files."));
|
||||
}).send();
|
||||
}
|
||||
|
||||
void FormController::generateSecret(base::lambda<void()> callback) {
|
||||
_secretCallbacks.push_back(callback);
|
||||
void FormController::generateSecret(bytes::const_span password) {
|
||||
if (_saveSecretRequestId) {
|
||||
return;
|
||||
}
|
||||
auto secret = GenerateSecretBytes();
|
||||
|
||||
auto randomSaltPart = bytes::vector(8);
|
||||
bytes::set_random(randomSaltPart);
|
||||
auto newSecureSaltFull = bytes::concatenate(
|
||||
_password.newSecureSalt,
|
||||
randomSaltPart);
|
||||
|
||||
const auto hashForSecret = openssl::Sha512(bytes::concatenate(
|
||||
newSecureSaltFull,
|
||||
password,
|
||||
newSecureSaltFull));
|
||||
const auto hashForAuth = openssl::Sha256(bytes::concatenate(
|
||||
_password.salt,
|
||||
password,
|
||||
_password.salt));
|
||||
|
||||
auto secureSecretHash = CountSecureSecretHash(secret);
|
||||
auto encryptedSecret = EncryptSecretBytes(
|
||||
secret,
|
||||
_passwordHashForSecret);
|
||||
hashForSecret);
|
||||
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
||||
_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
|
||||
MTP_bytes(_passwordHashForAuth),
|
||||
MTP_bytes(hashForAuth),
|
||||
MTP_account_passwordInputSettings(
|
||||
MTP_flags(Flag::f_new_secure_secret),
|
||||
MTPbytes(), // new_salt
|
||||
MTPbytes(), // new_password_hash
|
||||
MTPstring(), // hint
|
||||
MTPstring(), // email
|
||||
MTP_bytes(encryptedSecret))
|
||||
MTP_bytes(newSecureSaltFull),
|
||||
MTP_bytes(encryptedSecret),
|
||||
MTP_long(secureSecretHash))
|
||||
)).done([=](const MTPBool &result) {
|
||||
_saveSecretRequestId = 0;
|
||||
_secret = secret;
|
||||
//_password.salt = newPasswordSaltFull;
|
||||
for (const auto &callback : base::take(_secretCallbacks)) {
|
||||
callback();
|
||||
}
|
||||
|
@ -583,23 +581,12 @@ void FormController::generateSecret(base::lambda<void()> callback) {
|
|||
}
|
||||
|
||||
void FormController::requestForm() {
|
||||
auto scope = QVector<MTPstring>();
|
||||
scope.reserve(_request.scope.size());
|
||||
for (const auto &element : _request.scope) {
|
||||
scope.push_back(MTP_string(element));
|
||||
}
|
||||
auto normalizedKey = _request.publicKey;
|
||||
normalizedKey.replace("\r\n", "\n");
|
||||
const auto bytes = normalizedKey.toUtf8();
|
||||
_formRequestId = request(MTPaccount_GetAuthorizationForm(
|
||||
MTP_flags(MTPaccount_GetAuthorizationForm::Flag::f_origin
|
||||
| MTPaccount_GetAuthorizationForm::Flag::f_public_key),
|
||||
MTP_int(_request.botId),
|
||||
MTP_vector<MTPstring>(std::move(scope)),
|
||||
MTP_string(_origin),
|
||||
MTPstring(), // package_name
|
||||
MTPstring(), // bundle_id
|
||||
MTP_bytes(bytes)
|
||||
MTP_string(_request.scope),
|
||||
MTP_bytes(normalizedKey.toUtf8())
|
||||
)).done([=](const MTPaccount_AuthorizationForm &result) {
|
||||
_formRequestId = 0;
|
||||
formDone(result);
|
||||
|
@ -608,130 +595,110 @@ void FormController::requestForm() {
|
|||
}).send();
|
||||
}
|
||||
|
||||
auto FormController::convertValue(
|
||||
const MTPSecureValue &value) const -> Value {
|
||||
auto result = Value();
|
||||
switch (value.type()) {
|
||||
case mtpc_secureValueEmpty: {
|
||||
const auto &data = value.c_secureValueEmpty();
|
||||
result.name = qs(data.vname);
|
||||
} break;
|
||||
case mtpc_secureValueData: {
|
||||
const auto &data = value.c_secureValueData();
|
||||
result.name = qs(data.vname);
|
||||
result.dataEncrypted = data.vdata.v;
|
||||
const auto hashString = qs(data.vhash);
|
||||
for (auto i = 0, count = hashString.size(); i + 1 < count; i += 2) {
|
||||
auto digit = [&](QChar ch) -> int {
|
||||
const auto code = ch.unicode();
|
||||
if (code >= 'a' && code <= 'f') {
|
||||
return (code - 'a') + 10;
|
||||
} else if (code >= 'A' && code <= 'F') {
|
||||
return (code - 'A') + 10;
|
||||
} else if (code >= '0' && code <= '9') {
|
||||
return (code - '0');
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
const auto ch1 = digit(hashString[i]);
|
||||
const auto ch2 = digit(hashString[i + 1]);
|
||||
if (ch1 >= 0 && ch2 >= 0) {
|
||||
const auto byte = ch1 * 16 + ch2;
|
||||
result.dataHash.push_back(byte);
|
||||
}
|
||||
template <typename DataType>
|
||||
auto FormController::parseEncryptedField(
|
||||
Field::Type type,
|
||||
const DataType &data) const -> Field {
|
||||
Expects(data.vdata.type() == mtpc_secureData);
|
||||
|
||||
auto result = Field(type);
|
||||
if (data.has_verified()) {
|
||||
result.verification = parseVerified(data.vverified);
|
||||
}
|
||||
result.encryptedSecret = bytes::make_vector(data.vsecret.v);
|
||||
result.hash = bytes::make_vector(data.vhash.v);
|
||||
const auto &fields = data.vdata.c_secureData();
|
||||
result.originalData = fields.vdata.v;
|
||||
result.dataHash = bytes::make_vector(fields.vdata_hash.v);
|
||||
for (const auto &file : data.vfiles.v) {
|
||||
switch (file.type()) {
|
||||
case mtpc_secureFileEmpty: {
|
||||
result.files.push_back(File());
|
||||
} break;
|
||||
case mtpc_secureFile: {
|
||||
const auto &fields = file.c_secureFile();
|
||||
auto normal = File();
|
||||
normal.id = fields.vid.v;
|
||||
normal.accessHash = fields.vaccess_hash.v;
|
||||
normal.size = fields.vsize.v;
|
||||
normal.dcId = fields.vdc_id.v;
|
||||
normal.fileHash = bytes::make_vector(fields.vfile_hash.v);
|
||||
result.files.push_back(std::move(normal));
|
||||
} break;
|
||||
}
|
||||
if (result.dataHash.size() != 32) {
|
||||
result.dataHash.clear();
|
||||
}
|
||||
// result.dataHash = data.vhash.v;
|
||||
result.dataSecretEncrypted = data.vsecret.v;
|
||||
} break;
|
||||
case mtpc_secureValueText: {
|
||||
const auto &data = value.c_secureValueText();
|
||||
result.name = qs(data.vname);
|
||||
result.text = qs(data.vtext);
|
||||
result.textHash = data.vhash.v;
|
||||
} break;
|
||||
case mtpc_secureValueFile: {
|
||||
const auto &data = value.c_secureValueFile();
|
||||
result.name = qs(data.vname);
|
||||
const auto hashString = qs(data.vhash);
|
||||
for (auto i = 0, count = hashString.size(); i + 1 < count; i += 2) {
|
||||
auto digit = [&](QChar ch) -> int {
|
||||
const auto code = ch.unicode();
|
||||
if (code >= 'a' && code <= 'f') {
|
||||
return (code - 'a') + 10;
|
||||
} else if (code >= 'A' && code <= 'F') {
|
||||
return (code - 'A') + 10;
|
||||
} else if (code >= '0' && code <= '9') {
|
||||
return (code - '0');
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
const auto ch1 = digit(hashString[i]);
|
||||
const auto ch2 = digit(hashString[i + 1]);
|
||||
if (ch1 >= 0 && ch2 >= 0) {
|
||||
const auto byte = ch1 * 16 + ch2;
|
||||
result.filesHash.push_back(byte);
|
||||
}
|
||||
}
|
||||
if (result.filesHash.size() != 32) {
|
||||
result.filesHash.clear();
|
||||
}
|
||||
// result.filesHash = data.vhash.v;
|
||||
result.filesSecretEncrypted = data.vsecret.v;
|
||||
for (const auto &file : data.vfile.v) {
|
||||
switch (file.type()) {
|
||||
case mtpc_secureFileEmpty: {
|
||||
result.files.push_back(File());
|
||||
} break;
|
||||
case mtpc_secureFile: {
|
||||
const auto &fields = file.c_secureFile();
|
||||
auto normal = File();
|
||||
normal.id = fields.vid.v;
|
||||
normal.accessHash = fields.vaccess_hash.v;
|
||||
normal.size = fields.vsize.v;
|
||||
normal.dcId = fields.vdc_id.v;
|
||||
const auto fileHashString = qs(fields.vfile_hash);
|
||||
for (auto i = 0, count = fileHashString.size(); i + 1 < count; i += 2) {
|
||||
auto digit = [&](QChar ch) -> int {
|
||||
const auto code = ch.unicode();
|
||||
if (code >= 'a' && code <= 'f') {
|
||||
return (code - 'a') + 10;
|
||||
} else if (code >= 'A' && code <= 'F') {
|
||||
return (code - 'A') + 10;
|
||||
} else if (code >= '0' && code <= '9') {
|
||||
return (code - '0');
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
const auto ch1 = digit(fileHashString[i]);
|
||||
const auto ch2 = digit(fileHashString[i + 1]);
|
||||
if (ch1 >= 0 && ch2 >= 0) {
|
||||
const auto byte = ch1 * 16 + ch2;
|
||||
normal.fileHash.push_back(gsl::byte(byte));
|
||||
}
|
||||
}
|
||||
if (normal.fileHash.size() != 32) {
|
||||
normal.fileHash.clear();
|
||||
}
|
||||
// normal.fileHash = bytes::make_vector(fields.vfile_hash.v);
|
||||
result.files.push_back(std::move(normal));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename DataType>
|
||||
auto FormController::parsePlainTextField(
|
||||
Field::Type type,
|
||||
const QByteArray &value,
|
||||
const DataType &data) const -> Field {
|
||||
auto result = Field(type);
|
||||
const auto check = bytes::compare(
|
||||
bytes::make_span(data.vhash.v),
|
||||
openssl::Sha256(bytes::make_span(value)));
|
||||
if (check != 0) {
|
||||
LOG(("API Error: Bad hash for plain text value. "
|
||||
"Value '%1', hash '%2'"
|
||||
).arg(QString::fromUtf8(value)
|
||||
).arg(Logs::mb(data.vhash.v.data(), data.vhash.v.size()).str()
|
||||
));
|
||||
return result;
|
||||
}
|
||||
result.parsedData[QString("value")] = QString::fromUtf8(value);
|
||||
if (data.has_verified()) {
|
||||
result.verification = parseVerified(data.vverified);
|
||||
}
|
||||
result.hash = bytes::make_vector(data.vhash.v);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto FormController::parseValue(
|
||||
const MTPSecureValue &value) const -> Field {
|
||||
switch (value.type()) {
|
||||
case mtpc_secureValueIdentity: {
|
||||
return parseEncryptedField(
|
||||
Field::Type::Identity,
|
||||
value.c_secureValueIdentity());
|
||||
} break;
|
||||
case mtpc_secureValueAddress: {
|
||||
return parseEncryptedField(
|
||||
Field::Type::Address,
|
||||
value.c_secureValueAddress());
|
||||
} break;
|
||||
case mtpc_secureValuePhone: {
|
||||
const auto &data = value.c_secureValuePhone();
|
||||
return parsePlainTextField(
|
||||
Field::Type::Phone,
|
||||
data.vphone.v,
|
||||
data);
|
||||
} break;
|
||||
case mtpc_secureValueEmail: {
|
||||
const auto &data = value.c_secureValueEmail();
|
||||
return parsePlainTextField(
|
||||
Field::Type::Phone,
|
||||
data.vemail.v,
|
||||
data);
|
||||
} break;
|
||||
}
|
||||
Unexpected("secureValue type.");
|
||||
}
|
||||
|
||||
auto FormController::parseVerified(const MTPSecureValueVerified &data) const
|
||||
-> Verification {
|
||||
Expects(data.type() == mtpc_secureValueVerified);
|
||||
|
||||
const auto &fields = data.c_secureValueVerified();
|
||||
return Verification{ fields.vdate.v, qs(fields.vprovider) };
|
||||
}
|
||||
|
||||
auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
|
||||
for (auto &field : _form.fields) {
|
||||
if (auto &document = field.document) {
|
||||
for (auto &file : document->filesInEdit) {
|
||||
if (file.uploaded && file.uploaded->fullId == fullId) {
|
||||
return &file;
|
||||
}
|
||||
for (auto &file : field.filesInEdit) {
|
||||
if (file.uploaded && file.uploaded->fullId == fullId) {
|
||||
return &file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -741,11 +708,9 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
|
|||
auto FormController::findFile(const FileKey &key)
|
||||
-> std::pair<Field*, File*> {
|
||||
for (auto &field : _form.fields) {
|
||||
if (auto &document = field.document) {
|
||||
for (auto &file : document->files) {
|
||||
if (file.dcId == key.dcId && file.id == key.id) {
|
||||
return { &field, &file };
|
||||
}
|
||||
for (auto &file : field.files) {
|
||||
if (file.dcId == key.dcId && file.id == key.id) {
|
||||
return { &field, &file };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -763,33 +728,40 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
|
|||
Expects(result.type() == mtpc_account_authorizationForm);
|
||||
|
||||
const auto &data = result.c_account_authorizationForm();
|
||||
_form.requestWrite = data.is_request_write();
|
||||
if (_request.botId != data.vbot_id.v) {
|
||||
LOG(("API Error: Wrong account.authorizationForm.bot_id."));
|
||||
_request.botId = data.vbot_id.v;
|
||||
}
|
||||
App::feedUsers(data.vusers);
|
||||
for (const auto &field : data.vfields.v) {
|
||||
Assert(field.type() == mtpc_authField);
|
||||
|
||||
auto values = std::vector<Field>();
|
||||
for (const auto &value : data.vvalues.v) {
|
||||
values.push_back(parseValue(value));
|
||||
}
|
||||
const auto findValue = [&](Field::Type type) -> Field* {
|
||||
for (auto &value : values) {
|
||||
if (value.type == type) {
|
||||
return &value;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
_form.requestWrite = false;
|
||||
App::feedUsers(data.vusers);
|
||||
for (const auto &required : data.vrequired_types.v) {
|
||||
using Type = Field::Type;
|
||||
|
||||
const auto &data = field.c_authField();
|
||||
const auto type = [&] {
|
||||
switch (data.vtype.type()) {
|
||||
case mtpc_authFieldTypeIdentity: return Type::Identity;
|
||||
case mtpc_authFieldTypeAddress: return Type::Address;
|
||||
case mtpc_authFieldTypeEmail: return Type::Email;
|
||||
case mtpc_authFieldTypePhone: return Type::Phone;
|
||||
switch (required.type()) {
|
||||
case mtpc_secureValueTypeIdentity: return Type::Identity;
|
||||
case mtpc_secureValueTypeAddress: return Type::Address;
|
||||
case mtpc_secureValueTypeEmail: return Type::Email;
|
||||
case mtpc_secureValueTypePhone: return Type::Phone;
|
||||
}
|
||||
Unexpected("Type in authField.type.");
|
||||
Unexpected("Type in secureValueType type.");
|
||||
}();
|
||||
auto entry = Field(type);
|
||||
entry.data = convertValue(data.vdata);
|
||||
if (data.has_document()) {
|
||||
entry.document = convertValue(data.vdocument);
|
||||
|
||||
if (auto field = findValue(type)) {
|
||||
_form.fields.push_back(std::move(*field));
|
||||
} else {
|
||||
_form.fields.push_back(Field(type));
|
||||
}
|
||||
_form.fields.push_back(std::move(entry));
|
||||
}
|
||||
_bot = App::userLoaded(_request.botId);
|
||||
}
|
||||
|
@ -837,17 +809,19 @@ void FormController::passwordFail(const RPCError &error) {
|
|||
|
||||
void FormController::parsePassword(const MTPDaccount_noPassword &result) {
|
||||
_password.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern);
|
||||
_password.newSalt = result.vnew_salt.v;
|
||||
openssl::AddRandomSeed(bytes::make_span(result.vsecret_random.v));
|
||||
_password.newSalt = bytes::make_vector(result.vnew_salt.v);
|
||||
_password.newSecureSalt = bytes::make_vector(result.vnew_secure_salt.v);
|
||||
openssl::AddRandomSeed(bytes::make_span(result.vsecure_random.v));
|
||||
}
|
||||
|
||||
void FormController::parsePassword(const MTPDaccount_password &result) {
|
||||
_password.hint = qs(result.vhint);
|
||||
_password.hasRecovery = mtpIsTrue(result.vhas_recovery);
|
||||
_password.salt = result.vcurrent_salt.v;
|
||||
_password.salt = bytes::make_vector(result.vcurrent_salt.v);
|
||||
_password.unconfirmedPattern = qs(result.vemail_unconfirmed_pattern);
|
||||
_password.newSalt = result.vnew_salt.v;
|
||||
openssl::AddRandomSeed(bytes::make_span(result.vsecret_random.v));
|
||||
_password.newSalt = bytes::make_vector(result.vnew_salt.v);
|
||||
_password.newSecureSalt = bytes::make_vector(result.vnew_secure_salt.v);
|
||||
openssl::AddRandomSeed(bytes::make_span(result.vsecure_random.v));
|
||||
}
|
||||
|
||||
FormController::~FormController() = default;
|
||||
|
|
|
@ -25,12 +25,12 @@ namespace Passport {
|
|||
struct FormRequest {
|
||||
FormRequest(
|
||||
UserId botId,
|
||||
const QStringList &scope,
|
||||
const QString &scope,
|
||||
const QString &callbackUrl,
|
||||
const QString &publicKey);
|
||||
|
||||
UserId botId;
|
||||
QStringList scope;
|
||||
QString scope;
|
||||
QString callbackUrl;
|
||||
QString publicKey;
|
||||
|
||||
|
@ -128,23 +128,9 @@ private:
|
|||
File fields;
|
||||
std::unique_ptr<UploadedScan> uploaded;
|
||||
};
|
||||
struct Value {
|
||||
QString name;
|
||||
|
||||
QByteArray dataEncrypted;
|
||||
QByteArray dataHash;
|
||||
QByteArray dataSecretEncrypted;
|
||||
std::map<QString, QString> values;
|
||||
|
||||
QString text;
|
||||
QByteArray textHash;
|
||||
|
||||
std::vector<File> files;
|
||||
QByteArray filesHash;
|
||||
QByteArray filesSecretEncrypted;
|
||||
bytes::vector filesSecret;
|
||||
|
||||
std::vector<EditFile> filesInEdit;
|
||||
struct Verification {
|
||||
TimeId date;
|
||||
QString provider;
|
||||
};
|
||||
struct Field {
|
||||
enum class Type {
|
||||
|
@ -153,25 +139,36 @@ private:
|
|||
Phone,
|
||||
Email,
|
||||
};
|
||||
|
||||
explicit Field(Type type);
|
||||
Field(Field &&other) = default;
|
||||
Field &operator=(Field &&other) = default;
|
||||
|
||||
Type type;
|
||||
Value data;
|
||||
base::optional<Value> document;
|
||||
QByteArray originalData;
|
||||
std::map<QString, QString> parsedData;
|
||||
bytes::vector dataHash;
|
||||
std::vector<File> files;
|
||||
std::vector<EditFile> filesInEdit;
|
||||
bytes::vector secret;
|
||||
bytes::vector encryptedSecret;
|
||||
bytes::vector hash;
|
||||
base::optional<Verification> verification;
|
||||
};
|
||||
struct Form {
|
||||
bool requestWrite = false;
|
||||
std::vector<Field> fields;
|
||||
};
|
||||
struct PasswordSettings {
|
||||
QByteArray salt;
|
||||
QByteArray newSalt;
|
||||
bytes::vector salt;
|
||||
bytes::vector newSalt;
|
||||
bytes::vector newSecureSalt;
|
||||
QString hint;
|
||||
QString unconfirmedPattern;
|
||||
QString confirmedEmail;
|
||||
bool hasRecovery = false;
|
||||
};
|
||||
Value convertValue(const MTPSecureValue &value) const;
|
||||
|
||||
EditFile *findEditFile(const FullMsgId &fullId);
|
||||
std::pair<Field*, File*> findFile(const FileKey &key);
|
||||
|
||||
|
@ -182,21 +179,39 @@ private:
|
|||
void formFail(const RPCError &error);
|
||||
void parseForm(const MTPaccount_AuthorizationForm &result);
|
||||
void showForm();
|
||||
Field parseValue(const MTPSecureValue &value) const;
|
||||
template <typename DataType>
|
||||
Field parseEncryptedField(
|
||||
Field::Type type,
|
||||
const DataType &data) const;
|
||||
template <typename DataType>
|
||||
Field parsePlainTextField(
|
||||
Field::Type type,
|
||||
const QByteArray &value,
|
||||
const DataType &data) const;
|
||||
Verification parseVerified(const MTPSecureValueVerified &data) const;
|
||||
|
||||
void passwordDone(const MTPaccount_Password &result);
|
||||
void passwordFail(const RPCError &error);
|
||||
void parsePassword(const MTPDaccount_noPassword &settings);
|
||||
void parsePassword(const MTPDaccount_password &settings);
|
||||
bytes::vector passwordHashForAuth(bytes::const_span password) const;
|
||||
void validateSecureSecret(
|
||||
bytes::const_span salt,
|
||||
bytes::const_span encryptedSecret,
|
||||
bytes::const_span password);
|
||||
void decryptFields();
|
||||
void decryptField(Field &field);
|
||||
bool validateFieldSecret(Field &field);
|
||||
void resetField(Field &field);
|
||||
|
||||
IdentityData fieldDataIdentity(const Field &field) const;
|
||||
std::vector<ScanInfo> fieldFilesIdentity(const Field &field) const;
|
||||
void saveIdentity(int index);
|
||||
|
||||
void loadFiles(const std::vector<File> &files);
|
||||
void fileLoaded(FileKey key, const QByteArray &bytes);
|
||||
std::map<QString, QString> fillData(const Value &from) const;
|
||||
void saveData(int index);
|
||||
void saveFiles(int index);
|
||||
void generateSecret(base::lambda<void()> callback);
|
||||
void generateSecret(bytes::const_span password);
|
||||
|
||||
template <typename FileHashes>
|
||||
bytes::vector computeFilesHash(
|
||||
|
@ -210,7 +225,6 @@ private:
|
|||
not_null<Window::Controller*> _controller;
|
||||
FormRequest _request;
|
||||
UserData *_bot = nullptr;
|
||||
QString _origin;
|
||||
|
||||
mtpRequestId _formRequestId = 0;
|
||||
mtpRequestId _passwordRequestId = 0;
|
||||
|
@ -221,12 +235,9 @@ private:
|
|||
std::map<FileKey, std::unique_ptr<mtpFileLoader>> _fileLoaders;
|
||||
rpl::event_stream<ScanInfo> _scanUpdated;
|
||||
|
||||
bytes::vector _passwordHashForSecret;
|
||||
bytes::vector _passwordHashForAuth;
|
||||
bytes::vector _secret;
|
||||
std::vector<base::lambda<void()>> _secretCallbacks;
|
||||
mtpRequestId _saveSecretRequestId = 0;
|
||||
QString _passwordEmail;
|
||||
rpl::event_stream<> _secretReady;
|
||||
rpl::event_stream<QString> _passwordError;
|
||||
|
||||
|
|
|
@ -85,12 +85,14 @@ void CloudPasswordState::onTurnOff() {
|
|||
MTPaccount_UpdatePasswordSettings(
|
||||
MTP_bytes(QByteArray()),
|
||||
MTP_account_passwordInputSettings(
|
||||
MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email),
|
||||
MTP_bytes(QByteArray()),
|
||||
MTP_bytes(QByteArray()),
|
||||
MTP_string(QString()),
|
||||
MTP_string(QString()),
|
||||
MTP_bytes(QByteArray()))),
|
||||
MTP_flags(MTPDaccount_passwordInputSettings::Flag::f_email),
|
||||
MTP_bytes(QByteArray()), // new_salt
|
||||
MTP_bytes(QByteArray()), // new_password_hash
|
||||
MTP_string(QString()), // hint
|
||||
MTP_string(QString()), // email
|
||||
MTP_bytes(QByteArray()), // new_secure_salt
|
||||
MTP_bytes(QByteArray()), // new_secure_secret
|
||||
MTP_long(0))), // new_secure_secret_hash
|
||||
rpcDone(&CloudPasswordState::offPasswordDone),
|
||||
rpcFail(&CloudPasswordState::offPasswordFail));
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue