aboutsummaryrefslogtreecommitdiffhomepage
path: root/tde2e/td/e2e/EncryptedStorage.cpp
blob: 44aa056c34bb02caab3224ef401c42310e2bb796 (plain)
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2026
//
// 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/e2e/EncryptedStorage.h"

#include "td/e2e/Blockchain.h"

#include "td/telegram/e2e_api.hpp"

#include "td/utils/crypto.h"
#include "td/utils/logging.h"
#include "td/utils/overloaded.h"
#include "td/utils/tl_parsers.h"

namespace tde2e_core {

api::UserId from_tl(td::int64 user_id) {
  return user_id;
}

api::Name from_tl(td::e2e_api::e2e_personalName &name) {
  return api::Name{name.first_name_, name.last_name_};
}

api::UserId from_tl(td::e2e_api::e2e_personalUserId &user_id) {
  return user_id.user_id_;
}

api::PhoneNumber from_tl(td::e2e_api::e2e_personalPhoneNumber &phone_number) {
  return api::PhoneNumber{phone_number.phone_number_};
}

api::EmojiNonces from_tl(td::e2e_api::e2e_personalEmojiNonces &emoji_nonces) {
  using Flags = td::e2e_api::e2e_personalEmojiNonces;
  api::EmojiNonces res;
  if ((emoji_nonces.flags_ & Flags::SELF_NONCE_MASK) != 0) {
    res.self_nonce = from_td(emoji_nonces.self_nonce_);
  }
  return res;
}

api::ContactState from_tl(td::e2e_api::e2e_personalContactState &contact_state) {
  return api::ContactState{contact_state.is_contact_ ? api::ContactState::Contact : api::ContactState::NotContact};
}

void init(api::Contact &contact, api::Entry<api::UserId> user_id) {
  contact.o_user_id = user_id;
}

void init(api::Contact &contact, api::Entry<api::Name> name) {
  contact.o_name = std::move(name);
}

void init(api::Contact &contact, api::Entry<api::PhoneNumber> phone_number) {
  contact.o_phone_number = std::move(phone_number);
}

void init(api::Contact &contact, api::Entry<api::EmojiNonces> emoji_nonces) {
  contact.emoji_nonces = std::move(emoji_nonces);
}

void init(api::Contact &contact, api::Entry<api::ContactState> contact_state) {
  contact.contact_state = std::move(contact_state);
}

api::Contact from_tl(td::e2e_api::e2e_valueContactByPublicKey &value) {
  api::Contact contact;
  for (auto &entry : value.entries_) {
    td::e2e_api::downcast_call(*entry->personal_, [&](auto &tl_value) {
      auto entry_value = from_tl(tl_value);
      using ValueT = decltype(entry_value);
      init(contact, api::Entry<ValueT>{api::Entry<ValueT>::Self, static_cast<td::uint32>(entry->signed_at_),
                                       std::move(entry_value)});
    });
  }
  return contact;
}

bool reduce(api::Entry<api::EmojiNonces> &a, const api::Entry<api::EmojiNonces> &b) {
  // do not care about timestamp, first write always win
  // TODO: handle source
  auto &nonces = a.value;
  const auto &other_nonces = b.value;

  bool changed = false;

  if (!nonces.self_nonce && other_nonces.self_nonce) {
    nonces.self_nonce = other_nonces.self_nonce;
    changed = true;
  }
  if (!nonces.contact_nonce_hash && other_nonces.contact_nonce_hash) {
    nonces.contact_nonce_hash = other_nonces.contact_nonce_hash;
    changed = true;
  }
  if (!nonces.contact_nonce && other_nonces.contact_nonce) {
    nonces.contact_nonce = other_nonces.contact_nonce;
    changed = true;
  }
  return changed;
}

template <class T>
bool reduce(api::Entry<T> &a, const api::Entry<T> &b) {
  if (a.timestamp > b.timestamp) {
    a = std::move(b);
    return false;
  }
  // TODO: handle source
  return false;
}

template <class T>
bool reduce(api::Entry<T> &a, const std::optional<api::Entry<T>> &b) {
  if (!b) {
    return false;
  }
  return reduce(a, *b);
}

template <class T>
bool reduce(std::optional<api::Entry<T>> &a, const std::optional<api::Entry<T>> &b) {
  if (!a) {
    a = b;
    return static_cast<bool>(b);
  }
  if (!b) {
    return false;
  }
  return reduce(*a, *b);
}

bool reduce(Update &a, const Update &b) {
  bool changed = false;
  changed = reduce(a.o_user_id, b.o_user_id);
  changed = reduce(a.o_name, b.o_name);
  changed = reduce(a.o_phone_number, b.o_phone_number);
  changed = reduce(a.o_emoji_nonces, b.o_emoji_nonces);
  changed = reduce(a.o_contact_state, b.o_contact_state);
  return changed;
}

std::optional<Value> apply_update(const std::optional<Value> &o_value, const Update &update) {
  auto value = o_value.value_or(Value());
  bool changed = false;
  changed |= reduce(value.o_name, update.o_name);
  changed |= reduce(value.o_phone_number, update.o_phone_number);
  changed |= reduce(value.o_user_id, update.o_user_id);
  changed |= reduce(value.emoji_nonces, update.o_emoji_nonces);
  changed |= reduce(value.contact_state, update.o_contact_state);
  if (changed) {
    return value;
  }
  return std::nullopt;
}

td::Status validate(const api::EmojiNonces &nonces) {
  if (nonces.contact_nonce && !nonces.self_nonce) {
    return td::Status::Error("Receive contact_nonce BEFORE self_nonce");
  }
  if (nonces.contact_nonce && !nonces.contact_nonce_hash) {
    return td::Status::Error("Receive contact_nonce BEFORE concat_nonce_hash");
  }
  if (nonces.contact_nonce) {
    auto &contact_nonce = nonces.contact_nonce.value();
    api::Int256 contact_nonce_hash;
    td::sha256(td::Slice(contact_nonce.data(), contact_nonce.size()),
               td::MutableSlice(contact_nonce_hash.data(), contact_nonce_hash.size()));

    if (contact_nonce_hash != nonces.contact_nonce_hash.value()) {
      return td::Status::Error("Invalid concat_nonce (hash mismatch)");
    }
  }
  return td::Status::OK();
}

td::Result<EncryptedStorage::UpdateId> EncryptedStorage::update(Key key, Update update) {
  LOG(INFO) << "Update [receive] " << key << " " << update;

  auto update_id = ++next_update_id_;
  auto it = updates_.find(key);
  if (it == updates_.end()) {
    // create pending update (original value is unknown)
    updates_.emplace(key, UpdateInfo{{update_id}, std::move(update), {}});
    LOG(INFO) << "Update [delay] " << key << " " << update;
    return update_id;
  }

  auto &update_info = it->second;
  reduce(update_info.update, update);
  update_info.update_ids.emplace_back(update_id);
  LOG(INFO) << "Update [reduce] " << key << " " << update_info.update;

  if (update_info.o_new_value && !reapply_update(update_info, std::move(update_info.o_new_value))) {
    LOG(INFO) << "Update [drop] " << key << " " << update;
    updates_.erase(it);
  }
  return update_id;
}

td::Result<std::optional<Value>> EncryptedStorage::get(Key key, bool optimistic) {
  auto it = partial_key_value_.find(key);
  if (it != partial_key_value_.end()) {
    if (optimistic) {
      auto update_it = updates_.find(key);
      if (update_it != updates_.end()) {
        CHECK(update_it->second.o_new_value);
        return *update_it->second.o_new_value;
      }
    }
    return it->second;
  }
  return td::Status::Error("NEED_PROOF");
}

td::int64 EncryptedStorage::get_height() const {
  return blockchain_.get_height();
}

td::Result<EncryptedStorage::KeyValueUpdates> EncryptedStorage::apply_block(td::Slice block) {
  TRY_RESULT(changes, blockchain_.try_apply_block(block));
  KeyValueUpdates updates;
  for (auto &change : changes) {
    bool skip = false;
    td::Result<std::pair<Key, std::optional<Value>>> r_p;
    std::visit(td::overloaded([&](ChangeNoop &noop) {},
                              [&](ChangeSetValue &set_value) { r_p = parse(set_value.key, set_value.value); },
                              [&](ChangeSetGroupState &) { skip = true; }, [&](ChangeSetSharedKey &) { skip = true; }),
               change.value);
    if (skip) {
      continue;
    }
    if (r_p.is_error()) {
      LOG(ERROR) << "BUG! change from blockchain is ignored: " << r_p.error();
      continue;
    }
    auto p = r_p.move_as_ok();
    updates.updates.emplace_back(p.first, p.second);
    sync_entry(std::move(p.first), std::move(p.second), true);
  }
  return updates;
}

td::Status EncryptedStorage::add_proof(td::Slice proof, td::Span<std::string> keys) {
  TRY_STATUS(blockchain_.add_proof(proof));
  // sync keys
  for (const auto &key : keys) {
    auto r_value = blockchain_.get_value(key);
    if (r_value.is_error()) {
      LOG(ERROR) << "Failed to get value from proof " << r_value.error();
      continue;
    }

    auto raw_value = r_value.move_as_ok();
    auto r_p = parse(key, raw_value);
    if (r_p.is_error()) {
      LOG(ERROR) << "BUG! value from blockchain is ignored: " << r_p.error();
      continue;
    }

    auto p = r_p.move_as_ok();
    sync_entry(std::move(p.first), std::move(p.second));
  }

  return td::Status::OK();
}

EncryptedStorage::BlockchainState EncryptedStorage::get_blockchain_state() {
  //TODO add indexes

  // check if some values are unknown
  BlockchainState state;
  std::vector<Change> changes;
  for (auto &update : updates_) {
    if (!update.second.o_new_value) {
      state.need_proofs.emplace_back(encrypt_key(update.first));
    } else {
      changes.emplace_back(
          Change{ChangeSetValue{encrypt_key(update.first), encrypt_value(update.second.o_new_value.value())}});
    }
  }
  if (!changes.empty()) {
    state.next_block = blockchain_.build_block(changes, private_key_).move_as_ok();
  }
  return state;
}

EncryptedStorage::KeyValueUpdates EncryptedStorage::pull_updates() {
  return std::move(pending_key_value_updates_);
}

td::Result<std::pair<Key, std::optional<Value>>> EncryptedStorage::parse(td::Slice raw_key, td::Slice raw_value) {
  TRY_RESULT(key, decrypt_key(raw_key));
  TRY_RESULT(value, decrypt_value(raw_value));
  return std::make_pair(std::move(key), std::move(value));
}

void EncryptedStorage::sync_entry(Key key, std::optional<Value> value, bool rewrite) {
  LOG(INFO) << "Sync [new] " << key << " " << value;
  auto p = partial_key_value_.try_emplace(key, std::move(value));
  if (!p.second) {
    if (rewrite) {
      p.first->second = std::move(value);
    } else {
      // CHECK(p.first->second == value);
    }
  }

  if (p.second || rewrite) {
    auto it = updates_.find(key);
    if (it != updates_.end()) {
      auto &update_info = it->second;
      if (!reapply_update(update_info, p.first->second)) {
        LOG(INFO) << "Update [drop] " << key << " " << update_info.update;
        updates_.erase(it);
      }
    }
  }
}

bool EncryptedStorage::reapply_update(UpdateInfo &update_info, const std::optional<Value> &value) {
  auto o_new_value = apply_update(value, update_info.update);
  if (o_new_value) {
    update_info.o_new_value = std::move(o_new_value);
    LOG(INFO) << "Update [reapply] value=" << update_info.o_new_value;
    return true;
  }

  // TODO: complete updates
  return false;
}

std::string EncryptedStorage::encrypt_key(const Key &key) const {
  td::string res(32, '\0');
  auto iv = secret_for_key_.as_slice().substr(32, 32).str();
  td::aes_cbc_encrypt(secret_for_key_.as_slice().substr(0, 32), iv, key.public_key.as_slice(), res);
  return res;
}

td::Result<Key> EncryptedStorage::decrypt_key(td::Slice raw_key) const {
  if (raw_key.size() != 32) {
    return td::Status::Error("Invalid key length");
  }
  td::UInt256 key;
  auto iv = secret_for_key_.as_slice().substr(32, 32).str();
  td::aes_cbc_decrypt(secret_for_key_.as_slice().substr(0, 32), iv, raw_key, key.as_mutable_slice());
  return Key{key};
}

std::string EncryptedStorage::encrypt_value(const Value &value) const {
  return MessageEncryption::encrypt_data(serialize_boxed(*to_tl(value)), secret_for_value_).as_slice().str();
}

td::Result<std::optional<Value>> EncryptedStorage::decrypt_value(td::Slice raw_value) const {
  if (raw_value.empty()) {
    return std::nullopt;
  }
  TRY_RESULT(decrypted_raw_value, MessageEncryption::decrypt_data(raw_value, secret_for_value_));
  td::TlParser parser(decrypted_raw_value);
  auto value_tl =
      td::e2e_api::move_object_as<td::e2e_api::e2e_valueContactByPublicKey>(td::e2e_api::e2e_Value::fetch(parser));
  parser.fetch_end();
  TRY_STATUS(parser.get_status());
  return from_tl(*value_tl);
}

}  // namespace tde2e_core