Bug 879861 - Part 3a: SecureElement UiccConnector implementation. r=allstars.chh
authorSiddartha Pothapragada <Siddartha.Pothapragada@telekom.com>
Wed, 11 Feb 2015 03:55:00 -0500
changeset 256101 1d8a6e33379efcc470146e60b38123f258dca1cf
parent 256100 c46f533989827f76cc7964ff8e2c74a97391a408
child 256102 374b013116c244d5c1a7195b2af69e95d8b5553b
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersallstars
bugs879861
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 879861 - Part 3a: SecureElement UiccConnector implementation. r=allstars.chh
CLOBBER
dom/secureelement/gonk/UiccConnector.js
dom/secureelement/gonk/UiccConnector.manifest
dom/secureelement/gonk/nsISecureElementConnector.idl
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-bug 832837 removes nsISecurityWarningDialogs.idl, which requires a clobber according to bug 1114669
+Bug 879861 - Touch CLOBBER because adding a new IDL is a crapshoot these days.
new file mode 100644
--- /dev/null
+++ b/dom/secureelement/gonk/UiccConnector.js
@@ -0,0 +1,326 @@
+/* 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,
+   iccProvider, SEUtils */
+
+const { 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", 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
+let 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, "iccProvider",
+                                   "@mozilla.org/ril/content-helper;1",
+                                   "nsIIccProvider");
+
+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 iccProvider's channel management
+ * related interfaces that implements nsISecureElementConnector interface.
+ */
+function UiccConnector() {
+  this._init();
+}
+
+UiccConnector.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISecureElementConnector]),
+  classID: UICCCONNECTOR_CID,
+  classInfo: XPCOMUtils.generateCI({
+    classID: UICCCONNECTOR_CID,
+    contractID: UICCCONNECTOR_CONTRACTID,
+    classDescription: "UiccConnector",
+    interfaces: [Ci.nsISecureElementConnector,
+                 Ci.nsIIccListener,
+                 Ci.nsIObserver]
+  }),
+
+  _isPresent: false,
+
+  _init: function() {
+    Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+    iccProvider.registerIccMsg(PREFERRED_UICC_CLIENTID, 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);
+    iccProvider.unregisterIccMsg(PREFERRED_UICC_CLIENTID, this);
+  },
+
+  _updatePresenceState: function() {
+    // Consider following Card states as not quite ready for performing
+    // IccChannel* related commands
+    let notReadyStates = [
+      "unknown",
+      "illegal",
+      "personalizationInProgress",
+      "permanentBlocked",
+    ];
+    let cardState = iccProvider.getCardState(PREFERRED_UICC_CLIENTID);
+    this._isPresent = cardState !== null &&
+                      notReadyStates.indexOf(cardState) == -1;
+  },
+
+  // 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) {
+    iccProvider.iccExchangeAPDU(PREFERRED_UICC_CLIENTID, 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.
+    iccProvider.iccOpenChannel(PREFERRED_UICC_CLIENTID, 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;
+    }
+
+    iccProvider.iccCloseChannel(PREFERRED_UICC_CLIENTID, 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);
+        }
+      }
+    });
+  },
+
+  /**
+   * nsIIccListener interface methods.
+   */
+  notifyStkCommand: function() {},
+
+  notifyStkSessionEnd: function() {},
+
+  notifyIccInfoChanged: function() {},
+
+  notifyCardStateChanged: function() {
+    this._updatePresenceState();
+  },
+
+  /**
+   * nsIObserver interface methods.
+   */
+
+  observe: function(subject, topic, data) {
+    if (topic === NS_XPCOM_SHUTDOWN_OBSERVER_ID) {
+      this._shutdown();
+    }
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UiccConnector]);
new file mode 100644
--- /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}
new file mode 100644
--- /dev/null
+++ b/dom/secureelement/gonk/nsISecureElementConnector.idl
@@ -0,0 +1,104 @@
+/* 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(88e23684-0de3-4792-83a0-0eb67a6ca448)]
+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);
+};