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 std::wstring spn_wide = base::ASCIIToWide(spn); 364 SECURITY_STATUS status = library_->InitializeSecurityContext( 365 &cred_, // phCredential 366 ctxt_ptr, // phContext 367 const_cast<wchar_t *>(spn_wide.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 429