summaryrefslogtreecommitdiffstats
path: root/dom/secureelement/gonk/SecureElement.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/secureelement/gonk/SecureElement.js')
-rw-r--r--dom/secureelement/gonk/SecureElement.js514
1 files changed, 514 insertions, 0 deletions
diff --git a/dom/secureelement/gonk/SecureElement.js b/dom/secureelement/gonk/SecureElement.js
new file mode 100644
index 000000000..144c6d8d6
--- /dev/null
+++ b/dom/secureelement/gonk/SecureElement.js
@@ -0,0 +1,514 @@
+/* Copyright 2012 Mozilla Foundation and Mozilla contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Copyright © 2014, Deutsche Telekom, Inc. */
+
+"use strict";
+
+/* globals dump, Components, XPCOMUtils, SE, Services, UiccConnector,
+ SEUtils, ppmm, gMap, UUIDGenerator */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+XPCOMUtils.defineLazyGetter(this, "SE", () => {
+ let obj = {};
+ Cu.import("resource://gre/modules/se_consts.js", obj);
+ return obj;
+});
+
+// set to true in se_consts.js to see debug messages
+var DEBUG = SE.DEBUG_SE;
+function debug(s) {
+ if (DEBUG) {
+ dump("-*- SecureElement: " + s + "\n");
+ }
+}
+
+const SE_IPC_SECUREELEMENT_MSG_NAMES = [
+ "SE:GetSEReaders",
+ "SE:OpenChannel",
+ "SE:CloseChannel",
+ "SE:TransmitAPDU"
+];
+
+const SECUREELEMENTMANAGER_CONTRACTID =
+ "@mozilla.org/secureelement/parent-manager;1";
+const SECUREELEMENTMANAGER_CID =
+ Components.ID("{48f4e650-28d2-11e4-8c21-0800200c9a66}");
+const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+ "@mozilla.org/parentprocessmessagemanager;1",
+ "nsIMessageBroadcaster");
+
+XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
+ "@mozilla.org/uuid-generator;1",
+ "nsIUUIDGenerator");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SEUtils",
+ "resource://gre/modules/SEUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "UiccConnector", () => {
+ let uiccClass = Cc["@mozilla.org/secureelement/connector/uicc;1"];
+ return uiccClass ? uiccClass.getService(Ci.nsISecureElementConnector) : null;
+});
+
+function getConnector(type) {
+ switch (type) {
+ case SE.TYPE_UICC:
+ return UiccConnector;
+ case SE.TYPE_ESE:
+ default:
+ debug("Unsupported SEConnector : " + type);
+ return null;
+ }
+}
+
+/**
+ * 'gMap' is a nested dictionary object that manages all the information
+ * pertaining to channels for a given application (appId). It manages the
+ * relationship between given application and its opened channels.
+ */
+XPCOMUtils.defineLazyGetter(this, "gMap", function() {
+ return {
+ // example structure of AppInfoMap
+ // {
+ // "appId1": {
+ // target: target1,
+ // channels: {
+ // "channelToken1": {
+ // seType: "uicc",
+ // aid: "aid1",
+ // channelNumber: 1
+ // },
+ // "channelToken2": { ... }
+ // }
+ // },
+ // "appId2": { ... }
+ // }
+ appInfoMap: {},
+
+ registerSecureElementTarget: function(appId, target) {
+ if (this.isAppIdRegistered(appId)) {
+ debug("AppId: " + appId + "already registered");
+ return;
+ }
+
+ this.appInfoMap[appId] = {
+ target: target,
+ channels: {}
+ };
+
+ debug("Registered a new SE target " + appId);
+ },
+
+ unregisterSecureElementTarget: function(target) {
+ let appId = Object.keys(this.appInfoMap).find((id) => {
+ return this.appInfoMap[id].target === target;
+ });
+
+ if (!appId) {
+ return;
+ }
+
+ debug("Unregistered SE Target for AppId: " + appId);
+ delete this.appInfoMap[appId];
+ },
+
+ isAppIdRegistered: function(appId) {
+ return this.appInfoMap[appId] !== undefined;
+ },
+
+ getChannelCountByAppIdType: function(appId, type) {
+ return Object.keys(this.appInfoMap[appId].channels)
+ .reduce((cnt, ch) => ch.type === type ? ++cnt : cnt, 0);
+ },
+
+ // Add channel to the appId. Upon successfully adding the entry
+ // this function will return the 'token'
+ addChannel: function(appId, type, aid, channelNumber) {
+ let token = UUIDGenerator.generateUUID().toString();
+ this.appInfoMap[appId].channels[token] = {
+ seType: type,
+ aid: aid,
+ channelNumber: channelNumber
+ };
+ return token;
+ },
+
+ removeChannel: function(appId, channelToken) {
+ if (this.appInfoMap[appId].channels[channelToken]) {
+ debug("Deleting channel with token : " + channelToken);
+ delete this.appInfoMap[appId].channels[channelToken];
+ }
+ },
+
+ getChannel: function(appId, channelToken) {
+ if (!this.appInfoMap[appId].channels[channelToken]) {
+ return null;
+ }
+
+ return this.appInfoMap[appId].channels[channelToken];
+ },
+
+ getChannelsByTarget: function(target) {
+ let appId = Object.keys(this.appInfoMap).find((id) => {
+ return this.appInfoMap[id].target === target;
+ });
+
+ if (!appId) {
+ return [];
+ }
+
+ return Object.keys(this.appInfoMap[appId].channels)
+ .map(token => this.appInfoMap[appId].channels[token]);
+ },
+
+ getTargets: function() {
+ return Object.keys(this.appInfoMap)
+ .map(appId => this.appInfoMap[appId].target);
+ },
+ };
+});
+
+/**
+ * 'SecureElementManager' is the main object that handles IPC messages from
+ * child process. It interacts with other objects such as 'gMap' & 'Connector
+ * instances (UiccConnector, eSEConnector)' to perform various
+ * SE-related (open, close, transmit) operations.
+ * @TODO: Bug 1118097 Support slot based SE/reader names
+ * @TODO: Bug 1118101 Introduce SE type specific permissions
+ */
+function SecureElementManager() {
+ this._registerMessageListeners();
+ this._registerSEListeners();
+ Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ this._acEnforcer =
+ Cc["@mozilla.org/secureelement/access-control/ace;1"]
+ .getService(Ci.nsIAccessControlEnforcer);
+}
+
+SecureElementManager.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIMessageListener,
+ Ci.nsISEListener,
+ Ci.nsIObserver]),
+ classID: SECUREELEMENTMANAGER_CID,
+ classInfo: XPCOMUtils.generateCI({
+ classID: SECUREELEMENTMANAGER_CID,
+ classDescription: "SecureElementManager",
+ interfaces: [Ci.nsIMessageListener,
+ Ci.nsISEListener,
+ Ci.nsIObserver]
+ }),
+
+ // Stores information about supported SE types and their presence.
+ // key: secure element type, value: (Boolean) is present/accessible
+ _sePresence: {},
+
+ _acEnforcer: null,
+
+ _shutdown: function() {
+ this._acEnforcer = null;
+ this.secureelement = null;
+ Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ this._unregisterMessageListeners();
+ this._unregisterSEListeners();
+ },
+
+ _registerMessageListeners: function() {
+ ppmm.addMessageListener("child-process-shutdown", this);
+ for (let msgname of SE_IPC_SECUREELEMENT_MSG_NAMES) {
+ ppmm.addMessageListener(msgname, this);
+ }
+ },
+
+ _unregisterMessageListeners: function() {
+ ppmm.removeMessageListener("child-process-shutdown", this);
+ for (let msgname of SE_IPC_SECUREELEMENT_MSG_NAMES) {
+ ppmm.removeMessageListener(msgname, this);
+ }
+ ppmm = null;
+ },
+
+ _registerSEListeners: function() {
+ let connector = getConnector(SE.TYPE_UICC);
+ if (!connector) {
+ return;
+ }
+
+ this._sePresence[SE.TYPE_UICC] = false;
+ connector.registerListener(this);
+ },
+
+ _unregisterSEListeners: function() {
+ Object.keys(this._sePresence).forEach((type) => {
+ let connector = getConnector(type);
+ if (connector) {
+ connector.unregisterListener(this);
+ }
+ });
+
+ this._sePresence = {};
+ },
+
+ notifySEPresenceChanged: function(type, isPresent) {
+ // we need to notify all targets, even those without open channels,
+ // app could've stored the reader without actually using it
+ debug("notifying DOM about SE state change");
+ this._sePresence[type] = isPresent;
+ gMap.getTargets().forEach(target => {
+ let result = { type: type, isPresent: isPresent };
+ target.sendAsyncMessage("SE:ReaderPresenceChanged", { result: result });
+ });
+ },
+
+ _canOpenChannel: function(appId, type) {
+ let opened = gMap.getChannelCountByAppIdType(appId, type);
+ let limit = SE.MAX_CHANNELS_ALLOWED_PER_SESSION;
+ // UICC basic channel is not accessible see comment in se_consts.js
+ limit = type === SE.TYPE_UICC ? limit - 1 : limit;
+ return opened < limit;
+ },
+
+ _handleOpenChannel: function(msg, callback) {
+ if (!this._canOpenChannel(msg.appId, msg.type)) {
+ debug("Max channels per session exceed");
+ callback({ error: SE.ERROR_GENERIC });
+ return;
+ }
+
+ let connector = getConnector(msg.type);
+ if (!connector) {
+ debug("No SE connector available");
+ callback({ error: SE.ERROR_NOTPRESENT });
+ return;
+ }
+
+ this._acEnforcer.isAccessAllowed(msg.appId, msg.type, msg.aid)
+ .then((allowed) => {
+ if (!allowed) {
+ callback({ error: SE.ERROR_SECURITY });
+ return;
+ }
+ connector.openChannel(SEUtils.byteArrayToHexString(msg.aid), {
+
+ notifyOpenChannelSuccess: (channelNumber, openResponse) => {
+ // Add the new 'channel' to the map upon success
+ let channelToken =
+ gMap.addChannel(msg.appId, msg.type, msg.aid, channelNumber);
+ if (channelToken) {
+ callback({
+ error: SE.ERROR_NONE,
+ channelToken: channelToken,
+ isBasicChannel: (channelNumber === SE.BASIC_CHANNEL),
+ openResponse: SEUtils.hexStringToByteArray(openResponse)
+ });
+ } else {
+ callback({ error: SE.ERROR_GENERIC });
+ }
+ },
+
+ notifyError: (reason) => {
+ debug("Failed to open the channel to AID : " +
+ SEUtils.byteArrayToHexString(msg.aid) +
+ ", Rejected with Reason : " + reason);
+ callback({ error: SE.ERROR_GENERIC, reason: reason, response: [] });
+ }
+ });
+ })
+ .catch((error) => {
+ debug("Failed to get info from accessControlEnforcer " + error);
+ callback({ error: SE.ERROR_SECURITY });
+ });
+ },
+
+ _handleTransmit: function(msg, callback) {
+ let channel = gMap.getChannel(msg.appId, msg.channelToken);
+ if (!channel) {
+ debug("Invalid token:" + msg.channelToken + ", appId: " + msg.appId);
+ callback({ error: SE.ERROR_GENERIC });
+ return;
+ }
+
+ let connector = getConnector(channel.seType);
+ if (!connector) {
+ debug("No SE connector available");
+ callback({ error: SE.ERROR_NOTPRESENT });
+ return;
+ }
+
+ // Bug 1137533 - ACE GPAccessRulesManager APDU filters
+ connector.exchangeAPDU(channel.channelNumber, msg.apdu.cla, msg.apdu.ins,
+ msg.apdu.p1, msg.apdu.p2,
+ SEUtils.byteArrayToHexString(msg.apdu.data),
+ msg.apdu.le, {
+ notifyExchangeAPDUResponse: (sw1, sw2, response) => {
+ callback({
+ error: SE.ERROR_NONE,
+ sw1: sw1,
+ sw2: sw2,
+ response: SEUtils.hexStringToByteArray(response)
+ });
+ },
+
+ notifyError: (reason) => {
+ debug("Transmit failed, rejected with Reason : " + reason);
+ callback({ error: SE.ERROR_INVALIDAPPLICATION, reason: reason });
+ }
+ });
+ },
+
+ _handleCloseChannel: function(msg, callback) {
+ let channel = gMap.getChannel(msg.appId, msg.channelToken);
+ if (!channel) {
+ debug("Invalid token:" + msg.channelToken + ", appId:" + msg.appId);
+ callback({ error: SE.ERROR_GENERIC });
+ return;
+ }
+
+ let connector = getConnector(channel.seType);
+ if (!connector) {
+ debug("No SE connector available");
+ callback({ error: SE.ERROR_NOTPRESENT });
+ return;
+ }
+
+ connector.closeChannel(channel.channelNumber, {
+ notifyCloseChannelSuccess: () => {
+ gMap.removeChannel(msg.appId, msg.channelToken);
+ callback({ error: SE.ERROR_NONE });
+ },
+
+ notifyError: (reason) => {
+ debug("Failed to close channel with token: " + msg.channelToken +
+ ", reason: "+ reason);
+ callback({ error: SE.ERROR_BADSTATE, reason: reason });
+ }
+ });
+ },
+
+ _handleGetSEReadersRequest: function(msg, target, callback) {
+ gMap.registerSecureElementTarget(msg.appId, target);
+ let readers = Object.keys(this._sePresence).map(type => {
+ return { type: type, isPresent: this._sePresence[type] };
+ });
+ callback({ readers: readers, error: SE.ERROR_NONE });
+ },
+
+ _handleChildProcessShutdown: function(target) {
+ let channels = gMap.getChannelsByTarget(target);
+
+ let createCb = (seType, channelNumber) => {
+ return {
+ notifyCloseChannelSuccess: () => {
+ debug("closed " + seType + ", channel " + channelNumber);
+ },
+
+ notifyError: (reason) => {
+ debug("Failed to close " + seType + " channel " +
+ channelNumber + ", reason: " + reason);
+ }
+ };
+ };
+
+ channels.forEach((channel) => {
+ let connector = getConnector(channel.seType);
+ if (!connector) {
+ return;
+ }
+
+ connector.closeChannel(channel.channelNumber,
+ createCb(channel.seType, channel.channelNumber));
+ });
+
+ gMap.unregisterSecureElementTarget(target);
+ },
+
+ _sendSEResponse: function(msg, result) {
+ let promiseStatus = (result.error === SE.ERROR_NONE) ? "Resolved" : "Rejected";
+ result.resolverId = msg.data.resolverId;
+ msg.target.sendAsyncMessage(msg.name + promiseStatus, {result: result});
+ },
+
+ _isValidMessage: function(msg) {
+ let appIdValid = gMap.isAppIdRegistered(msg.data.appId);
+ return msg.name === "SE:GetSEReaders" ? true : appIdValid;
+ },
+
+ /**
+ * nsIMessageListener interface methods.
+ */
+
+ receiveMessage: function(msg) {
+ DEBUG && debug("Received '" + msg.name + "' message from content process" +
+ ": " + JSON.stringify(msg.data));
+
+ if (msg.name === "child-process-shutdown") {
+ this._handleChildProcessShutdown(msg.target);
+ return null;
+ }
+
+ if (SE_IPC_SECUREELEMENT_MSG_NAMES.indexOf(msg.name) !== -1) {
+ if (!msg.target.assertPermission("secureelement-manage")) {
+ debug("SecureElement message " + msg.name + " from a content process " +
+ "with no 'secureelement-manage' privileges.");
+ return null;
+ }
+ } else {
+ debug("Ignoring unknown message type: " + msg.name);
+ return null;
+ }
+
+ let callback = (result) => this._sendSEResponse(msg, result);
+ if (!this._isValidMessage(msg)) {
+ debug("Message not valid");
+ callback({ error: SE.ERROR_GENERIC });
+ return null;
+ }
+
+ switch (msg.name) {
+ case "SE:GetSEReaders":
+ this._handleGetSEReadersRequest(msg.data, msg.target, callback);
+ break;
+ case "SE:OpenChannel":
+ this._handleOpenChannel(msg.data, callback);
+ break;
+ case "SE:CloseChannel":
+ this._handleCloseChannel(msg.data, callback);
+ break;
+ case "SE:TransmitAPDU":
+ this._handleTransmit(msg.data, callback);
+ break;
+ }
+ return null;
+ },
+
+ /**
+ * nsIObserver interface methods.
+ */
+
+ observe: function(subject, topic, data) {
+ if (topic === NS_XPCOM_SHUTDOWN_OBSERVER_ID) {
+ this._shutdown();
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SecureElementManager]);