Remove IdentityService and ContextualIdentityService.
This commit is contained in:
parent
b372b55725
commit
e5ce656e91
|
@ -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);
|
|
@ -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']
|
|
@ -21,7 +21,6 @@ DIRS += [
|
|||
'commandlines',
|
||||
'console',
|
||||
'contentprefs',
|
||||
'contextualidentity',
|
||||
'cookie',
|
||||
'crashmonitor',
|
||||
'downloads',
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
|
@ -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();
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
|
@ -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();
|
|
@ -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();
|
|
@ -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));
|
||||
},
|
||||
|
||||
};
|
|
@ -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;
|
|
@ -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'
|
|
@ -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);
|
||||
};
|
|
@ -8,7 +8,6 @@ DIRS += [
|
|||
'components',
|
||||
'content',
|
||||
'forgetaboutsite',
|
||||
'identity',
|
||||
'locales',
|
||||
'modules',
|
||||
'mozapps/downloads',
|
||||
|
|
Loading…
Reference in New Issue