Bug 753238 - Identity (BrowserID) module. r=dolske,rrelyea
authorJed Parsons <jparsons@mozilla.com>
Tue, 03 Jul 2012 14:53:37 -0700
changeset 98573 88070ff09ccd685ece07d0219eccc6e7992be145
parent 98572 921d27b8a76e83cee2023dfb2c3fa501ad49ab14
child 98574 2699c091b91b16fa2e97ff4f6485cd70409cb92a
push id11544
push usermozilla@noorenberghe.ca
push dateFri, 06 Jul 2012 23:40:29 +0000
treeherdermozilla-inbound@2699c091b91b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske, rrelyea
bugs753238
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 753238 - Identity (BrowserID) module. r=dolske,rrelyea
browser/installer/package-manifest.in
toolkit/identity/Identity.jsm
toolkit/identity/IdentityCryptoService.cpp
toolkit/identity/IdentityProvider.jsm
toolkit/identity/IdentityStore.jsm
toolkit/identity/LogUtils.jsm
toolkit/identity/Makefile.in
toolkit/identity/RelyingParty.jsm
toolkit/identity/jwcrypto.jsm
toolkit/identity/nsIIdentityCryptoService.idl
toolkit/library/Makefile.in
toolkit/library/nsStaticXULComponents.cpp
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -214,16 +214,17 @@
 #ifdef MOZ_GTK2
 @BINPATH@/components/filepicker.xpt
 #endif
 @BINPATH@/components/find.xpt
 @BINPATH@/components/fuel.xpt
 @BINPATH@/components/gfx.xpt
 @BINPATH@/components/html5.xpt
 @BINPATH@/components/htmlparser.xpt
+@BINPATH@/components/identity.xpt
 @BINPATH@/components/imglib2.xpt
 @BINPATH@/components/imgicon.xpt
 @BINPATH@/components/inspector.xpt
 @BINPATH@/components/intl.xpt
 @BINPATH@/components/jar.xpt
 #ifdef MOZ_JSDEBUGGER
 @BINPATH@/components/jsdservice.xpt
 #endif
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/Identity.jsm
@@ -0,0 +1,305 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["IdentityService"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+Cu.import("resource://gre/modules/identity/IdentityStore.jsm");
+Cu.import("resource://gre/modules/identity/RelyingParty.jsm");
+Cu.import("resource://gre/modules/identity/IdentityProvider.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+                                  "jwcrypto",
+                                  "resource://gre/modules/identity/jwcrypto.jsm");
+
+function log(...aMessageArgs) {
+  Logger.log([null].concat(aMessageArgs));
+}
+function reportError(...aMessageArgs) {
+  Logger.reportError([null].concat(aMessageArgs));
+}
+
+function IDService() {
+  Services.obs.addObserver(this, "quit-application-granted", false);
+  Services.obs.addObserver(this, "identity-auth-complete", false);
+
+  this._store = IdentityStore;
+  this.RP = RelyingParty;
+  this.IDP = IdentityProvider;
+}
+
+IDService.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "quit-application-granted":
+        Services.obs.removeObserver(this, "quit-application-granted");
+        this.shutdown();
+        break;
+      case "identity-auth-complete":
+        if (!aSubject || !aSubject.wrappedJSObject)
+          break;
+        let subject = aSubject.wrappedJSObject;
+        log("NOW SELECT", aSubject.wrappedJSObject);
+        // We have authenticated in order to provision an identity.
+        // So try again.
+        this.selectIdentity(subject.rpId, subject.identity);
+        break;
+    }
+  },
+
+  reset: function reset() {
+    // Explicitly call reset() on our RP and IDP classes.
+    // This is here to make testing easier.  When the
+    // quit-application-granted signal is emitted, reset() will be
+    // called here, on RP, on IDP, and on the store.  So you don't
+    // need to use this :)
+    this._store.reset();
+    this.RP.reset();
+    this.IDP.reset();
+  },
+
+  shutdown: function shutdown() {
+    log("shutdown");
+    Services.obs.removeObserver(this, "identity-auth-complete");
+    Services.obs.removeObserver(this, "quit-application-granted");
+  },
+
+  /**
+   * Parse an email into username and domain if it is valid, else return null
+   */
+  parseEmail: function parseEmail(email) {
+    var match = email.match(/^([^@]+)@([^@^/]+.[a-z]+)$/);
+    if (match) {
+      return {
+        username: match[1],
+        domain: match[2]
+      };
+    }
+    return null;
+  },
+
+  /**
+   * The UX wants to add a new identity
+   * often followed by selectIdentity()
+   *
+   * @param aIdentity
+   *        (string) the email chosen for login
+   */
+  addIdentity: function addIdentity(aIdentity) {
+    if (this._store.fetchIdentity(aIdentity) === null) {
+      this._store.addIdentity(aIdentity, null, null);
+    }
+  },
+
+  /**
+   * The UX comes back and calls selectIdentity once the user has picked
+   * an identity.
+   *
+   * @param aRPId
+   *        (integer) the id of the doc object obtained in .watch() and
+   *                  passed to the UX component.
+   *
+   * @param aIdentity
+   *        (string) the email chosen for login
+   */
+  selectIdentity: function selectIdentity(aRPId, aIdentity) {
+    log("selectIdentity: RP id:", aRPId, "identity:", aIdentity);
+
+    // Get the RP that was stored when watch() was invoked.
+    let rp = this.RP._rpFlows[aRPId];
+    if (!rp) {
+      reportError("selectIdentity", "Invalid RP id: ", aRPId);
+      return;
+    }
+
+    // It's possible that we are in the process of provisioning an
+    // identity.
+    let provId = rp.provId;
+
+    let rpLoginOptions = {
+      loggedInEmail: aIdentity,
+      origin: rp.origin
+    };
+    log("selectIdentity: provId:", provId, "origin:", rp.origin);
+
+    // Once we have a cert, and once the user is authenticated with the
+    // IdP, we can generate an assertion and deliver it to the doc.
+    let self = this;
+    this.RP._generateAssertion(rp.origin, aIdentity, function hadReadyAssertion(err, assertion) {
+      if (!err && assertion) {
+        self.RP._doLogin(rp, rpLoginOptions, assertion);
+        return;
+
+      }
+      // Need to provision an identity first.  Begin by discovering
+      // the user's IdP.
+      self._discoverIdentityProvider(aIdentity, function gotIDP(err, idpParams) {
+        if (err) {
+          rp.doError(err);
+          return;
+        }
+
+        // The idpParams tell us where to go to provision and authenticate
+        // the identity.
+        self.IDP._provisionIdentity(aIdentity, idpParams, provId, function gotID(err, aProvId) {
+
+          // Provision identity may have created a new provision flow
+          // for us.  To make it easier to relate provision flows with
+          // RP callers, we cross index the two here.
+          rp.provId = aProvId;
+          self.IDP._provisionFlows[aProvId].rpId = aRPId;
+
+          // At this point, we already have a cert.  If the user is also
+          // already authenticated with the IdP, then we can try again
+          // to generate an assertion and login.
+          if (err) {
+            // We are not authenticated.  If we have already tried to
+            // authenticate and failed, then this is a "hard fail" and
+            // we give up.  Otherwise we try to authenticate with the
+            // IdP.
+
+            if (self.IDP._provisionFlows[aProvId].didAuthentication) {
+              self.IDP._cleanUpProvisionFlow(aProvId);
+              self.RP._cleanUpProvisionFlow(aRPId, aProvId);
+              log("ERROR: selectIdentity: authentication hard fail");
+              rp.doError("Authentication fail.");
+              return;
+            }
+            // Try to authenticate with the IdP.  Note that we do
+            // not clean up the provision flow here.  We will continue
+            // to use it.
+            self.IDP._doAuthentication(aProvId, idpParams);
+            return;
+          }
+
+          // Provisioning flows end when a certificate has been registered.
+          // Thus IdentityProvider's registerCertificate() cleans up the
+          // current provisioning flow.  We only do this here on error.
+          self.RP._generateAssertion(rp.origin, aIdentity, function gotAssertion(err, assertion) {
+            if (err) {
+              rp.doError(err);
+              return;
+            }
+            self.RP._doLogin(rp, rpLoginOptions, assertion);
+            self.RP._cleanUpProvisionFlow(aRPId, aProvId);
+            return;
+          });
+        });
+      });
+    });
+  },
+
+  // methods for chrome and add-ons
+
+  /**
+   * Discover the IdP for an identity
+   *
+   * @param aIdentity
+   *        (string) the email we're logging in with
+   *
+   * @param aCallback
+   *        (function) callback to invoke on completion
+   *                   with first-positional parameter the error.
+   */
+  _discoverIdentityProvider: function _discoverIdentityProvider(aIdentity, aCallback) {
+    // XXX bug 767610 - validate email address call
+    // When that is available, we can remove this custom parser
+    var parsedEmail = this.parseEmail(aIdentity);
+    if (parsedEmail === null) {
+      return aCallback("Could not parse email: " + aIdentity);
+    }
+    log("_discoverIdentityProvider: identity:", aIdentity, "domain:", parsedEmail.domain);
+
+    this._fetchWellKnownFile(parsedEmail.domain, function fetchedWellKnown(err, idpParams) {
+      // idpParams includes the pk, authorization url, and
+      // provisioning url.
+
+      // XXX bug 769861 follow any authority delegations
+      // if no well-known at any point in the delegation
+      // fall back to browserid.org as IdP
+      return aCallback(err, idpParams);
+    });
+  },
+
+  /**
+   * Fetch the well-known file from the domain.
+   *
+   * @param aDomain
+   *
+   * @param aScheme
+   *        (string) (optional) Protocol to use.  Default is https.
+   *                 This is necessary because we are unable to test
+   *                 https.
+   *
+   * @param aCallback
+   *
+   */
+  _fetchWellKnownFile: function _fetchWellKnownFile(aDomain, aCallback, aScheme='https') {
+    // XXX bug 769854 make tests https and remove aScheme option
+    let url = aScheme + '://' + aDomain + "/.well-known/browserid";
+    log("_fetchWellKnownFile:", url);
+
+    // this appears to be a more successful way to get at xmlhttprequest (which supposedly will close with a window
+    let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+                .getService(Ci.nsIXMLHttpRequest);
+
+    // XXX bug 769865 gracefully handle being off-line
+    // XXX bug 769866 decide on how to handle redirects
+    req.open("GET", url, true);
+    req.responseType = "json";
+    req.mozBackgroundRequest = true;
+    req.onload = function _fetchWellKnownFile_onload() {
+      if (req.status < 200 || req.status >= 400) {
+        log("_fetchWellKnownFile", url, ": server returned status:", req.status);
+        return aCallback("Error");
+      }
+      try {
+        let idpParams = req.response;
+
+        // Verify that the IdP returned a valid configuration
+        if (! (idpParams.provisioning &&
+            idpParams.authentication &&
+            idpParams['public-key'])) {
+          let errStr= "Invalid well-known file from: " + aDomain;
+          log("_fetchWellKnownFile:", errStr);
+          return aCallback(errStr);
+        }
+
+        let callbackObj = {
+          domain: aDomain,
+          idpParams: idpParams,
+        };
+        log("_fetchWellKnownFile result: ", callbackObj);
+        // Yay.  Valid IdP configuration for the domain.
+        return aCallback(null, callbackObj);
+
+      } catch (err) {
+        reportError("_fetchWellKnownFile", "Bad configuration from", aDomain, err);
+        return aCallback(err.toString());
+      }
+    };
+    req.onerror = function _fetchWellKnownFile_onerror() {
+      log("_fetchWellKnownFile", "ERROR:", req.status, req.statusText);
+      log("ERROR: _fetchWellKnownFile:", err);
+      return aCallback("Error");
+    };
+    req.send(null);
+  },
+
+};
+
+let IdentityService = new IDService();
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/IdentityCryptoService.cpp
@@ -0,0 +1,601 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIIdentityCryptoService.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNSSShutDown.h"
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "mozilla/Base64.h"
+
+#include "nss.h"
+#include "pk11pub.h"
+#include "secmod.h"
+#include "secerr.h"
+#include "keyhi.h"
+#include "cryptohi.h"
+
+using namespace mozilla;
+
+namespace {
+
+void
+HexEncode(const SECItem * it, nsACString & result)
+{
+  const char * digits = "0123456789ABCDEF";
+  result.SetCapacity((it->len * 2) + 1);
+  result.SetLength(it->len * 2);
+  char * p = result.BeginWriting();
+  for (unsigned int i = 0; i < it->len; ++i) {
+    *p++ = digits[it->data[i] >> 4];
+    *p++ = digits[it->data[i] & 0x0f];
+  }
+}
+
+nsresult
+Base64UrlEncodeImpl(const nsACString & utf8Input, nsACString & result)
+{
+  nsresult rv = Base64Encode(utf8Input, result);
+
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsACString::char_type * out = result.BeginWriting();
+  nsACString::size_type length = result.Length();
+  // base64url encoding is defined in RFC 4648. It replaces the last two
+  // alphabet characters of base64 encoding with '-' and '_' respectively.
+  for (unsigned int i = 0; i < length; ++i) {
+    if (out[i] == '+') {
+      out[i] = '-';
+    } else if (out[i] == '/') {
+      out[i] = '_';
+    }
+  }
+
+  return NS_OK;
+}
+
+
+nsresult
+PRErrorCode_to_nsresult(PRErrorCode error)
+{
+  if (!error) {
+    MOZ_NOT_REACHED("Function failed without calling PR_GetError");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  // From NSSErrorsService::GetXPCOMFromNSSError
+  return NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_SECURITY, -1 * error);
+}
+
+// IMPORTANT: This must be called immediately after the function returning the
+// SECStatus result. The recommended usage is:
+//    nsresult rv = MapSECStatus(f(x, y, z));
+nsresult
+MapSECStatus(SECStatus rv)
+{
+  if (rv == SECSuccess)
+    return NS_OK;
+
+  PRErrorCode error = PR_GetError();
+  return PRErrorCode_to_nsresult(error);
+}
+
+#define DSA_KEY_TYPE_STRING (NS_LITERAL_CSTRING("DS160"))
+#define RSA_KEY_TYPE_STRING (NS_LITERAL_CSTRING("RS256"))
+
+class KeyPair : public nsIIdentityKeyPair, public nsNSSShutDownObject
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIIDENTITYKEYPAIR
+
+  KeyPair(SECKEYPrivateKey* aPrivateKey, SECKEYPublicKey* aPublicKey);
+
+private:
+  ~KeyPair()
+  {
+    destructorSafeDestroyNSSReference();
+    shutdown(calledFromObject);
+  }
+
+  void virtualDestroyNSSReference() MOZ_OVERRIDE
+  {
+    destructorSafeDestroyNSSReference();
+  }
+
+  void destructorSafeDestroyNSSReference()
+  {
+    nsNSSShutDownPreventionLock locker;
+    if (isAlreadyShutDown())
+      return;
+
+    SECKEY_DestroyPrivateKey(mPrivateKey);
+    mPrivateKey = NULL;
+    SECKEY_DestroyPublicKey(mPublicKey);
+    mPublicKey = NULL;
+  }
+
+  SECKEYPrivateKey * mPrivateKey;
+  SECKEYPublicKey * mPublicKey;
+
+  KeyPair(const KeyPair &) MOZ_DELETE;
+  void operator=(const KeyPair &) MOZ_DELETE;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(KeyPair, nsIIdentityKeyPair)
+
+class KeyGenRunnable : public nsRunnable, public nsNSSShutDownObject
+{
+public:
+  NS_DECL_NSIRUNNABLE
+
+  KeyGenRunnable(KeyType keyType, nsIIdentityKeyGenCallback * aCallback);
+
+private:
+  ~KeyGenRunnable()
+  {
+    destructorSafeDestroyNSSReference();
+    shutdown(calledFromObject);
+  }
+
+  virtual void virtualDestroyNSSReference() MOZ_OVERRIDE
+  {
+    destructorSafeDestroyNSSReference();
+  }
+
+  void destructorSafeDestroyNSSReference()
+  {
+    nsNSSShutDownPreventionLock locker;
+    if (isAlreadyShutDown())
+      return;
+
+     mKeyPair = NULL;
+  }
+
+  const KeyType mKeyType; // in
+  nsCOMPtr<nsIIdentityKeyGenCallback> mCallback; // in
+  nsresult mRv; // out
+  nsCOMPtr<KeyPair> mKeyPair; // out
+
+  KeyGenRunnable(const KeyGenRunnable &) MOZ_DELETE;
+  void operator=(const KeyGenRunnable &) MOZ_DELETE;
+};
+
+class SignRunnable : public nsRunnable, public nsNSSShutDownObject
+{
+public:
+  NS_DECL_NSIRUNNABLE
+
+  SignRunnable(const nsACString & textToSign, SECKEYPrivateKey * privateKey,
+               nsIIdentitySignCallback * aCallback);
+
+private:
+  ~SignRunnable()
+  {
+    destructorSafeDestroyNSSReference();
+    shutdown(calledFromObject);
+  }
+
+  void virtualDestroyNSSReference() MOZ_OVERRIDE
+  {
+    destructorSafeDestroyNSSReference();
+  }
+
+  void destructorSafeDestroyNSSReference()
+  {
+    nsNSSShutDownPreventionLock locker;
+    if (isAlreadyShutDown())
+      return;
+
+    SECKEY_DestroyPrivateKey(mPrivateKey);
+    mPrivateKey = NULL;
+  }
+
+  const nsCString mTextToSign; // in
+  SECKEYPrivateKey* mPrivateKey; // in
+  const nsCOMPtr<nsIIdentitySignCallback> mCallback; // in
+  nsresult mRv; // out
+  nsCString mSignature; // out
+
+private:
+  SignRunnable(const SignRunnable &) MOZ_DELETE;
+  void operator=(const SignRunnable &) MOZ_DELETE;
+};
+
+class IdentityCryptoService : public nsIIdentityCryptoService
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIIDENTITYCRYPTOSERVICE
+
+  IdentityCryptoService() { }
+  nsresult Init()
+  {
+    nsresult rv;
+    nsCOMPtr<nsISupports> dummyUsedToEnsureNSSIsInitialized
+      = do_GetService("@mozilla.org/psm;1");
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+private:
+  IdentityCryptoService(const KeyPair &) MOZ_DELETE;
+  void operator=(const IdentityCryptoService &) MOZ_DELETE;
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(IdentityCryptoService, nsIIdentityCryptoService)
+
+NS_IMETHODIMP
+IdentityCryptoService::GenerateKeyPair(
+  const nsACString & keyTypeString, nsIIdentityKeyGenCallback * callback)
+{
+  KeyType keyType;
+  if (keyTypeString.Equals(RSA_KEY_TYPE_STRING)) {
+    keyType = rsaKey;
+  } else if (keyTypeString.Equals(DSA_KEY_TYPE_STRING)) {
+    keyType = dsaKey;
+  } else {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsCOMPtr<nsIRunnable> r = new KeyGenRunnable(keyType, callback);
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = NS_NewThread(getter_AddRefs(thread), r);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+IdentityCryptoService::Base64UrlEncode(const nsACString & utf8Input,
+                                       nsACString & result)
+{
+  return Base64UrlEncodeImpl(utf8Input, result);
+}
+
+KeyPair::KeyPair(SECKEYPrivateKey * privateKey, SECKEYPublicKey * publicKey)
+  : mPrivateKey(privateKey)
+  , mPublicKey(publicKey)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexRSAPublicKeyExponent(nsACString & result)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(mPublicKey->keyType == rsaKey, NS_ERROR_NOT_AVAILABLE);
+  HexEncode(&mPublicKey->u.rsa.publicExponent, result);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexRSAPublicKeyModulus(nsACString & result)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(mPublicKey->keyType == rsaKey, NS_ERROR_NOT_AVAILABLE);
+  HexEncode(&mPublicKey->u.rsa.modulus, result);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexDSAPrime(nsACString & result)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(mPublicKey->keyType == dsaKey, NS_ERROR_NOT_AVAILABLE);
+  HexEncode(&mPublicKey->u.dsa.params.prime, result);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexDSASubPrime(nsACString & result)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(mPublicKey->keyType == dsaKey, NS_ERROR_NOT_AVAILABLE);
+  HexEncode(&mPublicKey->u.dsa.params.subPrime, result);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexDSAGenerator(nsACString & result)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(mPublicKey->keyType == dsaKey, NS_ERROR_NOT_AVAILABLE);
+  HexEncode(&mPublicKey->u.dsa.params.base, result);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetHexDSAPublicValue(nsACString & result)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+  NS_ENSURE_TRUE(mPublicKey->keyType == dsaKey, NS_ERROR_NOT_AVAILABLE);
+  HexEncode(&mPublicKey->u.dsa.publicValue, result);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+KeyPair::GetKeyType(nsACString & result)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE(mPublicKey, NS_ERROR_NOT_AVAILABLE);
+
+  switch (mPublicKey->keyType) {
+    case rsaKey: result = RSA_KEY_TYPE_STRING; return NS_OK;
+    case dsaKey: result = DSA_KEY_TYPE_STRING; return NS_OK;
+    default: return NS_ERROR_UNEXPECTED;
+  }
+}
+
+NS_IMETHODIMP
+KeyPair::Sign(const nsACString & textToSign,
+              nsIIdentitySignCallback* callback)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  nsCOMPtr<nsIRunnable> r = new SignRunnable(textToSign, mPrivateKey,
+                                             callback);
+
+  nsCOMPtr<nsIThread> thread;
+  nsresult rv = NS_NewThread(getter_AddRefs(thread), r);
+  return rv;
+}
+
+KeyGenRunnable::KeyGenRunnable(KeyType keyType,
+                               nsIIdentityKeyGenCallback * callback)
+  : mKeyType(keyType)
+  , mCallback(callback)
+  , mRv(NS_ERROR_NOT_INITIALIZED)
+{
+}
+
+MOZ_WARN_UNUSED_RESULT nsresult
+GenerateKeyPair(PK11SlotInfo * slot,
+                NS_OUTPARAM SECKEYPrivateKey ** privateKey,
+                NS_OUTPARAM SECKEYPublicKey ** publicKey,
+                CK_MECHANISM_TYPE mechanism,
+                void * params)
+{
+  *publicKey = NULL;
+  *privateKey = PK11_GenerateKeyPair(slot, mechanism, params, publicKey,
+                                     PR_FALSE /*isPerm*/,
+                                     PR_TRUE /*isSensitive*/,
+                                     NULL /*&pwdata*/);
+  if (!*privateKey) {
+    MOZ_ASSERT(!*publicKey);
+    return PRErrorCode_to_nsresult(PR_GetError());
+  }
+  if (!*publicKey) {
+	SECKEY_DestroyPrivateKey(*privateKey);
+	*privateKey = NULL;
+    MOZ_NOT_REACHED("PK11_GnerateKeyPair returned private key without public "
+                    "key");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  return NS_OK;
+}
+
+
+MOZ_WARN_UNUSED_RESULT nsresult
+GenerateRSAKeyPair(PK11SlotInfo * slot,
+                   NS_OUTPARAM SECKEYPrivateKey ** privateKey,
+                   NS_OUTPARAM SECKEYPublicKey ** publicKey)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  PK11RSAGenParams rsaParams;
+  rsaParams.keySizeInBits = 2048;
+  rsaParams.pe = 0x10001;
+  return GenerateKeyPair(slot, privateKey, publicKey, CKM_RSA_PKCS_KEY_PAIR_GEN,
+                         &rsaParams);
+}
+
+MOZ_WARN_UNUSED_RESULT nsresult
+GenerateDSAKeyPair(PK11SlotInfo * slot,
+                   NS_OUTPARAM SECKEYPrivateKey ** privateKey,
+                   NS_OUTPARAM SECKEYPublicKey ** publicKey)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  // XXX: These could probably be static const arrays, but this way we avoid
+  // compiler warnings and also we avoid having to worry much about whether the
+  // functions that take these inputs will (unexpectedly) modify them.
+
+  // Using NIST parameters. Some other BrowserID components require that these
+  // exact parameters are used.
+  PRUint8 P[] = {
+    0xFF,0x60,0x04,0x83,0xDB,0x6A,0xBF,0xC5,0xB4,0x5E,0xAB,0x78,
+    0x59,0x4B,0x35,0x33,0xD5,0x50,0xD9,0xF1,0xBF,0x2A,0x99,0x2A,
+    0x7A,0x8D,0xAA,0x6D,0xC3,0x4F,0x80,0x45,0xAD,0x4E,0x6E,0x0C,
+    0x42,0x9D,0x33,0x4E,0xEE,0xAA,0xEF,0xD7,0xE2,0x3D,0x48,0x10,
+    0xBE,0x00,0xE4,0xCC,0x14,0x92,0xCB,0xA3,0x25,0xBA,0x81,0xFF,
+    0x2D,0x5A,0x5B,0x30,0x5A,0x8D,0x17,0xEB,0x3B,0xF4,0xA0,0x6A,
+    0x34,0x9D,0x39,0x2E,0x00,0xD3,0x29,0x74,0x4A,0x51,0x79,0x38,
+    0x03,0x44,0xE8,0x2A,0x18,0xC4,0x79,0x33,0x43,0x8F,0x89,0x1E,
+    0x22,0xAE,0xEF,0x81,0x2D,0x69,0xC8,0xF7,0x5E,0x32,0x6C,0xB7,
+    0x0E,0xA0,0x00,0xC3,0xF7,0x76,0xDF,0xDB,0xD6,0x04,0x63,0x8C,
+    0x2E,0xF7,0x17,0xFC,0x26,0xD0,0x2E,0x17
+  };
+
+  PRUint8 Q[] = {
+    0xE2,0x1E,0x04,0xF9,0x11,0xD1,0xED,0x79,0x91,0x00,0x8E,0xCA,
+    0xAB,0x3B,0xF7,0x75,0x98,0x43,0x09,0xC3
+  };
+
+  PRUint8 G[] = {
+    0xC5,0x2A,0x4A,0x0F,0xF3,0xB7,0xE6,0x1F,0xDF,0x18,0x67,0xCE,
+    0x84,0x13,0x83,0x69,0xA6,0x15,0x4F,0x4A,0xFA,0x92,0x96,0x6E,
+    0x3C,0x82,0x7E,0x25,0xCF,0xA6,0xCF,0x50,0x8B,0x90,0xE5,0xDE,
+    0x41,0x9E,0x13,0x37,0xE0,0x7A,0x2E,0x9E,0x2A,0x3C,0xD5,0xDE,
+    0xA7,0x04,0xD1,0x75,0xF8,0xEB,0xF6,0xAF,0x39,0x7D,0x69,0xE1,
+    0x10,0xB9,0x6A,0xFB,0x17,0xC7,0xA0,0x32,0x59,0x32,0x9E,0x48,
+    0x29,0xB0,0xD0,0x3B,0xBC,0x78,0x96,0xB1,0x5B,0x4A,0xDE,0x53,
+    0xE1,0x30,0x85,0x8C,0xC3,0x4D,0x96,0x26,0x9A,0xA8,0x90,0x41,
+    0xF4,0x09,0x13,0x6C,0x72,0x42,0xA3,0x88,0x95,0xC9,0xD5,0xBC,
+    0xCA,0xD4,0xF3,0x89,0xAF,0x1D,0x7A,0x4B,0xD1,0x39,0x8B,0xD0,
+    0x72,0xDF,0xFA,0x89,0x62,0x33,0x39,0x7A
+  };
+
+  MOZ_STATIC_ASSERT(PR_ARRAY_SIZE(P) == 1024 / PR_BITS_PER_BYTE, "bad DSA P");
+  MOZ_STATIC_ASSERT(PR_ARRAY_SIZE(Q) ==  160 / PR_BITS_PER_BYTE, "bad DSA Q");
+  MOZ_STATIC_ASSERT(PR_ARRAY_SIZE(G) == 1024 / PR_BITS_PER_BYTE, "bad DSA G");
+
+  PQGParams pqgParams  = {
+    NULL /*arena*/,
+    { siBuffer, P, PR_ARRAY_SIZE(P) },
+    { siBuffer, Q, PR_ARRAY_SIZE(Q) },
+    { siBuffer, G, PR_ARRAY_SIZE(G) }
+  };
+
+  return GenerateKeyPair(slot, privateKey, publicKey, CKM_DSA_KEY_PAIR_GEN,
+                         &pqgParams);
+}
+
+NS_IMETHODIMP
+KeyGenRunnable::Run()
+{
+  if (!NS_IsMainThread()) {
+    nsNSSShutDownPreventionLock locker;
+    if (isAlreadyShutDown()) {
+      mRv = NS_ERROR_NOT_AVAILABLE;
+    } else {
+      // We always want to use the internal slot for BrowserID; in particular,
+      // we want to avoid smartcard slots.
+      PK11SlotInfo *slot = PK11_GetInternalSlot();
+      if (!slot) {
+        mRv = NS_ERROR_UNEXPECTED;
+      } else {
+        SECKEYPrivateKey *privk = NULL;
+        SECKEYPublicKey *pubk = NULL;
+
+        switch (mKeyType) {
+        case rsaKey:
+          mRv = GenerateRSAKeyPair(slot, &privk, &pubk);
+          break;
+        case dsaKey:
+          mRv = GenerateDSAKeyPair(slot, &privk, &pubk);
+          break;
+        default:
+          MOZ_NOT_REACHED("unknown key type");
+          mRv = NS_ERROR_UNEXPECTED;
+        }
+
+        PK11_FreeSlot(slot);
+
+        if (NS_SUCCEEDED(mRv)) {
+          MOZ_ASSERT(privk);
+          MOZ_ASSERT(pubk);
+		  // mKeyPair will take over ownership of privk and pubk
+          mKeyPair = new KeyPair(privk, pubk);
+        }
+      }
+    }
+
+    NS_DispatchToMainThread(this);
+  } else {
+    // Back on Main Thread
+    (void) mCallback->GenerateKeyPairFinished(mRv, mKeyPair);
+  }
+  return NS_OK;
+}
+
+SignRunnable::SignRunnable(const nsACString & aText,
+                           SECKEYPrivateKey * privateKey,
+                           nsIIdentitySignCallback * aCallback)
+  : mTextToSign(aText)
+  , mPrivateKey(SECKEY_CopyPrivateKey(privateKey))
+  , mCallback(aCallback)
+  , mRv(NS_ERROR_NOT_INITIALIZED)
+{
+}
+
+NS_IMETHODIMP
+SignRunnable::Run()
+{
+  if (!NS_IsMainThread()) {
+    nsNSSShutDownPreventionLock locker;
+    if (isAlreadyShutDown()) {
+      mRv = NS_ERROR_NOT_AVAILABLE;
+    } else {
+      // We need the output in PKCS#11 format, not DER encoding, so we must use
+      // PK11_HashBuf and PK11_Sign instead of SEC_SignData.
+
+      SECItem sig = { siBuffer, NULL, 0 };
+      int sigLength = PK11_SignatureLen(mPrivateKey);
+      if (sigLength <= 0) {
+        mRv = PRErrorCode_to_nsresult(PR_GetError());
+      } else if (!SECITEM_AllocItem(NULL, &sig, sigLength)) {
+        mRv = PRErrorCode_to_nsresult(PR_GetError());
+      } else {
+        PRUint8 hash[32]; // big enough for SHA-1 or SHA-256
+        SECOidTag hashAlg = mPrivateKey->keyType == dsaKey ? SEC_OID_SHA1
+                                                           : SEC_OID_SHA256;
+        SECItem hashItem = { siBuffer, hash,
+                             hashAlg == SEC_OID_SHA1 ? 20 : 32 };
+
+        mRv = MapSECStatus(PK11_HashBuf(hashAlg, hash,
+                    const_cast<PRUint8*>(reinterpret_cast<const PRUint8 *>(
+                                            mTextToSign.get())),
+                                      mTextToSign.Length()));
+        if (NS_SUCCEEDED(mRv)) {
+          mRv = MapSECStatus(PK11_Sign(mPrivateKey, &sig, &hashItem));
+        }
+        if (NS_SUCCEEDED(mRv)) {
+          nsDependentCSubstring sigString(
+            reinterpret_cast<const char*>(sig.data), sig.len);
+          mRv = Base64UrlEncodeImpl(sigString, mSignature);
+        }
+        SECITEM_FreeItem(&sig, false);
+      }
+    }
+
+    NS_DispatchToMainThread(this);
+  } else {
+    // Back on Main Thread
+    (void) mCallback->SignFinished(mRv, mSignature);
+  }
+
+  return NS_OK;
+}
+
+// XPCOM module registration
+
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(IdentityCryptoService, Init)
+
+#define NS_IDENTITYCRYPTOSERVICE_CID \
+  {0xbea13a3a, 0x44e8, 0x4d7f, {0xa0, 0xa2, 0x2c, 0x67, 0xf8, 0x4e, 0x3a, 0x97}}
+
+NS_DEFINE_NAMED_CID(NS_IDENTITYCRYPTOSERVICE_CID);
+
+const mozilla::Module::CIDEntry kCIDs[] = {
+  { &kNS_IDENTITYCRYPTOSERVICE_CID, false, NULL, IdentityCryptoServiceConstructor },
+  { NULL }
+};
+
+const mozilla::Module::ContractIDEntry kContracts[] = {
+  { "@mozilla.org/identity/crypto-service;1", &kNS_IDENTITYCRYPTOSERVICE_CID },
+  { NULL }
+};
+
+const mozilla::Module kModule = {
+  mozilla::Module::kVersion,
+  kCIDs,
+  kContracts
+};
+
+} // unnamed namespace
+
+NSMODULE_DEFN(identity) = &kModule;
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/IdentityProvider.jsm
@@ -0,0 +1,477 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+Cu.import("resource://gre/modules/identity/Sandbox.jsm");
+
+const EXPORTED_SYMBOLS = ["IdentityProvider"];
+const FALLBACK_PROVIDER = "browserid.org";
+
+XPCOMUtils.defineLazyModuleGetter(this,
+                                  "jwcrypto",
+                                  "resource://gre/modules/identity/jwcrypto.jsm");
+
+function log(...aMessageArgs) {
+  Logger.log(["IDP"].concat(aMessageArgs));
+}
+function reportError(...aMessageArgs) {
+  Logger.reportError(["IDP"].concat(aMessageArgs));
+}
+
+
+function IdentityProviderService() {
+  XPCOMUtils.defineLazyModuleGetter(this,
+                                    "_store",
+                                    "resource://gre/modules/identity/IdentityStore.jsm",
+                                    "IdentityStore");
+
+  this.reset();
+}
+
+IdentityProviderService.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "quit-application-granted":
+        Services.obs.removeObserver(this, "quit-application-granted");
+        this.shutdown();
+        break;
+    }
+  },
+
+  reset: function IDP_reset() {
+    // Clear the provisioning flows.  Provision flows contain an
+    // identity, idpParams (how to reach the IdP to provision and
+    // authenticate), a callback (a completion callback for when things
+    // are done), and a provisioningFrame (which is the provisioning
+    // sandbox).  Additionally, two callbacks will be attached:
+    // beginProvisioningCallback and genKeyPairCallback.
+    this._provisionFlows = {};
+
+    // Clear the authentication flows.  Authentication flows attach
+    // to provision flows.  In the process of provisioning an id, it
+    // may be necessary to authenticate with an IdP.  The authentication
+    // flow maintains the state of that authentication process.
+    this._authenticationFlows = {};
+  },
+
+  getProvisionFlow: function getProvisionFlow(aProvId, aErrBack) {
+    let provFlow = this._provisionFlows[aProvId];
+    if (provFlow) {
+      return provFlow;
+    }
+
+    let err = "No provisioning flow found with id " + aProvId;
+    log("ERROR:", err);
+    if (typeof aErrBack === 'function') {
+      aErrBack(err);
+    }
+  },
+
+  shutdown: function RP_shutdown() {
+    this.reset();
+    Services.obs.removeObserver(this, "quit-application-granted");
+  },
+
+  get securityLevel() {
+    return 1;
+  },
+
+  get certDuration() {
+    switch(this.securityLevel) {
+      default:
+        return 3600;
+    }
+  },
+
+  /**
+   * Provision an Identity
+   *
+   * @param aIdentity
+   *        (string) the email we're logging in with
+   *
+   * @param aIDPParams
+   *        (object) parameters of the IdP
+   *
+   * @param aCallback
+   *        (function) callback to invoke on completion
+   *                   with first-positional parameter the error.
+   */
+  _provisionIdentity: function _provisionIdentity(aIdentity, aIDPParams, aProvId, aCallback) {
+    let provPath = aIDPParams.idpParams.provisioning;
+    let url = Services.io.newURI("https://" + aIDPParams.domain, null, null).resolve(provPath);
+    log("_provisionIdentity: identity:", aIdentity, "url:", url);
+
+    // If aProvId is not null, then we already have a flow
+    // with a sandbox.  Otherwise, get a sandbox and create a
+    // new provision flow.
+
+    if (aProvId) {
+      // Re-use an existing sandbox
+      log("_provisionIdentity: re-using sandbox in provisioning flow with id:", aProvId);
+      this._provisionFlows[aProvId].provisioningSandbox.reload();
+
+    } else {
+      this._createProvisioningSandbox(url, function createdSandbox(aSandbox) {
+        // create a provisioning flow, using the sandbox id, and
+        // stash callback associated with this provisioning workflow.
+
+        let provId = aSandbox.id;
+        this._provisionFlows[provId] = {
+          identity: aIdentity,
+          idpParams: aIDPParams,
+          securityLevel: this.securityLevel,
+          provisioningSandbox: aSandbox,
+          callback: function doCallback(aErr) {
+            aCallback(aErr, provId);
+          },
+        };
+
+        log("_provisionIdentity: Created sandbox and provisioning flow with id:", provId);
+        // XXX bug 769862 - provisioning flow should timeout after N seconds
+
+      }.bind(this));
+    }
+  },
+
+  // DOM Methods
+  /**
+   * the provisioning iframe sandbox has called navigator.id.beginProvisioning()
+   *
+   * @param aCaller
+   *        (object)  the iframe sandbox caller with all callbacks and
+   *                  other information.  Callbacks include:
+   *                  - doBeginProvisioningCallback(id, duration_s)
+   *                  - doGenKeyPairCallback(pk)
+   */
+  beginProvisioning: function beginProvisioning(aCaller) {
+    log("beginProvisioning:", aCaller.id);
+
+    // Expect a flow for this caller already to be underway.
+    let provFlow = this.getProvisionFlow(aCaller.id, aCaller.doError);
+
+    // keep the caller object around
+    provFlow.caller = aCaller;
+
+    let identity = provFlow.identity;
+    let frame = provFlow.provisioningFrame;
+
+    // Determine recommended length of cert.
+    let duration = this.certDuration;
+
+    // Make a record that we have begun provisioning.  This is required
+    // for genKeyPair.
+    provFlow.didBeginProvisioning = true;
+
+    // Let the sandbox know to invoke the callback to beginProvisioning with
+    // the identity and cert length.
+    return aCaller.doBeginProvisioningCallback(identity, duration);
+  },
+
+  /**
+   * the provisioning iframe sandbox has called
+   * navigator.id.raiseProvisioningFailure()
+   *
+   * @param aProvId
+   *        (int)  the identifier of the provisioning flow tied to that sandbox
+   * @param aReason
+   */
+  raiseProvisioningFailure: function raiseProvisioningFailure(aProvId, aReason) {
+    reportError("Provisioning failure", aReason);
+
+    // look up the provisioning caller and its callback
+    let provFlow = this.getProvisionFlow(aProvId);
+
+    // Sandbox is deleted in _cleanUpProvisionFlow in case we re-use it.
+
+    // This may be either a "soft" or "hard" fail.  If it's a
+    // soft fail, we'll flow through setAuthenticationFlow, where
+    // the provision flow data will be copied into a new auth
+    // flow.  If it's a hard fail, then the callback will be
+    // responsible for cleaning up the now defunct provision flow.
+
+    // invoke the callback with an error.
+    provFlow.callback(aReason);
+  },
+
+  /**
+   * When navigator.id.genKeyPair is called from provisioning iframe sandbox.
+   * Generates a keypair for the current user being provisioned.
+   *
+   * @param aProvId
+   *        (int)  the identifier of the provisioning caller tied to that sandbox
+   *
+   * It is an error to call genKeypair without receiving the callback for
+   * the beginProvisioning() call first.
+   */
+  genKeyPair: function genKeyPair(aProvId) {
+    // Look up the provisioning caller and make sure it's valid.
+    let provFlow = this.getProvisionFlow(aProvId);
+
+    if (!provFlow.didBeginProvisioning) {
+      let errStr = "ERROR: genKeyPair called before beginProvisioning";
+      log(errStr);
+      provFlow.callback(errStr);
+      return;
+    }
+
+    // Ok generate a keypair
+    jwcrypto.generateKeyPair(jwcrypto.ALGORITHMS.DS160, function gkpCb(err, kp) {
+      log("in gkp callback");
+      if (err) {
+        log("ERROR: genKeyPair:" + err);
+        provFlow.callback(err);
+        return;
+      }
+
+      provFlow.kp = kp;
+
+      // Serialize the publicKey of the keypair and send it back to the
+      // sandbox.
+      log("genKeyPair: generated keypair for provisioning flow with id:", aProvId);
+      provFlow.caller.doGenKeyPairCallback(provFlow.kp.serializedPublicKey);
+    }.bind(this));
+  },
+
+  /**
+   * When navigator.id.registerCertificate is called from provisioning iframe
+   * sandbox.
+   *
+   * Sets the certificate for the user for which a certificate was requested
+   * via a preceding call to beginProvisioning (and genKeypair).
+   *
+   * @param aProvId
+   *        (integer) the identifier of the provisioning caller tied to that
+   *                  sandbox
+   *
+   * @param aCert
+   *        (String)  A JWT representing the signed certificate for the user
+   *                  being provisioned, provided by the IdP.
+   */
+  registerCertificate: function registerCertificate(aProvId, aCert) {
+    log("registerCertificate:", aProvId, aCert);
+
+    // look up provisioning caller, make sure it's valid.
+    let provFlow = this.getProvisionFlow(aProvId);
+
+    if (!provFlow.caller) {
+      reportError("registerCertificate", "No provision flow or caller");
+      return;
+    }
+    if (!provFlow.kp)  {
+      let errStr = "Cannot register a certificate without a keypair";
+      reportError("registerCertificate", errStr);
+      provFlow.callback(errStr);
+      return;
+    }
+
+    // store the keypair and certificate just provided in IDStore.
+    this._store.addIdentity(provFlow.identity, provFlow.kp, aCert);
+
+    // Great success!
+    provFlow.callback(null);
+
+    // Clean up the flow.
+    this._cleanUpProvisionFlow(aProvId);
+  },
+
+  /**
+   * Begin the authentication process with an IdP
+   *
+   * @param aProvId
+   *        (int) the identifier of the provisioning flow which failed
+   *
+   * @param aCallback
+   *        (function) to invoke upon completion, with
+   *                   first-positional-param error.
+   */
+  _doAuthentication: function _doAuthentication(aProvId, aIDPParams) {
+    log("_doAuthentication: provId:", aProvId, "idpParams:", aIDPParams);
+    // create an authentication caller and its identifier AuthId
+    // stash aIdentity, idpparams, and callback in it.
+
+    // extract authentication URL from idpParams
+    let authPath = aIDPParams.idpParams.authentication;
+    let authURI = Services.io.newURI("https://" + aIDPParams.domain, null, null).resolve(authPath);
+
+    // beginAuthenticationFlow causes the "identity-auth" topic to be
+    // observed.  Since it's sending a notification to the DOM, there's
+    // no callback.  We wait for the DOM to trigger the next phase of
+    // provisioning.
+    this._beginAuthenticationFlow(aProvId, authURI);
+
+    // either we bind the AuthID to the sandbox ourselves, or UX does that,
+    // in which case we need to tell UX the AuthId.
+    // Currently, the UX creates the UI and gets the AuthId from the window
+    // and sets is with setAuthenticationFlow
+  },
+
+  /**
+   * The authentication frame has called navigator.id.beginAuthentication
+   *
+   * IMPORTANT: the aCaller is *always* non-null, even if this is called from
+   * a regular content page. We have to make sure, on every DOM call, that
+   * aCaller is an expected authentication-flow identifier. If not, we throw
+   * an error or something.
+   *
+   * @param aCaller
+   *        (object)  the authentication caller
+   *
+   */
+  beginAuthentication: function beginAuthentication(aCaller) {
+    log("beginAuthentication: caller id:", aCaller.id);
+
+    // Begin the authentication flow after having concluded a provisioning
+    // flow.  The aCaller that the DOM gives us will have the same ID as
+    // the provisioning flow we just concluded.  (see setAuthenticationFlow)
+    let authFlow = this._authenticationFlows[aCaller.id];
+    if (!authFlow) {
+      return aCaller.doError("beginAuthentication: no flow for caller id", aCaller.id);
+    }
+
+    authFlow.caller = aCaller;
+
+    let identity = this._provisionFlows[authFlow.provId].identity;
+
+    // tell the UI to start the authentication process
+    log("beginAuthentication: authFlow:", aCaller.id, "identity:", identity);
+    return authFlow.caller.doBeginAuthenticationCallback(identity);
+  },
+
+  /**
+   * The auth frame has called navigator.id.completeAuthentication
+   *
+   * @param aAuthId
+   *        (int)  the identifier of the authentication caller tied to that sandbox
+   *
+   */
+  completeAuthentication: function completeAuthentication(aAuthId) {
+    log("completeAuthentication:", aAuthId);
+
+    // look up the AuthId caller, and get its callback.
+    let authFlow = this._authenticationFlows[aAuthId];
+    if (!authFlow) {
+      reportError("completeAuthentication", "No auth flow with id", aAuthId);
+      return;
+    }
+    let provId = authFlow.provId;
+
+    // delete caller
+    delete authFlow['caller'];
+    delete this._authenticationFlows[aAuthId];
+
+    let provFlow = this.getProvisionFlow(provId);
+    provFlow.didAuthentication = true;
+    let subject = {
+      rpId: provFlow.rpId,
+      identity: provFlow.identity,
+    };
+    Services.obs.notifyObservers({ wrappedJSObject: subject }, "identity-auth-complete", aAuthId);
+  },
+
+  /**
+   * The auth frame has called navigator.id.cancelAuthentication
+   *
+   * @param aAuthId
+   *        (int)  the identifier of the authentication caller
+   *
+   */
+  cancelAuthentication: function cancelAuthentication(aAuthId) {
+    log("cancelAuthentication:", aAuthId);
+
+    // look up the AuthId caller, and get its callback.
+    let authFlow = this._authenticationFlows[aAuthId];
+    if (!authFlow) {
+      reportError("cancelAuthentication", "No auth flow with id:", aAuthId);
+      return;
+    }
+    let provId = authFlow.provId;
+
+    // delete caller
+    delete authFlow['caller'];
+    delete this._authenticationFlows[aAuthId];
+
+    let provFlow = this.getProvisionFlow(provId);
+    provFlow.didAuthentication = true;
+    Services.obs.notifyObservers(null, "identity-auth-complete", aAuthId);
+
+    // invoke callback with ERROR.
+    let errStr = "Authentication canceled by IDP";
+    log("ERROR: cancelAuthentication:", errStr);
+    provFlow.callback(errStr);
+  },
+
+  /**
+   * Called by the UI to set the ID and caller for the authentication flow after it gets its ID
+   */
+  setAuthenticationFlow: function(aAuthId, aProvId) {
+    // this is the transition point between the two flows,
+    // provision and authenticate.  We tell the auth flow which
+    // provisioning flow it is started from.
+    log("setAuthenticationFlow: authId:", aAuthId, "provId:", aProvId);
+    this._authenticationFlows[aAuthId] = { provId: aProvId };
+    this._provisionFlows[aProvId].authId = aAuthId;
+  },
+
+  /**
+   * Load the provisioning URL in a hidden frame to start the provisioning
+   * process.
+   */
+  _createProvisioningSandbox: function _createProvisioningSandbox(aURL, aCallback) {
+    log("_createProvisioningSandbox:", aURL);
+    new Sandbox(aURL, aCallback);
+  },
+
+  /**
+   * Load the authentication UI to start the authentication process.
+   */
+  _beginAuthenticationFlow: function _beginAuthenticationFlow(aProvId, aURL) {
+    log("_beginAuthenticationFlow:", aProvId, aURL);
+    let propBag = {provId: aProvId};
+
+    Services.obs.notifyObservers({wrappedJSObject:propBag}, "identity-auth", aURL);
+  },
+
+  /**
+   * Clean up a provision flow and the authentication flow and sandbox
+   * that may be attached to it.
+   */
+  _cleanUpProvisionFlow: function _cleanUpProvisionFlow(aProvId) {
+    log('_cleanUpProvisionFlow:', aProvId);
+    let prov = this._provisionFlows[aProvId];
+
+    // Clean up the sandbox, if there is one.
+    if (prov.provisioningSandbox) {
+      let sandbox = this._provisionFlows[aProvId]['provisioningSandbox'];
+      if (sandbox.free) {
+        log('_cleanUpProvisionFlow: freeing sandbox');
+        sandbox.free();
+      }
+      delete this._provisionFlows[aProvId]['provisioningSandbox'];
+    }
+
+    // Clean up a related authentication flow, if there is one.
+    if (this._authenticationFlows[prov.authId]) {
+      delete this._authenticationFlows[prov.authId];
+    }
+
+    // Finally delete the provision flow
+    delete this._provisionFlows[aProvId];
+  }
+
+};
+
+let IdentityProvider = new IdentityProviderService();
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/IdentityStore.jsm
@@ -0,0 +1,97 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const EXPORTED_SYMBOLS = ["IdentityStore"];
+
+// the data store for IDService
+// written as a separate thing so it can easily be mocked
+function IDServiceStore() {
+  this.reset();
+}
+
+// Note: eventually these methods may be async, but we haven no need for this
+// for now, since we're not storing to disk.
+IDServiceStore.prototype = {
+  addIdentity: function addIdentity(aEmail, aKeyPair, aCert) {
+    this._identities[aEmail] = {keyPair: aKeyPair, cert: aCert};
+  },
+  fetchIdentity: function fetchIdentity(aEmail) {
+    return aEmail in this._identities ? this._identities[aEmail] : null;
+  },
+  removeIdentity: function removeIdentity(aEmail) {
+    let data = this._identities[aEmail];
+    delete this._identities[aEmail];
+    return data;
+  },
+  getIdentities: function getIdentities() {
+    // XXX - should clone?
+    return this._identities;
+  },
+  clearCert: function clearCert(aEmail) {
+    // XXX - should remove key from store?
+    this._identities[aEmail].cert = null;
+    this._identities[aEmail].keyPair = null;
+  },
+
+  /**
+   * set the login state for a given origin
+   *
+   * @param aOrigin
+   *        (string) a web origin
+   *
+   * @param aState
+   *        (boolean) whether or not the user is logged in
+   *
+   * @param aEmail
+   *        (email) the email address the user is logged in with,
+   *                or, if not logged in, the default email for that origin.
+   */
+  setLoginState: function setLoginState(aOrigin, aState, aEmail) {
+    if (aState && !aEmail) {
+      throw "isLoggedIn cannot be set to true without an email";
+    }
+    return this._loginStates[aOrigin] = {isLoggedIn: aState, email: aEmail};
+  },
+  getLoginState: function getLoginState(aOrigin) {
+    return aOrigin in this._loginStates ? this._loginStates[aOrigin] : null;
+  },
+  clearLoginState: function clearLoginState(aOrigin) {
+    delete this._loginStates[aOrigin];
+  },
+
+  reset: function Store_reset() {
+    // _identities associates emails with keypairs and certificates
+    this._identities = {};
+
+    // _loginStates associates. remote origins with a login status and
+    // the email the user has chosen as his or her identity when logging
+    // into that origin.
+    this._loginStates = {};
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "quit-application-granted":
+        Services.obs.removeObserver(this, "quit-application-granted");
+        this.reset();
+        break;
+    }
+  },
+};
+
+let IdentityStore = new IDServiceStore();
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/LogUtils.jsm
@@ -0,0 +1,106 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["Logger"];
+const PREF_DEBUG = "toolkit.identity.debug";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function IdentityLogger() {
+  Services.prefs.addObserver(PREF_DEBUG, this, false);
+  this._debug = Services.prefs.getBoolPref(PREF_DEBUG);
+  return this;
+}
+
+IdentityLogger.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+  observe: function observe(aSubject, aTopic, aData) {
+    switch(aTopic) {
+      case "nsPref:changed":
+        this._debug = Services.prefs.getBoolPref(PREF_DEBUG);
+        break;
+
+      case "quit-application-granted":
+        Services.prefs.removeObserver(PREF_DEBUG, this);
+        break;
+
+      default:
+        this.log("Logger observer", "Unknown topic:", aTopic);
+        break;
+    }
+  },
+
+  _generateLogMessage: function _generateLogMessage(aPrefix, ...args) {
+    // create a string representation of a list of arbitrary things
+    let strings = [];
+
+    // XXX bug 770418 - args look like flattened array, not list of strings
+
+    args.forEach(function(arg) {
+      if (typeof arg === 'string') {
+        strings.push(arg);
+      } else if (typeof arg === 'undefined') {
+        strings.push('undefined');
+      } else if (arg === null) {
+        strings.push('null');
+      } else {
+        try {
+          strings.push(JSON.stringify(arg, null, 2));
+        } catch(err) {
+          strings.push("<<something>>");
+        }
+      }
+    });
+    return 'Identity ' + aPrefix + ': ' + strings.join(' : ');
+  },
+
+  /**
+   * log() - utility function to print a list of arbitrary things
+   * Depends on IdentityStore (bottom of this file) for _debug.
+   *
+   * Enable with about:config pref toolkit.identity.debug
+   */
+  log: function log(aPrefix, ...args) {
+    if (!this._debug) {
+      return;
+    }
+    if (typeof this === 'undefined') {
+      for (var frame=Components.stack; frame; frame = frame.caller) {
+        dump (frame + "\n");
+      }
+    }
+    let output = this._generateLogMessage(aPrefix, args);
+    dump(output + "\n");
+
+    // Additionally, make the output visible in the Error Console
+    Services.console.logStringMessage(output);
+  },
+
+  /**
+   * reportError() - report an error through component utils as well as
+   * our log function
+   */
+  reportError: function reportError(aPrefix, ...aArgs) {
+    let prefix = aPrefix + ' ERROR';
+
+    // Report the error in the browser
+    let output = this._generateLogMessage(aPrefix, aArgs);
+    Cu.reportError("Identity: " + output);
+    dump(output + "\n");
+  }
+
+};
+
+let Logger = new IdentityLogger();
--- a/toolkit/identity/Makefile.in
+++ b/toolkit/identity/Makefile.in
@@ -4,17 +4,43 @@
 
 DEPTH     = ../..
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
+FAIL_ON_WARNINGS := 1
+
+MODULE = identity
+MODULE_NAME = identity
+XPIDL_MODULE = identity
+LIBRARY_NAME = identity
+FORCE_STATIC_LIB = 1
+LIBXUL_LIBRARY = 1
+IS_COMPONENT = 1
+GRE_MODULE = 1
+EXPORT_LIBRARY = 1
+
+XPIDLSRCS = \
+  nsIIdentityCryptoService.idl \
+  $(NULL)
+
+CPPSRCS = \
+  IdentityCryptoService.cpp \
+  $(NULL)
+
 EXTRA_JS_MODULES = \
+	Identity.jsm \
+	IdentityProvider.jsm \
+	IdentityStore.jsm \
+	jwcrypto.jsm \
+	LogUtils.jsm \
+	RelyingParty.jsm \
 	Sandbox.jsm \
 	$(NULL)
 
 ifdef ENABLE_TESTS
     DIRS += tests
 endif
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/RelyingParty.jsm
@@ -0,0 +1,368 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+Cu.import("resource://gre/modules/identity/IdentityStore.jsm");
+
+const EXPORTED_SYMBOLS = ["RelyingParty"];
+
+XPCOMUtils.defineLazyModuleGetter(this,
+                                  "jwcrypto",
+                                  "resource://gre/modules/identity/jwcrypto.jsm");
+
+function log(...aMessageArgs) {
+  Logger.log(["RP"].concat(aMessageArgs));
+}
+function reportError(...aMessageArgs) {
+  Logger.reportError(["RP"].concat(aMessageArgs));
+}
+
+function IdentityRelyingParty() {
+  // The store is a singleton shared among Identity, RelyingParty, and
+  // IdentityProvider.  The Identity module takes care of resetting
+  // state in the _store on shutdown.
+  this._store = IdentityStore;
+
+  this.reset();
+}
+
+IdentityRelyingParty.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
+
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "quit-application-granted":
+        Services.obs.removeObserver(this, "quit-application-granted");
+        this.shutdown();
+        break;
+
+    }
+  },
+
+  reset: function RP_reset() {
+    // Forget all documents that call in.  (These are sometimes
+    // referred to as callers.)
+    this._rpFlows = {};
+  },
+
+  shutdown: function RP_shutdown() {
+    this.reset();
+    Services.obs.removeObserver(this, "quit-application-granted");
+  },
+
+  /**
+   * Register a listener for a given windowID as a result of a call to
+   * navigator.id.watch().
+   *
+   * @param aCaller
+   *        (Object)  an object that represents the caller document, and
+   *                  is expected to have properties:
+   *                  - id (unique, e.g. uuid)
+   *                  - loggedInEmail (string or null)
+   *                  - origin (string)
+   *
+   *                  and a bunch of callbacks
+   *                  - doReady()
+   *                  - doLogin()
+   *                  - doLogout()
+   *                  - doError()
+   *                  - doCancel()
+   *
+   */
+  watch: function watch(aRpCaller) {
+    this._rpFlows[aRpCaller.id] = aRpCaller;
+    let origin = aRpCaller.origin;
+    let state = this._store.getLoginState(origin) || { isLoggedIn: false, email: null };
+
+    log("watch: rpId:", aRpCaller.id,
+        "origin:", origin,
+        "loggedInEmail:", aRpCaller.loggedInEmail,
+        "loggedIn:", state.isLoggedIn,
+        "email:", state.email);
+
+    // If the user is already logged in, then there are three cases
+    // to deal with:
+    //
+    //   1. the email is valid and unchanged:  'ready'
+    //   2. the email is null:                 'login'; 'ready'
+    //   3. the email has changed:             'login'; 'ready'
+    if (state.isLoggedIn) {
+      if (state.email && aRpCaller.loggedInEmail === state.email) {
+        this._notifyLoginStateChanged(aRpCaller.id, state.email);
+        return aRpCaller.doReady();
+
+      } else if (aRpCaller.loggedInEmail === null) {
+        // Generate assertion for existing login
+        let options = {loggedInEmail: state.email, origin: origin};
+        return this._doLogin(aRpCaller, options);
+
+      } else {
+        // A loggedInEmail different from state.email has been specified.
+        // Change login identity.
+
+        let options = {loggedInEmail: state.email, origin: origin};
+        return this._doLogin(aRpCaller, options);
+      }
+
+    // If the user is not logged in, there are two cases:
+    //
+    //   1. a logged in email was provided: 'ready'; 'logout'
+    //   2. not logged in, no email given:  'ready';
+
+    } else {
+      if (aRpCaller.loggedInEmail) {
+        return this._doLogout(aRpCaller, {origin: origin});
+
+      } else {
+        return aRpCaller.doReady();
+      }
+    }
+  },
+
+  /**
+   * A utility for watch() to set state and notify the dom
+   * on login
+   *
+   * Note that this calls _getAssertion
+   */
+  _doLogin: function _doLogin(aRpCaller, aOptions, aAssertion) {
+    log("_doLogin: rpId:", aRpCaller.id, "origin:", aOptions.origin);
+
+    let loginWithAssertion = function loginWithAssertion(assertion) {
+      this._store.setLoginState(aOptions.origin, true, aOptions.loggedInEmail);
+      this._notifyLoginStateChanged(aRpCaller.id, aOptions.loggedInEmail);
+      aRpCaller.doLogin(assertion);
+      aRpCaller.doReady();
+    }.bind(this);
+
+    if (aAssertion) {
+      loginWithAssertion(aAssertion);
+    } else {
+      this._getAssertion(aOptions, function gotAssertion(err, assertion) {
+        if (err) {
+          reportError("_doLogin:", "Failed to get assertion on login attempt:", err);
+          this._doLogout(aRpCaller);
+        } else {
+          loginWithAssertion(assertion);
+        }
+      }.bind(this));
+    }
+  },
+
+  /**
+   * A utility for watch() to set state and notify the dom
+   * on logout.
+   */
+  _doLogout: function _doLogout(aRpCaller, aOptions) {
+    log("_doLogout: rpId:", aRpCaller.id, "origin:", aOptions.origin);
+
+    let state = this._store.getLoginState(aOptions.origin) || {};
+
+    state.isLoggedIn = false;
+    this._notifyLoginStateChanged(aRpCaller.id, null);
+
+    aRpCaller.doLogout();
+    aRpCaller.doReady();
+  },
+
+  /**
+   * For use with login or logout, emit 'identity-login-state-changed'
+   *
+   * The notification will send the rp caller id in the properties,
+   * and the email of the user in the message.
+   *
+   * @param aRpCallerId
+   *        (integer) The id of the RP caller
+   *
+   * @param aIdentity
+   *        (string) The email of the user whose login state has changed
+   */
+  _notifyLoginStateChanged: function _notifyLoginStateChanged(aRpCallerId, aIdentity) {
+    log("_notifyLoginStateChanged: rpId:", aRpCallerId, "identity:", aIdentity);
+
+    let options = {rpId: aRpCallerId};
+    Services.obs.notifyObservers({wrappedJSObject: options},
+                                 "identity-login-state-changed",
+                                 aIdentity);
+  },
+
+  /**
+   * Initiate a login with user interaction as a result of a call to
+   * navigator.id.request().
+   *
+   * @param aRPId
+   *        (integer)  the id of the doc object obtained in .watch()
+   *
+   * @param aOptions
+   *        (Object)  options including privacyPolicy, termsOfService
+   */
+  request: function request(aRPId, aOptions) {
+    log("request: rpId:", aRPId);
+
+    // Notify UX to display identity picker.
+    // Pass the doc id to UX so it can pass it back to us later.
+    let options = {rpId: aRPId};
+
+    // Append URLs after resolving
+    let rp = this._rpFlows[aRPId];
+    let baseURI = Services.io.newURI(rp.origin, null, null);
+    for (let optionName of ["privacyPolicy", "termsOfService"]) {
+      if (aOptions[optionName]) {
+        options[optionName] = baseURI.resolve(aOptions[optionName]);
+      }
+    }
+
+    Services.obs.notifyObservers({wrappedJSObject: options}, "identity-request", null);
+  },
+
+  /**
+   * Invoked when a user wishes to logout of a site (for instance, when clicking
+   * on an in-content logout button).
+   *
+   * @param aRpCallerId
+   *        (integer)  the id of the doc object obtained in .watch()
+   *
+   */
+  logout: function logout(aRpCallerId) {
+    log("logout: RP caller id:", aRpCallerId);
+    let rp = this._rpFlows[aRpCallerId];
+    if (rp && rp.origin) {
+      let origin = rp.origin;
+      log("logout: origin:", origin);
+      this._doLogout(rp, {origin: origin});
+    } else {
+      log("logout: no RP found with id:", aRpCallerId);
+    }
+    // We don't delete this._rpFlows[aRpCallerId], because
+    // the user might log back in again.
+  },
+
+  getDefaultEmailForOrigin: function getDefaultEmailForOrigin(aOrigin) {
+    let identities = this.getIdentitiesForSite(aOrigin);
+    let result = identities.lastUsed || null;
+    log("getDefaultEmailForOrigin:", aOrigin, "->", result);
+    return result;
+  },
+
+  /**
+   * Return the list of identities a user may want to use to login to aOrigin.
+   */
+  getIdentitiesForSite: function getIdentitiesForSite(aOrigin) {
+    let rv = { result: [] };
+    for (let id in this._store.getIdentities()) {
+      rv.result.push(id);
+    }
+    let loginState = this._store.getLoginState(aOrigin);
+    if (loginState && loginState.email)
+      rv.lastUsed = loginState.email;
+    return rv;
+  },
+
+  /**
+   * Obtain a BrowserID assertion with the specified characteristics.
+   *
+   * @param aCallback
+   *        (Function) Callback to be called with (err, assertion) where 'err'
+   *        can be an Error or NULL, and 'assertion' can be NULL or a valid
+   *        BrowserID assertion. If no callback is provided, an exception is
+   *        thrown.
+   *
+   * @param aOptions
+   *        (Object) An object that may contain the following properties:
+   *
+   *          "audience"      : The audience for which the assertion is to be
+   *                            issued. If this property is not set an exception
+   *                            will be thrown.
+   *
+   *        Any properties not listed above will be ignored.
+   */
+  _getAssertion: function _getAssertion(aOptions, aCallback) {
+    let audience = aOptions.origin;
+    let email = aOptions.loggedInEmail || this.getDefaultEmailForOrigin(audience);
+    log("_getAssertion: audience:", audience, "email:", email);
+    if (!audience) {
+      throw "audience required for _getAssertion";
+    }
+
+    // We might not have any identity info for this email
+    if (!this._store.fetchIdentity(email)) {
+      this._store.addIdentity(email, null, null);
+    }
+
+    let cert = this._store.fetchIdentity(email)['cert'];
+    if (cert) {
+      this._generateAssertion(audience, email, function generatedAssertion(err, assertion) {
+        if (err) {
+          log("ERROR: _getAssertion:", err);
+        }
+        log("_getAssertion: generated assertion:", assertion);
+        return aCallback(err, assertion);
+      });
+    }
+  },
+
+  /**
+   * Generate an assertion, including provisioning via IdP if necessary,
+   * but no user interaction, so if provisioning fails, aCallback is invoked
+   * with an error.
+   *
+   * @param aAudience
+   *        (string) web origin
+   *
+   * @param aIdentity
+   *        (string) the email we're logging in with
+   *
+   * @param aCallback
+   *        (function) callback to invoke on completion
+   *                   with first-positional parameter the error.
+   */
+  _generateAssertion: function _generateAssertion(aAudience, aIdentity, aCallback) {
+    log("_generateAssertion: audience:", aAudience, "identity:", aIdentity);
+
+    let id = this._store.fetchIdentity(aIdentity);
+    if (! (id && id.cert)) {
+      let errStr = "Cannot generate an assertion without a certificate";
+      log("ERROR: _generateAssertion:", errStr);
+      aCallback(errStr);
+      return;
+    }
+
+    let kp = id.keyPair;
+
+    if (!kp) {
+      let errStr = "Cannot generate an assertion without a keypair";
+      log("ERROR: _generateAssertion:", errStr);
+      aCallback(errStr);
+      return;
+    }
+
+    jwcrypto.generateAssertion(id.cert, kp, aAudience, aCallback);
+  },
+
+  /**
+   * Clean up references to the provisioning flow for the specified RP.
+   */
+  _cleanUpProvisionFlow: function RP_cleanUpProvisionFlow(aRPId, aProvId) {
+    let rp = this._rpFlows[aRPId];
+    if (rp) {
+      delete rp['provId'];
+    } else {
+      log("Error: Couldn't delete provision flow ", aProvId, " for RP ", aRPId);
+    }
+  },
+
+};
+
+let RelyingParty = new IdentityRelyingParty();
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/jwcrypto.jsm
@@ -0,0 +1,133 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/identity/LogUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+                                  "IDLog",
+                                  "resource://gre/modules/identity/IdentityStore.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+                                   "IdentityCryptoService",
+                                   "@mozilla.org/identity/crypto-service;1",
+                                   "nsIIdentityCryptoService");
+
+const EXPORTED_SYMBOLS = ["jwcrypto"];
+
+const ALGORITHMS = { RS256: "RS256", DS160: "DS160" };
+
+function log(...aMessageArgs) {
+  Logger.log(["jwcrypto"].concat(aMessageArgs));
+}
+
+function generateKeyPair(aAlgorithmName, aCallback) {
+  log("Generate key pair; alg =", aAlgorithmName);
+
+  IdentityCryptoService.generateKeyPair(aAlgorithmName, function(rv, aKeyPair) {
+    if (!Components.isSuccessCode(rv)) {
+      return aCallback("key generation failed");
+    }
+
+    var publicKey;
+
+    switch (aKeyPair.keyType) {
+     case ALGORITHMS.RS256:
+      publicKey = {
+        algorithm: "RS",
+        exponent:  aKeyPair.hexRSAPublicKeyExponent,
+        modulus:   aKeyPair.hexRSAPublicKeyModulus
+      };
+      break;
+
+     case ALGORITHMS.DS160:
+      publicKey = {
+        algorithm: "DS",
+        y: aKeyPair.hexDSAPublicValue,
+        p: aKeyPair.hexDSAPrime,
+        q: aKeyPair.hexDSASubPrime,
+        g: aKeyPair.hexDSAGenerator
+      };
+      break;
+
+    default:
+      return aCallback("unknown key type");
+    }
+
+    let keyWrapper = {
+      serializedPublicKey: JSON.stringify(publicKey),
+      _kp: aKeyPair
+    };
+
+    return aCallback(null, keyWrapper);
+  });
+}
+
+function sign(aPayload, aKeypair, aCallback) {
+  aKeypair._kp.sign(aPayload, function(rv, signature) {
+    if (!Components.isSuccessCode(rv)) {
+      log("ERROR: signer.sign failed");
+      return aCallback("Sign failed");
+    }
+    log("signer.sign: success");
+    return aCallback(null, signature);
+  });
+}
+
+function jwcryptoClass()
+{
+}
+
+jwcryptoClass.prototype = {
+  isCertValid: function(aCert, aCallback) {
+    // XXX check expiration, bug 769850
+    aCallback(true);
+  },
+
+  generateKeyPair: function(aAlgorithmName, aCallback) {
+    log("generating");
+    generateKeyPair(aAlgorithmName, aCallback);
+  },
+
+  generateAssertion: function(aCert, aKeyPair, aAudience, aCallback) {
+    // for now, we hack the algorithm name
+    // XXX bug 769851
+    var header = {"alg": "DS128"};
+    var headerBytes = IdentityCryptoService.base64UrlEncode(
+                          JSON.stringify(header));
+
+    var payload = {
+      // expires in 2 minutes
+      // XXX clock skew needs exploration bug 769852
+      exp: Date.now() + (2 * 60 * 1000),
+      aud: aAudience
+    };
+    var payloadBytes = IdentityCryptoService.base64UrlEncode(
+                          JSON.stringify(payload));
+
+    log("payload bytes", payload, payloadBytes);
+    sign(headerBytes + "." + payloadBytes, aKeyPair, function(err, signature) {
+      if (err)
+        return aCallback(err);
+
+      var signedAssertion = headerBytes + "." + payloadBytes + "." + signature;
+      return aCallback(null, aCert + "~" + signedAssertion);
+    });
+  }
+
+};
+
+var jwcrypto = new jwcryptoClass();
+jwcrypto.ALGORITHMS = ALGORITHMS;
new file mode 100644
--- /dev/null
+++ b/toolkit/identity/nsIIdentityCryptoService.idl
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIIdentityKeyGenCallback;
+interface nsIIdentitySignCallback;
+
+/* Naming and calling conventions:
+ *
+ * A"hex" prefix means "hex-encoded string representation of a byte sequence"
+ * e.g. "ae34bcdf123"
+ *
+ * A "base64url" prefix means "base-64-URL-encoded string repressentation of a
+ * byte sequence.
+ * e.g. "eyJhbGciOiJSUzI1NiJ9"
+ * http://en.wikipedia.org/wiki/Base64#Variants_summary_table
+ * we use the no-padding approach to base64-url-encoding
+ *
+ * Callbacks take an "in nsresult rv" argument that indicates whether the async
+ * operation succeeded. On success, rv will be a success code
+ * (NS_SUCCEEDED(rv) / Components.isSuccessCode(rv)) and the remaining
+ * arguments are as defined in the documentation for the callback. When the
+ * operation fails, rv will be a failure code (NS_FAILED(rv) /
+ * !Components.isSuccessCode(rv)) and the values of the remaining arguments will
+ * be unspecified.
+ *
+ * Key Types:
+ *
+ * "RS256": RSA + SHA-256.
+ *
+ * "DS160": DSA with SHA-1. A 1024-bit prime and a 160-bit subprime with SHA-1.
+ *
+ * we use these abbreviated algorithm names as per the JWA spec
+ * http://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-02
+ */
+
+// "@mozilla.org/identity/crypto-service;1"
+[scriptable, builtinclass, uuid(f087e6bc-dd33-4f6c-a106-dd786e052ee9)]
+interface nsIIdentityCryptoService : nsISupports
+{
+  void generateKeyPair(in AUTF8String algorithm,
+                       in nsIIdentityKeyGenCallback callback);
+
+  ACString base64UrlEncode(in AUTF8String toEncode);
+};
+
+/**
+ * This interface provides a keypair and signing interface for Identity functionality
+ */
+[scriptable, uuid(73962dc7-8ee7-4346-a12b-b039e1d9b54d)]
+interface nsIIdentityKeyPair : nsISupports
+{
+  readonly attribute AUTF8String keyType;
+
+  // RSA properties, only accessible when keyType == "RS256"
+
+  readonly attribute AUTF8String hexRSAPublicKeyExponent;
+  readonly attribute AUTF8String hexRSAPublicKeyModulus;
+
+  // DSA properties, only accessible when keyType == "DS128"
+  readonly attribute AUTF8String hexDSAPrime;       // p
+  readonly attribute AUTF8String hexDSASubPrime;    // q
+  readonly attribute AUTF8String hexDSAGenerator;   // g
+  readonly attribute AUTF8String hexDSAPublicValue; // y
+
+  void sign(in AUTF8String aText,
+            in nsIIdentitySignCallback callback);
+
+  // XXX implement verification bug 769856
+  // AUTF8String verify(in AUTF8String aSignature, in AUTF8String encodedPublicKey);
+
+};
+
+/**
+ * This interface provides a JavaScript callback object used to collect the
+ * nsIIdentityServeKeyPair when the keygen operation is complete
+ *
+ * though there is discussion as to whether we need the nsresult,
+ * we keep it so we can track deeper crypto errors.
+ */
+[scriptable, function, uuid(90f24ca2-2b05-4ca9-8aec-89d38e2f905a)]
+interface nsIIdentityKeyGenCallback : nsISupports
+{
+  void generateKeyPairFinished(in nsresult rv,
+                               in nsIIdentityKeyPair keyPair);
+};
+
+/**
+ * This interface provides a JavaScript callback object used to collect the
+ * AUTF8String signature
+ */
+[scriptable, function, uuid(2d3e5036-374b-4b47-a430-1196b67b890f)]
+interface nsIIdentitySignCallback : nsISupports
+{
+  /** On success, base64urlSignature is the base-64-URL-encoded signature
+   *
+   * For RS256 signatures, XXX bug 769858
+   *
+   * For DSA128 signatures, the signature is the r value concatenated with the
+   * s value, each component padded with leading zeroes as necessary.
+   */
+  void signFinished(in nsresult rv, in ACString base64urlSignature);
+};
--- a/toolkit/library/Makefile.in
+++ b/toolkit/library/Makefile.in
@@ -143,16 +143,17 @@ COMPONENT_LIBS += \
   necko \
   uconv \
   i18n \
   chardet \
   jar$(VERSION_NUMBER) \
   startupcache \
   pref \
   htmlpars \
+  identity \
   imglib2 \
   gkgfx \
   gklayout \
   docshell \
   embedcomponents \
   webbrwsr \
   nsappshell \
   txmgr \
--- a/toolkit/library/nsStaticXULComponents.cpp
+++ b/toolkit/library/nsStaticXULComponents.cpp
@@ -214,16 +214,17 @@
     SPELLCHECK_MODULE                        \
     LAYOUT_DEBUG_MODULE                      \
     UNIXPROXY_MODULE                         \
     OSXPROXY_MODULE                          \
     WINDOWSPROXY_MODULE                      \
     JSCTYPES_MODULE                          \
     MODULE(jsreflect)                        \
     MODULE(jsperf)                           \
+    MODULE(identity)                         \
     MODULE(nsServicesCryptoModule)           \
     MOZ_APP_COMPONENT_MODULES                \
     MODULE(nsTelemetryModule)                \
     MODULE(jsinspector)                      \
     MODULE(jsdebugger)                       \
     /* end of list */
 
 #define MODULE(_name) \