1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "sync/util/cryptographer.h"
6
7#include <algorithm>
8
9#include "base/base64.h"
10#include "base/basictypes.h"
11#include "base/logging.h"
12#include "sync/protocol/nigori_specifics.pb.h"
13#include "sync/util/encryptor.h"
14
15namespace syncer {
16
17const char kNigoriTag[] = "google_chrome_nigori";
18
19// We name a particular Nigori instance (ie. a triplet consisting of a hostname,
20// a username, and a password) by calling Permute on this string. Since the
21// output of Permute is always the same for a given triplet, clients will always
22// assign the same name to a particular triplet.
23const char kNigoriKeyName[] = "nigori-key";
24
25Cryptographer::Cryptographer(Encryptor* encryptor)
26    : encryptor_(encryptor) {
27  DCHECK(encryptor);
28}
29
30Cryptographer::Cryptographer(const Cryptographer& other)
31    : encryptor_(other.encryptor_),
32      default_nigori_name_(other.default_nigori_name_) {
33  for (NigoriMap::const_iterator it = other.nigoris_.begin();
34       it != other.nigoris_.end();
35       ++it) {
36    std::string user_key, encryption_key, mac_key;
37    it->second->ExportKeys(&user_key, &encryption_key, &mac_key);
38    linked_ptr<Nigori> nigori_copy(new Nigori());
39    nigori_copy->InitByImport(user_key, encryption_key, mac_key);
40    nigoris_.insert(std::make_pair(it->first, nigori_copy));
41  }
42
43  if (other.pending_keys_) {
44    pending_keys_.reset(new sync_pb::EncryptedData(*(other.pending_keys_)));
45  }
46}
47
48Cryptographer::~Cryptographer() {}
49
50
51void Cryptographer::Bootstrap(const std::string& restored_bootstrap_token) {
52  if (is_initialized()) {
53    NOTREACHED();
54    return;
55  }
56
57  std::string serialized_nigori_key =
58      UnpackBootstrapToken(restored_bootstrap_token);
59  if (serialized_nigori_key.empty())
60    return;
61  ImportNigoriKey(serialized_nigori_key);
62}
63
64bool Cryptographer::CanDecrypt(const sync_pb::EncryptedData& data) const {
65  return nigoris_.end() != nigoris_.find(data.key_name());
66}
67
68bool Cryptographer::CanDecryptUsingDefaultKey(
69    const sync_pb::EncryptedData& data) const {
70  return !default_nigori_name_.empty() &&
71         data.key_name() == default_nigori_name_;
72}
73
74bool Cryptographer::Encrypt(
75    const ::google::protobuf::MessageLite& message,
76    sync_pb::EncryptedData* encrypted) const {
77  DCHECK(encrypted);
78  if (default_nigori_name_.empty()) {
79    LOG(ERROR) << "Cryptographer not ready, failed to encrypt.";
80    return false;
81  }
82
83  std::string serialized;
84  if (!message.SerializeToString(&serialized)) {
85    LOG(ERROR) << "Message is invalid/missing a required field.";
86    return false;
87  }
88
89  return EncryptString(serialized, encrypted);
90}
91
92bool Cryptographer::EncryptString(
93    const std::string& serialized,
94    sync_pb::EncryptedData* encrypted) const {
95  if (CanDecryptUsingDefaultKey(*encrypted)) {
96    const std::string& original_serialized = DecryptToString(*encrypted);
97    if (original_serialized == serialized) {
98      DVLOG(2) << "Re-encryption unnecessary, encrypted data already matches.";
99      return true;
100    }
101  }
102
103  NigoriMap::const_iterator default_nigori =
104      nigoris_.find(default_nigori_name_);
105  if (default_nigori == nigoris_.end()) {
106    LOG(ERROR) << "Corrupt default key.";
107    return false;
108  }
109
110  encrypted->set_key_name(default_nigori_name_);
111  if (!default_nigori->second->Encrypt(serialized,
112                                       encrypted->mutable_blob())) {
113    LOG(ERROR) << "Failed to encrypt data.";
114    return false;
115  }
116  return true;
117}
118
119bool Cryptographer::Decrypt(const sync_pb::EncryptedData& encrypted,
120                            ::google::protobuf::MessageLite* message) const {
121  DCHECK(message);
122  std::string plaintext = DecryptToString(encrypted);
123  return message->ParseFromString(plaintext);
124}
125
126std::string Cryptographer::DecryptToString(
127    const sync_pb::EncryptedData& encrypted) const {
128  NigoriMap::const_iterator it = nigoris_.find(encrypted.key_name());
129  if (nigoris_.end() == it) {
130    NOTREACHED() << "Cannot decrypt message";
131    return std::string();  // Caller should have called CanDecrypt(encrypt).
132  }
133
134  std::string plaintext;
135  if (!it->second->Decrypt(encrypted.blob(), &plaintext)) {
136    return std::string();
137  }
138
139  return plaintext;
140}
141
142bool Cryptographer::GetKeys(sync_pb::EncryptedData* encrypted) const {
143  DCHECK(encrypted);
144  DCHECK(!nigoris_.empty());
145
146  // Create a bag of all the Nigori parameters we know about.
147  sync_pb::NigoriKeyBag bag;
148  for (NigoriMap::const_iterator it = nigoris_.begin(); it != nigoris_.end();
149       ++it) {
150    const Nigori& nigori = *it->second;
151    sync_pb::NigoriKey* key = bag.add_key();
152    key->set_name(it->first);
153    nigori.ExportKeys(key->mutable_user_key(),
154                      key->mutable_encryption_key(),
155                      key->mutable_mac_key());
156  }
157
158  // Encrypt the bag with the default Nigori.
159  return Encrypt(bag, encrypted);
160}
161
162bool Cryptographer::AddKey(const KeyParams& params) {
163  // Create the new Nigori and make it the default encryptor.
164  scoped_ptr<Nigori> nigori(new Nigori);
165  if (!nigori->InitByDerivation(params.hostname,
166                                params.username,
167                                params.password)) {
168    NOTREACHED();  // Invalid username or password.
169    return false;
170  }
171  return AddKeyImpl(nigori.Pass(), true);
172}
173
174bool Cryptographer::AddNonDefaultKey(const KeyParams& params) {
175  DCHECK(is_initialized());
176  // Create the new Nigori and add it to the keybag.
177  scoped_ptr<Nigori> nigori(new Nigori);
178  if (!nigori->InitByDerivation(params.hostname,
179                                params.username,
180                                params.password)) {
181    NOTREACHED();  // Invalid username or password.
182    return false;
183  }
184  return AddKeyImpl(nigori.Pass(), false);
185}
186
187bool Cryptographer::AddKeyFromBootstrapToken(
188    const std::string restored_bootstrap_token) {
189  // Create the new Nigori and make it the default encryptor.
190  std::string serialized_nigori_key = UnpackBootstrapToken(
191      restored_bootstrap_token);
192  return ImportNigoriKey(serialized_nigori_key);
193}
194
195bool Cryptographer::AddKeyImpl(scoped_ptr<Nigori> initialized_nigori,
196                               bool set_as_default) {
197  std::string name;
198  if (!initialized_nigori->Permute(Nigori::Password, kNigoriKeyName, &name)) {
199    NOTREACHED();
200    return false;
201  }
202
203  nigoris_[name] = make_linked_ptr(initialized_nigori.release());
204
205  // Check if the key we just added can decrypt the pending keys and add them
206  // too if so.
207  if (pending_keys_.get() && CanDecrypt(*pending_keys_)) {
208    sync_pb::NigoriKeyBag pending_bag;
209    Decrypt(*pending_keys_, &pending_bag);
210    InstallKeyBag(pending_bag);
211    SetDefaultKey(pending_keys_->key_name());
212    pending_keys_.reset();
213  }
214
215  // The just-added key takes priority over the pending keys as default.
216  if (set_as_default) SetDefaultKey(name);
217  return true;
218}
219
220void Cryptographer::InstallKeys(const sync_pb::EncryptedData& encrypted) {
221  DCHECK(CanDecrypt(encrypted));
222
223  sync_pb::NigoriKeyBag bag;
224  if (!Decrypt(encrypted, &bag))
225    return;
226  InstallKeyBag(bag);
227}
228
229void Cryptographer::SetDefaultKey(const std::string& key_name) {
230  DCHECK(nigoris_.end() != nigoris_.find(key_name));
231  default_nigori_name_ = key_name;
232}
233
234void Cryptographer::SetPendingKeys(const sync_pb::EncryptedData& encrypted) {
235  DCHECK(!CanDecrypt(encrypted));
236  DCHECK(!encrypted.blob().empty());
237  pending_keys_.reset(new sync_pb::EncryptedData(encrypted));
238}
239
240const sync_pb::EncryptedData& Cryptographer::GetPendingKeys() const {
241  DCHECK(has_pending_keys());
242  return *(pending_keys_.get());
243}
244
245bool Cryptographer::DecryptPendingKeys(const KeyParams& params) {
246  Nigori nigori;
247  if (!nigori.InitByDerivation(params.hostname,
248                               params.username,
249                               params.password)) {
250    NOTREACHED();
251    return false;
252  }
253
254  std::string plaintext;
255  if (!nigori.Decrypt(pending_keys_->blob(), &plaintext))
256    return false;
257
258  sync_pb::NigoriKeyBag bag;
259  if (!bag.ParseFromString(plaintext)) {
260    NOTREACHED();
261    return false;
262  }
263  InstallKeyBag(bag);
264  const std::string& new_default_key_name = pending_keys_->key_name();
265  SetDefaultKey(new_default_key_name);
266  pending_keys_.reset();
267  return true;
268}
269
270bool Cryptographer::GetBootstrapToken(std::string* token) const {
271  DCHECK(token);
272  std::string unencrypted_token = GetDefaultNigoriKeyData();
273  if (unencrypted_token.empty())
274    return false;
275
276  std::string encrypted_token;
277  if (!encryptor_->EncryptString(unencrypted_token, &encrypted_token)) {
278    NOTREACHED();
279    return false;
280  }
281
282  base::Base64Encode(encrypted_token, token);
283
284  return true;
285}
286
287std::string Cryptographer::UnpackBootstrapToken(
288    const std::string& token) const {
289  if (token.empty())
290    return std::string();
291
292  std::string encrypted_data;
293  if (!base::Base64Decode(token, &encrypted_data)) {
294    DLOG(WARNING) << "Could not decode token.";
295    return std::string();
296  }
297
298  std::string unencrypted_token;
299  if (!encryptor_->DecryptString(encrypted_data, &unencrypted_token)) {
300    DLOG(WARNING) << "Decryption of bootstrap token failed.";
301    return std::string();
302  }
303  return unencrypted_token;
304}
305
306void Cryptographer::InstallKeyBag(const sync_pb::NigoriKeyBag& bag) {
307  int key_size = bag.key_size();
308  for (int i = 0; i < key_size; ++i) {
309    const sync_pb::NigoriKey key = bag.key(i);
310    // Only use this key if we don't already know about it.
311    if (nigoris_.end() == nigoris_.find(key.name())) {
312      scoped_ptr<Nigori> new_nigori(new Nigori);
313      if (!new_nigori->InitByImport(key.user_key(),
314                                    key.encryption_key(),
315                                    key.mac_key())) {
316        NOTREACHED();
317        continue;
318      }
319      nigoris_[key.name()] = make_linked_ptr(new_nigori.release());
320    }
321  }
322}
323
324bool Cryptographer::KeybagIsStale(
325    const sync_pb::EncryptedData& encrypted_bag) const {
326  if (!is_ready())
327    return false;
328  if (encrypted_bag.blob().empty())
329    return true;
330  if (!CanDecrypt(encrypted_bag))
331    return false;
332  if (!CanDecryptUsingDefaultKey(encrypted_bag))
333    return true;
334  sync_pb::NigoriKeyBag bag;
335  if (!Decrypt(encrypted_bag, &bag)) {
336    LOG(ERROR) << "Failed to decrypt keybag for stale check. "
337               << "Assuming keybag is corrupted.";
338    return true;
339  }
340  if (static_cast<size_t>(bag.key_size()) < nigoris_.size())
341    return true;
342  return false;
343}
344
345std::string Cryptographer::GetDefaultNigoriKeyName() const {
346  return default_nigori_name_;
347}
348
349std::string Cryptographer::GetDefaultNigoriKeyData() const {
350  if (!is_initialized())
351    return std::string();
352  NigoriMap::const_iterator iter = nigoris_.find(default_nigori_name_);
353  if (iter == nigoris_.end())
354    return std::string();
355  sync_pb::NigoriKey key;
356  if (!iter->second->ExportKeys(key.mutable_user_key(),
357                                key.mutable_encryption_key(),
358                                key.mutable_mac_key()))
359    return std::string();
360  return key.SerializeAsString();
361}
362
363bool Cryptographer::ImportNigoriKey(const std::string serialized_nigori_key) {
364  if (serialized_nigori_key.empty())
365    return false;
366
367  sync_pb::NigoriKey key;
368  if (!key.ParseFromString(serialized_nigori_key))
369    return false;
370
371  scoped_ptr<Nigori> nigori(new Nigori);
372  if (!nigori->InitByImport(key.user_key(), key.encryption_key(),
373                            key.mac_key())) {
374    NOTREACHED();
375    return false;
376  }
377
378  if (!AddKeyImpl(nigori.Pass(), true))
379    return false;
380  return true;
381}
382
383}  // namespace syncer
384