diff options
Diffstat (limited to 'pySim/utils.py')
-rw-r--r-- | pySim/utils.py | 460 |
1 files changed, 6 insertions, 454 deletions
diff --git a/pySim/utils.py b/pySim/utils.py index afa476b..05ad962 100644 --- a/pySim/utils.py +++ b/pySim/utils.py @@ -10,6 +10,8 @@ import datetime import argparse from io import BytesIO from typing import Optional, List, Dict, Any, Tuple, NewType, Union +from osmocom.utils import * +from osmocom.tlv import bertlv_encode_tag, bertlv_encode_len # Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com> # Copyright (C) 2021 Harald Welte <laforge@osmocom.org> @@ -28,374 +30,6 @@ from typing import Optional, List, Dict, Any, Tuple, NewType, Union # along with this program. If not, see <http://www.gnu.org/licenses/>. # -# just to differentiate strings of hex nibbles from everything else -Hexstr = NewType('Hexstr', str) -SwHexstr = NewType('SwHexstr', str) -SwMatchstr = NewType('SwMatchstr', str) -ResTuple = Tuple[Hexstr, SwHexstr] - -def h2b(s: Hexstr) -> bytearray: - """convert from a string of hex nibbles to a sequence of bytes""" - return bytearray.fromhex(s) - - -def b2h(b: bytearray) -> Hexstr: - """convert from a sequence of bytes to a string of hex nibbles""" - return ''.join(['%02x' % (x) for x in b]) - - -def h2i(s: Hexstr) -> List[int]: - """convert from a string of hex nibbles to a list of integers""" - return [(int(x, 16) << 4)+int(y, 16) for x, y in zip(s[0::2], s[1::2])] - - -def i2h(s: List[int]) -> Hexstr: - """convert from a list of integers to a string of hex nibbles""" - return ''.join(['%02x' % (x) for x in s]) - - -def h2s(s: Hexstr) -> str: - """convert from a string of hex nibbles to an ASCII string""" - return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2]) - if int(x + y, 16) != 0xff]) - - -def s2h(s: str) -> Hexstr: - """convert from an ASCII string to a string of hex nibbles""" - b = bytearray() - b.extend(map(ord, s)) - return b2h(b) - - -def i2s(s: List[int]) -> str: - """convert from a list of integers to an ASCII string""" - return ''.join([chr(x) for x in s]) - - -def swap_nibbles(s: Hexstr) -> Hexstr: - """swap the nibbles in a hex string""" - return ''.join([x+y for x, y in zip(s[1::2], s[0::2])]) - - -def rpad(s: str, l: int, c='f') -> str: - """pad string on the right side. - Args: - s : string to pad - l : total length to pad to - c : padding character - Returns: - String 's' padded with as many 'c' as needed to reach total length of 'l' - """ - return s + c * (l - len(s)) - - -def lpad(s: str, l: int, c='f') -> str: - """pad string on the left side. - Args: - s : string to pad - l : total length to pad to - c : padding character - Returns: - String 's' padded with as many 'c' as needed to reach total length of 'l' - """ - return c * (l - len(s)) + s - - -def half_round_up(n: int) -> int: - return (n + 1)//2 - - -def str_sanitize(s: str) -> str: - """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that - there are no whitespaces at the end and at the beginning of the string. - - Args: - s : string to sanitize - Returns: - filtered result of string 's' - """ - - chars_to_keep = string.digits + string.ascii_letters + string.punctuation - res = ''.join([c if c in chars_to_keep else ' ' for c in s]) - return res.strip() - -######################################################################### -# poor man's COMPREHENSION-TLV decoder. -######################################################################### - - -def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]: - """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1""" - if binary[0] in [0x00, 0x80, 0xff]: - raise ValueError("Found illegal value 0x%02x in %s" % - (binary[0], binary)) - if binary[0] == 0x7f: - # three-byte tag - tag = binary[0] << 16 | binary[1] << 8 | binary[2] - return (tag, binary[3:]) - elif binary[0] == 0xff: - return None, binary - else: - # single byte tag - tag = binary[0] - return (tag, binary[1:]) - - -def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]: - """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1""" - if binary[0] in [0x00, 0x80, 0xff]: - raise ValueError("Found illegal value 0x%02x in %s" % - (binary[0], binary)) - if binary[0] == 0x7f: - # three-byte tag - tag = (binary[1] & 0x7f) << 8 - tag |= binary[2] - compr = bool(binary[1] & 0x80) - return ({'comprehension': compr, 'tag': tag}, binary[3:]) - else: - # single byte tag - tag = binary[0] & 0x7f - compr = bool(binary[0] & 0x80) - return ({'comprehension': compr, 'tag': tag}, binary[1:]) - - -def comprehensiontlv_encode_tag(tag) -> bytes: - """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1""" - # permit caller to specify tag also as integer value - if isinstance(tag, int): - compr = bool(tag < 0xff and tag & 0x80) - tag = {'tag': tag, 'comprehension': compr} - compr = tag.get('comprehension', False) - if tag['tag'] in [0x00, 0x80, 0xff] or tag['tag'] > 0xff: - # 3-byte format - byte3 = tag['tag'] & 0xff - byte2 = (tag['tag'] >> 8) & 0x7f - if compr: - byte2 |= 0x80 - return b'\x7f' + byte2.to_bytes(1, 'big') + byte3.to_bytes(1, 'big') - else: - # 1-byte format - ret = tag['tag'] - if compr: - ret |= 0x80 - return ret.to_bytes(1, 'big') - -# length value coding is equal to BER-TLV - - -def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]: - """Parse a single TLV IE at the start of the given binary data. - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (tag:dict, len:int, remainder:bytes) - """ - (tagdict, remainder) = comprehensiontlv_parse_tag(binary) - (length, remainder) = bertlv_parse_len(remainder) - value = remainder[:length] - remainder = remainder[length:] - return (tagdict, length, value, remainder) - - -######################################################################### -# poor man's BER-TLV decoder. To be a more sophisticated OO library later -######################################################################### - -def bertlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]: - """Get a single raw Tag from start of input according to ITU-T X.690 8.1.2 - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (tag:int, remainder:bytes) - """ - # check for FF padding at the end, as customary in SIM card files - if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff: - return None, binary - tag = binary[0] & 0x1f - if tag <= 30: - return binary[0], binary[1:] - else: # multi-byte tag - tag = binary[0] - i = 1 - last = False - while not last: - last = not bool(binary[i] & 0x80) - tag <<= 8 - tag |= binary[i] - i += 1 - return tag, binary[i:] - - -def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]: - """Parse a single Tag value according to ITU-T X.690 8.1.2 - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes) - """ - cls = binary[0] >> 6 - constructed = bool(binary[0] & 0x20) - tag = binary[0] & 0x1f - if tag <= 30: - return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:]) - else: # multi-byte tag - tag = 0 - i = 1 - last = False - while not last: - last = not bool(binary[i] & 0x80) - tag <<= 7 - tag |= binary[i] & 0x7f - i += 1 - return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:]) - - -def bertlv_encode_tag(t) -> bytes: - """Encode a single Tag value according to ITU-T X.690 8.1.2 - """ - def get_top7_bits(inp: int) -> Tuple[int, int]: - """Get top 7 bits of integer. Returns those 7 bits as integer and the remaining LSBs.""" - remain_bits = inp.bit_length() - if remain_bits >= 7: - bitcnt = 7 - else: - bitcnt = remain_bits - outp = inp >> (remain_bits - bitcnt) - remainder = inp & ~ (inp << (remain_bits - bitcnt)) - return outp, remainder - - def count_int_bytes(inp: int) -> int: - """count the number of bytes require to represent the given integer.""" - i = 1 - inp = inp >> 8 - while inp: - i += 1 - inp = inp >> 8 - return i - - if isinstance(t, int): - # first convert to a dict representation - tag_size = count_int_bytes(t) - t, _remainder = bertlv_parse_tag(t.to_bytes(tag_size, 'big')) - tag = t['tag'] - constructed = t['constructed'] - cls = t['class'] - if tag <= 30: - t = tag & 0x1f - if constructed: - t |= 0x20 - t |= (cls & 3) << 6 - return bytes([t]) - else: # multi-byte tag - t = 0x1f - if constructed: - t |= 0x20 - t |= (cls & 3) << 6 - tag_bytes = bytes([t]) - remain = tag - while True: - t, remain = get_top7_bits(remain) - if remain: - t |= 0x80 - tag_bytes += bytes([t]) - if not remain: - break - return tag_bytes - - -def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]: - """Parse a single Length value according to ITU-T X.690 8.1.3; - only the definite form is supported here. - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (length, remainder) - """ - if binary[0] < 0x80: - return (binary[0], binary[1:]) - else: - num_len_oct = binary[0] & 0x7f - length = 0 - if len(binary) < num_len_oct + 1: - return (0, b'') - for i in range(1, 1+num_len_oct): - length <<= 8 - length |= binary[i] - return (length, binary[1+num_len_oct:]) - - -def bertlv_encode_len(length: int) -> bytes: - """Encode a single Length value according to ITU-T X.690 8.1.3; - only the definite form is supported here. - Args: - length : length value to be encoded - Returns: - binary output data of BER-TLV length field - """ - if length < 0x80: - return length.to_bytes(1, 'big') - elif length <= 0xff: - return b'\x81' + length.to_bytes(1, 'big') - elif length <= 0xffff: - return b'\x82' + length.to_bytes(2, 'big') - elif length <= 0xffffff: - return b'\x83' + length.to_bytes(3, 'big') - elif length <= 0xffffffff: - return b'\x84' + length.to_bytes(4, 'big') - else: - raise ValueError("Length > 32bits not supported") - - -def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]: - """Parse a single TLV IE at the start of the given binary data. - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (tag:dict, len:int, remainder:bytes) - """ - (tagdict, remainder) = bertlv_parse_tag(binary) - (length, remainder) = bertlv_parse_len(remainder) - value = remainder[:length] - remainder = remainder[length:] - return (tagdict, length, value, remainder) - - -def dgi_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]: - # In absence of any clear spec guidance we assume it's always 16 bit - return int.from_bytes(binary[:2], 'big'), binary[2:] - -def dgi_encode_tag(t: int) -> bytes: - return t.to_bytes(2, 'big') - -def dgi_encode_len(length: int) -> bytes: - """Encode a single Length value according to GlobalPlatform Systems Scripting Language - Specification v1.1.0 Annex B. - Args: - length : length value to be encoded - Returns: - binary output data of encoded length field - """ - if length < 255: - return length.to_bytes(1, 'big') - elif length <= 0xffff: - return b'\xff' + length.to_bytes(2, 'big') - else: - raise ValueError("Length > 32bits not supported") - -def dgi_parse_len(binary: bytes) -> Tuple[int, bytes]: - """Parse a single Length value according to GlobalPlatform Systems Scripting Language - Specification v1.1.0 Annex B. - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (length, remainder) - """ - if binary[0] == 255: - assert len(binary) >= 3 - return ((binary[1] << 8) | binary[2]), binary[3:] - else: - return binary[0], binary[1:] - # IMSI encoded format: # For IMSI 0123456789ABCDE: # @@ -411,6 +45,10 @@ def dgi_parse_len(binary: bytes) -> Tuple[int, bytes]: # Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an # even length IMSI only uses half of the last byte. +SwHexstr = NewType('SwHexstr', str) +SwMatchstr = NewType('SwMatchstr', str) +ResTuple = Tuple[Hexstr, SwHexstr] + def enc_imsi(imsi: str): """Converts a string IMSI into the encoded value of the EF""" l = half_round_up( @@ -783,29 +421,6 @@ def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr: return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2) -def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool: - """ - Check if a string is a valid hexstring - """ - - # Filter obviously bad strings - if not string: - return False - if len(string) < minlen or minlen < 2: - return False - if len(string) % 2: - return False - if maxlen and len(string) > maxlen: - return False - - # Try actual encoding to be sure - try: - _try_encode = h2b(string) - return True - except: - return False - - def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr: """ The ADM pin can be supplied either in its hexadecimal form or as @@ -937,26 +552,6 @@ def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = return '\n'.join(table) -def auto_int(x): - """Helper function for argparse to accept hexadecimal integers.""" - return int(x, 0) - -def _auto_uint(x, max_val: int): - """Helper function for argparse to accept hexadecimal or decimal integers.""" - ret = int(x, 0) - if ret < 0 or ret > max_val: - raise argparse.ArgumentTypeError('Number exceeds permited value range (0, %u)' % max_val) - return ret - -def auto_uint7(x): - return _auto_uint(x, 127) - -def auto_uint8(x): - return _auto_uint(x, 255) - -def auto_uint16(x): - return _auto_uint(x, 65535) - def expand_hex(hexstring, length): """Expand a given hexstring to a specified length by replacing "." or ".." with a filler that is derived from the neighboring nibbles respective @@ -1011,17 +606,6 @@ def expand_hex(hexstring, length): return hexstring -class JsonEncoder(json.JSONEncoder): - """Extend the standard library JSONEncoder with support for more types.""" - - def default(self, o): - if isinstance(o, (BytesIO, bytes, bytearray)): - return b2h(o) - elif isinstance(o, datetime.datetime): - return o.isoformat() - return json.JSONEncoder.default(self, o) - - def boxed_heading_str(heading, width=80): """Generate a string that contains a boxed heading.""" # Auto-enlarge box if heading exceeds length @@ -1430,35 +1014,3 @@ class CardCommandSet: if cla and not cmd.match_cla(cla): return None return cmd - - -def all_subclasses(cls) -> set: - """Recursively get all subclasses of a specified class""" - return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)]) - -def is_hexstr_or_decimal(instr: str) -> str: - """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of - [hexa]decimal digits only.""" - if instr.isdecimal(): - return instr - if not all(c in string.hexdigits for c in instr): - raise ValueError('Input must be [hexa]decimal') - if len(instr) & 1: - raise ValueError('Input has un-even number of hex digits') - return instr - -def is_hexstr(instr: str) -> str: - """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of - an even sequence of hexadecimal digits only.""" - if not all(c in string.hexdigits for c in instr): - raise ValueError('Input must be hexadecimal') - if len(instr) & 1: - raise ValueError('Input has un-even number of hex digits') - return instr - -def is_decimal(instr: str) -> str: - """Method that can be used as 'type' in argparse.add_argument() to validate the value consists of - an even sequence of decimal digits only.""" - if not instr.isdecimal(): - raise ValueError('Input must decimal') - return instr |