mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-01 03:50:43 +00:00
First version of IdentityBox with encrypted data.
This commit is contained in:
parent
07e8a2bd85
commit
f633ead3ab
@ -207,20 +207,40 @@ inline BigNum operator-(const BigNum &a, const BigNum &b) {
|
||||
return result;
|
||||
}
|
||||
|
||||
inline base::byte_array<SHA256_DIGEST_LENGTH> Sha256(base::const_byte_span bytes) {
|
||||
auto result = base::byte_array<SHA256_DIGEST_LENGTH>();
|
||||
SHA256(reinterpret_cast<const unsigned char*>(bytes.data()), bytes.size(), reinterpret_cast<unsigned char*>(result.data()));
|
||||
inline base::byte_array<SHA512_DIGEST_LENGTH> Sha512(
|
||||
base::const_byte_span bytes) {
|
||||
auto result = base::byte_array<SHA512_DIGEST_LENGTH>();
|
||||
SHA512(
|
||||
reinterpret_cast<const unsigned char*>(bytes.data()),
|
||||
bytes.size(),
|
||||
reinterpret_cast<unsigned char*>(result.data()));
|
||||
return result;
|
||||
}
|
||||
|
||||
inline base::byte_array<SHA_DIGEST_LENGTH> Sha1(base::const_byte_span bytes) {
|
||||
inline base::byte_array<SHA256_DIGEST_LENGTH> Sha256(
|
||||
base::const_byte_span bytes) {
|
||||
auto result = base::byte_array<SHA256_DIGEST_LENGTH>();
|
||||
SHA256(
|
||||
reinterpret_cast<const unsigned char*>(bytes.data()),
|
||||
bytes.size(),
|
||||
reinterpret_cast<unsigned char*>(result.data()));
|
||||
return result;
|
||||
}
|
||||
|
||||
inline base::byte_array<SHA_DIGEST_LENGTH> Sha1(
|
||||
base::const_byte_span bytes) {
|
||||
auto result = base::byte_array<SHA_DIGEST_LENGTH>();
|
||||
SHA1(reinterpret_cast<const unsigned char*>(bytes.data()), bytes.size(), reinterpret_cast<unsigned char*>(result.data()));
|
||||
SHA1(
|
||||
reinterpret_cast<const unsigned char*>(bytes.data()),
|
||||
bytes.size(),
|
||||
reinterpret_cast<unsigned char*>(result.data()));
|
||||
return result;
|
||||
}
|
||||
|
||||
inline int FillRandom(base::byte_span bytes) {
|
||||
return RAND_bytes(reinterpret_cast<unsigned char*>(bytes.data()), bytes.size());
|
||||
return RAND_bytes(
|
||||
reinterpret_cast<unsigned char*>(bytes.data()),
|
||||
bytes.size());
|
||||
}
|
||||
|
||||
inline void AddRandomSeed(base::const_byte_span bytes) {
|
||||
|
@ -494,6 +494,10 @@ int32 *hashSha256(const void *data, uint32 len, void *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
|
||||
|
||||
namespace {
|
||||
|
85
Telegram/SourceFiles/passport/passport_edit_identity_box.cpp
Normal file
85
Telegram/SourceFiles/passport/passport_edit_identity_box.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "passport/passport_edit_identity_box.h"
|
||||
|
||||
#include "passport/passport_form_controller.h"
|
||||
#include "ui/widgets/input_fields.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "styles/style_widgets.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_passport.h"
|
||||
|
||||
namespace Passport {
|
||||
|
||||
IdentityBox::IdentityBox(
|
||||
QWidget*,
|
||||
not_null<FormController*> controller,
|
||||
int fieldIndex,
|
||||
const IdentityData &data)
|
||||
: _controller(controller)
|
||||
, _fieldIndex(fieldIndex)
|
||||
, _name(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
langFactory(lng_signup_firstname),
|
||||
data.name)
|
||||
, _surname(
|
||||
this,
|
||||
st::defaultInputField,
|
||||
langFactory(lng_signup_lastname),
|
||||
data.surname) {
|
||||
}
|
||||
|
||||
void IdentityBox::prepare() {
|
||||
setTitle(langFactory(lng_passport_identity_title));
|
||||
|
||||
addButton(langFactory(lng_settings_save), [=] {
|
||||
save();
|
||||
});
|
||||
addButton(langFactory(lng_cancel), [=] {
|
||||
closeBox();
|
||||
});
|
||||
|
||||
setDimensions(
|
||||
st::boxWideWidth,
|
||||
(st::contactPadding.top()
|
||||
+ _name->height()
|
||||
+ st::contactSkip
|
||||
+ _surname->height()
|
||||
+ st::contactPadding.bottom()
|
||||
+ st::boxPadding.bottom()));
|
||||
}
|
||||
|
||||
void IdentityBox::setInnerFocus() {
|
||||
_name->setFocusFast();
|
||||
}
|
||||
|
||||
void IdentityBox::resizeEvent(QResizeEvent *e) {
|
||||
BoxContent::resizeEvent(e);
|
||||
|
||||
_name->resize((width()
|
||||
- st::boxPadding.left()
|
||||
- st::boxPadding.right()),
|
||||
_name->height());
|
||||
_surname->resize(_name->width(), _surname->height());
|
||||
_name->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
st::contactPadding.top());
|
||||
_surname->moveToLeft(
|
||||
st::boxPadding.left(),
|
||||
_name->y() + _name->height() + st::contactSkip);
|
||||
}
|
||||
|
||||
void IdentityBox::save() {
|
||||
auto data = IdentityData();
|
||||
data.name = _name->getLastText();
|
||||
data.surname = _surname->getLastText();
|
||||
_controller->saveFieldIdentity(_fieldIndex, data);
|
||||
}
|
||||
|
||||
} // namespace Passport
|
50
Telegram/SourceFiles/passport/passport_edit_identity_box.h
Normal file
50
Telegram/SourceFiles/passport/passport_edit_identity_box.h
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace Ui {
|
||||
class InputField;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Passport {
|
||||
|
||||
class FormController;
|
||||
|
||||
struct IdentityData {
|
||||
QString name;
|
||||
QString surname;
|
||||
};
|
||||
|
||||
class IdentityBox : public BoxContent {
|
||||
public:
|
||||
IdentityBox(
|
||||
QWidget*,
|
||||
not_null<FormController*> controller,
|
||||
int fieldIndex,
|
||||
const IdentityData &data);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void setInnerFocus() override;
|
||||
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
private:
|
||||
void save();
|
||||
|
||||
not_null<FormController*> _controller;
|
||||
int _fieldIndex = -1;
|
||||
|
||||
object_ptr<Ui::InputField> _name;
|
||||
object_ptr<Ui::InputField> _surname;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Passport
|
323
Telegram/SourceFiles/passport/passport_encryption.cpp
Normal file
323
Telegram/SourceFiles/passport/passport_encryption.cpp
Normal file
@ -0,0 +1,323 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "passport/passport_encryption.h"
|
||||
|
||||
#include "base/openssl_help.h"
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
namespace Passport {
|
||||
namespace {
|
||||
|
||||
constexpr auto kAesKeyLength = 32;
|
||||
constexpr auto kAesIvLength = 16;
|
||||
constexpr auto kSecretSize = 32;
|
||||
constexpr auto kMinPadding = 32;
|
||||
constexpr auto kMaxPadding = 255;
|
||||
constexpr auto kAlignTo = 16;
|
||||
|
||||
base::byte_vector SerializeData(const std::map<QString, QString> &data) {
|
||||
auto root = QJsonObject();
|
||||
for (const auto &[key, value] : data) {
|
||||
root.insert(key, value);
|
||||
}
|
||||
auto document = QJsonDocument(root);
|
||||
const auto result = document.toJson(QJsonDocument::Compact);
|
||||
const auto bytes = gsl::as_bytes(gsl::make_span(result));
|
||||
return { bytes.begin(), bytes.end() };
|
||||
}
|
||||
|
||||
std::map<QString, QString> DeserializeData(base::const_byte_span bytes) {
|
||||
const auto serialized = QByteArray::fromRawData(
|
||||
reinterpret_cast<const char*>(bytes.data()),
|
||||
bytes.size());
|
||||
auto error = QJsonParseError();
|
||||
auto document = QJsonDocument::fromJson(serialized, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG(("API Error: Could not deserialize decrypted JSON, error %1"
|
||||
).arg(error.errorString()));
|
||||
return {};
|
||||
} else if (!document.isObject()) {
|
||||
LOG(("API Error: decrypted JSON root is not an object."));
|
||||
return {};
|
||||
}
|
||||
auto object = document.object();
|
||||
auto result = std::map<QString, QString>();
|
||||
for (auto i = object.constBegin(), e = object.constEnd(); i != e; ++i) {
|
||||
const auto key = i.key();
|
||||
switch (i->type()) {
|
||||
case QJsonValue::Null: {
|
||||
LOG(("API Error: null found inside decrypted JSON root. "
|
||||
"Defaulting to empty string value."));
|
||||
result[key] = QString();
|
||||
} break;
|
||||
case QJsonValue::Undefined: {
|
||||
LOG(("API Error: undefined found inside decrypted JSON root. "
|
||||
"Defaulting to empty string value."));
|
||||
result[key] = QString();
|
||||
} break;
|
||||
case QJsonValue::Bool: {
|
||||
LOG(("API Error: bool found inside decrypted JSON root. "
|
||||
"Aborting."));
|
||||
return {};
|
||||
} break;
|
||||
case QJsonValue::Double: {
|
||||
LOG(("API Error: double found inside decrypted JSON root. "
|
||||
"Converting to string."));
|
||||
result[key] = QString::number(i->toDouble());
|
||||
} break;
|
||||
case QJsonValue::String: {
|
||||
result[key] = i->toString();
|
||||
} break;
|
||||
case QJsonValue::Array: {
|
||||
LOG(("API Error: array found inside decrypted JSON root. "
|
||||
"Aborting."));
|
||||
return {};
|
||||
} break;
|
||||
case QJsonValue::Object: {
|
||||
LOG(("API Error: object found inside decrypted JSON root. "
|
||||
"Aborting."));
|
||||
return {};
|
||||
} break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct AesParams {
|
||||
base::byte_array<kAesKeyLength> key;
|
||||
base::byte_array<kAesIvLength> iv;
|
||||
};
|
||||
|
||||
AesParams PrepareAesParams(base::const_byte_span secretHash) {
|
||||
const auto hash = openssl::Sha512(secretHash);
|
||||
const auto view = gsl::make_span(hash);
|
||||
const auto key = view.subspan(0, kAesKeyLength);
|
||||
const auto iv = view.subspan(kAesKeyLength, kAesIvLength);
|
||||
|
||||
auto result = AesParams();
|
||||
base::copy_bytes(result.key, view.subspan(0, kAesKeyLength));
|
||||
base::copy_bytes(result.iv, view.subspan(kAesKeyLength, kAesIvLength));
|
||||
return result;
|
||||
}
|
||||
|
||||
base::byte_vector EncryptOrDecrypt(
|
||||
base::const_byte_span initial,
|
||||
AesParams &¶ms,
|
||||
int encryptOrDecrypt) {
|
||||
Expects((initial.size() & 0x0F) == 0);
|
||||
|
||||
auto aesKey = AES_KEY();
|
||||
const auto error = (encryptOrDecrypt == AES_ENCRYPT)
|
||||
? AES_set_encrypt_key(
|
||||
reinterpret_cast<const uchar*>(params.key.data()),
|
||||
params.key.size() * CHAR_BIT,
|
||||
&aesKey)
|
||||
: AES_set_decrypt_key(
|
||||
reinterpret_cast<const uchar*>(params.key.data()),
|
||||
params.key.size() * CHAR_BIT,
|
||||
&aesKey);
|
||||
if (error != 0) {
|
||||
LOG(("App Error: Could not AES_set_encrypt_key, result %1"
|
||||
).arg(error));
|
||||
return {};
|
||||
}
|
||||
auto result = base::byte_vector(initial.size());
|
||||
AES_cbc_encrypt(
|
||||
reinterpret_cast<const uchar*>(initial.data()),
|
||||
reinterpret_cast<uchar*>(result.data()),
|
||||
initial.size(),
|
||||
&aesKey,
|
||||
reinterpret_cast<uchar*>(params.iv.data()),
|
||||
encryptOrDecrypt);
|
||||
return result;
|
||||
}
|
||||
|
||||
base::byte_vector Encrypt(
|
||||
base::const_byte_span decrypted,
|
||||
AesParams &¶ms) {
|
||||
return EncryptOrDecrypt(decrypted, std::move(params), AES_ENCRYPT);
|
||||
}
|
||||
|
||||
base::byte_vector Decrypt(
|
||||
base::const_byte_span encrypted,
|
||||
AesParams &¶ms) {
|
||||
return EncryptOrDecrypt(encrypted, std::move(params), AES_DECRYPT);
|
||||
}
|
||||
|
||||
base::byte_vector PasswordHashForSecret(
|
||||
base::const_byte_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(base::const_byte_span bytes) {
|
||||
const auto full = std::accumulate(
|
||||
bytes.begin(),
|
||||
bytes.end(),
|
||||
0ULL,
|
||||
[](uint64 sum, gsl::byte value) { return sum + uchar(value); });
|
||||
const auto mod = (full % 255ULL);
|
||||
return (mod == 239);
|
||||
}
|
||||
|
||||
bool CheckSecretBytes(base::const_byte_span secret) {
|
||||
return CheckBytesMod255(secret);
|
||||
}
|
||||
|
||||
base::byte_vector GenerateSecretBytes() {
|
||||
auto result = base::byte_vector(kSecretSize);
|
||||
memset_rand(result.data(), result.size());
|
||||
const auto full = std::accumulate(
|
||||
result.begin(),
|
||||
result.end(),
|
||||
0ULL,
|
||||
[](uint64 sum, gsl::byte value) { return sum + uchar(value); });
|
||||
const auto mod = (full % 255ULL);
|
||||
const auto add = 255ULL + 239 - mod;
|
||||
auto first = (static_cast<uchar>(result[0]) + add) % 255ULL;
|
||||
result[0] = static_cast<gsl::byte>(first);
|
||||
return result;
|
||||
}
|
||||
|
||||
base::byte_vector DecryptSecretBytes(
|
||||
base::const_byte_span encryptedSecret,
|
||||
base::const_byte_span passwordHashForSecret) {
|
||||
if (encryptedSecret.empty()) {
|
||||
return {};
|
||||
} else if (encryptedSecret.size() != kSecretSize) {
|
||||
LOG(("API Error: Wrong secret size %1"
|
||||
).arg(encryptedSecret.size()));
|
||||
return {};
|
||||
}
|
||||
auto params = PrepareAesParams(passwordHashForSecret);
|
||||
auto result = Decrypt(encryptedSecret, std::move(params));
|
||||
if (!CheckSecretBytes(result)) {
|
||||
LOG(("API Error: Bad secret bytes."));
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
base::byte_vector EncryptSecretBytes(
|
||||
base::const_byte_span secret,
|
||||
base::const_byte_span passwordHashForSecret) {
|
||||
Expects(secret.size() == kSecretSize);
|
||||
Expects(CheckSecretBytes(secret) == true);
|
||||
|
||||
auto params = PrepareAesParams(passwordHashForSecret);
|
||||
return Encrypt(secret, std::move(params));
|
||||
}
|
||||
|
||||
base::byte_vector Concatenate(
|
||||
base::const_byte_span a,
|
||||
base::const_byte_span b) {
|
||||
auto result = base::byte_vector(a.size() + b.size());
|
||||
base::copy_bytes(result, a);
|
||||
base::copy_bytes(gsl::make_span(result).subspan(a.size()), b);
|
||||
return result;
|
||||
}
|
||||
|
||||
EncryptedData EncryptData(
|
||||
base::const_byte_span dataSecret,
|
||||
const std::map<QString, QString> &data) {
|
||||
Expects(dataSecret.size() == kSecretSize);
|
||||
|
||||
const auto bytes = SerializeData(data);
|
||||
|
||||
constexpr auto kFromPadding = kMinPadding + kAlignTo - 1;
|
||||
constexpr auto kPaddingDelta = kMaxPadding - kFromPadding;
|
||||
const auto randomPadding = kFromPadding
|
||||
+ (rand_value<uint32>() % kPaddingDelta);
|
||||
const auto padding = randomPadding
|
||||
- ((bytes.size() + randomPadding) % kAlignTo);
|
||||
Assert(padding >= kMinPadding && padding <= kMaxPadding);
|
||||
|
||||
auto unencrypted = base::byte_vector(padding + bytes.size());
|
||||
Assert(unencrypted.size() % kAlignTo == 0);
|
||||
|
||||
unencrypted[0] = static_cast<gsl::byte>(padding);
|
||||
memset_rand(unencrypted.data() + 1, padding - 1);
|
||||
base::copy_bytes(
|
||||
gsl::make_span(unencrypted).subspan(padding),
|
||||
bytes);
|
||||
const auto dataHash = openssl::Sha256(unencrypted);
|
||||
const auto dataSecretHash = openssl::Sha512(
|
||||
Concatenate(dataSecret, dataHash));
|
||||
auto params = PrepareAesParams(dataSecretHash);
|
||||
return {
|
||||
{ dataHash.begin(), dataHash.end() },
|
||||
Encrypt(unencrypted, std::move(params))
|
||||
};
|
||||
}
|
||||
|
||||
std::map<QString, QString> DecryptData(
|
||||
base::const_byte_span encrypted,
|
||||
base::const_byte_span dataHash,
|
||||
base::const_byte_span dataSecret) {
|
||||
constexpr auto kDataHashSize = 32;
|
||||
if (encrypted.empty()) {
|
||||
return {};
|
||||
} else if (dataHash.size() != kDataHashSize) {
|
||||
LOG(("API Error: Bad data hash size %1").arg(dataHash.size()));
|
||||
return {};
|
||||
} else if (dataSecret.size() != kSecretSize) {
|
||||
LOG(("API Error: Bad data secret size %1").arg(dataSecret.size()));
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto dataSecretHash = openssl::Sha512(
|
||||
Concatenate(dataSecret, dataHash));
|
||||
auto params = PrepareAesParams(dataSecretHash);
|
||||
const auto decrypted = Decrypt(encrypted, std::move(params));
|
||||
if (base::compare_bytes(openssl::Sha256(decrypted), dataHash) != 0) {
|
||||
LOG(("API Error: Bad data hash."));
|
||||
return {};
|
||||
}
|
||||
const auto padding = static_cast<uchar>(decrypted[0]);
|
||||
if (padding < kMinPadding
|
||||
|| padding > kMaxPadding
|
||||
|| padding > decrypted.size()) {
|
||||
LOG(("API Error: Bad padding value %1").arg(padding));
|
||||
return {};
|
||||
}
|
||||
const auto bytes = gsl::make_span(decrypted).subspan(padding);
|
||||
return DeserializeData(bytes);
|
||||
}
|
||||
|
||||
base::byte_vector PrepareValueHash(
|
||||
base::const_byte_span dataHash,
|
||||
base::const_byte_span valueSecret) {
|
||||
const auto result = openssl::Sha256(Concatenate(dataHash, valueSecret));
|
||||
return { result.begin(), result.end() };
|
||||
}
|
||||
|
||||
base::byte_vector EncryptValueSecret(
|
||||
base::const_byte_span valueSecret,
|
||||
base::const_byte_span secret,
|
||||
base::const_byte_span valueHash) {
|
||||
const auto valueSecretHash = openssl::Sha512(
|
||||
Concatenate(secret, valueHash));
|
||||
return EncryptSecretBytes(valueSecret, valueSecretHash);
|
||||
}
|
||||
|
||||
base::byte_vector DecryptValueSecret(
|
||||
base::const_byte_span encrypted,
|
||||
base::const_byte_span secret,
|
||||
base::const_byte_span valueHash) {
|
||||
const auto valueSecretHash = openssl::Sha512(
|
||||
Concatenate(secret, valueHash));
|
||||
return DecryptSecretBytes(encrypted, valueSecretHash);
|
||||
}
|
||||
|
||||
} // namespace Passport
|
51
Telegram/SourceFiles/passport/passport_encryption.h
Normal file
51
Telegram/SourceFiles/passport/passport_encryption.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Passport {
|
||||
|
||||
base::byte_vector GenerateSecretBytes();
|
||||
|
||||
base::byte_vector EncryptSecretBytes(
|
||||
base::const_byte_span secret,
|
||||
base::const_byte_span passwordHashForSecret);
|
||||
base::byte_vector DecryptSecretBytes(
|
||||
base::const_byte_span encryptedSecret,
|
||||
base::const_byte_span passwordHashForSecret);
|
||||
|
||||
base::byte_vector PasswordHashForSecret(base::const_byte_span passwordUtf8);
|
||||
|
||||
struct EncryptedData {
|
||||
base::byte_vector hash;
|
||||
base::byte_vector bytes;
|
||||
};
|
||||
|
||||
EncryptedData EncryptData(
|
||||
base::const_byte_span dataSecret,
|
||||
const std::map<QString, QString> &data);
|
||||
|
||||
std::map<QString, QString> DecryptData(
|
||||
base::const_byte_span encrypted,
|
||||
base::const_byte_span dataHash,
|
||||
base::const_byte_span dataSecret);
|
||||
|
||||
base::byte_vector PrepareValueHash(
|
||||
base::const_byte_span dataHash,
|
||||
base::const_byte_span valueSecret);
|
||||
|
||||
base::byte_vector EncryptValueSecret(
|
||||
base::const_byte_span valueSecret,
|
||||
base::const_byte_span secret,
|
||||
base::const_byte_span valueHash);
|
||||
|
||||
base::byte_vector DecryptValueSecret(
|
||||
base::const_byte_span encrypted,
|
||||
base::const_byte_span secret,
|
||||
base::const_byte_span valueHash);
|
||||
|
||||
} // namespace Passport
|
@ -171,6 +171,9 @@ void FormBox::Inner::refresh() {
|
||||
bool ready) {
|
||||
if (_rows.size() <= index) {
|
||||
_rows.push_back(object_ptr<FormRow>(this, title, description));
|
||||
_rows[index]->addClickHandler([=] {
|
||||
_controller->editField(index);
|
||||
});
|
||||
}
|
||||
_rows[index++]->setReady(ready);
|
||||
});
|
||||
|
@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "passport/passport_form_controller.h"
|
||||
|
||||
#include "passport/passport_form_box.h"
|
||||
#include "passport/passport_edit_identity_box.h"
|
||||
#include "passport/passport_encryption.h"
|
||||
#include "boxes/confirm_box.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "base/openssl_help.h"
|
||||
@ -49,11 +51,15 @@ void FormController::submitPassword(const QString &password) {
|
||||
|
||||
if (_passwordCheckRequestId) {
|
||||
return;
|
||||
} else if (password.isEmpty()) {
|
||||
_passwordError.fire(QString());
|
||||
}
|
||||
const auto data = _password.salt + password.toUtf8() + _password.salt;
|
||||
const auto hash = hashSha256(data.constData(), data.size());
|
||||
const auto passwordBytes = password.toUtf8();
|
||||
const auto data = _password.salt + passwordBytes + _password.salt;
|
||||
const auto hash = openssl::Sha256(gsl::as_bytes(gsl::make_span(data)));
|
||||
_passwordHashForAuth = { hash.begin(), hash.end() };
|
||||
_passwordCheckRequestId = request(MTPaccount_GetPasswordSettings(
|
||||
MTP_bytes(gsl::as_bytes(gsl::make_span(hash)))
|
||||
MTP_bytes(_passwordHashForAuth)
|
||||
)).handleFloodErrors(
|
||||
).done([=](const MTPaccount_PasswordSettings &result) {
|
||||
Expects(result.type() == mtpc_account_passwordSettings);
|
||||
@ -61,7 +67,14 @@ void FormController::submitPassword(const QString &password) {
|
||||
_passwordCheckRequestId = 0;
|
||||
const auto &data = result.c_account_passwordSettings();
|
||||
_passwordEmail = qs(data.vemail);
|
||||
_secret = byteVectorFromMTP(data.vsecure_secret);
|
||||
const auto hash = openssl::Sha512(gsl::as_bytes(gsl::make_span(passwordBytes)));
|
||||
_passwordHashForSecret = { hash.begin(), hash.end() };
|
||||
_secret = DecryptSecretBytes(
|
||||
bytesFromMTP(data.vsecure_secret),
|
||||
_passwordHashForSecret);
|
||||
for (auto &field : _form.fields) {
|
||||
field.data.values = fillData(field.data);
|
||||
}
|
||||
_secretReady.fire({});
|
||||
}).fail([=](const RPCError &error) {
|
||||
_passwordCheckRequestId = 0;
|
||||
@ -133,6 +146,142 @@ void FormController::fillRows(
|
||||
}
|
||||
}
|
||||
|
||||
void FormController::editField(int index) {
|
||||
Expects(index >= 0 && index < _form.fields.size());
|
||||
|
||||
auto box = [&]() -> object_ptr<BoxContent> {
|
||||
const auto &field = _form.fields[index];
|
||||
switch (field.type) {
|
||||
case Field::Type::Identity:
|
||||
return Box<IdentityBox>(this, index, fieldDataIdentity(field));
|
||||
}
|
||||
return { nullptr };
|
||||
}();
|
||||
if (box) {
|
||||
_editBox = Ui::show(std::move(box), LayerOption::KeepOther);
|
||||
}
|
||||
}
|
||||
|
||||
IdentityData FormController::fieldDataIdentity(const Field &field) const {
|
||||
const auto &map = field.data.values;
|
||||
auto result = IdentityData();
|
||||
if (const auto i = map.find(qsl("first_name")); i != map.cend()) {
|
||||
result.name = i->second;
|
||||
}
|
||||
if (const auto i = map.find(qsl("last_name")); i != map.cend()) {
|
||||
result.surname = i->second;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void FormController::saveFieldIdentity(
|
||||
int index,
|
||||
const IdentityData &data) {
|
||||
Expects(_editBox != nullptr);
|
||||
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;
|
||||
|
||||
saveData(index);
|
||||
|
||||
_editBox->closeBox();
|
||||
}
|
||||
|
||||
std::map<QString, QString> FormController::fillData(
|
||||
const Value &from) const {
|
||||
if (from.data.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
const auto valueHash = gsl::as_bytes(gsl::make_span(from.dataHash));
|
||||
const auto valueSecret = DecryptValueSecret(
|
||||
gsl::as_bytes(gsl::make_span(from.dataSecret)),
|
||||
_secret,
|
||||
valueHash);
|
||||
return DecryptData(
|
||||
gsl::as_bytes(gsl::make_span(from.data)),
|
||||
valueHash,
|
||||
valueSecret);
|
||||
}
|
||||
|
||||
void FormController::saveData(int index) {
|
||||
Expects(index >= 0 && index < _form.fields.size());
|
||||
|
||||
if (_secret.empty()) {
|
||||
generateSecret([=] {
|
||||
saveData(index);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const auto &data = _form.fields[index].data;
|
||||
const auto valueSecret = GenerateSecretBytes();
|
||||
const auto encrypted = EncryptData(valueSecret, 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));
|
||||
}
|
||||
|
||||
const auto encryptedValueSecret = EncryptValueSecret(
|
||||
valueSecret,
|
||||
_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);
|
||||
}
|
||||
}).fail([=](const RPCError &error) {
|
||||
// #TODO
|
||||
}).send();
|
||||
}
|
||||
|
||||
void FormController::generateSecret(base::lambda<void()> callback) {
|
||||
if (_saveSecretRequestId) {
|
||||
return;
|
||||
}
|
||||
auto secret = GenerateSecretBytes();
|
||||
auto encryptedSecret = EncryptSecretBytes(
|
||||
secret,
|
||||
_passwordHashForSecret);
|
||||
using Flag = MTPDaccount_passwordInputSettings::Flag;
|
||||
_saveSecretRequestId = request(MTPaccount_UpdatePasswordSettings(
|
||||
MTP_bytes(_passwordHashForAuth),
|
||||
MTP_account_passwordInputSettings(
|
||||
MTP_flags(Flag::f_new_secure_secret),
|
||||
MTPbytes(), // new_salt
|
||||
MTPbytes(), // new_password_hash
|
||||
MTPstring(), // hint
|
||||
MTPstring(), // email
|
||||
MTP_bytes(encryptedSecret))
|
||||
)).done([=](const MTPBool &result) {
|
||||
_saveSecretRequestId = 0;
|
||||
_secret = secret;
|
||||
callback();
|
||||
}).fail([=](const RPCError &error) {
|
||||
// #TODO wrong password hash error?
|
||||
Ui::show(Box<InformBox>("Saving encrypted value failed."));
|
||||
_saveSecretRequestId = 0;
|
||||
}).send();
|
||||
}
|
||||
|
||||
void FormController::requestForm() {
|
||||
auto scope = QVector<MTPstring>();
|
||||
scope.reserve(_request.scope.size());
|
||||
@ -171,7 +320,30 @@ auto FormController::convertValue(
|
||||
const auto &data = value.c_secureValueData();
|
||||
result.name = qs(data.vname);
|
||||
result.data = data.vdata.v;
|
||||
result.dataHash = data.vhash.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);
|
||||
}
|
||||
}
|
||||
if (result.dataHash.size() != 32) {
|
||||
result.dataHash.clear();
|
||||
}
|
||||
// result.dataHash = data.vhash.v;
|
||||
result.dataSecret = data.vsecret.v;
|
||||
} break;
|
||||
case mtpc_secureValueText: {
|
||||
|
@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "mtproto/sender.h"
|
||||
|
||||
class BoxContent;
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
} // namespace Window
|
||||
@ -29,13 +31,10 @@ struct FormRequest {
|
||||
|
||||
};
|
||||
|
||||
struct IdentityData;
|
||||
|
||||
class FormController : private MTP::Sender {
|
||||
public:
|
||||
struct PasswordCheckResult {
|
||||
QByteArray secret;
|
||||
|
||||
};
|
||||
|
||||
FormController(
|
||||
not_null<Window::Controller*> controller,
|
||||
const FormRequest &request);
|
||||
@ -56,6 +55,9 @@ public:
|
||||
QString title,
|
||||
QString description,
|
||||
bool ready)> callback);
|
||||
void editField(int index);
|
||||
|
||||
void saveFieldIdentity(int index, const IdentityData &data);
|
||||
|
||||
private:
|
||||
struct File {
|
||||
@ -71,6 +73,7 @@ private:
|
||||
QByteArray data;
|
||||
QByteArray dataHash;
|
||||
QByteArray dataSecret;
|
||||
std::map<QString, QString> values;
|
||||
|
||||
QString text;
|
||||
QByteArray textHash;
|
||||
@ -118,6 +121,12 @@ private:
|
||||
void parsePassword(const MTPDaccount_noPassword &settings);
|
||||
void parsePassword(const MTPDaccount_password &settings);
|
||||
|
||||
IdentityData fieldDataIdentity(const Field &field) const;
|
||||
|
||||
std::map<QString, QString> fillData(const Value &from) const;
|
||||
void saveData(int index);
|
||||
void generateSecret(base::lambda<void()> callback);
|
||||
|
||||
not_null<Window::Controller*> _controller;
|
||||
FormRequest _request;
|
||||
UserData *_bot = nullptr;
|
||||
@ -130,11 +139,16 @@ private:
|
||||
PasswordSettings _password;
|
||||
Form _form;
|
||||
|
||||
base::byte_vector _passwordHashForSecret;
|
||||
base::byte_vector _passwordHashForAuth;
|
||||
base::byte_vector _secret;
|
||||
mtpRequestId _saveSecretRequestId = 0;
|
||||
QString _passwordEmail;
|
||||
rpl::event_stream<> _secretReady;
|
||||
rpl::event_stream<QString> _passwordError;
|
||||
|
||||
QPointer<BoxContent> _editBox;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Passport
|
||||
|
@ -454,6 +454,10 @@
|
||||
<(src_loc)/mtproto/type_utils.h
|
||||
<(src_loc)/overview/overview_layout.cpp
|
||||
<(src_loc)/overview/overview_layout.h
|
||||
<(src_loc)/passport/passport_edit_identity_box.cpp
|
||||
<(src_loc)/passport/passport_edit_identity_box.h
|
||||
<(src_loc)/passport/passport_encryption.cpp
|
||||
<(src_loc)/passport/passport_encryption.h
|
||||
<(src_loc)/passport/passport_form_box.cpp
|
||||
<(src_loc)/passport/passport_form_box.h
|
||||
<(src_loc)/passport/passport_form_controller.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user