Update API scheme.

This commit is contained in:
John Preston 2018-03-29 18:44:34 +04:00
parent 2bc60fa54f
commit b0a9d26a94
10 changed files with 370 additions and 338 deletions

View File

@ -281,7 +281,7 @@ messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDis
messageActionScreenshotTaken#4792929b = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction;
messageActionCustomAction#fae69f56 message:string = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction;
messageActionBotAllowed#abe9affe domain:string = MessageAction; messageActionBotAllowed#abe9affe domain:string = MessageAction;
messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSentMe#9bc8ec4 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted payload:bytes = MessageAction;
messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = 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; 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;
@ -589,9 +589,9 @@ account.authorizations#1250abde authorizations:Vector<Authorization> = account.A
account.noPassword#5ea182f6 new_salt:bytes new_secure_salt:bytes secure_random:bytes 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.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#48ec1750 email:string secure_salt:bytes secure_secret:bytes secure_secret_hash:long = account.PasswordSettings; account.passwordSettings#7bd9c3f1 email:string secure_salt:bytes secure_secret:bytes secure_secret_id:long = account.PasswordSettings;
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; account.passwordInputSettings#21ffa60d 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_id:flags.2?long = account.PasswordInputSettings;
auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
@ -967,28 +967,28 @@ help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vect
help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate; help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate;
help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = 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:bytes = InputSecureFile; inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash:bytes secret:bytes = InputSecureFile;
inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile; inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;
secureFileEmpty#64199744 = SecureFile; secureFileEmpty#64199744 = SecureFile;
secureFile#40ad69ba id:long access_hash:long size:int dc_id:int file_hash:bytes = SecureFile; secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_hash:bytes secret:bytes = SecureFile;
secureData#262b0134 data:bytes data_hash:bytes = SecureData; secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData;
secureValueVerified#612d86df date:int provider:string = SecureValueVerified; secureValueVerified#2c8e61e2 date:int = SecureValueVerified;
secureValueTypeIdentity#37da58ca = SecureValueType; secureValueTypeIdentity#37da58ca = SecureValueType;
secureValueTypeAddress#cbe31e26 = SecureValueType; secureValueTypeAddress#cbe31e26 = SecureValueType;
secureValueTypePhone#b320aadb = SecureValueType; secureValueTypePhone#b320aadb = SecureValueType;
secureValueTypeEmail#8e3ca7ee = SecureValueType; secureValueTypeEmail#8e3ca7ee = SecureValueType;
secureValueIdentity#a6c927ad flags:# data:SecureData files:Vector<SecureFile> secret:bytes hash:bytes verified:flags.0?SecureValueVerified = SecureValue; secureValueIdentity#4838ff84 flags:# data:SecureData files:Vector<SecureFile> hash:bytes verified:flags.0?SecureValueVerified = SecureValue;
secureValueAddress#74a0d79c flags:# data:SecureData files:Vector<SecureFile> secret:bytes hash:bytes verified:flags.0?SecureValueVerified = SecureValue; secureValueAddress#2b9f6bef flags:# data:SecureData files:Vector<SecureFile> hash:bytes verified:flags.0?SecureValueVerified = SecureValue;
secureValuePhone#a1ca84fe flags:# phone:string 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; secureValueEmail#c4db6579 flags:# email:string hash:bytes verified:flags.0?SecureValueVerified = SecureValue;
inputSecureValueIdentity#ae27d606 data:SecureData files:Vector<InputSecureFile> secret:bytes hash:bytes = InputSecureValue; inputSecureValueIdentity#df93404 data:SecureData files:Vector<InputSecureFile> hash:bytes = InputSecureValue;
inputSecureValueAddress#c1f733e5 data:SecureData files:Vector<InputSecureFile> secret:bytes hash:bytes = InputSecureValue; inputSecureValueAddress#5589502 data:SecureData files:Vector<InputSecureFile> hash:bytes = InputSecureValue;
inputSecureValuePhone#141e00b8 phone:string hash:bytes = InputSecureValue; inputSecureValuePhone#141e00b8 phone:string hash:bytes = InputSecureValue;
inputSecureValueEmail#2dc15b9a email:string hash:bytes = InputSecureValue; inputSecureValueEmail#2dc15b9a email:string hash:bytes = InputSecureValue;
@ -996,9 +996,9 @@ secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
secureValueSaved#c9147049 files:Vector<SecureFile> = SecureValueSaved; secureValueSaved#c9147049 files:Vector<SecureFile> = SecureValueSaved;
secureCredentialsEncrypted#628fe12a data:bytes secret:bytes hash:bytes = SecureCredentialsEncrypted; secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
account.authorizationForm#8d9dddeb flags:# accepted:flags.0?true required_types:Vector<SecureValueType> values:Vector<SecureValue> users:Vector<User> = account.AuthorizationForm; account.authorizationForm#4cace8c4 required_types:Vector<SecureValueType> values:Vector<SecureValue> users:Vector<User> = account.AuthorizationForm;
account.sentEmailCode#28b1633b email_pattern:string = account.SentEmailCode; account.sentEmailCode#28b1633b email_pattern:string = account.SentEmailCode;
@ -1058,10 +1058,10 @@ account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
account.resetWebAuthorization#2d01b9ef hash:long = Bool; account.resetWebAuthorization#2d01b9ef hash:long = Bool;
account.resetWebAuthorizations#682d2594 = Bool; account.resetWebAuthorizations#682d2594 = Bool;
account.getSecureValue#d97e77cb user_id:InputUser types:Vector<SecureValueType> = Vector<SecureValue>; account.getSecureValue#d97e77cb user_id:InputUser types:Vector<SecureValueType> = Vector<SecureValue>;
account.saveSecureValue#842659a5 value:InputSecureValue secure_secret_hash:long = SecureValueSaved; account.saveSecureValue#78969d0b value:InputSecureValue secure_secret_id:long = SecureValueSaved;
account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool; account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool;
account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm; 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.acceptAuthorization#8d5e02e6 bot_id:int scope:string public_key:string value_hashes:Vector<SecureValueHash> credentials:SecureCredentialsEncrypted payload:bytes = Bool;
account.sendVerifyPhoneCode#823380b4 flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode; 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.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode;

View File

@ -494,10 +494,6 @@ int32 *hashSha256(const void *data, uint32 len, void *dest) {
return (int32*)SHA256((const uchar*)data, (size_t)len, (uchar*)dest); return (int32*)SHA256((const uchar*)data, (size_t)len, (uchar*)dest);
} }
int32 *hashSha512(const void *data, uint32 len, void *dest) {
return (int32*)SHA512((const uchar*)data, (size_t)len, (uchar*)dest);
}
// md5 hash, taken somewhere from the internet // md5 hash, taken somewhere from the internet
namespace { namespace {

View File

@ -909,7 +909,7 @@ bool Messenger::openLocalUrl(const QString &url) {
auto params = url_parse_params(proxyMatch->captured(1), UrlParamNameTransform::ToLower); auto params = url_parse_params(proxyMatch->captured(1), UrlParamNameTransform::ToLower);
ProxiesBoxController::ShowApplyConfirmation(ProxyData::Type::Mtproto, params); ProxiesBoxController::ShowApplyConfirmation(ProxyData::Type::Mtproto, params);
return true; return true;
} else if (auto authMatch = regex_match(qsl("^auth/?\\?(.+)(#|$)"), command, matchOptions)) { } else if (auto authMatch = regex_match(qsl("^secureid/?\\?(.+)(#|$)"), command, matchOptions)) {
const auto params = url_parse_params( const auto params = url_parse_params(
authMatch->captured(1), authMatch->captured(1),
UrlParamNameTransform::ToLower); UrlParamNameTransform::ToLower);

View File

@ -143,11 +143,11 @@ void ScanButton::paintEvent(QPaintEvent *e) {
IdentityBox::IdentityBox( IdentityBox::IdentityBox(
QWidget*, QWidget*,
not_null<FormController*> controller, not_null<FormController*> controller,
int fieldIndex, int valueIndex,
const IdentityData &data, const IdentityData &data,
std::vector<ScanInfo> &&files) std::vector<ScanInfo> &&files)
: _controller(controller) : _controller(controller)
, _fieldIndex(fieldIndex) , _valueIndex(valueIndex)
, _files(std::move(files)) , _files(std::move(files))
, _uploadScan(this, "Upload scans") // #TODO langs , _uploadScan(this, "Upload scans") // #TODO langs
, _name( , _name(
@ -175,7 +175,7 @@ void IdentityBox::prepare() {
_scans.back()->resizeToWidth(st::boxWideWidth); _scans.back()->resizeToWidth(st::boxWideWidth);
_scans.back()->deleteClicks( _scans.back()->deleteClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
_controller->deleteScan(_fieldIndex, index - 1); _controller->deleteScan(_valueIndex, index - 1);
}, lifetime()); }, lifetime());
} }
@ -294,14 +294,14 @@ void IdentityBox::encryptScan(const QString &path) {
} }
void IdentityBox::encryptScanContent(QByteArray &&content) { void IdentityBox::encryptScanContent(QByteArray &&content) {
_controller->uploadScan(_fieldIndex, std::move(content)); _controller->uploadScan(_valueIndex, std::move(content));
} }
void IdentityBox::save() { void IdentityBox::save() {
auto data = IdentityData(); auto data = IdentityData();
data.name = _name->getLastText(); data.name = _name->getLastText();
data.surname = _surname->getLastText(); data.surname = _surname->getLastText();
_controller->saveFieldIdentity(_fieldIndex, data); _controller->saveValueIdentity(_valueIndex, data);
} }
} // namespace Passport } // namespace Passport

View File

@ -30,7 +30,7 @@ public:
IdentityBox( IdentityBox(
QWidget*, QWidget*,
not_null<FormController*> controller, not_null<FormController*> controller,
int fieldIndex, int valueIndex,
const IdentityData &data, const IdentityData &data,
std::vector<ScanInfo> &&files); std::vector<ScanInfo> &&files);
@ -50,7 +50,7 @@ private:
void save(); void save();
not_null<FormController*> _controller; not_null<FormController*> _controller;
int _fieldIndex = -1; int _valueIndex = -1;
std::vector<ScanInfo> _files; std::vector<ScanInfo> _files;

View File

@ -26,8 +26,8 @@ struct AesParams {
bytes::vector iv; bytes::vector iv;
}; };
AesParams PrepareAesParams(bytes::const_span secretHash) { AesParams PrepareAesParams(bytes::const_span bytesForEncryptionKey) {
const auto hash = openssl::Sha512(secretHash); const auto hash = openssl::Sha512(bytesForEncryptionKey);
const auto view = gsl::make_span(hash); const auto view = gsl::make_span(hash);
auto result = AesParams(); auto result = AesParams();
@ -82,14 +82,6 @@ bytes::vector Decrypt(
return EncryptOrDecrypt(encrypted, std::move(params), AES_DECRYPT); return EncryptOrDecrypt(encrypted, std::move(params), AES_DECRYPT);
} }
bytes::vector PasswordHashForSecret(
bytes::const_span passwordUtf8) {
//new_secure_salt = new_salt + random_bytes(8) // #TODO
//password_hash = SHA512(new_secure_salt + password + new_secure_salt)
const auto result = openssl::Sha512(passwordUtf8);
return { result.begin(), result.end() };
}
bool CheckBytesMod255(bytes::const_span bytes) { bool CheckBytesMod255(bytes::const_span bytes) {
const auto full = ranges::accumulate( const auto full = ranges::accumulate(
bytes, bytes,
@ -119,7 +111,7 @@ bytes::vector GenerateSecretBytes() {
bytes::vector DecryptSecretBytes( bytes::vector DecryptSecretBytes(
bytes::const_span encryptedSecret, bytes::const_span encryptedSecret,
bytes::const_span passwordHashForSecret) { bytes::const_span bytesForEncryptionKey) {
if (encryptedSecret.empty()) { if (encryptedSecret.empty()) {
return {}; return {};
} else if (encryptedSecret.size() != kSecretSize) { } else if (encryptedSecret.size() != kSecretSize) {
@ -127,7 +119,7 @@ bytes::vector DecryptSecretBytes(
).arg(encryptedSecret.size())); ).arg(encryptedSecret.size()));
return {}; return {};
} }
auto params = PrepareAesParams(passwordHashForSecret); auto params = PrepareAesParams(bytesForEncryptionKey);
auto result = Decrypt(encryptedSecret, std::move(params)); auto result = Decrypt(encryptedSecret, std::move(params));
if (!CheckSecretBytes(result)) { if (!CheckSecretBytes(result)) {
LOG(("API Error: Bad secret bytes.")); LOG(("API Error: Bad secret bytes."));
@ -138,11 +130,11 @@ bytes::vector DecryptSecretBytes(
bytes::vector EncryptSecretBytes( bytes::vector EncryptSecretBytes(
bytes::const_span secret, bytes::const_span secret,
bytes::const_span passwordHashForSecret) { bytes::const_span bytesForEncryptionKey) {
Expects(secret.size() == kSecretSize); Expects(secret.size() == kSecretSize);
Expects(CheckSecretBytes(secret) == true); Expects(CheckSecretBytes(secret) == true);
auto params = PrepareAesParams(passwordHashForSecret); auto params = PrepareAesParams(bytesForEncryptionKey);
return Encrypt(secret, std::move(params)); return Encrypt(secret, std::move(params));
} }
@ -154,11 +146,26 @@ bytes::vector DecryptSecureSecret(
Expects(!encryptedSecret.empty()); Expects(!encryptedSecret.empty());
Expects(!password.empty()); Expects(!password.empty());
const auto hash = openssl::Sha512(bytes::concatenate( const auto bytesForEncryptionKey = bytes::concatenate(
salt, salt,
password, password,
salt)); salt);
return DecryptSecretBytes(encryptedSecret, hash); return DecryptSecretBytes(encryptedSecret, bytesForEncryptionKey);
}
bytes::vector EncryptSecureSecret(
bytes::const_span salt,
bytes::const_span secret,
bytes::const_span password) {
Expects(!salt.empty());
Expects(secret.size() == kSecretSize);
Expects(!password.empty());
const auto bytesForEncryptionKey = bytes::concatenate(
salt,
password,
salt);
return EncryptSecretBytes(secret, bytesForEncryptionKey);
} }
bytes::vector SerializeData(const std::map<QString, QString> &data) { bytes::vector SerializeData(const std::map<QString, QString> &data) {
@ -252,11 +259,11 @@ EncryptedData EncryptData(
gsl::make_span(unencrypted).subspan(padding), gsl::make_span(unencrypted).subspan(padding),
bytes); bytes);
const auto dataHash = openssl::Sha256(unencrypted); const auto dataHash = openssl::Sha256(unencrypted);
const auto dataSecretHash = openssl::Sha512(bytes::concatenate( const auto bytesForEncryptionKey = bytes::concatenate(
dataSecret, dataSecret,
dataHash)); dataHash);
auto params = PrepareAesParams(dataSecretHash); auto params = PrepareAesParams(bytesForEncryptionKey);
return { return {
{ dataSecret.begin(), dataSecret.end() }, { dataSecret.begin(), dataSecret.end() },
{ dataHash.begin(), dataHash.end() }, { dataHash.begin(), dataHash.end() },
@ -279,10 +286,10 @@ bytes::vector DecryptData(
return {}; return {};
} }
const auto dataSecretHash = openssl::Sha512(bytes::concatenate( const auto bytesForEncryptionKey = bytes::concatenate(
dataSecret, dataSecret,
dataHash)); dataHash);
auto params = PrepareAesParams(dataSecretHash); auto params = PrepareAesParams(bytesForEncryptionKey);
const auto decrypted = Decrypt(encrypted, std::move(params)); const auto decrypted = Decrypt(encrypted, std::move(params));
if (bytes::compare(openssl::Sha256(decrypted), dataHash) != 0) { if (bytes::compare(openssl::Sha256(decrypted), dataHash) != 0) {
LOG(("API Error: Bad data hash.")); LOG(("API Error: Bad data hash."));
@ -317,19 +324,20 @@ bytes::vector EncryptValueSecret(
bytes::const_span valueSecret, bytes::const_span valueSecret,
bytes::const_span secret, bytes::const_span secret,
bytes::const_span valueHash) { bytes::const_span valueHash) {
const auto valueSecretHash = openssl::Sha512( const auto bytesForEncryptionKey = bytes::concatenate(
bytes::concatenate(secret, valueHash)); secret,
return EncryptSecretBytes(valueSecret, valueSecretHash); valueHash);
return EncryptSecretBytes(valueSecret, bytesForEncryptionKey);
} }
bytes::vector DecryptValueSecret( bytes::vector DecryptValueSecret(
bytes::const_span encrypted, bytes::const_span encrypted,
bytes::const_span secret, bytes::const_span secret,
bytes::const_span valueHash) { bytes::const_span valueHash) {
const auto valueSecretHash = openssl::Sha512(bytes::concatenate( const auto bytesForEncryptionKey = bytes::concatenate(
secret, secret,
valueHash)); valueHash);
return DecryptSecretBytes(encrypted, valueSecretHash); return DecryptSecretBytes(encrypted, bytesForEncryptionKey);
} }
uint64 CountSecureSecretHash(bytes::const_span secret) { uint64 CountSecureSecretHash(bytes::const_span secret) {

View File

@ -11,20 +11,15 @@ namespace Passport {
bytes::vector GenerateSecretBytes(); bytes::vector GenerateSecretBytes();
bytes::vector EncryptSecretBytes( bytes::vector EncryptSecureSecret(
bytes::const_span salt,
bytes::const_span secret, bytes::const_span secret,
bytes::const_span passwordHashForSecret); bytes::const_span password);
bytes::vector DecryptSecretBytes(
bytes::const_span encryptedSecret,
bytes::const_span passwordHashForSecret);
bytes::vector DecryptSecureSecret( bytes::vector DecryptSecureSecret(
bytes::const_span salt, bytes::const_span salt,
bytes::const_span encryptedSecret, bytes::const_span encryptedSecret,
bytes::const_span password); bytes::const_span password);
bytes::vector PasswordHashForSecret(bytes::const_span passwordUtf8);
bytes::vector SerializeData(const std::map<QString, QString> &data); bytes::vector SerializeData(const std::map<QString, QString> &data);
std::map<QString, QString> DeserializeData(bytes::const_span bytes); std::map<QString, QString> DeserializeData(bytes::const_span bytes);

View File

@ -172,7 +172,7 @@ void FormBox::Inner::refresh() {
if (_rows.size() <= index) { if (_rows.size() <= index) {
_rows.push_back(object_ptr<FormRow>(this, title, description)); _rows.push_back(object_ptr<FormRow>(this, title, description));
_rows[index]->addClickHandler([=] { _rows[index]->addClickHandler([=] {
_controller->editField(index); _controller->editValue(index);
}); });
} }
_rows[index++]->setReady(ready); _rows[index++]->setReady(ready);

View File

@ -42,7 +42,7 @@ FormRequest::FormRequest(
, publicKey(publicKey) { , publicKey(publicKey) {
} }
FormController::UploadedScan::~UploadedScan() { FormController::UploadScanData::~UploadScanData() {
if (fullId) { if (fullId) {
Auth().uploader().cancel(fullId); Auth().uploader().cancel(fullId);
} }
@ -50,24 +50,12 @@ FormController::UploadedScan::~UploadedScan() {
FormController::EditFile::EditFile( FormController::EditFile::EditFile(
const File &fields, const File &fields,
std::unique_ptr<UploadedScan> &&uploaded) std::unique_ptr<UploadScanData> &&uploadData)
: fields(std::move(fields)) : fields(std::move(fields))
, uploaded(std::move(uploaded)) { , uploadData(std::move(uploadData)) {
} }
FormController::Field::Field(Type type) : type(type) { FormController::Value::Value(Type type) : type(type) {
}
template <typename FileHashes>
bytes::vector FormController::computeFilesHash(
FileHashes fileHashes,
bytes::const_span valueSecret) {
auto vec = bytes::concatenate(fileHashes);
auto hashesVector = std::vector<bytes::const_span>();
for (const auto &hash : fileHashes) {
hashesVector.push_back(bytes::make_span(hash));
}
return PrepareFilesHash(hashesVector, valueSecret);
} }
FormController::FormController( FormController::FormController(
@ -131,15 +119,17 @@ void FormController::validateSecureSecret(
if (!salt.empty() && !encryptedSecret.empty()) { if (!salt.empty() && !encryptedSecret.empty()) {
_secret = DecryptSecureSecret(salt, encryptedSecret, password); _secret = DecryptSecureSecret(salt, encryptedSecret, password);
if (_secret.empty()) { if (_secret.empty()) {
_secretId = 0;
LOG(("API Error: Failed to decrypt secure secret. " LOG(("API Error: Failed to decrypt secure secret. "
"Forgetting all files and data :(")); "Forgetting all files and data :("));
for (auto &field : _form.fields) { for (auto &value : _form.rows) {
if (!field.encryptedSecret.empty()) { if (!value.data.original.isEmpty()) {
resetField(field); resetValue(value);
} }
} }
} else { } else {
decryptFields(); _secretId = CountSecureSecretHash(_secret);
decryptValues();
} }
} }
if (_secret.empty()) { if (_secret.empty()) {
@ -148,61 +138,71 @@ void FormController::validateSecureSecret(
_secretReady.fire({}); _secretReady.fire({});
} }
void FormController::decryptFields() { void FormController::decryptValues() {
Expects(!_secret.empty()); Expects(!_secret.empty());
for (auto &field : _form.fields) { for (auto &value : _form.rows) {
if (field.encryptedSecret.empty()) { if (value.data.original.isEmpty()) {
continue; continue;
} }
decryptField(field); decryptValue(value);
} }
} }
void FormController::decryptField(Field &field) { void FormController::decryptValue(Value &value) {
Expects(!_secret.empty()); Expects(!_secret.empty());
Expects(!field.encryptedSecret.empty()); Expects(!value.data.original.isEmpty());
if (!validateFieldSecret(field)) { if (!validateValueSecrets(value)) {
resetField(field); resetValue(value);
return; return;
} }
const auto dataHash = bytes::make_span(field.dataHash); value.data.parsed = DeserializeData(DecryptData(
field.parsedData = DeserializeData(DecryptData( bytes::make_span(value.data.original),
bytes::make_span(field.originalData), value.data.hash,
field.dataHash, value.data.secret));
field.secret));
} }
bool FormController::validateFieldSecret(Field &field) { bool FormController::validateValueSecrets(Value &value) {
field.secret = DecryptValueSecret( value.data.secret = DecryptValueSecret(
field.encryptedSecret, value.data.encryptedSecret,
_secret, _secret,
field.hash); value.data.hash);
if (field.secret.empty()) { if (value.data.secret.empty()) {
LOG(("API Error: Could not decrypt value secret. " LOG(("API Error: Could not decrypt data secret. "
"Forgetting files and data :(")); "Forgetting files and data :("));
return false; return false;
} }
const auto fileHashes = ranges::view::all( for (auto &file : value.files) {
field.files file.secret = DecryptValueSecret(
file.encryptedSecret,
_secret,
file.hash);
if (file.secret.empty()) {
LOG(("API Error: Could not decrypt file secret. "
"Forgetting files and data :("));
return false;
}
}
const auto fileHashesSecrets = ranges::view::all(
value.files
) | ranges::view::transform([](File &file) { ) | ranges::view::transform([](File &file) {
return bytes::make_span(file.fileHash); return bytes::concatenate(file.hash, file.encryptedSecret);
}); });
const auto countedHash = openssl::Sha256(bytes::concatenate( const auto countedHash = openssl::Sha256(bytes::concatenate(
field.dataHash, value.data.hash,
bytes::concatenate(fileHashes), value.data.encryptedSecret,
field.secret)); bytes::concatenate(fileHashesSecrets)));
if (field.hash != countedHash) { if (value.consistencyHash != countedHash) {
LOG(("API Error: Wrong hash after decrypting value secret. " LOG(("API Error: Wrong hash after decrypting value secrets. "
"Forgetting files and data :(")); "Forgetting files and data :("));
return false; return false;
} }
return true; return true;
} }
void FormController::resetField(Field &field) { void FormController::resetValue(Value &value) {
field = Field(field.type); value = Value(value.type);
} }
rpl::producer<QString> FormController::passwordError() const { rpl::producer<QString> FormController::passwordError() const {
@ -213,52 +213,52 @@ QString FormController::passwordHint() const {
return _password.hint; return _password.hint;
} }
void FormController::uploadScan(int fieldIndex, QByteArray &&content) { void FormController::uploadScan(int valueIndex, QByteArray &&content) {
Expects(_editBox != nullptr); Expects(_editBox != nullptr);
Expects(fieldIndex >= 0 && fieldIndex < _form.fields.size()); Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
auto &field = _form.fields[fieldIndex]; auto &value = _form.rows[valueIndex];
if (field.secret.empty()) { auto fileIndex = int(value.filesInEdit.size());
field.secret = GenerateSecretBytes(); value.filesInEdit.emplace_back(
}
auto fileIndex = int(field.filesInEdit.size());
field.filesInEdit.emplace_back(
File(), File(),
nullptr); nullptr);
const auto fileId = rand_value<uint64>(); const auto fileId = rand_value<uint64>();
auto &file = field.filesInEdit.back(); auto &file = value.filesInEdit.back();
file.fields.size = content.size(); file.fields.size = content.size();
file.fields.id = fileId; file.fields.id = fileId;
file.fields.dcId = MTP::maindc(); file.fields.dcId = MTP::maindc();
file.fields.secret = GenerateSecretBytes();
file.fields.date = unixtime();
file.fields.image = ReadImage(bytes::make_span(content)); file.fields.image = ReadImage(bytes::make_span(content));
file.fields.downloadOffset = file.fields.size; file.fields.downloadOffset = file.fields.size;
_scanUpdated.fire(collectScanInfo(file)); _scanUpdated.fire(collectScanInfo(file));
encryptScan(fieldIndex, fileIndex, std::move(content)); encryptScan(valueIndex, fileIndex, std::move(content));
} }
void FormController::encryptScan( void FormController::encryptScan(
int fieldIndex, int valueIndex,
int fileIndex, int fileIndex,
QByteArray &&content) { QByteArray &&content) {
Expects(_editBox != nullptr); Expects(_editBox != nullptr);
Expects(fieldIndex >= 0 && fieldIndex < _form.fields.size()); Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
Expects(fileIndex >= 0 Expects(fileIndex >= 0
&& fileIndex < _form.fields[fieldIndex].filesInEdit.size()); && fileIndex < _form.rows[valueIndex].filesInEdit.size());
auto &field = _form.fields[fieldIndex]; const auto &value = _form.rows[valueIndex];
const auto &file = value.filesInEdit[fileIndex].fields;
const auto weak = _editBox; const auto weak = _editBox;
crl::async([ crl::async([
=, =,
fileId = field.filesInEdit[fileIndex].fields.id, fileId = file.id,
bytes = std::move(content), bytes = std::move(content),
valueSecret = field.secret fileSecret = file.secret
] { ] {
auto data = EncryptData( auto data = EncryptData(
bytes::make_span(bytes), bytes::make_span(bytes),
valueSecret); fileSecret);
auto result = UploadedScan(); auto result = UploadScanData();
result.fileId = fileId; result.fileId = fileId;
result.hash = std::move(data.hash); result.hash = std::move(data.hash);
result.bytes = std::move(data.bytes); result.bytes = std::move(data.bytes);
@ -270,7 +270,7 @@ void FormController::encryptScan(
crl::on_main([=, encrypted = std::move(result)]() mutable { crl::on_main([=, encrypted = std::move(result)]() mutable {
if (weak) { if (weak) {
uploadEncryptedScan( uploadEncryptedScan(
fieldIndex, valueIndex,
fileIndex, fileIndex,
std::move(encrypted)); std::move(encrypted));
} }
@ -279,13 +279,13 @@ void FormController::encryptScan(
} }
void FormController::deleteScan( void FormController::deleteScan(
int fieldIndex, int valueIndex,
int fileIndex) { int fileIndex) {
Expects(fieldIndex >= 0 && fieldIndex < _form.fields.size()); Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
Expects(fileIndex >= 0 Expects(fileIndex >= 0
&& fileIndex < _form.fields[fieldIndex].filesInEdit.size()); && fileIndex < _form.rows[valueIndex].filesInEdit.size());
auto &file = _form.fields[fieldIndex].filesInEdit[fileIndex]; auto &file = _form.rows[valueIndex].filesInEdit[fileIndex];
file.deleted = !file.deleted; file.deleted = !file.deleted;
_scanUpdated.fire(collectScanInfo(file)); _scanUpdated.fire(collectScanInfo(file));
} }
@ -296,14 +296,17 @@ void FormController::subscribeToUploader() {
} }
using namespace Storage; using namespace Storage;
Auth().uploader().secureReady( Auth().uploader().secureReady(
) | rpl::start_with_next([=](const UploadSecureDone &data) { ) | rpl::start_with_next([=](const UploadSecureDone &data) {
scanUploadDone(data); scanUploadDone(data);
}, _uploaderSubscriptions); }, _uploaderSubscriptions);
Auth().uploader().secureProgress( Auth().uploader().secureProgress(
) | rpl::start_with_next([=](const UploadSecureProgress &data) { ) | rpl::start_with_next([=](const UploadSecureProgress &data) {
scanUploadProgress(data); scanUploadProgress(data);
}, _uploaderSubscriptions); }, _uploaderSubscriptions);
Auth().uploader().secureFailed( Auth().uploader().secureFailed(
) | rpl::start_with_next([=](const FullMsgId &fullId) { ) | rpl::start_with_next([=](const FullMsgId &fullId) {
scanUploadFail(fullId); scanUploadFail(fullId);
@ -311,44 +314,48 @@ void FormController::subscribeToUploader() {
} }
void FormController::uploadEncryptedScan( void FormController::uploadEncryptedScan(
int fieldIndex, int valueIndex,
int fileIndex, int fileIndex,
UploadedScan &&data) { UploadScanData &&data) {
Expects(_editBox != nullptr); Expects(_editBox != nullptr);
Expects(fieldIndex >= 0 && fieldIndex < _form.fields.size()); Expects(valueIndex >= 0 && valueIndex < _form.rows.size());
Expects(fileIndex >= 0 Expects(fileIndex >= 0
&& fileIndex < _form.fields[fieldIndex].filesInEdit.size()); && fileIndex < _form.rows[valueIndex].filesInEdit.size());
subscribeToUploader(); subscribeToUploader();
auto &file = _form.fields[fieldIndex].filesInEdit[fileIndex]; auto &file = _form.rows[valueIndex].filesInEdit[fileIndex];
file.uploaded = std::make_unique<UploadedScan>(std::move(data)); file.uploadData = std::make_unique<UploadScanData>(std::move(data));
auto prepared = std::make_shared<FileLoadResult>( auto prepared = std::make_shared<FileLoadResult>(
TaskId(), TaskId(),
file.uploaded->fileId, file.uploadData->fileId,
FileLoadTo(PeerId(0), false, MsgId(0)), FileLoadTo(PeerId(0), false, MsgId(0)),
TextWithTags(), TextWithTags(),
std::shared_ptr<SendingAlbum>(nullptr)); std::shared_ptr<SendingAlbum>(nullptr));
prepared->type = SendMediaType::Secure; prepared->type = SendMediaType::Secure;
prepared->content = QByteArray::fromRawData( prepared->content = QByteArray::fromRawData(
reinterpret_cast<char*>(file.uploaded->bytes.data()), reinterpret_cast<char*>(file.uploadData->bytes.data()),
file.uploaded->bytes.size()); file.uploadData->bytes.size());
prepared->setFileData(prepared->content); prepared->setFileData(prepared->content);
prepared->filemd5 = file.uploaded->md5checksum; prepared->filemd5 = file.uploadData->md5checksum;
file.uploaded->fullId = FullMsgId(0, clientMsgId()); file.uploadData->fullId = FullMsgId(0, clientMsgId());
Auth().uploader().upload(file.uploaded->fullId, std::move(prepared)); Auth().uploader().upload(file.uploadData->fullId, std::move(prepared));
} }
void FormController::scanUploadDone(const Storage::UploadSecureDone &data) { void FormController::scanUploadDone(const Storage::UploadSecureDone &data) {
if (const auto file = findEditFile(data.fullId)) { if (const auto file = findEditFile(data.fullId)) {
Assert(file->uploaded != nullptr); Assert(file->uploadData != nullptr);
Assert(file->uploaded->fileId == data.fileId); Assert(file->uploadData->fileId == data.fileId);
file->uploaded->partsCount = data.partsCount; file->uploadData->partsCount = data.partsCount;
file->fields.fileHash = std::move(file->uploaded->hash); file->fields.hash = std::move(file->uploadData->hash);
file->uploaded->fullId = FullMsgId(); file->fields.encryptedSecret = EncryptValueSecret(
file->fields.secret,
_secret,
file->fields.hash);
file->uploadData->fullId = FullMsgId();
_scanUpdated.fire(collectScanInfo(*file)); _scanUpdated.fire(collectScanInfo(*file));
} }
@ -357,9 +364,9 @@ void FormController::scanUploadDone(const Storage::UploadSecureDone &data) {
void FormController::scanUploadProgress( void FormController::scanUploadProgress(
const Storage::UploadSecureProgress &data) { const Storage::UploadSecureProgress &data) {
if (const auto file = findEditFile(data.fullId)) { if (const auto file = findEditFile(data.fullId)) {
Assert(file->uploaded != nullptr); Assert(file->uploadData != nullptr);
file->uploaded->offset = data.offset; file->uploadData->offset = data.offset;
_scanUpdated.fire(collectScanInfo(*file)); _scanUpdated.fire(collectScanInfo(*file));
} }
@ -367,9 +374,9 @@ void FormController::scanUploadProgress(
void FormController::scanUploadFail(const FullMsgId &fullId) { void FormController::scanUploadFail(const FullMsgId &fullId) {
if (const auto file = findEditFile(fullId)) { if (const auto file = findEditFile(fullId)) {
Assert(file->uploaded != nullptr); Assert(file->uploadData != nullptr);
file->uploaded->offset = -1; file->uploadData->offset = -1;
_scanUpdated.fire(collectScanInfo(*file)); _scanUpdated.fire(collectScanInfo(*file));
} }
@ -399,27 +406,27 @@ void FormController::fillRows(
QString title, QString title,
QString description, QString description,
bool ready)> callback) { bool ready)> callback) {
for (const auto &field : _form.fields) { for (const auto &value : _form.rows) {
switch (field.type) { switch (value.type) {
case Field::Type::Identity: case Value::Type::Identity:
callback( callback(
lang(lng_passport_identity_title), lang(lng_passport_identity_title),
lang(lng_passport_identity_description), lang(lng_passport_identity_description),
false); false);
break; break;
case Field::Type::Address: case Value::Type::Address:
callback( callback(
lang(lng_passport_address_title), lang(lng_passport_address_title),
lang(lng_passport_address_description), lang(lng_passport_address_description),
false); false);
break; break;
case Field::Type::Phone: case Value::Type::Phone:
callback( callback(
lang(lng_passport_phone_title), lang(lng_passport_phone_title),
App::self()->phone(), App::self()->phone(),
true); true);
break; break;
case Field::Type::Email: case Value::Type::Email:
callback( callback(
lang(lng_passport_email_title), lang(lng_passport_email_title),
lang(lng_passport_email_description), lang(lng_passport_email_description),
@ -429,24 +436,25 @@ void FormController::fillRows(
} }
} }
void FormController::editField(int index) { void FormController::editValue(int index) {
Expects(index >= 0 && index < _form.fields.size()); Expects(index >= 0 && index < _form.rows.size());
auto &value = _form.rows[index];
loadFiles(value.files);
value.filesInEdit = ranges::view::all(
value.files
) | ranges::view::transform([](const File &file) {
return EditFile(file, nullptr);
}) | ranges::to_vector;
auto box = [&]() -> object_ptr<BoxContent> { auto box = [&]() -> object_ptr<BoxContent> {
auto &field = _form.fields[index]; switch (value.type) {
switch (field.type) { case Value::Type::Identity:
case Field::Type::Identity:
loadFiles(field.files);
field.filesInEdit = ranges::view::all(
field.files
) | ranges::view::transform([](const File &file) {
return EditFile(file, nullptr);
}) | ranges::to_vector;
return Box<IdentityBox>( return Box<IdentityBox>(
this, this,
index, index,
fieldDataIdentity(field), valueDataIdentity(value),
fieldFilesIdentity(field)); valueFilesIdentity(value));
} }
return { nullptr }; return { nullptr };
}(); }();
@ -496,11 +504,15 @@ void FormController::loadFiles(std::vector<File> &files) {
} }
void FormController::fileLoadDone(FileKey key, const QByteArray &bytes) { void FormController::fileLoadDone(FileKey key, const QByteArray &bytes) {
if (const auto [field, file] = findFile(key); file != nullptr) { if (const auto [value, file] = findFile(key); file != nullptr) {
const auto decrypted = DecryptData( const auto decrypted = DecryptData(
bytes::make_span(bytes), bytes::make_span(bytes),
file->fileHash, file->hash,
field->secret); file->secret);
if (decrypted.empty()) {
fileLoadFail(key);
return;
}
file->downloadOffset = file->size; file->downloadOffset = file->size;
file->image = App::readImage(QByteArray::fromRawData( file->image = App::readImage(QByteArray::fromRawData(
reinterpret_cast<const char*>(decrypted.data()), reinterpret_cast<const char*>(decrypted.data()),
@ -514,7 +526,7 @@ void FormController::fileLoadDone(FileKey key, const QByteArray &bytes) {
} }
void FormController::fileLoadProgress(FileKey key, int offset) { void FormController::fileLoadProgress(FileKey key, int offset) {
if (const auto [field, file] = findFile(key); file != nullptr) { if (const auto [value, file] = findFile(key); file != nullptr) {
file->downloadOffset = offset; file->downloadOffset = offset;
if (const auto fileInEdit = findEditFile(key)) { if (const auto fileInEdit = findEditFile(key)) {
fileInEdit->fields.downloadOffset = file->downloadOffset; fileInEdit->fields.downloadOffset = file->downloadOffset;
@ -524,7 +536,7 @@ void FormController::fileLoadProgress(FileKey key, int offset) {
} }
void FormController::fileLoadFail(FileKey key) { void FormController::fileLoadFail(FileKey key) {
if (const auto [field, file] = findFile(key); file != nullptr) { if (const auto [value, file] = findFile(key); file != nullptr) {
file->downloadOffset = -1; file->downloadOffset = -1;
if (const auto fileInEdit = findEditFile(key)) { if (const auto fileInEdit = findEditFile(key)) {
fileInEdit->fields.downloadOffset = file->downloadOffset; fileInEdit->fields.downloadOffset = file->downloadOffset;
@ -545,15 +557,16 @@ ScanInfo FormController::collectScanInfo(const EditFile &file) const {
).arg(file.fields.downloadOffset ).arg(file.fields.downloadOffset
).arg(file.fields.size); ).arg(file.fields.size);
} else { } else {
return QString("download ready"); return QString("uploaded ")
+ langDateTimeFull(ParseDateTime(file.fields.date));
} }
} else if (file.uploaded) { } else if (file.uploadData) {
if (file.uploaded->offset < 0) { if (file.uploadData->offset < 0) {
return QString("upload failed"); return QString("upload failed");
} else if (file.uploaded->fullId) { } else if (file.uploadData->fullId) {
return QString("uploading %1 / %2" return QString("uploading %1 / %2"
).arg(file.uploaded->offset ).arg(file.uploadData->offset
).arg(file.uploaded->bytes.size()); ).arg(file.uploadData->bytes.size());
} else { } else {
return QString("upload ready"); return QString("upload ready");
} }
@ -567,8 +580,8 @@ ScanInfo FormController::collectScanInfo(const EditFile &file) const {
file.fields.image }; file.fields.image };
} }
IdentityData FormController::fieldDataIdentity(const Field &field) const { IdentityData FormController::valueDataIdentity(const Value &value) const {
const auto &map = field.parsedData; const auto &map = value.data.parsed;
auto result = IdentityData(); auto result = IdentityData();
if (const auto i = map.find(qsl("first_name")); i != map.cend()) { if (const auto i = map.find(qsl("first_name")); i != map.cend()) {
result.name = i->second; result.name = i->second;
@ -579,31 +592,31 @@ IdentityData FormController::fieldDataIdentity(const Field &field) const {
return result; return result;
} }
std::vector<ScanInfo> FormController::fieldFilesIdentity( std::vector<ScanInfo> FormController::valueFilesIdentity(
const Field &field) const { const Value &value) const {
auto result = std::vector<ScanInfo>(); auto result = std::vector<ScanInfo>();
for (const auto &file : field.filesInEdit) { for (const auto &file : value.filesInEdit) {
result.push_back(collectScanInfo(file)); result.push_back(collectScanInfo(file));
} }
return result; return result;
} }
void FormController::saveFieldIdentity( void FormController::saveValueIdentity(
int index, int index,
const IdentityData &data) { const IdentityData &data) {
Expects(_editBox != nullptr); Expects(_editBox != nullptr);
Expects(index >= 0 && index < _form.fields.size()); Expects(index >= 0 && index < _form.rows.size());
Expects(_form.fields[index].type == Field::Type::Identity); Expects(_form.rows[index].type == Value::Type::Identity);
_form.fields[index].parsedData[qsl("first_name")] = data.name; _form.rows[index].data.parsed[qsl("first_name")] = data.name;
_form.fields[index].parsedData[qsl("last_name")] = data.surname; _form.rows[index].data.parsed[qsl("last_name")] = data.surname;
saveIdentity(index); saveIdentity(index);
} }
void FormController::saveIdentity(int index) { void FormController::saveIdentity(int index) {
Expects(index >= 0 && index < _form.fields.size()); Expects(index >= 0 && index < _form.rows.size());
Expects(_form.fields[index].type == Field::Type::Identity); Expects(_form.rows[index].type == Value::Type::Identity);
if (_secret.empty()) { if (_secret.empty()) {
_secretCallbacks.push_back([=] { _secretCallbacks.push_back([=] {
@ -614,63 +627,69 @@ void FormController::saveIdentity(int index) {
_editBox->closeBox(); _editBox->closeBox();
auto &field = _form.fields[index]; auto &value = _form.rows[index];
auto inputFiles = QVector<MTPInputSecureFile>(); auto inputFiles = QVector<MTPInputSecureFile>();
inputFiles.reserve(field.filesInEdit.size()); inputFiles.reserve(value.filesInEdit.size());
for (const auto &file : field.filesInEdit) { for (const auto &file : value.filesInEdit) {
if (file.deleted) { if (file.deleted) {
continue; continue;
} else if (const auto uploaded = file.uploaded.get()) { } else if (const auto uploadData = file.uploadData.get()) {
inputFiles.push_back(MTP_inputSecureFileUploaded( inputFiles.push_back(MTP_inputSecureFileUploaded(
MTP_long(file.fields.id), MTP_long(file.fields.id),
MTP_int(uploaded->partsCount), MTP_int(uploadData->partsCount),
MTP_bytes(uploaded->md5checksum), MTP_bytes(uploadData->md5checksum),
MTP_bytes(file.fields.fileHash))); MTP_bytes(file.fields.hash),
MTP_bytes(file.fields.encryptedSecret)));
} else { } else {
inputFiles.push_back(MTP_inputSecureFile( inputFiles.push_back(MTP_inputSecureFile(
MTP_long(file.fields.id), MTP_long(file.fields.id),
MTP_long(file.fields.accessHash))); MTP_long(file.fields.accessHash)));
} }
} }
if (field.secret.empty()) {
field.secret = GenerateSecretBytes(); if (value.data.secret.empty()) {
value.data.secret = GenerateSecretBytes();
} }
const auto encryptedData = EncryptData( const auto encryptedData = EncryptData(
SerializeData(field.parsedData), SerializeData(value.data.parsed),
field.secret); value.data.secret);
const auto fileHashes = ranges::view::all( value.data.hash = encryptedData.hash;
field.filesInEdit value.data.encryptedSecret = EncryptValueSecret(
value.data.secret,
_secret,
value.data.hash);
const auto fileHashesSecrets = ranges::view::all(
value.filesInEdit
) | ranges::view::filter([](const EditFile &file) { ) | ranges::view::filter([](const EditFile &file) {
return !file.deleted; return !file.deleted;
}) | ranges::view::transform([](const EditFile &file) { }) | ranges::view::transform([](const EditFile &file) {
return bytes::make_span(file.fields.fileHash); return bytes::concatenate(
file.fields.hash,
file.fields.encryptedSecret);
}); });
const auto valueHash = openssl::Sha256(bytes::concatenate( value.consistencyHash = openssl::Sha256(bytes::concatenate(
encryptedData.hash, value.data.hash,
bytes::concatenate(fileHashes), value.data.encryptedSecret,
field.secret)); bytes::concatenate(fileHashesSecrets)));
field.encryptedSecret = EncryptValueSecret(
field.secret,
_secret,
valueHash);
request(MTPaccount_SaveSecureValue( request(MTPaccount_SaveSecureValue(
MTP_inputSecureValueIdentity( MTP_inputSecureValueIdentity(
MTP_secureData( MTP_secureData(
MTP_bytes(encryptedData.bytes), MTP_bytes(encryptedData.bytes),
MTP_bytes(encryptedData.hash)), MTP_bytes(value.data.hash),
MTP_bytes(value.data.encryptedSecret)),
MTP_vector<MTPInputSecureFile>(inputFiles), MTP_vector<MTPInputSecureFile>(inputFiles),
MTP_bytes(field.encryptedSecret), MTP_bytes(value.consistencyHash)),
MTP_bytes(valueHash)),
MTP_long(CountSecureSecretHash(_secret)) MTP_long(CountSecureSecretHash(_secret))
)).done([=](const MTPSecureValueSaved &result) { )).done([=](const MTPSecureValueSaved &result) {
Expects(result.type() == mtpc_secureValueSaved); Expects(result.type() == mtpc_secureValueSaved);
const auto &data = result.c_secureValueSaved(); const auto &data = result.c_secureValueSaved();
_form.fields[index].files = parseFiles( _form.rows[index].files = parseFiles(
data.vfiles.v, data.vfiles.v,
base::take(_form.fields[index].filesInEdit)); base::take(_form.rows[index].filesInEdit));
Ui::show(Box<InformBox>("Saved"), LayerOption::KeepOther); Ui::show(Box<InformBox>("Saved"), LayerOption::KeepOther);
}).fail([=](const RPCError &error) { }).fail([=](const RPCError &error) {
@ -690,19 +709,17 @@ void FormController::generateSecret(bytes::const_span password) {
_password.newSecureSalt, _password.newSecureSalt,
randomSaltPart); randomSaltPart);
const auto hashForSecret = openssl::Sha512(bytes::concatenate( auto secureSecretId = CountSecureSecretHash(secret);
auto encryptedSecret = EncryptSecureSecret(
newSecureSaltFull, newSecureSaltFull,
password, secret,
newSecureSaltFull)); password);
const auto hashForAuth = openssl::Sha256(bytes::concatenate( const auto hashForAuth = openssl::Sha256(bytes::concatenate(
_password.salt, _password.salt,
password, password,
_password.salt)); _password.salt));
auto secureSecretHash = CountSecureSecretHash(secret);
auto encryptedSecret = EncryptSecretBytes(
secret,
hashForSecret);
using Flag = MTPDaccount_passwordInputSettings::Flag; using Flag = MTPDaccount_passwordInputSettings::Flag;
_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings( _saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
MTP_bytes(hashForAuth), MTP_bytes(hashForAuth),
@ -714,10 +731,11 @@ void FormController::generateSecret(bytes::const_span password) {
MTPstring(), // email MTPstring(), // email
MTP_bytes(newSecureSaltFull), MTP_bytes(newSecureSaltFull),
MTP_bytes(encryptedSecret), MTP_bytes(encryptedSecret),
MTP_long(secureSecretHash)) MTP_long(secureSecretId))
)).done([=](const MTPBool &result) { )).done([=](const MTPBool &result) {
_saveSecretRequestId = 0; _saveSecretRequestId = 0;
_secret = secret; _secret = secret;
_secretId = secureSecretId;
//_password.salt = newPasswordSaltFull; //_password.salt = newPasswordSaltFull;
for (const auto &callback : base::take(_secretCallbacks)) { for (const auto &callback : base::take(_secretCallbacks)) {
callback(); callback();
@ -745,20 +763,20 @@ void FormController::requestForm() {
} }
template <typename DataType> template <typename DataType>
auto FormController::parseEncryptedField( auto FormController::parseEncryptedValue(
Field::Type type, Value::Type type,
const DataType &data) const -> Field { const DataType &data) const -> Value {
Expects(data.vdata.type() == mtpc_secureData); Expects(data.vdata.type() == mtpc_secureData);
auto result = Field(type); auto result = Value(type);
if (data.has_verified()) { if (data.has_verified()) {
result.verification = parseVerified(data.vverified); result.verification = parseVerified(data.vverified);
} }
result.encryptedSecret = bytes::make_vector(data.vsecret.v); result.consistencyHash = bytes::make_vector(data.vhash.v);
result.hash = bytes::make_vector(data.vhash.v);
const auto &fields = data.vdata.c_secureData(); const auto &fields = data.vdata.c_secureData();
result.originalData = fields.vdata.v; result.data.original = fields.vdata.v;
result.dataHash = bytes::make_vector(fields.vdata_hash.v); result.data.hash = bytes::make_vector(fields.vdata_hash.v);
result.data.encryptedSecret = bytes::make_vector(fields.vsecret.v);
result.files = parseFiles(data.vfiles.v); result.files = parseFiles(data.vfiles.v);
return result; return result;
} }
@ -782,28 +800,11 @@ auto FormController::parseFiles(
normal.id = fields.vid.v; normal.id = fields.vid.v;
normal.accessHash = fields.vaccess_hash.v; normal.accessHash = fields.vaccess_hash.v;
normal.size = fields.vsize.v; normal.size = fields.vsize.v;
normal.date = fields.vdate.v;
normal.dcId = fields.vdc_id.v; normal.dcId = fields.vdc_id.v;
normal.fileHash = bytes::make_vector(fields.vfile_hash.v); normal.hash = bytes::make_vector(fields.vfile_hash.v);
const auto i = ranges::find( normal.encryptedSecret = bytes::make_vector(fields.vsecret.v);
editData, fillDownloadedFile(normal, editData);
normal.fileHash,
[](const EditFile &file) { return file.fields.fileHash; });
if (i != editData.end()) {
normal.image = i->fields.image;
normal.downloadOffset = i->fields.downloadOffset;
if (i->uploaded) {
Local::writeImage(
StorageKey(
storageMix32To64(
SecureFileLocation,
normal.dcId),
normal.id),
StorageImageSaved(QByteArray::fromRawData(
reinterpret_cast<const char*>(
i->uploaded->bytes.data()),
i->uploaded->bytes.size())));
}
}
result.push_back(std::move(normal)); result.push_back(std::move(normal));
} break; } break;
} }
@ -813,12 +814,39 @@ auto FormController::parseFiles(
return result; return result;
} }
void FormController::fillDownloadedFile(
File &destination,
const std::vector<EditFile> &source) const {
const auto i = ranges::find(
source,
destination.hash,
[](const EditFile &file) { return file.fields.hash; });
if (i == source.end()) {
return;
}
destination.image = i->fields.image;
destination.downloadOffset = i->fields.downloadOffset;
if (!i->uploadData) {
return;
}
Local::writeImage(
StorageKey(
storageMix32To64(
SecureFileLocation,
destination.dcId),
destination.id),
StorageImageSaved(QByteArray::fromRawData(
reinterpret_cast<const char*>(
i->uploadData->bytes.data()),
i->uploadData->bytes.size())));
}
template <typename DataType> template <typename DataType>
auto FormController::parsePlainTextField( auto FormController::parsePlainTextValue(
Field::Type type, Value::Type type,
const QByteArray &value, const QByteArray &value,
const DataType &data) const -> Field { const DataType &data) const -> Value {
auto result = Field(type); auto result = Value(type);
const auto check = bytes::compare( const auto check = bytes::compare(
bytes::make_span(data.vhash.v), bytes::make_span(data.vhash.v),
openssl::Sha256(bytes::make_span(value))); openssl::Sha256(bytes::make_span(value)));
@ -830,38 +858,38 @@ auto FormController::parsePlainTextField(
)); ));
return result; return result;
} }
result.parsedData[QString("value")] = QString::fromUtf8(value); result.data.parsed[QString("value")] = QString::fromUtf8(value);
if (data.has_verified()) { if (data.has_verified()) {
result.verification = parseVerified(data.vverified); result.verification = parseVerified(data.vverified);
} }
result.hash = bytes::make_vector(data.vhash.v); result.consistencyHash = bytes::make_vector(data.vhash.v);
return result; return result;
} }
auto FormController::parseValue( auto FormController::parseValue(
const MTPSecureValue &value) const -> Field { const MTPSecureValue &value) const -> Value {
switch (value.type()) { switch (value.type()) {
case mtpc_secureValueIdentity: { case mtpc_secureValueIdentity: {
return parseEncryptedField( return parseEncryptedValue(
Field::Type::Identity, Value::Type::Identity,
value.c_secureValueIdentity()); value.c_secureValueIdentity());
} break; } break;
case mtpc_secureValueAddress: { case mtpc_secureValueAddress: {
return parseEncryptedField( return parseEncryptedValue(
Field::Type::Address, Value::Type::Address,
value.c_secureValueAddress()); value.c_secureValueAddress());
} break; } break;
case mtpc_secureValuePhone: { case mtpc_secureValuePhone: {
const auto &data = value.c_secureValuePhone(); const auto &data = value.c_secureValuePhone();
return parsePlainTextField( return parsePlainTextValue(
Field::Type::Phone, Value::Type::Phone,
data.vphone.v, data.vphone.v,
data); data);
} break; } break;
case mtpc_secureValueEmail: { case mtpc_secureValueEmail: {
const auto &data = value.c_secureValueEmail(); const auto &data = value.c_secureValueEmail();
return parsePlainTextField( return parsePlainTextValue(
Field::Type::Phone, Value::Type::Phone,
data.vemail.v, data.vemail.v,
data); data);
} break; } break;
@ -874,13 +902,13 @@ auto FormController::parseVerified(const MTPSecureValueVerified &data) const
Expects(data.type() == mtpc_secureValueVerified); Expects(data.type() == mtpc_secureValueVerified);
const auto &fields = data.c_secureValueVerified(); const auto &fields = data.c_secureValueVerified();
return Verification{ fields.vdate.v, qs(fields.vprovider) }; return Verification{ fields.vdate.v };
} }
auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* { auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
for (auto &field : _form.fields) { for (auto &value : _form.rows) {
for (auto &file : field.filesInEdit) { for (auto &file : value.filesInEdit) {
if (file.uploaded && file.uploaded->fullId == fullId) { if (file.uploadData && file.uploadData->fullId == fullId) {
return &file; return &file;
} }
} }
@ -889,8 +917,8 @@ auto FormController::findEditFile(const FullMsgId &fullId) -> EditFile* {
} }
auto FormController::findEditFile(const FileKey &key) -> EditFile* { auto FormController::findEditFile(const FileKey &key) -> EditFile* {
for (auto &field : _form.fields) { for (auto &value : _form.rows) {
for (auto &file : field.filesInEdit) { for (auto &file : value.filesInEdit) {
if (file.fields.dcId == key.dcId && file.fields.id == key.id) { if (file.fields.dcId == key.dcId && file.fields.id == key.id) {
return &file; return &file;
} }
@ -900,11 +928,11 @@ auto FormController::findEditFile(const FileKey &key) -> EditFile* {
} }
auto FormController::findFile(const FileKey &key) auto FormController::findFile(const FileKey &key)
-> std::pair<Field*, File*> { -> std::pair<Value*, File*> {
for (auto &field : _form.fields) { for (auto &value : _form.rows) {
for (auto &file : field.files) { for (auto &file : value.files) {
if (file.dcId == key.dcId && file.id == key.id) { if (file.dcId == key.dcId && file.id == key.id) {
return { &field, &file }; return { &value, &file };
} }
} }
} }
@ -923,11 +951,11 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
const auto &data = result.c_account_authorizationForm(); const auto &data = result.c_account_authorizationForm();
auto values = std::vector<Field>(); auto values = std::vector<Value>();
for (const auto &value : data.vvalues.v) { for (const auto &value : data.vvalues.v) {
values.push_back(parseValue(value)); values.push_back(parseValue(value));
} }
const auto findValue = [&](Field::Type type) -> Field* { const auto findValue = [&](Value::Type type) -> Value* {
for (auto &value : values) { for (auto &value : values) {
if (value.type == type) { if (value.type == type) {
return &value; return &value;
@ -936,10 +964,9 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
return nullptr; return nullptr;
}; };
_form.requestWrite = false;
App::feedUsers(data.vusers); App::feedUsers(data.vusers);
for (const auto &required : data.vrequired_types.v) { for (const auto &required : data.vrequired_types.v) {
using Type = Field::Type; using Type = Value::Type;
const auto type = [&] { const auto type = [&] {
switch (required.type()) { switch (required.type()) {
@ -951,10 +978,10 @@ void FormController::parseForm(const MTPaccount_AuthorizationForm &result) {
Unexpected("Type in secureValueType type."); Unexpected("Type in secureValueType type.");
}(); }();
if (auto field = findValue(type)) { if (auto value = findValue(type)) {
_form.fields.push_back(std::move(*field)); _form.rows.push_back(std::move(*value));
} else { } else {
_form.fields.push_back(Field(type)); _form.rows.push_back(Value(type));
} }
} }
_bot = App::userLoaded(_request.botId); _bot = App::userLoaded(_request.botId);

View File

@ -83,8 +83,8 @@ public:
rpl::producer<QString> passwordError() const; rpl::producer<QString> passwordError() const;
QString passwordHint() const; QString passwordHint() const;
void uploadScan(int fieldIndex, QByteArray &&content); void uploadScan(int valueIndex, QByteArray &&content);
void deleteScan(int fieldIndex, int fileIndex); void deleteScan(int valueIndex, int fileIndex);
rpl::producer<> secretReadyEvents() const; rpl::producer<> secretReadyEvents() const;
@ -97,15 +97,15 @@ public:
QString title, QString title,
QString description, QString description,
bool ready)> callback); bool ready)> callback);
void editField(int index); void editValue(int index);
void saveFieldIdentity(int index, const IdentityData &data); void saveValueIdentity(int index, const IdentityData &data);
~FormController(); ~FormController();
private: private:
struct UploadedScan { struct UploadScanData {
~UploadedScan(); ~UploadScanData();
FullMsgId fullId; FullMsgId fullId;
uint64 fileId = 0; uint64 fileId = 0;
@ -121,7 +121,10 @@ private:
uint64 accessHash = 0; uint64 accessHash = 0;
int32 size = 0; int32 size = 0;
int32 dcId = 0; int32 dcId = 0;
bytes::vector fileHash; TimeId date = 0;
bytes::vector hash;
bytes::vector secret;
bytes::vector encryptedSecret;
int downloadOffset = 0; int downloadOffset = 0;
QImage image; QImage image;
@ -129,17 +132,23 @@ private:
struct EditFile { struct EditFile {
EditFile( EditFile(
const File &fields, const File &fields,
std::unique_ptr<UploadedScan> &&uploaded); std::unique_ptr<UploadScanData> &&uploadData);
File fields; File fields;
std::unique_ptr<UploadedScan> uploaded; std::unique_ptr<UploadScanData> uploadData;
bool deleted = false; bool deleted = false;
}; };
struct Verification { struct Verification {
TimeId date; TimeId date;
QString provider;
}; };
struct Field { struct ValueData {
QByteArray original;
std::map<QString, QString> parsed;
bytes::vector hash;
bytes::vector secret;
bytes::vector encryptedSecret;
};
struct Value {
enum class Type { enum class Type {
Identity, Identity,
Address, Address,
@ -147,24 +156,19 @@ private:
Email, Email,
}; };
explicit Field(Type type); explicit Value(Type type);
Field(Field &&other) = default; Value(Value &&other) = default;
Field &operator=(Field &&other) = default; Value &operator=(Value &&other) = default;
Type type; Type type;
QByteArray originalData; ValueData data;
std::map<QString, QString> parsedData;
bytes::vector dataHash;
std::vector<File> files; std::vector<File> files;
std::vector<EditFile> filesInEdit; std::vector<EditFile> filesInEdit;
bytes::vector secret; bytes::vector consistencyHash;
bytes::vector encryptedSecret;
bytes::vector hash;
base::optional<Verification> verification; base::optional<Verification> verification;
}; };
struct Form { struct Form {
bool requestWrite = false; std::vector<Value> rows;
std::vector<Field> fields;
}; };
struct PasswordSettings { struct PasswordSettings {
bytes::vector salt; bytes::vector salt;
@ -178,7 +182,7 @@ private:
EditFile *findEditFile(const FullMsgId &fullId); EditFile *findEditFile(const FullMsgId &fullId);
EditFile *findEditFile(const FileKey &key); EditFile *findEditFile(const FileKey &key);
std::pair<Field*, File*> findFile(const FileKey &key); std::pair<Value*, File*> findFile(const FileKey &key);
void requestForm(); void requestForm();
void requestPassword(); void requestPassword();
@ -187,20 +191,23 @@ private:
void formFail(const RPCError &error); void formFail(const RPCError &error);
void parseForm(const MTPaccount_AuthorizationForm &result); void parseForm(const MTPaccount_AuthorizationForm &result);
void showForm(); void showForm();
Field parseValue(const MTPSecureValue &value) const; Value parseValue(const MTPSecureValue &value) const;
template <typename DataType> template <typename DataType>
Field parseEncryptedField( Value parseEncryptedValue(
Field::Type type, Value::Type type,
const DataType &data) const; const DataType &data) const;
template <typename DataType> template <typename DataType>
Field parsePlainTextField( Value parsePlainTextValue(
Field::Type type, Value::Type type,
const QByteArray &value, const QByteArray &text,
const DataType &data) const; const DataType &data) const;
Verification parseVerified(const MTPSecureValueVerified &data) const; Verification parseVerified(const MTPSecureValueVerified &data) const;
std::vector<File> parseFiles( std::vector<File> parseFiles(
const QVector<MTPSecureFile> &data, const QVector<MTPSecureFile> &data,
const std::vector<EditFile> &editData = {}) const; const std::vector<EditFile> &editData = {}) const;
void fillDownloadedFile(
File &destination,
const std::vector<EditFile> &source) const;
void passwordDone(const MTPaccount_Password &result); void passwordDone(const MTPaccount_Password &result);
void passwordFail(const RPCError &error); void passwordFail(const RPCError &error);
@ -211,13 +218,13 @@ private:
bytes::const_span salt, bytes::const_span salt,
bytes::const_span encryptedSecret, bytes::const_span encryptedSecret,
bytes::const_span password); bytes::const_span password);
void decryptFields(); void decryptValues();
void decryptField(Field &field); void decryptValue(Value &value);
bool validateFieldSecret(Field &field); bool validateValueSecrets(Value &value);
void resetField(Field &field); void resetValue(Value &value);
IdentityData fieldDataIdentity(const Field &field) const; IdentityData valueDataIdentity(const Value &value) const;
std::vector<ScanInfo> fieldFilesIdentity(const Field &field) const; std::vector<ScanInfo> valueFilesIdentity(const Value &value) const;
void saveIdentity(int index); void saveIdentity(int index);
void loadFiles(std::vector<File> &files); void loadFiles(std::vector<File> &files);
@ -226,17 +233,15 @@ private:
void fileLoadFail(FileKey key); void fileLoadFail(FileKey key);
void generateSecret(bytes::const_span password); void generateSecret(bytes::const_span password);
template <typename FileHashes>
bytes::vector computeFilesHash(
FileHashes fileHashes,
bytes::const_span valueHash);
void subscribeToUploader(); void subscribeToUploader();
void encryptScan( void encryptScan(
int fieldIndex, int valueIndex,
int fileIndex, int fileIndex,
QByteArray &&content); QByteArray &&content);
void uploadEncryptedScan(int fieldIndex, int fileIndex, UploadedScan &&data); void uploadEncryptedScan(
int valueIndex,
int fileIndex,
UploadScanData &&data);
void scanUploadDone(const Storage::UploadSecureDone &data); void scanUploadDone(const Storage::UploadSecureDone &data);
void scanUploadProgress(const Storage::UploadSecureProgress &data); void scanUploadProgress(const Storage::UploadSecureProgress &data);
void scanUploadFail(const FullMsgId &fullId); void scanUploadFail(const FullMsgId &fullId);
@ -256,6 +261,7 @@ private:
rpl::event_stream<ScanInfo> _scanUpdated; rpl::event_stream<ScanInfo> _scanUpdated;
bytes::vector _secret; bytes::vector _secret;
uint64 _secretId = 0;
std::vector<base::lambda<void()>> _secretCallbacks; std::vector<base::lambda<void()>> _secretCallbacks;
mtpRequestId _saveSecretRequestId = 0; mtpRequestId _saveSecretRequestId = 0;
rpl::event_stream<> _secretReady; rpl::event_stream<> _secretReady;