• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 namespace net {
18 
19 namespace {
20 
MapAcquireCredentialsStatusToError(SECURITY_STATUS status,const SEC_WCHAR * package)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 
AcquireExplicitCredentials(SSPILibrary * library,const SEC_WCHAR * package,const string16 & domain,const string16 & user,const string16 & password,CredHandle * cred)49 int 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 
AcquireDefaultCredentials(SSPILibrary * library,const SEC_WCHAR * package,CredHandle * cred)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 
MapInitializeSecurityContextStatusToError(SECURITY_STATUS status)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 
MapQuerySecurityPackageInfoStatusToError(SECURITY_STATUS status)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 
MapFreeContextBufferStatusToError(SECURITY_STATUS status)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 
HttpAuthSSPI(SSPILibrary * library,const std::string & scheme,SEC_WCHAR * security_package,ULONG max_token_length)187 HttpAuthSSPI::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 
~HttpAuthSSPI()201 HttpAuthSSPI::~HttpAuthSSPI() {
202   ResetSecurityContext();
203   if (SecIsValidHandle(&cred_)) {
204     library_->FreeCredentialsHandle(&cred_);
205     SecInvalidateHandle(&cred_);
206   }
207 }
208 
NeedsIdentity() const209 bool HttpAuthSSPI::NeedsIdentity() const {
210   return decoded_server_auth_token_.empty();
211 }
212 
Delegate()213 void HttpAuthSSPI::Delegate() {
214   can_delegate_ = true;
215 }
216 
ResetSecurityContext()217 void HttpAuthSSPI::ResetSecurityContext() {
218   if (SecIsValidHandle(&ctxt_)) {
219     library_->DeleteSecurityContext(&ctxt_);
220     SecInvalidateHandle(&ctxt_);
221   }
222 }
223 
ParseChallenge(HttpAuth::ChallengeTokenizer * tok)224 HttpAuth::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 
GenerateAuthToken(const string16 * username,const string16 * password,const std::wstring & spn,std::string * auth_token)253 int 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 
OnFirstRound(const string16 * username,const string16 * password)293 int 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 
GetNextSecurityToken(const std::wstring & spn,const void * in_token,int in_token_len,void ** out_token,int * out_token_len)315 int 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 
SplitDomainAndUser(const string16 & combined,string16 * domain,string16 * user)394 void 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 
DetermineMaxTokenLength(SSPILibrary * library,const std::wstring & package,ULONG * max_token_length)410 int 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