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 64string16 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 113void 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 235bool 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