Bug 753238 - Identity (BrowserID) module. r=dolske,rrelyea
authorJed Parsons <jparsons@mozilla.com>
Tue, 03 Jul 2012 14:53:37 -0700
changeset 101294 88070ff09ccd685ece07d0219eccc6e7992be145
parent 101293 921d27b8a76e83cee2023dfb2c3fa501ad49ab14
child 101295 2699c091b91b16fa2e97ff4f6485cd70409cb92a
push id1729
push userlsblakk@mozilla.com
push dateMon, 16 Jul 2012 20:02:43 +0000
treeherdermozilla-aurora@f4e75e148951 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdolske, rrelyea
bugs753238
milestone16.0a1
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) \