| OLD | NEW |
| (Empty) |
| 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 // See "SSPI Sample Application" at | |
| 6 // http://msdn.microsoft.com/en-us/library/aa918273.aspx | |
| 7 | |
| 8 #include "net/http/http_auth_sspi_win.h" | |
| 9 | |
| 10 #include "base/base64.h" | |
| 11 #include "base/logging.h" | |
| 12 #include "base/strings/string_util.h" | |
| 13 #include "base/strings/utf_string_conversions.h" | |
| 14 #include "net/base/net_errors.h" | |
| 15 #include "net/http/http_auth.h" | |
| 16 #include "net/http/http_auth_challenge_tokenizer.h" | |
| 17 | |
| 18 namespace net { | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 int MapAcquireCredentialsStatusToError(SECURITY_STATUS status, | |
| 23 const SEC_WCHAR* package) { | |
| 24 VLOG(1) << "AcquireCredentialsHandle returned 0x" << std::hex << status; | |
| 25 switch (status) { | |
| 26 case SEC_E_OK: | |
| 27 return OK; | |
| 28 case SEC_E_INSUFFICIENT_MEMORY: | |
| 29 return ERR_OUT_OF_MEMORY; | |
| 30 case SEC_E_INTERNAL_ERROR: | |
| 31 LOG(WARNING) | |
| 32 << "AcquireCredentialsHandle returned unexpected status 0x" | |
| 33 << std::hex << status; | |
| 34 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
| 35 case SEC_E_NO_CREDENTIALS: | |
| 36 case SEC_E_NOT_OWNER: | |
| 37 case SEC_E_UNKNOWN_CREDENTIALS: | |
| 38 return ERR_INVALID_AUTH_CREDENTIALS; | |
| 39 case SEC_E_SECPKG_NOT_FOUND: | |
| 40 // This indicates that the SSPI configuration does not match expectations | |
| 41 return ERR_UNSUPPORTED_AUTH_SCHEME; | |
| 42 default: | |
| 43 LOG(WARNING) | |
| 44 << "AcquireCredentialsHandle returned undocumented status 0x" | |
| 45 << std::hex << status; | |
| 46 return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 int AcquireExplicitCredentials(SSPILibrary* library, | |
| 51 const SEC_WCHAR* package, | |
| 52 const base::string16& domain, | |
| 53 const base::string16& user, | |
| 54 const base::string16& password, | |
| 55 CredHandle* cred) { | |
| 56 SEC_WINNT_AUTH_IDENTITY identity; | |
| 57 identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; | |
| 58 identity.User = | |
| 59 reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(user.c_str())); | |
| 60 identity.UserLength = user.size(); | |
| 61 identity.Domain = | |
| 62 reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(domain.c_str())); | |
| 63 identity.DomainLength = domain.size(); | |
| 64 identity.Password = | |
| 65 reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(password.c_str())); | |
| 66 identity.PasswordLength = password.size(); | |
| 67 | |
| 68 TimeStamp expiry; | |
| 69 | |
| 70 // Pass the username/password to get the credentials handle. | |
| 71 SECURITY_STATUS status = library->AcquireCredentialsHandle( | |
| 72 NULL, // pszPrincipal | |
| 73 const_cast<SEC_WCHAR*>(package), // pszPackage | |
| 74 SECPKG_CRED_OUTBOUND, // fCredentialUse | |
| 75 NULL, // pvLogonID | |
| 76 &identity, // pAuthData | |
| 77 NULL, // pGetKeyFn (not used) | |
| 78 NULL, // pvGetKeyArgument (not used) | |
| 79 cred, // phCredential | |
| 80 &expiry); // ptsExpiry | |
| 81 | |
| 82 return MapAcquireCredentialsStatusToError(status, package); | |
| 83 } | |
| 84 | |
| 85 int AcquireDefaultCredentials(SSPILibrary* library, const SEC_WCHAR* package, | |
| 86 CredHandle* cred) { | |
| 87 TimeStamp expiry; | |
| 88 | |
| 89 // Pass the username/password to get the credentials handle. | |
| 90 // Note: Since the 5th argument is NULL, it uses the default | |
| 91 // cached credentials for the logged in user, which can be used | |
| 92 // for a single sign-on. | |
| 93 SECURITY_STATUS status = library->AcquireCredentialsHandle( | |
| 94 NULL, // pszPrincipal | |
| 95 const_cast<SEC_WCHAR*>(package), // pszPackage | |
| 96 SECPKG_CRED_OUTBOUND, // fCredentialUse | |
| 97 NULL, // pvLogonID | |
| 98 NULL, // pAuthData | |
| 99 NULL, // pGetKeyFn (not used) | |
| 100 NULL, // pvGetKeyArgument (not used) | |
| 101 cred, // phCredential | |
| 102 &expiry); // ptsExpiry | |
| 103 | |
| 104 return MapAcquireCredentialsStatusToError(status, package); | |
| 105 } | |
| 106 | |
| 107 int MapInitializeSecurityContextStatusToError(SECURITY_STATUS status) { | |
| 108 VLOG(1) << "InitializeSecurityContext returned 0x" << std::hex << status; | |
| 109 switch (status) { | |
| 110 case SEC_E_OK: | |
| 111 case SEC_I_CONTINUE_NEEDED: | |
| 112 return OK; | |
| 113 case SEC_I_COMPLETE_AND_CONTINUE: | |
| 114 case SEC_I_COMPLETE_NEEDED: | |
| 115 case SEC_I_INCOMPLETE_CREDENTIALS: | |
| 116 case SEC_E_INCOMPLETE_MESSAGE: | |
| 117 case SEC_E_INTERNAL_ERROR: | |
| 118 // These are return codes reported by InitializeSecurityContext | |
| 119 // but not expected by Chrome (for example, INCOMPLETE_CREDENTIALS | |
| 120 // and INCOMPLETE_MESSAGE are intended for schannel). | |
| 121 LOG(WARNING) | |
| 122 << "InitializeSecurityContext returned unexpected status 0x" | |
| 123 << std::hex << status; | |
| 124 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; | |
| 125 case SEC_E_INSUFFICIENT_MEMORY: | |
| 126 return ERR_OUT_OF_MEMORY; | |
| 127 case SEC_E_UNSUPPORTED_FUNCTION: | |
| 128 NOTREACHED(); | |
| 129 return ERR_UNEXPECTED; | |
| 130 case SEC_E_INVALID_HANDLE: | |
| 131 NOTREACHED(); | |
| 132 return ERR_INVALID_HANDLE; | |
| 133 case SEC_E_INVALID_TOKEN: | |
| 134 return ERR_INVALID_RESPONSE; | |
| 135 case SEC_E_LOGON_DENIED: | |
| 136 return ERR_ACCESS_DENIED; | |
| 137 case SEC_E_NO_CREDENTIALS: | |
| 138 case SEC_E_WRONG_PRINCIPAL: | |
| 139 return ERR_INVALID_AUTH_CREDENTIALS; | |
| 140 case SEC_E_NO_AUTHENTICATING_AUTHORITY: | |
| 141 case SEC_E_TARGET_UNKNOWN: | |
| 142 return ERR_MISCONFIGURED_AUTH_ENVIRONMENT; | |
| 143 default: | |
| 144 LOG(WARNING) | |
| 145 << "InitializeSecurityContext returned undocumented status 0x" | |
| 146 << std::hex << status; | |
| 147 return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 int MapQuerySecurityPackageInfoStatusToError(SECURITY_STATUS status) { | |
| 152 VLOG(1) << "QuerySecurityPackageInfo returned 0x" << std::hex << status; | |
| 153 switch (status) { | |
| 154 case SEC_E_OK: | |
| 155 return OK; | |
| 156 case SEC_E_SECPKG_NOT_FOUND: | |
| 157 // This isn't a documented return code, but has been encountered | |
| 158 // during testing. | |
| 159 return ERR_UNSUPPORTED_AUTH_SCHEME; | |
| 160 default: | |
| 161 LOG(WARNING) | |
| 162 << "QuerySecurityPackageInfo returned undocumented status 0x" | |
| 163 << std::hex << status; | |
| 164 return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; | |
| 165 } | |
| 166 } | |
| 167 | |
| 168 int MapFreeContextBufferStatusToError(SECURITY_STATUS status) { | |
| 169 VLOG(1) << "FreeContextBuffer returned 0x" << std::hex << status; | |
| 170 switch (status) { | |
| 171 case SEC_E_OK: | |
| 172 return OK; | |
| 173 default: | |
| 174 // The documentation at | |
| 175 // http://msdn.microsoft.com/en-us/library/aa375416(VS.85).aspx | |
| 176 // only mentions that a non-zero (or non-SEC_E_OK) value is returned | |
| 177 // if the function fails, and does not indicate what the failure | |
| 178 // conditions are. | |
| 179 LOG(WARNING) | |
| 180 << "FreeContextBuffer returned undocumented status 0x" | |
| 181 << std::hex << status; | |
| 182 return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 } // anonymous namespace | |
| 187 | |
| 188 HttpAuthSSPI::HttpAuthSSPI(SSPILibrary* library, | |
| 189 const std::string& scheme, | |
| 190 const SEC_WCHAR* security_package, | |
| 191 ULONG max_token_length) | |
| 192 : library_(library), | |
| 193 scheme_(scheme), | |
| 194 security_package_(security_package), | |
| 195 max_token_length_(max_token_length), | |
| 196 can_delegate_(false) { | |
| 197 DCHECK(library_); | |
| 198 SecInvalidateHandle(&cred_); | |
| 199 SecInvalidateHandle(&ctxt_); | |
| 200 } | |
| 201 | |
| 202 HttpAuthSSPI::~HttpAuthSSPI() { | |
| 203 ResetSecurityContext(); | |
| 204 if (SecIsValidHandle(&cred_)) { | |
| 205 library_->FreeCredentialsHandle(&cred_); | |
| 206 SecInvalidateHandle(&cred_); | |
| 207 } | |
| 208 } | |
| 209 | |
| 210 bool HttpAuthSSPI::NeedsIdentity() const { | |
| 211 return decoded_server_auth_token_.empty(); | |
| 212 } | |
| 213 | |
| 214 bool HttpAuthSSPI::AllowsExplicitCredentials() const { | |
| 215 return true; | |
| 216 } | |
| 217 | |
| 218 void HttpAuthSSPI::Delegate() { | |
| 219 can_delegate_ = true; | |
| 220 } | |
| 221 | |
| 222 void HttpAuthSSPI::ResetSecurityContext() { | |
| 223 if (SecIsValidHandle(&ctxt_)) { | |
| 224 library_->DeleteSecurityContext(&ctxt_); | |
| 225 SecInvalidateHandle(&ctxt_); | |
| 226 } | |
| 227 } | |
| 228 | |
| 229 HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge( | |
| 230 HttpAuthChallengeTokenizer* tok) { | |
| 231 // Verify the challenge's auth-scheme. | |
| 232 if (!LowerCaseEqualsASCII(tok->scheme(), | |
| 233 base::StringToLowerASCII(scheme_).c_str())) | |
| 234 return HttpAuth::AUTHORIZATION_RESULT_INVALID; | |
| 235 | |
| 236 std::string encoded_auth_token = tok->base64_param(); | |
| 237 if (encoded_auth_token.empty()) { | |
| 238 // If a context has already been established, an empty challenge | |
| 239 // should be treated as a rejection of the current attempt. | |
| 240 if (SecIsValidHandle(&ctxt_)) | |
| 241 return HttpAuth::AUTHORIZATION_RESULT_REJECT; | |
| 242 DCHECK(decoded_server_auth_token_.empty()); | |
| 243 return HttpAuth::AUTHORIZATION_RESULT_ACCEPT; | |
| 244 } else { | |
| 245 // If a context has not already been established, additional tokens should | |
| 246 // not be present in the auth challenge. | |
| 247 if (!SecIsValidHandle(&ctxt_)) | |
| 248 return HttpAuth::AUTHORIZATION_RESULT_INVALID; | |
| 249 } | |
| 250 | |
| 251 std::string decoded_auth_token; | |
| 252 bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token); | |
| 253 if (!base64_rv) | |
| 254 return HttpAuth::AUTHORIZATION_RESULT_INVALID; | |
| 255 decoded_server_auth_token_ = decoded_auth_token; | |
| 256 return HttpAuth::AUTHORIZATION_RESULT_ACCEPT; | |
| 257 } | |
| 258 | |
| 259 int HttpAuthSSPI::GenerateAuthToken(const AuthCredentials* credentials, | |
| 260 const std::string& spn, | |
| 261 std::string* auth_token) { | |
| 262 // Initial challenge. | |
| 263 if (!SecIsValidHandle(&cred_)) { | |
| 264 int rv = OnFirstRound(credentials); | |
| 265 if (rv != OK) | |
| 266 return rv; | |
| 267 } | |
| 268 | |
| 269 DCHECK(SecIsValidHandle(&cred_)); | |
| 270 void* out_buf; | |
| 271 int out_buf_len; | |
| 272 int rv = GetNextSecurityToken( | |
| 273 spn, | |
| 274 static_cast<void *>(const_cast<char *>( | |
| 275 decoded_server_auth_token_.c_str())), | |
| 276 decoded_server_auth_token_.length(), | |
| 277 &out_buf, | |
| 278 &out_buf_len); | |
| 279 if (rv != OK) | |
| 280 return rv; | |
| 281 | |
| 282 // Base64 encode data in output buffer and prepend the scheme. | |
| 283 std::string encode_input(static_cast<char*>(out_buf), out_buf_len); | |
| 284 std::string encode_output; | |
| 285 base::Base64Encode(encode_input, &encode_output); | |
| 286 // OK, we are done with |out_buf| | |
| 287 free(out_buf); | |
| 288 *auth_token = scheme_ + " " + encode_output; | |
| 289 return OK; | |
| 290 } | |
| 291 | |
| 292 int HttpAuthSSPI::OnFirstRound(const AuthCredentials* credentials) { | |
| 293 DCHECK(!SecIsValidHandle(&cred_)); | |
| 294 int rv = OK; | |
| 295 if (credentials) { | |
| 296 base::string16 domain; | |
| 297 base::string16 user; | |
| 298 SplitDomainAndUser(credentials->username(), &domain, &user); | |
| 299 rv = AcquireExplicitCredentials(library_, security_package_, domain, | |
| 300 user, credentials->password(), &cred_); | |
| 301 if (rv != OK) | |
| 302 return rv; | |
| 303 } else { | |
| 304 rv = AcquireDefaultCredentials(library_, security_package_, &cred_); | |
| 305 if (rv != OK) | |
| 306 return rv; | |
| 307 } | |
| 308 | |
| 309 return rv; | |
| 310 } | |
| 311 | |
| 312 int HttpAuthSSPI::GetNextSecurityToken( | |
| 313 const std::string& spn, | |
| 314 const void* in_token, | |
| 315 int in_token_len, | |
| 316 void** out_token, | |
| 317 int* out_token_len) { | |
| 318 CtxtHandle* ctxt_ptr; | |
| 319 SecBufferDesc in_buffer_desc, out_buffer_desc; | |
| 320 SecBufferDesc* in_buffer_desc_ptr; | |
| 321 SecBuffer in_buffer, out_buffer; | |
| 322 | |
| 323 if (in_token_len > 0) { | |
| 324 // Prepare input buffer. | |
| 325 in_buffer_desc.ulVersion = SECBUFFER_VERSION; | |
| 326 in_buffer_desc.cBuffers = 1; | |
| 327 in_buffer_desc.pBuffers = &in_buffer; | |
| 328 in_buffer.BufferType = SECBUFFER_TOKEN; | |
| 329 in_buffer.cbBuffer = in_token_len; | |
| 330 in_buffer.pvBuffer = const_cast<void*>(in_token); | |
| 331 ctxt_ptr = &ctxt_; | |
| 332 in_buffer_desc_ptr = &in_buffer_desc; | |
| 333 } else { | |
| 334 // If there is no input token, then we are starting a new authentication | |
| 335 // sequence. If we have already initialized our security context, then | |
| 336 // we're incorrectly reusing the auth handler for a new sequence. | |
| 337 if (SecIsValidHandle(&ctxt_)) { | |
| 338 NOTREACHED(); | |
| 339 return ERR_UNEXPECTED; | |
| 340 } | |
| 341 ctxt_ptr = NULL; | |
| 342 in_buffer_desc_ptr = NULL; | |
| 343 } | |
| 344 | |
| 345 // Prepare output buffer. | |
| 346 out_buffer_desc.ulVersion = SECBUFFER_VERSION; | |
| 347 out_buffer_desc.cBuffers = 1; | |
| 348 out_buffer_desc.pBuffers = &out_buffer; | |
| 349 out_buffer.BufferType = SECBUFFER_TOKEN; | |
| 350 out_buffer.cbBuffer = max_token_length_; | |
| 351 out_buffer.pvBuffer = malloc(out_buffer.cbBuffer); | |
| 352 if (!out_buffer.pvBuffer) | |
| 353 return ERR_OUT_OF_MEMORY; | |
| 354 | |
| 355 DWORD context_flags = 0; | |
| 356 // Firefox only sets ISC_REQ_DELEGATE, but MSDN documentation indicates that | |
| 357 // ISC_REQ_MUTUAL_AUTH must also be set. | |
| 358 if (can_delegate_) | |
| 359 context_flags |= (ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH); | |
| 360 | |
| 361 // This returns a token that is passed to the remote server. | |
| 362 DWORD context_attribute; | |
| 363 base::string16 spn16 = base::ASCIIToUTF16(spn); | |
| 364 SECURITY_STATUS status = library_->InitializeSecurityContext( | |
| 365 &cred_, // phCredential | |
| 366 ctxt_ptr, // phContext | |
| 367 const_cast<base::char16*>(spn16.c_str()), // pszTargetName | |
| 368 context_flags, // fContextReq | |
| 369 0, // Reserved1 (must be 0) | |
| 370 SECURITY_NATIVE_DREP, // TargetDataRep | |
| 371 in_buffer_desc_ptr, // pInput | |
| 372 0, // Reserved2 (must be 0) | |
| 373 &ctxt_, // phNewContext | |
| 374 &out_buffer_desc, // pOutput | |
| 375 &context_attribute, // pfContextAttr | |
| 376 NULL); // ptsExpiry | |
| 377 int rv = MapInitializeSecurityContextStatusToError(status); | |
| 378 if (rv != OK) { | |
| 379 ResetSecurityContext(); | |
| 380 free(out_buffer.pvBuffer); | |
| 381 return rv; | |
| 382 } | |
| 383 if (!out_buffer.cbBuffer) { | |
| 384 free(out_buffer.pvBuffer); | |
| 385 out_buffer.pvBuffer = NULL; | |
| 386 } | |
| 387 *out_token = out_buffer.pvBuffer; | |
| 388 *out_token_len = out_buffer.cbBuffer; | |
| 389 return OK; | |
| 390 } | |
| 391 | |
| 392 void SplitDomainAndUser(const base::string16& combined, | |
| 393 base::string16* domain, | |
| 394 base::string16* user) { | |
| 395 // |combined| may be in the form "user" or "DOMAIN\user". | |
| 396 // Separate the two parts if they exist. | |
| 397 // TODO(cbentzel): I believe user@domain is also a valid form. | |
| 398 size_t backslash_idx = combined.find(L'\\'); | |
| 399 if (backslash_idx == base::string16::npos) { | |
| 400 domain->clear(); | |
| 401 *user = combined; | |
| 402 } else { | |
| 403 *domain = combined.substr(0, backslash_idx); | |
| 404 *user = combined.substr(backslash_idx + 1); | |
| 405 } | |
| 406 } | |
| 407 | |
| 408 int DetermineMaxTokenLength(SSPILibrary* library, | |
| 409 const std::wstring& package, | |
| 410 ULONG* max_token_length) { | |
| 411 DCHECK(library); | |
| 412 DCHECK(max_token_length); | |
| 413 PSecPkgInfo pkg_info = NULL; | |
| 414 SECURITY_STATUS status = library->QuerySecurityPackageInfo( | |
| 415 const_cast<wchar_t *>(package.c_str()), &pkg_info); | |
| 416 int rv = MapQuerySecurityPackageInfoStatusToError(status); | |
| 417 if (rv != OK) | |
| 418 return rv; | |
| 419 int token_length = pkg_info->cbMaxToken; | |
| 420 status = library->FreeContextBuffer(pkg_info); | |
| 421 rv = MapFreeContextBufferStatusToError(status); | |
| 422 if (rv != OK) | |
| 423 return rv; | |
| 424 *max_token_length = token_length; | |
| 425 return OK; | |
| 426 } | |
| 427 | |
| 428 } // namespace net | |
| OLD | NEW |