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/. */
5
6 //
7 // Negotiate Authentication Support Module
8 //
9 // Described by IETF Internet draft: draft-brezak-kerberos-http-00.txt
10 // (formerly draft-brezak-spnego-http-04.txt)
11 //
12 // Also described here:
13 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnsecure/html/http-sso-1.asp
14 //
15
16 #include "nsAuthSSPI.h"
17 #include "nsDNSService2.h"
18 #include "nsIServiceManager.h"
19 #include "nsIDNSService.h"
20 #include "nsIDNSRecord.h"
21 #include "nsMemory.h"
22 #include "nsNetCID.h"
23 #include "nsCOMPtr.h"
24 #include "nsICryptoHash.h"
25 #include "mozilla/Telemetry.h"
26
27 #include <windows.h>
28
29 #define SEC_SUCCESS(Status) ((Status) >= 0)
30
31 #ifndef KERB_WRAP_NO_ENCRYPT
32 #define KERB_WRAP_NO_ENCRYPT 0x80000001
33 #endif
34
35 #ifndef SECBUFFER_PADDING
36 #define SECBUFFER_PADDING 9
37 #endif
38
39 #ifndef SECBUFFER_STREAM
40 #define SECBUFFER_STREAM 10
41 #endif
42
43 //-----------------------------------------------------------------------------
44
45 static const wchar_t *const pTypeName[] = {L"Kerberos", L"Negotiate", L"NTLM"};
46
47 #ifdef DEBUG
48 #define CASE_(_x) \
49 case _x: \
50 return #_x;
MapErrorCode(int rc)51 static const char *MapErrorCode(int rc) {
52 switch (rc) {
53 CASE_(SEC_E_OK)
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)
67 }
68 return "<unknown>";
69 }
70 #else
71 #define MapErrorCode(_rc) ""
72 #endif
73
74 //-----------------------------------------------------------------------------
75
76 static PSecurityFunctionTableW sspi;
77
InitSSPI()78 static nsresult InitSSPI() {
79 LOG((" InitSSPI\n"));
80
81 sspi = InitSecurityInterfaceW();
82 if (!sspi) {
83 LOG(("InitSecurityInterfaceW failed"));
84 return NS_ERROR_UNEXPECTED;
85 }
86
87 return NS_OK;
88 }
89
90 //-----------------------------------------------------------------------------
91
MakeSN(const char * principal,nsCString & result)92 nsresult nsAuthSSPI::MakeSN(const char *principal, nsCString &result) {
93 nsresult rv;
94
95 nsAutoCString buf(principal);
96
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;
102
103 nsCOMPtr<nsIDNSService> dnsService =
104 do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
105 if (NS_FAILED(rv)) return rv;
106
107 auto dns = static_cast<nsDNSService *>(dnsService.get());
108
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
124 nsAutoCString cname;
125 rv = record->GetCanonicalName(cname);
126 if (NS_SUCCEEDED(rv)) {
127 result = StringHead(buf, index) + NS_LITERAL_CSTRING("/") + cname;
128 LOG(("Using SPN of [%s]\n", result.get()));
129 }
130 return rv;
131 }
132
133 //-----------------------------------------------------------------------------
134
nsAuthSSPI(pType package)135 nsAuthSSPI::nsAuthSSPI(pType package)
136 : mServiceFlags(REQ_DEFAULT),
137 mMaxTokenLen(0),
138 mPackage(package),
139 mCertDERData(nullptr),
140 mCertDERLength(0) {
141 memset(&mCred, 0, sizeof(mCred));
142 memset(&mCtxt, 0, sizeof(mCtxt));
143 }
144
~nsAuthSSPI()145 nsAuthSSPI::~nsAuthSSPI() {
146 Reset();
147
148 if (mCred.dwLower || mCred.dwUpper) {
149 #ifdef __MINGW32__
150 (sspi->FreeCredentialsHandle)(&mCred);
151 #else
152 (sspi->FreeCredentialHandle)(&mCred);
153 #endif
154 memset(&mCred, 0, sizeof(mCred));
155 }
156 }
157
Reset()158 void nsAuthSSPI::Reset() {
159 mIsFirst = true;
160
161 if (mCertDERData) {
162 free(mCertDERData);
163 mCertDERData = nullptr;
164 mCertDERLength = 0;
165 }
166
167 if (mCtxt.dwLower || mCtxt.dwUpper) {
168 (sspi->DeleteSecurityContext)(&mCtxt);
169 memset(&mCtxt, 0, sizeof(mCtxt));
170 }
171 }
172
NS_IMPL_ISUPPORTS(nsAuthSSPI,nsIAuthModule)173 NS_IMPL_ISUPPORTS(nsAuthSSPI, nsIAuthModule)
174
175 NS_IMETHODIMP
176 nsAuthSSPI::Init(const char *serviceName, uint32_t serviceFlags,
177 const char16_t *domain, const char16_t *username,
178 const char16_t *password) {
179 LOG((" nsAuthSSPI::Init\n"));
180
181 mIsFirst = true;
182 mCertDERLength = 0;
183 mCertDERData = nullptr;
184
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(serviceName && *serviceName, NS_ERROR_INVALID_ARG);
188
189 nsresult rv;
190
191 // XXX lazy initialization like this assumes that we are single threaded
192 if (!sspi) {
193 rv = InitSSPI();
194 if (NS_FAILED(rv)) return rv;
195 }
196 SEC_WCHAR *package;
197
198 package = (SEC_WCHAR *)pTypeName[(int)mPackage];
199
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",
203 // SSPI expects
204 // "<service class>/<hostname>", so swap the '@' for a '/'.
205 mServiceName.Assign(serviceName);
206 int32_t index = mServiceName.FindChar('@');
207 if (index == kNotFound) return NS_ERROR_UNEXPECTED;
208 mServiceName.Replace(index, 1, '/');
209 } else {
210 // Kerberos requires the canonical host, MakeSN takes care of this through a
211 // DNS lookup.
212 rv = MakeSN(serviceName, mServiceName);
213 if (NS_FAILED(rv)) return rv;
214 }
215
216 mServiceFlags = serviceFlags;
217
218 SECURITY_STATUS rc;
219
220 PSecPkgInfoW pinfo;
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;
225 }
226 mMaxTokenLen = pinfo->cbMaxToken;
227 (sspi->FreeContextBuffer)(pinfo);
228
229 MS_TimeStamp useBefore;
230
231 SEC_WINNT_AUTH_IDENTITY_W ai;
232 SEC_WINNT_AUTH_IDENTITY_W *pai = nullptr;
233
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 (username && password) {
238 // Keep a copy of these strings for the duration
239 mUsername.Assign(username);
240 mPassword.Assign(password);
241 mDomain.Assign(domain);
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;
249 pai = &ai;
250 }
251
252 rc = (sspi->AcquireCredentialsHandleW)(nullptr, package, SECPKG_CRED_OUTBOUND,
253 nullptr, pai, nullptr, nullptr, &mCred,
254 &useBefore);
255 if (rc != SEC_E_OK) return NS_ERROR_UNEXPECTED;
256
257 static bool sTelemetrySent = false;
258 if (!sTelemetrySent) {
259 mozilla::Telemetry::Accumulate(mozilla::Telemetry::NTLM_MODULE_USED_2,
260 serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
261 ? NTLM_MODULE_WIN_API_PROXY
262 : NTLM_MODULE_WIN_API_DIRECT);
263 sTelemetrySent = true;
264 }
265
266 LOG(("AcquireCredentialsHandle() succeeded.\n"));
267 return NS_OK;
268 }
269
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.
273 NS_IMETHODIMP
GetNextToken(const void * inToken,uint32_t inTokenLen,void ** outToken,uint32_t * outTokenLen)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;
281
282 SECURITY_STATUS rc;
283 MS_TimeStamp ignored;
284
285 DWORD ctxAttr, ctxReq = 0;
286 CtxtHandle *ctxIn;
287 SecBufferDesc ibd, obd;
288 // Optional second input buffer for the CBT (Channel Binding Token)
289 SecBuffer ib[2], ob;
290 // Pointer to the block of memory that stores the CBT
291 char *sspi_cbt = nullptr;
292 SEC_CHANNEL_BINDINGS pendpoint_binding;
293
294 LOG(("entering nsAuthSSPI::GetNextToken()\n"));
295
296 if (!mCred.dwLower && !mCred.dwUpper) {
297 LOG(("nsAuthSSPI::GetNextToken(), not initialized. exiting."));
298 return NS_ERROR_NOT_INITIALIZED;
299 }
300
301 if (mServiceFlags & REQ_DELEGATE) ctxReq |= ISC_REQ_DELEGATE;
302 if (mServiceFlags & REQ_MUTUAL_AUTH) ctxReq |= ISC_REQ_MUTUAL_AUTH;
303
304 if (inToken) {
305 if (mIsFirst) {
306 // First time if it comes with a token,
307 // the token represents the server certificate.
308 mIsFirst = false;
309 mCertDERLength = inTokenLen;
310 mCertDERData = moz_xmalloc(inTokenLen);
311 if (!mCertDERData) return NS_ERROR_OUT_OF_MEMORY;
312 memcpy(mCertDERData, inToken, inTokenLen);
313
314 // We are starting a new authentication sequence.
315 // If we have already initialized our
316 // security context, then we're in trouble because it means that the
317 // first sequence failed. We need to bail or else we might end up in
318 // an infinite loop.
319 if (mCtxt.dwLower || mCtxt.dwUpper) {
320 LOG(("Cannot restart authentication sequence!"));
321 return NS_ERROR_UNEXPECTED;
322 }
323 ctxIn = nullptr;
324 // The certificate needs to be erased before being passed
325 // to InitializeSecurityContextW().
326 inToken = nullptr;
327 inTokenLen = 0;
328 } else {
329 ibd.ulVersion = SECBUFFER_VERSION;
330 ibd.cBuffers = 0;
331 ibd.pBuffers = ib;
332
333 // If we have stored a certificate, the Channel Binding Token
334 // needs to be generated and sent in the first input buffer.
335 if (mCertDERLength > 0) {
336 // First we create a proper Endpoint Binding structure.
337 pendpoint_binding.dwInitiatorAddrType = 0;
338 pendpoint_binding.cbInitiatorLength = 0;
339 pendpoint_binding.dwInitiatorOffset = 0;
340 pendpoint_binding.dwAcceptorAddrType = 0;
341 pendpoint_binding.cbAcceptorLength = 0;
342 pendpoint_binding.dwAcceptorOffset = 0;
343 pendpoint_binding.cbApplicationDataLength = cbt_size;
344 pendpoint_binding.dwApplicationDataOffset =
345 sizeof(SEC_CHANNEL_BINDINGS);
346
347 // Then add it to the array of sec buffers accordingly.
348 ib[ibd.cBuffers].BufferType = SECBUFFER_CHANNEL_BINDINGS;
349 ib[ibd.cBuffers].cbBuffer = pendpoint_binding.cbApplicationDataLength +
350 pendpoint_binding.dwApplicationDataOffset;
351
352 sspi_cbt = (char *)moz_xmalloc(ib[ibd.cBuffers].cbBuffer);
353 if (!sspi_cbt) {
354 return NS_ERROR_OUT_OF_MEMORY;
355 }
356
357 // Helper to write in the memory block that stores the CBT
358 char *sspi_cbt_ptr = sspi_cbt;
359
360 ib[ibd.cBuffers].pvBuffer = sspi_cbt;
361 ibd.cBuffers++;
362
363 memcpy(sspi_cbt_ptr, &pendpoint_binding,
364 pendpoint_binding.dwApplicationDataOffset);
365 sspi_cbt_ptr += pendpoint_binding.dwApplicationDataOffset;
366
367 memcpy(sspi_cbt_ptr, end_point, end_point_length);
368 sspi_cbt_ptr += end_point_length;
369
370 // Start hashing. We are always doing SHA256, but depending
371 // on the certificate, a different alogirthm might be needed.
372 nsAutoCString hashString;
373
374 nsresult rv;
375 nsCOMPtr<nsICryptoHash> crypto;
376 crypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
377 if (NS_SUCCEEDED(rv)) rv = crypto->Init(nsICryptoHash::SHA256);
378 if (NS_SUCCEEDED(rv))
379 rv = crypto->Update((unsigned char *)mCertDERData, mCertDERLength);
380 if (NS_SUCCEEDED(rv)) rv = crypto->Finish(false, hashString);
381 if (NS_FAILED(rv)) {
382 free(mCertDERData);
383 mCertDERData = nullptr;
384 mCertDERLength = 0;
385 free(sspi_cbt);
386 return rv;
387 }
388
389 // Once the hash has been computed, we store it in memory right
390 // after the Endpoint structure and the "tls-server-end-point:"
391 // char array.
392 memcpy(sspi_cbt_ptr, hashString.get(), hash_size);
393
394 // Free memory used to store the server certificate
395 free(mCertDERData);
396 mCertDERData = nullptr;
397 mCertDERLength = 0;
398 } // End of CBT computation.
399
400 // We always need this SECBUFFER.
401 ib[ibd.cBuffers].BufferType = SECBUFFER_TOKEN;
402 ib[ibd.cBuffers].cbBuffer = inTokenLen;
403 ib[ibd.cBuffers].pvBuffer = (void *)inToken;
404 ibd.cBuffers++;
405 ctxIn = &mCtxt;
406 }
407 } else { // First time and without a token (no server certificate)
408 // We are starting a new authentication sequence. If we have already
409 // initialized our security context, then we're in trouble because it
410 // means that the first sequence failed. We need to bail or else we
411 // might end up in an infinite loop.
412 if (mCtxt.dwLower || mCtxt.dwUpper || mCertDERData || mCertDERLength) {
413 LOG(("Cannot restart authentication sequence!"));
414 return NS_ERROR_UNEXPECTED;
415 }
416 ctxIn = nullptr;
417 mIsFirst = false;
418 }
419
420 obd.ulVersion = SECBUFFER_VERSION;
421 obd.cBuffers = 1;
422 obd.pBuffers = &ob;
423 ob.BufferType = SECBUFFER_TOKEN;
424 ob.cbBuffer = mMaxTokenLen;
425 ob.pvBuffer = moz_xmalloc(ob.cbBuffer);
426 if (!ob.pvBuffer) {
427 if (sspi_cbt) free(sspi_cbt);
428 return NS_ERROR_OUT_OF_MEMORY;
429 }
430 memset(ob.pvBuffer, 0, ob.cbBuffer);
431
432 NS_ConvertUTF8toUTF16 wSN(mServiceName);
433 SEC_WCHAR *sn = (SEC_WCHAR *)wSN.get();
434
435 rc = (sspi->InitializeSecurityContextW)(
436 &mCred, ctxIn, sn, ctxReq, 0, SECURITY_NATIVE_DREP,
437 inToken ? &ibd : nullptr, 0, &mCtxt, &obd, &ctxAttr, &ignored);
438 if (rc == SEC_I_CONTINUE_NEEDED || rc == SEC_E_OK) {
439 if (rc == SEC_E_OK)
440 LOG(("InitializeSecurityContext: succeeded.\n"));
441 else
442 LOG(("InitializeSecurityContext: continue.\n"));
443
444 if (sspi_cbt) free(sspi_cbt);
445
446 if (!ob.cbBuffer) {
447 free(ob.pvBuffer);
448 ob.pvBuffer = nullptr;
449 }
450 *outToken = ob.pvBuffer;
451 *outTokenLen = ob.cbBuffer;
452
453 if (rc == SEC_E_OK) return NS_SUCCESS_AUTH_FINISHED;
454
455 return NS_OK;
456 }
457
458 LOG(("InitializeSecurityContext failed [rc=%d:%s]\n", rc, MapErrorCode(rc)));
459 Reset();
460 free(ob.pvBuffer);
461 return NS_ERROR_FAILURE;
462 }
463
464 NS_IMETHODIMP
Unwrap(const void * inToken,uint32_t inTokenLen,void ** outToken,uint32_t * outTokenLen)465 nsAuthSSPI::Unwrap(const void *inToken, uint32_t inTokenLen, void **outToken,
466 uint32_t *outTokenLen) {
467 SECURITY_STATUS rc;
468 SecBufferDesc ibd;
469 SecBuffer ib[2];
470
471 ibd.cBuffers = 2;
472 ibd.pBuffers = ib;
473 ibd.ulVersion = SECBUFFER_VERSION;
474
475 // SSPI Buf
476 ib[0].BufferType = SECBUFFER_STREAM;
477 ib[0].cbBuffer = inTokenLen;
478 ib[0].pvBuffer = moz_xmalloc(ib[0].cbBuffer);
479 if (!ib[0].pvBuffer) return NS_ERROR_OUT_OF_MEMORY;
480
481 memcpy(ib[0].pvBuffer, inToken, inTokenLen);
482
483 // app data
484 ib[1].BufferType = SECBUFFER_DATA;
485 ib[1].cbBuffer = 0;
486 ib[1].pvBuffer = nullptr;
487
488 rc = (sspi->DecryptMessage)(&mCtxt, &ibd,
489 0, // no sequence numbers
490 nullptr);
491
492 if (SEC_SUCCESS(rc)) {
493 // check if ib[1].pvBuffer is really just ib[0].pvBuffer, in which
494 // case we can let the caller free it. Otherwise, we need to
495 // clone it, and free the original
496 if (ib[0].pvBuffer == ib[1].pvBuffer) {
497 *outToken = ib[1].pvBuffer;
498 } else {
499 *outToken = nsMemory::Clone(ib[1].pvBuffer, ib[1].cbBuffer);
500 free(ib[0].pvBuffer);
501 if (!*outToken) return NS_ERROR_OUT_OF_MEMORY;
502 }
503 *outTokenLen = ib[1].cbBuffer;
504 } else
505 free(ib[0].pvBuffer);
506
507 if (!SEC_SUCCESS(rc)) return NS_ERROR_FAILURE;
508
509 return NS_OK;
510 }
511
512 // utility class used to free memory on exit
513 class secBuffers {
514 public:
515 SecBuffer ib[3];
516
secBuffers()517 secBuffers() { memset(&ib, 0, sizeof(ib)); }
518
~secBuffers()519 ~secBuffers() {
520 if (ib[0].pvBuffer) free(ib[0].pvBuffer);
521
522 if (ib[1].pvBuffer) free(ib[1].pvBuffer);
523
524 if (ib[2].pvBuffer) free(ib[2].pvBuffer);
525 }
526 };
527
528 NS_IMETHODIMP
Wrap(const void * inToken,uint32_t inTokenLen,bool confidential,void ** outToken,uint32_t * outTokenLen)529 nsAuthSSPI::Wrap(const void *inToken, uint32_t inTokenLen, bool confidential,
530 void **outToken, uint32_t *outTokenLen) {
531 SECURITY_STATUS rc;
532
533 SecBufferDesc ibd;
534 secBuffers bufs;
535 SecPkgContext_Sizes sizes;
536
537 rc = (sspi->QueryContextAttributesW)(&mCtxt, SECPKG_ATTR_SIZES, &sizes);
538
539 if (!SEC_SUCCESS(rc)) return NS_ERROR_FAILURE;
540
541 ibd.cBuffers = 3;
542 ibd.pBuffers = bufs.ib;
543 ibd.ulVersion = SECBUFFER_VERSION;
544
545 // SSPI
546 bufs.ib[0].cbBuffer = sizes.cbSecurityTrailer;
547 bufs.ib[0].BufferType = SECBUFFER_TOKEN;
548 bufs.ib[0].pvBuffer = moz_xmalloc(sizes.cbSecurityTrailer);
549
550 if (!bufs.ib[0].pvBuffer) return NS_ERROR_OUT_OF_MEMORY;
551
552 // APP Data
553 bufs.ib[1].BufferType = SECBUFFER_DATA;
554 bufs.ib[1].pvBuffer = moz_xmalloc(inTokenLen);
555 bufs.ib[1].cbBuffer = inTokenLen;
556
557 if (!bufs.ib[1].pvBuffer) return NS_ERROR_OUT_OF_MEMORY;
558
559 memcpy(bufs.ib[1].pvBuffer, inToken, inTokenLen);
560
561 // SSPI
562 bufs.ib[2].BufferType = SECBUFFER_PADDING;
563 bufs.ib[2].cbBuffer = sizes.cbBlockSize;
564 bufs.ib[2].pvBuffer = moz_xmalloc(bufs.ib[2].cbBuffer);
565
566 if (!bufs.ib[2].pvBuffer) return NS_ERROR_OUT_OF_MEMORY;
567
568 rc = (sspi->EncryptMessage)(&mCtxt, confidential ? 0 : KERB_WRAP_NO_ENCRYPT,
569 &ibd, 0);
570
571 if (SEC_SUCCESS(rc)) {
572 int len = bufs.ib[0].cbBuffer + bufs.ib[1].cbBuffer + bufs.ib[2].cbBuffer;
573 char *p = (char *)moz_xmalloc(len);
574
575 if (!p) return NS_ERROR_OUT_OF_MEMORY;
576
577 *outToken = (void *)p;
578 *outTokenLen = len;
579
580 memcpy(p, bufs.ib[0].pvBuffer, bufs.ib[0].cbBuffer);
581 p += bufs.ib[0].cbBuffer;
582
583 memcpy(p, bufs.ib[1].pvBuffer, bufs.ib[1].cbBuffer);
584 p += bufs.ib[1].cbBuffer;
585
586 memcpy(p, bufs.ib[2].pvBuffer, bufs.ib[2].cbBuffer);
587
588 return NS_OK;
589 }
590
591 return NS_ERROR_FAILURE;
592 }
593