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/. */
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 "nsComponentManagerUtils.h"
18 #include "nsDNSService2.h"
19 #include "nsIDNSService.h"
20 #include "nsIDNSRecord.h"
21 #include "nsMemory.h"
22 #include "nsNetCID.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsCOMPtr.h"
25 #include "nsICryptoHash.h"
26 #include "mozilla/Telemetry.h"
27 
28 #include <windows.h>
29 
30 #define SEC_SUCCESS(Status) ((Status) >= 0)
31 
32 #ifndef KERB_WRAP_NO_ENCRYPT
33 #  define KERB_WRAP_NO_ENCRYPT 0x80000001
34 #endif
35 
36 #ifndef SECBUFFER_PADDING
37 #  define SECBUFFER_PADDING 9
38 #endif
39 
40 #ifndef SECBUFFER_STREAM
41 #  define SECBUFFER_STREAM 10
42 #endif
43 
44 //-----------------------------------------------------------------------------
45 
46 static const wchar_t* const pTypeName[] = {L"Kerberos", L"Negotiate", L"NTLM"};
47 
48 #ifdef DEBUG
49 #  define CASE_(_x) \
50     case _x:        \
51       return #_x;
MapErrorCode(int rc)52 static const char* MapErrorCode(int rc) {
53   switch (rc) {
54     CASE_(SEC_E_OK)
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)
68   }
69   return "<unknown>";
70 }
71 #else
72 #  define MapErrorCode(_rc) ""
73 #endif
74 
75 //-----------------------------------------------------------------------------
76 
77 static PSecurityFunctionTableW sspi;
78 
InitSSPI()79 static nsresult InitSSPI() {
80   LOG(("  InitSSPI\n"));
81 
82   sspi = InitSecurityInterfaceW();
83   if (!sspi) {
84     LOG(("InitSecurityInterfaceW failed"));
85     return NS_ERROR_UNEXPECTED;
86   }
87 
88   return NS_OK;
89 }
90 
91 //-----------------------------------------------------------------------------
92 
MakeSN(const char * principal,nsCString & result)93 nsresult nsAuthSSPI::MakeSN(const char* principal, nsCString& result) {
94   nsresult rv;
95 
96   nsAutoCString buf(principal);
97 
98   // The service name looks like "protocol@hostname", we need to map
99   // this to a value that SSPI expects.  To be consistent with IE, we
100   // need to map '@' to '/' and canonicalize the hostname.
101   int32_t index = buf.FindChar('@');
102   if (index == kNotFound) return NS_ERROR_UNEXPECTED;
103 
104   nsCOMPtr<nsIDNSService> dnsService =
105       do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
106   if (NS_FAILED(rv)) return rv;
107 
108   auto dns = static_cast<nsDNSService*>(dnsService.get());
109 
110   // This could be expensive if our DNS cache cannot satisfy the request.
111   // However, we should have at least hit the OS resolver once prior to
112   // reaching this code, so provided the OS resolver has this information
113   // cached, we should not have to worry about blocking on this function call
114   // for very long.  NOTE: because we ask for the canonical hostname, we
115   // might end up requiring extra network activity in cases where the OS
116   // resolver might not have enough information to satisfy the request from
117   // its cache.  This is not an issue in versions of Windows up to WinXP.
118   nsCOMPtr<nsIDNSRecord> record;
119   mozilla::OriginAttributes attrs;
120   rv = dns->DeprecatedSyncResolve(Substring(buf, index + 1),
121                                   nsIDNSService::RESOLVE_CANONICAL_NAME, attrs,
122                                   getter_AddRefs(record));
123   if (NS_FAILED(rv)) return rv;
124   nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(record);
125   if (!rec) {
126     return NS_ERROR_UNEXPECTED;
127   }
128 
129   nsAutoCString cname;
130   rv = rec->GetCanonicalName(cname);
131   if (NS_SUCCEEDED(rv)) {
132     result = StringHead(buf, index) + "/"_ns + cname;
133     LOG(("Using SPN of [%s]\n", result.get()));
134   }
135   return rv;
136 }
137 
138 //-----------------------------------------------------------------------------
139 
nsAuthSSPI(pType package)140 nsAuthSSPI::nsAuthSSPI(pType package)
141     : mServiceFlags(REQ_DEFAULT),
142       mMaxTokenLen(0),
143       mPackage(package),
144       mCertDERData(nullptr),
145       mCertDERLength(0) {
146   memset(&mCred, 0, sizeof(mCred));
147   memset(&mCtxt, 0, sizeof(mCtxt));
148 }
149 
~nsAuthSSPI()150 nsAuthSSPI::~nsAuthSSPI() {
151   Reset();
152 
153   if (mCred.dwLower || mCred.dwUpper) {
154     (sspi->FreeCredentialsHandle)(&mCred);
155     memset(&mCred, 0, sizeof(mCred));
156   }
157 }
158 
Reset()159 void nsAuthSSPI::Reset() {
160   mIsFirst = true;
161 
162   if (mCertDERData) {
163     free(mCertDERData);
164     mCertDERData = nullptr;
165     mCertDERLength = 0;
166   }
167 
168   if (mCtxt.dwLower || mCtxt.dwUpper) {
169     (sspi->DeleteSecurityContext)(&mCtxt);
170     memset(&mCtxt, 0, sizeof(mCtxt));
171   }
172 }
173 
NS_IMPL_ISUPPORTS(nsAuthSSPI,nsIAuthModule)174 NS_IMPL_ISUPPORTS(nsAuthSSPI, nsIAuthModule)
175 
176 NS_IMETHODIMP
177 nsAuthSSPI::Init(const char* serviceName, uint32_t serviceFlags,
178                  const char16_t* domain, const char16_t* username,
179                  const char16_t* password) {
180   LOG(("  nsAuthSSPI::Init\n"));
181 
182   mIsFirst = true;
183   mCertDERLength = 0;
184   mCertDERData = nullptr;
185 
186   // The caller must supply a service name to be used. (For why we now require
187   // a service name for NTLM, see bug 487872.)
188   NS_ENSURE_TRUE(serviceName && *serviceName, NS_ERROR_INVALID_ARG);
189 
190   nsresult rv;
191 
192   // XXX lazy initialization like this assumes that we are single threaded
193   if (!sspi) {
194     rv = InitSSPI();
195     if (NS_FAILED(rv)) return rv;
196   }
197   SEC_WCHAR* package;
198 
199   package = (SEC_WCHAR*)pTypeName[(int)mPackage];
200 
201   if (mPackage == PACKAGE_TYPE_NTLM) {
202     // (bug 535193) For NTLM, just use the uri host, do not do canonical host
203     // lookups. The incoming serviceName is in the format: "protocol@hostname",
204     // SSPI expects
205     // "<service class>/<hostname>", so swap the '@' for a '/'.
206     mServiceName.Assign(serviceName);
207     int32_t index = mServiceName.FindChar('@');
208     if (index == kNotFound) return NS_ERROR_UNEXPECTED;
209     mServiceName.Replace(index, 1, '/');
210   } else {
211     // Kerberos requires the canonical host, MakeSN takes care of this through a
212     // DNS lookup.
213     rv = MakeSN(serviceName, mServiceName);
214     if (NS_FAILED(rv)) return rv;
215   }
216 
217   mServiceFlags = serviceFlags;
218 
219   SECURITY_STATUS rc;
220 
221   PSecPkgInfoW pinfo;
222   rc = (sspi->QuerySecurityPackageInfoW)(package, &pinfo);
223   if (rc != SEC_E_OK) {
224     LOG(("%s package not found\n", package));
225     return NS_ERROR_UNEXPECTED;
226   }
227   mMaxTokenLen = pinfo->cbMaxToken;
228   (sspi->FreeContextBuffer)(pinfo);
229 
230   MS_TimeStamp useBefore;
231 
232   SEC_WINNT_AUTH_IDENTITY_W ai;
233   SEC_WINNT_AUTH_IDENTITY_W* pai = nullptr;
234 
235   // domain, username, and password will be null if nsHttpNTLMAuth's
236   // ChallengeReceived returns false for identityInvalid. Use default
237   // credentials in this case by passing null for pai.
238   if (username && password) {
239     // Keep a copy of these strings for the duration
240     mUsername.Assign(username);
241     mPassword.Assign(password);
242     mDomain.Assign(domain);
243     ai.Domain = reinterpret_cast<unsigned short*>(mDomain.BeginWriting());
244     ai.DomainLength = mDomain.Length();
245     ai.User = reinterpret_cast<unsigned short*>(mUsername.BeginWriting());
246     ai.UserLength = mUsername.Length();
247     ai.Password = reinterpret_cast<unsigned short*>(mPassword.BeginWriting());
248     ai.PasswordLength = mPassword.Length();
249     ai.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
250     pai = &ai;
251   }
252 
253   rc = (sspi->AcquireCredentialsHandleW)(nullptr, package, SECPKG_CRED_OUTBOUND,
254                                          nullptr, pai, nullptr, nullptr, &mCred,
255                                          &useBefore);
256   if (rc != SEC_E_OK) return NS_ERROR_UNEXPECTED;
257 
258   static bool sTelemetrySent = false;
259   if (!sTelemetrySent) {
260     mozilla::Telemetry::Accumulate(mozilla::Telemetry::NTLM_MODULE_USED_2,
261                                    serviceFlags & nsIAuthModule::REQ_PROXY_AUTH
262                                        ? NTLM_MODULE_WIN_API_PROXY
263                                        : NTLM_MODULE_WIN_API_DIRECT);
264     sTelemetrySent = true;
265   }
266 
267   LOG(("AcquireCredentialsHandle() succeeded.\n"));
268   return NS_OK;
269 }
270 
271 // The arguments inToken and inTokenLen are used to pass in the server
272 // certificate (when available) in the first call of the function. The
273 // second time these arguments hold an input token.
274 NS_IMETHODIMP
GetNextToken(const void * inToken,uint32_t inTokenLen,void ** outToken,uint32_t * outTokenLen)275 nsAuthSSPI::GetNextToken(const void* inToken, uint32_t inTokenLen,
276                          void** outToken, uint32_t* outTokenLen) {
277   // String for end-point bindings.
278   const char end_point[] = "tls-server-end-point:";
279   const int end_point_length = sizeof(end_point) - 1;
280   const int hash_size = 32;  // Size of a SHA256 hash.
281   const int cbt_size = hash_size + end_point_length;
282 
283   SECURITY_STATUS rc;
284   MS_TimeStamp ignored;
285 
286   DWORD ctxAttr, ctxReq = 0;
287   CtxtHandle* ctxIn;
288   SecBufferDesc ibd, obd;
289   // Optional second input buffer for the CBT (Channel Binding Token)
290   SecBuffer ib[2], ob;
291   // Pointer to the block of memory that stores the CBT
292   char* sspi_cbt = nullptr;
293   SEC_CHANNEL_BINDINGS pendpoint_binding;
294 
295   LOG(("entering nsAuthSSPI::GetNextToken()\n"));
296 
297   if (!mCred.dwLower && !mCred.dwUpper) {
298     LOG(("nsAuthSSPI::GetNextToken(), not initialized. exiting."));
299     return NS_ERROR_NOT_INITIALIZED;
300   }
301 
302   if (mServiceFlags & REQ_DELEGATE) ctxReq |= ISC_REQ_DELEGATE;
303   if (mServiceFlags & REQ_MUTUAL_AUTH) ctxReq |= ISC_REQ_MUTUAL_AUTH;
304 
305   if (inToken) {
306     if (mIsFirst) {
307       // First time if it comes with a token,
308       // the token represents the server certificate.
309       mIsFirst = false;
310       mCertDERLength = inTokenLen;
311       mCertDERData = moz_xmalloc(inTokenLen);
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 
354         // Helper to write in the memory block that stores the CBT
355         char* sspi_cbt_ptr = sspi_cbt;
356 
357         ib[ibd.cBuffers].pvBuffer = sspi_cbt;
358         ibd.cBuffers++;
359 
360         memcpy(sspi_cbt_ptr, &pendpoint_binding,
361                pendpoint_binding.dwApplicationDataOffset);
362         sspi_cbt_ptr += pendpoint_binding.dwApplicationDataOffset;
363 
364         memcpy(sspi_cbt_ptr, end_point, end_point_length);
365         sspi_cbt_ptr += end_point_length;
366 
367         // Start hashing. We are always doing SHA256, but depending
368         // on the certificate, a different alogirthm might be needed.
369         nsAutoCString hashString;
370 
371         nsresult rv;
372         nsCOMPtr<nsICryptoHash> crypto;
373         crypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
374         if (NS_SUCCEEDED(rv)) rv = crypto->Init(nsICryptoHash::SHA256);
375         if (NS_SUCCEEDED(rv))
376           rv = crypto->Update((unsigned char*)mCertDERData, mCertDERLength);
377         if (NS_SUCCEEDED(rv)) rv = crypto->Finish(false, hashString);
378         if (NS_FAILED(rv)) {
379           free(mCertDERData);
380           mCertDERData = nullptr;
381           mCertDERLength = 0;
382           free(sspi_cbt);
383           return rv;
384         }
385 
386         // Once the hash has been computed, we store it in memory right
387         // after the Endpoint structure and the "tls-server-end-point:"
388         // char array.
389         memcpy(sspi_cbt_ptr, hashString.get(), hash_size);
390 
391         // Free memory used to store the server certificate
392         free(mCertDERData);
393         mCertDERData = nullptr;
394         mCertDERLength = 0;
395       }  // End of CBT computation.
396 
397       // We always need this SECBUFFER.
398       ib[ibd.cBuffers].BufferType = SECBUFFER_TOKEN;
399       ib[ibd.cBuffers].cbBuffer = inTokenLen;
400       ib[ibd.cBuffers].pvBuffer = (void*)inToken;
401       ibd.cBuffers++;
402       ctxIn = &mCtxt;
403     }
404   } else {  // First time and without a token (no server certificate)
405     // We are starting a new authentication sequence.  If we have already
406     // initialized our security context, then we're in trouble because it
407     // means that the first sequence failed.  We need to bail or else we
408     // might end up in an infinite loop.
409     if (mCtxt.dwLower || mCtxt.dwUpper || mCertDERData || mCertDERLength) {
410       LOG(("Cannot restart authentication sequence!"));
411       return NS_ERROR_UNEXPECTED;
412     }
413     ctxIn = nullptr;
414     mIsFirst = false;
415   }
416 
417   obd.ulVersion = SECBUFFER_VERSION;
418   obd.cBuffers = 1;
419   obd.pBuffers = &ob;
420   ob.BufferType = SECBUFFER_TOKEN;
421   ob.cbBuffer = mMaxTokenLen;
422   ob.pvBuffer = moz_xmalloc(ob.cbBuffer);
423   memset(ob.pvBuffer, 0, ob.cbBuffer);
424 
425   NS_ConvertUTF8toUTF16 wSN(mServiceName);
426   SEC_WCHAR* sn = (SEC_WCHAR*)wSN.get();
427 
428   rc = (sspi->InitializeSecurityContextW)(
429       &mCred, ctxIn, sn, ctxReq, 0, SECURITY_NATIVE_DREP,
430       inToken ? &ibd : nullptr, 0, &mCtxt, &obd, &ctxAttr, &ignored);
431   if (rc == SEC_I_CONTINUE_NEEDED || rc == SEC_E_OK) {
432     if (rc == SEC_E_OK)
433       LOG(("InitializeSecurityContext: succeeded.\n"));
434     else
435       LOG(("InitializeSecurityContext: continue.\n"));
436 
437     if (sspi_cbt) free(sspi_cbt);
438 
439     if (!ob.cbBuffer) {
440       free(ob.pvBuffer);
441       ob.pvBuffer = nullptr;
442     }
443     *outToken = ob.pvBuffer;
444     *outTokenLen = ob.cbBuffer;
445 
446     if (rc == SEC_E_OK) return NS_SUCCESS_AUTH_FINISHED;
447 
448     return NS_OK;
449   }
450 
451   LOG(("InitializeSecurityContext failed [rc=%d:%s]\n", rc, MapErrorCode(rc)));
452   Reset();
453   free(ob.pvBuffer);
454   return NS_ERROR_FAILURE;
455 }
456 
457 NS_IMETHODIMP
Unwrap(const void * inToken,uint32_t inTokenLen,void ** outToken,uint32_t * outTokenLen)458 nsAuthSSPI::Unwrap(const void* inToken, uint32_t inTokenLen, void** outToken,
459                    uint32_t* outTokenLen) {
460   SECURITY_STATUS rc;
461   SecBufferDesc ibd;
462   SecBuffer ib[2];
463 
464   ibd.cBuffers = 2;
465   ibd.pBuffers = ib;
466   ibd.ulVersion = SECBUFFER_VERSION;
467 
468   // SSPI Buf
469   ib[0].BufferType = SECBUFFER_STREAM;
470   ib[0].cbBuffer = inTokenLen;
471   ib[0].pvBuffer = moz_xmalloc(ib[0].cbBuffer);
472 
473   memcpy(ib[0].pvBuffer, inToken, inTokenLen);
474 
475   // app data
476   ib[1].BufferType = SECBUFFER_DATA;
477   ib[1].cbBuffer = 0;
478   ib[1].pvBuffer = nullptr;
479 
480   rc = (sspi->DecryptMessage)(&mCtxt, &ibd,
481                               0,  // no sequence numbers
482                               nullptr);
483 
484   if (SEC_SUCCESS(rc)) {
485     // check if ib[1].pvBuffer is really just ib[0].pvBuffer, in which
486     // case we can let the caller free it. Otherwise, we need to
487     // clone it, and free the original
488     if (ib[0].pvBuffer == ib[1].pvBuffer) {
489       *outToken = ib[1].pvBuffer;
490     } else {
491       *outToken = moz_xmemdup(ib[1].pvBuffer, ib[1].cbBuffer);
492       free(ib[0].pvBuffer);
493     }
494     *outTokenLen = ib[1].cbBuffer;
495   } else
496     free(ib[0].pvBuffer);
497 
498   if (!SEC_SUCCESS(rc)) return NS_ERROR_FAILURE;
499 
500   return NS_OK;
501 }
502 
503 // utility class used to free memory on exit
504 class secBuffers {
505  public:
506   SecBuffer ib[3];
507 
secBuffers()508   secBuffers() { memset(&ib, 0, sizeof(ib)); }
509 
~secBuffers()510   ~secBuffers() {
511     if (ib[0].pvBuffer) free(ib[0].pvBuffer);
512 
513     if (ib[1].pvBuffer) free(ib[1].pvBuffer);
514 
515     if (ib[2].pvBuffer) free(ib[2].pvBuffer);
516   }
517 };
518 
519 NS_IMETHODIMP
Wrap(const void * inToken,uint32_t inTokenLen,bool confidential,void ** outToken,uint32_t * outTokenLen)520 nsAuthSSPI::Wrap(const void* inToken, uint32_t inTokenLen, bool confidential,
521                  void** outToken, uint32_t* outTokenLen) {
522   SECURITY_STATUS rc;
523 
524   SecBufferDesc ibd;
525   secBuffers bufs;
526   SecPkgContext_Sizes sizes;
527 
528   rc = (sspi->QueryContextAttributesW)(&mCtxt, SECPKG_ATTR_SIZES, &sizes);
529 
530   if (!SEC_SUCCESS(rc)) return NS_ERROR_FAILURE;
531 
532   ibd.cBuffers = 3;
533   ibd.pBuffers = bufs.ib;
534   ibd.ulVersion = SECBUFFER_VERSION;
535 
536   // SSPI
537   bufs.ib[0].cbBuffer = sizes.cbSecurityTrailer;
538   bufs.ib[0].BufferType = SECBUFFER_TOKEN;
539   bufs.ib[0].pvBuffer = moz_xmalloc(sizes.cbSecurityTrailer);
540 
541   // APP Data
542   bufs.ib[1].BufferType = SECBUFFER_DATA;
543   bufs.ib[1].pvBuffer = moz_xmalloc(inTokenLen);
544   bufs.ib[1].cbBuffer = inTokenLen;
545 
546   memcpy(bufs.ib[1].pvBuffer, inToken, inTokenLen);
547 
548   // SSPI
549   bufs.ib[2].BufferType = SECBUFFER_PADDING;
550   bufs.ib[2].cbBuffer = sizes.cbBlockSize;
551   bufs.ib[2].pvBuffer = moz_xmalloc(bufs.ib[2].cbBuffer);
552 
553   rc = (sspi->EncryptMessage)(&mCtxt, confidential ? 0 : KERB_WRAP_NO_ENCRYPT,
554                               &ibd, 0);
555 
556   if (SEC_SUCCESS(rc)) {
557     int len = bufs.ib[0].cbBuffer + bufs.ib[1].cbBuffer + bufs.ib[2].cbBuffer;
558     char* p = (char*)moz_xmalloc(len);
559 
560     *outToken = (void*)p;
561     *outTokenLen = len;
562 
563     memcpy(p, bufs.ib[0].pvBuffer, bufs.ib[0].cbBuffer);
564     p += bufs.ib[0].cbBuffer;
565 
566     memcpy(p, bufs.ib[1].pvBuffer, bufs.ib[1].cbBuffer);
567     p += bufs.ib[1].cbBuffer;
568 
569     memcpy(p, bufs.ib[2].pvBuffer, bufs.ib[2].cbBuffer);
570 
571     return NS_OK;
572   }
573 
574   return NS_ERROR_FAILURE;
575 }
576