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