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"
22 int MapAcquireCredentialsStatusToError(SECURITY_STATUS status
,
23 const SEC_WCHAR
* package
) {
24 VLOG(1) << "AcquireCredentialsHandle returned 0x" << std::hex
<< status
;
28 case SEC_E_INSUFFICIENT_MEMORY
:
29 return ERR_OUT_OF_MEMORY
;
30 case SEC_E_INTERNAL_ERROR
:
32 << "AcquireCredentialsHandle returned unexpected status 0x"
33 << std::hex
<< status
;
34 return ERR_UNEXPECTED_SECURITY_LIBRARY_STATUS
;
35 case SEC_E_NO_CREDENTIALS
:
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
;
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
,
56 SEC_WINNT_AUTH_IDENTITY identity
;
57 identity
.Flags
= SEC_WINNT_AUTH_IDENTITY_UNICODE
;
59 reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(user
.c_str()));
60 identity
.UserLength
= user
.size();
62 reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(domain
.c_str()));
63 identity
.DomainLength
= domain
.size();
65 reinterpret_cast<unsigned short*>(const_cast<wchar_t*>(password
.c_str()));
66 identity
.PasswordLength
= password
.size();
70 // Pass the username/password to get the credentials handle.
71 SECURITY_STATUS status
= library
->AcquireCredentialsHandle(
73 const_cast<SEC_WCHAR
*>(package
), // pszPackage
74 SECPKG_CRED_OUTBOUND
, // fCredentialUse
76 &identity
, // pAuthData
77 NULL
, // pGetKeyFn (not used)
78 NULL
, // pvGetKeyArgument (not used)
80 &expiry
); // ptsExpiry
82 return MapAcquireCredentialsStatusToError(status
, package
);
85 int AcquireDefaultCredentials(SSPILibrary
* library
, const SEC_WCHAR
* package
,
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(
95 const_cast<SEC_WCHAR
*>(package
), // pszPackage
96 SECPKG_CRED_OUTBOUND
, // fCredentialUse
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
;
111 case SEC_I_CONTINUE_NEEDED
:
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).
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
:
129 return ERR_UNEXPECTED
;
130 case SEC_E_INVALID_HANDLE
:
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
;
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
;
156 case SEC_E_SECPKG_NOT_FOUND
:
157 // This isn't a documented return code, but has been encountered
159 return ERR_UNSUPPORTED_AUTH_SCHEME
;
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
;
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
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(
191 unsigned long fCredentialUse
,
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
)
248 security_package_(security_package
),
249 max_token_length_(max_token_length
),
250 can_delegate_(false) {
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 {
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
;
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
);
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
);
323 DCHECK(SecIsValidHandle(&cred_
));
326 int rv
= GetNextSecurityToken(
328 static_cast<void *>(const_cast<char *>(
329 decoded_server_auth_token_
.c_str())),
330 decoded_server_auth_token_
.length(),
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|
342 *auth_token
= scheme_
+ " " + encode_output
;
346 int HttpAuthSSPI::OnFirstRound(const AuthCredentials
* credentials
) {
347 DCHECK(!SecIsValidHandle(&cred_
));
350 base::string16 domain
;
352 SplitDomainAndUser(credentials
->username(), &domain
, &user
);
353 rv
= AcquireExplicitCredentials(library_
, security_package_
, domain
,
354 user
, credentials
->password(), &cred_
);
358 rv
= AcquireDefaultCredentials(library_
, security_package_
, &cred_
);
366 int HttpAuthSSPI::GetNextSecurityToken(
367 const std::string
& spn
,
368 const void* in_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
);
386 in_buffer_desc_ptr
= &in_buffer_desc
;
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_
)) {
393 return ERR_UNEXPECTED
;
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.
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
431 int rv
= MapInitializeSecurityContextStatusToError(status
);
433 ResetSecurityContext();
434 free(out_buffer
.pvBuffer
);
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
;
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
) {
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
) {
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
);
473 int token_length
= pkg_info
->cbMaxToken
;
474 status
= library
->FreeContextBuffer(pkg_info
);
475 rv
= MapFreeContextBufferStatusToError(status
);
478 *max_token_length
= token_length
;