1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/mtproto/AuthData.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/Random.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Time.h"
#include <algorithm>
namespace td {
namespace mtproto {
Status check_message_id_duplicates(int64 *saved_message_ids, size_t max_size, size_t &end_pos, int64 message_id) {
// In addition, the identifiers (msg_id) of the last N messages received from the other side must be stored, and if
// a message comes in with msg_id lower than all or equal to any of the stored values, that message is to be
// ignored. Otherwise, the new message msg_id is added to the set, and, if the number of stored msg_id values is
// greater than N, the oldest (i.e. the lowest) is forgotten.
if (end_pos == 2 * max_size) {
std::copy_n(&saved_message_ids[max_size], max_size, &saved_message_ids[0]);
end_pos = max_size;
}
if (end_pos == 0 || message_id > saved_message_ids[end_pos - 1]) {
// fast path
saved_message_ids[end_pos++] = message_id;
return Status::OK();
}
if (end_pos >= max_size && message_id < saved_message_ids[0]) {
return Status::Error(2, PSLICE() << "Ignore very old message " << message_id
<< " older than the oldest known message " << saved_message_ids[0]);
}
auto it = std::lower_bound(&saved_message_ids[0], &saved_message_ids[end_pos], message_id);
if (*it == message_id) {
return Status::Error(1, PSLICE() << "Ignore already processed message " << message_id);
}
std::copy_backward(it, &saved_message_ids[end_pos], &saved_message_ids[end_pos + 1]);
*it = message_id;
++end_pos;
return Status::OK();
}
AuthData::AuthData() {
server_salt_.salt = Random::secure_int64();
server_salt_.valid_since = -1e10;
server_salt_.valid_until = -1e10;
}
bool AuthData::is_ready(double now) {
if (!has_main_auth_key()) {
LOG(INFO) << "Need main auth key";
return false;
}
if (use_pfs() && !has_tmp_auth_key(now)) {
LOG(INFO) << "Need tmp auth key";
return false;
}
if (!has_salt(now)) {
LOG(INFO) << "Need salt";
return false;
}
return true;
}
bool AuthData::update_server_time_difference(double diff) {
if (!server_time_difference_was_updated_) {
server_time_difference_was_updated_ = true;
LOG(DEBUG) << "UPDATE_SERVER_TIME_DIFFERENCE: " << server_time_difference_ << " -> " << diff;
server_time_difference_ = diff;
} else if (server_time_difference_ + 1e-4 < diff) {
LOG(DEBUG) << "UPDATE_SERVER_TIME_DIFFERENCE: " << server_time_difference_ << " -> " << diff;
server_time_difference_ = diff;
} else {
return false;
}
LOG(DEBUG) << "SERVER_TIME: " << format::as_hex(static_cast<int32>(get_server_time(Time::now_cached())));
return true;
}
void AuthData::set_future_salts(const std::vector<ServerSalt> &salts, double now) {
if (salts.empty()) {
return;
}
future_salts_ = salts;
std::sort(future_salts_.begin(), future_salts_.end(),
[](const ServerSalt &a, const ServerSalt &b) { return a.valid_since > b.valid_since; });
update_salt(now);
}
std::vector<ServerSalt> AuthData::get_future_salts() const {
auto res = future_salts_;
res.push_back(server_salt_);
return res;
}
int64 AuthData::next_message_id(double now) {
double server_time = get_server_time(now);
auto t = static_cast<int64>(server_time * (static_cast<int64>(1) << 32));
// randomize lower bits for clocks with low precision
// TODO(perf) do not do this for systems with good precision?..
auto rx = Random::secure_int32();
auto to_xor = rx & ((1 << 22) - 1);
auto to_mul = ((rx >> 22) & 1023) + 1;
t ^= to_xor;
auto result = t & -4;
if (last_message_id_ >= result) {
result = last_message_id_ + 8 * to_mul;
}
last_message_id_ = result;
return result;
}
bool AuthData::is_valid_outbound_msg_id(int64 id, double now) const {
double server_time = get_server_time(now);
auto id_time = static_cast<double>(id) / static_cast<double>(static_cast<int64>(1) << 32);
return server_time - 150 < id_time && id_time < server_time + 30;
}
bool AuthData::is_valid_inbound_msg_id(int64 id, double now) const {
double server_time = get_server_time(now);
auto id_time = static_cast<double>(id) / static_cast<double>(static_cast<int64>(1) << 32);
return server_time - 300 < id_time && id_time < server_time + 30;
}
Status AuthData::check_packet(int64 session_id, int64 message_id, double now, bool &time_difference_was_updated) {
// Client is to check that the session_id field in the decrypted message indeed equals to that of an active session
// created by the client.
if (get_session_id() != static_cast<uint64>(session_id)) {
return Status::Error(PSLICE() << "Receive packet from different session " << session_id << " in session "
<< get_session_id());
}
// Client must check that msg_id has even parity for messages from client to server, and odd parity for messages
// from server to client.
if ((message_id & 1) == 0) {
return Status::Error(PSLICE() << "Receive invalid message identifier " << message_id);
}
TRY_STATUS(duplicate_checker_.check(message_id));
time_difference_was_updated = update_server_time_difference(static_cast<uint32>(message_id >> 32) - now);
// In addition, msg_id values that belong over 30 seconds in the future or over 300 seconds in the past are to be
// ignored (recall that msg_id approximately equals unixtime * 2^32). This is especially important for the server.
// The client would also find this useful (to protect from a replay attack), but only if it is certain of its time
// (for example, if its time has been synchronized with that of the server).
if (server_time_difference_was_updated_ && !is_valid_inbound_msg_id(message_id, now)) {
return Status::Error(PSLICE() << "Ignore too old or too new message " << message_id);
}
return Status::OK();
}
void AuthData::update_salt(double now) {
double server_time = get_server_time(now);
while (!future_salts_.empty() && (future_salts_.back().valid_since < server_time)) {
server_salt_ = future_salts_.back();
future_salts_.pop_back();
}
}
} // namespace mtproto
} // namespace td
|