Remove IdentityService and ContextualIdentityService.

This commit is contained in:
Fedor 2020-05-07 14:45:55 +03:00
parent b372b55725
commit e5ce656e91
16 changed files with 0 additions and 3112 deletions

View File

@ -1,344 +0,0 @@
/* 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/. */
this.EXPORTED_SYMBOLS = ["ContextualIdentityService"];
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const DEFAULT_TAB_COLOR = "#909090";
const SAVE_DELAY_MS = 1500;
XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
return Services.strings.createBundle("chrome://browser/locale/browser.properties");
});
XPCOMUtils.defineLazyGetter(this, "gTextDecoder", function () {
return new TextDecoder();
});
XPCOMUtils.defineLazyGetter(this, "gTextEncoder", function () {
return new TextEncoder();
});
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
"resource://gre/modules/DeferredTask.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
function _ContextualIdentityService(path) {
this.init(path);
}
_ContextualIdentityService.prototype = {
_defaultIdentities: [
{ userContextId: 1,
public: true,
icon: "fingerprint",
color: "blue",
l10nID: "userContextPersonal.label",
accessKey: "userContextPersonal.accesskey",
telemetryId: 1,
},
{ userContextId: 2,
public: true,
icon: "briefcase",
color: "orange",
l10nID: "userContextWork.label",
accessKey: "userContextWork.accesskey",
telemetryId: 2,
},
{ userContextId: 3,
public: true,
icon: "dollar",
color: "green",
l10nID: "userContextBanking.label",
accessKey: "userContextBanking.accesskey",
telemetryId: 3,
},
{ userContextId: 4,
public: true,
icon: "cart",
color: "pink",
l10nID: "userContextShopping.label",
accessKey: "userContextShopping.accesskey",
telemetryId: 4,
},
{ userContextId: 5,
public: false,
icon: "",
color: "",
name: "userContextIdInternal.thumbnail",
accessKey: "" },
],
_identities: null,
_openedIdentities: new Set(),
_lastUserContextId: 0,
_path: null,
_dataReady: false,
_saver: null,
init(path) {
this._path = path;
this._saver = new DeferredTask(() => this.save(), SAVE_DELAY_MS);
AsyncShutdown.profileBeforeChange.addBlocker("ContextualIdentityService: writing data",
() => this._saver.finalize());
this.load();
},
load() {
OS.File.read(this._path).then(bytes => {
// If synchronous loading happened in the meantime, exit now.
if (this._dataReady) {
return;
}
try {
let data = JSON.parse(gTextDecoder.decode(bytes));
if (data.version == 1) {
this.resetDefault();
}
if (data.version != 2) {
dump("ERROR - ContextualIdentityService - Unknown version found in " + this._path + "\n");
this.loadError(null);
return;
}
this._identities = data.identities;
this._lastUserContextId = data.lastUserContextId;
this._dataReady = true;
} catch (error) {
this.loadError(error);
}
}, (error) => {
this.loadError(error);
});
},
resetDefault() {
this._identities = this._defaultIdentities;
this._lastUserContextId = this._defaultIdentities.length;
this._dataReady = true;
this.saveSoon();
},
loadError(error) {
if (error != null &&
!(error instanceof OS.File.Error && error.becauseNoSuchFile) &&
!(error instanceof Components.Exception &&
error.result == Cr.NS_ERROR_FILE_NOT_FOUND)) {
// Let's report the error.
Cu.reportError(error);
}
// If synchronous loading happened in the meantime, exit now.
if (this._dataReady) {
return;
}
this.resetDefault();
},
saveSoon() {
this._saver.arm();
},
save() {
let object = {
version: 2,
lastUserContextId: this._lastUserContextId,
identities: this._identities
};
let bytes = gTextEncoder.encode(JSON.stringify(object));
return OS.File.writeAtomic(this._path, bytes,
{ tmpPath: this._path + ".tmp" });
},
create(name, icon, color) {
let identity = {
userContextId: ++this._lastUserContextId,
public: true,
icon,
color,
name
};
this._identities.push(identity);
this.saveSoon();
return Cu.cloneInto(identity, {});
},
update(userContextId, name, icon, color) {
let identity = this._identities.find(identity => identity.userContextId == userContextId &&
identity.public);
if (identity && name) {
identity.name = name;
identity.color = color;
identity.icon = icon;
delete identity.l10nID;
delete identity.accessKey;
this.saveSoon();
}
return !!identity;
},
remove(userContextId) {
let index = this._identities.findIndex(i => i.userContextId == userContextId && i.public);
if (index == -1) {
return false;
}
Services.obs.notifyObservers(null, "clear-origin-attributes-data",
JSON.stringify({ userContextId }));
this._identities.splice(index, 1);
this._openedIdentities.delete(userContextId);
this.saveSoon();
return true;
},
ensureDataReady() {
if (this._dataReady) {
return;
}
try {
// This reads the file and automatically detects the UTF-8 encoding.
let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
inputStream.init(new FileUtils.File(this._path),
FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
try {
let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
let data = json.decodeFromStream(inputStream,
inputStream.available());
this._identities = data.identities;
this._lastUserContextId = data.lastUserContextId;
this._dataReady = true;
} finally {
inputStream.close();
}
} catch (error) {
this.loadError(error);
return;
}
},
getIdentities() {
this.ensureDataReady();
return Cu.cloneInto(this._identities.filter(info => info.public), {});
},
getPrivateIdentity(name) {
this.ensureDataReady();
return Cu.cloneInto(this._identities.find(info => !info.public && info.name == name), {});
},
getIdentityFromId(userContextId) {
this.ensureDataReady();
return Cu.cloneInto(this._identities.find(info => info.userContextId == userContextId &&
info.public), {});
},
getUserContextLabel(userContextId) {
let identity = this.getIdentityFromId(userContextId);
if (!identity || !identity.public) {
return "";
}
// We cannot localize the user-created identity names.
if (identity.name) {
return identity.name;
}
return gBrowserBundle.GetStringFromName(identity.l10nID);
},
setTabStyle(tab) {
if (!tab.hasAttribute("usercontextid")) {
return;
}
let userContextId = tab.getAttribute("usercontextid");
let identity = this.getIdentityFromId(userContextId);
tab.setAttribute("data-identity-color", identity ? identity.color : "");
},
countContainerTabs() {
let count = 0;
this._forEachContainerTab(function() { ++count; });
return count;
},
closeAllContainerTabs() {
this._forEachContainerTab(function(tab, tabbrowser) {
tabbrowser.removeTab(tab);
});
},
_forEachContainerTab(callback) {
let windowList = Services.wm.getEnumerator("navigator:browser");
while (windowList.hasMoreElements()) {
let win = windowList.getNext();
if (win.closed || !win.gBrowser) {
continue;
}
let tabbrowser = win.gBrowser;
for (let i = tabbrowser.tabContainer.childNodes.length - 1; i >= 0; --i) {
let tab = tabbrowser.tabContainer.childNodes[i];
if (tab.hasAttribute("usercontextid")) {
callback(tab, tabbrowser);
}
}
}
},
telemetry(userContextId) {
let identity = this.getIdentityFromId(userContextId);
// Let's ignore unknown identities for now.
if (!identity || !identity.public) {
return;
}
if (!this._openedIdentities.has(userContextId)) {
this._openedIdentities.add(userContextId);
Services.telemetry.getHistogramById("UNIQUE_CONTAINERS_OPENED").add(1);
}
Services.telemetry.getHistogramById("TOTAL_CONTAINERS_OPENED").add(1);
if (identity.telemetryId) {
Services.telemetry.getHistogramById("CONTAINER_USED")
.add(identity.telemetryId);
}
},
createNewInstanceForTesting(path) {
return new _ContextualIdentityService(path);
},
};
let path = OS.Path.join(OS.Constants.Path.profileDir, "containers.json");
this.ContextualIdentityService = new _ContextualIdentityService(path);

View File

@ -1,7 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXTRA_JS_MODULES += ['ContextualIdentityService.jsm']

View File

@ -21,7 +21,6 @@ DIRS += [
'commandlines',
'console',
'contentprefs',
'contextualidentity',
'cookie',
'crashmonitor',
'downloads',

View File

@ -1,309 +0,0 @@
/* -*- js-indent-level: 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";
this.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.apply(Logger, ["core"].concat(aMessageArgs));
}
function reportError(...aMessageArgs) {
Logger.reportError.apply(Logger, ["core"].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("Auth complete:", 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");
// try to prevent abort/crash during shutdown of mochitest-browser2...
try {
Services.obs.removeObserver(this, "quit-application-granted");
} catch (e) {}
},
/**
* 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 = {
loggedInUser: 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) {
aCallback("Could not parse email: " + aIdentity);
return;
}
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"]
.createInstance(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);
},
};
this.IdentityService = new IDService();

View File

@ -1,571 +0,0 @@
/* -*- 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 "nsProxyRelease.h"
#include "nsString.h"
#include "mozilla/ArrayUtils.h" // ArrayLength
#include "mozilla/Base64.h"
#include "ScopedNSSTypes.h"
#include "NSSErrorsService.h"
#include "nss.h"
#include "pk11pub.h"
#include "secmod.h"
#include "secerr.h"
#include "keyhi.h"
#include "cryptohi.h"
#include <limits.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];
}
}
#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_THREADSAFE_ISUPPORTS
NS_DECL_NSIIDENTITYKEYPAIR
KeyPair(SECKEYPrivateKey* aPrivateKey, SECKEYPublicKey* aPublicKey,
nsIEventTarget* aOperationThread);
private:
~KeyPair()
{
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return;
}
destructorSafeDestroyNSSReference();
shutdown(ShutdownCalledFrom::Object);
}
void virtualDestroyNSSReference() override
{
destructorSafeDestroyNSSReference();
}
void destructorSafeDestroyNSSReference()
{
SECKEY_DestroyPrivateKey(mPrivateKey);
mPrivateKey = nullptr;
SECKEY_DestroyPublicKey(mPublicKey);
mPublicKey = nullptr;
}
SECKEYPrivateKey * mPrivateKey;
SECKEYPublicKey * mPublicKey;
nsCOMPtr<nsIEventTarget> mThread;
KeyPair(const KeyPair &) = delete;
void operator=(const KeyPair &) = delete;
};
NS_IMPL_ISUPPORTS(KeyPair, nsIIdentityKeyPair)
class KeyGenRunnable : public Runnable, public nsNSSShutDownObject
{
public:
NS_DECL_NSIRUNNABLE
KeyGenRunnable(KeyType keyType, nsIIdentityKeyGenCallback * aCallback,
nsIEventTarget* aOperationThread);
private:
~KeyGenRunnable()
{
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return;
}
destructorSafeDestroyNSSReference();
shutdown(ShutdownCalledFrom::Object);
}
virtual void virtualDestroyNSSReference() override
{
destructorSafeDestroyNSSReference();
}
void destructorSafeDestroyNSSReference()
{
}
const KeyType mKeyType; // in
nsMainThreadPtrHandle<nsIIdentityKeyGenCallback> mCallback; // in
nsresult mRv; // out
nsCOMPtr<nsIIdentityKeyPair> mKeyPair; // out
nsCOMPtr<nsIEventTarget> mThread;
KeyGenRunnable(const KeyGenRunnable &) = delete;
void operator=(const KeyGenRunnable &) = delete;
};
class SignRunnable : public Runnable, public nsNSSShutDownObject
{
public:
NS_DECL_NSIRUNNABLE
SignRunnable(const nsACString & textToSign, SECKEYPrivateKey * privateKey,
nsIIdentitySignCallback * aCallback);
private:
~SignRunnable()
{
nsNSSShutDownPreventionLock locker;
if (isAlreadyShutDown()) {
return;
}
destructorSafeDestroyNSSReference();
shutdown(ShutdownCalledFrom::Object);
}
void virtualDestroyNSSReference() override
{
destructorSafeDestroyNSSReference();
}
void destructorSafeDestroyNSSReference()
{
SECKEY_DestroyPrivateKey(mPrivateKey);
mPrivateKey = nullptr;
}
const nsCString mTextToSign; // in
SECKEYPrivateKey* mPrivateKey; // in
nsMainThreadPtrHandle<nsIIdentitySignCallback> mCallback; // in
nsresult mRv; // out
nsCString mSignature; // out
private:
SignRunnable(const SignRunnable &) = delete;
void operator=(const SignRunnable &) = delete;
};
class IdentityCryptoService final : public nsIIdentityCryptoService
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIIDENTITYCRYPTOSERVICE
IdentityCryptoService() { }
nsresult Init()
{
nsresult rv;
nsCOMPtr<nsISupports> dummyUsedToEnsureNSSIsInitialized
= do_GetService("@mozilla.org/psm;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIThread> thread;
rv = NS_NewNamedThread("IdentityCrypto", getter_AddRefs(thread));
NS_ENSURE_SUCCESS(rv, rv);
mThread = thread.forget();
return NS_OK;
}
private:
~IdentityCryptoService() { }
IdentityCryptoService(const KeyPair &) = delete;
void operator=(const IdentityCryptoService &) = delete;
nsCOMPtr<nsIEventTarget> mThread;
};
NS_IMPL_ISUPPORTS(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, mThread);
nsresult rv = mThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
IdentityCryptoService::Base64UrlEncode(const nsACString & utf8Input,
nsACString & result)
{
return Base64URLEncode(utf8Input.Length(),
reinterpret_cast<const uint8_t*>(utf8Input.BeginReading()),
Base64URLEncodePaddingPolicy::Include, result);
}
KeyPair::KeyPair(SECKEYPrivateKey * privateKey, SECKEYPublicKey * publicKey,
nsIEventTarget* operationThread)
: mPrivateKey(privateKey)
, mPublicKey(publicKey)
, mThread(operationThread)
{
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);
return mThread->Dispatch(r, NS_DISPATCH_NORMAL);
}
KeyGenRunnable::KeyGenRunnable(KeyType keyType,
nsIIdentityKeyGenCallback * callback,
nsIEventTarget* operationThread)
: mKeyType(keyType)
, mCallback(new nsMainThreadPtrHolder<nsIIdentityKeyGenCallback>(callback))
, mRv(NS_ERROR_NOT_INITIALIZED)
, mThread(operationThread)
{
}
MOZ_MUST_USE nsresult
GenerateKeyPair(PK11SlotInfo * slot,
SECKEYPrivateKey ** privateKey,
SECKEYPublicKey ** publicKey,
CK_MECHANISM_TYPE mechanism,
void * params)
{
*publicKey = nullptr;
*privateKey = PK11_GenerateKeyPair(slot, mechanism, params, publicKey,
PR_FALSE /*isPerm*/,
PR_TRUE /*isSensitive*/,
nullptr /*&pwdata*/);
if (!*privateKey) {
MOZ_ASSERT(!*publicKey);
return mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
}
if (!*publicKey) {
SECKEY_DestroyPrivateKey(*privateKey);
*privateKey = nullptr;
MOZ_CRASH("PK11_GnerateKeyPair returned private key without public key");
}
return NS_OK;
}
MOZ_MUST_USE nsresult
GenerateRSAKeyPair(PK11SlotInfo * slot,
SECKEYPrivateKey ** privateKey,
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_MUST_USE nsresult
GenerateDSAKeyPair(PK11SlotInfo * slot,
SECKEYPrivateKey ** privateKey,
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.
uint8_t 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
};
uint8_t Q[] = {
0xE2,0x1E,0x04,0xF9,0x11,0xD1,0xED,0x79,0x91,0x00,0x8E,0xCA,
0xAB,0x3B,0xF7,0x75,0x98,0x43,0x09,0xC3
};
uint8_t 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
};
static_assert(MOZ_ARRAY_LENGTH(P) == 1024 / CHAR_BIT, "bad DSA P");
static_assert(MOZ_ARRAY_LENGTH(Q) == 160 / CHAR_BIT, "bad DSA Q");
static_assert(MOZ_ARRAY_LENGTH(G) == 1024 / CHAR_BIT, "bad DSA G");
PQGParams pqgParams = {
nullptr /*arena*/,
{ siBuffer, P, static_cast<unsigned int>(mozilla::ArrayLength(P)) },
{ siBuffer, Q, static_cast<unsigned int>(mozilla::ArrayLength(Q)) },
{ siBuffer, G, static_cast<unsigned int>(mozilla::ArrayLength(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 = nullptr;
SECKEYPublicKey *pubk = nullptr;
switch (mKeyType) {
case rsaKey:
mRv = GenerateRSAKeyPair(slot, &privk, &pubk);
break;
case dsaKey:
mRv = GenerateDSAKeyPair(slot, &privk, &pubk);
break;
default:
MOZ_CRASH("unknown key type");
}
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, mThread);
}
}
}
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(new nsMainThreadPtrHolder<nsIIdentitySignCallback>(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, nullptr, 0 };
int sigLength = PK11_SignatureLen(mPrivateKey);
if (sigLength <= 0) {
mRv = mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
} else if (!SECITEM_AllocItem(nullptr, &sig, sigLength)) {
mRv = mozilla::psm::GetXPCOMFromNSSError(PR_GetError());
} else {
uint8_t 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 ? 20u : 32u };
mRv = MapSECStatus(PK11_HashBuf(hashAlg, hash,
const_cast<uint8_t*>(reinterpret_cast<const uint8_t *>(
mTextToSign.get())),
mTextToSign.Length()));
if (NS_SUCCEEDED(mRv)) {
mRv = MapSECStatus(PK11_Sign(mPrivateKey, &sig, &hashItem));
}
if (NS_SUCCEEDED(mRv)) {
mRv = Base64URLEncode(sig.len, sig.data,
Base64URLEncodePaddingPolicy::Include,
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, nullptr, IdentityCryptoServiceConstructor },
{ nullptr }
};
const mozilla::Module::ContractIDEntry kContracts[] = {
{ "@mozilla.org/identity/crypto-service;1", &kNS_IDENTITYCRYPTOSERVICE_CID },
{ nullptr }
};
const mozilla::Module kModule = {
mozilla::Module::kVersion,
kCIDs,
kContracts
};
} // unnamed namespace
NSMODULE_DEFN(identity) = &kModule;

View File

@ -1,496 +0,0 @@
/* -*- js-indent-level: 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");
this.EXPORTED_SYMBOLS = ["IdentityProvider"];
const FALLBACK_PROVIDER = "browserid.org";
XPCOMUtils.defineLazyModuleGetter(this,
"jwcrypto",
"resource://gre/modules/identity/jwcrypto.jsm");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["IDP"].concat(aMessageArgs));
}
function reportError(...aMessageArgs) {
Logger.reportError.apply(Logger, ["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]),
_sandboxConfigured: false,
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);
}
return undefined;
},
shutdown: function RP_shutdown() {
this.reset();
if (this._sandboxConfigured) {
// Tear down message manager listening on the hidden window
Cu.import("resource://gre/modules/DOMIdentity.jsm");
DOMIdentity._configureMessages(Services.appShell.hiddenDOMWindow, false);
this._sandboxConfigured = false;
}
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);
if (!this._sandboxConfigured) {
// Configure message manager listening on the hidden window
Cu.import("resource://gre/modules/DOMIdentity.jsm");
DOMIdentity._configureMessages(Services.appShell.hiddenDOMWindow, true);
this._sandboxConfigured = true;
}
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];
}
};
this.IdentityProvider = new IdentityProviderService();

View File

@ -1,97 +0,0 @@
/* -*- js-indent-level: 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");
this.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;
}
},
};
this.IdentityStore = new IDServiceStore();

View File

@ -1,111 +0,0 @@
/* -*- js-indent-level: 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/. */
// functions common to Identity.jsm and MinimalIdentity.jsm
"use strict";
this.EXPORTED_SYMBOLS = [
"checkDeprecated",
"checkRenamed",
"getRandomId",
"objectCopy",
"makeMessageObject",
];
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "Logger",
"resource://gre/modules/identity/LogUtils.jsm");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["Identity"].concat(aMessageArgs));
}
function defined(item) {
return typeof item !== 'undefined';
}
var checkDeprecated = this.checkDeprecated = function checkDeprecated(aOptions, aField) {
if (defined(aOptions[aField])) {
log("WARNING: field is deprecated:", aField);
return true;
}
return false;
};
this.checkRenamed = function checkRenamed(aOptions, aOldName, aNewName) {
if (defined(aOptions[aOldName]) &&
defined(aOptions[aNewName])) {
let err = "You cannot provide both " + aOldName + " and " + aNewName;
Logger.reportError(err);
throw new Error(err);
}
if (checkDeprecated(aOptions, aOldName)) {
aOptions[aNewName] = aOptions[aOldName];
delete(aOptions[aOldName]);
}
};
this.getRandomId = function getRandomId() {
return uuidgen.generateUUID().toString();
};
/*
* copy source object into target, excluding private properties
* (those whose names begin with an underscore)
*/
this.objectCopy = function objectCopy(source, target) {
let desc;
Object.getOwnPropertyNames(source).forEach(function(name) {
if (name[0] !== '_') {
desc = Object.getOwnPropertyDescriptor(source, name);
Object.defineProperty(target, name, desc);
}
});
};
this.makeMessageObject = function makeMessageObject(aRpCaller) {
let options = {};
options.id = aRpCaller.id;
options.origin = aRpCaller.origin;
// Backwards compatibility with Persona beta:
// loggedInUser can be undefined, null, or a string
options.loggedInUser = aRpCaller.loggedInUser;
// Special flag for internal calls for Persona in b2g
options._internal = aRpCaller._internal;
Object.keys(aRpCaller).forEach(function(option) {
// Duplicate the callerobject, scrubbing out functions and other
// internal variables (like _mm, the message manager object)
if (!Object.hasOwnProperty(this, option)
&& option[0] !== '_'
&& typeof aRpCaller[option] !== 'function') {
options[option] = aRpCaller[option];
}
});
// check validity of message structure
if ((typeof options.id === 'undefined') ||
(typeof options.origin === 'undefined')) {
let err = "id and origin required in relying-party message: " + JSON.stringify(options);
reportError(err);
throw new Error(err);
}
return options;
}

View File

@ -1,103 +0,0 @@
/* -*- js-indent-level: 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";
this.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
*
* Enable with about:config pref toolkit.identity.debug
*/
log: function log(aPrefix, ...args) {
if (!this._debug) {
return;
}
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(output);
dump("ERROR: " + output + "\n");
for (let frame = Components.stack.caller; frame; frame = frame.caller) {
dump(frame + "\n");
}
}
};
this.Logger = new IdentityLogger();

View File

@ -1,242 +0,0 @@
/* -*- js-indent-level: 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/. */
/*
* This alternate implementation of IdentityService provides just the
* channels for navigator.id, leaving the certificate storage to a
* server-provided app.
*
* On b2g, the messages identity-controller-watch, -request, and
* -logout, are observed by the component SignInToWebsite.jsm.
*/
"use strict";
this.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");
XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
"resource://gre/modules/identity/IdentityUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "makeMessageObject",
"resource://gre/modules/identity/IdentityUtils.jsm");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["minimal core"].concat(aMessageArgs));
}
function reportError(...aMessageArgs) {
Logger.reportError.apply(Logger, ["core"].concat(aMessageArgs));
}
function IDService() {
Services.obs.addObserver(this, "quit-application-granted", false);
// simplify, it's one object
this.RP = this;
this.IDP = this;
// keep track of flows
this._rpFlows = {};
this._authFlows = {};
this._provFlows = {};
}
IDService.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
observe: function observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "quit-application-granted":
this.shutdown();
break;
}
},
shutdown: function() {
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;
},
/**
* 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)
* - loggedInUser (string or null)
* - origin (string)
*
* and a bunch of callbacks
* - doReady()
* - doLogin()
* - doLogout()
* - doError()
* - doCancel()
*
*/
watch: function watch(aRpCaller) {
// store the caller structure and notify the UI observers
this._rpFlows[aRpCaller.id] = aRpCaller;
log("flows:", Object.keys(this._rpFlows).join(', '));
let options = makeMessageObject(aRpCaller);
log("sending identity-controller-watch:", options);
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-watch", null);
},
/*
* The RP has gone away; remove handles to the hidden iframe.
* It's probable that the frame will already have been cleaned up.
*/
unwatch: function unwatch(aRpId, aTargetMM) {
let rp = this._rpFlows[aRpId];
if (!rp) {
return;
}
let options = makeMessageObject({
id: aRpId,
origin: rp.origin,
messageManager: aTargetMM
});
log("sending identity-controller-unwatch for id", options.id, options.origin);
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-unwatch", null);
// Stop sending messages to this window
delete this._rpFlows[aRpId];
},
/**
* 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) {
let rp = this._rpFlows[aRPId];
if (!rp) {
reportError("request() called before watch()");
return;
}
// Notify UX to display identity picker.
// Pass the doc id to UX so it can pass it back to us later.
let options = makeMessageObject(rp);
objectCopy(aOptions, options);
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-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) {
let rp = this._rpFlows[aRpCallerId];
if (!rp) {
reportError("logout() called before watch()");
return;
}
let options = makeMessageObject(rp);
Services.obs.notifyObservers({wrappedJSObject: options}, "identity-controller-logout", null);
},
childProcessShutdown: function childProcessShutdown(messageManager) {
Object.keys(this._rpFlows).forEach(function(key) {
if (this._rpFlows[key]._mm === messageManager) {
log("child process shutdown for rp", key, "- deleting flow");
delete this._rpFlows[key];
}
}, this);
},
/*
* once the UI-and-display-logic components have received
* notifications, they call back with direct invocation of the
* following functions (doLogin, doLogout, or doReady)
*/
doLogin: function doLogin(aRpCallerId, aAssertion, aInternalParams) {
let rp = this._rpFlows[aRpCallerId];
if (!rp) {
log("WARNING: doLogin found no rp to go with callerId " + aRpCallerId);
return;
}
rp.doLogin(aAssertion, aInternalParams);
},
doLogout: function doLogout(aRpCallerId) {
let rp = this._rpFlows[aRpCallerId];
if (!rp) {
log("WARNING: doLogout found no rp to go with callerId " + aRpCallerId);
return;
}
// Logout from every site with the same origin
let origin = rp.origin;
Object.keys(this._rpFlows).forEach(function(key) {
let rp = this._rpFlows[key];
if (rp.origin === origin) {
rp.doLogout();
}
}.bind(this));
},
doReady: function doReady(aRpCallerId) {
let rp = this._rpFlows[aRpCallerId];
if (!rp) {
log("WARNING: doReady found no rp to go with callerId " + aRpCallerId);
return;
}
rp.doReady();
},
doCancel: function doCancel(aRpCallerId) {
let rp = this._rpFlows[aRpCallerId];
if (!rp) {
log("WARNING: doCancel found no rp to go with callerId " + aRpCallerId);
return;
}
rp.doCancel();
}
};
this.IdentityService = new IDService();

View File

@ -1,367 +0,0 @@
/* -*- js-indent-level: 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");
this.EXPORTED_SYMBOLS = ["RelyingParty"];
XPCOMUtils.defineLazyModuleGetter(this, "objectCopy",
"resource://gre/modules/identity/IdentityUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this,
"jwcrypto",
"resource://gre/modules/identity/jwcrypto.jsm");
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["RP"].concat(aMessageArgs));
}
function reportError(...aMessageArgs) {
Logger.reportError.apply(Logger, ["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)
* - loggedInUser (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,
"loggedInUser:", aRpCaller.loggedInUser,
"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.loggedInUser === state.email) {
this._notifyLoginStateChanged(aRpCaller.id, state.email);
return aRpCaller.doReady();
} else if (aRpCaller.loggedInUser === null) {
// Generate assertion for existing login
let options = {loggedInUser: state.email, origin: origin};
return this._doLogin(aRpCaller, options);
}
// A loggedInUser different from state.email has been specified.
// Change login identity.
let options = {loggedInUser: 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';
}
if (aRpCaller.loggedInUser) {
return this._doLogout(aRpCaller, {origin: origin});
}
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.loggedInUser);
this._notifyLoginStateChanged(aRpCaller.id, aOptions.loggedInUser);
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);
let rp = this._rpFlows[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, origin: rp.origin};
objectCopy(aOptions, options);
// Append URLs after resolving
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.loggedInUser || 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);
}
},
};
this.RelyingParty = new IdentityRelyingParty();

View File

@ -1,152 +0,0 @@
/* 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";
this.EXPORTED_SYMBOLS = ["Sandbox"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const XHTML_NS = "http://www.w3.org/1999/xhtml";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this,
"Logger",
"resource://gre/modules/identity/LogUtils.jsm");
/**
* An object that represents a sandbox in an iframe loaded with aURL. The
* callback provided to the constructor will be invoked when the sandbox is
* ready to be used. The callback will receive this object as its only argument.
*
* You must call free() when you are finished with the sandbox to explicitly
* free up all associated resources.
*
* @param aURL
* (string) URL to load in the sandbox.
*
* @param aCallback
* (function) Callback to be invoked with a Sandbox, when ready.
*/
this.Sandbox = function Sandbox(aURL, aCallback) {
// Normalize the URL so the comparison in _makeSandboxContentLoaded works
this._url = Services.io.newURI(aURL, null, null).spec;
this._log("Creating sandbox for:", this._url);
this._createFrame();
this._createSandbox(aCallback);
};
this.Sandbox.prototype = {
/**
* Use the outer window ID as the identifier of the sandbox.
*/
get id() {
return this._frame.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
},
/**
* Reload the URL in the sandbox. This is useful to reuse a Sandbox (same
* id and URL).
*/
reload: function Sandbox_reload(aCallback) {
this._log("reload:", this.id, ":", this._url);
this._createSandbox(function createdSandbox(aSandbox) {
this._log("reloaded sandbox id:", aSandbox.id);
aCallback(aSandbox);
}.bind(this));
},
/**
* Frees the sandbox and releases the iframe created to host it.
*/
free: function Sandbox_free() {
this._log("free:", this.id);
this._container.removeChild(this._frame);
this._frame = null;
this._container = null;
this._url = null;
},
/**
* Creates an empty, hidden iframe and sets it to the _frame
* property of this object.
*/
_createFrame: function Sandbox__createFrame() {
let hiddenWindow = Services.appShell.hiddenDOMWindow;
let doc = hiddenWindow.document;
// Insert iframe in to create docshell.
let frame = doc.createElementNS(XHTML_NS, "iframe");
frame.setAttribute("mozframetype", "content");
frame.sandbox = "allow-forms allow-scripts allow-same-origin";
frame.style.visibility = "collapse";
doc.documentElement.appendChild(frame);
let docShell = frame.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
// Stop about:blank from being loaded.
docShell.stop(Ci.nsIWebNavigation.STOP_NETWORK);
// Disable some types of content
docShell.allowAuth = false;
docShell.allowPlugins = false;
docShell.allowImages = false;
docShell.allowMedia = false;
docShell.allowWindowControl = false;
// Disable stylesheet loading since the document is not visible.
let markupDocViewer = docShell.contentViewer;
markupDocViewer.authorStyleDisabled = true;
// Set instance properties.
this._frame = frame;
this._container = doc.documentElement;
},
_createSandbox: function Sandbox__createSandbox(aCallback) {
let self = this;
function _makeSandboxContentLoaded(event) {
self._log("_makeSandboxContentLoaded:", self.id,
event.target.location.toString());
if (event.target != self._frame.contentDocument) {
return;
}
self._frame.removeEventListener(
"DOMWindowCreated", _makeSandboxContentLoaded, true
);
aCallback(self);
}
this._frame.addEventListener("DOMWindowCreated",
_makeSandboxContentLoaded,
true);
// Load the iframe.
let webNav = this._frame.contentWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation);
webNav.loadURI(
this._url,
Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE,
null, // referrer
null, // postData
null // headers
);
},
_log: function Sandbox__log(...aMessageArgs) {
Logger.log.apply(Logger, ["sandbox"].concat(aMessageArgs));
},
};

View File

@ -1,180 +0,0 @@
/* -*- js-indent-level: 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.defineLazyServiceGetter(this,
"IdentityCryptoService",
"@mozilla.org/identity/crypto-service;1",
"nsIIdentityCryptoService");
this.EXPORTED_SYMBOLS = ["jwcrypto"];
const ALGORITHMS = { RS256: "RS256", DS160: "DS160" };
const DURATION_MS = 1000 * 60 * 2; // 2 minutes default assertion lifetime
function log(...aMessageArgs) {
Logger.log.apply(Logger, ["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 = {
/*
* Determine the expiration of the assertion. Returns expiry date
* in milliseconds as integer.
*
* @param localtimeOffsetMsec (optional)
* The number of milliseconds that must be added to the local clock
* for it to agree with the server. For example, if the local clock
* if two minutes fast, localtimeOffsetMsec would be -120000
*
* @param now (options)
* Current date in milliseconds. Useful for mocking clock
* skew in testing.
*/
getExpiration: function(duration=DURATION_MS, localtimeOffsetMsec=0, now=Date.now()) {
return now + localtimeOffsetMsec + duration;
},
isCertValid: function(aCert, aCallback) {
// XXX check expiration, bug 769850
aCallback(true);
},
generateKeyPair: function(aAlgorithmName, aCallback) {
log("generating");
generateKeyPair(aAlgorithmName, aCallback);
},
/*
* Generate an assertion and return it through the provided callback.
*
* @param aCert
* Identity certificate
*
* @param aKeyPair
* KeyPair object
*
* @param aAudience
* Audience of the assertion
*
* @param aOptions (optional)
* Can include:
* {
* localtimeOffsetMsec: <clock offset in milliseconds>,
* now: <current date in milliseconds>
* duration: <validity duration for this assertion in milliseconds>
* }
*
* localtimeOffsetMsec is the number of milliseconds that need to be
* added to the local clock time to make it concur with the server.
* For example, if the local clock is two minutes fast, the offset in
* milliseconds would be -120000.
*
* @param aCallback
* Function to invoke with resulting assertion. Assertion
* will be string or null on failure.
*/
generateAssertion: function(aCert, aKeyPair, aAudience, aOptions, aCallback) {
if (typeof aOptions == "function") {
aCallback = aOptions;
aOptions = { };
}
// for now, we hack the algorithm name
// XXX bug 769851
var header = {"alg": "DS128"};
var headerBytes = IdentityCryptoService.base64UrlEncode(
JSON.stringify(header));
var payload = {
exp: this.getExpiration(
aOptions.duration, aOptions.localtimeOffsetMsec, aOptions.now),
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);
});
}
};
this.jwcrypto = new jwcryptoClass();
this.jwcrypto.ALGORITHMS = ALGORITHMS;

View File

@ -1,25 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
XPIDL_SOURCES += ['nsIIdentityCryptoService.idl']
XPIDL_MODULE = 'identity'
SOURCES += ['IdentityCryptoService.cpp']
EXTRA_JS_MODULES.identity += [
'Identity.jsm',
'IdentityProvider.jsm',
'IdentityStore.jsm',
'IdentityUtils.jsm',
'jwcrypto.jsm',
'LogUtils.jsm',
'MinimalIdentity.jsm',
'RelyingParty.jsm',
'Sandbox.jsm',
]
FINAL_LIBRARY = 'xul'

View File

@ -1,106 +0,0 @@
/* 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 padded 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);
};

View File

@ -8,7 +8,6 @@ DIRS += [
'components',
'content',
'forgetaboutsite',
'identity',
'locales',
'modules',
'mozapps/downloads',