diff --git a/td/mtproto/crypto.cpp b/td/mtproto/crypto.cpp index 0a536b881..ce2fda5c6 100644 --- a/td/mtproto/crypto.cpp +++ b/td/mtproto/crypto.cpp @@ -134,17 +134,21 @@ void RSA::decrypt(Slice from, MutableSlice to) const { std::memcpy(to.data(), result.c_str(), 256); } -/*** DH ***/ -Status DhHandshake::dh_check(Slice prime_str, const BigNum &prime, int32 g_int, const BigNum &g_a, const BigNum &g_b, - BigNumContext &ctx, DhCallback *callback) { - // 2. g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p. - // Since g is always equal to 2, 3, 4, 5, 6 or 7, this is easily done using quadratic reciprocity law, - // yielding a simple condition on - // * p mod 4g - namely, p mod 8 = 7 for g = 2; p mod 3 = 2 for g = 3; - // * no extra condition for g = 4; - // * p mod 5 = 1 or 4 for g = 5; - // * p mod 24 = 19 or 23 for g = 6; - // * p mod 7 = 3, 5 or 6 for g = 7. +Status DhHandshake::check_config(Slice prime_str, const BigNum &prime, int32 g_int, BigNumContext &ctx, + DhCallback *callback) { + // check that 2^2047 <= p < 2^2048 + if (prime.get_num_bits() != 2048) { + return Status::Error("p is not 2048-bit number"); + } + + // g generates a cyclic subgroup of prime order (p - 1) / 2, i.e. is a quadratic residue mod p. + // Since g is always equal to 2, 3, 4, 5, 6 or 7, this is easily done using quadratic reciprocity law, + // yielding a simple condition on + // * p mod 4g - namely, p mod 8 = 7 for g = 2; p mod 3 = 2 for g = 3; + // * no extra condition for g = 4; + // * p mod 5 = 1 or 4 for g = 5; + // * p mod 24 = 19 or 23 for g = 6; + // * p mod 7 = 3, 5 or 6 for g = 7. bool mod_ok; uint32 mod_r; @@ -174,40 +178,7 @@ Status DhHandshake::dh_check(Slice prime_str, const BigNum &prime, int32 g_int, return Status::Error("Bad prime mod 4g"); } - // IMPORTANT: Apart from the conditions on the Diffie-Hellman prime dh_prime and generator g, both sides are - // to check that g, g_a and g_b are greater than 1 and less than dh_prime - 1. - // We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well. - - // check that 2^2047 <= p < 2^2048 - if (prime.get_num_bits() != 2048) { - return Status::Error("p is not 2048-bit number"); - } - - BigNum left; - left.set_value(0); - left.set_bit(2048 - 64); - - BigNum right; - BigNum::sub(right, prime, left); - - if (BigNum::compare(left, g_a) > 0 || BigNum::compare(g_a, right) > 0 || BigNum::compare(left, g_b) > 0 || - BigNum::compare(g_b, right) > 0) { - std::string x(2048, '0'); - std::string y(2048, '0'); - for (int i = 0; i < 2048; i++) { - if (g_a.is_bit_set(i)) { - x[i] = '1'; - } - if (g_b.is_bit_set(i)) { - y[i] = '1'; - } - } - LOG(ERROR) << x; - LOG(ERROR) << y; - return Status::Error("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}"); - } - - // check whether p = dh_prime is a safe 2048-bit prime (meaning that both p and (p - 1) / 2 are prime) + // check whether p is a safe prime (meaning that both p and (p - 1) / 2 are prime) int is_good_prime = -1; if (callback) { is_good_prime = callback->is_good_prime(prime_str); @@ -234,21 +205,41 @@ Status DhHandshake::dh_check(Slice prime_str, const BigNum &prime, int32 g_int, if (callback) { callback->add_good_prime(prime_str); } + return Status::OK(); +} - // TODO(perf): - // Checks: - // After g and p have been checked by the client, it makes sense to cache the result, - // so as not to repeat lengthy computations in future. +Status DhHandshake::dh_check(Slice prime_str, const BigNum &prime, int32 g_int, const BigNum &g_a, const BigNum &g_b, + BigNumContext &ctx, DhCallback *callback) { + TRY_STATUS(check_config(prime_str, prime, g_int, ctx, callback)); - // If the verification takes too long time (which is the case for older mobile devices), - // one might initially run only 15 Miller-Rabin iterations for verifying primeness of p and (p - 1)/2 - // with error probability not exceeding one billionth, and do more iterations later in the background. + // IMPORTANT: Apart from the conditions on the Diffie-Hellman prime dh_prime and generator g, both sides are + // to check that g, g_a and g_b are greater than 1 and less than dh_prime - 1. + // We recommend checking that g_a and g_b are between 2^{2048-64} and dh_prime - 2^{2048-64} as well. - // Another optimization is to embed into the client application code a small table with some known "good" - // couples (g,p) (or just known safe primes p, since the condition on g is easily verified during execution), - // checked during code generation phase, so as to avoid doing such verification during runtime altogether. - // Server changes these values rarely, thus one usually has to put the current value of server's dh_prime - // into such a table. For example, current value of dh_prime equals (in big-endian byte order) ... + CHECK(prime.get_num_bits() == 2048); + BigNum left; + left.set_value(0); + left.set_bit(2048 - 64); + + BigNum right; + BigNum::sub(right, prime, left); + + if (BigNum::compare(left, g_a) > 0 || BigNum::compare(g_a, right) > 0 || BigNum::compare(left, g_b) > 0 || + BigNum::compare(g_b, right) > 0) { + std::string x(2048, '0'); + std::string y(2048, '0'); + for (int i = 0; i < 2048; i++) { + if (g_a.is_bit_set(i)) { + x[i] = '1'; + } + if (g_b.is_bit_set(i)) { + y[i] = '1'; + } + } + LOG(ERROR) << x; + LOG(ERROR) << y; + return Status::Error("g^a or g^b is not between 2^{2048-64} and dh_prime - 2^{2048-64}"); + } return Status::OK(); } @@ -276,6 +267,12 @@ void DhHandshake::set_config(int32 g_int, Slice prime_str) { BigNum::mod_exp(g_b_, g_, b_, prime_, ctx_); } +Status DhHandshake::check_config(int32 g_int, Slice prime_str, DhCallback *callback) { + BigNumContext ctx; + auto prime = BigNum::from_binary(prime_str); + return check_config(prime_str, prime, g_int, ctx, callback); +} + void DhHandshake::set_g_a_hash(Slice g_a_hash) { has_g_a_hash_ = true; ok_g_a_hash_ = false; diff --git a/td/mtproto/crypto.h b/td/mtproto/crypto.h index 648045d16..696146457 100644 --- a/td/mtproto/crypto.h +++ b/td/mtproto/crypto.h @@ -60,6 +60,8 @@ class DhHandshake { public: void set_config(int32 g_int, Slice prime_str); + static Status check_config(int32 g_int, Slice prime_str, DhCallback *callback); + bool has_config() const { return has_config_; } @@ -133,6 +135,8 @@ class DhHandshake { } private: + static Status check_config(Slice prime_str, const BigNum &prime, int32 g_int, BigNumContext &ctx, + DhCallback *callback) TD_WARN_UNUSED_RESULT; static Status dh_check(Slice prime_str, const BigNum &prime, int32 g_int, const BigNum &g_a, const BigNum &g_b, BigNumContext &ctx, DhCallback *callback) TD_WARN_UNUSED_RESULT;