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
64base::string16 NSSDecryptor::Decrypt(const std::string& crypt) const {
65  // Do nothing if NSS is not loaded.
66  if (!is_nss_initialized_)
67    return base::string16();
68
69  if (crypt.empty())
70    return base::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    if (!base::Base64Decode(crypt, &decoded_data))
78      return base::string16();
79    PK11SlotInfo* slot = GetKeySlotForDB();
80    SECStatus result = PK11_Authenticate(slot, PR_TRUE, NULL);
81    if (result != SECSuccess) {
82      FreeSlot(slot);
83      return base::string16();
84    }
85
86    SECItem request;
87    request.data = reinterpret_cast<unsigned char*>(
88        const_cast<char*>(decoded_data.data()));
89    request.len = static_cast<unsigned int>(decoded_data.size());
90    SECItem reply;
91    reply.data = NULL;
92    reply.len = 0;
93#if defined(USE_NSS)
94    result = PK11SDR_DecryptWithSlot(slot, &request, &reply, NULL);
95#else
96    result = PK11SDR_Decrypt(&request, &reply, NULL);
97#endif  // defined(USE_NSS)
98    if (result == SECSuccess)
99      plain.assign(reinterpret_cast<char*>(reply.data), reply.len);
100
101    SECITEM_FreeItem(&reply, PR_FALSE);
102    FreeSlot(slot);
103  } else {
104    // Deletes the leading '~' before decoding.
105    if (!base::Base64Decode(crypt.substr(1), &plain))
106      return base::string16();
107  }
108
109  return base::UTF8ToUTF16(plain);
110}
111
112// There are three versions of password files. They store saved user
113// names and passwords.
114// References:
115// http://kb.mozillazine.org/Signons.txt
116// http://kb.mozillazine.org/Signons2.txt
117// http://kb.mozillazine.org/Signons3.txt
118void NSSDecryptor::ParseSignons(
119    const std::string& content,
120    std::vector<autofill::PasswordForm>* forms) {
121  forms->clear();
122
123  // Splits the file content into lines.
124  std::vector<std::string> lines;
125  base::SplitString(content, '\n', &lines);
126
127  // The first line is the file version. We skip the unknown versions.
128  if (lines.empty())
129    return;
130  int version;
131  if (lines[0] == "#2c")
132    version = 1;
133  else if (lines[0] == "#2d")
134    version = 2;
135  else if (lines[0] == "#2e")
136    version = 3;
137  else
138    return;
139
140  GURL::Replacements rep;
141  rep.ClearQuery();
142  rep.ClearRef();
143  rep.ClearUsername();
144  rep.ClearPassword();
145
146  // Reads never-saved list. Domains are stored one per line.
147  size_t i;
148  for (i = 1; i < lines.size() && lines[i].compare(".") != 0; ++i) {
149    autofill::PasswordForm form;
150    form.origin = GURL(lines[i]).ReplaceComponents(rep);
151    form.signon_realm = form.origin.GetOrigin().spec();
152    form.blacklisted_by_user = true;
153    forms->push_back(form);
154  }
155  ++i;
156
157  // Reads saved passwords. The information is stored in blocks
158  // seperated by lines that only contain a dot. We find a block
159  // by the seperator and parse them one by one.
160  while (i < lines.size()) {
161    size_t begin = i;
162    size_t end = i + 1;
163    while (end < lines.size() && lines[end].compare(".") != 0)
164      ++end;
165    i = end + 1;
166
167    // A block has at least five lines.
168    if (end - begin < 5)
169      continue;
170
171    autofill::PasswordForm form;
172
173    // The first line is the site URL.
174    // For HTTP authentication logins, the URL may contain http realm,
175    // which will be in bracket:
176    //   sitename:8080 (realm)
177    GURL url;
178    std::string realm;
179    const char kRealmBracketBegin[] = " (";
180    const char kRealmBracketEnd[] = ")";
181    if (lines[begin].find(kRealmBracketBegin) != std::string::npos) {
182      // In this case, the scheme may not exsit. We assume that the
183      // scheme is HTTP.
184      if (lines[begin].find("://") == std::string::npos)
185        lines[begin] = "http://" + lines[begin];
186
187      size_t start = lines[begin].find(kRealmBracketBegin);
188      url = GURL(lines[begin].substr(0, start));
189
190      start += std::string(kRealmBracketBegin).size();
191      size_t end = lines[begin].rfind(kRealmBracketEnd);
192      realm = lines[begin].substr(start, end - start);
193    } else {
194      // Don't have http realm. It is the URL that the following passwords
195      // belong to.
196      url = GURL(lines[begin]);
197    }
198    // Skips this block if the URL is not valid.
199    if (!url.is_valid())
200      continue;
201    form.origin = url.ReplaceComponents(rep);
202    form.signon_realm = form.origin.GetOrigin().spec();
203    if (!realm.empty())
204      form.signon_realm += realm;
205    form.ssl_valid = form.origin.SchemeIsSecure();
206    ++begin;
207
208    // There may be multiple username/password pairs for this site.
209    // In this case, they are saved in one block without a seperated
210    // line (contains a dot).
211    while (begin + 4 < end) {
212      // The user name.
213      form.username_element = base::UTF8ToUTF16(lines[begin++]);
214      form.username_value = Decrypt(lines[begin++]);
215      // The element name has a leading '*'.
216      if (lines[begin].at(0) == '*') {
217        form.password_element = base::UTF8ToUTF16(lines[begin++].substr(1));
218        form.password_value = Decrypt(lines[begin++]);
219      } else {
220        // Maybe the file is bad, we skip to next block.
221        break;
222      }
223      // The action attribute from the form element. This line exists
224      // in versin 2 or above.
225      if (version >= 2) {
226        if (begin < end)
227          form.action = GURL(lines[begin]).ReplaceComponents(rep);
228        ++begin;
229      }
230      // Version 3 has an extra line for further use.
231      if (version == 3) {
232        ++begin;
233      }
234
235      forms->push_back(form);
236    }
237  }
238}
239
240bool NSSDecryptor::ReadAndParseSignons(const base::FilePath& sqlite_file,
241    std::vector<autofill::PasswordForm>* forms) {
242  sql::Connection db;
243  if (!db.Open(sqlite_file))
244    return false;
245
246  const char* query = "SELECT hostname FROM moz_disabledHosts";
247  sql::Statement s(db.GetUniqueStatement(query));
248  if (!s.is_valid())
249    return false;
250
251  GURL::Replacements rep;
252  rep.ClearQuery();
253  rep.ClearRef();
254  rep.ClearUsername();
255  rep.ClearPassword();
256  // Read domains for which passwords are never saved.
257  while (s.Step()) {
258    autofill::PasswordForm form;
259    form.origin = GURL(s.ColumnString(0)).ReplaceComponents(rep);
260    form.signon_realm = form.origin.GetOrigin().spec();
261    form.blacklisted_by_user = true;
262    forms->push_back(form);
263  }
264
265  const char* query2 = "SELECT hostname, httpRealm, formSubmitURL, "
266                       "usernameField, passwordField, encryptedUsername, "
267                       "encryptedPassword FROM moz_logins";
268
269  sql::Statement s2(db.GetUniqueStatement(query2));
270  if (!s2.is_valid())
271    return false;
272
273  while (s2.Step()) {
274    GURL url;
275    std::string realm(s2.ColumnString(1));
276    if (!realm.empty()) {
277      // In this case, the scheme may not exsit. Assume HTTP.
278      std::string host(s2.ColumnString(0));
279      if (host.find("://") == std::string::npos)
280        host = "http://" + host;
281      url = GURL(host);
282    } else {
283      url = GURL(s2.ColumnString(0));
284    }
285    // Skip this row if the URL is not valid.
286    if (!url.is_valid())
287      continue;
288
289    autofill::PasswordForm form;
290    form.origin = url.ReplaceComponents(rep);
291    form.signon_realm = form.origin.GetOrigin().spec();
292    if (!realm.empty()) {
293      form.signon_realm += realm;
294      // Non-empty realm indicates that it's not html form authentication entry.
295      // Extracted data doesn't allow us to distinguish basic_auth entry from
296      // digest_auth entry, so let's assume basic_auth.
297      form.scheme = autofill::PasswordForm::SCHEME_BASIC;
298    }
299    form.ssl_valid = form.origin.SchemeIsSecure();
300    // The user name, password and action.
301    form.username_element = s2.ColumnString16(3);
302    form.username_value = Decrypt(s2.ColumnString(5));
303    form.password_element = s2.ColumnString16(4);
304    form.password_value = Decrypt(s2.ColumnString(6));
305    form.action = GURL(s2.ColumnString(2)).ReplaceComponents(rep);
306    forms->push_back(form);
307  }
308  return true;
309}
310