Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Side by Side Diff: chrome/browser/password_manager/password_store_mac.cc

Issue 115658: First phase of Mac Keychain integration. For the overall plan, see the design... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 11 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2009 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/password_manager/password_store_mac.h"
6 #include "chrome/browser/password_manager/password_store_mac_internal.h"
7
8 #include <CoreServices/CoreServices.h>
9 #include <string>
10 #include <vector>
11
12 #include "base/logging.h"
13 #include "base/string_util.h"
14 #include "base/time.h"
15 #include "chrome/browser/keychain_mac.h"
16
17 namespace internal_keychain_helpers {
18
19 // TODO(stuartmorgan): signon_realm for proxies is not yet supported.
20 bool ExtractSignonRealmComponents(const std::string& signon_realm,
21 std::string* server, int* port,
22 bool* is_secure,
23 std::string* security_domain) {
24 // The signon_realm will be the Origin portion of a URL for an HTML form,
25 // and the same but with the security domain as a path for HTTP auth.
26 GURL realm_as_url(signon_realm);
27 if (!realm_as_url.is_valid()) {
28 return false;
29 }
30
31 if (server)
32 *server = realm_as_url.host();
33 if (is_secure)
34 *is_secure = realm_as_url.SchemeIsSecure();
35 if (port)
36 *port = realm_as_url.has_port() ? atoi(realm_as_url.port().c_str()) : 0;
37 if (security_domain) {
38 // Strip the leading '/' off of the path to get the security domain.
39 *security_domain = realm_as_url.path().substr(1);
40 }
41 return true;
42 }
43
44 GURL URLFromComponents(bool is_secure, const std::string& host, int port,
45 const std::string& path) {
46 GURL::Replacements url_components;
47 std::string scheme(is_secure ? "https" : "http");
48 url_components.SetSchemeStr(scheme);
49 url_components.SetHostStr(host);
50 std::string port_string; // Must remain in scope until after we do replacing.
51 if (port != kAnyPort) {
52 std::ostringstream port_stringstream;
53 port_stringstream << port;
54 port_string = port_stringstream.str();
55 url_components.SetPortStr(port_string);
56 }
57 url_components.SetPathStr(path);
58
59 GURL url("http://dummy.com"); // ReplaceComponents needs a valid URL.
60 return url.ReplaceComponents(url_components);
61 }
62
63 // The time string is of the form "yyyyMMddHHmmss'Z", in UTC time.
64 bool TimeFromKeychainTimeString(const char* time_string_bytes,
65 unsigned int byte_length,
66 base::Time* time) {
67 DCHECK(time);
68
69 char* time_string = static_cast<char*>(malloc(byte_length + 1));
70 memcpy(time_string, time_string_bytes, byte_length);
71 time_string[byte_length] = '\0';
72 base::Time::Exploded exploded_time;
73 bzero(&exploded_time, sizeof(exploded_time));
74 int assignments = sscanf(time_string, "%4d%2d%2d%2d%2d%2dZ",
75 &exploded_time.year, &exploded_time.month,
76 &exploded_time.day_of_month, &exploded_time.hour,
77 &exploded_time.minute, &exploded_time.second);
78 free(time_string);
79
80 if (assignments == 6) {
81 *time = base::Time::FromUTCExploded(exploded_time);
82 return true;
83 }
84 return false;
85 }
86
87 SecAuthenticationType AuthTypeForScheme(PasswordForm::Scheme scheme) {
88 switch (scheme) {
89 case PasswordForm::SCHEME_HTML: return kSecAuthenticationTypeHTMLForm;
90 case PasswordForm::SCHEME_BASIC: return kSecAuthenticationTypeHTTPBasic;
91 case PasswordForm::SCHEME_DIGEST: return kSecAuthenticationTypeHTTPDigest;
92 case PasswordForm::SCHEME_OTHER: return kSecAuthenticationTypeDefault;
93 }
94 NOTREACHED();
95 return kSecAuthenticationTypeDefault;
96 }
97
98 PasswordForm::Scheme SchemeForAuthType(SecAuthenticationType auth_type) {
99 switch (auth_type) {
100 case kSecAuthenticationTypeHTMLForm: return PasswordForm::SCHEME_HTML;
101 case kSecAuthenticationTypeHTTPBasic: return PasswordForm::SCHEME_BASIC;
102 case kSecAuthenticationTypeHTTPDigest: return PasswordForm::SCHEME_DIGEST;
103 default: return PasswordForm::SCHEME_OTHER;
104 }
105 }
106
107 // Searches |keychain| for all items usable for the given signon_realm, and
108 // puts them in |items|. The caller is responsible for calling keychain->Free
109 // on each of them when it is finished with them.
110 void FindMatchingKeychainItems(const MacKeychain& keychain,
111 const std::string& signon_realm,
112 PasswordForm::Scheme scheme,
113 std::vector<SecKeychainItemRef>* items) {
114 // Construct a keychain search based on the signon_realm and scheme.
115 std::string server;
116 std::string security_domain;
117 int port;
118 bool is_secure;
119 if (!internal_keychain_helpers::ExtractSignonRealmComponents(
120 signon_realm, &server, &port, &is_secure, &security_domain)) {
121 // TODO(stuartmorgan): Proxies will currently fail here, since their
122 // signon_realm is not a URL. We need to detect the proxy case and handle
123 // it specially.
124 return;
125 }
126
127 const char* server_c_str = server.c_str();
128 UInt32 port_uint = port;
129 SecProtocolType protocol = is_secure ? kSecProtocolTypeHTTPS
130 : kSecProtocolTypeHTTP;
131 SecAuthenticationType auth_type =
132 internal_keychain_helpers::AuthTypeForScheme(scheme);
133 const char* security_domain_c_str = security_domain.c_str();
134
135 // kSecSecurityDomainItemAttr must be last, so that it can be "removed" when
136 // not applicable.
137 SecKeychainAttribute attributes[] = {
138 { kSecServerItemAttr, strlen(server_c_str),
139 const_cast<void*>(reinterpret_cast<const void*>(server_c_str)) },
140 { kSecPortItemAttr, sizeof(port_uint), static_cast<void*>(&port_uint) },
141 { kSecProtocolItemAttr, sizeof(protocol), static_cast<void*>(&protocol) },
142 { kSecAuthenticationTypeItemAttr, sizeof(auth_type),
143 static_cast<void*>(&auth_type) },
144 { kSecSecurityDomainItemAttr, strlen(security_domain_c_str),
145 const_cast<void*>(reinterpret_cast<const void*>(security_domain_c_str)) }
146 };
147 SecKeychainAttributeList search_attributes = { arraysize(attributes),
148 attributes };
149 // For HTML forms, we don't want the security domain to be part of the
150 // search, so trim that off of the attribute list.
151 if (scheme == PasswordForm::SCHEME_HTML) {
152 search_attributes.count -= 1;
153 }
154
155 SecKeychainSearchRef keychain_search = NULL;
156 OSStatus result = keychain.SearchCreateFromAttributes(
157 NULL, kSecInternetPasswordItemClass, &search_attributes,
158 &keychain_search);
159
160 if (result != noErr) {
161 LOG(ERROR) << "Keychain lookup failed for " << server << " with error "
162 << result;
163 return;
164 }
165
166 SecKeychainItemRef keychain_item;
167 while (keychain.SearchCopyNext(keychain_search, &keychain_item) == noErr) {
168 // Consumer is responsible for deleting the forms when they are done.
169 items->push_back(keychain_item);
170 }
171
172 keychain.Free(keychain_search);
173 }
174
175 bool FillPasswordFormFromKeychainItem(const MacKeychain& keychain,
176 const SecKeychainItemRef& keychain_item,
177 PasswordForm* form) {
178 DCHECK(form);
179
180 SecKeychainAttributeInfo attrInfo;
181 UInt32 tags[] = { kSecAccountItemAttr,
182 kSecServerItemAttr,
183 kSecPortItemAttr,
184 kSecPathItemAttr,
185 kSecProtocolItemAttr,
186 kSecAuthenticationTypeItemAttr,
187 kSecSecurityDomainItemAttr,
188 kSecCreationDateItemAttr,
189 kSecNegativeItemAttr };
190 attrInfo.count = arraysize(tags);
191 attrInfo.tag = tags;
192 attrInfo.format = NULL;
193
194 SecKeychainAttributeList *attrList;
195 UInt32 password_length;
196 void* password_data;
197 OSStatus result = keychain.ItemCopyAttributesAndData(keychain_item, &attrInfo,
198 NULL, &attrList,
199 &password_length,
200 &password_data);
201
202 if (result != noErr) {
203 // We don't log errSecAuthFailed because that just means that the user
204 // chose not to allow us access to the item.
205 if (result != errSecAuthFailed) {
206 LOG(ERROR) << "Keychain data load failed: " << result;
207 }
208 return false;
209 }
210
211 UTF8ToWide(static_cast<const char *>(password_data), password_length,
212 &(form->password_value));
213
214 int port = kAnyPort;
215 std::string server;
216 std::string security_domain;
217 std::string path;
218 for (unsigned int i = 0; i < attrList->count; i++) {
219 SecKeychainAttribute attr = attrList->attr[i];
220 if (!attr.data) {
221 continue;
222 }
223 switch (attr.tag) {
224 case kSecAccountItemAttr:
225 UTF8ToWide(static_cast<const char *>(attr.data), attr.length,
226 &(form->username_value));
227 break;
228 case kSecServerItemAttr:
229 server.assign(static_cast<const char *>(attr.data), attr.length);
230 break;
231 case kSecPortItemAttr:
232 port = *(static_cast<UInt32*>(attr.data));
233 break;
234 case kSecPathItemAttr:
235 path.assign(static_cast<const char *>(attr.data), attr.length);
236 break;
237 case kSecProtocolItemAttr:
238 {
239 SecProtocolType protocol = *(static_cast<SecProtocolType*>(attr.data));
240 // TODO(stuartmorgan): Handle proxy types
241 form->ssl_valid = (protocol == kSecProtocolTypeHTTPS);
242 break;
243 }
244 case kSecAuthenticationTypeItemAttr:
245 {
246 SecAuthenticationType auth_type =
247 *(static_cast<SecAuthenticationType*>(attr.data));
248 form->scheme = internal_keychain_helpers::SchemeForAuthType(auth_type);
249 break;
250 }
251 case kSecSecurityDomainItemAttr:
252 security_domain.assign(static_cast<const char *>(attr.data),
253 attr.length);
254 break;
255 case kSecCreationDateItemAttr:
256 // The only way to get a date out of Keychain is as a string. Really.
257 // (The docs claim it's an int, but the header is correct.)
258 internal_keychain_helpers::TimeFromKeychainTimeString(
259 static_cast<char*>(attr.data), attr.length, &form->date_created);
260 break;
261 case kSecNegativeItemAttr:
262 Boolean negative_item = *(static_cast<Boolean*>(attr.data));
263 if (negative_item) {
264 form->blacklisted_by_user = true;
265 }
266 break;
267 }
268 }
269 keychain.ItemFreeAttributesAndData(attrList, password_data);
270
271 // kSecNegativeItemAttr doesn't seem to actually be in widespread use. In
272 // practice, other browsers seem to use a "" or " " password (and a special
273 // user name) to indicated blacklist entries.
274 if (form->password_value.empty() || form->password_value == L" ") {
275 form->blacklisted_by_user = true;
276 }
277
278 form->origin = internal_keychain_helpers::URLFromComponents(form->ssl_valid,
279 server, port,
280 path);
281 // TODO(stuartmorgan): Handle proxies, which need a different signon_realm
282 // format.
283 form->signon_realm = form->origin.GetOrigin().spec();
284 if (form->scheme != PasswordForm::SCHEME_HTML) {
285 form->signon_realm.append(security_domain);
286 }
287 return true;
288 }
289
290 } // internal_keychain_helpers
291
292
293 PasswordStoreMac::PasswordStoreMac(MacKeychain* keychain)
294 : keychain_(keychain) {
295 DCHECK(keychain_.get());
296 }
297
298 void PasswordStoreMac::AddLoginImpl(const PasswordForm& form) {
299 NOTIMPLEMENTED();
300 }
301
302 void PasswordStoreMac::UpdateLoginImpl(const PasswordForm& form) {
303 NOTIMPLEMENTED();
304 }
305
306 void PasswordStoreMac::RemoveLoginImpl(const PasswordForm& form) {
307 NOTIMPLEMENTED();
308 }
309
310 void PasswordStoreMac::GetLoginsImpl(GetLoginsRequest* request) {
311 std::vector<SecKeychainItemRef> keychain_items;
312
313 internal_keychain_helpers::FindMatchingKeychainItems(
314 *keychain_, request->form.signon_realm, request->form.scheme,
315 &keychain_items);
316
317 std::vector<PasswordForm*> forms;
318
319 for (std::vector<SecKeychainItemRef>::iterator i = keychain_items.begin();
320 i != keychain_items.end(); ++i) {
321 // Consumer is responsible for deleting the forms when they are done...
322 PasswordForm* form = new PasswordForm();
323 SecKeychainItemRef keychain_item = *i;
324 if (internal_keychain_helpers::FillPasswordFormFromKeychainItem(
325 *keychain_, keychain_item, form)) {
326 forms.push_back(form);
327 }
328 // ... but we need to clean up the keychain item.
329 keychain_->Free(keychain_item);
330 }
331
332 NotifyConsumer(request, forms);
333 }
OLDNEW
« no previous file with comments | « chrome/browser/password_manager/password_store_mac.h ('k') | chrome/browser/password_manager/password_store_mac_internal.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698