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"
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)
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
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.
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
{
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
;
81 autofill::PasswordForm
CreateBlacklistPasswordForm(
82 const std::string
& blacklist_host
) {
83 GURL::Replacements rep
;
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;
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();
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.
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
) {
117 return base::string16();
121 request
.data
= reinterpret_cast<unsigned char*>(
122 const_cast<char*>(decoded_data
.data()));
123 request
.len
= static_cast<unsigned int>(decoded_data
.size());
127 #if defined(USE_NSS_CERTS)
128 result
= PK11SDR_DecryptWithSlot(slot
, &request
, &reply
, NULL
);
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
);
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.
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
) {
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.
167 if (lines
[0] == "#2c")
169 else if (lines
[0] == "#2d")
171 else if (lines
[0] == "#2e")
176 // Reads never-saved list. Domains are stored one per line.
178 for (i
= 1; i
< lines
.size() && lines
[i
].compare(".") != 0; ++i
)
179 forms
->push_back(CreateBlacklistPasswordForm(lines
[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()) {
188 while (end
< lines
.size() && lines
[end
].compare(".") != 0)
192 // A block has at least five lines.
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
);
211 raw_password_info
.host
= lines
[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
) {
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
++];
229 // Maybe the file is bad, we skip to next block.
232 // The action attribute from the form element. This line exists
233 // in versin 2 or above.
236 raw_password_info
.form_action
= lines
[begin
];
239 // Version 3 has an extra line for further use.
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
) {
254 if (!db
.Open(sqlite_file
))
257 const char query
[] = "SELECT hostname FROM moz_disabledHosts";
258 sql::Statement
s(db
.GetUniqueStatement(query
));
262 // Read domains for which passwords are never saved.
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
));
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
);
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
))
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
))
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
))
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
);
341 bool NSSDecryptor::CreatePasswordFormFromRawInfo(
342 const FirefoxRawPasswordInfo
& raw_password_info
,
343 autofill::PasswordForm
* form
) {
344 GURL::Replacements rep
;
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
);
356 url
= GURL(raw_password_info
.host
);
358 // Skip this login if the URL is not valid.
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
);