Home | History | Annotate | Download | only in importer
      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 "content/public/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 
     64 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
    113 void NSSDecryptor::ParseSignons(
    114     const std::string& content,
    115     std::vector<content::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     content::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     content::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 
    235 bool NSSDecryptor::ReadAndParseSignons(const base::FilePath& sqlite_file,
    236     std::vector<content::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     content::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     content::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