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