| OLD | NEW |
| (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 } |
| OLD | NEW |