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.
5 #include "chrome/utility/importer/nss_decryptor.h"
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"
19 #if defined(USE_NSS_CERTS)
22 #endif // defined(USE_NSS_CERTS)
24 // This method is based on some Firefox code in
25 // security/manager/ssl/src/nsSDR.cpp
26 // The license block is:
28 /* ***** BEGIN LICENSE BLOCK *****
29 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
41 * The Original Code is the Netscape security libraries.
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.
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.
62 * ***** END LICENSE BLOCK ***** */
64 base::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();
70 return base::string16();
72 // The old style password is encoded in base64. They are identified
73 // by a leading '~'. Otherwise, we should decrypt the text.
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
) {
83 return base::string16();
87 request
.data
= reinterpret_cast<unsigned char*>(
88 const_cast<char*>(decoded_data
.data()));
89 request
.len
= static_cast<unsigned int>(decoded_data
.size());
93 #if defined(USE_NSS_CERTS)
94 result
= PK11SDR_DecryptWithSlot(slot
, &request
, &reply
, NULL
);
96 result
= PK11SDR_Decrypt(&request
, &reply
, NULL
);
97 #endif // defined(USE_NSS_CERTS)
98 if (result
== SECSuccess
)
99 plain
.assign(reinterpret_cast<char*>(reply
.data
), reply
.len
);
101 SECITEM_FreeItem(&reply
, PR_FALSE
);
104 // Deletes the leading '~' before decoding.
105 if (!base::Base64Decode(crypt
.substr(1), &plain
))
106 return base::string16();
109 return base::UTF8ToUTF16(plain
);
112 // There are three versions of password files. They store saved user
113 // names and passwords.
115 // http://kb.mozillazine.org/Signons.txt
116 // http://kb.mozillazine.org/Signons2.txt
117 // http://kb.mozillazine.org/Signons3.txt
118 void NSSDecryptor::ParseSignons(
119 const std::string
& content
,
120 std::vector
<autofill::PasswordForm
>* forms
) {
123 // Splits the file content into lines.
124 std::vector
<std::string
> lines
;
125 base::SplitString(content
, '\n', &lines
);
127 // The first line is the file version. We skip the unknown versions.
131 if (lines
[0] == "#2c")
133 else if (lines
[0] == "#2d")
135 else if (lines
[0] == "#2e")
140 GURL::Replacements rep
;
146 // Reads never-saved list. Domains are stored one per line.
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
);
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()) {
163 while (end
< lines
.size() && lines
[end
].compare(".") != 0)
167 // A block has at least five lines.
171 autofill::PasswordForm form
;
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)
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
184 if (lines
[begin
].find("://") == std::string::npos
)
185 lines
[begin
] = "http://" + lines
[begin
];
187 size_t start
= lines
[begin
].find(kRealmBracketBegin
);
188 url
= GURL(lines
[begin
].substr(0, start
));
190 start
+= std::string(kRealmBracketBegin
).size();
191 size_t end
= lines
[begin
].rfind(kRealmBracketEnd
);
192 realm
= lines
[begin
].substr(start
, end
- start
);
194 // Don't have http realm. It is the URL that the following passwords
196 url
= GURL(lines
[begin
]);
198 // Skips this block if the URL is not valid.
201 form
.origin
= url
.ReplaceComponents(rep
);
202 form
.signon_realm
= form
.origin
.GetOrigin().spec();
204 form
.signon_realm
+= realm
;
205 form
.ssl_valid
= form
.origin
.SchemeIsCryptographic();
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
) {
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
++]);
220 // Maybe the file is bad, we skip to next block.
223 // The action attribute from the form element. This line exists
224 // in versin 2 or above.
227 form
.action
= GURL(lines
[begin
]).ReplaceComponents(rep
);
230 // Version 3 has an extra line for further use.
235 forms
->push_back(form
);
240 bool NSSDecryptor::ReadAndParseSignons(const base::FilePath
& sqlite_file
,
241 std::vector
<autofill::PasswordForm
>* forms
) {
243 if (!db
.Open(sqlite_file
))
246 const char query
[] = "SELECT hostname FROM moz_disabledHosts";
247 sql::Statement
s(db
.GetUniqueStatement(query
));
251 GURL::Replacements rep
;
256 // Read domains for which passwords are never saved.
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
);
265 const char query2
[] = "SELECT hostname, httpRealm, formSubmitURL, "
266 "usernameField, passwordField, encryptedUsername, "
267 "encryptedPassword FROM moz_logins";
269 sql::Statement
s2(db
.GetUniqueStatement(query2
));
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
;
283 url
= GURL(s2
.ColumnString(0));
285 // Skip this row if the URL is not valid.
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
;
299 form
.ssl_valid
= form
.origin
.SchemeIsCryptographic();
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
);