1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 this.EXPORTED_SYMBOLS = ["MobileIdentityManager"];
9 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
11 Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
12 Cu.import("resource://gre/modules/MobileIdentityUIGlueCommon.jsm");
13 Cu.import("resource://gre/modules/Promise.jsm");
14 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
16 XPCOMUtils.defineLazyModuleGetter(this, "MobileIdentityCredentialsStore",
17 "resource://gre/modules/MobileIdentityCredentialsStore.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "MobileIdentityClient",
20 "resource://gre/modules/MobileIdentityClient.jsm");
22 XPCOMUtils.defineLazyModuleGetter(this, "MobileIdentitySmsMtVerificationFlow",
23 "resource://gre/modules/MobileIdentitySmsMtVerificationFlow.jsm");
25 XPCOMUtils.defineLazyModuleGetter(this, "MobileIdentitySmsMoMtVerificationFlow",
26 "resource://gre/modules/MobileIdentitySmsMoMtVerificationFlow.jsm");
28 XPCOMUtils.defineLazyModuleGetter(this, "PhoneNumberUtils",
29 "resource://gre/modules/PhoneNumberUtils.jsm");
31 XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
32 "resource://gre/modules/identity/jwcrypto.jsm");
34 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
35 "@mozilla.org/uuid-generator;1",
38 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
39 "@mozilla.org/parentprocessmessagemanager;1",
40 "nsIMessageListenerManager");
42 XPCOMUtils.defineLazyServiceGetter(this, "permissionManager",
43 "@mozilla.org/permissionmanager;1",
44 "nsIPermissionManager");
46 XPCOMUtils.defineLazyServiceGetter(this, "securityManager",
47 "@mozilla.org/scriptsecuritymanager;1",
48 "nsIScriptSecurityManager");
50 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
51 "@mozilla.org/AppsService;1",
55 XPCOMUtils.defineLazyServiceGetter(this, "Ril",
57 "nsIRadioInterfaceLayer");
59 XPCOMUtils.defineLazyServiceGetter(this, "IccProvider",
60 "@mozilla.org/ril/content-helper;1",
63 XPCOMUtils.defineLazyServiceGetter(this, "MobileConnectionService",
64 "@mozilla.org/mobileconnection/mobileconnectionservice;1",
65 "nsIMobileConnectionService");
69 this.MobileIdentityManager = {
72 log.debug("MobileIdentityManager init");
73 Services.obs.addObserver(this, "xpcom-shutdown", false);
74 ppmm.addMessageListener(GET_ASSERTION_IPC_MSG, this);
75 this.messageManagers = {};
77 this.certificates = {};
80 receiveMessage: function(aMessage) {
81 log.debug("Received " + aMessage.name);
83 if (aMessage.name !== GET_ASSERTION_IPC_MSG) {
87 let msg = aMessage.json;
89 // We save the message target message manager so we can later dispatch
90 // back messages without broadcasting to all child processes.
91 let promiseId = msg.promiseId;
92 this.messageManagers[promiseId] = aMessage.target;
94 this.getMobileIdAssertion(aMessage.principal, promiseId, msg.options);
97 observe: function(subject, topic, data) {
98 if (topic != "xpcom-shutdown") {
102 ppmm.removeMessageListener(GET_ASSERTION_IPC_MSG, this);
103 Services.obs.removeObserver(this, "xpcom-shutdown");
104 this.messageManagers = null;
107 /*********************************************************
109 ********************************************************/
111 // We have these getters to allow mocking RIL stuff from the tests.
120 if (this._iccProvider) {
121 return this._iccProvider;
126 get mobileConnectionService() {
127 if (this._mobileConnectionService) {
128 return this._mobileConnectionService;
130 return MobileConnectionService;
136 return this._iccInfo;
141 notifyStkCommand: function() {},
143 notifyStkSessionEnd: function() {},
145 notifyCardStateChanged: function() {},
147 notifyIccInfoChanged: function() {
148 // If we receive a notification about an ICC info change, we clear
149 // the ICC related caches so they can be rebuilt with the new changes.
151 log.debug("ICC info changed observed. Clearing caches");
153 // We don't need to keep listening for changes until we rebuild the
155 for (let i = 0; i < self._iccInfo.length; i++) {
156 self.iccProvider.unregisterIccMsg(self._iccInfo[i].clientId,
160 self._iccInfo = null;
165 // _iccInfo is a local cache containing the information about the SIM cards
166 // that is interesting for the Mobile ID flow.
167 // The index of this array does not necesarily need to match the real
168 // identifier of the SIM card ("clientId" or "serviceId" in RIL language).
171 for (let i = 0; i < this.ril.numRadioInterfaces; i++) {
172 let rilContext = this.ril.getRadioInterface(i).rilContext;
174 log.warn("Tried to get the RIL context for an invalid service ID " + i);
178 let info = rilContext.iccInfo;
179 if (!info || !info.iccid ||
180 !info.mcc || !info.mcc.length ||
181 !info.mnc || !info.mnc.length) {
182 log.warn("Absent or invalid ICC info");
186 let connection = this.mobileConnectionService.getItemByServiceId(i);
187 let voice = connection && connection.voice;
188 let data = connection && connection.data;
192 voice.network.shortName &&
193 voice.network.shortName.length) {
194 operator = voice.network.shortName;
197 data.network.shortName &&
198 data.network.shortName.length) {
199 operator = data.network.shortName;
203 // Because it is possible that the _iccInfo array index doesn't match
204 // the real client ID, we need to store this value for later usage.
209 // GSM SIMs may have MSISDN while CDMA SIMs may have MDN
210 msisdn: info.msisdn || info.mdn || null,
212 roaming: voice && voice.roaming
215 // We need to subscribe to ICC change notifications so we can refresh
216 // the cache if any change is observed.
217 this.iccProvider.registerIccMsg(i, iccListener);
220 return this._iccInfo;
236 for (let i = 0; i < this.iccInfo.length; i++) {
237 this._iccIds.push(this.iccInfo[i].iccId);
246 if (!this._credStore) {
247 this._credStore = new MobileIdentityCredentialsStore();
248 this._credStore.init();
250 return this._credStore;
255 this._ui = Cc["@mozilla.org/services/mobileid-ui-glue;1"]
256 .createInstance(Ci.nsIMobileIdentityUIGlue);
257 this._ui.oncancel = this.onUICancel.bind(this);
258 this._ui.onresendcode = this.onUIResendCode.bind(this);
265 this._client = new MobileIdentityClient();
271 return this.iccInfo && this.iccInfo.length > 1;
274 getVerificationOptionsForIcc: function(aServiceId) {
275 log.debug("getVerificationOptionsForIcc " + aServiceId);
276 log.debug("iccInfo ${}", this.iccInfo[aServiceId]);
277 // First of all we need to check if we already have existing credentials
278 // for the given SIM information (ICC ID or MSISDN). If we have no valid
279 // credentials, we have to check with the server which options do we have
280 // to verify the associated phone number.
281 return this.credStore.getByIccId(this.iccInfo[aServiceId].iccId)
287 return this.credStore.getByMsisdn(this.iccInfo[aServiceId].msisdn);
293 this.iccInfo[aServiceId].credentials = creds;
296 // We have no credentials for this SIM, so we need to ask the server
297 // which options do we have to verify the phone number.
298 // But we need to be online...
299 if (Services.io.offline) {
300 return Promise.reject(ERROR_OFFLINE);
302 return this.client.discover(this.iccInfo[aServiceId].msisdn,
303 this.iccInfo[aServiceId].mcc,
304 this.iccInfo[aServiceId].mnc,
305 this.iccInfo[aServiceId].roaming);
310 // If we already have credentials for this ICC and no discover request
311 // is done, we just bail out.
312 if (!result || !result.verificationMethods) {
315 log.debug("Discover result ${}", result);
316 this.iccInfo[aServiceId].verificationMethods = result.verificationMethods;
317 this.iccInfo[aServiceId].verificationDetails = result.verificationDetails;
318 this.iccInfo[aServiceId].canDoSilentVerification =
319 (result.verificationMethods.indexOf(SMS_MO_MT) != -1);
325 getVerificationOptions: function() {
326 log.debug("getVerificationOptions");
327 // We try to get if we already have credentials for any of the inserted
328 // SIM cards if any is available and we try to get the possible
329 // verification mechanisms for these SIM cards.
330 // All this information will be stored in iccInfo.
331 if (!this.iccInfo || !this.iccInfo.length) {
332 let deferred = Promise.defer();
333 deferred.resolve(null);
334 return deferred.promise;
338 for (let i = 0; i < this.iccInfo.length; i++) {
339 promises.push(this.getVerificationOptionsForIcc(i));
341 return Promise.all(promises);
344 getKeyPair: function(aSessionToken) {
345 if (this.keyPairs[aSessionToken] &&
346 this.keyPairs[aSessionToken].validUntil > this.client.hawk.now()) {
347 return Promise.resolve(this.keyPairs[aSessionToken].keyPair);
350 let validUntil = this.client.hawk.now() + KEY_LIFETIME;
351 let deferred = Promise.defer();
352 jwcrypto.generateKeyPair("DS160", (error, kp) => {
354 return deferred.reject(error);
356 this.keyPairs[aSessionToken] = {
358 validUntil: validUntil
360 delete this.certificates[aSessionToken];
361 deferred.resolve(kp);
364 return deferred.promise;
367 getCertificate: function(aSessionToken, aPublicKey) {
368 log.debug("getCertificate");
369 if (this.certificates[aSessionToken] &&
370 this.certificates[aSessionToken].validUntil > this.client.hawk.now()) {
371 return Promise.resolve(this.certificates[aSessionToken].cert);
374 if (Services.io.offline) {
375 return Promise.reject(ERROR_OFFLINE);
378 let validUntil = this.client.hawk.now() + KEY_LIFETIME;
379 let deferred = Promise.defer();
380 this.client.sign(aSessionToken, CERTIFICATE_LIFETIME,
384 log.debug("Got signed certificate");
385 this.certificates[aSessionToken] = {
386 cert: signedCert.cert,
387 validUntil: validUntil
389 deferred.resolve(signedCert.cert);
393 return deferred.promise;
396 /*********************************************************
397 * Setters (for test only purposes)
398 ********************************************************/
403 set credStore(aCredStore) {
404 this._credStore = aCredStore;
407 set client(aClient) {
408 this._client = aClient;
411 set iccInfo(aIccInfo) {
412 this._iccInfo = aIccInfo;
415 /*********************************************************
417 ********************************************************/
419 onUICancel: function() {
420 log.debug("UI cancel");
421 if (this.activeVerificationFlow) {
422 this.rejectVerification();
426 onUIResendCode: function() {
427 log.debug("UI resend code");
428 if (!this.activeVerificationFlow) {
431 this.doVerification();
434 /*********************************************************
436 *********************************************************/
437 success: function(aPromiseId, aResult) {
438 let mm = this.messageManagers[aPromiseId];
439 mm.sendAsyncMessage("MobileId:GetAssertion:Return:OK", {
440 promiseId: aPromiseId,
445 error: function(aPromiseId, aError) {
446 let mm = this.messageManagers[aPromiseId];
447 mm.sendAsyncMessage("MobileId:GetAssertion:Return:KO", {
448 promiseId: aPromiseId,
453 /*********************************************************
455 ********************************************************/
457 addPermission: function(aPrincipal) {
458 permissionManager.addFromPrincipal(aPrincipal, MOBILEID_PERM,
459 Ci.nsIPermissionManager.ALLOW_ACTION);
462 /*********************************************************
463 * Phone number verification
464 ********************************************************/
466 rejectVerification: function(aReason) {
467 if (!this.activeVerificationDeferred) {
470 this.activeVerificationDeferred.reject(aReason);
471 this.activeVerificationDeferred = null;
472 this.cleanupVerification(true /* unregister */);
475 resolveVerification: function(aResult) {
476 if (!this.activeVerificationDeferred) {
479 this.activeVerificationDeferred.resolve(aResult);
480 this.activeVerificationDeferred = null;
481 this.cleanupVerification();
484 cleanupVerification: function(aUnregister = false) {
485 if (!this.activeVerificationFlow) {
488 this.activeVerificationFlow.cleanup(aUnregister);
489 this.activeVerificationFlow = null;
492 doVerification: function() {
493 this.activeVerificationFlow.doVerification()
495 (verificationResult) => {
496 log.debug("onVerificationResult ");
497 if (!verificationResult || !verificationResult.sessionToken ||
498 !verificationResult.msisdn) {
499 return this.rejectVerification(
500 ERROR_INTERNAL_INVALID_VERIFICATION_RESULT
503 this.resolveVerification(verificationResult);
509 // Verification timeout.
510 log.warn("doVerification " + reason);
515 _verificationFlow: function(aToVerify, aOrigin) {
516 log.debug("toVerify ${}", aToVerify);
518 // We create the corresponding verification flow and save its instance
519 // in case that we need to cancel it or retrigger it because the user
520 // requested its cancelation or a resend of the verification code.
521 if (aToVerify.verificationMethod.indexOf(SMS_MT) != -1 &&
523 aToVerify.verificationDetails &&
524 aToVerify.verificationDetails.mtSender) {
525 this.activeVerificationFlow = new MobileIdentitySmsMtVerificationFlow({
527 msisdn: aToVerify.msisdn,
530 iccId: aToVerify.iccId,
531 external: aToVerify.serviceId === undefined,
532 mtSender: aToVerify.verificationDetails.mtSender
538 } else if (aToVerify.verificationMethod.indexOf(SMS_MO_MT) != -1 &&
539 aToVerify.serviceId &&
540 aToVerify.verificationDetails &&
541 aToVerify.verificationDetails.moVerifier &&
542 aToVerify.verificationDetails.mtSender) {
543 this.activeVerificationFlow = new MobileIdentitySmsMoMtVerificationFlow({
545 serviceId: aToVerify.serviceId,
546 iccId: aToVerify.iccId,
547 mtSender: aToVerify.verificationDetails.mtSender,
548 moVerifier: aToVerify.verificationDetails.moVerifier
555 return Promise.reject(ERROR_INTERNAL_CANNOT_VERIFY_SELECTION);
558 if (!this.activeVerificationFlow) {
559 return Promise.reject(ERROR_INTERNAL_CANNOT_CREATE_VERIFICATION_FLOW);
562 this.activeVerificationDeferred = Promise.defer();
563 this.doVerification();
564 return this.activeVerificationDeferred.promise;
567 verificationFlow: function(aUserSelection, aOrigin) {
568 log.debug("verificationFlow ${}", aUserSelection);
570 if (!aUserSelection) {
571 return Promise.reject(ERROR_INTERNAL_INVALID_USER_SELECTION);
574 let serviceId = aUserSelection.serviceId || undefined;
575 // We check if the user entered phone number corresponds with any of the
576 // inserted SIMs known phone numbers.
577 if (aUserSelection.msisdn && this.iccInfo) {
578 for (let i = 0; i < this.iccInfo.length; i++) {
579 if (aUserSelection.msisdn == this.iccInfo[i].msisdn) {
588 if (serviceId !== undefined) {
589 log.debug("iccInfo ${}", this.iccInfo[serviceId]);
590 toVerify.serviceId = serviceId;
591 toVerify.iccId = this.iccInfo[serviceId].iccId;
592 toVerify.msisdn = this.iccInfo[serviceId].msisdn;
593 toVerify.mcc = this.iccInfo[serviceId].mcc;
594 toVerify.mnc = this.iccInfo[serviceId].mnc;
595 toVerify.verificationMethod =
596 this.iccInfo[serviceId].verificationMethods[0];
597 toVerify.verificationDetails =
598 this.iccInfo[serviceId].verificationDetails[toVerify.verificationMethod];
599 return this._verificationFlow(toVerify, aOrigin);
601 toVerify.msisdn = aUserSelection.msisdn;
602 toVerify.mcc = aUserSelection.mcc;
603 return this.client.discover(aUserSelection.msisdn,
606 (discoverResult) => {
607 if (!discoverResult || !discoverResult.verificationMethods) {
608 return Promise.reject(ERROR_INTERNAL_UNEXPECTED);
610 log.debug("discoverResult ${}", discoverResult);
611 toVerify.verificationMethod = discoverResult.verificationMethods[0];
612 toVerify.verificationDetails =
613 discoverResult.verificationDetails[toVerify.verificationMethod];
614 return this._verificationFlow(toVerify, aOrigin);
621 /*********************************************************
622 * UI prompt functions.
623 ********************************************************/
625 // The phone number prompt will be used to confirm that the user wants to
626 // verify and share a known phone number and to allow her to introduce an
627 // external phone or to select between phone numbers or SIM cards (if the
628 // phones are not known) in a multi-SIM scenario.
629 // This prompt will be considered as the permission prompt and its choice
630 // will be remembered per origin by default.
631 prompt: function prompt(aPrincipal, aManifestURL, aPhoneInfo) {
632 log.debug("prompt ${principal} ${manifest} ${phoneInfo}", {
633 principal: aPrincipal,
634 manifest: aManifestURL,
635 phoneInfo: aPhoneInfo
638 let phoneInfoArray = [];
641 phoneInfoArray.push(aPhoneInfo);
645 for (let i = 0; i < this.iccInfo.length; i++) {
646 // If we don't know the msisdn, there is no previous credentials and
647 // a silent verification is not possible or if the msisdn is the one
648 // that is already chosen, we don't list this SIM as an option.
649 if ((!this.iccInfo[i].msisdn && !this.iccInfo[i].credentials &&
650 !this.iccInfo[i].canDoSilentVerification) ||
652 (this.iccInfo[i].msisdn == aPhoneInfo.msisdn ||
653 this.iccInfo[i].iccId == aPhoneInfo.iccId))) {
657 let phoneInfo = new MobileIdentityUIGluePhoneInfo(
658 this.iccInfo[i].msisdn,
659 this.iccInfo[i].operator,
661 this.iccInfo[i].iccId, // iccId
664 phoneInfoArray.push(phoneInfo);
668 return this.ui.startFlow(aManifestURL, phoneInfoArray)
671 log.debug("startFlow result ${} ", result);
673 (!result.phoneNumber && (result.serviceId === undefined))) {
674 return Promise.reject(ERROR_INTERNAL_INVALID_PROMPT_RESULT);
680 // If the user selected one of the existing SIM cards we have to check
681 // that we either have the MSISDN for that SIM, we have already existing
682 // credentials or we can do a silent verification that does not require
683 // us to have the MSISDN in advance.
684 // result.serviceId can be "0".
685 if (result.serviceId !== undefined &&
686 result.serviceId !== null) {
687 let icc = this.iccInfo[result.serviceId];
688 log.debug("icc ${}", icc);
689 if (!icc || !icc.msisdn && !icc.canDoSilentVerification &&
691 return Promise.reject(ERROR_INTERNAL_CANNOT_VERIFY_SELECTION);
696 msisdn = result.prefix ? result.prefix + result.phoneNumber
697 : result.phoneNumber;
701 // We need to check that the selected phone number is valid and
702 // if it is not notify the UI about the error and allow the user to
705 !PhoneNumberUtils.parseWithMCC(msisdn, mcc)) {
706 this.ui.error(ERROR_INVALID_PHONE_NUMBER);
707 return this.prompt(aPrincipal, aManifestURL, aPhoneInfo);
710 log.debug("Selected msisdn (if any): " + msisdn + " - " + mcc);
712 // The user gave permission for the requester origin, so we store it.
713 this.addPermission(aPrincipal);
718 serviceId: result.serviceId
724 promptAndVerify: function(aPrincipal, aManifestURL, aCreds) {
725 log.debug("promptAndVerify " + aPrincipal + ", " + aManifestURL +
729 if (Services.io.offline) {
730 return Promise.reject(ERROR_OFFLINE);
733 // Before prompting the user we need to check with the server the
734 // phone number verification methods that are possible with the
735 // SIMs inserted in the device.
736 return this.getVerificationOptions()
739 // If we have an exisiting credentials, we add its associated
740 // phone number information to the list of choices to present
741 // to the user within the selection prompt.
744 phoneInfo = new MobileIdentityUIGluePhoneInfo(
747 undefined, // service ID
748 aCreds.iccId, // iccId
752 return this.prompt(aPrincipal, aManifestURL, phoneInfo);
757 log.debug("promptResult ${}", promptResult);
758 // If we had credentials and the user didn't change her
759 // selection we return them. Otherwise, we need to verify
761 if (promptResult.msisdn && aCreds &&
762 promptResult.msisdn == aCreds.msisdn) {
766 // We might already have credentials for the user selected icc. In
767 // that case, we update the credentials store with the new origin and
768 // return the credentials.
769 if (promptResult.serviceId) {
770 let creds = this.iccInfo[promptResult.serviceId].credentials;
772 this.credStore.add(creds.iccId, creds.msisdn, aPrincipal.origin,
773 creds.sessionToken, this.iccIds);
778 // Or we might already have credentials for the selected phone
779 // number and so we do the same: update the credentials store with the
780 // new origin and return the credentials.
781 return this.credStore.getByMsisdn(promptResult.msisdn)
785 this.credStore.add(creds.iccId, creds.msisdn, aPrincipal.origin,
786 creds.sessionToken, this.iccIds);
789 // Otherwise, we need to verify the new number selected by the
791 return this.verificationFlow(promptResult, aPrincipal.origin);
798 /*********************************************************
800 *********************************************************/
802 checkNewCredentials: function(aOldCreds, aNewCreds, aOrigin) {
803 // If there were previous credentials and the user changed her
804 // choice, we need to remove the origin from the old credentials.
805 if (aNewCreds.msisdn != aOldCreds.msisdn) {
806 return this.credStore.removeOrigin(aOldCreds.msisdn,
814 // Otherwise, we update the status of the SIM cards in the device
815 // so we know that the user decided not to take the chance to change
816 // her selection. We won't bother her again until a new SIM card
817 // change is detected.
818 return this.credStore.setDeviceIccIds(aOldCreds.msisdn, this.iccIds)
827 /*********************************************************
828 * Assertion generation
829 ********************************************************/
831 generateAssertion: function(aCredentials, aOrigin) {
832 if (!aCredentials.sessionToken) {
833 return Promise.reject(ERROR_INTERNAL_INVALID_TOKEN);
836 let deferred = Promise.defer();
838 this.getKeyPair(aCredentials.sessionToken)
841 log.debug("keyPair " + keyPair.serializedPublicKey);
843 duration: ASSERTION_LIFETIME,
844 now: this.client.hawk.now(),
845 localtimeOffsetMsec: this.client.hawk.localtimeOffsetMsec
848 this.getCertificate(aCredentials.sessionToken,
849 keyPair.serializedPublicKey)
852 log.debug("generateAssertion " + signedCert);
853 jwcrypto.generateAssertion(signedCert, keyPair,
855 (error, assertion) => {
857 log.error("Error generating assertion " + err);
858 deferred.reject(error);
861 this.credStore.add(aCredentials.iccId,
864 aCredentials.sessionToken,
868 deferred.resolve(assertion);
877 return deferred.promise;
880 getMobileIdAssertion: function(aPrincipal, aPromiseId, aOptions) {
881 log.debug("getMobileIdAssertion ${}", aPrincipal);
883 let uri = Services.io.newURI(aPrincipal.origin, null, null);
884 let principal = securityManager.getAppCodebasePrincipal(
885 uri, aPrincipal.appId, aPrincipal.isInBrowserElement);
886 let manifestURL = appsService.getManifestURLByLocalId(aPrincipal.appId);
888 let permission = permissionManager.testPermissionFromPrincipal(
893 if (permission == Ci.nsIPermissionManager.DENY_ACTION ||
894 permission == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
895 this.error(aPromiseId, ERROR_PERMISSION_DENIED);
901 // First of all we look if we already have credentials for this origin.
902 // If we don't have credentials it means that it is the first time that
903 // the caller requested an assertion.
904 this.credStore.getByOrigin(aPrincipal.origin)
907 log.debug("creds ${creds} - ${origin}", { creds: creds,
908 origin: aPrincipal.origin });
909 if (!creds || !creds.sessionToken) {
910 log.debug("No credentials");
916 // Even if we already have credentials for this origin, the consumer
917 // of the API might want to force the identity selection dialog.
918 if (aOptions.forceSelection || aOptions.refreshCredentials) {
919 return this.promptAndVerify(principal, manifestURL, creds)
922 return this.checkNewCredentials(creds, newCreds,
928 // SIM change scenario.
930 // It is possible that the SIM cards inserted in the device at the
931 // moment of the previous verification where we obtained the credentials
932 // has changed. In that case, we need to let the user knowabout this
933 // situation. Otherwise, we just return the credentials.
934 log.debug("Looking for SIM changes. Credentials ICCS ${creds} " +
935 "Device ICCS ${device}", { creds: creds.deviceIccIds,
936 device: this.iccIds });
937 let simChanged = (creds.deviceIccIds == null && this.iccIds != null) ||
938 (creds.deviceIccIds != null && this.iccIds == null);
941 creds.deviceIccIds != null &&
942 this.iccIds != null) {
943 simChanged = creds.deviceIccIds.length != this.iccIds.length;
947 creds.deviceIccIds != null &&
948 this.iccIds != null) {
949 let intersection = creds.deviceIccIds.filter((n) => {
950 return this.iccIds.indexOf(n) != -1;
952 simChanged = intersection.length != creds.deviceIccIds.length ||
953 intersection.length != this.iccIds.length;
960 // At this point we know that the SIM associated with the credentials
961 // is not present in the device any more or a new SIM has been detected,
962 // so we need to ask the user what to do.
963 return this.promptAndVerify(principal, manifestURL, creds)
966 return this.checkNewCredentials(creds, newCreds,
974 // Even if we have credentails it is possible that the user has
975 // removed the permission to share its mobile id with this origin, so
976 // we check the permission and if it is not granted, we ask the user
977 // before generating and sharing the assertion.
978 // If we've just prompted the user in the previous step, the permission
979 // is already granted and stored so we just progress the credentials.
980 // But we have to refresh the cached permission before checking.
982 permission = permissionManager.testPermissionFromPrincipal(
986 if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
989 return this.promptAndVerify(principal, manifestURL, creds);
991 return this.promptAndVerify(principal, manifestURL);
997 return this.generateAssertion(creds, principal.origin);
999 return Promise.reject(ERROR_INTERNAL_CANNOT_GENERATE_ASSERTION);
1005 return Promise.reject(ERROR_INTERNAL_CANNOT_GENERATE_ASSERTION);
1008 // Get the verified phone number from the assertion.
1009 let segments = assertion.split(".");
1011 return Promise.reject(ERROR_INVALID_ASSERTION);
1014 // We need to translate the base64 alphabet used in JWT to our base64
1015 // alphabet before calling atob.
1016 let decodedPayload = JSON.parse(atob(segments[1].replace(/-/g, '+')
1017 .replace(/_/g, '/')));
1019 if (!decodedPayload || !decodedPayload.verifiedMSISDN) {
1020 return Promise.reject(ERROR_INVALID_ASSERTION);
1023 this.ui.verified(decodedPayload.verifiedMSISDN);
1025 this.success(aPromiseId, assertion);
1031 log.error("getMobileIdAssertion rejected with ${}", error);
1033 // If we got an invalid token error means that the credentials that
1034 // we have are not valid anymore and so we need to refresh them. We
1035 // do that removing the stored credentials and starting over. We also
1036 // make sure that we do this only once.
1037 if (error === ERROR_INVALID_AUTH_TOKEN &&
1038 !aOptions.refreshCredentials) {
1039 log.debug("Need to get new credentials");
1040 aOptions.refreshCredentials = true;
1041 _creds && this.credStore.delete(_creds.msisdn);
1042 this.getMobileIdAssertion(aPrincipal, aPromiseId, aOptions);
1046 // Notify the error to the UI.
1047 this.ui.error(error);
1049 this.error(aPromiseId, error);
1056 MobileIdentityManager.init();