mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-16 18:38:32 +03:00
The class Iccid uses a BcdAdapter to encoded/decode the ICCID. This works fine for ICCIDs that have an even (20) number of digits. In case the digit count is odd (19), the ICCID the last digit requires padding. Let's switch to PaddedBcdAdapter for encoding/decoding, to ensure that odd-length ICCIDs are padded automatically. Change-Id: I527a44ba454656a0d682ceb590eec6d9d0ac883a Related: OS#6868
635 lines
29 KiB
Python
635 lines
29 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Various definitions related to GSMA consumer + IoT eSIM / eUICC
|
|
|
|
Does *not* implement anything related to M2M eUICC
|
|
|
|
Related Specs: GSMA SGP.21, SGP.22, SGP.31, SGP32
|
|
"""
|
|
|
|
# Copyright (C) 2023 Harald Welte <laforge@osmocom.org>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import argparse
|
|
|
|
from construct import Array, Struct, FlagsEnum, GreedyRange
|
|
from cmd2 import cmd2, CommandSet, with_default_category
|
|
from osmocom.utils import Hexstr
|
|
from osmocom.tlv import *
|
|
from osmocom.construct import *
|
|
|
|
from pySim.exceptions import SwMatchError
|
|
from pySim.utils import Hexstr, SwHexstr, SwMatchstr
|
|
from pySim.commands import SimCardCommands
|
|
from pySim.ts_102_221 import CardProfileUICC
|
|
import pySim.global_platform
|
|
|
|
# SGP.02 Section 2.2.2
|
|
class Sgp02Eid(BER_TLV_IE, tag=0x5a):
|
|
_construct = BcdAdapter(GreedyBytes)
|
|
|
|
# patch this into global_platform, to allow 'get_data sgp02_eid' in EF.ECASD
|
|
pySim.global_platform.DataCollection.possible_nested.append(Sgp02Eid)
|
|
|
|
def compute_eid_checksum(eid) -> str:
|
|
"""Compute and add/replace check digits of an EID value according to GSMA SGP.29 Section 10."""
|
|
if isinstance(eid, str):
|
|
if len(eid) == 30:
|
|
# first pad by 2 digits
|
|
eid += "00"
|
|
elif len(eid) == 32:
|
|
# zero the last two digits
|
|
eid = eid[:-2] + "00"
|
|
else:
|
|
raise ValueError("and EID must be 30 or 32 digits")
|
|
eid_int = int(eid)
|
|
elif isinstance(eid, int):
|
|
eid_int = eid
|
|
if eid_int % 100:
|
|
# zero the last two digits
|
|
eid_int -= eid_int % 100
|
|
# Using the resulting 32 digits as a decimal integer, compute the remainder of that number on division by
|
|
# 97, Subtract the remainder from 98, and use the decimal result for the two check digits, if the result
|
|
# is one digit long, its value SHALL be prefixed by one digit of 0.
|
|
csum = 98 - (eid_int % 97)
|
|
eid_int += csum
|
|
return str(eid_int)
|
|
|
|
def verify_eid_checksum(eid) -> bool:
|
|
"""Verify the check digits of an EID value according to GSMA SGP.29 Section 10."""
|
|
# Using the 32 digits as a decimal integer, compute the remainder of that number on division by 97. If the
|
|
# remainder of the division is 1, the verification is successful; otherwise the EID is invalid.
|
|
return int(eid) % 97 == 1
|
|
|
|
class VersionAdapter(Adapter):
|
|
"""convert an EUICC Version (3-int array) to a textual representation."""
|
|
|
|
def _decode(self, obj, context, path):
|
|
return "%u.%u.%u" % (obj[0], obj[1], obj[2])
|
|
|
|
def _encode(self, obj, context, path):
|
|
return [int(x) for x in obj.split('.')]
|
|
|
|
VersionType = VersionAdapter(Array(3, Int8ub))
|
|
|
|
# Application Identifiers as defined in GSMA SGP.02 Annex H
|
|
AID_ISD_R = "A0000005591010FFFFFFFF8900000100"
|
|
AID_ECASD = "A0000005591010FFFFFFFF8900000200"
|
|
AID_ISD_P_FILE = "A0000005591010FFFFFFFF8900000D00"
|
|
AID_ISD_P_MODULE = "A0000005591010FFFFFFFF8900000E00"
|
|
|
|
class SupportedVersionNumber(BER_TLV_IE, tag=0x82):
|
|
_construct = GreedyBytes
|
|
|
|
class IsdrProprietaryApplicationTemplate(BER_TLV_IE, tag=0xe0, nested=[SupportedVersionNumber]):
|
|
# FIXME: lpaeSupport - what kind of tag would it have?
|
|
pass
|
|
|
|
# GlobalPlatform 2.1.1 Section 9.9.3.1 from pySim/global_platform.py extended with E0
|
|
class FciTemplate(BER_TLV_IE, tag=0x6f, nested=pySim.global_platform.FciTemplateNestedList +
|
|
[IsdrProprietaryApplicationTemplate]):
|
|
pass
|
|
|
|
|
|
# SGP.22 Section 5.7.3: GetEuiccConfiguredAddresses
|
|
class DefaultDpAddress(BER_TLV_IE, tag=0x80):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class RootDsAddress(BER_TLV_IE, tag=0x81):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class EuiccConfiguredAddresses(BER_TLV_IE, tag=0xbf3c, nested=[DefaultDpAddress, RootDsAddress]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.4: SetDefaultDpAddress
|
|
class SetDefaultDpAddrRes(BER_TLV_IE, tag=0x80):
|
|
_construct = Enum(Int8ub, ok=0, undefinedError=127)
|
|
class SetDefaultDpAddress(BER_TLV_IE, tag=0xbf3f, nested=[DefaultDpAddress, SetDefaultDpAddrRes]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.7: GetEUICCChallenge
|
|
class EuiccChallenge(BER_TLV_IE, tag=0x80):
|
|
_construct = Bytes(16)
|
|
class GetEuiccChallenge(BER_TLV_IE, tag=0xbf2e, nested=[EuiccChallenge]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.8: GetEUICCInfo
|
|
class SVN(BER_TLV_IE, tag=0x82):
|
|
_construct = VersionType
|
|
class SubjectKeyIdentifier(BER_TLV_IE, tag=0x04):
|
|
_construct = GreedyBytes
|
|
class EuiccCiPkiListForVerification(BER_TLV_IE, tag=0xa9, nested=[SubjectKeyIdentifier]):
|
|
pass
|
|
class EuiccCiPkiListForSigning(BER_TLV_IE, tag=0xaa, nested=[SubjectKeyIdentifier]):
|
|
pass
|
|
class EuiccInfo1(BER_TLV_IE, tag=0xbf20, nested=[SVN, EuiccCiPkiListForVerification, EuiccCiPkiListForSigning]):
|
|
pass
|
|
class ProfileVersion(BER_TLV_IE, tag=0x81):
|
|
_construct = VersionType
|
|
class EuiccFirmwareVer(BER_TLV_IE, tag=0x83):
|
|
_construct = VersionType
|
|
class ExtCardResource(BER_TLV_IE, tag=0x84):
|
|
_construct = GreedyBytes
|
|
class UiccCapability(BER_TLV_IE, tag=0x85):
|
|
_construct = GreedyBytes # FIXME
|
|
class TS102241Version(BER_TLV_IE, tag=0x86):
|
|
_construct = VersionType
|
|
class GlobalPlatformVersion(BER_TLV_IE, tag=0x87):
|
|
_construct = VersionType
|
|
class RspCapability(BER_TLV_IE, tag=0x88):
|
|
_construct = GreedyBytes # FIXME
|
|
class EuiccCategory(BER_TLV_IE, tag=0x8b):
|
|
_construct = Enum(Int8ub, other=0, basicEuicc=1, mediumEuicc=2, contactlessEuicc=3)
|
|
class PpVersion(BER_TLV_IE, tag=0x04):
|
|
_construct = VersionType
|
|
class SsAcreditationNumber(BER_TLV_IE, tag=0x0c):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class IpaMode(BER_TLV_IE, tag=0x90): # see SGP.32 v1.0
|
|
_construct = Enum(Int8ub, ipad=0, ipea=1)
|
|
class IotVersion(BER_TLV_IE, tag=0x80): # see SGP.32 v1.0
|
|
_construct = VersionType
|
|
class IotVersionSeq(BER_TLV_IE, tag=0xa0, nested=[IotVersion]): # see SGP.32 v1.0
|
|
pass
|
|
class IotSpecificInfo(BER_TLV_IE, tag=0x94, nested=[IotVersionSeq]): # see SGP.32 v1.0
|
|
pass
|
|
class EuiccInfo2(BER_TLV_IE, tag=0xbf22, nested=[ProfileVersion, SVN, EuiccFirmwareVer, ExtCardResource,
|
|
UiccCapability, TS102241Version, GlobalPlatformVersion,
|
|
RspCapability, EuiccCiPkiListForVerification,
|
|
EuiccCiPkiListForSigning, EuiccCategory, PpVersion,
|
|
SsAcreditationNumber, IpaMode, IotSpecificInfo]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.9: ListNotification
|
|
class ProfileMgmtOperation(BER_TLV_IE, tag=0x81):
|
|
# we have to ignore the first byte which tells us how many padding bits are used in the last octet
|
|
_construct = Struct(Byte, "pmo"/FlagsEnum(Byte, install=0x80, enable=0x40, disable=0x20, delete=0x10))
|
|
class ListNotificationReq(BER_TLV_IE, tag=0xbf28, nested=[ProfileMgmtOperation]):
|
|
pass
|
|
class SeqNumber(BER_TLV_IE, tag=0x80):
|
|
_construct = Asn1DerInteger()
|
|
class NotificationAddress(BER_TLV_IE, tag=0x0c):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class Iccid(BER_TLV_IE, tag=0x5a):
|
|
_construct = PaddedBcdAdapter(GreedyBytes)
|
|
class NotificationMetadata(BER_TLV_IE, tag=0xbf2f, nested=[SeqNumber, ProfileMgmtOperation,
|
|
NotificationAddress, Iccid]):
|
|
pass
|
|
class NotificationMetadataList(BER_TLV_IE, tag=0xa0, nested=[NotificationMetadata]):
|
|
pass
|
|
class ListNotificationsResultError(BER_TLV_IE, tag=0x81):
|
|
_construct = Enum(Int8ub, undefinedError=127)
|
|
class ListNotificationResp(BER_TLV_IE, tag=0xbf28, nested=[NotificationMetadataList,
|
|
ListNotificationsResultError]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.11: RemoveNotificationFromList
|
|
class DeleteNotificationStatus(BER_TLV_IE, tag=0x80):
|
|
_construct = Enum(Int8ub, ok=0, nothingToDelete=1, undefinedError=127)
|
|
class NotificationSentReq(BER_TLV_IE, tag=0xbf30, nested=[SeqNumber]):
|
|
pass
|
|
class NotificationSentResp(BER_TLV_IE, tag=0xbf30, nested=[DeleteNotificationStatus]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.12: LoadCRL: FIXME
|
|
class LoadCRL(BER_TLV_IE, tag=0xbf35, nested=[]): # FIXME
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.15: GetProfilesInfo
|
|
class TagList(BER_TLV_IE, tag=0x5c):
|
|
_construct = GreedyRange(Int8ub) # FIXME: tags could be multi-byte
|
|
class ProfileInfoListReq(BER_TLV_IE, tag=0xbf2d, nested=[TagList]): # FIXME: SearchCriteria
|
|
pass
|
|
class IsdpAid(BER_TLV_IE, tag=0x4f):
|
|
_construct = GreedyBytes
|
|
class ProfileState(BER_TLV_IE, tag=0x9f70):
|
|
_construct = Enum(Int8ub, disabled=0, enabled=1)
|
|
class ProfileNickname(BER_TLV_IE, tag=0x90):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class ServiceProviderName(BER_TLV_IE, tag=0x91):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class ProfileName(BER_TLV_IE, tag=0x92):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class IconType(BER_TLV_IE, tag=0x93):
|
|
_construct = Enum(Int8ub, jpg=0, png=1)
|
|
class Icon(BER_TLV_IE, tag=0x94):
|
|
_construct = GreedyBytes
|
|
class ProfileClass(BER_TLV_IE, tag=0x95):
|
|
_construct = Enum(Int8ub, test=0, provisioning=1, operational=2)
|
|
class ProfileInfo(BER_TLV_IE, tag=0xe3, nested=[Iccid, IsdpAid, ProfileState, ProfileNickname,
|
|
ServiceProviderName, ProfileName, IconType, Icon,
|
|
ProfileClass]): # FIXME: more IEs
|
|
pass
|
|
class ProfileInfoSeq(BER_TLV_IE, tag=0xa0, nested=[ProfileInfo]):
|
|
pass
|
|
class ProfileInfoListError(BER_TLV_IE, tag=0x81):
|
|
_construct = Enum(Int8ub, incorrectInputValues=1, undefinedError=2)
|
|
class ProfileInfoListResp(BER_TLV_IE, tag=0xbf2d, nested=[ProfileInfoSeq, ProfileInfoListError]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.16:: EnableProfile
|
|
class RefreshFlag(BER_TLV_IE, tag=0x81): # FIXME
|
|
_construct = Int8ub # FIXME
|
|
class EnableResult(BER_TLV_IE, tag=0x80):
|
|
_construct = Enum(Int8ub, ok=0, iccidOrAidNotFound=1, profileNotInDisabledState=2,
|
|
disallowedByPolicy=3, wrongProfileReenabling=4, catBusy=5, undefinedError=127)
|
|
class ProfileIdentifier(BER_TLV_IE, tag=0xa0, nested=[IsdpAid, Iccid]):
|
|
pass
|
|
class EnableProfileReq(BER_TLV_IE, tag=0xbf31, nested=[ProfileIdentifier, RefreshFlag]):
|
|
pass
|
|
class EnableProfileResp(BER_TLV_IE, tag=0xbf31, nested=[EnableResult]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.17 DisableProfile
|
|
class DisableResult(BER_TLV_IE, tag=0x80):
|
|
_construct = Enum(Int8ub, ok=0, iccidOrAidNotFound=1, profileNotInEnabledState=2,
|
|
disallowedByPolicy=3, catBusy=5, undefinedError=127)
|
|
class DisableProfileReq(BER_TLV_IE, tag=0xbf32, nested=[ProfileIdentifier, RefreshFlag]):
|
|
pass
|
|
class DisableProfileResp(BER_TLV_IE, tag=0xbf32, nested=[DisableResult]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.18: DeleteProfile
|
|
class DeleteResult(BER_TLV_IE, tag=0x80):
|
|
_construct = Enum(Int8ub, ok=0, iccidOrAidNotFound=1, profileNotInDisabledState=2,
|
|
disallowedByPolicy=3, undefinedError=127)
|
|
class DeleteProfileReq(BER_TLV_IE, tag=0xbf33, nested=[IsdpAid, Iccid]):
|
|
pass
|
|
class DeleteProfileResp(BER_TLV_IE, tag=0xbf33, nested=[DeleteResult]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.19: EuiccMemoryReset
|
|
class ResetOptions(BER_TLV_IE, tag=0x82):
|
|
_construct = FlagsEnum(Byte, deleteOperationalProfiles=0x80, deleteFieldLoadedTestProfiles=0x40,
|
|
resetDefaultSmdpAddress=0x20)
|
|
class ResetResult(BER_TLV_IE, tag=0x80):
|
|
_construct = Enum(Int8ub, ok=0, nothingToDelete=1, undefinedError=127)
|
|
class EuiccMemoryResetReq(BER_TLV_IE, tag=0xbf34, nested=[ResetOptions]):
|
|
pass
|
|
class EuiccMemoryResetResp(BER_TLV_IE, tag=0xbf34, nested=[ResetResult]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.20 GetEID
|
|
class EidValue(BER_TLV_IE, tag=0x5a):
|
|
_construct = GreedyBytes
|
|
class GetEuiccData(BER_TLV_IE, tag=0xbf3e, nested=[TagList, EidValue]):
|
|
pass
|
|
|
|
# SGP.22 Section 5.7.21: ES10c SetNickname
|
|
class SnrProfileNickname(BER_TLV_IE, tag=0x8f):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class SetNicknameReq(BER_TLV_IE, tag=0xbf29, nested=[Iccid, SnrProfileNickname]):
|
|
pass
|
|
class SetNicknameResult(BER_TLV_IE, tag=0x80):
|
|
_construct = Enum(Int8ub, ok=0, iccidNotFound=1, undefinedError=127)
|
|
class SetNicknameResp(BER_TLV_IE, tag=0xbf29, nested=[SetNicknameResult]):
|
|
pass
|
|
|
|
# SGP.32 Section 5.9.10: ES10b: GetCerts
|
|
class GetCertsReq(BER_TLV_IE, tag=0xbf56):
|
|
pass
|
|
class EumCertificate(BER_TLV_IE, tag=0xa5):
|
|
_construct = GreedyBytes
|
|
class EuiccCertificate(BER_TLV_IE, tag=0xa6):
|
|
_construct = GreedyBytes
|
|
class GetCertsError(BER_TLV_IE, tag=0x81):
|
|
_construct = Enum(Int8ub, invalidCiPKId=1, undefinedError=127)
|
|
class GetCertsResp(BER_TLV_IE, tag=0xbf56, nested=[EumCertificate, EuiccCertificate, GetCertsError]):
|
|
pass
|
|
|
|
# SGP.32 Section 5.9.18: ES10b: GetEimConfigurationData
|
|
class EimId(BER_TLV_IE, tag=0x80):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class EimFqdn(BER_TLV_IE, tag=0x81):
|
|
_construct = Utf8Adapter(GreedyBytes)
|
|
class EimIdType(BER_TLV_IE, tag=0x82):
|
|
_construct = Enum(Int8ub, eimIdTypeOid=1, eimIdTypeFqdn=2, eimIdTypeProprietary=3)
|
|
class CounterValue(BER_TLV_IE, tag=0x83):
|
|
_construct = Asn1DerInteger()
|
|
class AssociationToken(BER_TLV_IE, tag=0x84):
|
|
_construct = Asn1DerInteger()
|
|
class EimSupportedProtocol(BER_TLV_IE, tag=0x87):
|
|
_construct = Enum(Int8ub, eimRetrieveHttps=0, eimRetrieveCoaps=1, eimInjectHttps=2, eimInjectCoaps=3,
|
|
eimProprietary=4)
|
|
# FIXME: eimPublicKeyData, trustedPublicKeyDataTls, euiccCiPKId
|
|
class EimConfigurationData(BER_TLV_IE, tag=0x80, nested=[EimId, EimFqdn, EimIdType, CounterValue,
|
|
AssociationToken, EimSupportedProtocol]):
|
|
pass
|
|
class EimConfigurationDataSeq(BER_TLV_IE, tag=0xa0, nested=[EimConfigurationData]):
|
|
pass
|
|
class GetEimConfigurationData(BER_TLV_IE, tag=0xbf55, nested=[EimConfigurationDataSeq]):
|
|
pass
|
|
|
|
class CardApplicationISDR(pySim.global_platform.CardApplicationSD):
|
|
def __init__(self):
|
|
super().__init__(name='ADF.ISD-R', aid=AID_ISD_R,
|
|
desc='ISD-R (Issuer Security Domain Root) Application')
|
|
self.adf.decode_select_response = self.decode_select_response
|
|
self.adf.shell_commands += [self.AddlShellCommands()]
|
|
# we attempt to retrieve ISD-R key material from CardKeyProvider identified by EID
|
|
self.adf.scp_key_identity = 'EID'
|
|
|
|
@staticmethod
|
|
def store_data(scc: SimCardCommands, tx_do: Hexstr, exp_sw: SwMatchstr ="9000") -> Tuple[Hexstr, SwHexstr]:
|
|
"""Perform STORE DATA according to Table 47+48 in Section 5.7.2 of SGP.22.
|
|
Only single-block store supported for now."""
|
|
capdu = '80E29100%02x%s00' % (len(tx_do)//2, tx_do)
|
|
return scc.send_apdu_checksw(capdu, exp_sw)
|
|
|
|
@staticmethod
|
|
def store_data_tlv(scc: SimCardCommands, cmd_do, resp_cls, exp_sw: SwMatchstr = '9000'):
|
|
"""Transceive STORE DATA APDU with the card, transparently encoding the command data from TLV
|
|
and decoding the response data tlv."""
|
|
if cmd_do:
|
|
cmd_do_enc = cmd_do.to_tlv()
|
|
cmd_do_len = len(cmd_do_enc)
|
|
if cmd_do_len > 255:
|
|
return ValueError('DO > 255 bytes not supported yet')
|
|
else:
|
|
cmd_do_enc = b''
|
|
(data, _sw) = CardApplicationISDR.store_data(scc, b2h(cmd_do_enc), exp_sw=exp_sw)
|
|
if data:
|
|
if resp_cls:
|
|
resp_do = resp_cls()
|
|
resp_do.from_tlv(h2b(data))
|
|
return resp_do
|
|
else:
|
|
return data
|
|
else:
|
|
return None
|
|
|
|
@staticmethod
|
|
def get_eid(scc: SimCardCommands) -> str:
|
|
ged_cmd = GetEuiccData(children=[TagList(decoded=[0x5A])])
|
|
ged = CardApplicationISDR.store_data_tlv(scc, ged_cmd, GetEuiccData)
|
|
d = ged.to_dict()
|
|
return b2h(flatten_dict_lists(d['get_euicc_data'])['eid_value'])
|
|
|
|
def decode_select_response(self, data_hex: Hexstr) -> object:
|
|
t = FciTemplate()
|
|
t.from_tlv(h2b(data_hex))
|
|
d = t.to_dict()
|
|
return flatten_dict_lists(d['fci_template'])
|
|
|
|
@with_default_category('Application-Specific Commands')
|
|
class AddlShellCommands(CommandSet):
|
|
|
|
es10x_store_data_parser = argparse.ArgumentParser()
|
|
es10x_store_data_parser.add_argument('TX_DO', help='Hexstring of encoded to-be-transmitted DO')
|
|
|
|
@cmd2.with_argparser(es10x_store_data_parser)
|
|
def do_es10x_store_data(self, opts):
|
|
"""Perform a raw STORE DATA command as defined for the ES10x eUICC interface."""
|
|
(_data, _sw) = CardApplicationISDR.store_data(self._cmd.lchan.scc, opts.TX_DO)
|
|
|
|
def do_get_euicc_configured_addresses(self, _opts):
|
|
"""Perform an ES10a GetEuiccConfiguredAddresses function."""
|
|
eca = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, EuiccConfiguredAddresses(), EuiccConfiguredAddresses)
|
|
d = eca.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['euicc_configured_addresses']))
|
|
|
|
set_def_dp_addr_parser = argparse.ArgumentParser()
|
|
set_def_dp_addr_parser.add_argument('DP_ADDRESS', help='Default SM-DP+ address as UTF-8 string')
|
|
|
|
@cmd2.with_argparser(set_def_dp_addr_parser)
|
|
def do_set_default_dp_address(self, opts):
|
|
"""Perform an ES10a SetDefaultDpAddress function."""
|
|
sdda_cmd = SetDefaultDpAddress(children=[DefaultDpAddress(decoded=opts.DP_ADDRESS)])
|
|
sdda = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, sdda_cmd, SetDefaultDpAddress)
|
|
d = sdda.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['set_default_dp_address']))
|
|
|
|
def do_get_euicc_challenge(self, _opts):
|
|
"""Perform an ES10b GetEUICCChallenge function."""
|
|
gec = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, GetEuiccChallenge(), GetEuiccChallenge)
|
|
d = gec.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['get_euicc_challenge']))
|
|
|
|
def do_get_euicc_info1(self, _opts):
|
|
"""Perform an ES10b GetEUICCInfo (1) function."""
|
|
ei1 = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, EuiccInfo1(), EuiccInfo1)
|
|
d = ei1.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['euicc_info1']))
|
|
|
|
def do_get_euicc_info2(self, _opts):
|
|
"""Perform an ES10b GetEUICCInfo (2) function."""
|
|
ei2 = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, EuiccInfo2(), EuiccInfo2)
|
|
d = ei2.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['euicc_info2']))
|
|
|
|
def do_list_notification(self, _opts):
|
|
"""Perform an ES10b ListNotification function."""
|
|
ln = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, ListNotificationReq(), ListNotificationResp)
|
|
d = ln.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['list_notification_resp']))
|
|
|
|
rem_notif_parser = argparse.ArgumentParser()
|
|
rem_notif_parser.add_argument('SEQ_NR', type=int, help='Sequence Number of the to-be-removed notification')
|
|
|
|
@cmd2.with_argparser(rem_notif_parser)
|
|
def do_remove_notification_from_list(self, opts):
|
|
"""Perform an ES10b RemoveNotificationFromList function."""
|
|
rn_cmd = NotificationSentReq(children=[SeqNumber(decoded=opts.SEQ_NR)])
|
|
rn = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, rn_cmd, NotificationSentResp)
|
|
d = rn.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['notification_sent_resp']))
|
|
|
|
def do_get_profiles_info(self, _opts):
|
|
"""Perform an ES10c GetProfilesInfo function."""
|
|
pi = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, ProfileInfoListReq(), ProfileInfoListResp)
|
|
d = pi.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['profile_info_list_resp']))
|
|
|
|
en_prof_parser = argparse.ArgumentParser()
|
|
en_prof_grp = en_prof_parser.add_mutually_exclusive_group()
|
|
en_prof_grp.add_argument('--isdp-aid', help='Profile identified by its ISD-P AID')
|
|
en_prof_grp.add_argument('--iccid', help='Profile identified by its ICCID')
|
|
en_prof_parser.add_argument('--refresh-required', action='store_true', help='whether a REFRESH is required')
|
|
|
|
@cmd2.with_argparser(en_prof_parser)
|
|
def do_enable_profile(self, opts):
|
|
"""Perform an ES10c EnableProfile function."""
|
|
if opts.isdp_aid:
|
|
p_id = ProfileIdentifier(children=[IsdpAid(decoded=opts.isdp_aid)])
|
|
elif opts.iccid:
|
|
p_id = ProfileIdentifier(children=[Iccid(decoded=opts.iccid)])
|
|
else:
|
|
# this is guaranteed by argparse; but we need this to make pylint happy
|
|
raise ValueError('Either ISD-P AID or ICCID must be given')
|
|
ep_cmd_contents = [p_id, RefreshFlag(decoded=opts.refresh_required)]
|
|
ep_cmd = EnableProfileReq(children=ep_cmd_contents)
|
|
ep = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, ep_cmd, EnableProfileResp)
|
|
d = ep.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['enable_profile_resp']))
|
|
|
|
dis_prof_parser = argparse.ArgumentParser()
|
|
dis_prof_grp = dis_prof_parser.add_mutually_exclusive_group()
|
|
dis_prof_grp.add_argument('--isdp-aid', help='Profile identified by its ISD-P AID')
|
|
dis_prof_grp.add_argument('--iccid', help='Profile identified by its ICCID')
|
|
dis_prof_parser.add_argument('--refresh-required', action='store_true', help='whether a REFRESH is required')
|
|
|
|
@cmd2.with_argparser(dis_prof_parser)
|
|
def do_disable_profile(self, opts):
|
|
"""Perform an ES10c DisableProfile function."""
|
|
if opts.isdp_aid:
|
|
p_id = ProfileIdentifier(children=[IsdpAid(decoded=opts.isdp_aid)])
|
|
elif opts.iccid:
|
|
p_id = ProfileIdentifier(children=[Iccid(decoded=opts.iccid)])
|
|
else:
|
|
# this is guaranteed by argparse; but we need this to make pylint happy
|
|
raise ValueError('Either ISD-P AID or ICCID must be given')
|
|
dp_cmd_contents = [p_id, RefreshFlag(decoded=opts.refresh_required)]
|
|
dp_cmd = DisableProfileReq(children=dp_cmd_contents)
|
|
dp = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, dp_cmd, DisableProfileResp)
|
|
d = dp.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['disable_profile_resp']))
|
|
|
|
del_prof_parser = argparse.ArgumentParser()
|
|
del_prof_grp = del_prof_parser.add_mutually_exclusive_group()
|
|
del_prof_grp.add_argument('--isdp-aid', help='Profile identified by its ISD-P AID')
|
|
del_prof_grp.add_argument('--iccid', help='Profile identified by its ICCID')
|
|
|
|
@cmd2.with_argparser(del_prof_parser)
|
|
def do_delete_profile(self, opts):
|
|
"""Perform an ES10c DeleteProfile function."""
|
|
if opts.isdp_aid:
|
|
p_id = IsdpAid(decoded=opts.isdp_aid)
|
|
elif opts.iccid:
|
|
p_id = Iccid(decoded=opts.iccid)
|
|
else:
|
|
# this is guaranteed by argparse; but we need this to make pylint happy
|
|
raise ValueError('Either ISD-P AID or ICCID must be given')
|
|
dp_cmd_contents = [p_id]
|
|
dp_cmd = DeleteProfileReq(children=dp_cmd_contents)
|
|
dp = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, dp_cmd, DeleteProfileResp)
|
|
d = dp.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['delete_profile_resp']))
|
|
|
|
mem_res_parser = argparse.ArgumentParser()
|
|
mem_res_parser.add_argument('--delete-operational', action='store_true',
|
|
help='Delete all operational profiles')
|
|
mem_res_parser.add_argument('--delete-test-field-installed', action='store_true',
|
|
help='Delete all test profiles, except pre-installed ones')
|
|
mem_res_parser.add_argument('--reset-smdp-address', action='store_true',
|
|
help='Reset the SM-DP+ address')
|
|
|
|
@cmd2.with_argparser(mem_res_parser)
|
|
def do_euicc_memory_reset(self, opts):
|
|
"""Perform an ES10c eUICCMemoryReset function. This will permanently delete the selected subset of
|
|
profiles from the eUICC."""
|
|
flags = {}
|
|
if opts.delete_operational:
|
|
flags['deleteOperationalProfiles'] = True
|
|
if opts.delete_test_field_installed:
|
|
flags['deleteFieldLoadedTestProfiles'] = True
|
|
if opts.reset_smdp_address:
|
|
flags['resetDefaultSmdpAddress'] = True
|
|
|
|
mr_cmd = EuiccMemoryResetReq(children=[ResetOptions(decoded=flags)])
|
|
mr = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, mr_cmd, EuiccMemoryResetResp)
|
|
d = mr.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['euicc_memory_reset_resp']))
|
|
|
|
def do_get_eid(self, _opts):
|
|
"""Perform an ES10c GetEID function."""
|
|
ged_cmd = GetEuiccData(children=[TagList(decoded=[0x5A])])
|
|
ged = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, ged_cmd, GetEuiccData)
|
|
d = ged.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['get_euicc_data']))
|
|
|
|
set_nickname_parser = argparse.ArgumentParser()
|
|
set_nickname_parser.add_argument('--profile-nickname', help='Nickname of the profile')
|
|
set_nickname_parser.add_argument('ICCID', help='ICCID of the profile whose nickname to set')
|
|
|
|
@cmd2.with_argparser(set_nickname_parser)
|
|
def do_set_nickname(self, opts):
|
|
"""Perform an ES10c SetNickname function."""
|
|
nickname = opts.profile_nickname or ''
|
|
sn_cmd_contents = [Iccid(decoded=opts.ICCID), ProfileNickname(decoded=nickname)]
|
|
sn_cmd = SetNicknameReq(children=sn_cmd_contents)
|
|
sn = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, sn_cmd, SetNicknameResp)
|
|
d = sn.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['set_nickname_resp']))
|
|
|
|
def do_get_certs(self, _opts):
|
|
"""Perform an ES10c GetCerts() function on an IoT eUICC."""
|
|
gc = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, GetCertsReq(), GetCertsResp)
|
|
d = gc.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['get_certs_resp']))
|
|
|
|
def do_get_eim_configuration_data(self, _opts):
|
|
"""Perform an ES10b GetEimConfigurationData function on an Iot eUICC."""
|
|
gec = CardApplicationISDR.store_data_tlv(self._cmd.lchan.scc, GetEimConfigurationData(),
|
|
GetEimConfigurationData)
|
|
d = gec.to_dict()
|
|
self._cmd.poutput_json(flatten_dict_lists(d['get_eim_configuration_data']))
|
|
|
|
class CardApplicationECASD(pySim.global_platform.CardApplicationSD):
|
|
def decode_select_response(self, data_hex: Hexstr) -> object:
|
|
t = FciTemplate()
|
|
t.from_tlv(h2b(data_hex))
|
|
d = t.to_dict()
|
|
return flatten_dict_lists(d['fci_template'])
|
|
|
|
def __init__(self):
|
|
super().__init__(name='ADF.ECASD', aid=AID_ECASD,
|
|
desc='ECASD (eUICC Controlling Authority Security Domain) Application')
|
|
self.adf.decode_select_response = self.decode_select_response
|
|
self.adf.shell_commands += [self.AddlShellCommands()]
|
|
# we attempt to retrieve ECASD key material from CardKeyProvider identified by EID
|
|
self.adf.scp_key_identity = 'EID'
|
|
|
|
@with_default_category('Application-Specific Commands')
|
|
class AddlShellCommands(CommandSet):
|
|
pass
|
|
|
|
class CardProfileEuiccSGP32(CardProfileUICC):
|
|
ORDER = 5
|
|
|
|
def __init__(self):
|
|
super().__init__(name='IoT eUICC (SGP.32)')
|
|
|
|
@classmethod
|
|
def _try_match_card(cls, scc: SimCardCommands) -> None:
|
|
# try a command only supported by SGP.32
|
|
scc.cla_byte = "00"
|
|
scc.select_adf(AID_ISD_R)
|
|
CardApplicationISDR.store_data_tlv(scc, GetCertsReq(), GetCertsResp)
|
|
|
|
class CardProfileEuiccSGP22(CardProfileUICC):
|
|
ORDER = 6
|
|
|
|
def __init__(self):
|
|
super().__init__(name='Consumer eUICC (SGP.22)')
|
|
|
|
@classmethod
|
|
def _try_match_card(cls, scc: SimCardCommands) -> None:
|
|
# try to read EID from ISD-R
|
|
scc.cla_byte = "00"
|
|
scc.select_adf(AID_ISD_R)
|
|
eid = CardApplicationISDR.get_eid(scc)
|
|
# TODO: Store EID identity?
|
|
|
|
class CardProfileEuiccSGP02(CardProfileUICC):
|
|
ORDER = 7
|
|
|
|
def __init__(self):
|
|
super().__init__(name='M2M eUICC (SGP.02)')
|
|
|
|
@classmethod
|
|
def _try_match_card(cls, scc: SimCardCommands) -> None:
|
|
scc.cla_byte = "00"
|
|
scc.select_adf(AID_ECASD)
|
|
scc.get_data(0x5a)
|
|
# TODO: Store EID identity?
|