1 /* vim:set ts=4 sw=4 sts=4 et cindent: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // Negotiate Authentication Support Module
9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
10 // (formerly draft-brezak-spnego-http-04.txt)
12 // Also described here:
13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
16 #include "nsAuthSSPI.h"
17 #include "nsIServiceManager.h"
18 #include "nsIDNSService.h"
19 #include "nsIDNSRecord.h"
22 #include "nsICryptoHash.h"
23 #include "mozilla/Telemetry.h"
27 #define SEC_SUCCESS(Status) ((Status) >= 0)
29 #ifndef KERB_WRAP_NO_ENCRYPT
30 #define KERB_WRAP_NO_ENCRYPT 0x80000001
33 #ifndef SECBUFFER_PADDING
34 #define SECBUFFER_PADDING 9
37 #ifndef SECBUFFER_STREAM
38 #define SECBUFFER_STREAM 10
41 //-----------------------------------------------------------------------------
43 static const wchar_t *const pTypeName
[] = {
50 #define CASE_(_x) case _x: return # _x;
51 static const char *MapErrorCode(int rc
)
55 CASE_(SEC_I_CONTINUE_NEEDED
)
56 CASE_(SEC_I_COMPLETE_NEEDED
)
57 CASE_(SEC_I_COMPLETE_AND_CONTINUE
)
58 CASE_(SEC_E_INCOMPLETE_MESSAGE
)
59 CASE_(SEC_I_INCOMPLETE_CREDENTIALS
)
60 CASE_(SEC_E_INVALID_HANDLE
)
61 CASE_(SEC_E_TARGET_UNKNOWN
)
62 CASE_(SEC_E_LOGON_DENIED
)
63 CASE_(SEC_E_INTERNAL_ERROR
)
64 CASE_(SEC_E_NO_CREDENTIALS
)
65 CASE_(SEC_E_NO_AUTHENTICATING_AUTHORITY
)
66 CASE_(SEC_E_INSUFFICIENT_MEMORY
)
67 CASE_(SEC_E_INVALID_TOKEN
)
72 #define MapErrorCode(_rc) ""
75 //-----------------------------------------------------------------------------
77 static PSecurityFunctionTableW sspi
;
84 sspi
= InitSecurityInterfaceW();
86 LOG(("InitSecurityInterfaceW failed"));
87 return NS_ERROR_UNEXPECTED
;
93 //-----------------------------------------------------------------------------
96 MakeSN(const char *principal
, nsCString
&result
)
100 nsAutoCString
buf(principal
);
102 // The service name looks like "protocol@hostname", we need to map
103 // this to a value that SSPI expects. To be consistent with IE, we
104 // need to map '@' to '/' and canonicalize the hostname.
105 int32_t index
= buf
.FindChar('@');
106 if (index
== kNotFound
)
107 return NS_ERROR_UNEXPECTED
;
109 nsCOMPtr
<nsIDNSService
> dns
= do_GetService(NS_DNSSERVICE_CONTRACTID
, &rv
);
113 // This could be expensive if our DNS cache cannot satisfy the request.
114 // However, we should have at least hit the OS resolver once prior to
115 // reaching this code, so provided the OS resolver has this information
116 // cached, we should not have to worry about blocking on this function call
117 // for very long. NOTE: because we ask for the canonical hostname, we
118 // might end up requiring extra network activity in cases where the OS
119 // resolver might not have enough information to satisfy the request from
120 // its cache. This is not an issue in versions of Windows up to WinXP.
121 nsCOMPtr
<nsIDNSRecord
> record
;
122 rv
= dns
->Resolve(Substring(buf
, index
+ 1),
123 nsIDNSService::RESOLVE_CANONICAL_NAME
,
124 getter_AddRefs(record
));
129 rv
= record
->GetCanonicalName(cname
);
130 if (NS_SUCCEEDED(rv
)) {
131 result
= StringHead(buf
, index
) + NS_LITERAL_CSTRING("/") + cname
;
132 LOG(("Using SPN of [%s]\n", result
.get()));
137 //-----------------------------------------------------------------------------
139 nsAuthSSPI::nsAuthSSPI(pType package
)
140 : mServiceFlags(REQ_DEFAULT
)
143 , mCertDERData(nullptr)
146 memset(&mCred
, 0, sizeof(mCred
));
147 memset(&mCtxt
, 0, sizeof(mCtxt
));
150 nsAuthSSPI::~nsAuthSSPI()
154 if (mCred
.dwLower
|| mCred
.dwUpper
) {
156 (sspi
->FreeCredentialsHandle
)(&mCred
);
158 (sspi
->FreeCredentialHandle
)(&mCred
);
160 memset(&mCred
, 0, sizeof(mCred
));
170 nsMemory::Free(mCertDERData
);
171 mCertDERData
= nullptr;
175 if (mCtxt
.dwLower
|| mCtxt
.dwUpper
) {
176 (sspi
->DeleteSecurityContext
)(&mCtxt
);
177 memset(&mCtxt
, 0, sizeof(mCtxt
));
181 NS_IMPL_ISUPPORTS(nsAuthSSPI
, nsIAuthModule
)
184 nsAuthSSPI::Init(const char *serviceName
,
185 uint32_t serviceFlags
,
186 const char16_t
*domain
,
187 const char16_t
*username
,
188 const char16_t
*password
)
190 LOG((" nsAuthSSPI::Init\n"));
194 mCertDERData
= nullptr;
196 // The caller must supply a service name to be used. (For why we now require
197 // a service name for NTLM, see bug 487872.)
198 NS_ENSURE_TRUE(serviceName
&& *serviceName
, NS_ERROR_INVALID_ARG
);
202 // XXX lazy initialization like this assumes that we are single threaded
210 package
= (SEC_WCHAR
*) pTypeName
[(int)mPackage
];
212 if (mPackage
== PACKAGE_TYPE_NTLM
) {
213 // (bug 535193) For NTLM, just use the uri host, do not do canonical host lookups.
214 // The incoming serviceName is in the format: "protocol@hostname", SSPI expects
215 // "<service class>/<hostname>", so swap the '@' for a '/'.
216 mServiceName
.Assign(serviceName
);
217 int32_t index
= mServiceName
.FindChar('@');
218 if (index
== kNotFound
)
219 return NS_ERROR_UNEXPECTED
;
220 mServiceName
.Replace(index
, 1, '/');
223 // Kerberos requires the canonical host, MakeSN takes care of this through a
225 rv
= MakeSN(serviceName
, mServiceName
);
230 mServiceFlags
= serviceFlags
;
235 rc
= (sspi
->QuerySecurityPackageInfoW
)(package
, &pinfo
);
236 if (rc
!= SEC_E_OK
) {
237 LOG(("%s package not found\n", package
));
238 return NS_ERROR_UNEXPECTED
;
240 mMaxTokenLen
= pinfo
->cbMaxToken
;
241 (sspi
->FreeContextBuffer
)(pinfo
);
243 MS_TimeStamp useBefore
;
245 SEC_WINNT_AUTH_IDENTITY_W ai
;
246 SEC_WINNT_AUTH_IDENTITY_W
*pai
= nullptr;
248 // domain, username, and password will be null if nsHttpNTLMAuth's ChallengeReceived
249 // returns false for identityInvalid. Use default credentials in this case by passing
251 if (username
&& password
) {
252 // Keep a copy of these strings for the duration
253 mUsername
.Assign(username
);
254 mPassword
.Assign(password
);
255 mDomain
.Assign(domain
);
256 ai
.Domain
= reinterpret_cast<unsigned short*>(mDomain
.BeginWriting());
257 ai
.DomainLength
= mDomain
.Length();
258 ai
.User
= reinterpret_cast<unsigned short*>(mUsername
.BeginWriting());
259 ai
.UserLength
= mUsername
.Length();
260 ai
.Password
= reinterpret_cast<unsigned short*>(mPassword
.BeginWriting());
261 ai
.PasswordLength
= mPassword
.Length();
262 ai
.Flags
= SEC_WINNT_AUTH_IDENTITY_UNICODE
;
266 rc
= (sspi
->AcquireCredentialsHandleW
)(nullptr,
268 SECPKG_CRED_OUTBOUND
,
276 return NS_ERROR_UNEXPECTED
;
278 static bool sTelemetrySent
= false;
279 if (!sTelemetrySent
) {
280 mozilla::Telemetry::Accumulate(
281 mozilla::Telemetry::NTLM_MODULE_USED_2
,
282 serviceFlags
& nsIAuthModule::REQ_PROXY_AUTH
283 ? NTLM_MODULE_WIN_API_PROXY
284 : NTLM_MODULE_WIN_API_DIRECT
);
285 sTelemetrySent
= true;
288 LOG(("AcquireCredentialsHandle() succeeded.\n"));
292 // The arguments inToken and inTokenLen are used to pass in the server
293 // certificate (when available) in the first call of the function. The
294 // second time these arguments hold an input token.
296 nsAuthSSPI::GetNextToken(const void *inToken
,
299 uint32_t *outTokenLen
)
301 // String for end-point bindings.
302 const char end_point
[] = "tls-server-end-point:";
303 const int end_point_length
= sizeof(end_point
) - 1;
304 const int hash_size
= 32; // Size of a SHA256 hash.
305 const int cbt_size
= hash_size
+ end_point_length
;
308 MS_TimeStamp ignored
;
310 DWORD ctxAttr
, ctxReq
= 0;
312 SecBufferDesc ibd
, obd
;
313 // Optional second input buffer for the CBT (Channel Binding Token)
315 // Pointer to the block of memory that stores the CBT
316 char* sspi_cbt
= nullptr;
317 SEC_CHANNEL_BINDINGS pendpoint_binding
;
319 LOG(("entering nsAuthSSPI::GetNextToken()\n"));
321 if (!mCred
.dwLower
&& !mCred
.dwUpper
) {
322 LOG(("nsAuthSSPI::GetNextToken(), not initialized. exiting."));
323 return NS_ERROR_NOT_INITIALIZED
;
326 if (mServiceFlags
& REQ_DELEGATE
)
327 ctxReq
|= ISC_REQ_DELEGATE
;
328 if (mServiceFlags
& REQ_MUTUAL_AUTH
)
329 ctxReq
|= ISC_REQ_MUTUAL_AUTH
;
333 // First time if it comes with a token,
334 // the token represents the server certificate.
336 mCertDERLength
= inTokenLen
;
337 mCertDERData
= nsMemory::Alloc(inTokenLen
);
339 return NS_ERROR_OUT_OF_MEMORY
;
340 memcpy(mCertDERData
, inToken
, inTokenLen
);
342 // We are starting a new authentication sequence.
343 // If we have already initialized our
344 // security context, then we're in trouble because it means that the
345 // first sequence failed. We need to bail or else we might end up in
347 if (mCtxt
.dwLower
|| mCtxt
.dwUpper
) {
348 LOG(("Cannot restart authentication sequence!"));
349 return NS_ERROR_UNEXPECTED
;
352 // The certificate needs to be erased before being passed
353 // to InitializeSecurityContextW().
357 ibd
.ulVersion
= SECBUFFER_VERSION
;
361 // If we have stored a certificate, the Channel Binding Token
362 // needs to be generated and sent in the first input buffer.
363 if (mCertDERLength
> 0) {
364 // First we create a proper Endpoint Binding structure.
365 pendpoint_binding
.dwInitiatorAddrType
= 0;
366 pendpoint_binding
.cbInitiatorLength
= 0;
367 pendpoint_binding
.dwInitiatorOffset
= 0;
368 pendpoint_binding
.dwAcceptorAddrType
= 0;
369 pendpoint_binding
.cbAcceptorLength
= 0;
370 pendpoint_binding
.dwAcceptorOffset
= 0;
371 pendpoint_binding
.cbApplicationDataLength
= cbt_size
;
372 pendpoint_binding
.dwApplicationDataOffset
=
373 sizeof(SEC_CHANNEL_BINDINGS
);
375 // Then add it to the array of sec buffers accordingly.
376 ib
[ibd
.cBuffers
].BufferType
= SECBUFFER_CHANNEL_BINDINGS
;
377 ib
[ibd
.cBuffers
].cbBuffer
=
378 pendpoint_binding
.cbApplicationDataLength
379 + pendpoint_binding
.dwApplicationDataOffset
;
381 sspi_cbt
= (char *) nsMemory::Alloc(ib
[ibd
.cBuffers
].cbBuffer
);
383 return NS_ERROR_OUT_OF_MEMORY
;
386 // Helper to write in the memory block that stores the CBT
387 char* sspi_cbt_ptr
= sspi_cbt
;
389 ib
[ibd
.cBuffers
].pvBuffer
= sspi_cbt
;
392 memcpy(sspi_cbt_ptr
, &pendpoint_binding
,
393 pendpoint_binding
.dwApplicationDataOffset
);
394 sspi_cbt_ptr
+= pendpoint_binding
.dwApplicationDataOffset
;
396 memcpy(sspi_cbt_ptr
, end_point
, end_point_length
);
397 sspi_cbt_ptr
+= end_point_length
;
399 // Start hashing. We are always doing SHA256, but depending
400 // on the certificate, a different alogirthm might be needed.
401 nsAutoCString hashString
;
404 nsCOMPtr
<nsICryptoHash
> crypto
;
405 crypto
= do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID
, &rv
);
406 if (NS_SUCCEEDED(rv
))
407 rv
= crypto
->Init(nsICryptoHash::SHA256
);
408 if (NS_SUCCEEDED(rv
))
409 rv
= crypto
->Update((unsigned char*)mCertDERData
, mCertDERLength
);
410 if (NS_SUCCEEDED(rv
))
411 rv
= crypto
->Finish(false, hashString
);
413 nsMemory::Free(mCertDERData
);
414 mCertDERData
= nullptr;
416 nsMemory::Free(sspi_cbt
);
420 // Once the hash has been computed, we store it in memory right
421 // after the Endpoint structure and the "tls-server-end-point:"
423 memcpy(sspi_cbt_ptr
, hashString
.get(), hash_size
);
425 // Free memory used to store the server certificate
426 nsMemory::Free(mCertDERData
);
427 mCertDERData
= nullptr;
429 } // End of CBT computation.
431 // We always need this SECBUFFER.
432 ib
[ibd
.cBuffers
].BufferType
= SECBUFFER_TOKEN
;
433 ib
[ibd
.cBuffers
].cbBuffer
= inTokenLen
;
434 ib
[ibd
.cBuffers
].pvBuffer
= (void *) inToken
;
438 } else { // First time and without a token (no server certificate)
439 // We are starting a new authentication sequence. If we have already
440 // initialized our security context, then we're in trouble because it
441 // means that the first sequence failed. We need to bail or else we
442 // might end up in an infinite loop.
443 if (mCtxt
.dwLower
|| mCtxt
.dwUpper
|| mCertDERData
|| mCertDERLength
) {
444 LOG(("Cannot restart authentication sequence!"));
445 return NS_ERROR_UNEXPECTED
;
451 obd
.ulVersion
= SECBUFFER_VERSION
;
454 ob
.BufferType
= SECBUFFER_TOKEN
;
455 ob
.cbBuffer
= mMaxTokenLen
;
456 ob
.pvBuffer
= nsMemory::Alloc(ob
.cbBuffer
);
459 nsMemory::Free(sspi_cbt
);
460 return NS_ERROR_OUT_OF_MEMORY
;
462 memset(ob
.pvBuffer
, 0, ob
.cbBuffer
);
464 NS_ConvertUTF8toUTF16
wSN(mServiceName
);
465 SEC_WCHAR
*sn
= (SEC_WCHAR
*) wSN
.get();
467 rc
= (sspi
->InitializeSecurityContextW
)(&mCred
,
472 SECURITY_NATIVE_DREP
,
473 inToken
? &ibd
: nullptr,
479 if (rc
== SEC_I_CONTINUE_NEEDED
|| rc
== SEC_E_OK
) {
483 LOG(("InitializeSecurityContext: succeeded.\n"));
485 LOG(("InitializeSecurityContext: continue.\n"));
488 nsMemory::Free(sspi_cbt
);
491 nsMemory::Free(ob
.pvBuffer
);
492 ob
.pvBuffer
= nullptr;
494 *outToken
= ob
.pvBuffer
;
495 *outTokenLen
= ob
.cbBuffer
;
498 return NS_SUCCESS_AUTH_FINISHED
;
503 LOG(("InitializeSecurityContext failed [rc=%d:%s]\n", rc
, MapErrorCode(rc
)));
505 nsMemory::Free(ob
.pvBuffer
);
506 return NS_ERROR_FAILURE
;
510 nsAuthSSPI::Unwrap(const void *inToken
,
513 uint32_t *outTokenLen
)
521 ibd
.ulVersion
= SECBUFFER_VERSION
;
524 ib
[0].BufferType
= SECBUFFER_STREAM
;
525 ib
[0].cbBuffer
= inTokenLen
;
526 ib
[0].pvBuffer
= nsMemory::Alloc(ib
[0].cbBuffer
);
528 return NS_ERROR_OUT_OF_MEMORY
;
530 memcpy(ib
[0].pvBuffer
, inToken
, inTokenLen
);
533 ib
[1].BufferType
= SECBUFFER_DATA
;
535 ib
[1].pvBuffer
= nullptr;
537 rc
= (sspi
->DecryptMessage
)(
540 0, // no sequence numbers
544 if (SEC_SUCCESS(rc
)) {
545 // check if ib[1].pvBuffer is really just ib[0].pvBuffer, in which
546 // case we can let the caller free it. Otherwise, we need to
547 // clone it, and free the original
548 if (ib
[0].pvBuffer
== ib
[1].pvBuffer
) {
549 *outToken
= ib
[1].pvBuffer
;
552 *outToken
= nsMemory::Clone(ib
[1].pvBuffer
, ib
[1].cbBuffer
);
553 nsMemory::Free(ib
[0].pvBuffer
);
555 return NS_ERROR_OUT_OF_MEMORY
;
557 *outTokenLen
= ib
[1].cbBuffer
;
560 nsMemory::Free(ib
[0].pvBuffer
);
562 if (!SEC_SUCCESS(rc
))
563 return NS_ERROR_FAILURE
;
568 // utility class used to free memory on exit
575 secBuffers() { memset(&ib
, 0, sizeof(ib
)); }
580 nsMemory::Free(ib
[0].pvBuffer
);
583 nsMemory::Free(ib
[1].pvBuffer
);
586 nsMemory::Free(ib
[2].pvBuffer
);
591 nsAuthSSPI::Wrap(const void *inToken
,
595 uint32_t *outTokenLen
)
601 SecPkgContext_Sizes sizes
;
603 rc
= (sspi
->QueryContextAttributesW
)(
608 if (!SEC_SUCCESS(rc
))
609 return NS_ERROR_FAILURE
;
612 ibd
.pBuffers
= bufs
.ib
;
613 ibd
.ulVersion
= SECBUFFER_VERSION
;
616 bufs
.ib
[0].cbBuffer
= sizes
.cbSecurityTrailer
;
617 bufs
.ib
[0].BufferType
= SECBUFFER_TOKEN
;
618 bufs
.ib
[0].pvBuffer
= nsMemory::Alloc(sizes
.cbSecurityTrailer
);
620 if (!bufs
.ib
[0].pvBuffer
)
621 return NS_ERROR_OUT_OF_MEMORY
;
624 bufs
.ib
[1].BufferType
= SECBUFFER_DATA
;
625 bufs
.ib
[1].pvBuffer
= nsMemory::Alloc(inTokenLen
);
626 bufs
.ib
[1].cbBuffer
= inTokenLen
;
628 if (!bufs
.ib
[1].pvBuffer
)
629 return NS_ERROR_OUT_OF_MEMORY
;
631 memcpy(bufs
.ib
[1].pvBuffer
, inToken
, inTokenLen
);
634 bufs
.ib
[2].BufferType
= SECBUFFER_PADDING
;
635 bufs
.ib
[2].cbBuffer
= sizes
.cbBlockSize
;
636 bufs
.ib
[2].pvBuffer
= nsMemory::Alloc(bufs
.ib
[2].cbBuffer
);
638 if (!bufs
.ib
[2].pvBuffer
)
639 return NS_ERROR_OUT_OF_MEMORY
;
641 rc
= (sspi
->EncryptMessage
)(&mCtxt
,
642 confidential
? 0 : KERB_WRAP_NO_ENCRYPT
,
645 if (SEC_SUCCESS(rc
)) {
646 int len
= bufs
.ib
[0].cbBuffer
+ bufs
.ib
[1].cbBuffer
+ bufs
.ib
[2].cbBuffer
;
647 char *p
= (char *) nsMemory::Alloc(len
);
650 return NS_ERROR_OUT_OF_MEMORY
;
652 *outToken
= (void *) p
;
655 memcpy(p
, bufs
.ib
[0].pvBuffer
, bufs
.ib
[0].cbBuffer
);
656 p
+= bufs
.ib
[0].cbBuffer
;
658 memcpy(p
,bufs
.ib
[1].pvBuffer
, bufs
.ib
[1].cbBuffer
);
659 p
+= bufs
.ib
[1].cbBuffer
;
661 memcpy(p
,bufs
.ib
[2].pvBuffer
, bufs
.ib
[2].cbBuffer
);
666 return NS_ERROR_FAILURE
;