tdesktop/Telegram/SourceFiles/core/core_cloud_password.cpp

357 lines
10 KiB
C++

/*
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 "core/core_cloud_password.h"
#include "base/openssl_help.h"
#include "mtproto/mtproto_dh_utils.h"
namespace Core {
namespace {
using namespace openssl;
constexpr auto kAdditionalSalt = size_type(32);
constexpr auto kSizeForHash = 256;
bytes::vector NumBytesForHash(bytes::const_span number) {
const auto fill = kSizeForHash - number.size();
if (!fill) {
return bytes::make_vector(number);
}
auto result = bytes::vector(kSizeForHash);
const auto storage = bytes::make_span(result);
bytes::set_with_const(storage.subspan(0, fill), bytes::type(0));
bytes::copy(storage.subspan(fill), number);
return result;
}
bytes::vector BigNumForHash(const BigNum &number) {
auto result = number.getBytes();
if (result.size() == kSizeForHash) {
return result;
}
return NumBytesForHash(result);
}
bool IsPositive(const BigNum &number) {
return !number.isNegative() && (number.bitsSize() > 0);
}
bool IsGoodLarge(const BigNum &number, const BigNum &p) {
return IsPositive(number) && IsPositive(BigNum::Sub(p, number));
}
bytes::vector Xor(bytes::const_span a, bytes::const_span b) {
Expects(a.size() == b.size());
auto result = bytes::vector(a.size());
for (auto i = index_type(); i != a.size(); ++i) {
result[i] = a[i] ^ b[i];
}
return result;
};
bytes::vector ComputeHash(
const CloudPasswordAlgoModPow &algo,
bytes::const_span password) {
const auto hash1 = Sha256(algo.salt1, password, algo.salt1);
const auto hash2 = Sha256(algo.salt2, hash1, algo.salt2);
const auto hash3 = Pbkdf2Sha512(hash2, algo.salt1, algo.kIterations);
return Sha256(algo.salt2, hash3, algo.salt2);
}
CloudPasswordDigest ComputeDigest(
const CloudPasswordAlgoModPow &algo,
bytes::const_span password) {
if (!MTP::IsPrimeAndGood(algo.p, algo.g)) {
LOG(("API Error: Bad p/g in cloud password creation!"));
return {};
}
const auto value = BigNum::ModExp(
BigNum(algo.g),
BigNum(ComputeHash(algo, password)),
BigNum(algo.p));
if (value.failed()) {
LOG(("API Error: Failed to count g_x in cloud password creation!"));
return {};
}
return { BigNumForHash(value) };
}
CloudPasswordResult ComputeCheck(
const CloudPasswordCheckRequest &request,
const CloudPasswordAlgoModPow &algo,
bytes::const_span hash) {
const auto failed = [] {
return CloudPasswordResult{ MTP_inputCheckPasswordEmpty() };
};
const auto p = BigNum(algo.p);
const auto g = BigNum(algo.g);
const auto B = BigNum(request.B);
if (!MTP::IsPrimeAndGood(algo.p, algo.g)) {
LOG(("API Error: Bad p/g in cloud password creation!"));
return failed();
} else if (!IsGoodLarge(B, p)) {
LOG(("API Error: Bad B in cloud password check!"));
return failed();
}
const auto context = Context();
const auto x = BigNum(hash);
const auto pForHash = NumBytesForHash(algo.p);
const auto gForHash = BigNumForHash(g);
const auto BForHash = NumBytesForHash(request.B);
const auto g_x = BigNum::ModExp(g, x, p, context);
const auto k = BigNum(Sha256(pForHash, gForHash));
const auto kg_x = BigNum::ModMul(k, g_x, p, context);
const auto GenerateAndCheckRandom = [&] {
constexpr auto kRandomSize = 256;
while (true) {
auto random = bytes::vector(kRandomSize);
bytes::set_random(random);
const auto a = BigNum(random);
const auto A = BigNum::ModExp(g, a, p, context);
if (MTP::IsGoodModExpFirst(A, p)) {
auto AForHash = BigNumForHash(A);
const auto u = BigNum(Sha256(AForHash, BForHash));
if (IsPositive(u)) {
return std::make_tuple(a, std::move(AForHash), u);
}
}
}
};
const auto [a, AForHash, u] = GenerateAndCheckRandom();
const auto g_b = BigNum::ModSub(B, kg_x, p, context);
if (!MTP::IsGoodModExpFirst(g_b, p)) {
LOG(("API Error: Bad g_b in cloud password check!"));
return failed();
}
const auto ux = BigNum::Mul(u, x, context);
const auto a_ux = BigNum::Add(a, ux);
const auto S = BigNum::ModExp(g_b, a_ux, p, context);
if (S.failed()) {
LOG(("API Error: Failed to count S in cloud password check!"));
return failed();
}
const auto K = Sha256(BigNumForHash(S));
const auto M1 = Sha256(
Xor(Sha256(pForHash), Sha256(gForHash)),
Sha256(algo.salt1),
Sha256(algo.salt2),
AForHash,
BForHash,
K);
return CloudPasswordResult{ MTP_inputCheckPasswordSRP(
MTP_long(request.id),
MTP_bytes(AForHash),
MTP_bytes(M1))
};
}
bytes::vector ComputeHash(
v::null_t,
bytes::const_span password) {
Unexpected("Bad secure secret algorithm.");
}
bytes::vector ComputeHash(
const SecureSecretAlgoSHA512 &algo,
bytes::const_span password) {
return Sha512(algo.salt, password, algo.salt);
}
bytes::vector ComputeHash(
const SecureSecretAlgoPBKDF2 &algo,
bytes::const_span password) {
return Pbkdf2Sha512(password, algo.salt, algo.kIterations);
}
} // namespace
CloudPasswordAlgo ParseCloudPasswordAlgo(const MTPPasswordKdfAlgo &data) {
return data.match([](const MTPDpasswordKdfAlgoModPow &data) {
return CloudPasswordAlgo(CloudPasswordAlgoModPow{
bytes::make_vector(data.vsalt1().v),
bytes::make_vector(data.vsalt2().v),
data.vg().v,
bytes::make_vector(data.vp().v) });
}, [](const MTPDpasswordKdfAlgoUnknown &data) {
return CloudPasswordAlgo();
});
}
CloudPasswordCheckRequest ParseCloudPasswordCheckRequest(
const MTPDaccount_password &data) {
const auto algo = data.vcurrent_algo();
return CloudPasswordCheckRequest{
data.vsrp_id().value_or_empty(),
bytes::make_vector(data.vsrp_B().value_or_empty()),
(algo ? ParseCloudPasswordAlgo(*algo) : CloudPasswordAlgo())
};
}
CloudPasswordAlgo ValidateNewCloudPasswordAlgo(CloudPasswordAlgo &&parsed) {
if (!v::is<CloudPasswordAlgoModPow>(parsed)) {
return v::null;
}
auto &value = v::get<CloudPasswordAlgoModPow>(parsed);
const auto already = value.salt1.size();
value.salt1.resize(already + kAdditionalSalt);
bytes::set_random(bytes::make_span(value.salt1).subspan(already));
return std::move(parsed);
}
MTPPasswordKdfAlgo PrepareCloudPasswordAlgo(const CloudPasswordAlgo &data) {
return v::match(data, [](const CloudPasswordAlgoModPow &data) {
return MTP_passwordKdfAlgoModPow(
MTP_bytes(data.salt1),
MTP_bytes(data.salt2),
MTP_int(data.g),
MTP_bytes(data.p));
}, [](v::null_t) {
return MTP_passwordKdfAlgoUnknown();
});
}
CloudPasswordResult::operator bool() const {
return (result.type() != mtpc_inputCheckPasswordEmpty);
}
bytes::vector ComputeCloudPasswordHash(
const CloudPasswordAlgo &algo,
bytes::const_span password) {
return v::match(algo, [&](const CloudPasswordAlgoModPow &data) {
return ComputeHash(data, password);
}, [](v::null_t) -> bytes::vector {
Unexpected("Bad cloud password algorithm.");
});
}
CloudPasswordDigest ComputeCloudPasswordDigest(
const CloudPasswordAlgo &algo,
bytes::const_span password) {
return v::match(algo, [&](const CloudPasswordAlgoModPow &data) {
return ComputeDigest(data, password);
}, [](v::null_t) -> CloudPasswordDigest {
Unexpected("Bad cloud password algorithm.");
});
}
CloudPasswordResult ComputeCloudPasswordCheck(
const CloudPasswordCheckRequest &request,
bytes::const_span hash) {
return v::match(request.algo, [&](const CloudPasswordAlgoModPow &data) {
return ComputeCheck(request, data, hash);
}, [](v::null_t) -> CloudPasswordResult {
Unexpected("Bad cloud password algorithm.");
});
}
SecureSecretAlgo ParseSecureSecretAlgo(
const MTPSecurePasswordKdfAlgo &data) {
return data.match([](
const MTPDsecurePasswordKdfAlgoPBKDF2HMACSHA512iter100000 &data) {
return SecureSecretAlgo(SecureSecretAlgoPBKDF2{
bytes::make_vector(data.vsalt().v) });
}, [](const MTPDsecurePasswordKdfAlgoSHA512 &data) {
return SecureSecretAlgo(SecureSecretAlgoSHA512{
bytes::make_vector(data.vsalt().v) });
}, [](const MTPDsecurePasswordKdfAlgoUnknown &data) {
return SecureSecretAlgo();
});
}
SecureSecretAlgo ValidateNewSecureSecretAlgo(SecureSecretAlgo &&parsed) {
if (!v::is<SecureSecretAlgoPBKDF2>(parsed)) {
return v::null;
}
auto &value = v::get<SecureSecretAlgoPBKDF2>(parsed);
const auto already = value.salt.size();
value.salt.resize(already + kAdditionalSalt);
bytes::set_random(bytes::make_span(value.salt).subspan(already));
return std::move(parsed);
}
MTPSecurePasswordKdfAlgo PrepareSecureSecretAlgo(
const SecureSecretAlgo &data) {
return v::match(data, [](const SecureSecretAlgoPBKDF2 &data) {
return MTP_securePasswordKdfAlgoPBKDF2HMACSHA512iter100000(
MTP_bytes(data.salt));
}, [](const SecureSecretAlgoSHA512 &data) {
return MTP_securePasswordKdfAlgoSHA512(MTP_bytes(data.salt));
}, [](v::null_t) {
return MTP_securePasswordKdfAlgoUnknown();
});
}
bytes::vector ComputeSecureSecretHash(
const SecureSecretAlgo &algo,
bytes::const_span password) {
return v::match(algo, [&](const auto &data) {
return ComputeHash(data, password);
});
}
CloudPasswordState ParseCloudPasswordState(
const MTPDaccount_password &data) {
auto result = CloudPasswordState();
result.mtp.request = ParseCloudPasswordCheckRequest(data);
result.hasPassword = (!!result.mtp.request);
result.mtp.unknownAlgorithm = data.vcurrent_algo() && !result.hasPassword;
result.hasRecovery = data.is_has_recovery();
result.notEmptyPassport = data.is_has_secure_values();
result.hint = qs(data.vhint().value_or_empty());
result.mtp.newPassword = ValidateNewCloudPasswordAlgo(
ParseCloudPasswordAlgo(data.vnew_algo()));
result.mtp.newSecureSecret = ValidateNewSecureSecretAlgo(
ParseSecureSecretAlgo(data.vnew_secure_algo()));
result.unconfirmedPattern =
qs(data.vemail_unconfirmed_pattern().value_or_empty());
result.pendingResetDate = data.vpending_reset_date().value_or_empty();
result.outdatedClient = [&] {
const auto badSecureAlgo = data.vnew_secure_algo().match([](
const MTPDsecurePasswordKdfAlgoUnknown &) {
return true;
}, [](const auto &) {
return false;
});
if (badSecureAlgo) {
return true;
}
if (data.vcurrent_algo()) {
const auto badCurrentAlgo = data.vcurrent_algo()->match([](
const MTPDpasswordKdfAlgoUnknown &) {
return true;
}, [](const auto &) {
return false;
});
if (badCurrentAlgo) {
return true;
}
}
const auto badNewAlgo = data.vnew_algo().match([](
const MTPDpasswordKdfAlgoUnknown &) {
return true;
}, [](const auto &) {
return false;
});
if (badNewAlgo) {
return true;
}
return false;
}();
return result;
}
} // namespace Core