1// Copyright (c) 2011 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 "base/base64.h"
6#include "chrome/browser/sync/util/cryptographer.h"
7#include "chrome/browser/password_manager/encryptor.h"
8
9namespace browser_sync {
10
11const char kNigoriTag[] = "google_chrome_nigori";
12
13// We name a particular Nigori instance (ie. a triplet consisting of a hostname,
14// a username, and a password) by calling Permute on this string. Since the
15// output of Permute is always the same for a given triplet, clients will always
16// assign the same name to a particular triplet.
17const char kNigoriKeyName[] = "nigori-key";
18
19Cryptographer::Cryptographer() : default_nigori_(NULL) {
20}
21
22Cryptographer::~Cryptographer() {}
23
24void Cryptographer::Bootstrap(const std::string& restored_bootstrap_token) {
25  if (is_ready()) {
26    NOTREACHED();
27    return;
28  }
29
30  scoped_ptr<Nigori> nigori(UnpackBootstrapToken(restored_bootstrap_token));
31  if (nigori.get())
32    AddKeyImpl(nigori.release());
33}
34
35bool Cryptographer::CanDecrypt(const sync_pb::EncryptedData& data) const {
36  return nigoris_.end() != nigoris_.find(data.key_name());
37}
38
39bool Cryptographer::CanDecryptUsingDefaultKey(
40    const sync_pb::EncryptedData& data) const {
41  return default_nigori_ && (data.key_name() == default_nigori_->first);
42}
43
44bool Cryptographer::Encrypt(const ::google::protobuf::MessageLite& message,
45                            sync_pb::EncryptedData* encrypted) const {
46  DCHECK(encrypted);
47  DCHECK(default_nigori_);
48
49  std::string serialized;
50  if (!message.SerializeToString(&serialized)) {
51    NOTREACHED();  // |message| is invalid/missing a required field.
52    return false;
53  }
54
55  encrypted->set_key_name(default_nigori_->first);
56  if (!default_nigori_->second->Encrypt(serialized,
57                                        encrypted->mutable_blob())) {
58    NOTREACHED();  // Encrypt should not fail.
59    return false;
60  }
61  return true;
62}
63
64bool Cryptographer::Decrypt(const sync_pb::EncryptedData& encrypted,
65                            ::google::protobuf::MessageLite* message) const {
66  DCHECK(message);
67  std::string plaintext = DecryptToString(encrypted);
68  return message->ParseFromString(plaintext);
69}
70
71std::string Cryptographer::DecryptToString(
72    const sync_pb::EncryptedData& encrypted) const {
73  NigoriMap::const_iterator it = nigoris_.find(encrypted.key_name());
74  if (nigoris_.end() == it) {
75    NOTREACHED() << "Cannot decrypt message";
76    return std::string("");  // Caller should have called CanDecrypt(encrypt).
77  }
78
79  std::string plaintext;
80  if (!it->second->Decrypt(encrypted.blob(), &plaintext)) {
81    return std::string("");
82  }
83
84  return plaintext;
85}
86
87bool Cryptographer::GetKeys(sync_pb::EncryptedData* encrypted) const {
88  DCHECK(encrypted);
89  DCHECK(!nigoris_.empty());
90
91  // Create a bag of all the Nigori parameters we know about.
92  sync_pb::NigoriKeyBag bag;
93  for (NigoriMap::const_iterator it = nigoris_.begin(); it != nigoris_.end();
94       ++it) {
95    const Nigori& nigori = *it->second;
96    sync_pb::NigoriKey* key = bag.add_key();
97    key->set_name(it->first);
98    nigori.ExportKeys(key->mutable_user_key(),
99                      key->mutable_encryption_key(),
100                      key->mutable_mac_key());
101  }
102
103  // Encrypt the bag with the default Nigori.
104  return Encrypt(bag, encrypted);
105}
106
107bool Cryptographer::AddKey(const KeyParams& params) {
108  DCHECK(NULL == pending_keys_.get());
109
110  // Create the new Nigori and make it the default encryptor.
111  scoped_ptr<Nigori> nigori(new Nigori);
112  if (!nigori->InitByDerivation(params.hostname,
113                                params.username,
114                                params.password)) {
115    NOTREACHED();  // Invalid username or password.
116    return false;
117  }
118  return AddKeyImpl(nigori.release());
119}
120
121bool Cryptographer::AddKeyImpl(Nigori* initialized_nigori) {
122  scoped_ptr<Nigori> nigori(initialized_nigori);
123  std::string name;
124  if (!nigori->Permute(Nigori::Password, kNigoriKeyName, &name)) {
125    NOTREACHED();
126    return false;
127  }
128  nigoris_[name] = make_linked_ptr(nigori.release());
129  default_nigori_ = &*nigoris_.find(name);
130  return true;
131}
132
133bool Cryptographer::SetKeys(const sync_pb::EncryptedData& encrypted) {
134  DCHECK(CanDecrypt(encrypted));
135
136  sync_pb::NigoriKeyBag bag;
137  if (!Decrypt(encrypted, &bag)) {
138    return false;
139  }
140  InstallKeys(encrypted.key_name(), bag);
141  return true;
142}
143
144void Cryptographer::SetPendingKeys(const sync_pb::EncryptedData& encrypted) {
145  DCHECK(!CanDecrypt(encrypted));
146  pending_keys_.reset(new sync_pb::EncryptedData(encrypted));
147}
148
149bool Cryptographer::DecryptPendingKeys(const KeyParams& params) {
150  Nigori nigori;
151  if (!nigori.InitByDerivation(params.hostname,
152                               params.username,
153                               params.password)) {
154    NOTREACHED();
155    return false;
156  }
157
158  std::string plaintext;
159  if (!nigori.Decrypt(pending_keys_->blob(), &plaintext))
160    return false;
161
162  sync_pb::NigoriKeyBag bag;
163  if (!bag.ParseFromString(plaintext)) {
164    NOTREACHED();
165    return false;
166  }
167  InstallKeys(pending_keys_->key_name(), bag);
168  pending_keys_.reset();
169  return true;
170}
171
172bool Cryptographer::GetBootstrapToken(std::string* token) const {
173  DCHECK(token);
174  if (!is_ready())
175    return false;
176
177  return PackBootstrapToken(default_nigori_->second.get(), token);
178}
179
180bool Cryptographer::PackBootstrapToken(const Nigori* nigori,
181                                       std::string* pack_into) const {
182  DCHECK(pack_into);
183  DCHECK(nigori);
184
185  sync_pb::NigoriKey key;
186  if (!nigori->ExportKeys(key.mutable_user_key(),
187                          key.mutable_encryption_key(),
188                          key.mutable_mac_key())) {
189    NOTREACHED();
190    return false;
191  }
192
193  std::string unencrypted_token;
194  if (!key.SerializeToString(&unencrypted_token)) {
195    NOTREACHED();
196    return false;
197  }
198
199  std::string encrypted_token;
200  if (!Encryptor::EncryptString(unencrypted_token, &encrypted_token)) {
201    NOTREACHED();
202    return false;
203  }
204
205  if (!base::Base64Encode(encrypted_token, pack_into)) {
206    NOTREACHED();
207    return false;
208  }
209  return true;
210}
211
212Nigori* Cryptographer::UnpackBootstrapToken(const std::string& token) const {
213  if (token.empty())
214    return NULL;
215
216  std::string encrypted_data;
217  if (!base::Base64Decode(token, &encrypted_data)) {
218    DLOG(WARNING) << "Could not decode token.";
219    return NULL;
220  }
221
222  std::string unencrypted_token;
223  if (!Encryptor::DecryptString(encrypted_data, &unencrypted_token)) {
224    DLOG(WARNING) << "Decryption of bootstrap token failed.";
225    return NULL;
226  }
227
228  sync_pb::NigoriKey key;
229  if (!key.ParseFromString(unencrypted_token)) {
230    DLOG(WARNING) << "Parsing of bootstrap token failed.";
231    return NULL;
232  }
233
234  scoped_ptr<Nigori> nigori(new Nigori);
235  if (!nigori->InitByImport(key.user_key(), key.encryption_key(),
236                            key.mac_key())) {
237    NOTREACHED();
238    return NULL;
239  }
240
241  return nigori.release();
242}
243
244void Cryptographer::InstallKeys(const std::string& default_key_name,
245                                const sync_pb::NigoriKeyBag& bag) {
246  int key_size = bag.key_size();
247  for (int i = 0; i < key_size; ++i) {
248    const sync_pb::NigoriKey key = bag.key(i);
249    // Only use this key if we don't already know about it.
250    if (nigoris_.end() == nigoris_.find(key.name())) {
251      scoped_ptr<Nigori> new_nigori(new Nigori);
252      if (!new_nigori->InitByImport(key.user_key(),
253                                    key.encryption_key(),
254                                    key.mac_key())) {
255        NOTREACHED();
256        continue;
257      }
258      nigoris_[key.name()] = make_linked_ptr(new_nigori.release());
259    }
260  }
261  DCHECK(nigoris_.end() != nigoris_.find(default_key_name));
262  default_nigori_ = &*nigoris_.find(default_key_name);
263}
264
265}  // namespace browser_sync
266