Bug 601645 - JavaScript API for NSS J-PAKE. r=mconnor a=blocking-beta8
authorBrian Smith <bsmith@mozilla.com>
Thu, 09 Dec 2010 18:27:48 -0800
changeset 59046 7916ff577de1f36ae45a0f944422bd1601dc7152
parent 59045 146e2854e3f5b16eae89dae61cdd247347edeac0
child 59047 006396b0b99071d92b619a0143c94e66506350d6
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersmconnor, blocking-beta8
bugs601645
milestone2.0b8pre
Bug 601645 - JavaScript API for NSS J-PAKE. r=mconnor a=blocking-beta8
browser/build.mk
services/crypto/Makefile.in
services/crypto/nsISyncJPAKE.idl
services/crypto/nsSyncJPAKE.cpp
services/crypto/nsSyncJPAKE.h
toolkit/library/libxul-config.mk
toolkit/library/nsStaticXULComponents.cpp
toolkit/toolkit-tiers.mk
--- a/browser/build.mk
+++ b/browser/build.mk
@@ -45,17 +45,16 @@ ifdef MOZ_EXTENSIONS
 tier_app_dirs += extensions
 endif
 
 tier_app_dirs += $(MOZ_BRANDING_DIRECTORY)
 
 tier_app_dirs += toolkit/components/console/hudservice
 
 ifdef MOZ_SERVICES_SYNC
-tier_app_dirs += services/crypto
 tier_app_dirs += services/sync
 endif
 
 tier_app_dirs += browser
 # Never add other tier_app_dirs after browser. They won't get packaged
 # properly on mac.
 
 installer:
--- a/services/crypto/Makefile.in
+++ b/services/crypto/Makefile.in
@@ -43,18 +43,37 @@ VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE = services-crypto
 XPIDL_MODULE = services-crypto
 
 XPIDLSRCS = \
   IWeaveCrypto.idl \
+  nsISyncJPAKE.idl \
+  $(NULL)
+
+LIBRARY_NAME   = services-crypto
+EXPORT_LIBRARY = 1
+IS_COMPONENT   = 1
+MODULE_NAME    = nsServicesCryptoModule
+LIBXUL_LIBRARY = 1
+FORCE_USE_PIC  = 1
+MOZILLA_INTERNAL_API = 1
+
+CPPSRCS = \
+  nsSyncJPAKE.cpp \
   $(NULL)
 
 libs::
 	$(PYTHON) $(topsrcdir)/config/nsinstall.py $(srcdir)/modules/* $(FINAL_TARGET)/modules/services-crypto
 
 ifdef ENABLE_TESTS
 DIRS += tests
 endif
 
 include $(topsrcdir)/config/rules.mk
+
+EXTRA_DSO_LDOPTS += \
+	$(MOZ_COMPONENT_LIBS) \
+    $(NSPR_LIBS) \
+    $(NSS_LIBS) \
+    $(NULL)
new file mode 100644
--- /dev/null
+++ b/services/crypto/nsISyncJPAKE.idl
@@ -0,0 +1,137 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Sync.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian Smith <bsmith@mozilla.com>
+ * Philipp von Weitershausen <philipp@weitershausen.de>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(5ab02a98-5122-4b90-93cd-f259c4b42e3a)]
+interface nsISyncJPAKE : nsISupports
+{
+  /**
+   * Perform first round of the JPAKE exchange.
+   *
+   * @param aSignerID
+   *        String identifying the signer.
+   * @param aGX1
+   *        Schnorr signature value g^x1, in hex representation.
+   * @param aGV1
+   *        Schnorr signature value g^v1 (v1 is a random value), in hex
+   *        representation.
+   * @param aR1
+   *        Schnorr signature value r1 = v1 - x1 * h, in hex representation.
+   * @param aGX2
+   *        Schnorr signature value g^x2, in hex representation.
+   * @param aGV2
+   *        Schnorr signature value g^v2 (v2 is a random value), in hex
+   *        representation.
+   * @param aR2
+   *        Schnorr signature value r2 = v2 - x2 * h, in hex representation.
+   */
+  void round1(in ACString aSignerID,
+              out ACString aGX1,
+              out ACString aGV1,
+              out ACString aR1,
+              out ACString aGX2,
+              out ACString aGV2,
+              out ACString aR2);
+
+  /**
+   * Perform second round of the JPAKE exchange.
+   *
+   * @param aPeerID
+   *        String identifying the peer.
+   * @param aPIN
+   *        String containing the weak secret (PIN).
+   * @param aGX3
+   *        Schnorr signature value g^x3, in hex representation.
+   * @param aGV3
+   *        Schnorr signature value g^v3 (v3 is a random value), in hex
+   *        representation.
+   * @param aR3
+   *        Schnorr signature value r3 = v3 - x3 * h, in hex representation.
+   * @param aGX4
+   *        Schnorr signature value g^x4, in hex representation.
+   * @param aGV4
+   *        Schnorr signature value g^v4 (v4 is a random value), in hex
+   *        representation.
+   * @param aR4
+   *        Schnorr signature value r4 = v4 - x4 * h, in hex representation.
+   * @param aA
+   *        Schnorr signature value A, in hex representation.
+   * @param aGVA
+   *        Schnorr signature value g^va (va is a random value), in hex
+   *        representation.
+   * @param aRA
+   *        Schnorr signature value ra = va - xa * h, in hex representation.
+   */
+  void round2(in ACString aPeerID,
+              in ACString aPIN,
+              in ACString aGX3,
+              in ACString aGV3,
+              in ACString aR3,
+              in ACString aGX4,
+              in ACString aGV4,
+              in ACString aR4,
+              out ACString aA,
+              out ACString aGVA,
+              out ACString aRA);
+
+  /**
+   * Perform the final step of the JPAKE exchange. This will compute
+   * the key and expand the key to two keys, an AES256 encryption key
+   * and a 256 bit HMAC key. It returns a key confirmation value
+   * (SHA256d of the key) and the encryption and HMAC keys.
+   *
+   * @param aB
+   *        Schnorr signature value B, in hex representation.
+   * @param aGVB
+   *        Schnorr signature value g^vb (vb is a random value), in hex
+   *        representation.
+   * @param aRB
+   *        Schnorr signature value rb = vb - xb * h, in hex representation.
+   * @param aAES256Key
+   *        The AES 256 encryption key, in base64 representation.
+   * @param aHMAC256Key
+   *        The 256 bit HMAC key, in base64 representation.
+   */
+  void final(in ACString aB,
+             in ACString aGVB,
+             in ACString aRB,
+             in ACString aHkdfInfo,
+             out ACString aAES256Key,
+             out ACString aHMAC256Key);
+};
new file mode 100644
--- /dev/null
+++ b/services/crypto/nsSyncJPAKE.cpp
@@ -0,0 +1,476 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Sync.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian Smith <bsmith@mozilla.com>
+ * Philipp von Weitershausen <philipp@weitershausen.de>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsSyncJPAKE.h"
+#include "mozilla/ModuleUtils.h"
+#include <pk11pub.h>
+#include <keyhi.h>
+#include <pkcs11.h>
+#include <nscore.h>
+#include <secmodt.h>
+#include <secport.h>
+#include <secerr.h>
+#include <nsDebug.h>
+#include <nsError.h>
+#include <base64.h>
+#include <nsString.h>
+
+static PRBool
+hex_from_2char(const unsigned char *c2, unsigned char *byteval)
+{
+  int i;
+  unsigned char offset;
+  *byteval = 0;
+  for (i=0; i<2; i++) {
+    if (c2[i] >= '0' && c2[i] <= '9') {
+      offset = c2[i] - '0';
+      *byteval |= offset << 4*(1-i);
+    } else if (c2[i] >= 'a' && c2[i] <= 'f') {
+      offset = c2[i] - 'a';
+      *byteval |= (offset + 10) << 4*(1-i);
+    } else if (c2[i] >= 'A' && c2[i] <= 'F') {
+      offset = c2[i] - 'A';
+      *byteval |= (offset + 10) << 4*(1-i);
+    } else {
+      return PR_FALSE;
+    }
+  }
+  return PR_TRUE;
+}
+
+static PRBool
+fromHex(const char * str, unsigned char * p, size_t sLen)
+{
+  size_t i;
+  if (sLen & 1)
+    return PR_FALSE;
+
+  for (i = 0; i < sLen / 2; ++i) {
+    if (!hex_from_2char((const unsigned char *) str + (2*i),
+                        (unsigned char *) p + i)) {
+      return PR_FALSE;
+    }
+  }
+  return PR_TRUE;
+}
+
+static nsresult
+fromHexString(const nsACString & str, unsigned char * p, size_t pMaxLen)
+{
+  char * strData = (char *) str.Data();
+  unsigned len = str.Length();
+  NS_ENSURE_ARG(len / 2 <= pMaxLen);
+  if (!fromHex(strData, p, len)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  return NS_OK;
+}
+
+static PRBool
+toHexString(const unsigned char * str, unsigned len, nsACString & out)
+{
+  static const char digits[] = "0123456789ABCDEF";
+  if (!out.SetCapacity(2 * len))
+    return NS_ERROR_OUT_OF_MEMORY;
+  out.SetLength(0);
+  for (unsigned i = 0; i < len; ++i) {
+    out.Append(digits[str[i] >> 4]);
+    out.Append(digits[str[i] & 0x0f]);
+  }
+  return PR_TRUE;
+}
+
+static nsresult
+mapErrno()
+{
+  int err = PORT_GetError();
+  switch (err) {
+    case SEC_ERROR_NO_MEMORY:   return NS_ERROR_OUT_OF_MEMORY;
+    default:                    return NS_ERROR_UNEXPECTED;
+  }
+}
+
+#define NUM_ELEM(x) (sizeof(x) / sizeof (x)[0])
+
+static const char p[] = 
+  "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C"
+  "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F"
+  "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1"
+  "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B"
+  "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394"
+  "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0"
+  "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E"
+  "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D"
+  "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F"
+  "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D"
+  "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E"
+  "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73";
+static const char q[] =
+  "CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D";
+static const char g[] = 
+  "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37"
+  "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB"
+  "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1"
+  "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8"
+  "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17"
+  "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C"
+  "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3"
+  "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B"
+  "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8"
+  "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828"
+  "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33"
+  "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B";
+
+NS_IMETHODIMP nsSyncJPAKE::Round1(const nsACString & aSignerID,
+                                  nsACString & aGX1 NS_OUTPARAM,
+                                  nsACString & aGV1 NS_OUTPARAM,
+                                  nsACString & aR1 NS_OUTPARAM,
+                                  nsACString & aGX2 NS_OUTPARAM,
+                                  nsACString & aGV2 NS_OUTPARAM,
+                                  nsACString & aR2 NS_OUTPARAM)
+{
+  NS_ENSURE_STATE(round == JPAKENotStarted);
+  NS_ENSURE_STATE(key == NULL);
+
+  static CK_MECHANISM_TYPE mechanisms[] = {
+    CKM_NSS_JPAKE_ROUND1_SHA256,
+    CKM_NSS_JPAKE_ROUND2_SHA256,
+    CKM_NSS_JPAKE_FINAL_SHA256
+  };
+
+  PK11SlotInfo * slot = PK11_GetBestSlotMultiple(mechanisms,
+                                                 NUM_ELEM(mechanisms), NULL);
+  NS_ENSURE_STATE(slot != NULL);
+    
+  CK_BYTE pBuf[(NUM_ELEM(p) - 1) / 2];
+  CK_BYTE qBuf[(NUM_ELEM(q) - 1) / 2];
+  CK_BYTE gBuf[(NUM_ELEM(g) - 1) / 2];
+    
+  CK_KEY_TYPE keyType = CKK_NSS_JPAKE_ROUND1;
+  NS_ENSURE_STATE(fromHex(p, pBuf, (NUM_ELEM(p) - 1)));
+  NS_ENSURE_STATE(fromHex(q, qBuf, (NUM_ELEM(q) - 1)));
+  NS_ENSURE_STATE(fromHex(g, gBuf, (NUM_ELEM(g) - 1)));
+  CK_ATTRIBUTE keyTemplate[] = {
+    { CKA_NSS_JPAKE_SIGNERID, (CK_BYTE *) aSignerID.Data(),
+                              aSignerID.Length() },
+    { CKA_KEY_TYPE, &keyType, sizeof keyType },
+    { CKA_PRIME,    pBuf,     sizeof pBuf },
+    { CKA_SUBPRIME, qBuf,     sizeof qBuf },
+    { CKA_BASE,     gBuf,     sizeof gBuf }
+  };
+
+  CK_BYTE gx1Buf[NUM_ELEM(p) / 2];
+  CK_BYTE gv1Buf[NUM_ELEM(p) / 2];
+  CK_BYTE r1Buf [NUM_ELEM(p) / 2];
+  CK_BYTE gx2Buf[NUM_ELEM(p) / 2];
+  CK_BYTE gv2Buf[NUM_ELEM(p) / 2];
+  CK_BYTE r2Buf [NUM_ELEM(p) / 2];
+  CK_NSS_JPAKERound1Params rp = {
+      { gx1Buf, sizeof gx1Buf, gv1Buf, sizeof gv1Buf, r1Buf, sizeof r1Buf },
+      { gx2Buf, sizeof gx2Buf, gv2Buf, sizeof gv2Buf, r2Buf, sizeof r2Buf }
+  };
+  SECItem paramsItem;
+  paramsItem.data = (unsigned char *) &rp;
+  paramsItem.len = sizeof rp;
+  key = PK11_KeyGenWithTemplate(slot, CKM_NSS_JPAKE_ROUND1_SHA256,
+                                CKM_NSS_JPAKE_ROUND1_SHA256,
+                                &paramsItem, keyTemplate,
+                                NUM_ELEM(keyTemplate), NULL);
+  nsresult rv = key != NULL
+              ? NS_OK
+              : mapErrno();
+  if (rv == NS_OK) {
+    NS_ENSURE_TRUE(toHexString(rp.gx1.pGX, rp.gx1.ulGXLen, aGX1) &&
+                   toHexString(rp.gx1.pGV, rp.gx1.ulGVLen, aGV1) &&
+                   toHexString(rp.gx1.pR,  rp.gx1.ulRLen,  aR1)  &&
+                   toHexString(rp.gx2.pGX, rp.gx2.ulGXLen, aGX2) &&
+                   toHexString(rp.gx2.pGV, rp.gx2.ulGVLen, aGV2) &&
+                   toHexString(rp.gx2.pR,  rp.gx2.ulRLen,  aR2),
+                   NS_ERROR_OUT_OF_MEMORY);
+    round = JPAKEBeforeRound2;
+  }
+  return rv;
+}
+
+NS_IMETHODIMP nsSyncJPAKE::Round2(const nsACString & aPeerID,
+                                  const nsACString & aPIN,
+                                  const nsACString & aGX3,
+                                  const nsACString & aGV3,
+                                  const nsACString & aR3,
+                                  const nsACString & aGX4,
+                                  const nsACString & aGV4,
+                                  const nsACString & aR4,
+                                  nsACString & aA NS_OUTPARAM,
+                                  nsACString & aGVA NS_OUTPARAM,
+                                  nsACString & aRA NS_OUTPARAM)
+{
+  NS_ENSURE_STATE(round == JPAKEBeforeRound2);
+  NS_ENSURE_STATE(key != NULL);
+  NS_ENSURE_ARG(!aPeerID.IsEmpty());
+
+  /* PIN cannot be equal to zero when converted to a bignum. NSS 3.12.9 J-PAKE
+     assumes that the caller has already done this check. Future versions of 
+     NSS J-PAKE will do this check internally. See Bug 609068 Comment 4 */
+  PRBool foundNonZero = PR_FALSE;
+  for (size_t i = 0; i < aPIN.Length(); ++i) {
+    if (aPIN[i] != 0) {
+      foundNonZero = PR_TRUE;
+      break;
+    }
+  }
+  NS_ENSURE_ARG(foundNonZero);
+
+  CK_BYTE gx3Buf[NUM_ELEM(p)/2], gv3Buf[NUM_ELEM(p)/2], r3Buf [NUM_ELEM(p)/2];
+  CK_BYTE gx4Buf[NUM_ELEM(p)/2], gv4Buf[NUM_ELEM(p)/2], r4Buf [NUM_ELEM(p)/2];
+  CK_BYTE gxABuf[NUM_ELEM(p)/2], gvABuf[NUM_ELEM(p)/2], rABuf [NUM_ELEM(p)/2];
+  nsresult         rv = fromHexString(aGX3, gx3Buf, sizeof gx3Buf);
+  if (rv == NS_OK) rv = fromHexString(aGV3, gv3Buf, sizeof gv3Buf);
+  if (rv == NS_OK) rv = fromHexString(aR3,  r3Buf,  sizeof r3Buf);
+  if (rv == NS_OK) rv = fromHexString(aGX4, gx4Buf, sizeof gx4Buf);
+  if (rv == NS_OK) rv = fromHexString(aGV4, gv4Buf, sizeof gv4Buf);
+  if (rv == NS_OK) rv = fromHexString(aR4,  r4Buf,  sizeof r4Buf);
+  if (rv != NS_OK)
+    return rv;
+
+  CK_NSS_JPAKERound2Params rp;
+  rp.pSharedKey = (CK_BYTE *) aPIN.Data();
+  rp.ulSharedKeyLen = aPIN.Length();
+  rp.gx3.pGX = gx3Buf; rp.gx3.ulGXLen = aGX3.Length() / 2;
+  rp.gx3.pGV = gv3Buf; rp.gx3.ulGVLen = aGV3.Length() / 2;
+  rp.gx3.pR  = r3Buf;  rp.gx3.ulRLen  = aR3 .Length() / 2;
+  rp.gx4.pGX = gx4Buf; rp.gx4.ulGXLen = aGX4.Length() / 2;
+  rp.gx4.pGV = gv4Buf; rp.gx4.ulGVLen = aGV4.Length() / 2;
+  rp.gx4.pR  = r4Buf;  rp.gx4.ulRLen  = aR4 .Length() / 2;
+  rp.A.pGX   = gxABuf; rp.A  .ulGXLen = sizeof gxABuf;
+  rp.A.pGV   = gvABuf; rp.A  .ulGVLen = sizeof gxABuf;
+  rp.A.pR    = rABuf;  rp.A  .ulRLen  = sizeof gxABuf;
+  SECItem paramsItem;
+  paramsItem.data = (unsigned char *) &rp;
+  paramsItem.len = sizeof rp;
+  CK_KEY_TYPE keyType = CKK_NSS_JPAKE_ROUND2;
+  CK_ATTRIBUTE keyTemplate[] = {
+    { CKA_NSS_JPAKE_PEERID, (CK_BYTE *) aPeerID.Data(), aPeerID.Length(), },
+    { CKA_KEY_TYPE, &keyType, sizeof keyType }
+  };
+  PK11SymKey * newKey = PK11_DeriveWithTemplate(key,
+                                                CKM_NSS_JPAKE_ROUND2_SHA256,
+                                                &paramsItem,
+                                                CKM_NSS_JPAKE_FINAL_SHA256,
+                                                CKA_DERIVE, 0,
+                                                keyTemplate,
+                                                NUM_ELEM(keyTemplate),
+                                                PR_FALSE);
+  if (newKey != NULL) {
+    if (toHexString(rp.A.pGX, rp.A.ulGXLen, aA) &&
+        toHexString(rp.A.pGV, rp.A.ulGVLen, aGVA) &&
+        toHexString(rp.A.pR, rp.A.ulRLen, aRA)) {
+      round = JPAKEAfterRound2;
+      PK11_FreeSymKey(key);
+      key = newKey;
+      return NS_OK;
+    } else {
+      PK11_FreeSymKey(newKey);
+      rv = NS_ERROR_OUT_OF_MEMORY;
+    }
+  } else
+    rv = mapErrno();
+
+  return rv;
+}
+
+static nsresult
+setBase64(const unsigned char * data, unsigned len, nsACString & out)
+{
+  nsresult rv = NS_OK;
+  const char * base64 = BTOA_DataToAscii(data, len);
+  
+  if (base64 != NULL) {
+    size_t len = PORT_Strlen(base64);
+    if (out.SetCapacity(len)) {
+      out.SetLength(0);
+      out.Append(base64, len);
+      PORT_Free((void*) base64);
+    } else {
+      rv = NS_ERROR_OUT_OF_MEMORY;
+    }
+  } else {
+    rv = NS_ERROR_OUT_OF_MEMORY;
+  }
+  return rv;
+}
+
+static nsresult
+base64KeyValue(PK11SymKey * key, nsACString & keyString)
+{
+  nsresult rv = NS_OK;
+  if (PK11_ExtractKeyValue(key) == SECSuccess) {
+    const SECItem * value = PK11_GetKeyData(key);
+    rv = value != NULL && value->data != NULL && value->len > 0
+       ? setBase64(value->data, value->len, keyString)
+       : NS_ERROR_UNEXPECTED;
+  } else {
+    rv = mapErrno();
+  }
+  return rv;
+}
+
+static nsresult
+extractBase64KeyValue(PK11SymKey * keyBlock, CK_ULONG bitPosition,
+                      CK_MECHANISM_TYPE destMech, int keySize,
+                      nsACString & keyString)
+{
+  SECItem paramsItem;
+  paramsItem.data = (CK_BYTE *) &bitPosition;
+  paramsItem.len = sizeof bitPosition;
+  PK11SymKey * key = PK11_Derive(keyBlock, CKM_EXTRACT_KEY_FROM_KEY,
+                                 &paramsItem, destMech,
+                                 CKA_SIGN, keySize);
+  if (key == NULL)
+    return mapErrno();
+  nsresult rv = base64KeyValue(key, keyString);
+  PK11_FreeSymKey(key);
+  return rv;
+}
+
+
+NS_IMETHODIMP nsSyncJPAKE::Final(const nsACString & aB,
+                                 const nsACString & aGVB,
+                                 const nsACString & aRB,
+                                 const nsACString & aHKDFInfo,
+                                 nsACString & aAES256Key NS_OUTPARAM,
+                                 nsACString & aHMAC256Key NS_OUTPARAM)
+{
+  static const unsigned AES256_KEY_SIZE = 256 / 8;
+  static const unsigned HMAC_SHA256_KEY_SIZE = 256 / 8;
+  CK_EXTRACT_PARAMS aesBitPosition = 0;
+  CK_EXTRACT_PARAMS hmacBitPosition = aesBitPosition + (AES256_KEY_SIZE * 8);
+
+  NS_ENSURE_STATE(round == JPAKEAfterRound2);
+  NS_ENSURE_STATE(key != NULL);
+
+  CK_BYTE gxBBuf[NUM_ELEM(p)/2], gvBBuf[NUM_ELEM(p)/2], rBBuf [NUM_ELEM(p)/2];
+  nsresult         rv = fromHexString(aB,   gxBBuf, sizeof gxBBuf);
+  if (rv == NS_OK) rv = fromHexString(aGVB, gvBBuf, sizeof gvBBuf);
+  if (rv == NS_OK) rv = fromHexString(aRB,  rBBuf,  sizeof rBBuf);
+  if (rv != NS_OK)
+    return rv;
+
+  CK_NSS_JPAKEFinalParams rp;
+  rp.B.pGX = gxBBuf; rp.B.ulGXLen = aB  .Length() / 2;
+  rp.B.pGV = gvBBuf; rp.B.ulGVLen = aGVB.Length() / 2;
+  rp.B.pR  = rBBuf;  rp.B.ulRLen  = aRB .Length() / 2;
+  SECItem paramsItem;
+  paramsItem.data = (unsigned char *) &rp;
+  paramsItem.len = sizeof rp;
+  PK11SymKey * keyMaterial = PK11_Derive(key, CKM_NSS_JPAKE_FINAL_SHA256,
+                                         &paramsItem, CKM_NSS_HKDF_SHA256,
+                                         CKA_DERIVE, 0);
+  PK11SymKey * keyBlock = NULL;
+
+  if (keyMaterial == NULL)
+    rv = mapErrno();
+
+  if (rv == NS_OK) {
+    CK_NSS_HKDFParams hkdfParams;
+    hkdfParams.bExtract = CK_TRUE;
+    hkdfParams.pSalt = NULL;
+    hkdfParams.ulSaltLen = 0;
+    hkdfParams.bExpand = CK_TRUE;
+    hkdfParams.pInfo = (CK_BYTE *) aHKDFInfo.Data();
+    hkdfParams.ulInfoLen = aHKDFInfo.Length();
+    paramsItem.data = (unsigned char *) &hkdfParams;
+    paramsItem.len = sizeof hkdfParams;
+    keyBlock = PK11_Derive(keyMaterial, CKM_NSS_HKDF_SHA256,
+                           &paramsItem, CKM_EXTRACT_KEY_FROM_KEY,
+                           CKA_DERIVE, AES256_KEY_SIZE + HMAC_SHA256_KEY_SIZE);
+    if (keyBlock == NULL)
+      rv = mapErrno();
+  }
+
+  if (rv == NS_OK) {
+    rv = extractBase64KeyValue(keyBlock, aesBitPosition, CKM_AES_CBC,
+                               AES256_KEY_SIZE, aAES256Key);
+  }
+  if (rv == NS_OK) {
+    rv = extractBase64KeyValue(keyBlock, hmacBitPosition, CKM_SHA256_HMAC,
+                               HMAC_SHA256_KEY_SIZE, aHMAC256Key);
+  }
+
+  if (rv == NS_OK) {
+    SECStatus srv = PK11_ExtractKeyValue(keyMaterial);
+    NS_ENSURE_TRUE(srv == SECSuccess, NS_ERROR_UNEXPECTED); // XXX leaks
+    SECItem * keyMaterialBytes = PK11_GetKeyData(keyMaterial);
+    NS_ENSURE_TRUE(keyMaterialBytes != NULL, NS_ERROR_UNEXPECTED);
+  }
+
+  if (keyBlock != NULL)
+    PK11_FreeSymKey(keyBlock);
+  if (keyMaterial != NULL)
+    PK11_FreeSymKey(keyMaterial);
+
+  return rv;
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsSyncJPAKE)
+NS_DEFINE_NAMED_CID(NS_SYNCJPAKE_CID);
+
+nsSyncJPAKE::nsSyncJPAKE() : round(JPAKENotStarted), key(NULL) { }
+
+nsSyncJPAKE::~nsSyncJPAKE()
+{
+  if (key != NULL)
+    PK11_FreeSymKey(key);
+}
+
+static const mozilla::Module::CIDEntry kServicesCryptoCIDs[] = {
+  { &kNS_SYNCJPAKE_CID, false, NULL, nsSyncJPAKEConstructor },
+  { NULL }
+};
+
+static const mozilla::Module::ContractIDEntry kServicesCryptoContracts[] = {
+  { NS_SYNCJPAKE_CONTRACTID, &kNS_SYNCJPAKE_CID },
+  { NULL }
+};
+
+static const mozilla::Module kServicesCryptoModule = {
+  mozilla::Module::kVersion,
+  kServicesCryptoCIDs,
+  kServicesCryptoContracts
+};
+
+NSMODULE_DEFN(nsServicesCryptoModule) = &kServicesCryptoModule;
new file mode 100644
--- /dev/null
+++ b/services/crypto/nsSyncJPAKE.h
@@ -0,0 +1,65 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Sync.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Brian Smith <bsmith@mozilla.com>
+ * Philipp von Weitershausen <philipp@weitershausen.de>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#ifndef nsSyncJPAKE_h__
+#define nsSyncJPAKE_h__
+
+#include "nsISyncJPAKE.h"
+
+#define NS_SYNCJPAKE_CONTRACTID \
+  "@mozilla.org/services-crypto/sync-jpake;1"
+
+#define NS_SYNCJPAKE_CID \
+  {0x0b9721c0, 0x1805, 0x47c3, {0x86, 0xce, 0x68, 0x13, 0x79, 0x5a, 0x78, 0x3f}}
+
+typedef struct PK11SymKeyStr PK11SymKey;
+
+class nsSyncJPAKE : public nsISyncJPAKE
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSISYNCJPAKE
+  nsSyncJPAKE();
+  virtual ~nsSyncJPAKE();
+private:
+  enum { JPAKENotStarted, JPAKEBeforeRound2, JPAKEAfterRound2 } round;
+  PK11SymKey * key;
+};
+
+NS_IMPL_ISUPPORTS1(nsSyncJPAKE, nsISyncJPAKE)
+
+#endif // nsSyncJPAKE_h__
--- a/toolkit/library/libxul-config.mk
+++ b/toolkit/library/libxul-config.mk
@@ -345,16 +345,20 @@ DEFINES += -DMOZ_SPELLCHECK
 COMPONENT_LIBS += spellchecker
 endif
 
 ifdef MOZ_ZIPWRITER
 DEFINES += -DMOZ_ZIPWRITER
 COMPONENT_LIBS += zipwriter
 endif
 
+ifdef MOZ_SERVICES_SYNC
+COMPONENT_LIBS += services-crypto
+endif
+
 ifdef MOZ_DEBUG
 ifdef ENABLE_TESTS
 COMPONENT_LIBS += gkdebug
 endif
 endif
 
 ifdef MOZ_APP_COMPONENT_LIBS
 COMPONENT_LIBS += $(MOZ_APP_COMPONENT_LIBS)
--- a/toolkit/library/nsStaticXULComponents.cpp
+++ b/toolkit/library/nsStaticXULComponents.cpp
@@ -228,16 +228,22 @@
 #endif
 
 #if defined(BUILD_CTYPES)
 #define JSCTYPES_MODULE MODULE(jsctypes)
 #else
 #define JSCTYPES_MODULE
 #endif
 
+#ifdef MOZ_SERVICES_SYNC
+#define SERVICES_CRYPTO_MODULE MODULE(nsServicesCryptoModule)
+#else
+#define SERVICES_CRYPTO_MODULE
+#endif
+
 #if defined(MOZ_APP_COMPONENT_INCLUDE)
 #include MOZ_APP_COMPONENT_INCLUDE
 #else
 #define APP_COMPONENT_MODULES
 #endif
 
 #define XUL_MODULES                          \
     MODULE(nsUConvModule)                    \
@@ -283,16 +289,17 @@
     SYSTEMPREF_MODULES                       \
     SPELLCHECK_MODULE                        \
     LAYOUT_DEBUG_MODULE                      \
     UNIXPROXY_MODULE                         \
     OSXPROXY_MODULE                          \
     WINDOWSPROXY_MODULE                      \
     JSCTYPES_MODULE                          \
     MODULE(jsperf)                           \
+    SERVICES_CRYPTO_MODULE                   \
     APP_COMPONENT_MODULES                    \
     /* end of list */
 
 #define MODULE(_name) \
   NSMODULE_DECL(_name);
 
 XUL_MODULES
 
--- a/toolkit/toolkit-tiers.mk
+++ b/toolkit/toolkit-tiers.mk
@@ -230,16 +230,20 @@ tier_platform_dirs	+= security/manager
 else
 tier_platform_dirs	+= security/manager/boot/public security/manager/ssl/public
 endif
 
 ifdef MOZ_PREF_EXTENSIONS
 tier_platform_dirs += extensions/pref
 endif
 
+ifdef MOZ_SERVICES_SYNC
+tier_platform_dirs += services/crypto
+endif
+
 # JavaXPCOM JNI code is compiled into libXUL
 ifdef MOZ_JAVAXPCOM
 tier_platform_dirs += extensions/java/xpcom/src
 endif
 
 ifndef BUILD_STATIC_LIBS
 ifneq (,$(MOZ_ENABLE_GTK2))
 tier_platform_dirs += embedding/browser/gtk