summaryrefslogtreecommitdiffstats
path: root/dom/secureelement/gonk
diff options
context:
space:
mode:
Diffstat (limited to 'dom/secureelement/gonk')
-rw-r--r--dom/secureelement/gonk/ACEService.js139
-rw-r--r--dom/secureelement/gonk/ACEService.manifest2
-rw-r--r--dom/secureelement/gonk/GPAccessRulesManager.js436
-rw-r--r--dom/secureelement/gonk/GPAccessRulesManager.manifest2
-rw-r--r--dom/secureelement/gonk/SecureElement.js514
-rw-r--r--dom/secureelement/gonk/SecureElement.manifest18
-rw-r--r--dom/secureelement/gonk/UiccConnector.js360
-rw-r--r--dom/secureelement/gonk/UiccConnector.manifest17
-rw-r--r--dom/secureelement/gonk/gp_consts.js62
-rw-r--r--dom/secureelement/gonk/nsIAccessControlEnforcer.idl32
-rw-r--r--dom/secureelement/gonk/nsIAccessRulesManager.idl50
-rw-r--r--dom/secureelement/gonk/nsISecureElementConnector.idl124
-rw-r--r--dom/secureelement/gonk/se_consts.js68
13 files changed, 1824 insertions, 0 deletions
diff --git a/dom/secureelement/gonk/ACEService.js b/dom/secureelement/gonk/ACEService.js
new file mode 100644
index 000000000..b52ba5fab
--- /dev/null
+++ b/dom/secureelement/gonk/ACEService.js
@@ -0,0 +1,139 @@
+/* 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/. */
+
+/* Copyright © 2015, Deutsche Telekom, Inc. */
+
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SEUtils",
+ "resource://gre/modules/SEUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "SE", function() {
+ let obj = {};
+ Cu.import("resource://gre/modules/se_consts.js", obj);
+ return obj;
+});
+
+var DEBUG = SE.DEBUG_ACE;
+function debug(msg) {
+ if (DEBUG) {
+ dump("ACEservice: " + msg + "\n");
+ }
+}
+
+/**
+ * Implements decision making algorithm as described in GPD specification,
+ * mostly in 3.1, 3.2 and 4.2.3.
+ *
+ * TODO: Bug 1137533: Implement GPAccessRulesManager APDU filters
+ */
+function GPAccessDecision(rules, certHash, aid) {
+ this.rules = rules;
+ this.certHash = certHash;
+ this.aid = aid;
+}
+
+GPAccessDecision.prototype = {
+ isAccessAllowed: function isAccessAllowed() {
+ // GPD SE Access Control v1.1, 3.4.1, Table 3-2: (Conflict resolution)
+ // If a specific rule allows, all other non-specific access is denied.
+ // Conflicting specific rules will resolve to the first Allowed == "true"
+ // match. Given no specific rule, the global "All" rules will determine
+ // access. "Some", skips further processing if access Allowed == "true".
+ //
+ // Access must be decided before the SE connector openChannel, and the
+ // exchangeAPDU call.
+ //
+ // NOTE: This implementation may change with the introduction of APDU
+ // filters.
+ let decision = this.rules.some(this._decideAppAccess.bind(this));
+ return decision;
+ },
+
+ _decideAppAccess: function _decideAppAccess(rule) {
+ let appMatched, appletMatched;
+
+ // GPD SE AC 4.2.3: Algorithm for Applying Rules
+ // Specific rule overrides global rule.
+ //
+ // DeviceAppID is the application hash, and the AID is SE Applet ID:
+ //
+ // GPD SE AC 4.2.3 A:
+ // SearchRuleFor(DeviceAppID, AID)
+ // GPD SE AC 4.2.3 B: If no rule fits A:
+ // SearchRuleFor(<AllDeviceApplications>, AID)
+ // GPD SE AC 4.2.3 C: If no rule fits A or B:
+ // SearchRuleFor(DeviceAppID, <AllSEApplications>)
+ // GPD SE AC 4.2.3 D: If no rule fits A, B, or C:
+ // SearchRuleFor(<AllDeviceApplications>, <AllSEApplications>)
+
+ // Device App
+ appMatched = Array.isArray(rule.application) ?
+ // GPD SE AC 4.2.3 A and 4.2.3 C (DeviceAppID rule)
+ this._appCertHashMatches(rule.application) :
+ // GPD SE AC 4.2.3 B and 4.2.3 D (All Device Applications)
+ rule.application === Ci.nsIAccessRulesManager.ALLOW_ALL;
+
+ if (!appMatched) {
+ return false; // bail out early.
+ }
+
+ // SE Applet
+ appletMatched = Array.isArray(rule.applet) ?
+ // GPD SE AC 4.2.3 A and 4.2.3 B (AID rule)
+ SEUtils.arraysEqual(rule.applet, this.aid) :
+ // GPD SE AC 4.2.3 C and 4.2.3 D (All AID)
+ rule.applet === Ci.nsIAccessRulesManager.ALL_APPLET;
+
+ return appletMatched;
+ },
+
+ _appCertHashMatches: function _appCertHashMatches(hashArray) {
+ if (!Array.isArray(hashArray)) {
+ return false;
+ }
+
+ return !!(hashArray.find((hash) => {
+ return SEUtils.arraysEqual(hash, this.certHash);
+ }));
+ }
+};
+
+function ACEService() {
+ this._rulesManagers = new Map();
+
+ this._rulesManagers.set(
+ SE.TYPE_UICC,
+ Cc["@mozilla.org/secureelement/access-control/rules-manager;1"]
+ .createInstance(Ci.nsIAccessRulesManager));
+}
+
+ACEService.prototype = {
+ _rulesManagers: null,
+
+ isAccessAllowed: function isAccessAllowed(localId, seType, aid) {
+ if(!Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps")) {
+ debug("Certified apps debug enabled, allowing access");
+ return Promise.resolve(true);
+ }
+
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ _getDevCertHashForApp: function getDevCertHashForApp(manifestURL) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ classID: Components.ID("{882a7463-2ca7-4d61-a89a-10eb6fd70478}"),
+ contractID: "@mozilla.org/secureelement/access-control/ace;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessControlEnforcer])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ACEService]);
+
diff --git a/dom/secureelement/gonk/ACEService.manifest b/dom/secureelement/gonk/ACEService.manifest
new file mode 100644
index 000000000..40949c83d
--- /dev/null
+++ b/dom/secureelement/gonk/ACEService.manifest
@@ -0,0 +1,2 @@
+component {882a7463-2ca7-4d61-a89a-10eb6fd70478} ACEService.js
+contract @mozilla.org/secureelement/access-control/ace;1 {882a7463-2ca7-4d61-a89a-10eb6fd70478} \ No newline at end of file
diff --git a/dom/secureelement/gonk/GPAccessRulesManager.js b/dom/secureelement/gonk/GPAccessRulesManager.js
new file mode 100644
index 000000000..dce11ec09
--- /dev/null
+++ b/dom/secureelement/gonk/GPAccessRulesManager.js
@@ -0,0 +1,436 @@
+/* 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/. */
+
+/* Copyright © 2015, Deutsche Telekom, Inc. */
+
+"use strict";
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+XPCOMUtils.defineLazyServiceGetter(this, "UiccConnector",
+ "@mozilla.org/secureelement/connector/uicc;1",
+ "nsISecureElementConnector");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SEUtils",
+ "resource://gre/modules/SEUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "SE", function() {
+ let obj = {};
+ Cu.import("resource://gre/modules/se_consts.js", obj);
+ return obj;
+});
+
+XPCOMUtils.defineLazyGetter(this, "GP", function() {
+ let obj = {};
+ Cu.import("resource://gre/modules/gp_consts.js", obj);
+ return obj;
+});
+
+var DEBUG = SE.DEBUG_ACE;
+function debug(msg) {
+ if (DEBUG) {
+ dump("-*- GPAccessRulesManager " + msg);
+ }
+}
+
+/**
+ * Based on [1] - "GlobalPlatform Device Technology
+ * Secure Element Access Control Version 1.0".
+ * GPAccessRulesManager reads and parses access rules from SE file system
+ * as defined in section #7 of [1]: "Structure of Access Rule Files (ARF)".
+ * Rules retrieval from ARA-M applet is not implmented due to lack of
+ * commercial implemenations of ARA-M.
+ * @todo Bug 1137537: Implement ARA-M support according to section #4 of [1]
+ */
+function GPAccessRulesManager() {}
+
+GPAccessRulesManager.prototype = {
+ // source [1] section 7.1.3 PKCS#15 Selection
+ PKCS_AID: "a000000063504b43532d3135",
+
+ // APDUs (ISO 7816-4) for accessing rules on SE file system
+ // see for more details: http://www.cardwerk.com/smartcards/
+ // smartcard_standard_ISO7816-4_6_basic_interindustry_commands.aspx
+ READ_BINARY: [GP.CLA_SM, GP.INS_RB, GP.P1_RB, GP.P2_RB],
+ GET_RESPONSE: [GP.CLA_SM, GP.INS_GR, GP.P1_GR, GP.P2_GR],
+ SELECT_BY_DF: [GP.CLA_SM, GP.INS_SF, GP.P1_SF_DF, GP.P2_SF_FCP],
+
+ // Non-null if there is a channel open
+ channel: null,
+
+ // Refresh tag path in the acMain file as described in GPD spec,
+ // sections 7.1.5 and C.1.
+ REFRESH_TAG_PATH: [GP.TAG_SEQUENCE, GP.TAG_OCTETSTRING],
+ refreshTag: null,
+
+ // Contains rules as read from the SE
+ rules: [],
+
+ // Returns the latest rules. Results are cached.
+ getAccessRules: function getAccessRules() {
+ debug("getAccessRules");
+
+ return new Promise((resolve, reject) => {
+ this._readAccessRules(() => resolve(this.rules));
+ });
+ },
+
+ _readAccessRules: Task.async(function*(done) {
+ try {
+ yield this._openChannel(this.PKCS_AID);
+
+ let odf = yield this._readODF();
+ let dodf = yield this._readDODF(odf);
+
+ let acmf = yield this._readACMF(dodf);
+ let refreshTag = acmf[this.REFRESH_TAG_PATH[0]]
+ [this.REFRESH_TAG_PATH[1]];
+
+ // Update cached rules based on refreshTag.
+ if (SEUtils.arraysEqual(this.refreshTag, refreshTag)) {
+ debug("_readAccessRules: refresh tag equals to the one saved.");
+ yield this._closeChannel();
+ return done();
+ }
+
+ this.refreshTag = refreshTag;
+ debug("_readAccessRules: refresh tag saved: " + this.refreshTag);
+
+ let acrf = yield this._readACRules(acmf);
+ let accf = yield this._readACConditions(acrf);
+ this.rules = yield this._parseRules(acrf, accf);
+
+ DEBUG && debug("_readAccessRules: " + JSON.stringify(this.rules, 0, 2));
+
+ yield this._closeChannel();
+ done();
+ } catch (error) {
+ debug("_readAccessRules: " + error);
+ this.rules = [];
+ yield this._closeChannel();
+ done();
+ }
+ }),
+
+ _openChannel: function _openChannel(aid) {
+ if (this.channel !== null) {
+ debug("_openChannel: Channel already opened, rejecting.");
+ return Promise.reject();
+ }
+
+ return new Promise((resolve, reject) => {
+ UiccConnector.openChannel(aid, {
+ notifyOpenChannelSuccess: (channel, openResponse) => {
+ debug("_openChannel/notifyOpenChannelSuccess: Channel " + channel +
+ " opened, open response: " + openResponse);
+ this.channel = channel;
+ resolve();
+ },
+ notifyError: (error) => {
+ debug("_openChannel/notifyError: failed to open channel, error: " +
+ error);
+ reject(error);
+ }
+ });
+ });
+ },
+
+ _closeChannel: function _closeChannel() {
+ if (this.channel === null) {
+ debug("_closeChannel: Channel not opened, rejecting.");
+ return Promise.reject();
+ }
+
+ return new Promise((resolve, reject) => {
+ UiccConnector.closeChannel(this.channel, {
+ notifyCloseChannelSuccess: () => {
+ debug("_closeChannel/notifyCloseChannelSuccess: chanel " +
+ this.channel + " closed");
+ this.channel = null;
+ resolve();
+ },
+ notifyError: (error) => {
+ debug("_closeChannel/notifyError: error closing channel, error" +
+ error);
+ reject(error);
+ }
+ });
+ });
+ },
+
+ _exchangeAPDU: function _exchangeAPDU(bytes) {
+ DEBUG && debug("apdu " + JSON.stringify(bytes));
+
+ let apdu = this._bytesToAPDU(bytes);
+ return new Promise((resolve, reject) => {
+ UiccConnector.exchangeAPDU(this.channel, apdu.cla,
+ apdu.ins, apdu.p1, apdu.p2, apdu.data, apdu.le,
+ {
+ notifyExchangeAPDUResponse: (sw1, sw2, data) => {
+ debug("APDU response is " + sw1.toString(16) + sw2.toString(16) +
+ " data: " + data);
+
+ // 90 00 is "success"
+ if (sw1 !== 0x90 && sw2 !== 0x00) {
+ debug("rejecting APDU response");
+ reject(new Error("Response " + sw1 + "," + sw2));
+ return;
+ }
+
+ resolve(this._parseTLV(data));
+ },
+
+ notifyError: (error) => {
+ debug("_exchangeAPDU/notifyError " + error);
+ reject(error);
+ }
+ }
+ );
+ });
+ },
+
+ _readBinaryFile: function _readBinaryFile(selectResponse) {
+ DEBUG && debug("Select response: " + JSON.stringify(selectResponse));
+ // 0x80 tag parameter - get the elementary file (EF) length
+ // without structural information.
+ let fileLength = selectResponse[GP.TAG_FCP][0x80];
+
+ // If file is empty, no need to attempt to read it.
+ if (fileLength[0] === 0 && fileLength[1] === 0) {
+ return Promise.resolve(null);
+ }
+
+ // TODO READ BINARY with filelength not supported
+ // let readApdu = this.READ_BINARY.concat(fileLength);
+ return this._exchangeAPDU(this.READ_BINARY);
+ },
+
+ _selectAndRead: function _selectAndRead(df) {
+ return this._exchangeAPDU(this.SELECT_BY_DF.concat(df.length & 0xFF, df))
+ .then((resp) => this._readBinaryFile(resp));
+ },
+
+ _readODF: function _readODF() {
+ debug("_readODF");
+ return this._selectAndRead(GP.ODF_DF);
+ },
+
+ _readDODF: function _readDODF(odfFile) {
+ debug("_readDODF, ODF file: " + odfFile);
+
+ // Data Object Directory File (DODF) is used as an entry point to the
+ // Access Control data. It is specified in PKCS#15 section 6.7.6.
+ // DODF is referenced by the ODF file, which looks as follows:
+ // A7 06
+ // 30 04
+ // 04 02 XY WZ
+ // where [0xXY, 0xWZ] is a DF of DODF file.
+ let DODF_DF = odfFile[GP.TAG_EF_ODF][GP.TAG_SEQUENCE][GP.TAG_OCTETSTRING];
+ return this._selectAndRead(DODF_DF);
+ },
+
+ _readACMF: function _readACMF(dodfFile) {
+ debug("_readACMF, DODF file: " + dodfFile);
+
+ // ACMF file DF is referenced in DODF file, which looks like this:
+ //
+ // A1 29
+ // 30 00
+ // 30 0F
+ // 0C 0D 47 50 20 53 45 20 41 63 63 20 43 74 6C
+ // A1 14
+ // 30 12
+ // 06 0A 2A 86 48 86 FC 6B 81 48 01 01 <-- GPD registered OID
+ // 30 04
+ // 04 02 AB CD <-- ACMF DF
+ // A1 2B
+ // 30 00
+ // 30 0F
+ // 0C 0D 53 41 54 53 41 20 47 54 4F 20 31 2E 31
+ // A1 16
+ // 30 14
+ // 06 0C 2B 06 01 04 01 2A 02 6E 03 01 01 01 <-- some other OID
+ // 30 04
+ // 04 02 XY WZ <-- some other file's DF
+ //
+ // DODF file consists of DataTypes with oidDO entries. Entry with OID
+ // equal to "1.2.840.114283.200.1.1" ("2A 86 48 86 FC 6B 81 48 01 01")
+ // contains DF of the ACMF. In the file above, it means that ACMF DF
+ // equals to [0xAB, 0xCD], and not [0xXY, 0xWZ].
+ //
+ // Algorithm used to encode OID to an byte array:
+ // http://www.snmpsharpnet.com/?p=153
+
+ let gpdOid = [0x2A, // 1.2
+ 0x86, 0x48, // 840
+ 0x86, 0xFC, 0x6B, // 114283
+ 0x81, 0x48, // 129
+ 0x01, // 1
+ 0x01]; // 1
+
+ let records = SEUtils.ensureIsArray(dodfFile[GP.TAG_EXTERNALDO]);
+
+ // Look for the OID registered for GPD SE.
+ let gpdRecords = records.filter((record) => {
+ let oid = record[GP.TAG_EXTERNALDO][GP.TAG_SEQUENCE][GP.TAG_OID];
+ return SEUtils.arraysEqual(oid, gpdOid);
+ });
+
+ // [1] 7.1.5: "There shall be only one ACMF file per Secure Element.
+ // If a Secure Element contains several ACMF files, then the security shall
+ // be considered compromised and the Access Control enforcer shall forbid
+ // access to all (...) apps."
+ if (gpdRecords.length !== 1) {
+ return Promise.reject(new Error(gpdRecords.length + " ACMF files found"));
+ }
+
+ let ACMain_DF = gpdRecords[0][GP.TAG_EXTERNALDO][GP.TAG_SEQUENCE]
+ [GP.TAG_SEQUENCE][GP.TAG_OCTETSTRING];
+ return this._selectAndRead(ACMain_DF);
+ },
+
+ _readACRules: function _readACRules(acMainFile) {
+ debug("_readACRules, ACMain file: " + acMainFile);
+
+ // ACMF looks like this:
+ //
+ // 30 10
+ // 04 08 XX XX XX XX XX XX XX XX
+ // 30 04
+ // 04 02 XY WZ
+ //
+ // where [XY, WZ] is a DF of ACRF, and XX XX XX XX XX XX XX XX is a refresh
+ // tag.
+
+ let ACRules_DF = acMainFile[GP.TAG_SEQUENCE][GP.TAG_SEQUENCE][GP.TAG_OCTETSTRING];
+ return this._selectAndRead(ACRules_DF);
+ },
+
+ _readACConditions: function _readACConditions(acRulesFile) {
+ debug("_readACCondition, ACRules file: " + acRulesFile);
+
+ let acRules = SEUtils.ensureIsArray(acRulesFile[GP.TAG_SEQUENCE]);
+ if (acRules.length === 0) {
+ debug("No rules found in ACRules file.");
+ return Promise.reject(new Error("No rules found in ACRules file"));
+ }
+
+ // We first read all the condition files referenced in the ACRules file,
+ // because ACRules file might reference one ACCondition file more than
+ // once. Since reading it isn't exactly fast, we optimize here.
+ let acReadQueue = Promise.resolve({});
+
+ acRules.forEach((ruleEntry) => {
+ let df = ruleEntry[GP.TAG_SEQUENCE][GP.TAG_OCTETSTRING];
+
+ // Promise chain read condition entries:
+ let readAcCondition = (acConditionFiles) => {
+ if (acConditionFiles[df] !== undefined) {
+ debug("Skipping previously read acCondition df: " + df);
+ return acConditionFiles;
+ }
+
+ return this._selectAndRead(df)
+ .then((acConditionFileContents) => {
+ acConditionFiles[df] = acConditionFileContents;
+ return acConditionFiles;
+ });
+ }
+
+ acReadQueue = acReadQueue.then(readAcCondition);
+ });
+
+ return acReadQueue;
+ },
+
+ _parseRules: function _parseRules(acRulesFile, acConditionFiles) {
+ DEBUG && debug("_parseRules: acConditionFiles " + JSON.stringify(acConditionFiles));
+ let rules = [];
+
+ let acRules = SEUtils.ensureIsArray(acRulesFile[GP.TAG_SEQUENCE]);
+ acRules.forEach((ruleEntry) => {
+ DEBUG && debug("Parsing one rule: " + JSON.stringify(ruleEntry));
+ let rule = {};
+
+ // 0xA0 and 0x82 tags as per GPD spec sections C.1 - C.3. 0xA0 means
+ // that rule describes access to one SE applet only (and its AID is
+ // given). 0x82 means that rule describes acccess to all SE applets.
+ let oneApplet = ruleEntry[GP.TAG_GPD_AID];
+ let allApplets = ruleEntry[GP.TAG_GPD_ALL];
+
+ if (oneApplet) {
+ rule.applet = oneApplet[GP.TAG_OCTETSTRING];
+ } else if (allApplets) {
+ rule.applet = Ci.nsIAccessRulesManager.ALL_APPLET;
+ } else {
+ throw Error("Unknown applet definition");
+ }
+
+ let df = ruleEntry[GP.TAG_SEQUENCE][GP.TAG_OCTETSTRING];
+ let condition = acConditionFiles[df];
+ if (condition === null) {
+ rule.application = Ci.nsIAccessRulesManager.DENY_ALL;
+ } else if (condition[GP.TAG_SEQUENCE]) {
+ if (!Array.isArray(condition[GP.TAG_SEQUENCE]) &&
+ !condition[GP.TAG_SEQUENCE][GP.TAG_OCTETSTRING]) {
+ rule.application = Ci.nsIAccessRulesManager.ALLOW_ALL;
+ } else {
+ rule.application = SEUtils.ensureIsArray(condition[GP.TAG_SEQUENCE])
+ .map((conditionEntry) => {
+ return conditionEntry[GP.TAG_OCTETSTRING];
+ });
+ }
+ } else {
+ throw Error("Unknown application definition");
+ }
+
+ DEBUG && debug("Rule parsed, adding to the list: " + JSON.stringify(rule));
+ rules.push(rule);
+ });
+
+ DEBUG && debug("All rules parsed, we have those in total: " + JSON.stringify(rules));
+ return rules;
+ },
+
+ _parseTLV: function _parseTLV(bytes) {
+ let containerTags = [
+ GP.TAG_SEQUENCE,
+ GP.TAG_FCP,
+ GP.TAG_GPD_AID,
+ GP.TAG_EXTERNALDO,
+ GP.TAG_INDIRECT,
+ GP.TAG_EF_ODF
+ ];
+ return SEUtils.parseTLV(bytes, containerTags);
+ },
+
+ // TODO consider removing if better format for storing
+ // APDU consts will be introduced
+ _bytesToAPDU: function _bytesToAPDU(arr) {
+ let apdu = {
+ cla: arr[0] & 0xFF,
+ ins: arr[1] & 0xFF,
+ p1: arr[2] & 0xFF,
+ p2: arr[3] & 0xFF,
+ p3: arr[4] & 0xFF,
+ le: 0
+ };
+
+ let data = (apdu.p3 > 0) ? (arr.slice(5)) : [];
+ apdu.data = (data.length) ? SEUtils.byteArrayToHexString(data) : null;
+ return apdu;
+ },
+
+ classID: Components.ID("{3e046b4b-9e66-439a-97e0-98a69f39f55f}"),
+ contractID: "@mozilla.org/secureelement/access-control/rules-manager;1",
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAccessRulesManager])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([GPAccessRulesManager]);
diff --git a/dom/secureelement/gonk/GPAccessRulesManager.manifest b/dom/secureelement/gonk/GPAccessRulesManager.manifest
new file mode 100644
index 000000000..2d7ea038b
--- /dev/null
+++ b/dom/secureelement/gonk/GPAccessRulesManager.manifest
@@ -0,0 +1,2 @@
+component {3e046b4b-9e66-439a-97e0-98a69f39f55f} GPAccessRulesManager.js
+contract @mozilla.org/secureelement/access-control/rules-manager;1 {3e046b4b-9e66-439a-97e0-98a69f39f55f}
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]);
diff --git a/dom/secureelement/gonk/SecureElement.manifest b/dom/secureelement/gonk/SecureElement.manifest
new file mode 100644
index 000000000..a76fcfc11
--- /dev/null
+++ b/dom/secureelement/gonk/SecureElement.manifest
@@ -0,0 +1,18 @@
+# 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.
+
+# SecureElementManager
+component {48f4e650-28d2-11e4-8c21-0800200c9a66} SecureElement.js
+contract @mozilla.org/secureelement/parent-manager;1 {48f4e650-28d2-11e4-8c21-0800200c9a66}
+category profile-after-change SecureElementManager @mozilla.org/secureelement/parent-manager;1
diff --git a/dom/secureelement/gonk/UiccConnector.js b/dom/secureelement/gonk/UiccConnector.js
new file mode 100644
index 000000000..517303de2
--- /dev/null
+++ b/dom/secureelement/gonk/UiccConnector.js
@@ -0,0 +1,360 @@
+/* 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 Components, XPCOMUtils, SE, dump, libcutils, Services,
+ iccService, SEUtils */
+
+const { interfaces: Ci, utils: Cu, results: Cr } = 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", function() {
+ 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_CONNECTOR;
+function debug(s) {
+ if (DEBUG) {
+ dump("-*- UiccConnector: " + s + "\n");
+ }
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "SEUtils",
+ "resource://gre/modules/SEUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "iccService",
+ "@mozilla.org/icc/iccservice;1",
+ "nsIIccService");
+
+const UICCCONNECTOR_CONTRACTID =
+ "@mozilla.org/secureelement/connector/uicc;1";
+const UICCCONNECTOR_CID =
+ Components.ID("{8e040e5d-c8c3-4c1b-ac82-c00d25d8c4a4}");
+const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
+
+// TODO: Bug 1118099 - Add multi-sim support.
+// In the Multi-sim, there is more than one client.
+// For now, use default clientID as 0. Ideally, SE parent process would like to
+// know which clients (uicc slot) are connected to CLF over SWP interface.
+const PREFERRED_UICC_CLIENTID =
+ libcutils.property_get("ro.moz.se.def_client_id", "0");
+
+/**
+ * 'UiccConnector' object is a wrapper over iccService's channel management
+ * related interfaces that implements nsISecureElementConnector interface.
+ */
+function UiccConnector() {
+ this._init();
+}
+
+UiccConnector.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISecureElementConnector,
+ Ci.nsIIccListener]),
+ classID: UICCCONNECTOR_CID,
+ classInfo: XPCOMUtils.generateCI({
+ classID: UICCCONNECTOR_CID,
+ contractID: UICCCONNECTOR_CONTRACTID,
+ classDescription: "UiccConnector",
+ interfaces: [Ci.nsISecureElementConnector,
+ Ci.nsIIccListener,
+ Ci.nsIObserver]
+ }),
+
+ _SEListeners: [],
+ _isPresent: false,
+
+ _init: function() {
+ Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.registerListener(this);
+
+ // Update the state in order to avoid race condition.
+ // By this time, 'notifyCardStateChanged (with proper card state)'
+ // may have occurred already before this module initialization.
+ this._updatePresenceState();
+ },
+
+ _shutdown: function() {
+ Services.obs.removeObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.unregisterListener(this);
+ },
+
+ _updatePresenceState: function() {
+ let uiccNotReadyStates = [
+ Ci.nsIIcc.CARD_STATE_UNKNOWN,
+ Ci.nsIIcc.CARD_STATE_ILLEGAL,
+ Ci.nsIIcc.CARD_STATE_PERSONALIZATION_IN_PROGRESS,
+ Ci.nsIIcc.CARD_STATE_PERMANENT_BLOCKED,
+ Ci.nsIIcc.CARD_STATE_UNDETECTED
+ ];
+
+ let cardState = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID).cardState;
+ let uiccPresent = cardState !== null &&
+ uiccNotReadyStates.indexOf(cardState) == -1;
+
+ if (this._isPresent === uiccPresent) {
+ return;
+ }
+
+ debug("Uicc presence changed " + this._isPresent + " -> " + uiccPresent);
+ this._isPresent = uiccPresent;
+ this._SEListeners.forEach((listener) => {
+ listener.notifySEPresenceChanged(SE.TYPE_UICC, this._isPresent);
+ });
+ },
+
+ // See GP Spec, 11.1.4 Class Byte Coding
+ _setChannelToCLAByte: function(cla, channel) {
+ if (channel < SE.LOGICAL_CHANNEL_NUMBER_LIMIT) {
+ // b7 = 0 indicates the first interindustry class byte coding
+ cla = (cla & 0x9C) & 0xFF | channel;
+ } else if (channel < SE.SUPPLEMENTARY_LOGICAL_CHANNEL_NUMBER_LIMIT) {
+ // b7 = 1 indicates the further interindustry class byte coding
+ cla = (cla & 0xB0) & 0xFF | 0x40 | (channel - SE.LOGICAL_CHANNEL_NUMBER_LIMIT);
+ } else {
+ debug("Channel number must be within [0..19]");
+ return SE.ERROR_GENERIC;
+ }
+ return cla;
+ },
+
+ _doGetOpenResponse: function(channel, length, callback) {
+ // Le value is set. It means that this is a request for all available
+ // response bytes.
+ let cla = this._setChannelToCLAByte(SE.CLA_GET_RESPONSE, channel);
+ this.exchangeAPDU(channel, cla, SE.INS_GET_RESPONSE, 0x00, 0x00,
+ null, length, {
+ notifyExchangeAPDUResponse: function(sw1, sw2, response) {
+ debug("GET Response : " + response);
+ if (callback) {
+ callback({
+ error: SE.ERROR_NONE,
+ sw1: sw1,
+ sw2: sw2,
+ response: response
+ });
+ }
+ },
+
+ notifyError: function(reason) {
+ debug("Failed to get open response: " +
+ ", Rejected with Reason : " + reason);
+ if (callback) {
+ callback({ error: SE.ERROR_INVALIDAPPLICATION, reason: reason });
+ }
+ }
+ });
+ },
+
+ _doIccExchangeAPDU: function(channel, cla, ins, p1, p2, p3,
+ data, appendResp, callback) {
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.iccExchangeAPDU(channel, cla & 0xFC, ins, p1, p2, p3, data, {
+ notifyExchangeAPDUResponse: (sw1, sw2, response) => {
+ debug("sw1 : " + sw1 + ", sw2 : " + sw2 + ", response : " + response);
+
+ // According to ETSI TS 102 221 , Section 7.2.2.3.1,
+ // Enforce 'Procedure bytes' checks before notifying the callback.
+ // Note that 'Procedure bytes'are special cases.
+ // There is no need to handle '0x60' procedure byte as it implies
+ // no-action from SE stack perspective. This procedure byte is not
+ // notified to application layer.
+ if (sw1 === 0x6C) {
+ // Use the previous command header with length as second procedure
+ // byte (SW2) as received and repeat the procedure.
+
+ // Recursive! and Pass empty response '' as args, since '0x6C'
+ // procedure does not have to deal with appended responses.
+ this._doIccExchangeAPDU(channel, cla, ins, p1, p2,
+ sw2, data, "", callback);
+ } else if (sw1 === 0x61) {
+ // Since the terminal waited for a second procedure byte and
+ // received it (sw2), send a GET RESPONSE command header to the UICC
+ // with a maximum length of 'XX', where 'XX' is the value of the
+ // second procedure byte (SW2).
+
+ let claWithChannel = this._setChannelToCLAByte(SE.CLA_GET_RESPONSE,
+ channel);
+
+ // Recursive, with GET RESPONSE bytes and '0x61' procedure IS interested
+ // in appended responses. Pass appended response and note that p3=sw2.
+ this._doIccExchangeAPDU(channel, claWithChannel, SE.INS_GET_RESPONSE,
+ 0x00, 0x00, sw2, null,
+ (response ? response + appendResp : appendResp),
+ callback);
+ } else if (callback) {
+ callback.notifyExchangeAPDUResponse(sw1, sw2, response);
+ }
+ },
+
+ notifyError: (reason) => {
+ debug("Failed to trasmit C-APDU over the channel # : " + channel +
+ ", Rejected with Reason : " + reason);
+ if (callback) {
+ callback.notifyError(reason);
+ }
+ }
+ });
+ },
+
+ /**
+ * nsISecureElementConnector interface methods.
+ */
+
+ /**
+ * Opens a channel on a default clientId
+ */
+ openChannel: function(aid, callback) {
+ if (!this._isPresent) {
+ callback.notifyError(SE.ERROR_NOTPRESENT);
+ return;
+ }
+
+ // TODO: Bug 1118106: Handle Resource management / leaks by persisting
+ // the newly opened channel in some persistent storage so that when this
+ // module gets restarted (say after opening a channel) in the event of
+ // some erroneous conditions such as gecko restart /, crash it can read
+ // the persistent storage to check if there are any held resources
+ // (opened channels) and close them.
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.iccOpenChannel(aid, {
+ notifyOpenChannelSuccess: (channel) => {
+ this._doGetOpenResponse(channel, 0x00, function(result) {
+ if (callback) {
+ callback.notifyOpenChannelSuccess(channel, result.response);
+ }
+ });
+ },
+
+ notifyError: (reason) => {
+ debug("Failed to open the channel to AID : " + aid +
+ ", Rejected with Reason : " + reason);
+ if (callback) {
+ callback.notifyError(reason);
+ }
+ }
+ });
+ },
+
+ /**
+ * Transmit the C-APDU (command) on default clientId.
+ */
+ exchangeAPDU: function(channel, cla, ins, p1, p2, data, le, callback) {
+ if (!this._isPresent) {
+ callback.notifyError(SE.ERROR_NOTPRESENT);
+ return;
+ }
+
+ if (data && data.length % 2 !== 0) {
+ callback.notifyError("Data should be a hex string with length % 2 === 0");
+ return;
+ }
+
+ cla = this._setChannelToCLAByte(cla, channel);
+ let lc = data ? data.length / 2 : 0;
+ let p3 = lc || le;
+
+ if (lc && (le !== -1)) {
+ data += SEUtils.byteArrayToHexString([le]);
+ }
+
+ // Pass empty response '' as args as we are not interested in appended
+ // responses yet!
+ debug("exchangeAPDU on Channel # " + channel);
+ this._doIccExchangeAPDU(channel, cla, ins, p1, p2, p3, data, "",
+ callback);
+ },
+
+ /**
+ * Closes the channel on default clientId.
+ */
+ closeChannel: function(channel, callback) {
+ if (!this._isPresent) {
+ callback.notifyError(SE.ERROR_NOTPRESENT);
+ return;
+ }
+
+ let icc = iccService.getIccByServiceId(PREFERRED_UICC_CLIENTID);
+ icc.iccCloseChannel(channel, {
+ notifyCloseChannelSuccess: function() {
+ debug("closeChannel successfully closed the channel # : " + channel);
+ if (callback) {
+ callback.notifyCloseChannelSuccess();
+ }
+ },
+
+ notifyError: function(reason) {
+ debug("Failed to close the channel # : " + channel +
+ ", Rejected with Reason : " + reason);
+ if (callback) {
+ callback.notifyError(reason);
+ }
+ }
+ });
+ },
+
+ registerListener: function(listener) {
+ if (this._SEListeners.indexOf(listener) !== -1) {
+ throw Cr.NS_ERROR_UNEXPECTED;
+ }
+
+ this._SEListeners.push(listener);
+ // immediately notify listener about the current state
+ listener.notifySEPresenceChanged(SE.TYPE_UICC, this._isPresent);
+ },
+
+ unregisterListener: function(listener) {
+ let idx = this._SEListeners.indexOf(listener);
+ if (idx !== -1) {
+ this._SEListeners.splice(idx, 1);
+ }
+ },
+
+ /**
+ * nsIIccListener interface methods.
+ */
+ notifyStkCommand: function() {},
+
+ notifyStkSessionEnd: function() {},
+
+ notifyIccInfoChanged: function() {},
+
+ notifyCardStateChanged: function() {
+ debug("Card state changed, updating UICC presence.");
+ this._updatePresenceState();
+ },
+
+ /**
+ * nsIObserver interface methods.
+ */
+
+ observe: function(subject, topic, data) {
+ if (topic === NS_XPCOM_SHUTDOWN_OBSERVER_ID) {
+ this._shutdown();
+ }
+ }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UiccConnector]);
diff --git a/dom/secureelement/gonk/UiccConnector.manifest b/dom/secureelement/gonk/UiccConnector.manifest
new file mode 100644
index 000000000..5ac8b3b7b
--- /dev/null
+++ b/dom/secureelement/gonk/UiccConnector.manifest
@@ -0,0 +1,17 @@
+# 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.
+
+# UiccConnector
+component {8e040e5d-c8c3-4c1b-ac82-c00d25d8c4a4} UiccConnector.js
+contract @mozilla.org/secureelement/connector/uicc;1 {8e040e5d-c8c3-4c1b-ac82-c00d25d8c4a4}
diff --git a/dom/secureelement/gonk/gp_consts.js b/dom/secureelement/gonk/gp_consts.js
new file mode 100644
index 000000000..7c3bc7165
--- /dev/null
+++ b/dom/secureelement/gonk/gp_consts.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+/* Copyright © 2015, Deutsche Telekom, Inc. */
+
+/* Object Directory File (ODF) is an elementary file which contain
+ pointers to other EFs. It is specified in PKCS#15 section 6.7. */
+this.ODF_DF = [0x50, 0x31];
+
+/* ISO 7816-4: secure messaging */
+this.CLA_SM = 0x00;
+
+/* ISO 7816-4, 5.4.1 table 11 */
+this.INS_SF = 0xA4; // select file
+this.INS_GR = 0xC0; // get response
+this.INS_RB = 0xB0; // read binary
+
+/* ISO 7816-4: select file, see 6.11.3, table 58 & 59 */
+this.P1_SF_DF = 0x00; // select DF
+this.P2_SF_FCP = 0x04; // return FCP
+
+/* ISO 7816-4: read binary, 6.1.3. P1 and P2 describe offset of the first byte
+ to be read. We always read the whole files at the moment. */
+this.P1_RB = 0x00;
+this.P2_RB = 0x00;
+
+/* ISO 7816-4: get response, 7.1.3 table 74, P1-P2 '0000' (other values RFU) */
+this.P1_GR = 0x00;
+this.P2_GR = 0x00;
+
+/* ISO 7816-4: 5.1.5 File Control Information, Table 1. For FCP and FMD. */
+this.TAG_PROPRIETARY = 0x00;
+this.TAG_NON_TLV = 0x53;
+this.TAG_BER_TLV = 0x73;
+
+/* ASN.1 tags */
+this.TAG_SEQUENCE = 0x30;
+this.TAG_OCTETSTRING = 0x04;
+this.TAG_OID = 0x06; // Object Identifier
+
+/* ISO 7816-4: 5.1.5 File Control Information, Templates. */
+this.TAG_FCP = 0x62; // File control parameters template
+this.TAG_FMD = 0x64; // File management data template
+this.TAG_FCI = 0x6F; // File control information template
+
+/* EF_DIR tags */
+this.TAG_APPLTEMPLATE = 0x61;
+this.TAG_APPLIDENTIFIER = 0x4F;
+this.TAG_APPLLABEL = 0x50;
+this.TAG_APPLPATH = 0x51;
+
+this.TAG_GPD_ALL = 0x82; // EF-ACRules - GPD spec. "all applets"
+
+/* Generic TLVs that are parsed */
+this.TAG_GPD_AID = 0xA0; // AID in the EF-ACRules - GPD spec, "one applet"
+this.TAG_EXTERNALDO = 0xA1; // External data objects - PKCS#15
+this.TAG_INDIRECT = 0xA5; // Indirect value.
+this.TAG_EF_ODF = 0xA7; // Elemenetary File Object Directory File
+
+// Allow this file to be imported via Components.utils.import().
+this.EXPORTED_SYMBOLS = Object.keys(this);
diff --git a/dom/secureelement/gonk/nsIAccessControlEnforcer.idl b/dom/secureelement/gonk/nsIAccessControlEnforcer.idl
new file mode 100644
index 000000000..7ad1a97f6
--- /dev/null
+++ b/dom/secureelement/gonk/nsIAccessControlEnforcer.idl
@@ -0,0 +1,32 @@
+/* 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/. */
+
+/* Copyright © 2015, Deutsche Telekom, Inc. */
+
+#include "nsISupports.idl"
+
+interface nsIVariant;
+
+[scriptable, uuid(4994a960-26d9-4d71-82dd-4505bd97bf2a)]
+interface nsIAccessControlEnforcer : nsISupports
+{
+ /**
+ * Determines whether application identified by its ID should be allowed
+ * to access Secure Element's applet identified by its AID. Decision
+ * is made according to the GPD specification.
+ *
+ * @param localId
+ * ID of an application accessing SE
+ * @param seType
+ * Type of the SE.
+ * @param aid
+ * AID of a SE applet
+ * @return Promise which is resolved to true if access should be allowed,
+ * false otherwise, and rejected if the application contains
+ * no developer certificate.
+ */
+ jsval isAccessAllowed(in unsigned long localId,
+ in DOMString seType,
+ in DOMString aid);
+};
diff --git a/dom/secureelement/gonk/nsIAccessRulesManager.idl b/dom/secureelement/gonk/nsIAccessRulesManager.idl
new file mode 100644
index 000000000..173f57c90
--- /dev/null
+++ b/dom/secureelement/gonk/nsIAccessRulesManager.idl
@@ -0,0 +1,50 @@
+/* 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/. */
+
+/* Copyright © 2015, Deutsche Telekom, Inc. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(7baedd2a-3189-4b03-b2a3-34016043b5e2)]
+interface nsIAccessRulesManager : nsISupports
+{
+ /* Wildcard: rule allows all applications to access an SE applet */
+ const unsigned short ALLOW_ALL = 1;
+ /* Wildcard: rule denies all applications to access an SE applet */
+ const unsigned short DENY_ALL = 2;
+ /* Wildcard: rule allows application(s) access to all SE applets */
+ const unsigned short ALL_APPLET = 3;
+
+ /**
+ * Initiates Access Rules Manager, this should perform the initial
+ * reading of rules from access rule source
+ * @return Promise which is resolved if init is successful or rejected
+ * otherwise
+ */
+ jsval init();
+
+ /**
+ * Retrieves all access rules.
+ *
+ * Rules are stored in an array. Each rule contains the following properties:
+ * - applet - describes an SE applet referenced by this rule. Might equal
+ * to an applet AID (as a byte array), or to a wildcard "all"
+ * meaning all applets.
+ * - application - describes an application referenced by this rule. Might
+ * be an array of developer certificate hashes (each as
+ * a byte array) in which case it lists all applications
+ * allowed access. Alternatively, might equal to wildcard
+ * "allowed-all" or "denied-all".
+ *
+ * Example rule format:
+ * [{ applet: ALL_APPLET,
+ * application: [[0x01, 0x02, ..., 0x20],
+ * [0x20, 0x19, ...., 0x01]],
+ * { applet: [0x00, 0x01, ..., 0x05],
+ * application: ALLOW_ALL}}]
+ *
+ * @return Promise which resolves with Array containing parsed access rules
+ */
+ jsval getAccessRules();
+};
diff --git a/dom/secureelement/gonk/nsISecureElementConnector.idl b/dom/secureelement/gonk/nsISecureElementConnector.idl
new file mode 100644
index 000000000..92cc1eb2b
--- /dev/null
+++ b/dom/secureelement/gonk/nsISecureElementConnector.idl
@@ -0,0 +1,124 @@
+/* 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"
+
+[scriptable, uuid(1ff3f35a-1b6f-4e65-a89e-a363b8604cd7)]
+interface nsISEChannelCallback : nsISupports
+{
+ /**
+ * Callback function to notify on successfully opening a logical channel.
+ *
+ * @param channel
+ * The Channel Number/Handle that is successfully opened.
+ * @param openResponse
+ * Response from SE for OpenChannel operation.
+ */
+ void notifyOpenChannelSuccess(in long channel, in DOMString openResponse);
+
+ /**
+ * Callback function to notify on successfully closing the logical channel.
+ *
+ */
+ void notifyCloseChannelSuccess();
+
+ /**
+ * Callback function to notify the status of 'seExchangeAPDU' command.
+ *
+ * @param sw1
+ * Response's First Status Byte
+ * @param sw2
+ * Response's Second Status Byte
+ * @param data
+ * Response's data
+ */
+ void notifyExchangeAPDUResponse(in octet sw1,
+ in octet sw2,
+ in DOMString data);
+
+ /**
+ * Callback function to notify error
+ *
+ * @param error
+ * Error describing the reason for failure.
+ */
+ void notifyError(in DOMString error);
+};
+
+[scriptable, uuid(417f59ee-f582-45b9-9a4e-e9dcefecb4f7)]
+interface nsISEListener : nsISupports
+{
+ void notifySEPresenceChanged(in DOMString seType, in boolean isPresent);
+};
+
+[scriptable, uuid(3cef313a-1d01-432d-9cd2-6610a80911f3)]
+interface nsISecureElementConnector : nsISupports
+{
+ /**
+ * Open a logical communication channel with the specific secure element type
+ *
+ * @param aid
+ * Application Identifier of the Card Applet on the secure element.
+ * @param callback
+ * callback to notify the result of the operation.
+ */
+ void openChannel(in DOMString aid,
+ in nsISEChannelCallback callback);
+
+ /**
+ * Exchanges APDU channel with the specific secure element type
+ *
+ * @param channel
+ * Channel on which C-APDU to be transmitted.
+ * @param cla
+ Class Byte.
+ * @param ins
+ Instruction Byte
+ * @param p1
+ Reference parameter first byte
+ * @param p2
+ Reference parameter second byte
+ * Refer to 3G TS 31.101 , 10.2 'Command APDU Structure' for all the cases.
+ * @param data
+ Sequence of C-APDU data octets
+ * @param le [optional]
+ * le is the length of expected response. If the response is not expected,
+ it should be explicitly set to -1.
+ * @param callback
+ * callback to notify the result of the operation.
+ */
+ void exchangeAPDU(in long channel,
+ in octet cla,
+ in octet ins,
+ in octet p1,
+ in octet p2,
+ in DOMString data,
+ in short le,
+ in nsISEChannelCallback callback);
+
+ /**
+ * Closes the logical communication channel to the specific secure element type
+ *
+ * @param channel
+ * Channel to be closed.
+ * @param callback
+ * callback to notify the result of the operation.
+ */
+ void closeChannel(in long channel,
+ in nsISEChannelCallback callback);
+
+ /**
+ * Register a Secure Element listener
+ *
+ * @param listener
+ */
+ void registerListener(in nsISEListener listener);
+
+ /**
+ * Unregister a Secure Element listener
+ *
+ * @param listener
+ */
+ void unregisterListener(in nsISEListener listener);
+};
diff --git a/dom/secureelement/gonk/se_consts.js b/dom/secureelement/gonk/se_consts.js
new file mode 100644
index 000000000..13489b7ae
--- /dev/null
+++ b/dom/secureelement/gonk/se_consts.js
@@ -0,0 +1,68 @@
+/* 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. */
+
+// Set to true to debug SecureElement (SE) stack
+this.DEBUG_ALL = false;
+
+// Set individually to debug specific layers
+this.DEBUG_CONNECTOR = DEBUG_ALL || false;
+this.DEBUG_ACE = DEBUG_ALL || false ;
+this.DEBUG_SE = DEBUG_ALL || false ;
+
+// Maximun logical channels per session.
+// For 'uicc' SE type this value is 3, as opening a basic channel' : 0
+// is not allowed for security reasons. In such scenarios, possible
+// supplementary logical channels available are : [1, 2, or 3].
+// However,Other SE types may support upto max 4 (including '0').
+this.MAX_CHANNELS_ALLOWED_PER_SESSION = 4;
+
+this.BASIC_CHANNEL = 0;
+
+// According GPCardSpec 2.2
+this.MAX_APDU_LEN = 255; // including APDU header
+
+// CLA (1 byte) + INS (1 byte) + P1 (1 byte) + P2 (1 byte)
+this.APDU_HEADER_LEN = 4;
+
+this.LOGICAL_CHANNEL_NUMBER_LIMIT = 4;
+this.SUPPLEMENTARY_LOGICAL_CHANNEL_NUMBER_LIMIT = 20;
+
+this.MIN_AID_LEN = 5;
+this.MAX_AID_LEN = 16;
+
+this.CLA_GET_RESPONSE = 0x00;
+
+this.INS_SELECT = 0xA4;
+this.INS_MANAGE_CHANNEL = 0x70;
+this.INS_GET_RESPONSE = 0xC0;
+
+// Match the following errors with SecureElement.webidl's SEError enum values
+this.ERROR_NONE = "";
+this.ERROR_SECURITY = "SESecurityError";
+this.ERROR_IO = "SEIoError";
+this.ERROR_BADSTATE = "SEBadStateError";
+this.ERROR_INVALIDCHANNEL = "SEInvalidChannelError";
+this.ERROR_INVALIDAPPLICATION = "SEInvalidApplicationError";
+this.ERROR_GENERIC = "SEGenericError";
+this.ERROR_NOTPRESENT = "SENotPresentError";
+this.ERROR_ILLEGALPARAMETER = "SEIllegalParameterError";
+
+this.TYPE_UICC = "uicc";
+this.TYPE_ESE = "eSE";
+
+// Allow this file to be imported via Components.utils.import().
+this.EXPORTED_SYMBOLS = Object.keys(this);