Add ICU message format support
[chromium-blink-merge.git] / chrome / utility / importer / nss_decryptor.cc
blob21e2d3b7d4abadc0957c14c7fc37f26b6fb2cd34
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.
5 #include "chrome/utility/importer/nss_decryptor.h"
7 #include <string>
8 #include <vector>
10 #include "base/base64.h"
11 #include "base/files/file_util.h"
12 #include "base/json/json_reader.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/strings/string_split.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "base/values.h"
18 #include "components/autofill/core/common/password_form.h"
19 #include "sql/connection.h"
20 #include "sql/statement.h"
22 #if defined(USE_NSS_CERTS)
23 #include <pk11pub.h>
24 #include <pk11sdr.h>
25 #endif // defined(USE_NSS_CERTS)
27 // This method is based on some Firefox code in
28 // security/manager/ssl/src/nsSDR.cpp
29 // The license block is:
31 /* ***** BEGIN LICENSE BLOCK *****
32 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
34 * The contents of this file are subject to the Mozilla Public License Version
35 * 1.1 (the "License"); you may not use this file except in compliance with
36 * the License. You may obtain a copy of the License at
37 * http://www.mozilla.org/MPL/
39 * Software distributed under the License is distributed on an "AS IS" basis,
40 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
41 * for the specific language governing rights and limitations under the
42 * License.
44 * The Original Code is the Netscape security libraries.
46 * The Initial Developer of the Original Code is
47 * Netscape Communications Corporation.
48 * Portions created by the Initial Developer are Copyright (C) 1994-2000
49 * the Initial Developer. All Rights Reserved.
51 * Contributor(s):
53 * Alternatively, the contents of this file may be used under the terms of
54 * either the GNU General Public License Version 2 or later (the "GPL"), or
55 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
56 * in which case the provisions of the GPL or the LGPL are applicable instead
57 * of those above. If you wish to allow use of your version of this file only
58 * under the terms of either the GPL or the LGPL, and not to allow others to
59 * use your version of this file under the terms of the MPL, indicate your
60 * decision by deleting the provisions above and replace them with the notice
61 * and other provisions required by the GPL or the LGPL. If you do not delete
62 * the provisions above, a recipient may use your version of this file under
63 * the terms of any one of the MPL, the GPL or the LGPL.
65 * ***** END LICENSE BLOCK ***** */
67 // Use this structure to store unprocessed information extracted from
68 // Firefox's password file.
69 struct FirefoxRawPasswordInfo {
70 std::string host;
71 std::string realm;
72 base::string16 username_element;
73 base::string16 password_element;
74 std::string encrypted_username;
75 std::string encrypted_password;
76 std::string form_action;
79 namespace {
81 autofill::PasswordForm CreateBlacklistPasswordForm(
82 const std::string& blacklist_host) {
83 GURL::Replacements rep;
84 rep.ClearQuery();
85 rep.ClearRef();
86 rep.ClearUsername();
87 rep.ClearPassword();
89 autofill::PasswordForm form;
90 form.origin = GURL(blacklist_host).ReplaceComponents(rep);
91 form.signon_realm = form.origin.GetOrigin().spec();
92 form.blacklisted_by_user = true;
93 return form;
96 } // namespace
98 base::string16 NSSDecryptor::Decrypt(const std::string& crypt) const {
99 // Do nothing if NSS is not loaded.
100 if (!is_nss_initialized_)
101 return base::string16();
103 if (crypt.empty())
104 return base::string16();
106 // The old style password is encoded in base64. They are identified
107 // by a leading '~'. Otherwise, we should decrypt the text.
108 std::string plain;
109 if (crypt[0] != '~') {
110 std::string decoded_data;
111 if (!base::Base64Decode(crypt, &decoded_data))
112 return base::string16();
113 PK11SlotInfo* slot = GetKeySlotForDB();
114 SECStatus result = PK11_Authenticate(slot, PR_TRUE, NULL);
115 if (result != SECSuccess) {
116 FreeSlot(slot);
117 return base::string16();
120 SECItem request;
121 request.data = reinterpret_cast<unsigned char*>(
122 const_cast<char*>(decoded_data.data()));
123 request.len = static_cast<unsigned int>(decoded_data.size());
124 SECItem reply;
125 reply.data = NULL;
126 reply.len = 0;
127 #if defined(USE_NSS_CERTS)
128 result = PK11SDR_DecryptWithSlot(slot, &request, &reply, NULL);
129 #else
130 result = PK11SDR_Decrypt(&request, &reply, NULL);
131 #endif // defined(USE_NSS_CERTS)
132 if (result == SECSuccess)
133 plain.assign(reinterpret_cast<char*>(reply.data), reply.len);
135 SECITEM_FreeItem(&reply, PR_FALSE);
136 FreeSlot(slot);
137 } else {
138 // Deletes the leading '~' before decoding.
139 if (!base::Base64Decode(crypt.substr(1), &plain))
140 return base::string16();
143 return base::UTF8ToUTF16(plain);
146 // There are three versions of password files. They store saved user
147 // names and passwords.
148 // References:
149 // http://kb.mozillazine.org/Signons.txt
150 // http://kb.mozillazine.org/Signons2.txt
151 // http://kb.mozillazine.org/Signons3.txt
152 void NSSDecryptor::ParseSignons(const base::FilePath& signon_file,
153 std::vector<autofill::PasswordForm>* forms) {
154 forms->clear();
156 std::string content;
157 base::ReadFileToString(signon_file, &content);
159 // Splits the file content into lines.
160 std::vector<std::string> lines = base::SplitString(
161 content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
163 // The first line is the file version. We skip the unknown versions.
164 if (lines.empty())
165 return;
166 int version;
167 if (lines[0] == "#2c")
168 version = 1;
169 else if (lines[0] == "#2d")
170 version = 2;
171 else if (lines[0] == "#2e")
172 version = 3;
173 else
174 return;
176 // Reads never-saved list. Domains are stored one per line.
177 size_t i;
178 for (i = 1; i < lines.size() && lines[i].compare(".") != 0; ++i)
179 forms->push_back(CreateBlacklistPasswordForm(lines[i]));
180 ++i;
182 // Reads saved passwords. The information is stored in blocks
183 // seperated by lines that only contain a dot. We find a block
184 // by the seperator and parse them one by one.
185 while (i < lines.size()) {
186 size_t begin = i;
187 size_t end = i + 1;
188 while (end < lines.size() && lines[end].compare(".") != 0)
189 ++end;
190 i = end + 1;
192 // A block has at least five lines.
193 if (end - begin < 5)
194 continue;
196 FirefoxRawPasswordInfo raw_password_info;
198 // The first line is the site URL.
199 // For HTTP authentication logins, the URL may contain http realm,
200 // which will be in bracket:
201 // sitename:8080 (realm)
202 const char kRealmBracketBegin[] = " (";
203 const char kRealmBracketEnd[] = ")";
204 if (lines[begin].find(kRealmBracketBegin) != std::string::npos) {
205 size_t start = lines[begin].find(kRealmBracketBegin);
206 raw_password_info.host = lines[begin].substr(0, start);
207 start += std::string(kRealmBracketBegin).size();
208 size_t end = lines[begin].rfind(kRealmBracketEnd);
209 raw_password_info.realm = lines[begin].substr(start, end - start);
210 } else {
211 raw_password_info.host = lines[begin];
214 ++begin;
216 // There may be multiple username/password pairs for this site.
217 // In this case, they are saved in one block without a seperated
218 // line (contains a dot).
219 while (begin + 4 < end) {
220 // The user name.
221 raw_password_info.username_element = base::UTF8ToUTF16(lines[begin++]);
222 raw_password_info.encrypted_username = lines[begin++];
223 // The element name has a leading '*'.
224 if (lines[begin].at(0) == '*') {
225 raw_password_info.password_element =
226 base::UTF8ToUTF16(lines[begin++].substr(1));
227 raw_password_info.encrypted_password = lines[begin++];
228 } else {
229 // Maybe the file is bad, we skip to next block.
230 break;
232 // The action attribute from the form element. This line exists
233 // in versin 2 or above.
234 if (version >= 2) {
235 if (begin < end)
236 raw_password_info.form_action = lines[begin];
237 ++begin;
239 // Version 3 has an extra line for further use.
240 if (version == 3)
241 ++begin;
243 autofill::PasswordForm form;
244 if (CreatePasswordFormFromRawInfo(raw_password_info, &form))
245 forms->push_back(form);
250 bool NSSDecryptor::ReadAndParseSignons(
251 const base::FilePath& sqlite_file,
252 std::vector<autofill::PasswordForm>* forms) {
253 sql::Connection db;
254 if (!db.Open(sqlite_file))
255 return false;
257 const char query[] = "SELECT hostname FROM moz_disabledHosts";
258 sql::Statement s(db.GetUniqueStatement(query));
259 if (!s.is_valid())
260 return false;
262 // Read domains for which passwords are never saved.
263 while (s.Step())
264 forms->push_back(CreateBlacklistPasswordForm(s.ColumnString(0)));
266 const char query2[] = "SELECT hostname, httpRealm, formSubmitURL, "
267 "usernameField, passwordField, encryptedUsername, "
268 "encryptedPassword FROM moz_logins";
270 sql::Statement s2(db.GetUniqueStatement(query2));
271 if (!s2.is_valid())
272 return false;
274 while (s2.Step()) {
275 FirefoxRawPasswordInfo raw_password_info;
276 raw_password_info.host = s2.ColumnString(0);
277 raw_password_info.realm = s2.ColumnString(1);
278 // The user name, password and action.
279 raw_password_info.username_element = s2.ColumnString16(3);
280 raw_password_info.encrypted_username = s2.ColumnString(5);
281 raw_password_info.password_element = s2.ColumnString16(4);
282 raw_password_info.encrypted_password = s2.ColumnString(6);
283 raw_password_info.form_action = s2.ColumnString(2);
284 autofill::PasswordForm form;
285 if (CreatePasswordFormFromRawInfo(raw_password_info, &form))
286 forms->push_back(form);
288 return true;
291 bool NSSDecryptor::ReadAndParseLogins(
292 const base::FilePath& json_file,
293 std::vector<autofill::PasswordForm>* forms) {
294 std::string json_content;
295 base::ReadFileToString(json_file, &json_content);
296 scoped_ptr<base::Value> parsed_json(base::JSONReader::Read(json_content));
297 const base::DictionaryValue* password_dict;
298 const base::ListValue* password_list;
299 const base::ListValue* blacklist_domains;
300 if (!parsed_json || !parsed_json->GetAsDictionary(&password_dict))
301 return false;
303 if (password_dict->GetList("disabledHosts", &blacklist_domains)) {
304 for (const base::Value* value : *blacklist_domains) {
305 std::string disabled_host;
306 if (!value->GetAsString(&disabled_host))
307 continue;
308 forms->push_back(CreateBlacklistPasswordForm(disabled_host));
312 if (password_dict->GetList("logins", &password_list)) {
313 for (const base::Value* value : *password_list) {
314 const base::DictionaryValue* password_detail;
315 if (!value->GetAsDictionary(&password_detail))
316 continue;
318 FirefoxRawPasswordInfo raw_password_info;
319 password_detail->GetString("hostname", &raw_password_info.host);
320 password_detail->GetString("usernameField",
321 &raw_password_info.username_element);
322 password_detail->GetString("passwordField",
323 &raw_password_info.password_element);
324 password_detail->GetString("encryptedUsername",
325 &raw_password_info.encrypted_username);
326 password_detail->GetString("encryptedPassword",
327 &raw_password_info.encrypted_password);
328 password_detail->GetString("formSubmitURL",
329 &raw_password_info.form_action);
330 password_detail->GetString("httpRealm", &raw_password_info.realm);
332 autofill::PasswordForm form;
333 if (CreatePasswordFormFromRawInfo(raw_password_info, &form))
334 forms->push_back(form);
338 return true;
341 bool NSSDecryptor::CreatePasswordFormFromRawInfo(
342 const FirefoxRawPasswordInfo& raw_password_info,
343 autofill::PasswordForm* form) {
344 GURL::Replacements rep;
345 rep.ClearQuery();
346 rep.ClearRef();
347 rep.ClearUsername();
348 rep.ClearPassword();
350 GURL url;
351 if (!raw_password_info.realm.empty() &&
352 raw_password_info.host.find("://") == std::string::npos) {
353 // Assume HTTP for forms with non-empty realm and no scheme in hostname.
354 url = GURL("http://" + raw_password_info.host);
355 } else {
356 url = GURL(raw_password_info.host);
358 // Skip this login if the URL is not valid.
359 if (!url.is_valid())
360 return false;
362 form->origin = url.ReplaceComponents(rep);
363 form->signon_realm = form->origin.GetOrigin().spec();
364 if (!raw_password_info.realm.empty()) {
365 form->signon_realm += raw_password_info.realm;
366 // Non-empty realm indicates that it's not html form authentication entry.
367 // Extracted data doesn't allow us to distinguish basic_auth entry from
368 // digest_auth entry, so let's assume basic_auth.
369 form->scheme = autofill::PasswordForm::SCHEME_BASIC;
371 form->ssl_valid = form->origin.SchemeIsCryptographic();
372 form->username_element = raw_password_info.username_element;
373 form->username_value = Decrypt(raw_password_info.encrypted_username);
374 form->password_element = raw_password_info.password_element;
375 form->password_value = Decrypt(raw_password_info.encrypted_password);
376 form->action = GURL(raw_password_info.form_action).ReplaceComponents(rep);
378 return true;