services/crypto/component/nsSyncJPAKE.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 #include "nsSyncJPAKE.h"
     6 #include "mozilla/ModuleUtils.h"
     7 #include <pk11pub.h>
     8 #include <keyhi.h>
     9 #include <pkcs11.h>
    10 #include <nscore.h>
    11 #include <secmodt.h>
    12 #include <secport.h>
    13 #include <secerr.h>
    14 #include <nsDebug.h>
    15 #include <nsError.h>
    16 #include <base64.h>
    17 #include <nsString.h>
    19 using mozilla::fallible_t;
    21 static bool
    22 hex_from_2char(const unsigned char *c2, unsigned char *byteval)
    23 {
    24   int i;
    25   unsigned char offset;
    26   *byteval = 0;
    27   for (i=0; i<2; i++) {
    28     if (c2[i] >= '0' && c2[i] <= '9') {
    29       offset = c2[i] - '0';
    30       *byteval |= offset << 4*(1-i);
    31     } else if (c2[i] >= 'a' && c2[i] <= 'f') {
    32       offset = c2[i] - 'a';
    33       *byteval |= (offset + 10) << 4*(1-i);
    34     } else if (c2[i] >= 'A' && c2[i] <= 'F') {
    35       offset = c2[i] - 'A';
    36       *byteval |= (offset + 10) << 4*(1-i);
    37     } else {
    38       return false;
    39     }
    40   }
    41   return true;
    42 }
    44 static bool
    45 fromHex(const char * str, unsigned char * p, size_t sLen)
    46 {
    47   size_t i;
    48   if (sLen & 1)
    49     return false;
    51   for (i = 0; i < sLen / 2; ++i) {
    52     if (!hex_from_2char((const unsigned char *) str + (2*i),
    53                         (unsigned char *) p + i)) {
    54       return false;
    55     }
    56   }
    57   return true;
    58 }
    60 static nsresult
    61 fromHexString(const nsACString & str, unsigned char * p, size_t pMaxLen)
    62 {
    63   char * strData = (char *) str.Data();
    64   unsigned len = str.Length();
    65   NS_ENSURE_ARG(len / 2 <= pMaxLen);
    66   if (!fromHex(strData, p, len)) {
    67     return NS_ERROR_INVALID_ARG;
    68   }
    69   return NS_OK;
    70 }
    72 static bool
    73 toHexString(const unsigned char * str, unsigned len, nsACString & out)
    74 {
    75   static const char digits[] = "0123456789ABCDEF";
    76   if (!out.SetCapacity(2 * len, fallible_t()))
    77     return false;
    78   out.SetLength(0);
    79   for (unsigned i = 0; i < len; ++i) {
    80     out.Append(digits[str[i] >> 4]);
    81     out.Append(digits[str[i] & 0x0f]);
    82   }
    83   return true;
    84 }
    86 static nsresult
    87 mapErrno()
    88 {
    89   int err = PORT_GetError();
    90   switch (err) {
    91     case SEC_ERROR_NO_MEMORY:   return NS_ERROR_OUT_OF_MEMORY;
    92     default:                    return NS_ERROR_UNEXPECTED;
    93   }
    94 }
    96 #define NUM_ELEM(x) (sizeof(x) / sizeof (x)[0])
    98 static const char p[] = 
    99   "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C"
   100   "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F"
   101   "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1"
   102   "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B"
   103   "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394"
   104   "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0"
   105   "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E"
   106   "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D"
   107   "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F"
   108   "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D"
   109   "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E"
   110   "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73";
   111 static const char q[] =
   112   "CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D";
   113 static const char g[] = 
   114   "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37"
   115   "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB"
   116   "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1"
   117   "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8"
   118   "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17"
   119   "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C"
   120   "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3"
   121   "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B"
   122   "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8"
   123   "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828"
   124   "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33"
   125   "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B";
   127 NS_IMETHODIMP nsSyncJPAKE::Round1(const nsACString & aSignerID,
   128                                   nsACString & aGX1,
   129                                   nsACString & aGV1,
   130                                   nsACString & aR1,
   131                                   nsACString & aGX2,
   132                                   nsACString & aGV2,
   133                                   nsACString & aR2)
   134 {
   135   NS_ENSURE_STATE(round == JPAKENotStarted);
   136   NS_ENSURE_STATE(key == nullptr);
   138   static CK_MECHANISM_TYPE mechanisms[] = {
   139     CKM_NSS_JPAKE_ROUND1_SHA256,
   140     CKM_NSS_JPAKE_ROUND2_SHA256,
   141     CKM_NSS_JPAKE_FINAL_SHA256
   142   };
   144   PK11SlotInfo * slot = PK11_GetBestSlotMultiple(mechanisms,
   145                                                  NUM_ELEM(mechanisms),
   146                                                  nullptr);
   147   NS_ENSURE_STATE(slot != nullptr);
   149   CK_BYTE pBuf[(NUM_ELEM(p) - 1) / 2];
   150   CK_BYTE qBuf[(NUM_ELEM(q) - 1) / 2];
   151   CK_BYTE gBuf[(NUM_ELEM(g) - 1) / 2];
   153   CK_KEY_TYPE keyType = CKK_NSS_JPAKE_ROUND1;
   154   NS_ENSURE_STATE(fromHex(p, pBuf, (NUM_ELEM(p) - 1)));
   155   NS_ENSURE_STATE(fromHex(q, qBuf, (NUM_ELEM(q) - 1)));
   156   NS_ENSURE_STATE(fromHex(g, gBuf, (NUM_ELEM(g) - 1)));
   157   CK_ATTRIBUTE keyTemplate[] = {
   158     { CKA_NSS_JPAKE_SIGNERID, (CK_BYTE *) aSignerID.Data(),
   159                               aSignerID.Length() },
   160     { CKA_KEY_TYPE, &keyType, sizeof keyType },
   161     { CKA_PRIME,    pBuf,     sizeof pBuf },
   162     { CKA_SUBPRIME, qBuf,     sizeof qBuf },
   163     { CKA_BASE,     gBuf,     sizeof gBuf }
   164   };
   166   CK_BYTE gx1Buf[NUM_ELEM(p) / 2];
   167   CK_BYTE gv1Buf[NUM_ELEM(p) / 2];
   168   CK_BYTE r1Buf [NUM_ELEM(p) / 2];
   169   CK_BYTE gx2Buf[NUM_ELEM(p) / 2];
   170   CK_BYTE gv2Buf[NUM_ELEM(p) / 2];
   171   CK_BYTE r2Buf [NUM_ELEM(p) / 2];
   172   CK_NSS_JPAKERound1Params rp = {
   173       { gx1Buf, sizeof gx1Buf, gv1Buf, sizeof gv1Buf, r1Buf, sizeof r1Buf },
   174       { gx2Buf, sizeof gx2Buf, gv2Buf, sizeof gv2Buf, r2Buf, sizeof r2Buf }
   175   };
   176   SECItem paramsItem;
   177   paramsItem.data = (unsigned char *) &rp;
   178   paramsItem.len = sizeof rp;
   179   key = PK11_KeyGenWithTemplate(slot, CKM_NSS_JPAKE_ROUND1_SHA256,
   180                                 CKM_NSS_JPAKE_ROUND1_SHA256,
   181                                 &paramsItem, keyTemplate,
   182                                 NUM_ELEM(keyTemplate), nullptr);
   183   nsresult rv = key != nullptr
   184               ? NS_OK
   185               : mapErrno();
   186   if (rv == NS_OK) {
   187     NS_ENSURE_TRUE(toHexString(rp.gx1.pGX, rp.gx1.ulGXLen, aGX1) &&
   188                    toHexString(rp.gx1.pGV, rp.gx1.ulGVLen, aGV1) &&
   189                    toHexString(rp.gx1.pR,  rp.gx1.ulRLen,  aR1)  &&
   190                    toHexString(rp.gx2.pGX, rp.gx2.ulGXLen, aGX2) &&
   191                    toHexString(rp.gx2.pGV, rp.gx2.ulGVLen, aGV2) &&
   192                    toHexString(rp.gx2.pR,  rp.gx2.ulRLen,  aR2),
   193                    NS_ERROR_OUT_OF_MEMORY);
   194     round = JPAKEBeforeRound2;
   195   }
   196   return rv;
   197 }
   199 NS_IMETHODIMP nsSyncJPAKE::Round2(const nsACString & aPeerID,
   200                                   const nsACString & aPIN,
   201                                   const nsACString & aGX3,
   202                                   const nsACString & aGV3,
   203                                   const nsACString & aR3,
   204                                   const nsACString & aGX4,
   205                                   const nsACString & aGV4,
   206                                   const nsACString & aR4,
   207                                   nsACString & aA,
   208                                   nsACString & aGVA,
   209                                   nsACString & aRA)
   210 {
   211   NS_ENSURE_STATE(round == JPAKEBeforeRound2);
   212   NS_ENSURE_STATE(key != nullptr);
   213   NS_ENSURE_ARG(!aPeerID.IsEmpty());
   215   /* PIN cannot be equal to zero when converted to a bignum. NSS 3.12.9 J-PAKE
   216      assumes that the caller has already done this check. Future versions of 
   217      NSS J-PAKE will do this check internally. See Bug 609068 Comment 4 */
   218   bool foundNonZero = false;
   219   for (size_t i = 0; i < aPIN.Length(); ++i) {
   220     if (aPIN[i] != 0) {
   221       foundNonZero = true;
   222       break;
   223     }
   224   }
   225   NS_ENSURE_ARG(foundNonZero);
   227   CK_BYTE gx3Buf[NUM_ELEM(p)/2], gv3Buf[NUM_ELEM(p)/2], r3Buf [NUM_ELEM(p)/2];
   228   CK_BYTE gx4Buf[NUM_ELEM(p)/2], gv4Buf[NUM_ELEM(p)/2], r4Buf [NUM_ELEM(p)/2];
   229   CK_BYTE gxABuf[NUM_ELEM(p)/2], gvABuf[NUM_ELEM(p)/2], rABuf [NUM_ELEM(p)/2];
   230   nsresult         rv = fromHexString(aGX3, gx3Buf, sizeof gx3Buf);
   231   if (rv == NS_OK) rv = fromHexString(aGV3, gv3Buf, sizeof gv3Buf);
   232   if (rv == NS_OK) rv = fromHexString(aR3,  r3Buf,  sizeof r3Buf);
   233   if (rv == NS_OK) rv = fromHexString(aGX4, gx4Buf, sizeof gx4Buf);
   234   if (rv == NS_OK) rv = fromHexString(aGV4, gv4Buf, sizeof gv4Buf);
   235   if (rv == NS_OK) rv = fromHexString(aR4,  r4Buf,  sizeof r4Buf);
   236   if (rv != NS_OK)
   237     return rv;
   239   CK_NSS_JPAKERound2Params rp;
   240   rp.pSharedKey = (CK_BYTE *) aPIN.Data();
   241   rp.ulSharedKeyLen = aPIN.Length();
   242   rp.gx3.pGX = gx3Buf; rp.gx3.ulGXLen = aGX3.Length() / 2;
   243   rp.gx3.pGV = gv3Buf; rp.gx3.ulGVLen = aGV3.Length() / 2;
   244   rp.gx3.pR  = r3Buf;  rp.gx3.ulRLen  = aR3 .Length() / 2;
   245   rp.gx4.pGX = gx4Buf; rp.gx4.ulGXLen = aGX4.Length() / 2;
   246   rp.gx4.pGV = gv4Buf; rp.gx4.ulGVLen = aGV4.Length() / 2;
   247   rp.gx4.pR  = r4Buf;  rp.gx4.ulRLen  = aR4 .Length() / 2;
   248   rp.A.pGX   = gxABuf; rp.A  .ulGXLen = sizeof gxABuf;
   249   rp.A.pGV   = gvABuf; rp.A  .ulGVLen = sizeof gxABuf;
   250   rp.A.pR    = rABuf;  rp.A  .ulRLen  = sizeof gxABuf;
   252   // Bug 629090: NSS 3.12.9 J-PAKE fails to check that gx^4 != 1, so check here.
   253   bool gx4Good = false;
   254   for (unsigned i = 0; i < rp.gx4.ulGXLen; ++i) {
   255     if (rp.gx4.pGX[i] > 1 || (rp.gx4.pGX[i] != 0 && i < rp.gx4.ulGXLen - 1)) {
   256       gx4Good = true;
   257       break;
   258     }
   259   }
   260   NS_ENSURE_ARG(gx4Good);
   262   SECItem paramsItem;
   263   paramsItem.data = (unsigned char *) &rp;
   264   paramsItem.len = sizeof rp;
   265   CK_KEY_TYPE keyType = CKK_NSS_JPAKE_ROUND2;
   266   CK_ATTRIBUTE keyTemplate[] = {
   267     { CKA_NSS_JPAKE_PEERID, (CK_BYTE *) aPeerID.Data(), aPeerID.Length(), },
   268     { CKA_KEY_TYPE, &keyType, sizeof keyType }
   269   };
   270   PK11SymKey * newKey = PK11_DeriveWithTemplate(key,
   271                                                 CKM_NSS_JPAKE_ROUND2_SHA256,
   272                                                 &paramsItem,
   273                                                 CKM_NSS_JPAKE_FINAL_SHA256,
   274                                                 CKA_DERIVE, 0,
   275                                                 keyTemplate,
   276                                                 NUM_ELEM(keyTemplate),
   277                                                 false);
   278   if (newKey != nullptr) {
   279     if (toHexString(rp.A.pGX, rp.A.ulGXLen, aA) &&
   280         toHexString(rp.A.pGV, rp.A.ulGVLen, aGVA) &&
   281         toHexString(rp.A.pR, rp.A.ulRLen, aRA)) {
   282       round = JPAKEAfterRound2;
   283       PK11_FreeSymKey(key);
   284       key = newKey;
   285       return NS_OK;
   286     } else {
   287       PK11_FreeSymKey(newKey);
   288       rv = NS_ERROR_OUT_OF_MEMORY;
   289     }
   290   } else
   291     rv = mapErrno();
   293   return rv;
   294 }
   296 static nsresult
   297 setBase64(const unsigned char * data, unsigned len, nsACString & out)
   298 {
   299   nsresult rv = NS_OK;
   300   const char * base64 = BTOA_DataToAscii(data, len);
   302   if (base64 != nullptr) {
   303     size_t len = PORT_Strlen(base64);
   304     if (out.SetCapacity(len, fallible_t())) {
   305       out.SetLength(0);
   306       out.Append(base64, len);
   307       PORT_Free((void*) base64);
   308     } else {
   309       rv = NS_ERROR_OUT_OF_MEMORY;
   310     }
   311   } else {
   312     rv = NS_ERROR_OUT_OF_MEMORY;
   313   }
   314   return rv;
   315 }
   317 static nsresult
   318 base64KeyValue(PK11SymKey * key, nsACString & keyString)
   319 {
   320   nsresult rv = NS_OK;
   321   if (PK11_ExtractKeyValue(key) == SECSuccess) {
   322     const SECItem * value = PK11_GetKeyData(key);
   323     rv = value != nullptr && value->data != nullptr && value->len > 0
   324        ? setBase64(value->data, value->len, keyString)
   325        : NS_ERROR_UNEXPECTED;
   326   } else {
   327     rv = mapErrno();
   328   }
   329   return rv;
   330 }
   332 static nsresult
   333 extractBase64KeyValue(PK11SymKey * keyBlock, CK_ULONG bitPosition,
   334                       CK_MECHANISM_TYPE destMech, int keySize,
   335                       nsACString & keyString)
   336 {
   337   SECItem paramsItem;
   338   paramsItem.data = (CK_BYTE *) &bitPosition;
   339   paramsItem.len = sizeof bitPosition;
   340   PK11SymKey * key = PK11_Derive(keyBlock, CKM_EXTRACT_KEY_FROM_KEY,
   341                                  &paramsItem, destMech,
   342                                  CKA_SIGN, keySize);
   343   if (key == nullptr)
   344     return mapErrno();
   345   nsresult rv = base64KeyValue(key, keyString);
   346   PK11_FreeSymKey(key);
   347   return rv;
   348 }
   351 NS_IMETHODIMP nsSyncJPAKE::Final(const nsACString & aB,
   352                                  const nsACString & aGVB,
   353                                  const nsACString & aRB,
   354                                  const nsACString & aHKDFInfo,
   355                                  nsACString & aAES256Key,
   356                                  nsACString & aHMAC256Key)
   357 {
   358   static const unsigned AES256_KEY_SIZE = 256 / 8;
   359   static const unsigned HMAC_SHA256_KEY_SIZE = 256 / 8;
   360   CK_EXTRACT_PARAMS aesBitPosition = 0;
   361   CK_EXTRACT_PARAMS hmacBitPosition = aesBitPosition + (AES256_KEY_SIZE * 8);
   363   NS_ENSURE_STATE(round == JPAKEAfterRound2);
   364   NS_ENSURE_STATE(key != nullptr);
   366   CK_BYTE gxBBuf[NUM_ELEM(p)/2], gvBBuf[NUM_ELEM(p)/2], rBBuf [NUM_ELEM(p)/2];
   367   nsresult         rv = fromHexString(aB,   gxBBuf, sizeof gxBBuf);
   368   if (rv == NS_OK) rv = fromHexString(aGVB, gvBBuf, sizeof gvBBuf);
   369   if (rv == NS_OK) rv = fromHexString(aRB,  rBBuf,  sizeof rBBuf);
   370   if (rv != NS_OK)
   371     return rv;
   373   CK_NSS_JPAKEFinalParams rp;
   374   rp.B.pGX = gxBBuf; rp.B.ulGXLen = aB  .Length() / 2;
   375   rp.B.pGV = gvBBuf; rp.B.ulGVLen = aGVB.Length() / 2;
   376   rp.B.pR  = rBBuf;  rp.B.ulRLen  = aRB .Length() / 2;
   377   SECItem paramsItem;
   378   paramsItem.data = (unsigned char *) &rp;
   379   paramsItem.len = sizeof rp;
   380   PK11SymKey * keyMaterial = PK11_Derive(key, CKM_NSS_JPAKE_FINAL_SHA256,
   381                                          &paramsItem, CKM_NSS_HKDF_SHA256,
   382                                          CKA_DERIVE, 0);
   383   PK11SymKey * keyBlock = nullptr;
   385   if (keyMaterial == nullptr)
   386     rv = mapErrno();
   388   if (rv == NS_OK) {
   389     CK_NSS_HKDFParams hkdfParams;
   390     hkdfParams.bExtract = CK_TRUE;
   391     hkdfParams.pSalt = nullptr;
   392     hkdfParams.ulSaltLen = 0;
   393     hkdfParams.bExpand = CK_TRUE;
   394     hkdfParams.pInfo = (CK_BYTE *) aHKDFInfo.Data();
   395     hkdfParams.ulInfoLen = aHKDFInfo.Length();
   396     paramsItem.data = (unsigned char *) &hkdfParams;
   397     paramsItem.len = sizeof hkdfParams;
   398     keyBlock = PK11_Derive(keyMaterial, CKM_NSS_HKDF_SHA256,
   399                            &paramsItem, CKM_EXTRACT_KEY_FROM_KEY,
   400                            CKA_DERIVE, AES256_KEY_SIZE + HMAC_SHA256_KEY_SIZE);
   401     if (keyBlock == nullptr)
   402       rv = mapErrno();
   403   }
   405   if (rv == NS_OK) {
   406     rv = extractBase64KeyValue(keyBlock, aesBitPosition, CKM_AES_CBC,
   407                                AES256_KEY_SIZE, aAES256Key);
   408   }
   409   if (rv == NS_OK) {
   410     rv = extractBase64KeyValue(keyBlock, hmacBitPosition, CKM_SHA256_HMAC,
   411                                HMAC_SHA256_KEY_SIZE, aHMAC256Key);
   412   }
   414   if (rv == NS_OK) {
   415     SECStatus srv = PK11_ExtractKeyValue(keyMaterial);
   416     NS_ENSURE_TRUE(srv == SECSuccess, NS_ERROR_UNEXPECTED); // XXX leaks
   417     SECItem * keyMaterialBytes = PK11_GetKeyData(keyMaterial);
   418     NS_ENSURE_TRUE(keyMaterialBytes != nullptr, NS_ERROR_UNEXPECTED);
   419   }
   421   if (keyBlock != nullptr)
   422     PK11_FreeSymKey(keyBlock);
   423   if (keyMaterial != nullptr)
   424     PK11_FreeSymKey(keyMaterial);
   426   return rv;
   427 }
   429 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSyncJPAKE)
   430 NS_DEFINE_NAMED_CID(NS_SYNCJPAKE_CID);
   432 nsSyncJPAKE::nsSyncJPAKE() : round(JPAKENotStarted), key(nullptr) { }
   434 nsSyncJPAKE::~nsSyncJPAKE()
   435 {
   436   if (key != nullptr)
   437     PK11_FreeSymKey(key);
   438 }
   440 static const mozilla::Module::CIDEntry kServicesCryptoCIDs[] = {
   441   { &kNS_SYNCJPAKE_CID, false, nullptr, nsSyncJPAKEConstructor },
   442   { nullptr }
   443 };
   445 static const mozilla::Module::ContractIDEntry kServicesCryptoContracts[] = {
   446   { NS_SYNCJPAKE_CONTRACTID, &kNS_SYNCJPAKE_CID },
   447   { nullptr }
   448 };
   450 static const mozilla::Module kServicesCryptoModule = {
   451   mozilla::Module::kVersion,
   452   kServicesCryptoCIDs,
   453   kServicesCryptoContracts
   454 };
   456 NSMODULE_DEFN(nsServicesCryptoModule) = &kServicesCryptoModule;

mercurial