diff options
Diffstat (limited to 'chromium/net/http/http_auth_sspi_win.cc')
-rw-r--r-- | chromium/net/http/http_auth_sspi_win.cc | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/chromium/net/http/http_auth_sspi_win.cc b/chromium/net/http/http_auth_sspi_win.cc new file mode 100644 index 00000000000..abc80508bd8 --- /dev/null +++ b/chromium/net/http/http_auth_sspi_win.cc @@ -0,0 +1,429 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// See "SSPI Sample Application" at +// http://msdn.microsoft.com/en-us/library/aa918273.aspx + +#include "net/http/http_auth_sspi_win.h" + +#include "base/base64.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "net/base/net_errors.h" +#include "net/http/http_auth.h" + +namespace net { + +namespace { + +int MapAcquireCredentialsStatusToError(SECURITY_STATUS status, + const SEC_WCHAR* package) { + VLOG(1) << "AcquireCredentialsHandle returned 0x" << std::hex << status; + switch (status) { + case SEC_E_OK: + return OK; + case SEC_E_INSUFFICIENT_MEMORY: + return ERR_OUT_OF_MEMORY; + case SEC_E_INTERNAL_ERROR: + LOG(WARNING) + << "AcquireCredentialsHandle returned unexpected status 0x" + << std::hex << status; + return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; + case SEC_E_NO_CREDENTIALS: + case SEC_E_NOT_OWNER: + case SEC_E_UNKNOWN_CREDENTIALS: + return ERR_INVALID_AUTH_CREDENTIALS; + case SEC_E_SECPKG_NOT_FOUND: + // This indicates that the SSPI configuration does not match expectations + return ERR_UNSUPPORTED_AUTH_SCHEME; + default: + LOG(WARNING) + << "AcquireCredentialsHandle returned undocumented status 0x" + << std::hex << status; + return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; + } +} + +int AcquireExplicitCredentials(SSPILibrary* library, + const SEC_WCHAR* package, + const base::string16& domain, + const base::string16& user, + const base::string16& password, + CredHandle* cred) { + SEC_WINNT_AUTH_IDENTITY identity; + identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + identity.User = + reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(user.c_str())); + identity.UserLength = user.size(); + identity.Domain = + reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(domain.c_str())); + identity.DomainLength = domain.size(); + identity.Password = + reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(password.c_str())); + identity.PasswordLength = password.size(); + + TimeStamp expiry; + + // Pass the username/password to get the credentials handle. + SECURITY_STATUS status = library->AcquireCredentialsHandle( + NULL, // pszPrincipal + const_cast<SEC_WCHAR*>(package), // pszPackage + SECPKG_CRED_OUTBOUND, // fCredentialUse + NULL, // pvLogonID + &identity, // pAuthData + NULL, // pGetKeyFn (not used) + NULL, // pvGetKeyArgument (not used) + cred, // phCredential + &expiry); // ptsExpiry + + return MapAcquireCredentialsStatusToError(status, package); +} + +int AcquireDefaultCredentials(SSPILibrary* library, const SEC_WCHAR* package, + CredHandle* cred) { + TimeStamp expiry; + + // Pass the username/password to get the credentials handle. + // Note: Since the 5th argument is NULL, it uses the default + // cached credentials for the logged in user, which can be used + // for a single sign-on. + SECURITY_STATUS status = library->AcquireCredentialsHandle( + NULL, // pszPrincipal + const_cast<SEC_WCHAR*>(package), // pszPackage + SECPKG_CRED_OUTBOUND, // fCredentialUse + NULL, // pvLogonID + NULL, // pAuthData + NULL, // pGetKeyFn (not used) + NULL, // pvGetKeyArgument (not used) + cred, // phCredential + &expiry); // ptsExpiry + + return MapAcquireCredentialsStatusToError(status, package); +} + +int MapInitializeSecurityContextStatusToError(SECURITY_STATUS status) { + VLOG(1) << "InitializeSecurityContext returned 0x" << std::hex << status; + switch (status) { + case SEC_E_OK: + case SEC_I_CONTINUE_NEEDED: + return OK; + case SEC_I_COMPLETE_AND_CONTINUE: + case SEC_I_COMPLETE_NEEDED: + case SEC_I_INCOMPLETE_CREDENTIALS: + case SEC_E_INCOMPLETE_MESSAGE: + case SEC_E_INTERNAL_ERROR: + // These are return codes reported by InitializeSecurityContext + // but not expected by Chrome (for example, INCOMPLETE_CREDENTIALS + // and INCOMPLETE_MESSAGE are intended for schannel). + LOG(WARNING) + << "InitializeSecurityContext returned unexpected status 0x" + << std::hex << status; + return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS; + case SEC_E_INSUFFICIENT_MEMORY: + return ERR_OUT_OF_MEMORY; + case SEC_E_UNSUPPORTED_FUNCTION: + NOTREACHED(); + return ERR_UNEXPECTED; + case SEC_E_INVALID_HANDLE: + NOTREACHED(); + return ERR_INVALID_HANDLE; + case SEC_E_INVALID_TOKEN: + return ERR_INVALID_RESPONSE; + case SEC_E_LOGON_DENIED: + return ERR_ACCESS_DENIED; + case SEC_E_NO_CREDENTIALS: + case SEC_E_WRONG_PRINCIPAL: + return ERR_INVALID_AUTH_CREDENTIALS; + case SEC_E_NO_AUTHENTICATING_AUTHORITY: + case SEC_E_TARGET_UNKNOWN: + return ERR_MISCONFIGURED_AUTH_ENVIRONMENT; + default: + LOG(WARNING) + << "InitializeSecurityContext returned undocumented status 0x" + << std::hex << status; + return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; + } +} + +int MapQuerySecurityPackageInfoStatusToError(SECURITY_STATUS status) { + VLOG(1) << "QuerySecurityPackageInfo returned 0x" << std::hex << status; + switch (status) { + case SEC_E_OK: + return OK; + case SEC_E_SECPKG_NOT_FOUND: + // This isn't a documented return code, but has been encountered + // during testing. + return ERR_UNSUPPORTED_AUTH_SCHEME; + default: + LOG(WARNING) + << "QuerySecurityPackageInfo returned undocumented status 0x" + << std::hex << status; + return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; + } +} + +int MapFreeContextBufferStatusToError(SECURITY_STATUS status) { + VLOG(1) << "FreeContextBuffer returned 0x" << std::hex << status; + switch (status) { + case SEC_E_OK: + return OK; + default: + // The documentation at + // http://msdn.microsoft.com/en-us/library/aa375416(VS.85).aspx + // only mentions that a non-zero (or non-SEC_E_OK) value is returned + // if the function fails, and does not indicate what the failure + // conditions are. + LOG(WARNING) + << "FreeContextBuffer returned undocumented status 0x" + << std::hex << status; + return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS; + } +} + +} // anonymous namespace + +HttpAuthSSPI::HttpAuthSSPI(SSPILibrary* library, + const std::string& scheme, + const SEC_WCHAR* security_package, + ULONG max_token_length) + : library_(library), + scheme_(scheme), + security_package_(security_package), + max_token_length_(max_token_length), + can_delegate_(false) { + DCHECK(library_); + SecInvalidateHandle(&cred_); + SecInvalidateHandle(&ctxt_); +} + +HttpAuthSSPI::~HttpAuthSSPI() { + ResetSecurityContext(); + if (SecIsValidHandle(&cred_)) { + library_->FreeCredentialsHandle(&cred_); + SecInvalidateHandle(&cred_); + } +} + +bool HttpAuthSSPI::NeedsIdentity() const { + return decoded_server_auth_token_.empty(); +} + +bool HttpAuthSSPI::AllowsExplicitCredentials() const { + return true; +} + +void HttpAuthSSPI::Delegate() { + can_delegate_ = true; +} + +void HttpAuthSSPI::ResetSecurityContext() { + if (SecIsValidHandle(&ctxt_)) { + library_->DeleteSecurityContext(&ctxt_); + SecInvalidateHandle(&ctxt_); + } +} + +HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge( + HttpAuth::ChallengeTokenizer* tok) { + // Verify the challenge's auth-scheme. + if (!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str())) + return HttpAuth::AUTHORIZATION_RESULT_INVALID; + + std::string encoded_auth_token = tok->base64_param(); + if (encoded_auth_token.empty()) { + // If a context has already been established, an empty challenge + // should be treated as a rejection of the current attempt. + if (SecIsValidHandle(&ctxt_)) + return HttpAuth::AUTHORIZATION_RESULT_REJECT; + DCHECK(decoded_server_auth_token_.empty()); + return HttpAuth::AUTHORIZATION_RESULT_ACCEPT; + } else { + // If a context has not already been established, additional tokens should + // not be present in the auth challenge. + if (!SecIsValidHandle(&ctxt_)) + return HttpAuth::AUTHORIZATION_RESULT_INVALID; + } + + std::string decoded_auth_token; + bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token); + if (!base64_rv) + return HttpAuth::AUTHORIZATION_RESULT_INVALID; + decoded_server_auth_token_ = decoded_auth_token; + return HttpAuth::AUTHORIZATION_RESULT_ACCEPT; +} + +int HttpAuthSSPI::GenerateAuthToken(const AuthCredentials* credentials, + const std::wstring& spn, + std::string* auth_token) { + // Initial challenge. + if (!SecIsValidHandle(&cred_)) { + int rv = OnFirstRound(credentials); + if (rv != OK) + return rv; + } + + DCHECK(SecIsValidHandle(&cred_)); + void* out_buf; + int out_buf_len; + int rv = GetNextSecurityToken( + spn, + static_cast<void *>(const_cast<char *>( + decoded_server_auth_token_.c_str())), + decoded_server_auth_token_.length(), + &out_buf, + &out_buf_len); + if (rv != OK) + return rv; + + // Base64 encode data in output buffer and prepend the scheme. + std::string encode_input(static_cast<char*>(out_buf), out_buf_len); + std::string encode_output; + bool base64_rv = base::Base64Encode(encode_input, &encode_output); + // OK, we are done with |out_buf| + free(out_buf); + if (!base64_rv) { + LOG(ERROR) << "Base64 encoding of auth token failed."; + return ERR_ENCODING_CONVERSION_FAILED; + } + *auth_token = scheme_ + " " + encode_output; + return OK; +} + +int HttpAuthSSPI::OnFirstRound(const AuthCredentials* credentials) { + DCHECK(!SecIsValidHandle(&cred_)); + int rv = OK; + if (credentials) { + base::string16 domain; + base::string16 user; + SplitDomainAndUser(credentials->username(), &domain, &user); + rv = AcquireExplicitCredentials(library_, security_package_, domain, + user, credentials->password(), &cred_); + if (rv != OK) + return rv; + } else { + rv = AcquireDefaultCredentials(library_, security_package_, &cred_); + if (rv != OK) + return rv; + } + + return rv; +} + +int HttpAuthSSPI::GetNextSecurityToken( + const std::wstring& spn, + const void* in_token, + int in_token_len, + void** out_token, + int* out_token_len) { + CtxtHandle* ctxt_ptr; + SecBufferDesc in_buffer_desc, out_buffer_desc; + SecBufferDesc* in_buffer_desc_ptr; + SecBuffer in_buffer, out_buffer; + + if (in_token_len > 0) { + // Prepare input buffer. + in_buffer_desc.ulVersion = SECBUFFER_VERSION; + in_buffer_desc.cBuffers = 1; + in_buffer_desc.pBuffers = &in_buffer; + in_buffer.BufferType = SECBUFFER_TOKEN; + in_buffer.cbBuffer = in_token_len; + in_buffer.pvBuffer = const_cast<void*>(in_token); + ctxt_ptr = &ctxt_; + in_buffer_desc_ptr = &in_buffer_desc; + } else { + // If there is no input token, then we are starting a new authentication + // sequence. If we have already initialized our security context, then + // we're incorrectly reusing the auth handler for a new sequence. + if (SecIsValidHandle(&ctxt_)) { + NOTREACHED(); + return ERR_UNEXPECTED; + } + ctxt_ptr = NULL; + in_buffer_desc_ptr = NULL; + } + + // Prepare output buffer. + out_buffer_desc.ulVersion = SECBUFFER_VERSION; + out_buffer_desc.cBuffers = 1; + out_buffer_desc.pBuffers = &out_buffer; + out_buffer.BufferType = SECBUFFER_TOKEN; + out_buffer.cbBuffer = max_token_length_; + out_buffer.pvBuffer = malloc(out_buffer.cbBuffer); + if (!out_buffer.pvBuffer) + return ERR_OUT_OF_MEMORY; + + DWORD context_flags = 0; + // Firefox only sets ISC_REQ_DELEGATE, but MSDN documentation indicates that + // ISC_REQ_MUTUAL_AUTH must also be set. + if (can_delegate_) + context_flags |= (ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH); + + // This returns a token that is passed to the remote server. + DWORD context_attribute; + SECURITY_STATUS status = library_->InitializeSecurityContext( + &cred_, // phCredential + ctxt_ptr, // phContext + const_cast<wchar_t *>(spn.c_str()), // pszTargetName + context_flags, // fContextReq + 0, // Reserved1 (must be 0) + SECURITY_NATIVE_DREP, // TargetDataRep + in_buffer_desc_ptr, // pInput + 0, // Reserved2 (must be 0) + &ctxt_, // phNewContext + &out_buffer_desc, // pOutput + &context_attribute, // pfContextAttr + NULL); // ptsExpiry + int rv = MapInitializeSecurityContextStatusToError(status); + if (rv != OK) { + ResetSecurityContext(); + free(out_buffer.pvBuffer); + return rv; + } + if (!out_buffer.cbBuffer) { + free(out_buffer.pvBuffer); + out_buffer.pvBuffer = NULL; + } + *out_token = out_buffer.pvBuffer; + *out_token_len = out_buffer.cbBuffer; + return OK; +} + +void SplitDomainAndUser(const base::string16& combined, + base::string16* domain, + base::string16* user) { + // |combined| may be in the form "user" or "DOMAIN\user". + // Separate the two parts if they exist. + // TODO(cbentzel): I believe user@domain is also a valid form. + size_t backslash_idx = combined.find(L'\\'); + if (backslash_idx == base::string16::npos) { + domain->clear(); + *user = combined; + } else { + *domain = combined.substr(0, backslash_idx); + *user = combined.substr(backslash_idx + 1); + } +} + +int DetermineMaxTokenLength(SSPILibrary* library, + const std::wstring& package, + ULONG* max_token_length) { + DCHECK(library); + DCHECK(max_token_length); + PSecPkgInfo pkg_info = NULL; + SECURITY_STATUS status = library->QuerySecurityPackageInfo( + const_cast<wchar_t *>(package.c_str()), &pkg_info); + int rv = MapQuerySecurityPackageInfoStatusToError(status); + if (rv != OK) + return rv; + int token_length = pkg_info->cbMaxToken; + status = library->FreeContextBuffer(pkg_info); + rv = MapFreeContextBufferStatusToError(status); + if (rv != OK) + return rv; + *max_token_length = token_length; + return OK; +} + +} // namespace net |