/* 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]);