1 // Copyright 2014 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 "components/signin/public/webdata/token_service_table.h"
6 
7 #include <map>
8 #include <string>
9 
10 #include "base/logging.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "components/os_crypt/os_crypt.h"
13 #include "components/webdata/common/web_database.h"
14 #include "sql/statement.h"
15 
16 namespace {
17 
GetKey()18 WebDatabaseTable::TypeKey GetKey() {
19   // We just need a unique constant. Use the address of a static that
20   // COMDAT folding won't touch in an optimizing linker.
21   static int table_key = 0;
22   return reinterpret_cast<void*>(&table_key);
23 }
24 
25 // Entries in the |Signin.TokenTable.ReadTokenFromDBResult| histogram.
26 enum ReadOneTokenResult {
27   READ_ONE_TOKEN_SUCCESS,
28   READ_ONE_TOKEN_DB_SUCCESS_DECRYPT_FAILED,
29   READ_ONE_TOKEN_DB_FAILED_BAD_ENTRY,
30   READ_ONE_TOKEN_MAX_VALUE
31 };
32 
33 }  // namespace
34 
FromWebDatabase(WebDatabase * db)35 TokenServiceTable* TokenServiceTable::FromWebDatabase(WebDatabase* db) {
36   return static_cast<TokenServiceTable*>(db->GetTable(GetKey()));
37 }
38 
GetTypeKey() const39 WebDatabaseTable::TypeKey TokenServiceTable::GetTypeKey() const {
40   return GetKey();
41 }
42 
CreateTablesIfNecessary()43 bool TokenServiceTable::CreateTablesIfNecessary() {
44   if (!db_->DoesTableExist("token_service")) {
45     if (!db_->Execute("CREATE TABLE token_service ("
46                       "service VARCHAR PRIMARY KEY NOT NULL,"
47                       "encrypted_token BLOB)")) {
48       NOTREACHED();
49       LOG(ERROR) << "Failed creating token_service table";
50       return false;
51     }
52   }
53   return true;
54 }
55 
IsSyncable()56 bool TokenServiceTable::IsSyncable() {
57   return true;
58 }
59 
MigrateToVersion(int version,bool * update_compatible_version)60 bool TokenServiceTable::MigrateToVersion(int version,
61                                          bool* update_compatible_version) {
62   return true;
63 }
64 
RemoveAllTokens()65 bool TokenServiceTable::RemoveAllTokens() {
66   VLOG(1) << "Remove all tokens";
67   sql::Statement s(db_->GetUniqueStatement("DELETE FROM token_service"));
68 
69   bool result = s.Run();
70   LOG_IF(ERROR, !result) << "Failed to remove all tokens";
71   return result;
72 }
73 
RemoveTokenForService(const std::string & service)74 bool TokenServiceTable::RemoveTokenForService(const std::string& service) {
75   sql::Statement s(
76       db_->GetUniqueStatement("DELETE FROM token_service WHERE service = ?"));
77   s.BindString(0, service);
78 
79   bool result = s.Run();
80   LOG_IF(ERROR, !result) << "Failed to remove token for " << service;
81   return result;
82 }
83 
SetTokenForService(const std::string & service,const std::string & token)84 bool TokenServiceTable::SetTokenForService(const std::string& service,
85                                            const std::string& token) {
86   std::string encrypted_token;
87   bool encrypted = OSCrypt::EncryptString(token, &encrypted_token);
88   if (!encrypted) {
89     LOG(ERROR) << "Failed to encrypt token (token will not be saved to DB).";
90     return false;
91   }
92 
93   // Don't bother with a cached statement since this will be a relatively
94   // infrequent operation.
95   sql::Statement s(
96       db_->GetUniqueStatement("INSERT OR REPLACE INTO token_service "
97                               "(service, encrypted_token) VALUES (?, ?)"));
98   s.BindString(0, service);
99   s.BindBlob(1, encrypted_token.data(),
100              static_cast<int>(encrypted_token.length()));
101 
102   bool result = s.Run();
103   LOG_IF(ERROR, !result) << "Failed to insert or replace token for " << service;
104   return result;
105 }
106 
GetAllTokens(std::map<std::string,std::string> * tokens)107 TokenServiceTable::Result TokenServiceTable::GetAllTokens(
108     std::map<std::string, std::string>* tokens) {
109   sql::Statement s(db_->GetUniqueStatement(
110       "SELECT service, encrypted_token FROM token_service"));
111 
112   UMA_HISTOGRAM_BOOLEAN("Signin.TokenTable.GetAllTokensSqlStatementValidity",
113                         s.is_valid());
114 
115   if (!s.is_valid()) {
116     LOG(ERROR) << "Failed to load tokens (invalid SQL statement).";
117     return TOKEN_DB_RESULT_SQL_INVALID_STATEMENT;
118   }
119 
120   int number_of_tokens_loaded = 0;
121 
122   Result read_all_tokens_result = TOKEN_DB_RESULT_SUCCESS;
123   while (s.Step()) {
124     ReadOneTokenResult read_token_result = READ_ONE_TOKEN_MAX_VALUE;
125 
126     std::string encrypted_token;
127     std::string decrypted_token;
128     std::string service;
129     service = s.ColumnString(0);
130     bool entry_ok =
131         !service.empty() && s.ColumnBlobAsString(1, &encrypted_token);
132     if (entry_ok) {
133       if (OSCrypt::DecryptString(encrypted_token, &decrypted_token)) {
134         (*tokens)[service] = decrypted_token;
135         read_token_result = READ_ONE_TOKEN_SUCCESS;
136         number_of_tokens_loaded++;
137       } else {
138         // Chrome relies on native APIs to encrypt and decrypt the tokens which
139         // may fail (see http://crbug.com/686485).
140         LOG(ERROR) << "Failed to decrypt token for service " << service;
141         read_token_result = READ_ONE_TOKEN_DB_SUCCESS_DECRYPT_FAILED;
142         read_all_tokens_result = TOKEN_DB_RESULT_DECRYPT_ERROR;
143       }
144     } else {
145       LOG(ERROR) << "Bad token entry for service " << service;
146       read_token_result = READ_ONE_TOKEN_DB_FAILED_BAD_ENTRY;
147       read_all_tokens_result = TOKEN_DB_RESULT_BAD_ENTRY;
148     }
149     DCHECK_LT(read_token_result, READ_ONE_TOKEN_MAX_VALUE);
150     UMA_HISTOGRAM_ENUMERATION("Signin.TokenTable.ReadTokenFromDBResult",
151                               read_token_result, READ_ONE_TOKEN_MAX_VALUE);
152   }
153   VLOG(1) << "Loaded tokens: result = " << read_all_tokens_result
154           << " ; number of tokens loaded = " << number_of_tokens_loaded;
155   return read_all_tokens_result;
156 }
157