Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / net / http / http_auth_sspi_win.cc
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