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