1// Copyright (c) 2010 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/string_util.h"
13#include "base/utf_string_conversions.h"
14#include "net/base/net_errors.h"
15#include "net/http/http_auth.h"
16
17namespace net {
18
19namespace {
20
21int 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
49int AcquireExplicitCredentials(SSPILibrary* library,
50                               const SEC_WCHAR* package,
51                               const string16& domain,
52                               const string16& user,
53                               const 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
84int 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
106int 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
150int 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
167int 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
187HttpAuthSSPI::HttpAuthSSPI(SSPILibrary* library,
188                           const std::string& scheme,
189                           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
201HttpAuthSSPI::~HttpAuthSSPI() {
202  ResetSecurityContext();
203  if (SecIsValidHandle(&cred_)) {
204    library_->FreeCredentialsHandle(&cred_);
205    SecInvalidateHandle(&cred_);
206  }
207}
208
209bool HttpAuthSSPI::NeedsIdentity() const {
210  return decoded_server_auth_token_.empty();
211}
212
213void HttpAuthSSPI::Delegate() {
214  can_delegate_ = true;
215}
216
217void HttpAuthSSPI::ResetSecurityContext() {
218  if (SecIsValidHandle(&ctxt_)) {
219    library_->DeleteSecurityContext(&ctxt_);
220    SecInvalidateHandle(&ctxt_);
221  }
222}
223
224HttpAuth::AuthorizationResult HttpAuthSSPI::ParseChallenge(
225    HttpAuth::ChallengeTokenizer* tok) {
226  // Verify the challenge's auth-scheme.
227  if (!LowerCaseEqualsASCII(tok->scheme(), StringToLowerASCII(scheme_).c_str()))
228    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
229
230  std::string encoded_auth_token = tok->base64_param();
231  if (encoded_auth_token.empty()) {
232    // If a context has already been established, an empty challenge
233    // should be treated as a rejection of the current attempt.
234    if (SecIsValidHandle(&ctxt_))
235      return HttpAuth::AUTHORIZATION_RESULT_REJECT;
236    DCHECK(decoded_server_auth_token_.empty());
237    return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
238  } else {
239    // If a context has not already been established, additional tokens should
240    // not be present in the auth challenge.
241    if (!SecIsValidHandle(&ctxt_))
242      return HttpAuth::AUTHORIZATION_RESULT_INVALID;
243  }
244
245  std::string decoded_auth_token;
246  bool base64_rv = base::Base64Decode(encoded_auth_token, &decoded_auth_token);
247  if (!base64_rv)
248    return HttpAuth::AUTHORIZATION_RESULT_INVALID;
249  decoded_server_auth_token_ = decoded_auth_token;
250  return HttpAuth::AUTHORIZATION_RESULT_ACCEPT;
251}
252
253int HttpAuthSSPI::GenerateAuthToken(const string16* username,
254                                    const string16* password,
255                                    const std::wstring& spn,
256                                    std::string* auth_token) {
257  DCHECK((username == NULL) == (password == NULL));
258
259  // Initial challenge.
260  if (!SecIsValidHandle(&cred_)) {
261    int rv = OnFirstRound(username, password);
262    if (rv != OK)
263      return rv;
264  }
265
266  DCHECK(SecIsValidHandle(&cred_));
267  void* out_buf;
268  int out_buf_len;
269  int rv = GetNextSecurityToken(
270      spn,
271      static_cast<void *>(const_cast<char *>(
272          decoded_server_auth_token_.c_str())),
273      decoded_server_auth_token_.length(),
274      &out_buf,
275      &out_buf_len);
276  if (rv != OK)
277    return rv;
278
279  // Base64 encode data in output buffer and prepend the scheme.
280  std::string encode_input(static_cast<char*>(out_buf), out_buf_len);
281  std::string encode_output;
282  bool base64_rv = base::Base64Encode(encode_input, &encode_output);
283  // OK, we are done with |out_buf|
284  free(out_buf);
285  if (!base64_rv) {
286    LOG(ERROR) << "Base64 encoding of auth token failed.";
287    return ERR_ENCODING_CONVERSION_FAILED;
288  }
289  *auth_token = scheme_ + " " + encode_output;
290  return OK;
291}
292
293int HttpAuthSSPI::OnFirstRound(const string16* username,
294                               const string16* password) {
295  DCHECK((username == NULL) == (password == NULL));
296  DCHECK(!SecIsValidHandle(&cred_));
297  int rv = OK;
298  if (username) {
299    string16 domain;
300    string16 user;
301    SplitDomainAndUser(*username, &domain, &user);
302    rv = AcquireExplicitCredentials(library_, security_package_, domain,
303                                    user, *password, &cred_);
304    if (rv != OK)
305      return rv;
306  } else {
307    rv = AcquireDefaultCredentials(library_, security_package_, &cred_);
308    if (rv != OK)
309      return rv;
310  }
311
312  return rv;
313}
314
315int HttpAuthSSPI::GetNextSecurityToken(
316    const std::wstring& spn,
317    const void* in_token,
318    int in_token_len,
319    void** out_token,
320    int* out_token_len) {
321  CtxtHandle* ctxt_ptr;
322  SecBufferDesc in_buffer_desc, out_buffer_desc;
323  SecBufferDesc* in_buffer_desc_ptr;
324  SecBuffer in_buffer, out_buffer;
325
326  if (in_token_len > 0) {
327    // Prepare input buffer.
328    in_buffer_desc.ulVersion = SECBUFFER_VERSION;
329    in_buffer_desc.cBuffers = 1;
330    in_buffer_desc.pBuffers = &in_buffer;
331    in_buffer.BufferType = SECBUFFER_TOKEN;
332    in_buffer.cbBuffer = in_token_len;
333    in_buffer.pvBuffer = const_cast<void*>(in_token);
334    ctxt_ptr = &ctxt_;
335    in_buffer_desc_ptr = &in_buffer_desc;
336  } else {
337    // If there is no input token, then we are starting a new authentication
338    // sequence.  If we have already initialized our security context, then
339    // we're incorrectly reusing the auth handler for a new sequence.
340    if (SecIsValidHandle(&ctxt_)) {
341      NOTREACHED();
342      return ERR_UNEXPECTED;
343    }
344    ctxt_ptr = NULL;
345    in_buffer_desc_ptr = NULL;
346  }
347
348  // Prepare output buffer.
349  out_buffer_desc.ulVersion = SECBUFFER_VERSION;
350  out_buffer_desc.cBuffers = 1;
351  out_buffer_desc.pBuffers = &out_buffer;
352  out_buffer.BufferType = SECBUFFER_TOKEN;
353  out_buffer.cbBuffer = max_token_length_;
354  out_buffer.pvBuffer = malloc(out_buffer.cbBuffer);
355  if (!out_buffer.pvBuffer)
356    return ERR_OUT_OF_MEMORY;
357
358  DWORD context_flags = 0;
359  // Firefox only sets ISC_REQ_DELEGATE, but MSDN documentation indicates that
360  // ISC_REQ_MUTUAL_AUTH must also be set.
361  if (can_delegate_)
362    context_flags |= (ISC_REQ_DELEGATE | ISC_REQ_MUTUAL_AUTH);
363
364  // This returns a token that is passed to the remote server.
365  DWORD context_attribute;
366  SECURITY_STATUS status = library_->InitializeSecurityContext(
367      &cred_,  // phCredential
368      ctxt_ptr,  // phContext
369      const_cast<wchar_t *>(spn.c_str()),  // pszTargetName
370      context_flags,  // fContextReq
371      0,  // Reserved1 (must be 0)
372      SECURITY_NATIVE_DREP,  // TargetDataRep
373      in_buffer_desc_ptr,  // pInput
374      0,  // Reserved2 (must be 0)
375      &ctxt_,  // phNewContext
376      &out_buffer_desc,  // pOutput
377      &context_attribute,  // pfContextAttr
378      NULL);  // ptsExpiry
379  int rv = MapInitializeSecurityContextStatusToError(status);
380  if (rv != OK) {
381    ResetSecurityContext();
382    free(out_buffer.pvBuffer);
383    return rv;
384  }
385  if (!out_buffer.cbBuffer) {
386    free(out_buffer.pvBuffer);
387    out_buffer.pvBuffer = NULL;
388  }
389  *out_token = out_buffer.pvBuffer;
390  *out_token_len = out_buffer.cbBuffer;
391  return OK;
392}
393
394void SplitDomainAndUser(const string16& combined,
395                        string16* domain,
396                        string16* user) {
397  // |combined| may be in the form "user" or "DOMAIN\user".
398  // Separate the two parts if they exist.
399  // TODO(cbentzel): I believe user@domain is also a valid form.
400  size_t backslash_idx = combined.find(L'\\');
401  if (backslash_idx == string16::npos) {
402    domain->clear();
403    *user = combined;
404  } else {
405    *domain = combined.substr(0, backslash_idx);
406    *user = combined.substr(backslash_idx + 1);
407  }
408}
409
410int DetermineMaxTokenLength(SSPILibrary* library,
411                            const std::wstring& package,
412                            ULONG* max_token_length) {
413  DCHECK(library);
414  DCHECK(max_token_length);
415  PSecPkgInfo pkg_info = NULL;
416  SECURITY_STATUS status = library->QuerySecurityPackageInfo(
417      const_cast<wchar_t *>(package.c_str()), &pkg_info);
418  int rv = MapQuerySecurityPackageInfoStatusToError(status);
419  if (rv != OK)
420    return rv;
421  int token_length = pkg_info->cbMaxToken;
422  status = library->FreeContextBuffer(pkg_info);
423  rv = MapFreeContextBufferStatusToError(status);
424  if (rv != OK)
425    return rv;
426  *max_token_length = token_length;
427  return OK;
428}
429
430}  // namespace net
431