mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-23 22:08:34 +03:00
Compare commits
21 Commits
pmaier/fix
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
473f31066c | ||
|
|
b59363b49e | ||
|
|
115b517c6a | ||
|
|
99aef1fecf | ||
|
|
caddd1c7a0 | ||
|
|
11a7a7e3b1 | ||
|
|
5138208ee6 | ||
|
|
5b2fabde62 | ||
|
|
24127e985a | ||
|
|
09ae327f8b | ||
|
|
d32bce19f6 | ||
|
|
83bfdc0d3b | ||
|
|
14ec52a06c | ||
|
|
209d13e233 | ||
|
|
3b50e64c8b | ||
|
|
b76cc80ea1 | ||
|
|
3b87ba3cba | ||
|
|
ea1d5af383 | ||
|
|
0634f77308 | ||
|
|
a5a5865c7c | ||
|
|
3752aeb94e |
@@ -285,7 +285,10 @@ if __name__ == '__main__':
|
|||||||
option_parser.add_argument("--admin", action='store_true', help="perform action as admin", default=False)
|
option_parser.add_argument("--admin", action='store_true', help="perform action as admin", default=False)
|
||||||
opts = option_parser.parse_args()
|
opts = option_parser.parse_args()
|
||||||
|
|
||||||
PySimLogger.setup(print, {logging.WARN: "\033[33m"}, opts.verbose)
|
PySimLogger.setup(print, {logging.WARN: "\033[33m"})
|
||||||
|
if (opts.verbose):
|
||||||
|
PySimLogger.set_verbose(True)
|
||||||
|
PySimLogger.set_level(logging.DEBUG)
|
||||||
|
|
||||||
# Open CSV file
|
# Open CSV file
|
||||||
cr = open_csv(opts)
|
cr = open_csv(opts)
|
||||||
|
|||||||
@@ -44,11 +44,6 @@ from pySim.legacy.ts_51_011 import EF
|
|||||||
from pySim.card_handler import *
|
from pySim.card_handler import *
|
||||||
from pySim.utils import *
|
from pySim.utils import *
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import logging
|
|
||||||
from pySim.log import PySimLogger
|
|
||||||
|
|
||||||
log = PySimLogger.get(Path(__file__).stem)
|
|
||||||
|
|
||||||
def parse_options():
|
def parse_options():
|
||||||
|
|
||||||
@@ -190,7 +185,6 @@ def parse_options():
|
|||||||
default=False, action="store_true")
|
default=False, action="store_true")
|
||||||
parser.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
|
parser.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
|
||||||
help="Use automatic card handling machine")
|
help="Use automatic card handling machine")
|
||||||
parser.add_argument("--verbose", help="Enable verbose logging", action='store_true', default=False)
|
|
||||||
|
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
|
||||||
@@ -776,9 +770,6 @@ if __name__ == '__main__':
|
|||||||
# Parse options
|
# Parse options
|
||||||
opts = parse_options()
|
opts = parse_options()
|
||||||
|
|
||||||
# Setup logger
|
|
||||||
PySimLogger.setup(print, {logging.WARN: "\033[33m"}, opts.verbose)
|
|
||||||
|
|
||||||
# Init card reader driver
|
# Init card reader driver
|
||||||
sl = init_reader(opts)
|
sl = init_reader(opts)
|
||||||
|
|
||||||
|
|||||||
@@ -46,17 +46,11 @@ from pySim.utils import dec_imsi, dec_iccid
|
|||||||
from pySim.legacy.utils import format_xplmn_w_act, dec_st, dec_msisdn
|
from pySim.legacy.utils import format_xplmn_w_act, dec_st, dec_msisdn
|
||||||
from pySim.ts_51_011 import EF_SMSP
|
from pySim.ts_51_011 import EF_SMSP
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import logging
|
|
||||||
from pySim.log import PySimLogger
|
|
||||||
|
|
||||||
log = PySimLogger.get(Path(__file__).stem)
|
|
||||||
|
|
||||||
option_parser = argparse.ArgumentParser(description='Legacy tool for reading some parts of a SIM card',
|
option_parser = argparse.ArgumentParser(description='Legacy tool for reading some parts of a SIM card',
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||||
option_parser.add_argument("--verbose", help="Enable verbose logging", action='store_true', default=False)
|
|
||||||
argparse_add_reader_args(option_parser)
|
argparse_add_reader_args(option_parser)
|
||||||
|
|
||||||
|
|
||||||
def select_app(adf: str, card: SimCard):
|
def select_app(adf: str, card: SimCard):
|
||||||
"""Select application by its AID"""
|
"""Select application by its AID"""
|
||||||
sw = 0
|
sw = 0
|
||||||
@@ -81,9 +75,6 @@ if __name__ == '__main__':
|
|||||||
# Parse options
|
# Parse options
|
||||||
opts = option_parser.parse_args()
|
opts = option_parser.parse_args()
|
||||||
|
|
||||||
# Setup logger
|
|
||||||
PySimLogger.setup(print, {logging.WARN: "\033[33m"}, opts.verbose)
|
|
||||||
|
|
||||||
# Init card reader driver
|
# Init card reader driver
|
||||||
sl = init_reader(opts)
|
sl = init_reader(opts)
|
||||||
|
|
||||||
|
|||||||
@@ -107,12 +107,12 @@ Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/
|
|||||||
kwargs = {'include_ipy': True}
|
kwargs = {'include_ipy': True}
|
||||||
|
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
PySimLogger.setup(self.poutput, {logging.WARN: YELLOW})
|
self._onchange_verbose('verbose', False, self.verbose);
|
||||||
self._onchange_verbose('verbose', False, self.verbose)
|
|
||||||
|
|
||||||
# pylint: disable=unexpected-keyword-arg
|
# pylint: disable=unexpected-keyword-arg
|
||||||
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
|
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
|
||||||
auto_load_commands=False, startup_script=script, **kwargs)
|
auto_load_commands=False, startup_script=script, **kwargs)
|
||||||
|
PySimLogger.setup(self.poutput, {logging.WARN: YELLOW})
|
||||||
self.intro = style(self.BANNER, fg=RED)
|
self.intro = style(self.BANNER, fg=RED)
|
||||||
self.default_category = 'pySim-shell built-in commands'
|
self.default_category = 'pySim-shell built-in commands'
|
||||||
self.card = None
|
self.card = None
|
||||||
@@ -1175,7 +1175,13 @@ if __name__ == '__main__':
|
|||||||
opts = option_parser.parse_args()
|
opts = option_parser.parse_args()
|
||||||
|
|
||||||
# Ensure that we are able to print formatted warnings from the beginning.
|
# Ensure that we are able to print formatted warnings from the beginning.
|
||||||
PySimLogger.setup(print, {logging.WARN: YELLOW}, opts.verbose)
|
PySimLogger.setup(print, {logging.WARN: YELLOW})
|
||||||
|
if opts.verbose:
|
||||||
|
PySimLogger.set_verbose(True)
|
||||||
|
PySimLogger.set_level(logging.DEBUG)
|
||||||
|
else:
|
||||||
|
PySimLogger.set_verbose(False)
|
||||||
|
PySimLogger.set_level(logging.INFO)
|
||||||
|
|
||||||
# Register csv-file as card data provider, either from specified CSV
|
# Register csv-file as card data provider, either from specified CSV
|
||||||
# or from CSV file in home directory
|
# or from CSV file in home directory
|
||||||
|
|||||||
@@ -72,10 +72,10 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
|
|||||||
if do[0] == 0x01:
|
if do[0] == 0x01:
|
||||||
self.decoded = {'generic_access_rule': 'always'}
|
self.decoded = {'generic_access_rule': 'always'}
|
||||||
return self.decoded
|
return self.decoded
|
||||||
return ValueError('Invalid 1-byte generic APDU access rule')
|
raise ValueError('Invalid 1-byte generic APDU access rule')
|
||||||
else:
|
else:
|
||||||
if len(do) % 8:
|
if len(do) % 8:
|
||||||
return ValueError('Invalid non-modulo-8 length of APDU filter: %d' % len(do))
|
raise ValueError('Invalid non-modulo-8 length of APDU filter: %d' % len(do))
|
||||||
self.decoded = {'apdu_filter': []}
|
self.decoded = {'apdu_filter': []}
|
||||||
offset = 0
|
offset = 0
|
||||||
while offset < len(do):
|
while offset < len(do):
|
||||||
@@ -90,19 +90,19 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
|
|||||||
return b'\x00'
|
return b'\x00'
|
||||||
if self.decoded['generic_access_rule'] == 'always':
|
if self.decoded['generic_access_rule'] == 'always':
|
||||||
return b'\x01'
|
return b'\x01'
|
||||||
return ValueError('Invalid 1-byte generic APDU access rule')
|
raise ValueError('Invalid 1-byte generic APDU access rule')
|
||||||
else:
|
else:
|
||||||
if not 'apdu_filter' in self.decoded:
|
if not 'apdu_filter' in self.decoded:
|
||||||
return ValueError('Invalid APDU AR DO')
|
raise ValueError('Invalid APDU AR DO')
|
||||||
filters = self.decoded['apdu_filter']
|
filters = self.decoded['apdu_filter']
|
||||||
res = b''
|
res = b''
|
||||||
for f in filters:
|
for f in filters:
|
||||||
if not 'header' in f or not 'mask' in f:
|
if not 'header' in f or not 'mask' in f:
|
||||||
return ValueError('APDU filter must contain header and mask')
|
raise ValueError('APDU filter must contain header and mask')
|
||||||
header_b = h2b(f['header'])
|
header_b = h2b(f['header'])
|
||||||
mask_b = h2b(f['mask'])
|
mask_b = h2b(f['mask'])
|
||||||
if len(header_b) != 4 or len(mask_b) != 4:
|
if len(header_b) != 4 or len(mask_b) != 4:
|
||||||
return ValueError('APDU filter header and mask must each be 4 bytes')
|
raise ValueError('APDU filter header and mask must each be 4 bytes')
|
||||||
res += header_b + mask_b
|
res += header_b + mask_b
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ class ADF_ARAM(CardADF):
|
|||||||
cmd_do_enc = cmd_do.to_ie()
|
cmd_do_enc = cmd_do.to_ie()
|
||||||
cmd_do_len = len(cmd_do_enc)
|
cmd_do_len = len(cmd_do_enc)
|
||||||
if cmd_do_len > 255:
|
if cmd_do_len > 255:
|
||||||
return ValueError('DO > 255 bytes not supported yet')
|
raise ValueError('DO > 255 bytes not supported yet')
|
||||||
else:
|
else:
|
||||||
cmd_do_enc = b''
|
cmd_do_enc = b''
|
||||||
cmd_do_len = 0
|
cmd_do_len = 0
|
||||||
@@ -361,7 +361,7 @@ class ADF_ARAM(CardADF):
|
|||||||
ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'always'}}]
|
ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'always'}}]
|
||||||
elif opts.apdu_filter:
|
elif opts.apdu_filter:
|
||||||
if len(opts.apdu_filter) % 16:
|
if len(opts.apdu_filter) % 16:
|
||||||
return ValueError('Invalid non-modulo-16 length of APDU filter: %d' % len(do))
|
raise ValueError(f'Invalid non-modulo-16 length of APDU filter: {len(opts.apdu_filter)}')
|
||||||
offset = 0
|
offset = 0
|
||||||
apdu_filter = []
|
apdu_filter = []
|
||||||
while offset < len(opts.apdu_filter):
|
while offset < len(opts.apdu_filter):
|
||||||
|
|||||||
@@ -128,10 +128,10 @@ class EF_AD(TransparentEF):
|
|||||||
cell_test = 0x04
|
cell_test = 0x04
|
||||||
|
|
||||||
def __init__(self, fid='6f43', sfid=None, name='EF.AD',
|
def __init__(self, fid='6f43', sfid=None, name='EF.AD',
|
||||||
desc='Service Provider Name', size=(3, None), **kwargs):
|
desc='Administrative Data', size=(3, None), **kwargs):
|
||||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
|
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
|
||||||
self._construct = Struct(
|
self._construct = Struct(
|
||||||
# Byte 1: Display Condition
|
# Byte 1: MS operation mode
|
||||||
'ms_operation_mode'/Enum(Byte, self.OP_MODE),
|
'ms_operation_mode'/Enum(Byte, self.OP_MODE),
|
||||||
# Bytes 2-3: Additional information
|
# Bytes 2-3: Additional information
|
||||||
'additional_info'/Bytes(2),
|
'additional_info'/Bytes(2),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import abc
|
|||||||
import requests
|
import requests
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from typing import Optional
|
from typing import Optional, Tuple
|
||||||
import base64
|
import base64
|
||||||
from twisted.web.server import Request
|
from twisted.web.server import Request
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ class JsonHttpApiFunction(abc.ABC):
|
|||||||
# receives from the a requesting client. The same applies vice versa to class variables that have an "output_"
|
# receives from the a requesting client. The same applies vice versa to class variables that have an "output_"
|
||||||
# prefix.
|
# prefix.
|
||||||
|
|
||||||
# path of the API function (e.g. '/gsma/rsp2/es2plus/confirmOrder')
|
# path of the API function (e.g. '/gsma/rsp2/es2plus/confirmOrder', see also method rewrite_url).
|
||||||
path = None
|
path = None
|
||||||
|
|
||||||
# dictionary of input parameters. key is parameter name, value is ApiParam class
|
# dictionary of input parameters. key is parameter name, value is ApiParam class
|
||||||
@@ -336,6 +336,22 @@ class JsonHttpApiFunction(abc.ABC):
|
|||||||
output[p] = p_class.decode(v)
|
output[p] = p_class.decode(v)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
def rewrite_url(self, data: dict, url: str) -> Tuple[dict, str]:
|
||||||
|
"""
|
||||||
|
Rewrite a static URL using information passed in the data dict. This method may be overloaded by a derived
|
||||||
|
class to allow fully dynamic URLs. The input parameters required for the URL rewriting may be passed using
|
||||||
|
data parameter. In case those parameters are additional parameters that are not intended to be passed to
|
||||||
|
the encode_client method later, they must be removed explcitly.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: (see JsonHttpApiClient and JsonHttpApiServer)
|
||||||
|
url: statically generated URL string (see comment in JsonHttpApiClient)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This implementation is a placeholder in which we do not perform any URL rewriting. We just pass through data
|
||||||
|
# and url unmodified.
|
||||||
|
return data, url
|
||||||
|
|
||||||
class JsonHttpApiClient():
|
class JsonHttpApiClient():
|
||||||
def __init__(self, api_func: JsonHttpApiFunction, url_prefix: str, func_req_id: Optional[str],
|
def __init__(self, api_func: JsonHttpApiFunction, url_prefix: str, func_req_id: Optional[str],
|
||||||
session: requests.Session):
|
session: requests.Session):
|
||||||
@@ -352,8 +368,16 @@ class JsonHttpApiClient():
|
|||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
def call(self, data: dict, func_call_id: Optional[str] = None, timeout=10) -> Optional[dict]:
|
def call(self, data: dict, func_call_id: Optional[str] = None, timeout=10) -> Optional[dict]:
|
||||||
"""Make an API call to the HTTP API endpoint represented by this object. Input data is passed in `data` as
|
"""
|
||||||
json-serializable dict. Output data is returned as json-deserialized dict."""
|
Make an API call to the HTTP API endpoint represented by this object. Input data is passed in `data` as
|
||||||
|
json-serializable fields. `data` may also contain additional parameters required for URL rewriting (see
|
||||||
|
rewrite_url in class JsonHttpApiFunction). Output data is returned as json-deserialized dict.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Input data required to perform the request.
|
||||||
|
func_call_id: Function Call Identifier, if present a header field is generated automatically.
|
||||||
|
timeout: Maximum amount of time to wait for the request to complete.
|
||||||
|
"""
|
||||||
|
|
||||||
# In case a function caller ID is supplied, use it together with the stored function requestor ID to generate
|
# In case a function caller ID is supplied, use it together with the stored function requestor ID to generate
|
||||||
# and prepend the header field according to SGP.22, section 6.5.1.1 and 6.5.1.3. (the presence of the header
|
# and prepend the header field according to SGP.22, section 6.5.1.1 and 6.5.1.3. (the presence of the header
|
||||||
@@ -362,6 +386,11 @@ class JsonHttpApiClient():
|
|||||||
data = {'header' : {'functionRequesterIdentifier': self.func_req_id,
|
data = {'header' : {'functionRequesterIdentifier': self.func_req_id,
|
||||||
'functionCallIdentifier': func_call_id}} | data
|
'functionCallIdentifier': func_call_id}} | data
|
||||||
|
|
||||||
|
# The URL used for the HTTP request (see below) normally consists of the initially given url_prefix
|
||||||
|
# concatenated with the path defined by the JsonHttpApiFunction definition. This static URL path may be
|
||||||
|
# rewritten by rewrite_url method defined in the JsonHttpApiFunction.
|
||||||
|
data, url = self.api_func.rewrite_url(data, self.url_prefix + self.api_func.path)
|
||||||
|
|
||||||
# Encode the message (the presence of mandatory fields is checked during encoding)
|
# Encode the message (the presence of mandatory fields is checked during encoding)
|
||||||
encoded = json.dumps(self.api_func.encode_client(data))
|
encoded = json.dumps(self.api_func.encode_client(data))
|
||||||
|
|
||||||
@@ -373,7 +402,6 @@ class JsonHttpApiClient():
|
|||||||
req_headers.update(self.api_func.extra_http_req_headers)
|
req_headers.update(self.api_func.extra_http_req_headers)
|
||||||
|
|
||||||
# Perform HTTP request
|
# Perform HTTP request
|
||||||
url = self.url_prefix + self.api_func.path
|
|
||||||
logger.debug("HTTP REQ %s - hdr: %s '%s'" % (url, req_headers, encoded))
|
logger.debug("HTTP REQ %s - hdr: %s '%s'" % (url, req_headers, encoded))
|
||||||
response = self.session.request(self.api_func.http_method, url, data=encoded, headers=req_headers, timeout=timeout)
|
response = self.session.request(self.api_func.http_method, url, data=encoded, headers=req_headers, timeout=timeout)
|
||||||
logger.debug("HTTP RSP-STS: [%u] hdr: %s" % (response.status_code, response.headers))
|
logger.debug("HTTP RSP-STS: [%u] hdr: %s" % (response.status_code, response.headers))
|
||||||
|
|||||||
@@ -151,6 +151,8 @@ class File:
|
|||||||
self.df_name = None
|
self.df_name = None
|
||||||
self.fill_pattern = None
|
self.fill_pattern = None
|
||||||
self.fill_pattern_repeat = False
|
self.fill_pattern_repeat = False
|
||||||
|
self.pstdo = None # pinStatusTemplateDO, mandatory for DF/ADF
|
||||||
|
self.lcsi = None # optional life cycle status indicator
|
||||||
# apply some defaults from profile
|
# apply some defaults from profile
|
||||||
if self.template:
|
if self.template:
|
||||||
self.from_template(self.template)
|
self.from_template(self.template)
|
||||||
@@ -278,6 +280,8 @@ class File:
|
|||||||
elif self.file_type in ['MF', 'DF', 'ADF']:
|
elif self.file_type in ['MF', 'DF', 'ADF']:
|
||||||
fdb_dec['file_type'] = 'df'
|
fdb_dec['file_type'] = 'df'
|
||||||
fdb_dec['structure'] = 'no_info_given'
|
fdb_dec['structure'] = 'no_info_given'
|
||||||
|
# pinStatusTemplateDO is mandatory for DF/ADF
|
||||||
|
fileDescriptor['pinStatusTemplateDO'] = self.pstdo
|
||||||
# build file descriptor based on above input data
|
# build file descriptor based on above input data
|
||||||
fd_dict = {}
|
fd_dict = {}
|
||||||
if len(fdb_dec):
|
if len(fdb_dec):
|
||||||
@@ -304,6 +308,8 @@ class File:
|
|||||||
# desired fill or repeat pattern in the "proprietaryEFInfo" element for the EF in Profiles
|
# desired fill or repeat pattern in the "proprietaryEFInfo" element for the EF in Profiles
|
||||||
# downloaded to a V2.2 or earlier eUICC.
|
# downloaded to a V2.2 or earlier eUICC.
|
||||||
fileDescriptor['proprietaryEFInfo'] = pefi
|
fileDescriptor['proprietaryEFInfo'] = pefi
|
||||||
|
if self.lcsi:
|
||||||
|
fileDescriptor['lcsi'] = self.lcsi
|
||||||
logger.debug("%s: to_fileDescriptor(%s)" % (self, fileDescriptor))
|
logger.debug("%s: to_fileDescriptor(%s)" % (self, fileDescriptor))
|
||||||
return fileDescriptor
|
return fileDescriptor
|
||||||
|
|
||||||
@@ -323,6 +329,8 @@ class File:
|
|||||||
if efFileSize:
|
if efFileSize:
|
||||||
self._file_size = self._decode_file_size(efFileSize)
|
self._file_size = self._decode_file_size(efFileSize)
|
||||||
|
|
||||||
|
self.pstdo = fileDescriptor.get('pinStatusTemplateDO', None)
|
||||||
|
self.lcsi = fileDescriptor.get('lcsi', None)
|
||||||
pefi = fileDescriptor.get('proprietaryEFInfo', {})
|
pefi = fileDescriptor.get('proprietaryEFInfo', {})
|
||||||
securityAttributesReferenced = fileDescriptor.get('securityAttributesReferenced', None)
|
securityAttributesReferenced = fileDescriptor.get('securityAttributesReferenced', None)
|
||||||
if securityAttributesReferenced:
|
if securityAttributesReferenced:
|
||||||
@@ -433,7 +441,7 @@ class File:
|
|||||||
elif k == 'fillFileContent':
|
elif k == 'fillFileContent':
|
||||||
stream.write(v)
|
stream.write(v)
|
||||||
else:
|
else:
|
||||||
return ValueError("Unknown key '%s' in tuple list" % k)
|
raise ValueError("Unknown key '%s' in tuple list" % k)
|
||||||
return stream.getvalue()
|
return stream.getvalue()
|
||||||
|
|
||||||
def file_content_to_tuples(self, optimize:bool = False) -> List[Tuple]:
|
def file_content_to_tuples(self, optimize:bool = False) -> List[Tuple]:
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ class ListOfSupportedOptions(BER_TLV_IE, tag=0x81):
|
|||||||
class SupportedKeysForScp03(BER_TLV_IE, tag=0x82):
|
class SupportedKeysForScp03(BER_TLV_IE, tag=0x82):
|
||||||
_construct = FlagsEnum(Byte, aes128=0x01, aes192=0x02, aes256=0x04)
|
_construct = FlagsEnum(Byte, aes128=0x01, aes192=0x02, aes256=0x04)
|
||||||
class SupportedTlsCipherSuitesForScp81(BER_TLV_IE, tag=0x83):
|
class SupportedTlsCipherSuitesForScp81(BER_TLV_IE, tag=0x83):
|
||||||
_consuruct = GreedyRange(Int16ub)
|
_construct = GreedyRange(Int16ub)
|
||||||
class ScpInformation(BER_TLV_IE, tag=0xa0, nested=[ScpType, ListOfSupportedOptions, SupportedKeysForScp03,
|
class ScpInformation(BER_TLV_IE, tag=0xa0, nested=[ScpType, ListOfSupportedOptions, SupportedKeysForScp03,
|
||||||
SupportedTlsCipherSuitesForScp81]):
|
SupportedTlsCipherSuitesForScp81]):
|
||||||
pass
|
pass
|
||||||
@@ -319,7 +319,7 @@ class CurrentSecurityLevel(BER_TLV_IE, tag=0xd3):
|
|||||||
# GlobalPlatform v2.3.1 Section 11.3.3.1.3
|
# GlobalPlatform v2.3.1 Section 11.3.3.1.3
|
||||||
class ApplicationAID(BER_TLV_IE, tag=0x4f):
|
class ApplicationAID(BER_TLV_IE, tag=0x4f):
|
||||||
_construct = GreedyBytes
|
_construct = GreedyBytes
|
||||||
class ApplicationTemplate(BER_TLV_IE, tag=0x61, ntested=[ApplicationAID]):
|
class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationAID]):
|
||||||
pass
|
pass
|
||||||
class ListOfApplications(BER_TLV_IE, tag=0x2f00, nested=[ApplicationTemplate]):
|
class ListOfApplications(BER_TLV_IE, tag=0x2f00, nested=[ApplicationTemplate]):
|
||||||
pass
|
pass
|
||||||
@@ -562,13 +562,13 @@ class ADF_SD(CardADF):
|
|||||||
|
|
||||||
@cmd2.with_argparser(store_data_parser)
|
@cmd2.with_argparser(store_data_parser)
|
||||||
def do_store_data(self, opts):
|
def do_store_data(self, opts):
|
||||||
"""Perform the GlobalPlatform GET DATA command in order to store some card-specific data.
|
"""Perform the GlobalPlatform STORE DATA command in order to store some card-specific data.
|
||||||
See GlobalPlatform CardSpecification v2.3 Section 11.11 for details."""
|
See GlobalPlatform CardSpecification v2.3 Section 11.11 for details."""
|
||||||
response_permitted = opts.response == 'may_be_returned'
|
response_permitted = opts.response == 'may_be_returned'
|
||||||
self.store_data(h2b(opts.DATA), opts.data_structure, opts.encryption, response_permitted)
|
self.store_data(h2b(opts.DATA), opts.data_structure, opts.encryption, response_permitted)
|
||||||
|
|
||||||
def store_data(self, data: bytes, structure:str = 'none', encryption:str = 'none', response_permitted: bool = False) -> bytes:
|
def store_data(self, data: bytes, structure:str = 'none', encryption:str = 'none', response_permitted: bool = False) -> bytes:
|
||||||
"""Perform the GlobalPlatform GET DATA command in order to store some card-specific data.
|
"""Perform the GlobalPlatform STORE DATA command in order to store some card-specific data.
|
||||||
See GlobalPlatform CardSpecification v2.3 Section 11.11 for details."""
|
See GlobalPlatform CardSpecification v2.3 Section 11.11 for details."""
|
||||||
max_cmd_len = self._cmd.lchan.scc.max_cmd_len
|
max_cmd_len = self._cmd.lchan.scc.max_cmd_len
|
||||||
# Table 11-89 of GP Card Specification v2.3
|
# Table 11-89 of GP Card Specification v2.3
|
||||||
@@ -585,7 +585,7 @@ class ADF_SD(CardADF):
|
|||||||
data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(chunk) + "00")
|
data, _sw = self._cmd.lchan.scc.send_apdu_checksw(hdr + b2h(chunk) + "00")
|
||||||
block_nr += 1
|
block_nr += 1
|
||||||
response += data
|
response += data
|
||||||
return data
|
return h2b(response)
|
||||||
|
|
||||||
put_key_parser = argparse.ArgumentParser()
|
put_key_parser = argparse.ArgumentParser()
|
||||||
put_key_parser.add_argument('--old-key-version-nr', type=auto_uint8, default=0, help='Old Key Version Number')
|
put_key_parser.add_argument('--old-key-version-nr', type=auto_uint8, default=0, help='Old Key Version Number')
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ class Scp03SessionKeys:
|
|||||||
"""Obtain the ICV value computed as described in 6.2.6.
|
"""Obtain the ICV value computed as described in 6.2.6.
|
||||||
This method has two modes:
|
This method has two modes:
|
||||||
* is_response=False for computing the ICV for C-ENC. Will pre-increment the counter.
|
* is_response=False for computing the ICV for C-ENC. Will pre-increment the counter.
|
||||||
* is_response=False for computing the ICV for R-DEC."""
|
* is_response=True for computing the ICV for R-DEC."""
|
||||||
if not is_response:
|
if not is_response:
|
||||||
self.block_nr += 1
|
self.block_nr += 1
|
||||||
# The binary value of this number SHALL be left padded with zeroes to form a full block.
|
# The binary value of this number SHALL be left padded with zeroes to form a full block.
|
||||||
|
|||||||
12
pySim/log.py
12
pySim/log.py
@@ -63,7 +63,7 @@ class PySimLogger:
|
|||||||
raise RuntimeError('static class, do not instantiate')
|
raise RuntimeError('static class, do not instantiate')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setup(print_callback = None, colors:dict = {}, verbose_debug:bool = False):
|
def setup(print_callback = None, colors:dict = {}):
|
||||||
"""
|
"""
|
||||||
Set a print callback function and color scheme. This function call is optional. In case this method is not
|
Set a print callback function and color scheme. This function call is optional. In case this method is not
|
||||||
called, default settings apply.
|
called, default settings apply.
|
||||||
@@ -72,20 +72,10 @@ class PySimLogger:
|
|||||||
have the following format: print_callback(message:str)
|
have the following format: print_callback(message:str)
|
||||||
colors : An optional dict through which certain log levels can be assigned a color.
|
colors : An optional dict through which certain log levels can be assigned a color.
|
||||||
(e.g. {logging.WARN: YELLOW})
|
(e.g. {logging.WARN: YELLOW})
|
||||||
verbose_debug: Enable verbose logging and set the loglevel DEBUG when set to true. Otherwise the
|
|
||||||
non-verbose logging is used and the loglevel is set to INFO. This setting can be changed
|
|
||||||
using the set_verbose and set_level methods at any time.
|
|
||||||
"""
|
"""
|
||||||
PySimLogger.print_callback = print_callback
|
PySimLogger.print_callback = print_callback
|
||||||
PySimLogger.colors = colors
|
PySimLogger.colors = colors
|
||||||
|
|
||||||
if (verbose_debug):
|
|
||||||
PySimLogger.set_verbose(True)
|
|
||||||
PySimLogger.set_level(logging.DEBUG)
|
|
||||||
else:
|
|
||||||
PySimLogger.set_verbose(False)
|
|
||||||
PySimLogger.set_level(logging.INFO)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def set_verbose(verbose:bool = False):
|
def set_verbose(verbose:bool = False):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -221,12 +221,12 @@ class OtaAlgoCrypt(OtaAlgo, abc.ABC):
|
|||||||
for subc in cls.__subclasses__():
|
for subc in cls.__subclasses__():
|
||||||
if subc.enum_name == otak.algo_crypt:
|
if subc.enum_name == otak.algo_crypt:
|
||||||
return subc(otak)
|
return subc(otak)
|
||||||
raise ValueError('No implementation for crypt algorithm %s' % otak.algo_auth)
|
raise ValueError('No implementation for crypt algorithm %s' % otak.algo_crypt)
|
||||||
|
|
||||||
class OtaAlgoAuth(OtaAlgo, abc.ABC):
|
class OtaAlgoAuth(OtaAlgo, abc.ABC):
|
||||||
def __init__(self, otak: OtaKeyset):
|
def __init__(self, otak: OtaKeyset):
|
||||||
if self.enum_name != otak.algo_auth:
|
if self.enum_name != otak.algo_auth:
|
||||||
raise ValueError('Cannot use algorithm %s with key for %s' % (self.enum_name, otak.algo_crypt))
|
raise ValueError('Cannot use algorithm %s with key for %s' % (self.enum_name, otak.algo_auth))
|
||||||
super().__init__(otak)
|
super().__init__(otak)
|
||||||
|
|
||||||
def sign(self, data:bytes) -> bytes:
|
def sign(self, data:bytes) -> bytes:
|
||||||
|
|||||||
10
pySim/sms.py
10
pySim/sms.py
@@ -169,8 +169,14 @@ class SMS_TPDU(abc.ABC):
|
|||||||
|
|
||||||
class SMS_DELIVER(SMS_TPDU):
|
class SMS_DELIVER(SMS_TPDU):
|
||||||
"""Representation of a SMS-DELIVER T-PDU. This is the Network to MS/UE (downlink) direction."""
|
"""Representation of a SMS-DELIVER T-PDU. This is the Network to MS/UE (downlink) direction."""
|
||||||
flags_construct = BitStruct('tp_rp'/Flag, 'tp_udhi'/Flag, 'tp_rp'/Flag, 'tp_sri'/Flag,
|
flags_construct = BitStruct('tp_rp'/Flag,
|
||||||
Padding(1), 'tp_mms'/Flag, 'tp_mti'/BitsInteger(2))
|
'tp_udhi'/Flag,
|
||||||
|
'tp_sri'/Flag,
|
||||||
|
Padding(1),
|
||||||
|
'tp_lp'/Flag,
|
||||||
|
'tp_mms'/Flag,
|
||||||
|
'tp_mti'/BitsInteger(2))
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
kwargs['tp_mti'] = 0
|
kwargs['tp_mti'] = 0
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|||||||
@@ -301,53 +301,24 @@ class LinkBaseTpdu(LinkBase):
|
|||||||
|
|
||||||
prev_tpdu = tpdu
|
prev_tpdu = tpdu
|
||||||
data, sw = self.send_tpdu(tpdu)
|
data, sw = self.send_tpdu(tpdu)
|
||||||
log.debug("T0: case #%u TPDU: %s => %s %s", case, tpdu, data or "(no data)", sw or "(no status word)")
|
|
||||||
|
|
||||||
# After sending the APDU/TPDU the UICC/eUICC or SIM may response with a status word that indicates that further
|
# When we have sent the first APDU, the SW may indicate that there are response bytes
|
||||||
# TPDUs have to be sent in order to complete the task.
|
# available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
|
||||||
|
# xx is the number of response bytes available.
|
||||||
|
# See also:
|
||||||
if sw is not None:
|
if sw is not None:
|
||||||
if case == 4 or self.apdu_strict == False:
|
while (sw[0:2] in ['9f', '61', '62', '63']):
|
||||||
# In case the APDU is a case #4 APDU, the UICC/eUICC/SIM may indicate that there is response data
|
# SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed
|
||||||
# available which has to be retrieved using a GET RESPONSE command TPDU.
|
# SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2
|
||||||
#
|
# SW1=62: ETSI TS 102 221 7.3.1.1.4 Clause 4b): 62xx, 63xx, 9xxx != 9000
|
||||||
# ETSI TS 102 221, section 7.3.1.1.4 is very cleare about the fact that the GET RESPONSE mechanism
|
tpdu_gr = tpdu[0:2] + 'c00000' + sw[2:4]
|
||||||
# shall only apply on case #4 APDUs but unfortunately it is impossible to distinguish between case #3
|
|
||||||
# and case #4 when the APDU format is not strictly followed. In order to be able to detect case #4
|
|
||||||
# correctly the Le byte (usually 0x00) must be present, is often forgotten. To avoid problems with
|
|
||||||
# legacy scripts that use raw APDU strings, we will still loosely apply GET RESPONSE based on what
|
|
||||||
# the status word indicates. Unless the user explicitly enables the strict mode (set apdu_strict true)
|
|
||||||
while True:
|
|
||||||
if sw in ['9000', '9100']:
|
|
||||||
# A status word of 9000 (or 9100 in case there is pending data from a proactive SIM command)
|
|
||||||
# indicates that either no response data was returnd or all response data has been retrieved
|
|
||||||
# successfully. We may discontinue the processing at this point.
|
|
||||||
break;
|
|
||||||
if sw[0:2] in ['61', '9f']:
|
|
||||||
# A status word of 61xx or 9fxx indicates that there is (still) response data available. We
|
|
||||||
# send a GET RESPONSE command with the length value indicated in the second byte of the status
|
|
||||||
# word. (see also ETSI TS 102 221, section 7.3.1.1.4, clause 4a and 3GPP TS 51.011 9.4.1 and
|
|
||||||
# ISO/IEC 7816-4, Table 5)
|
|
||||||
le_gr = sw[2:4]
|
|
||||||
elif sw[0:2] in ['62', '63']:
|
|
||||||
# There are corner cases (status word is 62xx or 63xx) where the UICC/eUICC/SIM asks us
|
|
||||||
# to send a dummy GET RESPONSE command. We send a GET RESPONSE command with a length of 0.
|
|
||||||
# (see also ETSI TS 102 221, section 7.3.1.1.4, clause 4b and ETSI TS 151 011, section 9.4.1)
|
|
||||||
le_gr = '00'
|
|
||||||
else:
|
|
||||||
# A status word other then the ones covered by the above logic may indicate an error. In this
|
|
||||||
# case we will discontinue the processing as well.
|
|
||||||
# (see also ETSI TS 102 221, section 7.3.1.1.4, clause 4c)
|
|
||||||
break
|
|
||||||
tpdu_gr = tpdu[0:2] + 'c00000' + le_gr
|
|
||||||
prev_tpdu = tpdu_gr
|
prev_tpdu = tpdu_gr
|
||||||
data_gr, sw = self.send_tpdu(tpdu_gr)
|
d, sw = self.send_tpdu(tpdu_gr)
|
||||||
log.debug("T0: GET RESPONSE TPDU: %s => %s %s", tpdu_gr, data_gr or "(no data)", sw or "(no status word)")
|
data += d
|
||||||
data += data_gr
|
|
||||||
if sw[0:2] == '6c':
|
if sw[0:2] == '6c':
|
||||||
# SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
|
# SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
|
||||||
tpdu_gr = prev_tpdu[0:8] + sw[2:4]
|
tpdu_gr = prev_tpdu[0:8] + sw[2:4]
|
||||||
data, sw = self.send_tpdu(tpdu_gr)
|
data, sw = self.send_tpdu(tpdu_gr)
|
||||||
log.debug("T0: repated case #%u TPDU: %s => %s %s", case, tpdu_gr, data or "(no data)", sw or "(no status word)")
|
|
||||||
|
|
||||||
return data, sw
|
return data, sw
|
||||||
|
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ class PcscSimLink(LinkBaseTpdu):
|
|||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
try:
|
try:
|
||||||
# To avoid leakage of resources, make sure the reader
|
# To avoid leakage of resources, make sure the reader is disconnected
|
||||||
# is disconnected
|
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
# Make card connection and select a suitable communication protocol
|
# Make card connection and select a suitable communication protocol
|
||||||
|
|||||||
@@ -1058,7 +1058,7 @@ class EF_OCSGL(LinFixedEF):
|
|||||||
# TS 31.102 Section 4.4.11.2 (Rel 15)
|
# TS 31.102 Section 4.4.11.2 (Rel 15)
|
||||||
class EF_5GS3GPPLOCI(TransparentEF):
|
class EF_5GS3GPPLOCI(TransparentEF):
|
||||||
def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size=(20, 20),
|
def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size=(20, 20),
|
||||||
desc='5S 3GP location information', **kwargs):
|
desc='5GS 3GPP location information', **kwargs):
|
||||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
|
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs)
|
||||||
upd_status_constr = Enum(
|
upd_status_constr = Enum(
|
||||||
Byte, updated=0, not_updated=1, roaming_not_allowed=2)
|
Byte, updated=0, not_updated=1, roaming_not_allowed=2)
|
||||||
@@ -1326,7 +1326,7 @@ class EF_5G_PROSE_UIR(TransparentEF):
|
|||||||
pass
|
pass
|
||||||
class FiveGDdnmfCtfAddrForUploading(BER_TLV_IE, tag=0x97):
|
class FiveGDdnmfCtfAddrForUploading(BER_TLV_IE, tag=0x97):
|
||||||
pass
|
pass
|
||||||
class ProSeConfigDataForUeToNetworkRelayUE(BER_TLV_IE, tag=0xa0,
|
class ProSeConfigDataForUsageInfoReporting(BER_TLV_IE, tag=0xa0,
|
||||||
nested=[EF_5G_PROSE_DD.ValidityTimer,
|
nested=[EF_5G_PROSE_DD.ValidityTimer,
|
||||||
CollectionPeriod, ReportingWindow,
|
CollectionPeriod, ReportingWindow,
|
||||||
ReportingIndicators,
|
ReportingIndicators,
|
||||||
@@ -1336,7 +1336,7 @@ class EF_5G_PROSE_UIR(TransparentEF):
|
|||||||
desc='5G ProSe configuration data for usage information reporting', **kwargs):
|
desc='5G ProSe configuration data for usage information reporting', **kwargs):
|
||||||
super().__init__(fid, sfid=sfid, name=name, desc=desc, **kwargs)
|
super().__init__(fid, sfid=sfid, name=name, desc=desc, **kwargs)
|
||||||
# contains TLV structure despite being TransparentEF, not BER-TLV ?!?
|
# contains TLV structure despite being TransparentEF, not BER-TLV ?!?
|
||||||
self._tlv = EF_5G_PROSE_UIR.ProSeConfigDataForUeToNetworkRelayUE
|
self._tlv = EF_5G_PROSE_UIR.ProSeConfigDataForUsageInfoReporting
|
||||||
|
|
||||||
# TS 31.102 Section 4.4.13.8 (Rel 18)
|
# TS 31.102 Section 4.4.13.8 (Rel 18)
|
||||||
class EF_5G_PROSE_U2URU(TransparentEF):
|
class EF_5G_PROSE_U2URU(TransparentEF):
|
||||||
|
|||||||
@@ -309,7 +309,6 @@ class EF_SMSP(LinFixedEF):
|
|||||||
'tp_dest_addr'/Flag)),
|
'tp_dest_addr'/Flag)),
|
||||||
'tp_dest_addr'/ScAddr,
|
'tp_dest_addr'/ScAddr,
|
||||||
'tp_sc_addr'/ScAddr,
|
'tp_sc_addr'/ScAddr,
|
||||||
|
|
||||||
'tp_pid'/Bytes(1),
|
'tp_pid'/Bytes(1),
|
||||||
'tp_dcs'/Bytes(1),
|
'tp_dcs'/Bytes(1),
|
||||||
'tp_vp_minutes'/EF_SMSP.ValidityPeriodAdapter(Byte))
|
'tp_vp_minutes'/EF_SMSP.ValidityPeriodAdapter(Byte))
|
||||||
@@ -1117,8 +1116,8 @@ class DF_GSM(CardDF):
|
|||||||
EF_MBI(),
|
EF_MBI(),
|
||||||
EF_MWIS(),
|
EF_MWIS(),
|
||||||
EF_CFIS(),
|
EF_CFIS(),
|
||||||
EF_EXT('6fc8', None, 'EF.EXT6', desc='Externsion6 (MBDN)'),
|
EF_EXT('6fc8', None, 'EF.EXT6', desc='Extension6 (MBDN)'),
|
||||||
EF_EXT('6fcc', None, 'EF.EXT7', desc='Externsion7 (CFIS)'),
|
EF_EXT('6fcc', None, 'EF.EXT7', desc='Extension7 (CFIS)'),
|
||||||
EF_SPDI(),
|
EF_SPDI(),
|
||||||
EF_MMSN(),
|
EF_MMSN(),
|
||||||
EF_EXT('6fcf', None, 'EF.EXT8', desc='Extension8 (MMSN)'),
|
EF_EXT('6fcf', None, 'EF.EXT8', desc='Extension8 (MMSN)'),
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
|
|||||||
|
|
||||||
def dec_plmn(threehexbytes: Hexstr) -> dict:
|
def dec_plmn(threehexbytes: Hexstr) -> dict:
|
||||||
res = {'mcc': "0", 'mnc': "0"}
|
res = {'mcc': "0", 'mnc': "0"}
|
||||||
dec_mcc_from_plmn_str(threehexbytes)
|
|
||||||
res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
|
res['mcc'] = dec_mcc_from_plmn_str(threehexbytes)
|
||||||
res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
|
res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
|
||||||
return res
|
return res
|
||||||
@@ -911,7 +910,8 @@ class DataObjectCollection:
|
|||||||
def encode(self, decoded) -> bytes:
|
def encode(self, decoded) -> bytes:
|
||||||
res = bytearray()
|
res = bytearray()
|
||||||
for i in decoded:
|
for i in decoded:
|
||||||
obj = self.members_by_name(i[0])
|
name = i[0]
|
||||||
|
obj = self.members_by_name[name]
|
||||||
res.append(obj.to_tlv())
|
res.append(obj.to_tlv())
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
INFO: Using PC/SC reader interface
|
Using PC/SC reader interface
|
||||||
Reading ...
|
Reading ...
|
||||||
Autodetected card type: Fairwaves-SIM
|
Autodetected card type: Fairwaves-SIM
|
||||||
ICCID: 8988219000000117833
|
ICCID: 8988219000000117833
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
INFO: Using PC/SC reader interface
|
Using PC/SC reader interface
|
||||||
Reading ...
|
Reading ...
|
||||||
Autodetected card type: Wavemobile-SIM
|
Autodetected card type: Wavemobile-SIM
|
||||||
ICCID: 89445310150011013678
|
ICCID: 89445310150011013678
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
INFO: Using PC/SC reader interface
|
Using PC/SC reader interface
|
||||||
Reading ...
|
Reading ...
|
||||||
Autodetected card type: fakemagicsim
|
Autodetected card type: fakemagicsim
|
||||||
ICCID: 1122334455667788990
|
ICCID: 1122334455667788990
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
INFO: Using PC/SC reader interface
|
Using PC/SC reader interface
|
||||||
Reading ...
|
Reading ...
|
||||||
Autodetected card type: sysmoISIM-SJA2
|
Autodetected card type: sysmoISIM-SJA2
|
||||||
ICCID: 8988211000000467343
|
ICCID: 8988211000000467343
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
INFO: Using PC/SC reader interface
|
Using PC/SC reader interface
|
||||||
Reading ...
|
Reading ...
|
||||||
Autodetected card type: sysmoISIM-SJA5
|
Autodetected card type: sysmoISIM-SJA5
|
||||||
ICCID: 8949440000001155314
|
ICCID: 8949440000001155314
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
INFO: Using PC/SC reader interface
|
Using PC/SC reader interface
|
||||||
Reading ...
|
Reading ...
|
||||||
Autodetected card type: sysmoUSIM-SJS1
|
Autodetected card type: sysmoUSIM-SJS1
|
||||||
ICCID: 8988211320300000028
|
ICCID: 8988211320300000028
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
INFO: Using PC/SC reader interface
|
Using PC/SC reader interface
|
||||||
Reading ...
|
Reading ...
|
||||||
Autodetected card type: sysmosim-gr1
|
Autodetected card type: sysmosim-gr1
|
||||||
ICCID: 2222334455667788990
|
ICCID: 2222334455667788990
|
||||||
|
|||||||
Reference in New Issue
Block a user