1 /* vim:set ts=4 sw=2 sts=2 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 "nsComponentManagerUtils.h"
18 #include "nsDNSService2.h"
19 #include "nsIDNSService.h"
20 #include "nsIDNSRecord.h"
22 #include "nsServiceManagerUtils.h"
24 #include "nsICryptoHash.h"
25 #include "mozilla/Telemetry.h"
29 #define SEC_SUCCESS(Status) ((Status) >= 0)
31 #ifndef KERB_WRAP_NO_ENCRYPT
32 # define KERB_WRAP_NO_ENCRYPT 0x80000001
35 #ifndef SECBUFFER_PADDING
36 # define SECBUFFER_PADDING 9
39 #ifndef SECBUFFER_STREAM
40 # define SECBUFFER_STREAM 10
43 //-----------------------------------------------------------------------------
45 static const wchar_t* const pTypeName
[] = {L
"Kerberos", L
"Negotiate", L
"NTLM"};
51 static const char* MapErrorCode(int rc
) {
54 CASE_(SEC_I_CONTINUE_NEEDED
)
55 CASE_(SEC_I_COMPLETE_NEEDED
)
56 CASE_(SEC_I_COMPLETE_AND_CONTINUE
)
57 CASE_(SEC_E_INCOMPLETE_MESSAGE
)
58 CASE_(SEC_I_INCOMPLETE_CREDENTIALS
)
59 CASE_(SEC_E_INVALID_HANDLE
)
60 CASE_(SEC_E_TARGET_UNKNOWN
)
61 CASE_(SEC_E_LOGON_DENIED
)
62 CASE_(SEC_E_INTERNAL_ERROR
)
63 CASE_(SEC_E_NO_CREDENTIALS
)
64 CASE_(SEC_E_NO_AUTHENTICATING_AUTHORITY
)
65 CASE_(SEC_E_INSUFFICIENT_MEMORY
)
66 CASE_(SEC_E_INVALID_TOKEN
)
71 # define MapErrorCode(_rc) ""
74 //-----------------------------------------------------------------------------
76 static PSecurityFunctionTableW sspi
;
78 static nsresult
InitSSPI() {
81 sspi
= InitSecurityInterfaceW();
83 LOG(("InitSecurityInterfaceW failed"));
84 return NS_ERROR_UNEXPECTED
;
90 //-----------------------------------------------------------------------------
92 nsresult
nsAuthSSPI::MakeSN(const nsACString
& principal
, nsCString
& result
) {
95 nsAutoCString
buf(principal
);
97 // The service name looks like "protocol@hostname", we need to map
98 // this to a value that SSPI expects. To be consistent with IE, we
99 // need to map '@' to '/' and canonicalize the hostname.
100 int32_t index
= buf
.FindChar('@');
101 if (index
== kNotFound
) return NS_ERROR_UNEXPECTED
;
103 nsCOMPtr
<nsIDNSService
> dnsService
=
104 do_GetService(NS_DNSSERVICE_CONTRACTID
, &rv
);
105 if (NS_FAILED(rv
)) return rv
;
107 auto dns
= static_cast<nsDNSService
*>(dnsService
.get());
109 // This could be expensive if our DNS cache cannot satisfy the request.
110 // However, we should have at least hit the OS resolver once prior to
111 // reaching this code, so provided the OS resolver has this information
112 // cached, we should not have to worry about blocking on this function call
113 // for very long. NOTE: because we ask for the canonical hostname, we
114 // might end up requiring extra network activity in cases where the OS
115 // resolver might not have enough information to satisfy the request from
116 // its cache. This is not an issue in versions of Windows up to WinXP.
117 nsCOMPtr
<nsIDNSRecord
> record
;
118 mozilla::OriginAttributes attrs
;
119 rv
= dns
->DeprecatedSyncResolve(Substring(buf
, index
+ 1),
120 nsIDNSService::RESOLVE_CANONICAL_NAME
, attrs
,
121 getter_AddRefs(record
));
122 if (NS_FAILED(rv
)) return rv
;
123 nsCOMPtr
<nsIDNSAddrRecord
> rec
= do_QueryInterface(record
);
125 return NS_ERROR_UNEXPECTED
;
129 rv
= rec
->GetCanonicalName(cname
);
130 if (NS_SUCCEEDED(rv
)) {
131 result
= StringHead(buf
, index
) + "/"_ns
+ cname
;
132 LOG(("Using SPN of [%s]\n", result
.get()));
137 //-----------------------------------------------------------------------------
139 nsAuthSSPI::nsAuthSSPI(pType package
)
140 : mServiceFlags(REQ_DEFAULT
),
143 mCertDERData(nullptr),
145 memset(&mCred
, 0, sizeof(mCred
));
146 memset(&mCtxt
, 0, sizeof(mCtxt
));
149 nsAuthSSPI::~nsAuthSSPI() {
152 if (mCred
.dwLower
|| mCred
.dwUpper
) {
153 (sspi
->FreeCredentialsHandle
)(&mCred
);
154 memset(&mCred
, 0, sizeof(mCred
));
158 void nsAuthSSPI::Reset() {
163 mCertDERData
= nullptr;
167 if (mCtxt
.dwLower
|| mCtxt
.dwUpper
) {
168 (sspi
->DeleteSecurityContext
)(&mCtxt
);
169 memset(&mCtxt
, 0, sizeof(mCtxt
));
173 NS_IMPL_ISUPPORTS(nsAuthSSPI
, nsIAuthModule
)
176 nsAuthSSPI::Init(const nsACString
& aServiceName
, uint32_t aServiceFlags
,
177 const nsAString
& aDomain
, const nsAString
& aUsername
,
178 const nsAString
& aPassword
) {
179 LOG((" nsAuthSSPI::Init\n"));
183 mCertDERData
= nullptr;
185 // The caller must supply a service name to be used. (For why we now require
186 // a service name for NTLM, see bug 487872.)
187 NS_ENSURE_TRUE(!aServiceName
.IsEmpty(), NS_ERROR_INVALID_ARG
);
191 // XXX lazy initialization like this assumes that we are single threaded
194 if (NS_FAILED(rv
)) return rv
;
198 package
= (SEC_WCHAR
*)pTypeName
[(int)mPackage
];
200 if (mPackage
== PACKAGE_TYPE_NTLM
) {
201 // (bug 535193) For NTLM, just use the uri host, do not do canonical host
202 // lookups. The incoming serviceName is in the format: "protocol@hostname",
204 // "<service class>/<hostname>", so swap the '@' for a '/'.
205 mServiceName
= aServiceName
;
206 int32_t index
= mServiceName
.FindChar('@');
207 if (index
== kNotFound
) return NS_ERROR_UNEXPECTED
;
208 mServiceName
.Replace(index
, 1, '/');
210 // Kerberos requires the canonical host, MakeSN takes care of this through a
212 rv
= MakeSN(aServiceName
, mServiceName
);
213 if (NS_FAILED(rv
)) return rv
;
216 mServiceFlags
= aServiceFlags
;
221 rc
= (sspi
->QuerySecurityPackageInfoW
)(package
, &pinfo
);
222 if (rc
!= SEC_E_OK
) {
223 LOG(("%S package not found\n", package
));
224 return NS_ERROR_UNEXPECTED
;
226 mMaxTokenLen
= pinfo
->cbMaxToken
;
227 (sspi
->FreeContextBuffer
)(pinfo
);
229 MS_TimeStamp useBefore
;
231 SEC_WINNT_AUTH_IDENTITY_W ai
;
232 SEC_WINNT_AUTH_IDENTITY_W
* pai
= nullptr;
234 // domain, username, and password will be null if nsHttpNTLMAuth's
235 // ChallengeReceived returns false for identityInvalid. Use default
236 // credentials in this case by passing null for pai.
237 if (!aUsername
.IsEmpty() && !aPassword
.IsEmpty()) {
238 // Keep a copy of these strings for the duration
239 mUsername
= aUsername
;
240 mPassword
= aPassword
;
242 ai
.Domain
= reinterpret_cast<unsigned short*>(mDomain
.BeginWriting());
243 ai
.DomainLength
= mDomain
.Length();
244 ai
.User
= reinterpret_cast<unsigned short*>(mUsername
.BeginWriting());
245 ai
.UserLength
= mUsername
.Length();
246 ai
.Password
= reinterpret_cast<unsigned short*>(mPassword
.BeginWriting());
247 ai
.PasswordLength
= mPassword
.Length();
248 ai
.Flags
= SEC_WINNT_AUTH_IDENTITY_UNICODE
;
252 rc
= (sspi
->AcquireCredentialsHandleW
)(nullptr, package
, SECPKG_CRED_OUTBOUND
,
253 nullptr, pai
, nullptr, nullptr, &mCred
,
255 if (rc
!= SEC_E_OK
) return NS_ERROR_UNEXPECTED
;
257 static bool sTelemetrySent
= false;
258 if (!sTelemetrySent
) {
259 mozilla::Telemetry::Accumulate(mozilla::Telemetry::NTLM_MODULE_USED_2
,
260 aServiceFlags
& nsIAuthModule::REQ_PROXY_AUTH
261 ? NTLM_MODULE_WIN_API_PROXY
262 : NTLM_MODULE_WIN_API_DIRECT
);
263 sTelemetrySent
= true;
266 LOG(("AcquireCredentialsHandle() succeeded.\n"));
270 // The arguments inToken and inTokenLen are used to pass in the server
271 // certificate (when available) in the first call of the function. The
272 // second time these arguments hold an input token.
274 nsAuthSSPI::GetNextToken(const void* inToken
, uint32_t inTokenLen
,
275 void** outToken
, uint32_t* outTokenLen
) {
276 // String for end-point bindings.
277 const char end_point
[] = "tls-server-end-point:";
278 const int end_point_length
= sizeof(end_point
) - 1;
279 const int hash_size
= 32; // Size of a SHA256 hash.
280 const int cbt_size
= hash_size
+ end_point_length
;
283 MS_TimeStamp ignored
;
285 DWORD ctxAttr
, ctxReq
= 0;
287 SecBufferDesc ibd
, obd
;
288 // Optional second input buffer for the CBT (Channel Binding Token)
290 // Pointer to the block of memory that stores the CBT
291 char* sspi_cbt
= nullptr;
292 SEC_CHANNEL_BINDINGS pendpoint_binding
;
294 LOG(("entering nsAuthSSPI::GetNextToken()\n"));
296 if (!mCred
.dwLower
&& !mCred
.dwUpper
) {
297 LOG(("nsAuthSSPI::GetNextToken(), not initialized. exiting."));
298 return NS_ERROR_NOT_INITIALIZED
;
301 if (mServiceFlags
& REQ_DELEGATE
) ctxReq
|= ISC_REQ_DELEGATE
;
302 if (mServiceFlags
& REQ_MUTUAL_AUTH
) ctxReq
|= ISC_REQ_MUTUAL_AUTH
;
306 // First time if it comes with a token,
307 // the token represents the server certificate.
309 mCertDERLength
= inTokenLen
;
310 mCertDERData
= moz_xmalloc(inTokenLen
);
311 memcpy(mCertDERData
, inToken
, inTokenLen
);
313 // We are starting a new authentication sequence.
314 // If we have already initialized our
315 // security context, then we're in trouble because it means that the
316 // first sequence failed. We need to bail or else we might end up in
318 if (mCtxt
.dwLower
|| mCtxt
.dwUpper
) {
319 LOG(("Cannot restart authentication sequence!"));
320 return NS_ERROR_UNEXPECTED
;
323 // The certificate needs to be erased before being passed
324 // to InitializeSecurityContextW().
328 ibd
.ulVersion
= SECBUFFER_VERSION
;
332 // If we have stored a certificate, the Channel Binding Token
333 // needs to be generated and sent in the first input buffer.
334 if (mCertDERLength
> 0) {
335 // First we create a proper Endpoint Binding structure.
336 pendpoint_binding
.dwInitiatorAddrType
= 0;
337 pendpoint_binding
.cbInitiatorLength
= 0;
338 pendpoint_binding
.dwInitiatorOffset
= 0;
339 pendpoint_binding
.dwAcceptorAddrType
= 0;
340 pendpoint_binding
.cbAcceptorLength
= 0;
341 pendpoint_binding
.dwAcceptorOffset
= 0;
342 pendpoint_binding
.cbApplicationDataLength
= cbt_size
;
343 pendpoint_binding
.dwApplicationDataOffset
=
344 sizeof(SEC_CHANNEL_BINDINGS
);
346 // Then add it to the array of sec buffers accordingly.
347 ib
[ibd
.cBuffers
].BufferType
= SECBUFFER_CHANNEL_BINDINGS
;
348 ib
[ibd
.cBuffers
].cbBuffer
= pendpoint_binding
.cbApplicationDataLength
+
349 pendpoint_binding
.dwApplicationDataOffset
;
351 sspi_cbt
= (char*)moz_xmalloc(ib
[ibd
.cBuffers
].cbBuffer
);
353 // Helper to write in the memory block that stores the CBT
354 char* sspi_cbt_ptr
= sspi_cbt
;
356 ib
[ibd
.cBuffers
].pvBuffer
= sspi_cbt
;
359 memcpy(sspi_cbt_ptr
, &pendpoint_binding
,
360 pendpoint_binding
.dwApplicationDataOffset
);
361 sspi_cbt_ptr
+= pendpoint_binding
.dwApplicationDataOffset
;
363 memcpy(sspi_cbt_ptr
, end_point
, end_point_length
);
364 sspi_cbt_ptr
+= end_point_length
;
366 // Start hashing. We are always doing SHA256, but depending
367 // on the certificate, a different alogirthm might be needed.
368 nsAutoCString hashString
;
371 nsCOMPtr
<nsICryptoHash
> crypto
;
372 crypto
= do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID
, &rv
);
373 if (NS_SUCCEEDED(rv
)) rv
= crypto
->Init(nsICryptoHash::SHA256
);
374 if (NS_SUCCEEDED(rv
))
375 rv
= crypto
->Update((unsigned char*)mCertDERData
, mCertDERLength
);
376 if (NS_SUCCEEDED(rv
)) rv
= crypto
->Finish(false, hashString
);
379 mCertDERData
= nullptr;
385 // Once the hash has been computed, we store it in memory right
386 // after the Endpoint structure and the "tls-server-end-point:"
388 memcpy(sspi_cbt_ptr
, hashString
.get(), hash_size
);
390 // Free memory used to store the server certificate
392 mCertDERData
= nullptr;
394 } // End of CBT computation.
396 // We always need this SECBUFFER.
397 ib
[ibd
.cBuffers
].BufferType
= SECBUFFER_TOKEN
;
398 ib
[ibd
.cBuffers
].cbBuffer
= inTokenLen
;
399 ib
[ibd
.cBuffers
].pvBuffer
= (void*)inToken
;
403 } else { // First time and without a token (no server certificate)
404 // We are starting a new authentication sequence. If we have already
405 // initialized our security context, then we're in trouble because it
406 // means that the first sequence failed. We need to bail or else we
407 // might end up in an infinite loop.
408 if (mCtxt
.dwLower
|| mCtxt
.dwUpper
|| mCertDERData
|| mCertDERLength
) {
409 LOG(("Cannot restart authentication sequence!"));
410 return NS_ERROR_UNEXPECTED
;
416 obd
.ulVersion
= SECBUFFER_VERSION
;
419 ob
.BufferType
= SECBUFFER_TOKEN
;
420 ob
.cbBuffer
= mMaxTokenLen
;
421 ob
.pvBuffer
= moz_xmalloc(ob
.cbBuffer
);
422 memset(ob
.pvBuffer
, 0, ob
.cbBuffer
);
424 NS_ConvertUTF8toUTF16
wSN(mServiceName
);
425 SEC_WCHAR
* sn
= (SEC_WCHAR
*)wSN
.get();
427 rc
= (sspi
->InitializeSecurityContextW
)(
428 &mCred
, ctxIn
, sn
, ctxReq
, 0, SECURITY_NATIVE_DREP
,
429 inToken
? &ibd
: nullptr, 0, &mCtxt
, &obd
, &ctxAttr
, &ignored
);
430 if (rc
== SEC_I_CONTINUE_NEEDED
|| rc
== SEC_E_OK
) {
432 LOG(("InitializeSecurityContext: succeeded.\n"));
434 LOG(("InitializeSecurityContext: continue.\n"));
436 if (sspi_cbt
) free(sspi_cbt
);
440 ob
.pvBuffer
= nullptr;
442 *outToken
= ob
.pvBuffer
;
443 *outTokenLen
= ob
.cbBuffer
;
445 if (rc
== SEC_E_OK
) return NS_SUCCESS_AUTH_FINISHED
;
450 LOG(("InitializeSecurityContext failed [rc=%ld:%s]\n", rc
, MapErrorCode(rc
)));
453 return NS_ERROR_FAILURE
;
457 nsAuthSSPI::Unwrap(const void* inToken
, uint32_t inTokenLen
, void** outToken
,
458 uint32_t* outTokenLen
) {
465 ibd
.ulVersion
= SECBUFFER_VERSION
;
468 ib
[0].BufferType
= SECBUFFER_STREAM
;
469 ib
[0].cbBuffer
= inTokenLen
;
470 ib
[0].pvBuffer
= moz_xmalloc(ib
[0].cbBuffer
);
472 memcpy(ib
[0].pvBuffer
, inToken
, inTokenLen
);
475 ib
[1].BufferType
= SECBUFFER_DATA
;
477 ib
[1].pvBuffer
= nullptr;
479 rc
= (sspi
->DecryptMessage
)(&mCtxt
, &ibd
,
480 0, // no sequence numbers
483 if (SEC_SUCCESS(rc
)) {
484 // check if ib[1].pvBuffer is really just ib[0].pvBuffer, in which
485 // case we can let the caller free it. Otherwise, we need to
486 // clone it, and free the original
487 if (ib
[0].pvBuffer
== ib
[1].pvBuffer
) {
488 *outToken
= ib
[1].pvBuffer
;
490 *outToken
= moz_xmemdup(ib
[1].pvBuffer
, ib
[1].cbBuffer
);
491 free(ib
[0].pvBuffer
);
493 *outTokenLen
= ib
[1].cbBuffer
;
495 free(ib
[0].pvBuffer
);
497 if (!SEC_SUCCESS(rc
)) return NS_ERROR_FAILURE
;
502 // utility class used to free memory on exit
507 secBuffers() { memset(&ib
, 0, sizeof(ib
)); }
510 if (ib
[0].pvBuffer
) free(ib
[0].pvBuffer
);
512 if (ib
[1].pvBuffer
) free(ib
[1].pvBuffer
);
514 if (ib
[2].pvBuffer
) free(ib
[2].pvBuffer
);
519 nsAuthSSPI::Wrap(const void* inToken
, uint32_t inTokenLen
, bool confidential
,
520 void** outToken
, uint32_t* outTokenLen
) {
525 SecPkgContext_Sizes sizes
;
527 rc
= (sspi
->QueryContextAttributesW
)(&mCtxt
, SECPKG_ATTR_SIZES
, &sizes
);
529 if (!SEC_SUCCESS(rc
)) return NS_ERROR_FAILURE
;
532 ibd
.pBuffers
= bufs
.ib
;
533 ibd
.ulVersion
= SECBUFFER_VERSION
;
536 bufs
.ib
[0].cbBuffer
= sizes
.cbSecurityTrailer
;
537 bufs
.ib
[0].BufferType
= SECBUFFER_TOKEN
;
538 bufs
.ib
[0].pvBuffer
= moz_xmalloc(sizes
.cbSecurityTrailer
);
541 bufs
.ib
[1].BufferType
= SECBUFFER_DATA
;
542 bufs
.ib
[1].pvBuffer
= moz_xmalloc(inTokenLen
);
543 bufs
.ib
[1].cbBuffer
= inTokenLen
;
545 memcpy(bufs
.ib
[1].pvBuffer
, inToken
, inTokenLen
);
548 bufs
.ib
[2].BufferType
= SECBUFFER_PADDING
;
549 bufs
.ib
[2].cbBuffer
= sizes
.cbBlockSize
;
550 bufs
.ib
[2].pvBuffer
= moz_xmalloc(bufs
.ib
[2].cbBuffer
);
552 rc
= (sspi
->EncryptMessage
)(&mCtxt
, confidential
? 0 : KERB_WRAP_NO_ENCRYPT
,
555 if (SEC_SUCCESS(rc
)) {
556 int len
= bufs
.ib
[0].cbBuffer
+ bufs
.ib
[1].cbBuffer
+ bufs
.ib
[2].cbBuffer
;
557 char* p
= (char*)moz_xmalloc(len
);
559 *outToken
= (void*)p
;
562 memcpy(p
, bufs
.ib
[0].pvBuffer
, bufs
.ib
[0].cbBuffer
);
563 p
+= bufs
.ib
[0].cbBuffer
;
565 memcpy(p
, bufs
.ib
[1].pvBuffer
, bufs
.ib
[1].cbBuffer
);
566 p
+= bufs
.ib
[1].cbBuffer
;
568 memcpy(p
, bufs
.ib
[2].pvBuffer
, bufs
.ib
[2].cbBuffer
);
573 return NS_ERROR_FAILURE
;