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.
4
5 #include "chrome/utility/importer/nss_decryptor.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/base64.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "components/autofill/core/common/password_form.h"
16 #include "sql/connection.h"
17 #include "sql/statement.h"
18
19 #if defined(USE_NSS)
20 #include <pk11pub.h>
21 #include <pk11sdr.h>
22 #endif // defined(USE_NSS)
23
24 // This method is based on some Firefox code in
25 // security/manager/ssl/src/nsSDR.cpp
26 // The license block is:
27
28 /* ***** BEGIN LICENSE BLOCK *****
29 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
30 *
31 * The contents of this file are subject to the Mozilla Public License Version
32 * 1.1 (the "License"); you may not use this file except in compliance with
33 * the License. You may obtain a copy of the License at
34 * http://www.mozilla.org/MPL/
35 *
36 * Software distributed under the License is distributed on an "AS IS" basis,
37 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
38 * for the specific language governing rights and limitations under the
39 * License.
40 *
41 * The Original Code is the Netscape security libraries.
42 *
43 * The Initial Developer of the Original Code is
44 * Netscape Communications Corporation.
45 * Portions created by the Initial Developer are Copyright (C) 1994-2000
46 * the Initial Developer. All Rights Reserved.
47 *
48 * Contributor(s):
49 *
50 * Alternatively, the contents of this file may be used under the terms of
51 * either the GNU General Public License Version 2 or later (the "GPL"), or
52 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
53 * in which case the provisions of the GPL or the LGPL are applicable instead
54 * of those above. If you wish to allow use of your version of this file only
55 * under the terms of either the GPL or the LGPL, and not to allow others to
56 * use your version of this file under the terms of the MPL, indicate your
57 * decision by deleting the provisions above and replace them with the notice
58 * and other provisions required by the GPL or the LGPL. If you do not delete
59 * the provisions above, a recipient may use your version of this file under
60 * the terms of any one of the MPL, the GPL or the LGPL.
61 *
62 * ***** END LICENSE BLOCK ***** */
63
Decrypt(const std::string & crypt) const64 string16 NSSDecryptor::Decrypt(const std::string& crypt) const {
65 // Do nothing if NSS is not loaded.
66 if (!is_nss_initialized_)
67 return string16();
68
69 // The old style password is encoded in base64. They are identified
70 // by a leading '~'. Otherwise, we should decrypt the text.
71 std::string plain;
72 if (crypt[0] != '~') {
73 std::string decoded_data;
74 base::Base64Decode(crypt, &decoded_data);
75 PK11SlotInfo* slot = GetKeySlotForDB();
76 SECStatus result = PK11_Authenticate(slot, PR_TRUE, NULL);
77 if (result != SECSuccess) {
78 FreeSlot(slot);
79 return string16();
80 }
81
82 SECItem request;
83 request.data = reinterpret_cast<unsigned char*>(
84 const_cast<char*>(decoded_data.data()));
85 request.len = static_cast<unsigned int>(decoded_data.size());
86 SECItem reply;
87 reply.data = NULL;
88 reply.len = 0;
89 #if defined(USE_NSS)
90 result = PK11SDR_DecryptWithSlot(slot, &request, &reply, NULL);
91 #else
92 result = PK11SDR_Decrypt(&request, &reply, NULL);
93 #endif // defined(USE_NSS)
94 if (result == SECSuccess)
95 plain.assign(reinterpret_cast<char*>(reply.data), reply.len);
96
97 SECITEM_FreeItem(&reply, PR_FALSE);
98 FreeSlot(slot);
99 } else {
100 // Deletes the leading '~' before decoding.
101 base::Base64Decode(crypt.substr(1), &plain);
102 }
103
104 return UTF8ToUTF16(plain);
105 }
106
107 // There are three versions of password files. They store saved user
108 // names and passwords.
109 // References:
110 // http://kb.mozillazine.org/Signons.txt
111 // http://kb.mozillazine.org/Signons2.txt
112 // http://kb.mozillazine.org/Signons3.txt
ParseSignons(const std::string & content,std::vector<autofill::PasswordForm> * forms)113 void NSSDecryptor::ParseSignons(
114 const std::string& content,
115 std::vector<autofill::PasswordForm>* forms) {
116 forms->clear();
117
118 // Splits the file content into lines.
119 std::vector<std::string> lines;
120 base::SplitString(content, '\n', &lines);
121
122 // The first line is the file version. We skip the unknown versions.
123 if (lines.empty())
124 return;
125 int version;
126 if (lines[0] == "#2c")
127 version = 1;
128 else if (lines[0] == "#2d")
129 version = 2;
130 else if (lines[0] == "#2e")
131 version = 3;
132 else
133 return;
134
135 GURL::Replacements rep;
136 rep.ClearQuery();
137 rep.ClearRef();
138 rep.ClearUsername();
139 rep.ClearPassword();
140
141 // Reads never-saved list. Domains are stored one per line.
142 size_t i;
143 for (i = 1; i < lines.size() && lines[i].compare(".") != 0; ++i) {
144 autofill::PasswordForm form;
145 form.origin = GURL(lines[i]).ReplaceComponents(rep);
146 form.signon_realm = form.origin.GetOrigin().spec();
147 form.blacklisted_by_user = true;
148 forms->push_back(form);
149 }
150 ++i;
151
152 // Reads saved passwords. The information is stored in blocks
153 // seperated by lines that only contain a dot. We find a block
154 // by the seperator and parse them one by one.
155 while (i < lines.size()) {
156 size_t begin = i;
157 size_t end = i + 1;
158 while (end < lines.size() && lines[end].compare(".") != 0)
159 ++end;
160 i = end + 1;
161
162 // A block has at least five lines.
163 if (end - begin < 5)
164 continue;
165
166 autofill::PasswordForm form;
167
168 // The first line is the site URL.
169 // For HTTP authentication logins, the URL may contain http realm,
170 // which will be in bracket:
171 // sitename:8080 (realm)
172 GURL url;
173 std::string realm;
174 const char kRealmBracketBegin[] = " (";
175 const char kRealmBracketEnd[] = ")";
176 if (lines[begin].find(kRealmBracketBegin) != std::string::npos) {
177 // In this case, the scheme may not exsit. We assume that the
178 // scheme is HTTP.
179 if (lines[begin].find("://") == std::string::npos)
180 lines[begin] = "http://" + lines[begin];
181
182 size_t start = lines[begin].find(kRealmBracketBegin);
183 url = GURL(lines[begin].substr(0, start));
184
185 start += std::string(kRealmBracketBegin).size();
186 size_t end = lines[begin].rfind(kRealmBracketEnd);
187 realm = lines[begin].substr(start, end - start);
188 } else {
189 // Don't have http realm. It is the URL that the following passwords
190 // belong to.
191 url = GURL(lines[begin]);
192 }
193 // Skips this block if the URL is not valid.
194 if (!url.is_valid())
195 continue;
196 form.origin = url.ReplaceComponents(rep);
197 form.signon_realm = form.origin.GetOrigin().spec();
198 if (!realm.empty())
199 form.signon_realm += realm;
200 form.ssl_valid = form.origin.SchemeIsSecure();
201 ++begin;
202
203 // There may be multiple username/password pairs for this site.
204 // In this case, they are saved in one block without a seperated
205 // line (contains a dot).
206 while (begin + 4 < end) {
207 // The user name.
208 form.username_element = UTF8ToUTF16(lines[begin++]);
209 form.username_value = Decrypt(lines[begin++]);
210 // The element name has a leading '*'.
211 if (lines[begin].at(0) == '*') {
212 form.password_element = UTF8ToUTF16(lines[begin++].substr(1));
213 form.password_value = Decrypt(lines[begin++]);
214 } else {
215 // Maybe the file is bad, we skip to next block.
216 break;
217 }
218 // The action attribute from the form element. This line exists
219 // in versin 2 or above.
220 if (version >= 2) {
221 if (begin < end)
222 form.action = GURL(lines[begin]).ReplaceComponents(rep);
223 ++begin;
224 }
225 // Version 3 has an extra line for further use.
226 if (version == 3) {
227 ++begin;
228 }
229
230 forms->push_back(form);
231 }
232 }
233 }
234
ReadAndParseSignons(const base::FilePath & sqlite_file,std::vector<autofill::PasswordForm> * forms)235 bool NSSDecryptor::ReadAndParseSignons(const base::FilePath& sqlite_file,
236 std::vector<autofill::PasswordForm>* forms) {
237 sql::Connection db;
238 if (!db.Open(sqlite_file))
239 return false;
240
241 const char* query = "SELECT hostname FROM moz_disabledHosts";
242 sql::Statement s(db.GetUniqueStatement(query));
243 if (!s.is_valid())
244 return false;
245
246 GURL::Replacements rep;
247 rep.ClearQuery();
248 rep.ClearRef();
249 rep.ClearUsername();
250 rep.ClearPassword();
251 // Read domains for which passwords are never saved.
252 while (s.Step()) {
253 autofill::PasswordForm form;
254 form.origin = GURL(s.ColumnString(0)).ReplaceComponents(rep);
255 form.signon_realm = form.origin.GetOrigin().spec();
256 form.blacklisted_by_user = true;
257 forms->push_back(form);
258 }
259
260 const char* query2 = "SELECT hostname, httpRealm, formSubmitURL, "
261 "usernameField, passwordField, encryptedUsername, "
262 "encryptedPassword FROM moz_logins";
263
264 sql::Statement s2(db.GetUniqueStatement(query2));
265 if (!s2.is_valid())
266 return false;
267
268 while (s2.Step()) {
269 GURL url;
270 std::string realm(s2.ColumnString(1));
271 if (!realm.empty()) {
272 // In this case, the scheme may not exsit. Assume HTTP.
273 std::string host(s2.ColumnString(0));
274 if (host.find("://") == std::string::npos)
275 host = "http://" + host;
276 url = GURL(host);
277 } else {
278 url = GURL(s2.ColumnString(0));
279 }
280 // Skip this row if the URL is not valid.
281 if (!url.is_valid())
282 continue;
283
284 autofill::PasswordForm form;
285 form.origin = url.ReplaceComponents(rep);
286 form.signon_realm = form.origin.GetOrigin().spec();
287 if (!realm.empty())
288 form.signon_realm += realm;
289 form.ssl_valid = form.origin.SchemeIsSecure();
290 // The user name, password and action.
291 form.username_element = s2.ColumnString16(3);
292 form.username_value = Decrypt(s2.ColumnString(5));
293 form.password_element = s2.ColumnString16(4);
294 form.password_value = Decrypt(s2.ColumnString(6));
295 form.action = GURL(s2.ColumnString(2)).ReplaceComponents(rep);
296 forms->push_back(form);
297 }
298 return true;
299 }
300