Move LowerCaseEqualsASCII to base namespace
[chromium-blink-merge.git] / net / http / http_auth_sspi_win.cc
blobc935d33a7c2e8b570a6305a6fb750f4180cc4226
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.
5 // See "SSPI Sample Application" at
6 // http://msdn.microsoft.com/en-us/library/aa918273.aspx
8 #include "net/http/http_auth_sspi_win.h"
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"
18 namespace net {
20 namespace {
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;
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();
68 TimeStamp expiry;
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
82 return MapAcquireCredentialsStatusToError(status, package);
85 int AcquireDefaultCredentials(SSPILibrary* library, const SEC_WCHAR* package,
86 CredHandle* cred) {
87 TimeStamp expiry;
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
104 return MapAcquireCredentialsStatusToError(status, package);
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;
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;
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;
186 } // anonymous namespace
188 SECURITY_STATUS SSPILibraryDefault::AcquireCredentialsHandle(
189 LPWSTR pszPrincipal,
190 LPWSTR pszPackage,
191 unsigned long fCredentialUse,
192 void* pvLogonId,
193 void* pvAuthData,
194 SEC_GET_KEY_FN pGetKeyFn,
195 void* pvGetKeyArgument,
196 PCredHandle phCredential,
197 PTimeStamp ptsExpiry) {
198 return ::AcquireCredentialsHandle(pszPrincipal, pszPackage, fCredentialUse,
199 pvLogonId, pvAuthData, pGetKeyFn,
200 pvGetKeyArgument, phCredential, ptsExpiry);
203 SECURITY_STATUS SSPILibraryDefault::InitializeSecurityContext(
204 PCredHandle phCredential,
205 PCtxtHandle phContext,
206 SEC_WCHAR* pszTargetName,
207 unsigned long fContextReq,
208 unsigned long Reserved1,
209 unsigned long TargetDataRep,
210 PSecBufferDesc pInput,
211 unsigned long Reserved2,
212 PCtxtHandle phNewContext,
213 PSecBufferDesc pOutput,
214 unsigned long* contextAttr,
215 PTimeStamp ptsExpiry) {
216 return ::InitializeSecurityContext(phCredential, phContext, pszTargetName,
217 fContextReq, Reserved1, TargetDataRep,
218 pInput, Reserved2, phNewContext, pOutput,
219 contextAttr, ptsExpiry);
222 SECURITY_STATUS SSPILibraryDefault::QuerySecurityPackageInfo(
223 LPWSTR pszPackageName,
224 PSecPkgInfoW* pkgInfo) {
225 return ::QuerySecurityPackageInfo(pszPackageName, pkgInfo);
228 SECURITY_STATUS SSPILibraryDefault::FreeCredentialsHandle(
229 PCredHandle phCredential) {
230 return ::FreeCredentialsHandle(phCredential);
233 SECURITY_STATUS SSPILibraryDefault::DeleteSecurityContext(
234 PCtxtHandle phContext) {
235 return ::DeleteSecurityContext(phContext);
238 SECURITY_STATUS SSPILibraryDefault::FreeContextBuffer(PVOID pvContextBuffer) {
239 return ::FreeContextBuffer(pvContextBuffer);
242 HttpAuthSSPI::HttpAuthSSPI(SSPILibrary* library,
243 const std::string& scheme,
244 const SEC_WCHAR* security_package,
245 ULONG max_token_length)
246 : library_(library),
247 scheme_(scheme),
248 security_package_(security_package),
249 max_token_length_(max_token_length),
250 can_delegate_(false) {
251 DCHECK(library_);
252 SecInvalidateHandle(&cred_);
253 SecInvalidateHandle(&ctxt_);
256 HttpAuthSSPI::~HttpAuthSSPI() {
257 ResetSecurityContext();
258 if (SecIsValidHandle(&cred_)) {
259 library_->FreeCredentialsHandle(&cred_);
260 SecInvalidateHandle(&cred_);
264 bool HttpAuthSSPI::NeedsIdentity() const {
265 return decoded_server_auth_token_.empty();
268 bool HttpAuthSSPI::AllowsExplicitCredentials() const {
269 return true;
272 void HttpAuthSSPI::Delegate() {
273 can_delegate_ = true;
276 void HttpAuthSSPI::ResetSecurityContext() {
277 if (SecIsValidHandle(&ctxt_)) {
278 library_->DeleteSecurityContext(&ctxt_);
279 SecInvalidateHandle(&ctxt_);
283 HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
284 HttpAuthChallengeTokenizer* tok) {
285 // Verify the challenge's auth-scheme.
286 if (!base::LowerCaseEqualsASCII(tok->scheme(),
287 base::StringToLowerASCII(scheme_).c_str()))
288 return HttpAuth::AUTHORIZATION_RESULT_INVALID;
290 std::string encoded_auth_token = tok->base64_param();
291 if (encoded_auth_token.empty()) {
292 // If a context has already been established, an empty challenge
293 // should be treated as a rejection of the current attempt.
294 if (SecIsValidHandle(&ctxt_))
295 return HttpAuth::AUTHORIZATION_RESULT_REJECT;
296 DCHECK(decoded_server_auth_token_.empty());
297 return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
298 } else {
299 // If a context has not already been established, additional tokens should
300 // not be present in the auth challenge.
301 if (!SecIsValidHandle(&ctxt_))
302 return HttpAuth::AUTHORIZATION_RESULT_INVALID;
305 std::string decoded_auth_token;
306 bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
307 if (!base64_rv)
308 return HttpAuth::AUTHORIZATION_RESULT_INVALID;
309 decoded_server_auth_token_ = decoded_auth_token;
310 return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
313 int HttpAuthSSPI::GenerateAuthToken(const AuthCredentials* credentials,
314 const std::string& spn,
315 std::string* auth_token) {
316 // Initial challenge.
317 if (!SecIsValidHandle(&cred_)) {
318 int rv = OnFirstRound(credentials);
319 if (rv != OK)
320 return rv;
323 DCHECK(SecIsValidHandle(&cred_));
324 void* out_buf;
325 int out_buf_len;
326 int rv = GetNextSecurityToken(
327 spn,
328 static_cast<void *>(const_cast<char *>(
329 decoded_server_auth_token_.c_str())),
330 decoded_server_auth_token_.length(),
331 &out_buf,
332 &out_buf_len);
333 if (rv != OK)
334 return rv;
336 // Base64 encode data in output buffer and prepend the scheme.
337 std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
338 std::string encode_output;
339 base::Base64Encode(encode_input, &encode_output);
340 // OK, we are done with |out_buf|
341 free(out_buf);
342 *auth_token = scheme_ + " " + encode_output;
343 return OK;
346 int HttpAuthSSPI::OnFirstRound(const AuthCredentials* credentials) {
347 DCHECK(!SecIsValidHandle(&cred_));
348 int rv = OK;
349 if (credentials) {
350 base::string16 domain;
351 base::string16 user;
352 SplitDomainAndUser(credentials->username(), &domain, &user);
353 rv = AcquireExplicitCredentials(library_, security_package_, domain,
354 user, credentials->password(), &cred_);
355 if (rv != OK)
356 return rv;
357 } else {
358 rv = AcquireDefaultCredentials(library_, security_package_, &cred_);
359 if (rv != OK)
360 return rv;
363 return rv;
366 int HttpAuthSSPI::GetNextSecurityToken(
367 const std::string& spn,
368 const void* in_token,
369 int in_token_len,
370 void** out_token,
371 int* out_token_len) {
372 CtxtHandle* ctxt_ptr;
373 SecBufferDesc in_buffer_desc, out_buffer_desc;
374 SecBufferDesc* in_buffer_desc_ptr;
375 SecBuffer in_buffer, out_buffer;
377 if (in_token_len > 0) {
378 // Prepare input buffer.
379 in_buffer_desc.ulVersion = SECBUFFER_VERSION;
380 in_buffer_desc.cBuffers = 1;
381 in_buffer_desc.pBuffers = &in_buffer;
382 in_buffer.BufferType = SECBUFFER_TOKEN;
383 in_buffer.cbBuffer = in_token_len;
384 in_buffer.pvBuffer = const_cast<void*>(in_token);
385 ctxt_ptr = &ctxt_;
386 in_buffer_desc_ptr = &in_buffer_desc;
387 } else {
388 // If there is no input token, then we are starting a new authentication
389 // sequence. If we have already initialized our security context, then
390 // we're incorrectly reusing the auth handler for a new sequence.
391 if (SecIsValidHandle(&ctxt_)) {
392 NOTREACHED();
393 return ERR_UNEXPECTED;
395 ctxt_ptr = NULL;
396 in_buffer_desc_ptr = NULL;
399 // Prepare output buffer.
400 out_buffer_desc.ulVersion = SECBUFFER_VERSION;
401 out_buffer_desc.cBuffers = 1;
402 out_buffer_desc.pBuffers = &out_buffer;
403 out_buffer.BufferType = SECBUFFER_TOKEN;
404 out_buffer.cbBuffer = max_token_length_;
405 out_buffer.pvBuffer = malloc(out_buffer.cbBuffer);
406 if (!out_buffer.pvBuffer)
407 return ERR_OUT_OF_MEMORY;
409 DWORD context_flags = 0;
410 // Firefox only sets ISC_REQ_DELEGATE, but MSDN documentation indicates that
411 // ISC_REQ_MUTUAL_AUTH must also be set.
412 if (can_delegate_)
413 context_flags |= (ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH);
415 // This returns a token that is passed to the remote server.
416 DWORD context_attribute;
417 base::string16 spn16 = base::ASCIIToUTF16(spn);
418 SECURITY_STATUS status = library_->InitializeSecurityContext(
419 &cred_, // phCredential
420 ctxt_ptr, // phContext
421 const_cast<base::char16*>(spn16.c_str()), // pszTargetName
422 context_flags, // fContextReq
423 0, // Reserved1 (must be 0)
424 SECURITY_NATIVE_DREP, // TargetDataRep
425 in_buffer_desc_ptr, // pInput
426 0, // Reserved2 (must be 0)
427 &ctxt_, // phNewContext
428 &out_buffer_desc, // pOutput
429 &context_attribute, // pfContextAttr
430 NULL); // ptsExpiry
431 int rv = MapInitializeSecurityContextStatusToError(status);
432 if (rv != OK) {
433 ResetSecurityContext();
434 free(out_buffer.pvBuffer);
435 return rv;
437 if (!out_buffer.cbBuffer) {
438 free(out_buffer.pvBuffer);
439 out_buffer.pvBuffer = NULL;
441 *out_token = out_buffer.pvBuffer;
442 *out_token_len = out_buffer.cbBuffer;
443 return OK;
446 void SplitDomainAndUser(const base::string16& combined,
447 base::string16* domain,
448 base::string16* user) {
449 // |combined| may be in the form "user" or "DOMAIN\user".
450 // Separate the two parts if they exist.
451 // TODO(cbentzel): I believe user@domain is also a valid form.
452 size_t backslash_idx = combined.find(L'\\');
453 if (backslash_idx == base::string16::npos) {
454 domain->clear();
455 *user = combined;
456 } else {
457 *domain = combined.substr(0, backslash_idx);
458 *user = combined.substr(backslash_idx + 1);
462 int DetermineMaxTokenLength(SSPILibrary* library,
463 const std::wstring& package,
464 ULONG* max_token_length) {
465 DCHECK(library);
466 DCHECK(max_token_length);
467 PSecPkgInfo pkg_info = NULL;
468 SECURITY_STATUS status = library->QuerySecurityPackageInfo(
469 const_cast<wchar_t *>(package.c_str()), &pkg_info);
470 int rv = MapQuerySecurityPackageInfoStatusToError(status);
471 if (rv != OK)
472 return rv;
473 int token_length = pkg_info->cbMaxToken;
474 status = library->FreeContextBuffer(pkg_info);
475 rv = MapFreeContextBufferStatusToError(status);
476 if (rv != OK)
477 return rv;
478 *max_token_length = token_length;
479 return OK;
482 } // namespace net