Home | History | Annotate | Download | only in importer
      1 // Copyright (c) 2011 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 "app/sql/connection.h"
     11 #include "app/sql/statement.h"
     12 #include "base/base64.h"
     13 #include "base/memory/scoped_ptr.h"
     14 #include "base/string_split.h"
     15 #include "base/string_util.h"
     16 #include "base/utf_string_conversions.h"
     17 #include "webkit/glue/password_form.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(const std::string& content,
    114                                 std::vector<webkit_glue::PasswordForm>* forms) {
    115   forms->clear();
    116 
    117   // Splits the file content into lines.
    118   std::vector<std::string> lines;
    119   base::SplitString(content, '\n', &lines);
    120 
    121   // The first line is the file version. We skip the unknown versions.
    122   if (lines.empty())
    123     return;
    124   int version;
    125   if (lines[0] == "#2c")
    126     version = 1;
    127   else if (lines[0] == "#2d")
    128     version = 2;
    129   else if (lines[0] == "#2e")
    130     version = 3;
    131   else
    132     return;
    133 
    134   GURL::Replacements rep;
    135   rep.ClearQuery();
    136   rep.ClearRef();
    137   rep.ClearUsername();
    138   rep.ClearPassword();
    139 
    140   // Reads never-saved list. Domains are stored one per line.
    141   size_t i;
    142   for (i = 1; i < lines.size() && lines[i].compare(".") != 0; ++i) {
    143     webkit_glue::PasswordForm form;
    144     form.origin = GURL(lines[i]).ReplaceComponents(rep);
    145     form.signon_realm = form.origin.GetOrigin().spec();
    146     form.blacklisted_by_user = true;
    147     forms->push_back(form);
    148   }
    149   ++i;
    150 
    151   // Reads saved passwords. The information is stored in blocks
    152   // seperated by lines that only contain a dot. We find a block
    153   // by the seperator and parse them one by one.
    154   while (i < lines.size()) {
    155     size_t begin = i;
    156     size_t end = i + 1;
    157     while (end < lines.size() && lines[end].compare(".") != 0)
    158       ++end;
    159     i = end + 1;
    160 
    161     // A block has at least five lines.
    162     if (end - begin < 5)
    163       continue;
    164 
    165     webkit_glue::PasswordForm form;
    166 
    167     // The first line is the site URL.
    168     // For HTTP authentication logins, the URL may contain http realm,
    169     // which will be in bracket:
    170     //   sitename:8080 (realm)
    171     GURL url;
    172     std::string realm;
    173     const char kRealmBracketBegin[] = " (";
    174     const char kRealmBracketEnd[] = ")";
    175     if (lines[begin].find(kRealmBracketBegin) != std::string::npos) {
    176       // In this case, the scheme may not exsit. We assume that the
    177       // scheme is HTTP.
    178       if (lines[begin].find("://") == std::string::npos)
    179         lines[begin] = "http://" + lines[begin];
    180 
    181       size_t start = lines[begin].find(kRealmBracketBegin);
    182       url = GURL(lines[begin].substr(0, start));
    183 
    184       start += std::string(kRealmBracketBegin).size();
    185       size_t end = lines[begin].rfind(kRealmBracketEnd);
    186       realm = lines[begin].substr(start, end - start);
    187     } else {
    188       // Don't have http realm. It is the URL that the following passwords
    189       // belong to.
    190       url = GURL(lines[begin]);
    191     }
    192     // Skips this block if the URL is not valid.
    193     if (!url.is_valid())
    194       continue;
    195     form.origin = url.ReplaceComponents(rep);
    196     form.signon_realm = form.origin.GetOrigin().spec();
    197     if (!realm.empty())
    198       form.signon_realm += realm;
    199     form.ssl_valid = form.origin.SchemeIsSecure();
    200     ++begin;
    201 
    202     // There may be multiple username/password pairs for this site.
    203     // In this case, they are saved in one block without a seperated
    204     // line (contains a dot).
    205     while (begin + 4 < end) {
    206       // The user name.
    207       form.username_element = UTF8ToUTF16(lines[begin++]);
    208       form.username_value = Decrypt(lines[begin++]);
    209       // The element name has a leading '*'.
    210       if (lines[begin].at(0) == '*') {
    211         form.password_element = UTF8ToUTF16(lines[begin++].substr(1));
    212         form.password_value = Decrypt(lines[begin++]);
    213       } else {
    214         // Maybe the file is bad, we skip to next block.
    215         break;
    216       }
    217       // The action attribute from the form element. This line exists
    218       // in versin 2 or above.
    219       if (version >= 2) {
    220         if (begin < end)
    221           form.action = GURL(lines[begin]).ReplaceComponents(rep);
    222         ++begin;
    223       }
    224       // Version 3 has an extra line for further use.
    225       if (version == 3) {
    226         ++begin;
    227       }
    228 
    229       forms->push_back(form);
    230     }
    231   }
    232 }
    233 
    234 bool NSSDecryptor::ReadAndParseSignons(const FilePath& sqlite_file,
    235     std::vector<webkit_glue::PasswordForm>* forms) {
    236   sql::Connection db;
    237   if (!db.Open(sqlite_file))
    238     return false;
    239 
    240   const char* query = "SELECT hostname FROM moz_disabledHosts";
    241   sql::Statement s(db.GetUniqueStatement(query));
    242   if (!s)
    243     return false;
    244 
    245   GURL::Replacements rep;
    246   rep.ClearQuery();
    247   rep.ClearRef();
    248   rep.ClearUsername();
    249   rep.ClearPassword();
    250   // Read domains for which passwords are never saved.
    251   while (s.Step()) {
    252     webkit_glue::PasswordForm form;
    253     form.origin = GURL(s.ColumnString(0)).ReplaceComponents(rep);
    254     form.signon_realm = form.origin.GetOrigin().spec();
    255     form.blacklisted_by_user = true;
    256     forms->push_back(form);
    257   }
    258 
    259   const char* query2 = "SELECT hostname, httpRealm, formSubmitURL, "
    260                        "usernameField, passwordField, encryptedUsername, "
    261                        "encryptedPassword FROM moz_logins";
    262 
    263   sql::Statement s2(db.GetUniqueStatement(query2));
    264   if (!s2)
    265     return false;
    266 
    267   while (s2.Step()) {
    268     GURL url;
    269     std::string realm(s2.ColumnString(1));
    270     if (!realm.empty()) {
    271       // In this case, the scheme may not exsit. Assume HTTP.
    272       std::string host(s2.ColumnString(0));
    273       if (host.find("://") == std::string::npos)
    274         host = "http://" + host;
    275       url = GURL(host);
    276     } else {
    277       url = GURL(s2.ColumnString(0));
    278     }
    279     // Skip this row if the URL is not valid.
    280     if (!url.is_valid())
    281       continue;
    282 
    283     webkit_glue::PasswordForm form;
    284     form.origin = url.ReplaceComponents(rep);
    285     form.signon_realm = form.origin.GetOrigin().spec();
    286     if (!realm.empty())
    287       form.signon_realm += realm;
    288     form.ssl_valid = form.origin.SchemeIsSecure();
    289     // The user name, password and action.
    290     form.username_element = s2.ColumnString16(3);
    291     form.username_value = Decrypt(s2.ColumnString(5));
    292     form.password_element = s2.ColumnString16(4);
    293     form.password_value = Decrypt(s2.ColumnString(6));
    294     form.action = GURL(s2.ColumnString(2)).ReplaceComponents(rep);
    295     forms->push_back(form);
    296   }
    297   return true;
    298 }
    299