OLD | NEW |
1 // Copyright 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "sync/syncable/nigori_util.h" | 5 #include "components/sync/syncable/nigori_util.h" |
6 | 6 |
7 #include <stddef.h> | 7 #include <stddef.h> |
8 #include <stdint.h> | 8 #include <stdint.h> |
9 | 9 |
10 #include <queue> | 10 #include <queue> |
11 #include <string> | 11 #include <string> |
12 #include <vector> | 12 #include <vector> |
13 | 13 |
14 #include "base/json/json_writer.h" | 14 #include "base/json/json_writer.h" |
15 #include "sync/syncable/directory.h" | 15 #include "components/sync/base/cryptographer.h" |
16 #include "sync/syncable/entry.h" | 16 #include "components/sync/syncable/directory.h" |
17 #include "sync/syncable/mutable_entry.h" | 17 #include "components/sync/syncable/entry.h" |
18 #include "sync/syncable/nigori_handler.h" | 18 #include "components/sync/syncable/mutable_entry.h" |
19 #include "sync/syncable/syncable_util.h" | 19 #include "components/sync/syncable/nigori_handler.h" |
20 #include "sync/syncable/syncable_write_transaction.h" | 20 #include "components/sync/syncable/syncable_util.h" |
21 #include "sync/util/cryptographer.h" | 21 #include "components/sync/syncable/syncable_write_transaction.h" |
22 | 22 |
23 namespace syncer { | 23 namespace syncer { |
24 namespace syncable { | 24 namespace syncable { |
25 | 25 |
26 bool ProcessUnsyncedChangesForEncryption( | 26 bool ProcessUnsyncedChangesForEncryption(WriteTransaction* const trans) { |
27 WriteTransaction* const trans) { | |
28 NigoriHandler* nigori_handler = trans->directory()->GetNigoriHandler(); | 27 NigoriHandler* nigori_handler = trans->directory()->GetNigoriHandler(); |
29 ModelTypeSet encrypted_types = nigori_handler->GetEncryptedTypes(trans); | 28 ModelTypeSet encrypted_types = nigori_handler->GetEncryptedTypes(trans); |
30 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); | 29 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); |
31 DCHECK(cryptographer->is_ready()); | 30 DCHECK(cryptographer->is_ready()); |
32 | 31 |
33 // Get list of all datatypes with unsynced changes. It's possible that our | 32 // Get list of all datatypes with unsynced changes. It's possible that our |
34 // local changes need to be encrypted if encryption for that datatype was | 33 // local changes need to be encrypted if encryption for that datatype was |
35 // just turned on (and vice versa). | 34 // just turned on (and vice versa). |
36 // Note: we do not attempt to re-encrypt data with a new key here as key | 35 // Note: we do not attempt to re-encrypt data with a new key here as key |
37 // changes in this code path are likely due to consistency issues (we have | 36 // changes in this code path are likely due to consistency issues (we have |
38 // to be updated to a key we already have, e.g. an old key). | 37 // to be updated to a key we already have, e.g. an old key). |
39 std::vector<int64_t> handles; | 38 std::vector<int64_t> handles; |
40 GetUnsyncedEntries(trans, &handles); | 39 GetUnsyncedEntries(trans, &handles); |
41 for (size_t i = 0; i < handles.size(); ++i) { | 40 for (size_t i = 0; i < handles.size(); ++i) { |
42 MutableEntry entry(trans, GET_BY_HANDLE, handles[i]); | 41 MutableEntry entry(trans, GET_BY_HANDLE, handles[i]); |
43 const sync_pb::EntitySpecifics& specifics = entry.GetSpecifics(); | 42 const sync_pb::EntitySpecifics& specifics = entry.GetSpecifics(); |
44 // Ignore types that don't need encryption or entries that are already | 43 // Ignore types that don't need encryption or entries that are already |
45 // encrypted. | 44 // encrypted. |
46 if (!SpecificsNeedsEncryption(encrypted_types, specifics)) | 45 if (!SpecificsNeedsEncryption(encrypted_types, specifics)) |
47 continue; | 46 continue; |
48 if (!UpdateEntryWithEncryption(trans, specifics, &entry)) | 47 if (!UpdateEntryWithEncryption(trans, specifics, &entry)) |
49 return false; | 48 return false; |
50 } | 49 } |
51 return true; | 50 return true; |
52 } | 51 } |
53 | 52 |
54 bool VerifyUnsyncedChangesAreEncrypted( | 53 bool VerifyUnsyncedChangesAreEncrypted(BaseTransaction* const trans, |
55 BaseTransaction* const trans, | 54 ModelTypeSet encrypted_types) { |
56 ModelTypeSet encrypted_types) { | |
57 std::vector<int64_t> handles; | 55 std::vector<int64_t> handles; |
58 GetUnsyncedEntries(trans, &handles); | 56 GetUnsyncedEntries(trans, &handles); |
59 for (size_t i = 0; i < handles.size(); ++i) { | 57 for (size_t i = 0; i < handles.size(); ++i) { |
60 Entry entry(trans, GET_BY_HANDLE, handles[i]); | 58 Entry entry(trans, GET_BY_HANDLE, handles[i]); |
61 if (!entry.good()) { | 59 if (!entry.good()) { |
62 NOTREACHED(); | 60 NOTREACHED(); |
63 return false; | 61 return false; |
64 } | 62 } |
65 if (EntryNeedsEncryption(encrypted_types, entry)) | 63 if (EntryNeedsEncryption(encrypted_types, entry)) |
66 return false; | 64 return false; |
67 } | 65 } |
68 return true; | 66 return true; |
69 } | 67 } |
70 | 68 |
71 bool EntryNeedsEncryption(ModelTypeSet encrypted_types, | 69 bool EntryNeedsEncryption(ModelTypeSet encrypted_types, const Entry& entry) { |
72 const Entry& entry) { | |
73 if (!entry.GetUniqueServerTag().empty()) | 70 if (!entry.GetUniqueServerTag().empty()) |
74 return false; // We don't encrypt unique server nodes. | 71 return false; // We don't encrypt unique server nodes. |
75 ModelType type = entry.GetModelType(); | 72 ModelType type = entry.GetModelType(); |
76 if (type == PASSWORDS || IsControlType(type)) | 73 if (type == PASSWORDS || IsControlType(type)) |
77 return false; | 74 return false; |
78 // Checking NON_UNIQUE_NAME is not necessary for the correctness of encrypting | 75 // Checking NON_UNIQUE_NAME is not necessary for the correctness of encrypting |
79 // the data, nor for determining if data is encrypted. We simply ensure it has | 76 // the data, nor for determining if data is encrypted. We simply ensure it has |
80 // been overwritten to avoid any possible leaks of sensitive data. | 77 // been overwritten to avoid any possible leaks of sensitive data. |
81 return SpecificsNeedsEncryption(encrypted_types, entry.GetSpecifics()) || | 78 return SpecificsNeedsEncryption(encrypted_types, entry.GetSpecifics()) || |
82 (encrypted_types.Has(type) && | 79 (encrypted_types.Has(type) && |
83 entry.GetNonUniqueName() != kEncryptedString); | 80 entry.GetNonUniqueName() != kEncryptedString); |
84 } | 81 } |
85 | 82 |
86 bool SpecificsNeedsEncryption(ModelTypeSet encrypted_types, | 83 bool SpecificsNeedsEncryption(ModelTypeSet encrypted_types, |
87 const sync_pb::EntitySpecifics& specifics) { | 84 const sync_pb::EntitySpecifics& specifics) { |
88 const ModelType type = GetModelTypeFromSpecifics(specifics); | 85 const ModelType type = GetModelTypeFromSpecifics(specifics); |
89 if (type == PASSWORDS || IsControlType(type)) | 86 if (type == PASSWORDS || IsControlType(type)) |
90 return false; // These types have their own encryption schemes. | 87 return false; // These types have their own encryption schemes. |
91 if (!encrypted_types.Has(type)) | 88 if (!encrypted_types.Has(type)) |
92 return false; // This type does not require encryption | 89 return false; // This type does not require encryption |
93 return !specifics.has_encrypted(); | 90 return !specifics.has_encrypted(); |
94 } | 91 } |
95 | 92 |
96 // Mainly for testing. | 93 // Mainly for testing. |
97 bool VerifyDataTypeEncryptionForTest( | 94 bool VerifyDataTypeEncryptionForTest(BaseTransaction* const trans, |
98 BaseTransaction* const trans, | 95 ModelType type, |
99 ModelType type, | 96 bool is_encrypted) { |
100 bool is_encrypted) { | |
101 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); | 97 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); |
102 if (type == PASSWORDS || IsControlType(type)) { | 98 if (type == PASSWORDS || IsControlType(type)) { |
103 NOTREACHED(); | 99 NOTREACHED(); |
104 return true; | 100 return true; |
105 } | 101 } |
106 Entry type_root(trans, GET_TYPE_ROOT, type); | 102 Entry type_root(trans, GET_TYPE_ROOT, type); |
107 if (!type_root.good()) { | 103 if (!type_root.good()) { |
108 NOTREACHED(); | 104 NOTREACHED(); |
109 return false; | 105 return false; |
110 } | 106 } |
(...skipping 30 matching lines...) Expand all Loading... |
141 if (!cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())) | 137 if (!cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())) |
142 return false; | 138 return false; |
143 } | 139 } |
144 } | 140 } |
145 // Push the successor. | 141 // Push the successor. |
146 to_visit.push(child.GetSuccessorId()); | 142 to_visit.push(child.GetSuccessorId()); |
147 } | 143 } |
148 return true; | 144 return true; |
149 } | 145 } |
150 | 146 |
151 bool UpdateEntryWithEncryption( | 147 bool UpdateEntryWithEncryption(BaseTransaction* const trans, |
152 BaseTransaction* const trans, | 148 const sync_pb::EntitySpecifics& new_specifics, |
153 const sync_pb::EntitySpecifics& new_specifics, | 149 syncable::MutableEntry* entry) { |
154 syncable::MutableEntry* entry) { | |
155 NigoriHandler* nigori_handler = trans->directory()->GetNigoriHandler(); | 150 NigoriHandler* nigori_handler = trans->directory()->GetNigoriHandler(); |
156 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); | 151 Cryptographer* cryptographer = trans->directory()->GetCryptographer(trans); |
157 ModelType type = GetModelTypeFromSpecifics(new_specifics); | 152 ModelType type = GetModelTypeFromSpecifics(new_specifics); |
158 DCHECK_GE(type, FIRST_REAL_MODEL_TYPE); | 153 DCHECK_GE(type, FIRST_REAL_MODEL_TYPE); |
159 const sync_pb::EntitySpecifics& old_specifics = entry->GetSpecifics(); | 154 const sync_pb::EntitySpecifics& old_specifics = entry->GetSpecifics(); |
160 const ModelTypeSet encrypted_types = | 155 const ModelTypeSet encrypted_types = |
161 nigori_handler? | 156 nigori_handler ? nigori_handler->GetEncryptedTypes(trans) |
162 nigori_handler->GetEncryptedTypes(trans) : ModelTypeSet(); | 157 : ModelTypeSet(); |
163 // It's possible the nigori lost the set of encrypted types. If the current | 158 // It's possible the nigori lost the set of encrypted types. If the current |
164 // specifics are already encrypted, we want to ensure we continue encrypting. | 159 // specifics are already encrypted, we want to ensure we continue encrypting. |
165 bool was_encrypted = old_specifics.has_encrypted(); | 160 bool was_encrypted = old_specifics.has_encrypted(); |
166 sync_pb::EntitySpecifics generated_specifics; | 161 sync_pb::EntitySpecifics generated_specifics; |
167 if (new_specifics.has_encrypted()) { | 162 if (new_specifics.has_encrypted()) { |
168 NOTREACHED() << "New specifics already has an encrypted blob."; | 163 NOTREACHED() << "New specifics already has an encrypted blob."; |
169 return false; | 164 return false; |
170 } | 165 } |
171 if ((!SpecificsNeedsEncryption(encrypted_types, new_specifics) && | 166 if ((!SpecificsNeedsEncryption(encrypted_types, new_specifics) && |
172 !was_encrypted) || | 167 !was_encrypted) || |
173 !cryptographer || !cryptographer->is_initialized()) { | 168 !cryptographer || !cryptographer->is_initialized()) { |
174 // No encryption required or we are unable to encrypt. | 169 // No encryption required or we are unable to encrypt. |
175 generated_specifics.CopyFrom(new_specifics); | 170 generated_specifics.CopyFrom(new_specifics); |
176 } else { | 171 } else { |
177 // Encrypt new_specifics into generated_specifics. | 172 // Encrypt new_specifics into generated_specifics. |
178 if (VLOG_IS_ON(2)) { | 173 if (VLOG_IS_ON(2)) { |
179 std::unique_ptr<base::DictionaryValue> value(entry->ToValue(NULL)); | 174 std::unique_ptr<base::DictionaryValue> value(entry->ToValue(NULL)); |
180 std::string info; | 175 std::string info; |
181 base::JSONWriter::WriteWithOptions( | 176 base::JSONWriter::WriteWithOptions( |
182 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &info); | 177 *value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &info); |
183 DVLOG(2) << "Encrypting specifics of type " | 178 DVLOG(2) << "Encrypting specifics of type " << ModelTypeToString(type) |
184 << ModelTypeToString(type) | 179 << " with content: " << info; |
185 << " with content: " | |
186 << info; | |
187 } | 180 } |
188 // Only copy over the old specifics if it is of the right type and already | 181 // Only copy over the old specifics if it is of the right type and already |
189 // encrypted. The first time we encrypt a node we start from scratch, hence | 182 // encrypted. The first time we encrypt a node we start from scratch, hence |
190 // removing all the unencrypted data, but from then on we only want to | 183 // removing all the unencrypted data, but from then on we only want to |
191 // update the node if the data changes or the encryption key changes. | 184 // update the node if the data changes or the encryption key changes. |
192 if (GetModelTypeFromSpecifics(old_specifics) == type && | 185 if (GetModelTypeFromSpecifics(old_specifics) == type && was_encrypted) { |
193 was_encrypted) { | |
194 generated_specifics.CopyFrom(old_specifics); | 186 generated_specifics.CopyFrom(old_specifics); |
195 } else { | 187 } else { |
196 AddDefaultFieldValue(type, &generated_specifics); | 188 AddDefaultFieldValue(type, &generated_specifics); |
197 } | 189 } |
198 // Does not change anything if underlying encrypted blob was already up | 190 // Does not change anything if underlying encrypted blob was already up |
199 // to date and encrypted with the default key. | 191 // to date and encrypted with the default key. |
200 if (!cryptographer->Encrypt(new_specifics, | 192 if (!cryptographer->Encrypt(new_specifics, |
201 generated_specifics.mutable_encrypted())) { | 193 generated_specifics.mutable_encrypted())) { |
202 NOTREACHED() << "Could not encrypt data for node of type " | 194 NOTREACHED() << "Could not encrypt data for node of type " |
203 << ModelTypeToString(type); | 195 << ModelTypeToString(type); |
204 return false; | 196 return false; |
205 } | 197 } |
206 } | 198 } |
207 | 199 |
208 // It's possible this entry was encrypted but didn't properly overwrite the | 200 // It's possible this entry was encrypted but didn't properly overwrite the |
209 // non_unique_name (see crbug.com/96314). | 201 // non_unique_name (see crbug.com/96314). |
210 bool encrypted_without_overwriting_name = (was_encrypted && | 202 bool encrypted_without_overwriting_name = |
211 entry->GetNonUniqueName() != kEncryptedString); | 203 (was_encrypted && entry->GetNonUniqueName() != kEncryptedString); |
212 | 204 |
213 // If we're encrypted but the name wasn't overwritten properly we still want | 205 // If we're encrypted but the name wasn't overwritten properly we still want |
214 // to rewrite the entry, irrespective of whether the specifics match. | 206 // to rewrite the entry, irrespective of whether the specifics match. |
215 if (!encrypted_without_overwriting_name && | 207 if (!encrypted_without_overwriting_name && |
216 old_specifics.SerializeAsString() == | 208 old_specifics.SerializeAsString() == |
217 generated_specifics.SerializeAsString()) { | 209 generated_specifics.SerializeAsString()) { |
218 DVLOG(2) << "Specifics of type " << ModelTypeToString(type) | 210 DVLOG(2) << "Specifics of type " << ModelTypeToString(type) |
219 << " already match, dropping change."; | 211 << " already match, dropping change."; |
220 return true; | 212 return true; |
221 } | 213 } |
222 | 214 |
223 if (generated_specifics.has_encrypted()) { | 215 if (generated_specifics.has_encrypted()) { |
224 // Overwrite the possibly sensitive non-specifics data. | 216 // Overwrite the possibly sensitive non-specifics data. |
225 entry->PutNonUniqueName(kEncryptedString); | 217 entry->PutNonUniqueName(kEncryptedString); |
226 // For bookmarks we actually put bogus data into the unencrypted specifics, | 218 // For bookmarks we actually put bogus data into the unencrypted specifics, |
227 // else the server will try to do it for us. | 219 // else the server will try to do it for us. |
228 if (type == BOOKMARKS) { | 220 if (type == BOOKMARKS) { |
229 sync_pb::BookmarkSpecifics* bookmark_specifics = | 221 sync_pb::BookmarkSpecifics* bookmark_specifics = |
230 generated_specifics.mutable_bookmark(); | 222 generated_specifics.mutable_bookmark(); |
231 if (!entry->GetIsDir()) | 223 if (!entry->GetIsDir()) |
232 bookmark_specifics->set_url(kEncryptedString); | 224 bookmark_specifics->set_url(kEncryptedString); |
233 bookmark_specifics->set_title(kEncryptedString); | 225 bookmark_specifics->set_title(kEncryptedString); |
234 } | 226 } |
235 } | 227 } |
236 entry->PutSpecifics(generated_specifics); | 228 entry->PutSpecifics(generated_specifics); |
237 DVLOG(1) << "Overwriting specifics of type " | 229 DVLOG(1) << "Overwriting specifics of type " << ModelTypeToString(type) |
238 << ModelTypeToString(type) | |
239 << " and marking for syncing."; | 230 << " and marking for syncing."; |
240 syncable::MarkForSyncing(entry); | 231 syncable::MarkForSyncing(entry); |
241 return true; | 232 return true; |
242 } | 233 } |
243 | 234 |
244 void UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types, | 235 void UpdateNigoriFromEncryptedTypes(ModelTypeSet encrypted_types, |
245 bool encrypt_everything, | 236 bool encrypt_everything, |
246 sync_pb::NigoriSpecifics* nigori) { | 237 sync_pb::NigoriSpecifics* nigori) { |
247 nigori->set_encrypt_everything(encrypt_everything); | 238 nigori->set_encrypt_everything(encrypt_everything); |
248 static_assert(37 == MODEL_TYPE_COUNT, "update encrypted types"); | 239 static_assert(37 == MODEL_TYPE_COUNT, "update encrypted types"); |
249 nigori->set_encrypt_bookmarks( | 240 nigori->set_encrypt_bookmarks(encrypted_types.Has(BOOKMARKS)); |
250 encrypted_types.Has(BOOKMARKS)); | 241 nigori->set_encrypt_preferences(encrypted_types.Has(PREFERENCES)); |
251 nigori->set_encrypt_preferences( | 242 nigori->set_encrypt_autofill_profile(encrypted_types.Has(AUTOFILL_PROFILE)); |
252 encrypted_types.Has(PREFERENCES)); | |
253 nigori->set_encrypt_autofill_profile( | |
254 encrypted_types.Has(AUTOFILL_PROFILE)); | |
255 nigori->set_encrypt_autofill(encrypted_types.Has(AUTOFILL)); | 243 nigori->set_encrypt_autofill(encrypted_types.Has(AUTOFILL)); |
256 nigori->set_encrypt_autofill_wallet_metadata( | 244 nigori->set_encrypt_autofill_wallet_metadata( |
257 encrypted_types.Has(AUTOFILL_WALLET_METADATA)); | 245 encrypted_types.Has(AUTOFILL_WALLET_METADATA)); |
258 nigori->set_encrypt_themes(encrypted_types.Has(THEMES)); | 246 nigori->set_encrypt_themes(encrypted_types.Has(THEMES)); |
259 nigori->set_encrypt_typed_urls( | 247 nigori->set_encrypt_typed_urls(encrypted_types.Has(TYPED_URLS)); |
260 encrypted_types.Has(TYPED_URLS)); | |
261 nigori->set_encrypt_extension_settings( | 248 nigori->set_encrypt_extension_settings( |
262 encrypted_types.Has(EXTENSION_SETTINGS)); | 249 encrypted_types.Has(EXTENSION_SETTINGS)); |
263 nigori->set_encrypt_extensions( | 250 nigori->set_encrypt_extensions(encrypted_types.Has(EXTENSIONS)); |
264 encrypted_types.Has(EXTENSIONS)); | 251 nigori->set_encrypt_search_engines(encrypted_types.Has(SEARCH_ENGINES)); |
265 nigori->set_encrypt_search_engines( | |
266 encrypted_types.Has(SEARCH_ENGINES)); | |
267 nigori->set_encrypt_sessions(encrypted_types.Has(SESSIONS)); | 252 nigori->set_encrypt_sessions(encrypted_types.Has(SESSIONS)); |
268 nigori->set_encrypt_app_settings( | 253 nigori->set_encrypt_app_settings(encrypted_types.Has(APP_SETTINGS)); |
269 encrypted_types.Has(APP_SETTINGS)); | |
270 nigori->set_encrypt_apps(encrypted_types.Has(APPS)); | 254 nigori->set_encrypt_apps(encrypted_types.Has(APPS)); |
271 nigori->set_encrypt_app_notifications( | 255 nigori->set_encrypt_app_notifications(encrypted_types.Has(APP_NOTIFICATIONS)); |
272 encrypted_types.Has(APP_NOTIFICATIONS)); | |
273 nigori->set_encrypt_dictionary(encrypted_types.Has(DICTIONARY)); | 256 nigori->set_encrypt_dictionary(encrypted_types.Has(DICTIONARY)); |
274 nigori->set_encrypt_favicon_images(encrypted_types.Has(FAVICON_IMAGES)); | 257 nigori->set_encrypt_favicon_images(encrypted_types.Has(FAVICON_IMAGES)); |
275 nigori->set_encrypt_favicon_tracking(encrypted_types.Has(FAVICON_TRACKING)); | 258 nigori->set_encrypt_favicon_tracking(encrypted_types.Has(FAVICON_TRACKING)); |
276 nigori->set_encrypt_articles(encrypted_types.Has(ARTICLES)); | 259 nigori->set_encrypt_articles(encrypted_types.Has(ARTICLES)); |
277 nigori->set_encrypt_app_list(encrypted_types.Has(APP_LIST)); | 260 nigori->set_encrypt_app_list(encrypted_types.Has(APP_LIST)); |
278 nigori->set_encrypt_arc_package(encrypted_types.Has(ARC_PACKAGE)); | 261 nigori->set_encrypt_arc_package(encrypted_types.Has(ARC_PACKAGE)); |
279 } | 262 } |
280 | 263 |
281 ModelTypeSet GetEncryptedTypesFromNigori( | 264 ModelTypeSet GetEncryptedTypesFromNigori( |
282 const sync_pb::NigoriSpecifics& nigori) { | 265 const sync_pb::NigoriSpecifics& nigori) { |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
323 encrypted_types.Put(ARTICLES); | 306 encrypted_types.Put(ARTICLES); |
324 if (nigori.encrypt_app_list()) | 307 if (nigori.encrypt_app_list()) |
325 encrypted_types.Put(APP_LIST); | 308 encrypted_types.Put(APP_LIST); |
326 if (nigori.encrypt_arc_package()) | 309 if (nigori.encrypt_arc_package()) |
327 encrypted_types.Put(ARC_PACKAGE); | 310 encrypted_types.Put(ARC_PACKAGE); |
328 return encrypted_types; | 311 return encrypted_types; |
329 } | 312 } |
330 | 313 |
331 } // namespace syncable | 314 } // namespace syncable |
332 } // namespace syncer | 315 } // namespace syncer |
OLD | NEW |