mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-16 18:38:32 +03:00
Migrate over to using pyosmocom
We're creating a 'pyosmocom' pypi module which contains a number of core Osmocom libraries / interfaces that are not specific to SIM card stuff contained here. The main modules moved in this initial step are pySim.tlv, pySim.utils and pySim.construct. utils is split, not all of the contents is unrelated to SIM Cards. The other two are moved completely. Change-Id: I4b63e45bcb0c9ba2424dacf85e0222aee735f411
This commit is contained in:
@@ -77,7 +77,7 @@ Please install the following dependencies:
|
||||
- cmd2 >= 1.5.0
|
||||
- colorlog
|
||||
- construct >= 2.9.51
|
||||
- gsm0338
|
||||
- pyosmocom
|
||||
- jsonpath-ng
|
||||
- packaging
|
||||
- pycryptodomex
|
||||
|
||||
@@ -22,8 +22,8 @@ import sys
|
||||
import csv
|
||||
import argparse
|
||||
from Cryptodome.Cipher import AES
|
||||
from osmocom.utils import h2b, b2h, Hexstr
|
||||
|
||||
from pySim.utils import h2b, b2h, Hexstr
|
||||
from pySim.card_key_provider import CardKeyProviderCsv
|
||||
|
||||
def dict_keys_to_upper(d: dict) -> dict:
|
||||
|
||||
@@ -28,10 +28,11 @@ from cryptography import x509
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
|
||||
from osmocom.utils import h2b, b2h, swap_nibbles, is_hexstr
|
||||
from osmocom.tlv import bertlv_parse_one_rawtag, bertlv_return_one_rawtlv
|
||||
|
||||
import pySim.esim.rsp as rsp
|
||||
from pySim.esim import es9p, PMO
|
||||
from pySim.utils import h2b, b2h, swap_nibbles, is_hexstr
|
||||
from pySim.utils import bertlv_parse_one_rawtag, bertlv_return_one_rawtlv
|
||||
from pySim.esim.x509_cert import CertAndPrivkey
|
||||
from pySim.esim.es8p import BoundProfilePackage
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ import logging
|
||||
import zipfile
|
||||
from pathlib import Path as PlPath
|
||||
from typing import List
|
||||
from osmocom.utils import h2b, b2h, swap_nibbles
|
||||
|
||||
from pySim.esim.saip import *
|
||||
from pySim.esim.saip.validation import CheckBasicStructure
|
||||
from pySim import javacard
|
||||
from pySim.utils import h2b, b2h, swap_nibbles
|
||||
from pySim.pprint import HexBytesPrettyPrinter
|
||||
|
||||
pp = HexBytesPrettyPrinter(indent=4,width=500)
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from pySim.utils import bertlv_parse_one, bertlv_encode_tag, b2h, h2b
|
||||
from osmocom.utils import b2h, h2b
|
||||
from osmocom.tlv import bertlv_parse_one, bertlv_encode_tag
|
||||
|
||||
def process_one_level(content: bytes, indent: int):
|
||||
remainder = content
|
||||
|
||||
@@ -32,7 +32,7 @@ from klein import Klein
|
||||
from twisted.web.iweb import IRequest
|
||||
import asn1tools
|
||||
|
||||
from pySim.utils import h2b, b2h, swap_nibbles
|
||||
from osmocom.utils import h2b, b2h, swap_nibbles
|
||||
|
||||
import pySim.esim.rsp as rsp
|
||||
from pySim.esim import saip, PMO
|
||||
|
||||
@@ -33,11 +33,12 @@ import sys
|
||||
import traceback
|
||||
import json
|
||||
import csv
|
||||
from osmocom.utils import h2b, swap_nibbles, rpad
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.transport import init_reader, argparse_add_reader_args
|
||||
from pySim.legacy.cards import _cards_classes, card_detect
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid
|
||||
from pySim.utils import derive_milenage_opc, calculate_luhn, dec_iccid
|
||||
from pySim.ts_51_011 import EF_AD
|
||||
from pySim.legacy.ts_51_011 import EF
|
||||
from pySim.card_handler import *
|
||||
|
||||
@@ -29,6 +29,8 @@ import random
|
||||
import re
|
||||
import sys
|
||||
|
||||
from osmocom.utils import h2b, h2s, swap_nibbles, rpad
|
||||
|
||||
from pySim.ts_51_011 import EF_SST_map, EF_AD
|
||||
from pySim.legacy.ts_51_011 import EF, DF
|
||||
from pySim.ts_31_102 import EF_UST_map
|
||||
@@ -40,7 +42,7 @@ from pySim.commands import SimCardCommands
|
||||
from pySim.transport import init_reader, argparse_add_reader_args
|
||||
from pySim.exceptions import SwMatchError
|
||||
from pySim.legacy.cards import card_detect, SimCard, UsimCard, IsimCard
|
||||
from pySim.utils import h2b, h2s, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_msisdn
|
||||
from pySim.utils import dec_imsi, dec_iccid, dec_msisdn
|
||||
from pySim.legacy.utils import format_xplmn_w_act, dec_st
|
||||
|
||||
option_parser = argparse.ArgumentParser(description='Legacy tool for reading some parts of a SIM card',
|
||||
|
||||
@@ -47,11 +47,13 @@ from io import StringIO
|
||||
|
||||
from pprint import pprint as pp
|
||||
|
||||
from osmocom.utils import h2b, b2h, i2h, swap_nibbles, rpad, JsonEncoder, is_hexstr, is_decimal
|
||||
from osmocom.utils import is_hexstr_or_decimal, Hexstr
|
||||
from osmocom.tlv import bertlv_parse_one
|
||||
|
||||
from pySim.exceptions import *
|
||||
from pySim.transport import init_reader, ApduTracer, argparse_add_reader_args, ProactiveHandler
|
||||
from pySim.utils import h2b, b2h, i2h, swap_nibbles, rpad, JsonEncoder, bertlv_parse_one, sw_match
|
||||
from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, Hexstr, dec_iccid
|
||||
from pySim.utils import is_hexstr_or_decimal, is_hexstr, is_decimal
|
||||
from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, dec_iccid, sw_match
|
||||
from pySim.card_handler import CardHandler, CardHandlerAuto
|
||||
|
||||
from pySim.filesystem import CardMF, CardEF, CardDF, CardADF
|
||||
|
||||
@@ -8,7 +8,8 @@ from pprint import pprint as pp
|
||||
from pySim.apdu import *
|
||||
from pySim.runtime import RuntimeState
|
||||
|
||||
from pySim.utils import JsonEncoder
|
||||
from osmocom.utils import JsonEncoder
|
||||
|
||||
from pySim.cards import UiccCardBase
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.profile import CardProfile
|
||||
|
||||
@@ -29,12 +29,11 @@ import abc
|
||||
import typing
|
||||
from typing import List, Dict, Optional
|
||||
from termcolor import colored
|
||||
|
||||
from construct import Byte, GreedyBytes
|
||||
from construct import Optional as COptional
|
||||
from osmocom.construct import *
|
||||
from osmocom.utils import *
|
||||
|
||||
from pySim.construct import *
|
||||
from pySim.utils import *
|
||||
from pySim.runtime import RuntimeLchan, RuntimeState, lchan_nr_from_cla
|
||||
from pySim.filesystem import CardADF, CardFile, TransparentEF, LinFixedEF
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from construct import FlagsEnum, Struct
|
||||
from pySim.tlv import flatten_dict_lists
|
||||
from osmocom.tlv import flatten_dict_lists
|
||||
from osmocom.construct import *
|
||||
from pySim.apdu import ApduCommand, ApduCommandSet
|
||||
from pySim.construct import *
|
||||
from pySim.global_platform import InstallParameters
|
||||
|
||||
class GpDelete(ApduCommand, n='DELETE', ins=0xE4, cla=['8X', 'CX', 'EX']):
|
||||
|
||||
@@ -22,11 +22,12 @@ import logging
|
||||
|
||||
from construct import GreedyRange, Struct
|
||||
|
||||
from pySim.construct import *
|
||||
from osmocom.utils import i2h
|
||||
from osmocom.construct import *
|
||||
|
||||
from pySim.filesystem import *
|
||||
from pySim.runtime import RuntimeLchan
|
||||
from pySim.apdu import ApduCommand, ApduCommandSet
|
||||
from pySim.utils import i2h
|
||||
from pySim import cat
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
|
||||
from construct import Struct
|
||||
from osmocom.construct import *
|
||||
|
||||
from pySim.construct import *
|
||||
from pySim.apdu import ApduCommand, ApduCommandSet
|
||||
from pySim.ts_102_221 import FcpTemplate
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ from typing import Dict
|
||||
|
||||
from construct import BitStruct, Enum, BitsInteger, Int8ub, Bytes, this, Struct, If, Switch, Const
|
||||
from construct import Optional as COptional
|
||||
from osmocom.construct import *
|
||||
|
||||
from pySim.filesystem import *
|
||||
from pySim.construct import *
|
||||
from pySim.ts_31_102 import SUCI_TlvDataObject
|
||||
from pySim.apdu import ApduCommand, ApduCommandSet
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ Support for the Secure Element Access Control, specifically the ARA-M inside an
|
||||
|
||||
from construct import GreedyBytes, GreedyString, Struct, Enum, Int8ub, Int16ub
|
||||
from construct import Optional as COptional
|
||||
from pySim.construct import *
|
||||
from osmocom.construct import *
|
||||
from osmocom.tlv import *
|
||||
from osmocom.utils import Hexstr
|
||||
from pySim.filesystem import *
|
||||
from pySim.tlv import *
|
||||
from pySim.utils import Hexstr
|
||||
import pySim.global_platform
|
||||
|
||||
# various BER-TLV encoded Data Objects (DOs)
|
||||
|
||||
@@ -30,7 +30,7 @@ operation with pySim-shell.
|
||||
|
||||
from typing import List, Dict, Optional
|
||||
from Cryptodome.Cipher import AES
|
||||
from pySim.utils import h2b, b2h
|
||||
from osmocom.utils import h2b, b2h
|
||||
|
||||
import abc
|
||||
import csv
|
||||
|
||||
@@ -23,10 +23,11 @@
|
||||
#
|
||||
|
||||
from typing import Optional, Tuple
|
||||
from osmocom.utils import *
|
||||
|
||||
from pySim.ts_102_221 import EF_DIR, CardProfileUICC
|
||||
from pySim.ts_51_011 import DF_GSM
|
||||
|
||||
from pySim.utils import *
|
||||
from pySim.utils import SwHexstr
|
||||
from pySim.commands import Path, SimCardCommands
|
||||
|
||||
class CardBase:
|
||||
|
||||
@@ -23,9 +23,10 @@ from bidict import bidict
|
||||
from construct import Int8ub, Int16ub, Byte, Bytes, BitsInteger
|
||||
from construct import Struct, Enum, BitStruct, this
|
||||
from construct import GreedyBytes, Switch, GreedyRange, FlagsEnum
|
||||
from pySim.tlv import TLV_IE, COMPR_TLV_IE, BER_TLV_IE, TLV_IE_Collection
|
||||
from pySim.construct import PlmnAdapter, BcdAdapter, HexAdapter, GsmStringAdapter, TonNpi, GsmString
|
||||
from pySim.utils import b2h, dec_xplmn_w_act
|
||||
from osmocom.tlv import TLV_IE, COMPR_TLV_IE, BER_TLV_IE, TLV_IE_Collection
|
||||
from osmocom.construct import PlmnAdapter, BcdAdapter, HexAdapter, GsmStringAdapter, TonNpi, GsmString
|
||||
from osmocom.utils import b2h
|
||||
from pySim.utils import dec_xplmn_w_act
|
||||
|
||||
# Tag values as per TS 101 220 Table 7.23
|
||||
|
||||
|
||||
@@ -20,15 +20,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import enum
|
||||
|
||||
from construct import Bytewise, BitStruct, BitsInteger, Struct, FlagsEnum
|
||||
from osmocom.utils import *
|
||||
from osmocom.construct import *
|
||||
|
||||
from pySim.utils import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.profile import match_ruim
|
||||
from pySim.profile import CardProfile, CardProfileAddon
|
||||
from pySim.ts_51_011 import CardProfileSIM
|
||||
from pySim.ts_51_011 import DF_TELECOM, DF_GSM
|
||||
from pySim.ts_51_011 import EF_ServiceTable
|
||||
from pySim.construct import *
|
||||
|
||||
|
||||
# Mapping between CDMA Service Number and its description
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
|
||||
from typing import List, Tuple
|
||||
import typing # construct also has a Union, so we do typing.Union below
|
||||
|
||||
from construct import Construct, Struct, Const, Select
|
||||
from construct import Optional as COptional
|
||||
from pySim.construct import LV, filter_dict
|
||||
from pySim.utils import rpad, lpad, b2h, h2b, sw_match, bertlv_encode_len, h2i, i2h, str_sanitize, expand_hex, SwMatchstr
|
||||
from pySim.utils import Hexstr, SwHexstr, ResTuple
|
||||
from osmocom.construct import LV, filter_dict
|
||||
from osmocom.utils import rpad, lpad, b2h, h2b, h2i, i2h, str_sanitize, Hexstr
|
||||
from osmocom.tlv import bertlv_encode_len
|
||||
|
||||
from pySim.utils import sw_match, expand_hex, SwHexstr, ResTuple, SwMatchstr
|
||||
from pySim.exceptions import SwMatchError
|
||||
from pySim.transport import LinkBase
|
||||
|
||||
|
||||
@@ -1,587 +0,0 @@
|
||||
"""Utility code related to the integration of the 'construct' declarative parser."""
|
||||
|
||||
import typing
|
||||
import codecs
|
||||
import ipaddress
|
||||
|
||||
import gsm0338
|
||||
|
||||
from construct.lib.containers import Container, ListContainer
|
||||
from construct.core import EnumIntegerString
|
||||
from construct import Adapter, Prefixed, Int8ub, GreedyBytes, Default, Flag, Byte, Construct, Enum
|
||||
from construct import BitsInteger, BitStruct, Bytes, StreamError, stream_read_entire, stream_write
|
||||
from construct import SizeofError, IntegerError, swapbytes
|
||||
from construct.core import evaluate
|
||||
from construct.lib import integertypes
|
||||
|
||||
from pySim.utils import b2h, h2b, swap_nibbles
|
||||
|
||||
# (C) 2021-2022 by 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/>.
|
||||
|
||||
|
||||
class HexAdapter(Adapter):
|
||||
"""convert a bytes() type to a string of hex nibbles."""
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
return b2h(obj)
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return h2b(obj)
|
||||
|
||||
class Utf8Adapter(Adapter):
|
||||
"""convert a bytes() type that contains utf8 encoded text to human readable text."""
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
# In case the string contains only 0xff bytes we interpret it as an empty string
|
||||
if obj == b'\xff' * len(obj):
|
||||
return ""
|
||||
return codecs.decode(obj, "utf-8")
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return codecs.encode(obj, "utf-8")
|
||||
|
||||
class GsmOrUcs2Adapter(Adapter):
|
||||
"""Try to encode into a GSM 03.38 string; if that fails, fall back to UCS-2 as described
|
||||
in TS 102 221 Annex A."""
|
||||
def _decode(self, obj, context, path):
|
||||
# In case the string contains only 0xff bytes we interpret it as an empty string
|
||||
if obj == b'\xff' * len(obj):
|
||||
return ""
|
||||
# one of the magic bytes of TS 102 221 Annex A
|
||||
if obj[0] in [0x80, 0x81, 0x82]:
|
||||
ad = Ucs2Adapter(GreedyBytes)
|
||||
else:
|
||||
ad = GsmString(GreedyBytes)
|
||||
return ad._decode(obj, context, path)
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
# first try GSM 03.38; then fall back to TS 102 221 Annex A UCS-2
|
||||
try:
|
||||
ad = GsmString(GreedyBytes)
|
||||
return ad._encode(obj, context, path)
|
||||
except:
|
||||
ad = Ucs2Adapter(GreedyBytes)
|
||||
return ad._encode(obj, context, path)
|
||||
|
||||
class Ucs2Adapter(Adapter):
|
||||
"""convert a bytes() type that contains UCS2 encoded characters encoded as defined in TS 102 221
|
||||
Annex A to normal python string representation (and back)."""
|
||||
def _decode(self, obj, context, path):
|
||||
# In case the string contains only 0xff bytes we interpret it as an empty string
|
||||
if obj == b'\xff' * len(obj):
|
||||
return ""
|
||||
if obj[0] == 0x80:
|
||||
# TS 102 221 Annex A Variant 1
|
||||
return codecs.decode(obj[1:], 'utf_16_be')
|
||||
elif obj[0] == 0x81:
|
||||
# TS 102 221 Annex A Variant 2
|
||||
out = ""
|
||||
# second byte contains a value indicating the number of characters
|
||||
num_of_chars = obj[1]
|
||||
# the third byte contains an 8 bit number which defines bits 15 to 8 of a 16 bit base
|
||||
# pointer, where bit 16 is set to zero, and bits 7 to 1 are also set to zero. These
|
||||
# sixteen bits constitute a base pointer to a "half-page" in the UCS2 code space
|
||||
base_ptr = obj[2] << 7
|
||||
for ch in obj[3:3+num_of_chars]:
|
||||
# if bit 8 of the byte is set to zero, the remaining 7 bits of the byte contain a
|
||||
# GSM Default Alphabet character, whereas if bit 8 of the byte is set to one, then
|
||||
# the remaining seven bits are an offset value added to the 16 bit base pointer
|
||||
# defined earlier, and the resultant 16 bit value is a UCS2 code point
|
||||
if ch & 0x80:
|
||||
codepoint = (ch & 0x7f) + base_ptr
|
||||
out += codecs.decode(codepoint.to_bytes(2, byteorder='big'), 'utf_16_be')
|
||||
else:
|
||||
out += codecs.decode(bytes([ch]), 'gsm03.38')
|
||||
return out
|
||||
elif obj[0] == 0x82:
|
||||
# TS 102 221 Annex A Variant 3
|
||||
out = ""
|
||||
# second byte contains a value indicating the number of characters
|
||||
num_of_chars = obj[1]
|
||||
# third and fourth bytes contain a 16 bit number which defines the complete 16 bit base
|
||||
# pointer to a half-page in the UCS2 code space, for use with some or all of the
|
||||
# remaining bytes in the string
|
||||
base_ptr = obj[2] << 8 | obj[3]
|
||||
for ch in obj[4:4+num_of_chars]:
|
||||
# if bit 8 of the byte is set to zero, the remaining 7 bits of the byte contain a
|
||||
# GSM Default Alphabet character, whereas if bit 8 of the byte is set to one, the
|
||||
# remaining seven bits are an offset value added to the base pointer defined in
|
||||
# bytes three and four, and the resultant 16 bit value is a UCS2 code point, else: #
|
||||
# GSM default alphabet
|
||||
if ch & 0x80:
|
||||
codepoint = (ch & 0x7f) + base_ptr
|
||||
out += codecs.decode(codepoint.to_bytes(2, byteorder='big'), 'utf_16_be')
|
||||
else:
|
||||
out += codecs.decode(bytes([ch]), 'gsm03.38')
|
||||
return out
|
||||
else:
|
||||
raise ValueError('First byte of TS 102 221 UCS-2 must be 0x80, 0x81 or 0x82')
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
def encodable_in_gsm338(instr: str) -> bool:
|
||||
"""Determine if given input string is encode-ale in gsm03.38."""
|
||||
try:
|
||||
# TODO: figure out if/how we can constrain to default alphabet. The gsm0338
|
||||
# library seems to include the spanish lock/shift table
|
||||
codecs.encode(instr, 'gsm03.38')
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def codepoints_not_in_gsm338(instr: str) -> typing.List[int]:
|
||||
"""Return an integer list of UCS2 codepoints for all characters of 'inster'
|
||||
which are not representable in the GSM 03.38 default alphabet."""
|
||||
codepoint_list = []
|
||||
for c in instr:
|
||||
if encodable_in_gsm338(c):
|
||||
continue
|
||||
c_codepoint = int.from_bytes(codecs.encode(c, 'utf_16_be'), byteorder='big')
|
||||
codepoint_list.append(c_codepoint)
|
||||
return codepoint_list
|
||||
|
||||
def diff_between_min_and_max_of_list(inlst: typing.List) -> int:
|
||||
return max(inlst) - min(inlst)
|
||||
|
||||
def encodable_in_variant2(instr: str) -> bool:
|
||||
codepoint_prefix = None
|
||||
for c in instr:
|
||||
if encodable_in_gsm338(c):
|
||||
continue
|
||||
c_codepoint = int.from_bytes(codecs.encode(c, 'utf_16_be'), byteorder='big')
|
||||
if c_codepoint >= 0x8000:
|
||||
return False
|
||||
c_prefix = c_codepoint >> 7
|
||||
if codepoint_prefix is None:
|
||||
codepoint_prefix = c_prefix
|
||||
else:
|
||||
if c_prefix != codepoint_prefix:
|
||||
return False
|
||||
return True
|
||||
|
||||
def encodable_in_variant3(instr: str) -> bool:
|
||||
codepoint_list = codepoints_not_in_gsm338(instr)
|
||||
# compute delta between max and min; check if it's encodable in 7 bits
|
||||
if diff_between_min_and_max_of_list(codepoint_list) >= 0x80:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _encode_variant1(instr: str) -> bytes:
|
||||
"""Encode according to TS 102 221 Annex A Variant 1"""
|
||||
return b'\x80' + codecs.encode(instr, 'utf_16_be')
|
||||
|
||||
def _encode_variant2(instr: str) -> bytes:
|
||||
"""Encode according to TS 102 221 Annex A Variant 2"""
|
||||
codepoint_prefix = None
|
||||
# second byte contains a value indicating the number of characters
|
||||
hdr = b'\x81' + len(instr).to_bytes(1, byteorder='big')
|
||||
chars = b''
|
||||
for c in instr:
|
||||
try:
|
||||
enc = codecs.encode(c, 'gsm03.38')
|
||||
except ValueError:
|
||||
c_codepoint = int.from_bytes(codecs.encode(c, 'utf_16_be'), byteorder='big')
|
||||
c_prefix = c_codepoint >> 7
|
||||
if codepoint_prefix is None:
|
||||
codepoint_prefix = c_prefix
|
||||
assert codepoint_prefix == c_prefix
|
||||
enc = (0x80 + (c_codepoint & 0x7f)).to_bytes(1, byteorder='big')
|
||||
chars += enc
|
||||
if codepoint_prefix is None:
|
||||
codepoint_prefix = 0
|
||||
return hdr + codepoint_prefix.to_bytes(1, byteorder='big') + chars
|
||||
|
||||
def _encode_variant3(instr: str) -> bytes:
|
||||
"""Encode according to TS 102 221 Annex A Variant 3"""
|
||||
# second byte contains a value indicating the number of characters
|
||||
hdr = b'\x82' + len(instr).to_bytes(1, byteorder='big')
|
||||
chars = b''
|
||||
codepoint_list = codepoints_not_in_gsm338(instr)
|
||||
codepoint_base = min(codepoint_list)
|
||||
for c in instr:
|
||||
try:
|
||||
# if bit 8 of the byte is set to zero, the remaining 7 bits of the byte contain a GSM
|
||||
# Default # Alphabet character
|
||||
enc = codecs.encode(c, 'gsm03.38')
|
||||
except ValueError:
|
||||
# if bit 8 of the byte is set to one, the remaining seven bits are an offset
|
||||
# value added to the base pointer defined in bytes three and four, and the
|
||||
# resultant 16 bit value is a UCS2 code point
|
||||
c_codepoint = int.from_bytes(codecs.encode(c, 'utf_16_be'), byteorder='big')
|
||||
c_codepoint_delta = c_codepoint - codepoint_base
|
||||
assert c_codepoint_delta < 0x80
|
||||
enc = (0x80 + c_codepoint_delta).to_bytes(1, byteorder='big')
|
||||
chars += enc
|
||||
# third and fourth bytes contain a 16 bit number which defines the complete 16 bit base
|
||||
# pointer to a half-page in the UCS2 code space
|
||||
return hdr + codepoint_base.to_bytes(2, byteorder='big') + chars
|
||||
|
||||
if encodable_in_variant2(obj):
|
||||
return _encode_variant2(obj)
|
||||
elif encodable_in_variant3(obj):
|
||||
return _encode_variant3(obj)
|
||||
else:
|
||||
return _encode_variant1(obj)
|
||||
|
||||
class BcdAdapter(Adapter):
|
||||
"""convert a bytes() type to a string of BCD nibbles."""
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
return swap_nibbles(b2h(obj))
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return h2b(swap_nibbles(obj))
|
||||
|
||||
class PlmnAdapter(BcdAdapter):
|
||||
"""convert a bytes(3) type to BCD string like 262-02 or 262-002."""
|
||||
def _decode(self, obj, context, path):
|
||||
bcd = super()._decode(obj, context, path)
|
||||
if bcd[3] == 'f':
|
||||
return '-'.join([bcd[:3], bcd[4:]])
|
||||
else:
|
||||
return '-'.join([bcd[:3], bcd[3:]])
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
l = obj.split('-')
|
||||
if len(l[1]) == 2:
|
||||
bcd = l[0] + 'f' + l[1]
|
||||
else:
|
||||
bcd = l[0] + l[1]
|
||||
return super()._encode(bcd, context, path)
|
||||
|
||||
class InvertAdapter(Adapter):
|
||||
"""inverse logic (false->true, true->false)."""
|
||||
@staticmethod
|
||||
def _invert_bool_in_obj(obj):
|
||||
for k,v in obj.items():
|
||||
# skip all private entries
|
||||
if k.startswith('_'):
|
||||
continue
|
||||
if v is False:
|
||||
obj[k] = True
|
||||
elif v is True:
|
||||
obj[k] = False
|
||||
return obj
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
return self._invert_bool_in_obj(obj)
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return self._invert_bool_in_obj(obj)
|
||||
|
||||
class Rpad(Adapter):
|
||||
"""
|
||||
Encoder appends padding bytes (b'\\xff') or characters up to target size.
|
||||
Decoder removes trailing padding bytes/characters.
|
||||
|
||||
Parameters:
|
||||
subcon: Subconstruct as defined by construct library
|
||||
pattern: set padding pattern (default: b'\\xff')
|
||||
num_per_byte: number of 'elements' per byte. E.g. for hex nibbles: 2
|
||||
"""
|
||||
|
||||
def __init__(self, subcon, pattern=b'\xff', num_per_byte=1):
|
||||
super().__init__(subcon)
|
||||
self.pattern = pattern
|
||||
self.num_per_byte = num_per_byte
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
return obj.rstrip(self.pattern)
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
target_size = self.sizeof() * self.num_per_byte
|
||||
if len(obj) > target_size:
|
||||
raise SizeofError("Input ({}) exceeds target size ({})".format(
|
||||
len(obj), target_size))
|
||||
return obj + self.pattern * (target_size - len(obj))
|
||||
|
||||
class MultiplyAdapter(Adapter):
|
||||
"""
|
||||
Decoder multiplies by multiplicator
|
||||
Encoder divides by multiplicator
|
||||
|
||||
Parameters:
|
||||
subcon: Subconstruct as defined by construct library
|
||||
multiplier: Multiplier to apply to raw encoded value
|
||||
"""
|
||||
|
||||
def __init__(self, subcon, multiplicator):
|
||||
super().__init__(subcon)
|
||||
self.multiplicator = multiplicator
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
return obj * 8
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return obj // 8
|
||||
|
||||
|
||||
class GsmStringAdapter(Adapter):
|
||||
"""Convert GSM 03.38 encoded bytes to a string."""
|
||||
|
||||
def __init__(self, subcon, codec='gsm03.38', err='strict'):
|
||||
super().__init__(subcon)
|
||||
self.codec = codec
|
||||
self.err = err
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
return obj.decode(self.codec)
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
return obj.encode(self.codec, self.err)
|
||||
|
||||
class Ipv4Adapter(Adapter):
|
||||
"""
|
||||
Encoder converts from 4 bytes to string representation (A.B.C.D).
|
||||
Decoder converts from string representation (A.B.C.D) to four bytes.
|
||||
"""
|
||||
def _decode(self, obj, context, path):
|
||||
ia = ipaddress.IPv4Address(obj)
|
||||
return ia.compressed
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
ia = ipaddress.IPv4Address(obj)
|
||||
return ia.packed
|
||||
|
||||
class Ipv6Adapter(Adapter):
|
||||
"""
|
||||
Encoder converts from 16 bytes to string representation.
|
||||
Decoder converts from string representation to 16 bytes.
|
||||
"""
|
||||
def _decode(self, obj, context, path):
|
||||
ia = ipaddress.IPv6Address(obj)
|
||||
return ia.compressed
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
ia = ipaddress.IPv6Address(obj)
|
||||
return ia.packed
|
||||
|
||||
class StripTrailerAdapter(Adapter):
|
||||
"""
|
||||
Encoder removes all trailing bytes matching the default_value
|
||||
Decoder pads input data up to total_length with default_value
|
||||
|
||||
This is used in constellations like "FlagsEnum(StripTrailerAdapter(GreedyBytes, 3), ..."
|
||||
where you have a bit-mask that may have 1, 2 or 3 bytes, depending on whether or not any
|
||||
of the LSBs are actually set.
|
||||
"""
|
||||
def __init__(self, subcon, total_length:int, default_value=b'\x00', min_len=1):
|
||||
super().__init__(subcon)
|
||||
assert len(default_value) == 1
|
||||
self.total_length = total_length
|
||||
self.default_value = default_value
|
||||
self.min_len = min_len
|
||||
|
||||
def _decode(self, obj, context, path):
|
||||
assert isinstance(obj, bytes)
|
||||
# pad with suppressed/missing bytes
|
||||
if len(obj) < self.total_length:
|
||||
obj += self.default_value * (self.total_length - len(obj))
|
||||
return int.from_bytes(obj, 'big')
|
||||
|
||||
def _encode(self, obj, context, path):
|
||||
assert isinstance(obj, int)
|
||||
obj = obj.to_bytes(self.total_length, 'big')
|
||||
# remove trailing bytes if they are zero
|
||||
while len(obj) > self.min_len and obj[-1] == self.default_value[0]:
|
||||
obj = obj[:-1]
|
||||
return obj
|
||||
|
||||
|
||||
def filter_dict(d, exclude_prefix='_'):
|
||||
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
|
||||
if not isinstance(d, dict):
|
||||
return d
|
||||
res = {}
|
||||
for (key, value) in d.items():
|
||||
if key.startswith(exclude_prefix):
|
||||
continue
|
||||
if isinstance(value, dict):
|
||||
res[key] = filter_dict(value)
|
||||
else:
|
||||
res[key] = value
|
||||
return res
|
||||
|
||||
|
||||
def normalize_construct(c, exclude_prefix: str = '_'):
|
||||
"""Convert a construct specific type to a related base type, mostly useful
|
||||
so we can serialize it."""
|
||||
# we need to include the filter_dict as we otherwise get elements like this
|
||||
# in the dict: '_io': <_io.BytesIO object at 0x7fdb64e05860> which we cannot json-serialize
|
||||
c = filter_dict(c, exclude_prefix)
|
||||
if isinstance(c, (Container, dict)):
|
||||
r = {k: normalize_construct(v) for (k, v) in c.items()}
|
||||
elif isinstance(c, ListContainer):
|
||||
r = [normalize_construct(x) for x in c]
|
||||
elif isinstance(c, list):
|
||||
r = [normalize_construct(x) for x in c]
|
||||
elif isinstance(c, EnumIntegerString):
|
||||
r = str(c)
|
||||
else:
|
||||
r = c
|
||||
return r
|
||||
|
||||
|
||||
def parse_construct(c, raw_bin_data: bytes, length: typing.Optional[int] = None, exclude_prefix: str = '_', context: dict = {}):
|
||||
"""Helper function to wrap around normalize_construct() and filter_dict()."""
|
||||
if not length:
|
||||
length = len(raw_bin_data)
|
||||
try:
|
||||
parsed = c.parse(raw_bin_data, total_len=length, **context)
|
||||
except StreamError as e:
|
||||
# if the input is all-ff, this means the content is undefined. Let's avoid passing StreamError
|
||||
# exceptions in those situations (which might occur if a length field 0xff is 255 but then there's
|
||||
# actually less bytes in the remainder of the file.
|
||||
if all(v == 0xff for v in raw_bin_data):
|
||||
return None
|
||||
else:
|
||||
raise e
|
||||
return normalize_construct(parsed, exclude_prefix)
|
||||
|
||||
def build_construct(c, decoded_data, context: dict = {}):
|
||||
"""Helper function to handle total_len."""
|
||||
return c.build(decoded_data, total_len=None, **context)
|
||||
|
||||
# here we collect some shared / common definitions of data types
|
||||
LV = Prefixed(Int8ub, HexAdapter(GreedyBytes))
|
||||
|
||||
# Default value for Reserved for Future Use (RFU) bits/bytes
|
||||
# See TS 31.101 Sec. "3.4 Coding Conventions"
|
||||
__RFU_VALUE = 0
|
||||
|
||||
# Field that packs Reserved for Future Use (RFU) bit
|
||||
FlagRFU = Default(Flag, __RFU_VALUE)
|
||||
|
||||
# Field that packs Reserved for Future Use (RFU) byte
|
||||
ByteRFU = Default(Byte, __RFU_VALUE)
|
||||
|
||||
# Field that packs all remaining Reserved for Future Use (RFU) bytes
|
||||
GreedyBytesRFU = Default(GreedyBytes, b'')
|
||||
|
||||
|
||||
def BitsRFU(n=1):
|
||||
'''
|
||||
Field that packs Reserved for Future Use (RFU) bit(s)
|
||||
as defined in TS 31.101 Sec. "3.4 Coding Conventions"
|
||||
|
||||
Use this for (currently) unused/reserved bits whose contents
|
||||
should be initialized automatically but should not be cleared
|
||||
in the future or when restoring read data (unlike padding).
|
||||
|
||||
Parameters:
|
||||
n (Integer): Number of bits (default: 1)
|
||||
'''
|
||||
return Default(BitsInteger(n), __RFU_VALUE)
|
||||
|
||||
|
||||
def BytesRFU(n=1):
|
||||
'''
|
||||
Field that packs Reserved for Future Use (RFU) byte(s)
|
||||
as defined in TS 31.101 Sec. "3.4 Coding Conventions"
|
||||
|
||||
Use this for (currently) unused/reserved bytes whose contents
|
||||
should be initialized automatically but should not be cleared
|
||||
in the future or when restoring read data (unlike padding).
|
||||
|
||||
Parameters:
|
||||
n (Integer): Number of bytes (default: 1)
|
||||
'''
|
||||
return Default(Bytes(n), __RFU_VALUE)
|
||||
|
||||
|
||||
def GsmString(n):
|
||||
'''
|
||||
GSM 03.38 encoded byte string of fixed length n.
|
||||
Encoder appends padding bytes (b'\\xff') to maintain
|
||||
length. Decoder removes those trailing bytes.
|
||||
|
||||
Exceptions are raised for invalid characters
|
||||
and length excess.
|
||||
|
||||
Parameters:
|
||||
n (Integer): Fixed length of the encoded byte string
|
||||
'''
|
||||
return GsmStringAdapter(Rpad(Bytes(n), pattern=b'\xff'), codec='gsm03.38')
|
||||
|
||||
def GsmOrUcs2String(n):
|
||||
'''
|
||||
GSM 03.38 or UCS-2 (TS 102 221 Annex A) encoded byte string of fixed length n.
|
||||
Encoder appends padding bytes (b'\\xff') to maintain
|
||||
length. Decoder removes those trailing bytes.
|
||||
|
||||
Exceptions are raised for invalid characters
|
||||
and length excess.
|
||||
|
||||
Parameters:
|
||||
n (Integer): Fixed length of the encoded byte string
|
||||
'''
|
||||
return GsmOrUcs2Adapter(Rpad(Bytes(n), pattern=b'\xff'))
|
||||
|
||||
class GreedyInteger(Construct):
|
||||
"""A variable-length integer implementation, think of combining GrredyBytes with BytesInteger."""
|
||||
def __init__(self, signed=False, swapped=False, minlen=0):
|
||||
super().__init__()
|
||||
self.signed = signed
|
||||
self.swapped = swapped
|
||||
self.minlen = minlen
|
||||
|
||||
def _parse(self, stream, context, path):
|
||||
data = stream_read_entire(stream, path)
|
||||
if evaluate(self.swapped, context):
|
||||
data = swapbytes(data)
|
||||
try:
|
||||
return int.from_bytes(data, byteorder='big', signed=self.signed)
|
||||
except ValueError as e:
|
||||
raise IntegerError(str(e), path=path)
|
||||
|
||||
def __bytes_required(self, i, minlen=0):
|
||||
if self.signed:
|
||||
raise NotImplementedError("FIXME: Implement support for encoding signed integer")
|
||||
|
||||
# compute how many bytes we need
|
||||
nbytes = 1
|
||||
while True:
|
||||
i = i >> 8
|
||||
if i == 0:
|
||||
break
|
||||
else:
|
||||
nbytes = nbytes + 1
|
||||
|
||||
# round up to the minimum number
|
||||
# of bytes we anticipate
|
||||
nbytes = max(nbytes, minlen)
|
||||
|
||||
return nbytes
|
||||
|
||||
def _build(self, obj, stream, context, path):
|
||||
if not isinstance(obj, integertypes):
|
||||
raise IntegerError(f"value {obj} is not an integer", path=path)
|
||||
length = self.__bytes_required(obj, self.minlen)
|
||||
try:
|
||||
data = obj.to_bytes(length, byteorder='big', signed=self.signed)
|
||||
except ValueError as e:
|
||||
raise IntegerError(str(e), path=path) from e
|
||||
if evaluate(self.swapped, context):
|
||||
data = swapbytes(data)
|
||||
stream_write(stream, data, length, path)
|
||||
return obj
|
||||
|
||||
# merged definitions of 24.008 + 23.040
|
||||
TypeOfNumber = Enum(BitsInteger(3), unknown=0, international=1, national=2, network_specific=3,
|
||||
short_code=4, alphanumeric=5, abbreviated=6, reserved_for_extension=7)
|
||||
NumberingPlan = Enum(BitsInteger(4), unknown=0, isdn_e164=1, data_x121=3, telex_f69=4,
|
||||
sc_specific_5=5, sc_specific_6=6, national=8, private=9,
|
||||
ermes=10, reserved_cts=11, reserved_for_extension=15)
|
||||
TonNpi = BitStruct('ext'/Flag, 'type_of_number'/TypeOfNumber, 'numbering_plan_id'/NumberingPlan)
|
||||
@@ -35,7 +35,8 @@ from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Hash import CMAC
|
||||
|
||||
from pySim.utils import bertlv_encode_len, bertlv_parse_one, b2h
|
||||
from osmocom.utils import b2h
|
||||
from osmocom.tlv import bertlv_encode_len, bertlv_parse_one
|
||||
|
||||
# don't log by default
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
|
||||
from pySim.utils import b2h, h2b, bertlv_encode_tag, bertlv_encode_len, bertlv_parse_one_rawtag
|
||||
from pySim.utils import bertlv_return_one_rawtlv
|
||||
from osmocom.utils import b2h, h2b
|
||||
from osmocom.tlv import bertlv_encode_tag, bertlv_encode_len, bertlv_parse_one_rawtag
|
||||
from osmocom.tlv import bertlv_return_one_rawtlv
|
||||
|
||||
import pySim.esim.rsp as rsp
|
||||
from pySim.esim.bsp import BspInstance
|
||||
|
||||
@@ -23,8 +23,9 @@ import shelve
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
from cryptography import x509
|
||||
from osmocom.utils import b2h
|
||||
from osmocom.tlv import bertlv_parse_one_rawtag, bertlv_return_one_rawtlv
|
||||
|
||||
from pySim.utils import bertlv_parse_one_rawtag, bertlv_return_one_rawtlv, b2h
|
||||
from pySim.esim import compile_asn1_subdir
|
||||
|
||||
asn1 = compile_asn1_subdir('rsp')
|
||||
|
||||
@@ -20,19 +20,19 @@ import abc
|
||||
import io
|
||||
from typing import Tuple, List, Optional, Dict, Union
|
||||
from collections import OrderedDict
|
||||
|
||||
import asn1tools
|
||||
from osmocom.utils import b2h, h2b, Hexstr
|
||||
from osmocom.tlv import BER_TLV_IE, bertlv_parse_tag, bertlv_parse_len
|
||||
from osmocom.construct import build_construct, parse_construct, GreedyInteger
|
||||
|
||||
from pySim.utils import bertlv_parse_tag, bertlv_parse_len, b2h, h2b, dec_imsi, Hexstr
|
||||
from pySim.utils import dec_imsi
|
||||
from pySim.ts_102_221 import FileDescriptor
|
||||
from pySim.filesystem import CardADF, Path
|
||||
from pySim.ts_31_102 import ADF_USIM
|
||||
from pySim.ts_31_103 import ADF_ISIM
|
||||
from pySim.construct import build_construct, parse_construct, GreedyInteger
|
||||
from pySim.esim import compile_asn1_subdir
|
||||
from pySim.esim.saip import templates
|
||||
from pySim.esim.saip import oid
|
||||
from pySim.tlv import BER_TLV_IE
|
||||
from pySim.global_platform import KeyType, KeyUsageQualifier
|
||||
from pySim.global_platform.uicc import UiccSdInstallParams
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import abc
|
||||
import io
|
||||
from typing import List, Tuple
|
||||
|
||||
from pySim.tlv import camel_to_snake
|
||||
from osmocom.tlv import camel_to_snake
|
||||
from pySim.utils import enc_iccid, enc_imsi, h2b, rpad, sanitize_iccid
|
||||
from pySim.esim.saip import ProfileElement, ProfileElementSequence
|
||||
|
||||
|
||||
@@ -27,11 +27,12 @@ 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.tlv import *
|
||||
from pySim.construct import *
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.utils import Hexstr, SwHexstr, SwMatchstr
|
||||
from pySim.commands import SimCardCommands
|
||||
import pySim.global_platform
|
||||
|
||||
def compute_eid_checksum(eid) -> str:
|
||||
|
||||
@@ -35,10 +35,11 @@ import cmd2
|
||||
from cmd2 import CommandSet, with_default_category
|
||||
from smartcard.util import toBytes
|
||||
|
||||
from pySim.utils import sw_match, h2b, b2h, is_hex, auto_int, auto_uint8, auto_uint16, is_hexstr, JsonEncoder
|
||||
from pySim.utils import bertlv_parse_one
|
||||
from osmocom.utils import h2b, b2h, is_hex, auto_int, auto_uint8, auto_uint16, is_hexstr, JsonEncoder
|
||||
from osmocom.tlv import bertlv_parse_one
|
||||
from osmocom.construct import filter_dict, parse_construct, build_construct
|
||||
|
||||
from pySim.construct import filter_dict, parse_construct, build_construct
|
||||
from pySim.utils import sw_match
|
||||
from pySim.jsonpath import js_path_modify
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.exceptions import SwMatchError
|
||||
|
||||
@@ -23,12 +23,13 @@ from construct import Optional as COptional
|
||||
from construct import Struct, GreedyRange, FlagsEnum, Int16ub, Int24ub, Padding, Bit, Const
|
||||
from Cryptodome.Random import get_random_bytes
|
||||
from Cryptodome.Cipher import DES, DES3, AES
|
||||
from osmocom.utils import *
|
||||
from osmocom.tlv import *
|
||||
from osmocom.construct import *
|
||||
from pySim.utils import ResTuple
|
||||
from pySim.card_key_provider import card_key_provider_get_field
|
||||
from pySim.global_platform.scp import SCP02, SCP03
|
||||
from pySim.construct import *
|
||||
from pySim.utils import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.tlv import *
|
||||
from pySim.profile import CardProfile
|
||||
from pySim.ota import SimFileAccessAndToolkitAppSpecParams
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ Also known as SCP81 for SIM/USIM/UICC/eUICC/eSIM OTA.
|
||||
from construct import Struct, Int8ub, Int16ub, Bytes, GreedyBytes, GreedyString, BytesInteger
|
||||
from construct import this, len_, Rebuild, Const
|
||||
from construct import Optional as COptional
|
||||
from osmocom.tlv import BER_TLV_IE
|
||||
|
||||
from pySim.tlv import BER_TLV_IE
|
||||
from pySim import cat
|
||||
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ from Cryptodome.Cipher import DES3, DES
|
||||
from Cryptodome.Util.strxor import strxor
|
||||
from construct import Struct, Bytes, Int8ub, Int16ub, Const
|
||||
from construct import Optional as COptional
|
||||
from pySim.utils import b2h, bertlv_parse_len, bertlv_encode_len
|
||||
from osmocom.utils import b2h
|
||||
from osmocom.tlv import bertlv_parse_len, bertlv_encode_len
|
||||
|
||||
from pySim.secure_channel import SecureChannel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,9 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from construct import Optional as COptional
|
||||
from construct import Struct, GreedyRange, FlagsEnum, Int16ub, Int24ub, Padding, Bit, Const
|
||||
from pySim.construct import *
|
||||
from pySim.utils import *
|
||||
from pySim.tlv import *
|
||||
from osmocom.construct import *
|
||||
from osmocom.utils import *
|
||||
from osmocom.tlv import *
|
||||
|
||||
# Section 11.6.2.3 / Table 11-58
|
||||
class SecurityDomainAid(BER_TLV_IE, tag=0x4f):
|
||||
|
||||
@@ -28,8 +28,8 @@ from pySim.utils import *
|
||||
from struct import pack, unpack
|
||||
from construct import Struct, Bytes, Int8ub, Int16ub, Int24ub, Int32ub, FlagsEnum
|
||||
from construct import Optional as COptional
|
||||
from osmocom.construct import *
|
||||
|
||||
from pySim.construct import *
|
||||
from pySim.profile import CardProfileAddon
|
||||
from pySim.filesystem import *
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ telecom-related protocol traces over UDP.
|
||||
import socket
|
||||
from construct import Optional as COptional
|
||||
from construct import Int8ub, Int8sb, Int32ub, BitStruct, Enum, GreedyBytes, Struct, Switch
|
||||
from construct import this, PaddedString
|
||||
from pySim.construct import *
|
||||
from construct import this, PaddedString, Flag, BitsInteger, Bytes
|
||||
from osmocom.construct import *
|
||||
|
||||
# The root definition of GSMTAP can be found at
|
||||
# https://cgit.osmocom.org/cgit/libosmocore/tree/include/osmocom/core/gsmtap.h
|
||||
|
||||
@@ -18,10 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from construct import GreedyBytes, GreedyString
|
||||
from pySim.construct import *
|
||||
from pySim.utils import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.tlv import *
|
||||
from osmocom.tlv import *
|
||||
from osmocom.construct import *
|
||||
|
||||
# Table 91 + Section 8.2.1.2
|
||||
class ApplicationId(BER_TLV_IE, tag=0x4f):
|
||||
|
||||
@@ -21,9 +21,9 @@ import struct
|
||||
from typing import Optional, Tuple
|
||||
from construct import Enum, Int8ub, Int16ub, Struct, Bytes, GreedyBytes, BitsInteger, BitStruct
|
||||
from construct import Flag, Padding, Switch, this, PrefixedArray, GreedyRange
|
||||
from osmocom.construct import *
|
||||
from osmocom.utils import b2h
|
||||
|
||||
from pySim.construct import *
|
||||
from pySim.utils import b2h
|
||||
from pySim.sms import UserDataHeader
|
||||
|
||||
# ETS TS 102 225 gives the general command structure and the dialects for CAT_TP, TCP/IP and HTTPS
|
||||
|
||||
@@ -18,8 +18,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional, Tuple
|
||||
from osmocom.utils import h2b, i2h, is_hex, Hexstr
|
||||
from osmocom.tlv import bertlv_parse_one
|
||||
|
||||
from pySim.utils import h2b, i2h, is_hex, bertlv_parse_one, Hexstr
|
||||
from pySim.exceptions import *
|
||||
from pySim.filesystem import *
|
||||
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import abc
|
||||
from pySim.utils import b2h, h2b, ResTuple, Hexstr
|
||||
from osmocom.utils import b2h, h2b, Hexstr
|
||||
|
||||
from pySim.utils import ResTuple
|
||||
|
||||
class SecureChannel(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
|
||||
@@ -23,9 +23,8 @@ from bidict import bidict
|
||||
from construct import Int8ub, Byte, Bytes, Bit, Flag, BitsInteger
|
||||
from construct import Struct, Enum, Tell, BitStruct, this, Padding
|
||||
from construct import Prefixed, GreedyRange, GreedyBytes
|
||||
|
||||
from pySim.construct import HexAdapter, BcdAdapter, TonNpi
|
||||
from pySim.utils import Hexstr, h2b, b2h
|
||||
from osmocom.construct import HexAdapter, BcdAdapter, TonNpi
|
||||
from osmocom.utils import Hexstr, h2b, b2h
|
||||
|
||||
from smpp.pdu import pdu_types, operations
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ from struct import unpack
|
||||
from construct import FlagsEnum, Byte, Struct, Int8ub, Bytes, Mapping, Enum, Padding, BitsInteger
|
||||
from construct import Bit, this, Int32ub, Int16ub, Nibble, BytesInteger, GreedyRange, Const
|
||||
from construct import Optional as COptional
|
||||
from osmocom.utils import *
|
||||
from osmocom.construct import *
|
||||
|
||||
from pySim.utils import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.runtime import RuntimeState
|
||||
from pySim.construct import *
|
||||
import pySim
|
||||
|
||||
key_type2str = {
|
||||
|
||||
553
pySim/tlv.py
553
pySim/tlv.py
@@ -1,553 +0,0 @@
|
||||
"""object-oriented TLV parser/encoder library."""
|
||||
|
||||
# (C) 2021 by Harald Welte <laforge@osmocom.org>
|
||||
# All Rights Reserved
|
||||
#
|
||||
# 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 inspect
|
||||
import abc
|
||||
import re
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
from pySim.utils import bertlv_encode_len, bertlv_parse_len, bertlv_encode_tag, bertlv_parse_tag
|
||||
from pySim.utils import comprehensiontlv_encode_tag, comprehensiontlv_parse_tag
|
||||
from pySim.utils import bertlv_parse_tag_raw, comprehensiontlv_parse_tag_raw
|
||||
from pySim.utils import dgi_parse_tag_raw, dgi_parse_len, dgi_encode_tag, dgi_encode_len
|
||||
|
||||
from pySim.construct import build_construct, parse_construct
|
||||
|
||||
|
||||
def camel_to_snake(name):
|
||||
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
|
||||
|
||||
class TlvMeta(abc.ABCMeta):
|
||||
"""Metaclass which we use to set some class variables at the time of defining a subclass.
|
||||
This allows us to create subclasses for each TLV/IE type, where the class represents fixed
|
||||
parameters like the tag/type and instances of it represent the actual TLV data."""
|
||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||
#print("TlvMeta_new_(mcs=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (mcs, name, bases, namespace, kwargs))
|
||||
x = super().__new__(mcs, name, bases, namespace)
|
||||
# this becomes a _class_ variable, not an instance variable
|
||||
x.tag = namespace.get('tag', kwargs.get('tag', None))
|
||||
x.desc = namespace.get('desc', kwargs.get('desc', None))
|
||||
nested = namespace.get('nested', kwargs.get('nested', None))
|
||||
if nested is None or inspect.isclass(nested) and issubclass(nested, TLV_IE_Collection):
|
||||
# caller has specified TLV_IE_Collection sub-class, we can directly reference it
|
||||
x.nested_collection_cls = nested
|
||||
else:
|
||||
# caller passed list of other TLV classes that might possibly appear within us,
|
||||
# build a dynamically-created TLV_IE_Collection sub-class and reference it
|
||||
name = 'auto_collection_%s' % (name)
|
||||
cls = type(name, (TLV_IE_Collection,), {'nested': nested})
|
||||
x.nested_collection_cls = cls
|
||||
return x
|
||||
|
||||
|
||||
class TlvCollectionMeta(abc.ABCMeta):
|
||||
"""Metaclass which we use to set some class variables at the time of defining a subclass.
|
||||
This allows us to create subclasses for each Collection type, where the class represents fixed
|
||||
parameters like the nested IE classes and instances of it represent the actual TLV data."""
|
||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||
#print("TlvCollectionMeta_new_(mcs=%s, name=%s, bases=%s, namespace=%s, kwargs=%s)" % (mcs, name, bases, namespace, kwargs))
|
||||
x = super().__new__(mcs, name, bases, namespace)
|
||||
# this becomes a _class_ variable, not an instance variable
|
||||
x.possible_nested = namespace.get('nested', kwargs.get('nested', None))
|
||||
return x
|
||||
|
||||
|
||||
class Transcodable(abc.ABC):
|
||||
_construct = None
|
||||
"""Base class for something that can be encoded + encoded. Decoding and Encoding happens either
|
||||
* via a 'construct' object stored in a derived class' _construct variable, or
|
||||
* via a 'construct' object stored in an instance _construct variable, or
|
||||
* via a derived class' _{to,from}_bytes() methods."""
|
||||
|
||||
def __init__(self):
|
||||
self.encoded = None
|
||||
self.decoded = None
|
||||
self._construct = None
|
||||
|
||||
def to_bytes(self, context: dict = {}) -> bytes:
|
||||
"""Convert from internal representation to binary bytes. Store the binary result
|
||||
in the internal state and return it."""
|
||||
if self.decoded is None:
|
||||
do = b''
|
||||
elif self._construct:
|
||||
do = build_construct(self._construct, self.decoded, context)
|
||||
elif self.__class__._construct:
|
||||
do = build_construct(self.__class__._construct, self.decoded, context)
|
||||
else:
|
||||
do = self._to_bytes()
|
||||
self.encoded = do
|
||||
return do
|
||||
|
||||
# not an abstractmethod, as it is only required if no _construct exists
|
||||
def _to_bytes(self):
|
||||
raise NotImplementedError('%s._to_bytes' % type(self).__name__)
|
||||
|
||||
def from_bytes(self, do: bytes, context: dict = {}):
|
||||
"""Convert from binary bytes to internal representation. Store the decoded result
|
||||
in the internal state and return it."""
|
||||
self.encoded = do
|
||||
if self.encoded == b'':
|
||||
self.decoded = None
|
||||
elif self._construct:
|
||||
self.decoded = parse_construct(self._construct, do, context=context)
|
||||
elif self.__class__._construct:
|
||||
self.decoded = parse_construct(self.__class__._construct, do, context=context)
|
||||
else:
|
||||
self.decoded = self._from_bytes(do)
|
||||
return self.decoded
|
||||
|
||||
# not an abstractmethod, as it is only required if no _construct exists
|
||||
def _from_bytes(self, do: bytes):
|
||||
raise NotImplementedError('%s._from_bytes' % type(self).__name__)
|
||||
|
||||
|
||||
class IE(Transcodable, metaclass=TlvMeta):
|
||||
# we specify the metaclass so any downstream subclasses will automatically use it
|
||||
"""Base class for various Information Elements. We understand the notion of a hierarchy
|
||||
of IEs on top of the Transcodable class."""
|
||||
# this is overridden by the TlvMeta metaclass, if it is used to create subclasses
|
||||
nested_collection_cls = None
|
||||
tag = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__()
|
||||
self.nested_collection = None
|
||||
if self.nested_collection_cls:
|
||||
self.nested_collection = self.nested_collection_cls()
|
||||
# if we are a constructed IE, [ordered] list of actual child-IE instances
|
||||
self.children = kwargs.get('children', [])
|
||||
self.decoded = kwargs.get('decoded', None)
|
||||
|
||||
def __repr__(self):
|
||||
"""Return a string representing the [nested] IE data (for print)."""
|
||||
if len(self.children):
|
||||
member_strs = [repr(x) for x in self.children]
|
||||
return '%s(%s)' % (type(self).__name__, ','.join(member_strs))
|
||||
else:
|
||||
return '%s(%s)' % (type(self).__name__, self.decoded)
|
||||
|
||||
def to_val_dict(self):
|
||||
"""Return a JSON-serializable dict representing just the [nested] value portion of the IE
|
||||
data. This does not include any indication of the type of 'self', so the resulting dict alone
|
||||
will be insufficient ot recreate an object from it without additional type information."""
|
||||
if len(self.children):
|
||||
return [x.to_dict() for x in self.children]
|
||||
else:
|
||||
return self.decoded
|
||||
|
||||
def from_val_dict(self, decoded):
|
||||
"""Set the IE internal decoded representation to data from the argument.
|
||||
If this is a nested IE, the child IE instance list is re-created.
|
||||
|
||||
This method is symmetrical to to_val_dict() aboe, i.e. there is no outer dict
|
||||
containig the snake-reformatted type name of 'self'."""
|
||||
if self.nested_collection:
|
||||
self.children = self.nested_collection.from_dict(decoded)
|
||||
else:
|
||||
self.children = []
|
||||
self.decoded = decoded
|
||||
|
||||
def to_dict(self):
|
||||
"""Return a JSON-serializable dict representing the [nested] IE data. The returned
|
||||
data contains an outer dict with the snake-reformatted type of 'self' and is hence
|
||||
sufficient to re-create an object from it."""
|
||||
return {camel_to_snake(type(self).__name__): self.to_val_dict()}
|
||||
|
||||
def from_dict(self, decoded: dict):
|
||||
"""Set the IE internal decoded representation to data from the argument.
|
||||
If this is a nested IE, the child IE instance list is re-created.
|
||||
|
||||
This method is symmetrical to to_dict() above, i.e. the outer dict must contain just a single
|
||||
key-value pair, where the key is the snake-reformatted type name of 'self'"""
|
||||
expected_key_name = camel_to_snake(type(self).__name__)
|
||||
if not expected_key_name in decoded:
|
||||
raise ValueError("Dict %s doesn't contain expected key %s" % (decoded, expected_key_name))
|
||||
self.from_val_dict(decoded[expected_key_name])
|
||||
|
||||
def is_constructed(self):
|
||||
"""Is this IE constructed by further nested IEs?"""
|
||||
return bool(len(self.children) > 0)
|
||||
|
||||
@abc.abstractmethod
|
||||
def to_ie(self, context: dict = {}) -> bytes:
|
||||
"""Convert the internal representation to entire IE including IE header."""
|
||||
|
||||
def to_bytes(self, context: dict = {}) -> bytes:
|
||||
"""Convert the internal representation *of the value part* to binary bytes."""
|
||||
if self.is_constructed():
|
||||
# concatenate the encoded IE of all children to form the value part
|
||||
out = b''
|
||||
for c in self.children:
|
||||
out += c.to_ie(context=context)
|
||||
return out
|
||||
else:
|
||||
return super().to_bytes(context=context)
|
||||
|
||||
def from_bytes(self, do: bytes, context: dict = {}):
|
||||
"""Parse *the value part* from binary bytes to internal representation."""
|
||||
if self.nested_collection:
|
||||
self.children = self.nested_collection.from_bytes(do, context=context)
|
||||
else:
|
||||
self.children = []
|
||||
return super().from_bytes(do, context=context)
|
||||
|
||||
def child_by_name(self, name: str) -> Optional['IE']:
|
||||
"""Return a child IE instance of given snake-case/json type name. This only works in case
|
||||
there is no more than one child IE of the given type."""
|
||||
children = list(filter(lambda c: camel_to_snake(type(c).__name__) == name, self.children))
|
||||
if len(children) > 1:
|
||||
raise KeyError('There are multiple children of class %s' % name)
|
||||
elif len(children) == 1:
|
||||
return children[0]
|
||||
|
||||
def child_by_type(self, cls) -> Optional['IE']:
|
||||
"""Return a child IE instance of given type (class). This only works in case
|
||||
there is no more than one child IE of the given type."""
|
||||
children = list(filter(lambda c: isinstance(c, cls), self.children))
|
||||
if len(children) > 1:
|
||||
raise KeyError('There are multiple children of class %s' % cls)
|
||||
elif len(children) == 1:
|
||||
return children[0]
|
||||
|
||||
|
||||
class TLV_IE(IE):
|
||||
"""Abstract base class for various TLV type Information Elements."""
|
||||
|
||||
def _compute_tag(self) -> int:
|
||||
"""Compute the tag (sometimes the tag encodes part of the value)."""
|
||||
return self.tag
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
"""Obtain the raw TAG at the start of the bytes provided by the user."""
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
"""Obtain the length encoded at the start of the bytes provided by the user."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def _encode_tag(self) -> bytes:
|
||||
"""Encode the tag part. Must be provided by derived (TLV format specific) class."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def _encode_len(self, val: bytes) -> bytes:
|
||||
"""Encode the length part assuming a certain binary value. Must be provided by
|
||||
derived (TLV format specific) class."""
|
||||
|
||||
def to_ie(self, context: dict = {}):
|
||||
return self.to_tlv(context=context)
|
||||
|
||||
def to_tlv(self, context: dict = {}):
|
||||
"""Convert the internal representation to binary TLV bytes."""
|
||||
val = self.to_bytes(context=context)
|
||||
return self._encode_tag() + self._encode_len(val) + val
|
||||
|
||||
def is_tag_compatible(self, rawtag) -> bool:
|
||||
"""Is the given rawtag compatible with this class?"""
|
||||
return rawtag == self._compute_tag()
|
||||
|
||||
def from_tlv(self, do: bytes, context: dict = {}):
|
||||
if len(do) == 0:
|
||||
return {}, b''
|
||||
(rawtag, remainder) = self.__class__._parse_tag_raw(do)
|
||||
if rawtag:
|
||||
if not self.is_tag_compatible(rawtag):
|
||||
raise ValueError("%s: Encountered tag %s doesn't match our supported tag %s" %
|
||||
(self, rawtag, self.tag))
|
||||
(length, remainder) = self.__class__._parse_len(remainder)
|
||||
value = remainder[:length]
|
||||
remainder = remainder[length:]
|
||||
else:
|
||||
value = do
|
||||
remainder = b''
|
||||
dec = self.from_bytes(value, context=context)
|
||||
return dec, remainder
|
||||
|
||||
|
||||
class COMPACT_TLV_IE(TLV_IE):
|
||||
"""TLV_IE formatted as COMPACT-TLV described in ISO 7816"""
|
||||
|
||||
@classmethod
|
||||
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return do[0] >> 4, do
|
||||
|
||||
@classmethod
|
||||
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
|
||||
rawtag, remainder = cls._parse_tag_raw(do)
|
||||
return {'tag': rawtag}, remainder
|
||||
|
||||
@classmethod
|
||||
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return do[0] & 0xf, do[1:]
|
||||
|
||||
def _encode_tag(self) -> bytes:
|
||||
"""Not needed as we override the to_tlv() method to encode tag+length into one byte."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _encode_len(self):
|
||||
"""Not needed as we override the to_tlv() method to encode tag+length into one byte."""
|
||||
raise NotImplementedError
|
||||
|
||||
def to_tlv(self, context: dict = {}):
|
||||
val = self.to_bytes(context=context)
|
||||
return bytes([(self.tag << 4) | (len(val) & 0xF)]) + val
|
||||
|
||||
|
||||
class BER_TLV_IE(TLV_IE):
|
||||
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
|
||||
|
||||
@classmethod
|
||||
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
|
||||
return bertlv_parse_tag(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return bertlv_parse_tag_raw(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return bertlv_parse_len(do)
|
||||
|
||||
def _encode_tag(self) -> bytes:
|
||||
return bertlv_encode_tag(self._compute_tag())
|
||||
|
||||
def _encode_len(self, val: bytes) -> bytes:
|
||||
return bertlv_encode_len(len(val))
|
||||
|
||||
|
||||
class ComprTlvMeta(TlvMeta):
|
||||
def __new__(mcs, name, bases, namespace, **kwargs):
|
||||
x = super().__new__(mcs, name, bases, namespace, **kwargs)
|
||||
if x.tag:
|
||||
# we currently assume that the tag values always have the comprehension bit set;
|
||||
# let's fix it up if a derived class has forgotten about that
|
||||
if x.tag > 0xff and x.tag & 0x8000 == 0:
|
||||
print("Fixing up COMPR_TLV_IE class %s: tag=0x%x has no comprehension bit" % (name, x.tag))
|
||||
x.tag = x.tag | 0x8000
|
||||
elif x.tag & 0x80 == 0:
|
||||
print("Fixing up COMPR_TLV_IE class %s: tag=0x%x has no comprehension bit" % (name, x.tag))
|
||||
x.tag = x.tag | 0x80
|
||||
return x
|
||||
|
||||
class COMPR_TLV_IE(TLV_IE, metaclass=ComprTlvMeta):
|
||||
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.comprehension = False
|
||||
|
||||
@classmethod
|
||||
def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
|
||||
return comprehensiontlv_parse_tag(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return comprehensiontlv_parse_tag_raw(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return bertlv_parse_len(do)
|
||||
|
||||
def is_tag_compatible(self, rawtag: int) -> bool:
|
||||
"""Override is_tag_compatible as we need to mask out the
|
||||
comprehension bit when doing compares."""
|
||||
ctag = self._compute_tag()
|
||||
if ctag > 0xff:
|
||||
return ctag & 0x7fff == rawtag & 0x7fff
|
||||
else:
|
||||
return ctag & 0x7f == rawtag & 0x7f
|
||||
|
||||
def _encode_tag(self) -> bytes:
|
||||
return comprehensiontlv_encode_tag(self._compute_tag())
|
||||
|
||||
def _encode_len(self, val: bytes) -> bytes:
|
||||
return bertlv_encode_len(len(val))
|
||||
|
||||
|
||||
class DGI_TLV_IE(TLV_IE):
|
||||
"""TLV_IE formated as GlobalPlatform Systems Scripting Language Specification v1.1.0 Annex B."""
|
||||
|
||||
@classmethod
|
||||
def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return dgi_parse_tag_raw(do)
|
||||
|
||||
@classmethod
|
||||
def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
|
||||
return dgi_parse_len(do)
|
||||
|
||||
def _encode_tag(self) -> bytes:
|
||||
return dgi_encode_tag(self._compute_tag())
|
||||
|
||||
def _encode_len(self, val: bytes) -> bytes:
|
||||
return dgi_encode_len(len(val))
|
||||
|
||||
|
||||
class TLV_IE_Collection(metaclass=TlvCollectionMeta):
|
||||
# we specify the metaclass so any downstream subclasses will automatically use it
|
||||
"""A TLV_IE_Collection consists of multiple TLV_IE classes identified by their tags.
|
||||
A given encoded DO may contain any of them in any order, and may contain multiple instances
|
||||
of each DO."""
|
||||
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
|
||||
possible_nested = []
|
||||
|
||||
def __init__(self, desc=None, **kwargs):
|
||||
self.desc = desc
|
||||
#print("possible_nested: ", self.possible_nested)
|
||||
self.members = kwargs.get('nested', self.possible_nested)
|
||||
self.members_by_tag = {}
|
||||
self.members_by_name = {}
|
||||
self.members_by_tag = {m.tag: m for m in self.members}
|
||||
self.members_by_name = {camel_to_snake(m.__name__): m for m in self.members}
|
||||
# if we are a constructed IE, [ordered] list of actual child-IE instances
|
||||
self.children = kwargs.get('children', [])
|
||||
self.encoded = None
|
||||
|
||||
def __str__(self):
|
||||
member_strs = [str(x) for x in self.members]
|
||||
return '%s(%s)' % (type(self).__name__, ','.join(member_strs))
|
||||
|
||||
def __repr__(self):
|
||||
member_strs = [repr(x) for x in self.members]
|
||||
return '%s(%s)' % (self.__class__, ','.join(member_strs))
|
||||
|
||||
def __add__(self, other):
|
||||
"""Extending TLV_IE_Collections with other TLV_IE_Collections or TLV_IEs."""
|
||||
if isinstance(other, TLV_IE_Collection):
|
||||
# adding one collection to another
|
||||
members = self.members + other.members
|
||||
return TLV_IE_Collection(self.desc, nested=members)
|
||||
elif inspect.isclass(other) and issubclass(other, TLV_IE):
|
||||
# adding a member to a collection
|
||||
return TLV_IE_Collection(self.desc, nested=self.members + [other])
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
def from_bytes(self, binary: bytes, context: dict = {}) -> List[TLV_IE]:
|
||||
"""Create a list of TLV_IEs from the collection based on binary input data.
|
||||
Args:
|
||||
binary : binary bytes of encoded data
|
||||
Returns:
|
||||
list of instances of TLV_IE sub-classes containing parsed data
|
||||
"""
|
||||
self.encoded = binary
|
||||
# list of instances of TLV_IE collection member classes appearing in the data
|
||||
res = []
|
||||
remainder = binary
|
||||
first = next(iter(self.members_by_tag.values()))
|
||||
# iterate until no binary trailer is left
|
||||
while len(remainder):
|
||||
context['siblings'] = res
|
||||
# obtain the tag at the start of the remainder
|
||||
tag, _r = first._parse_tag_raw(remainder)
|
||||
if tag is None:
|
||||
break
|
||||
if issubclass(first, COMPR_TLV_IE):
|
||||
tag = tag | 0x80 # HACK: always assume comprehension
|
||||
if tag in self.members_by_tag:
|
||||
cls = self.members_by_tag[tag]
|
||||
# create an instance and parse accordingly
|
||||
inst = cls()
|
||||
_dec, remainder = inst.from_tlv(remainder, context=context)
|
||||
res.append(inst)
|
||||
else:
|
||||
# unknown tag; create the related class on-the-fly using the same base class
|
||||
name = 'unknown_%s_%X' % (first.__base__.__name__, tag)
|
||||
cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [],
|
||||
'nested_collection_cls': None})
|
||||
cls._from_bytes = lambda s, a: {'raw': a.hex()}
|
||||
cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw'])
|
||||
# create an instance and parse accordingly
|
||||
inst = cls()
|
||||
_dec, remainder = inst.from_tlv(remainder, context=context)
|
||||
res.append(inst)
|
||||
self.children = res
|
||||
return res
|
||||
|
||||
def from_dict(self, decoded: List[dict]) -> List[TLV_IE]:
|
||||
"""Create a list of TLV_IE instances from the collection based on an array
|
||||
of dicts, where they key indicates the name of the TLV_IE subclass to use."""
|
||||
# list of instances of TLV_IE collection member classes appearing in the data
|
||||
res = []
|
||||
# iterate over members of the list passed into "decoded"
|
||||
for i in decoded:
|
||||
# iterate over all the keys (typically one!) within the current list item dict
|
||||
for k in i.keys():
|
||||
# check if we have a member identified by the dict key
|
||||
if k in self.members_by_name:
|
||||
# resolve the class for that name; create an instance of it
|
||||
cls = self.members_by_name[k]
|
||||
inst = cls()
|
||||
inst.from_dict({k: i[k]})
|
||||
res.append(inst)
|
||||
else:
|
||||
raise ValueError('%s: Unknown TLV Class %s in %s; expected %s' %
|
||||
(self, k, decoded, self.members_by_name.keys()))
|
||||
self.children = res
|
||||
return res
|
||||
|
||||
def to_dict(self):
|
||||
# we intentionally return not a dict, but a list of dicts. We could prefix by
|
||||
# self.__class__.__name__, but that is usually some meaningless auto-generated collection name.
|
||||
return [x.to_dict() for x in self.children]
|
||||
|
||||
def to_bytes(self, context: dict = {}):
|
||||
out = b''
|
||||
context['siblings'] = self.children
|
||||
for c in self.children:
|
||||
out += c.to_tlv(context=context)
|
||||
return out
|
||||
|
||||
def from_tlv(self, do, context: dict = {}):
|
||||
return self.from_bytes(do, context=context)
|
||||
|
||||
def to_tlv(self, context: dict = {}):
|
||||
return self.to_bytes(context=context)
|
||||
|
||||
|
||||
def flatten_dict_lists(inp):
|
||||
"""hierarchically flatten each list-of-dicts into a single dict. This is useful to
|
||||
make the output of hierarchical TLV decoder structures flatter and more easy to read."""
|
||||
def are_all_elements_dict(l):
|
||||
for e in l:
|
||||
if not isinstance(e, dict):
|
||||
return False
|
||||
return True
|
||||
|
||||
def are_elements_unique(lod):
|
||||
set_of_keys = {list(x.keys())[0] for x in lod}
|
||||
return len(lod) == len(set_of_keys)
|
||||
|
||||
if isinstance(inp, list):
|
||||
if are_all_elements_dict(inp) and are_elements_unique(inp):
|
||||
# flatten into one shared dict
|
||||
newdict = {}
|
||||
for e in inp:
|
||||
key = list(e.keys())[0]
|
||||
newdict[key] = e[key]
|
||||
inp = newdict
|
||||
# process result as any native dict
|
||||
return {k:flatten_dict_lists(v) for k,v in inp.items()}
|
||||
else:
|
||||
return [flatten_dict_lists(x) for x in inp]
|
||||
elif isinstance(inp, dict):
|
||||
return {k:flatten_dict_lists(v) for k,v in inp.items()}
|
||||
else:
|
||||
return inp
|
||||
@@ -8,9 +8,10 @@ import abc
|
||||
import argparse
|
||||
from typing import Optional, Tuple
|
||||
from construct import Construct
|
||||
from osmocom.utils import b2h, h2b, i2h, Hexstr
|
||||
|
||||
from pySim.exceptions import *
|
||||
from pySim.utils import sw_match, b2h, h2b, i2h, Hexstr, SwHexstr, SwMatchstr, ResTuple
|
||||
from pySim.utils import SwHexstr, SwMatchstr, ResTuple, sw_match
|
||||
from pySim.cat import ProactiveCommand, CommandDetails, DeviceIdentities, Result
|
||||
|
||||
#
|
||||
|
||||
@@ -22,10 +22,11 @@ import socket
|
||||
import os
|
||||
import argparse
|
||||
from typing import Optional
|
||||
from osmocom.utils import h2b, b2h, Hexstr
|
||||
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.exceptions import ReaderError, ProtocolError
|
||||
from pySim.utils import h2b, b2h, Hexstr, ResTuple
|
||||
from pySim.utils import ResTuple
|
||||
|
||||
|
||||
class L1CTLMessage:
|
||||
|
||||
@@ -22,8 +22,9 @@ import re
|
||||
import argparse
|
||||
from typing import Optional
|
||||
import serial
|
||||
from osmocom.utils import Hexstr
|
||||
|
||||
from pySim.utils import Hexstr, ResTuple
|
||||
from pySim.utils import ResTuple
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.exceptions import ReaderError, ProtocolError
|
||||
|
||||
|
||||
@@ -27,9 +27,11 @@ from smartcard.Exceptions import NoCardException, CardRequestTimeoutException, C
|
||||
from smartcard.System import readers
|
||||
from smartcard.ExclusiveConnectCardConnection import ExclusiveConnectCardConnection
|
||||
|
||||
from osmocom.utils import h2i, i2h, Hexstr
|
||||
|
||||
from pySim.exceptions import NoCardError, ProtocolError, ReaderError
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.utils import h2i, i2h, Hexstr, ResTuple
|
||||
from pySim.utils import ResTuple
|
||||
|
||||
|
||||
class PcscSimLink(LinkBase):
|
||||
|
||||
@@ -21,10 +21,11 @@ import os
|
||||
import argparse
|
||||
from typing import Optional
|
||||
import serial
|
||||
from osmocom.utils import h2b, b2h, Hexstr
|
||||
|
||||
from pySim.exceptions import NoCardError, ProtocolError
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.utils import h2b, b2h, Hexstr, ResTuple
|
||||
from pySim.utils import ResTuple
|
||||
|
||||
|
||||
class SerialSimLink(LinkBase):
|
||||
|
||||
@@ -21,10 +21,11 @@ from bidict import bidict
|
||||
from construct import Select, Const, Bit, Struct, Int16ub, FlagsEnum, GreedyString, ValidationError
|
||||
from construct import Optional as COptional, Computed
|
||||
|
||||
from pySim.construct import *
|
||||
from osmocom.construct import *
|
||||
from osmocom.utils import *
|
||||
from osmocom.tlv import *
|
||||
from pySim.utils import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.tlv import *
|
||||
from pySim.profile import CardProfile
|
||||
from pySim.profile import match_uicc
|
||||
from pySim import iso7816_4
|
||||
|
||||
@@ -19,11 +19,9 @@
|
||||
|
||||
from typing import List
|
||||
import argparse
|
||||
|
||||
import cmd2
|
||||
from cmd2 import CommandSet, with_default_category
|
||||
|
||||
from pySim.utils import b2h, auto_uint8, auto_uint16, is_hexstr
|
||||
from osmocom.utils import b2h, auto_uint8, auto_uint16, is_hexstr
|
||||
|
||||
from pySim.ts_102_221 import *
|
||||
|
||||
|
||||
@@ -17,13 +17,12 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from pySim.construct import *
|
||||
from construct import *
|
||||
from construct import Optional as COptional
|
||||
from osmocom.construct import *
|
||||
|
||||
#from pySim.utils import *
|
||||
from osmocom.tlv import BER_TLV_IE, TLV_IE_Collection
|
||||
from pySim.filesystem import CardDF, TransparentEF
|
||||
from pySim.tlv import BER_TLV_IE, TLV_IE_Collection
|
||||
|
||||
# TS102 310 Section 7.1
|
||||
class EF_EAPKEYS(TransparentEF):
|
||||
|
||||
@@ -32,6 +32,10 @@ from construct import Optional as COptional
|
||||
from construct import Int32ub, Nibble, GreedyRange, Struct, FlagsEnum, Switch, this, Int16ub, Padding
|
||||
from construct import Bytewise, Int24ub, PaddedString, PrefixedArray, If
|
||||
|
||||
from osmocom.utils import is_hexstr
|
||||
from osmocom.tlv import *
|
||||
from osmocom.construct import *
|
||||
|
||||
import pySim.ts_102_221
|
||||
from pySim.ts_51_011 import EF_ACMmax, EF_AAeM, EF_eMLPP, EF_CMI, EF_PNN
|
||||
from pySim.ts_51_011 import EF_MMSN, EF_MMSICP, EF_MMSUP, EF_MMSUCP, EF_VGCS, EF_VGCSS, EF_NIA
|
||||
@@ -40,13 +44,10 @@ from pySim.ts_51_011 import EF_CBMID, EF_CBMIR, EF_ADN, EF_CFIS, EF_SMS, EF_MSIS
|
||||
from pySim.ts_51_011 import EF_IMSI, EF_xPLMNwAcT, EF_SPN, EF_CBMI, EF_ACC, EF_PLMNsel
|
||||
from pySim.ts_51_011 import EF_Kc, EF_CPBCCH, EF_InvScan
|
||||
from pySim.ts_102_221 import EF_ARR
|
||||
from pySim.tlv import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.ts_31_102_telecom import DF_PHONEBOOK, EF_UServiceTable
|
||||
from pySim.ts_31_103_shared import EF_IMSConfigData, EF_XCAPConfigData, EF_MuDMiDConfigData
|
||||
from pySim.ts_31_103_shared import EF_AC_GBAUAPI, EF_IMSDCI
|
||||
from pySim.construct import *
|
||||
from pySim.utils import is_hexstr
|
||||
from pySim.cat import SMS_TPDU, DeviceIdentities, SMSPPDownload
|
||||
|
||||
# Mapping between USIM Service Number and its description
|
||||
|
||||
@@ -28,10 +28,10 @@ Needs to be a separate python module to avoid cyclic imports
|
||||
|
||||
from construct import Optional as COptional
|
||||
from construct import Struct, Int16ub, Int32ub
|
||||
from osmocom.tlv import *
|
||||
from osmocom.construct import *
|
||||
|
||||
from pySim.tlv import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.construct import *
|
||||
|
||||
# TS 31.102 Section 4.2.8
|
||||
class EF_UServiceTable(TransparentEF):
|
||||
|
||||
@@ -23,16 +23,16 @@ Various constants from 3GPP TS 31.103 V18.1.0
|
||||
#
|
||||
|
||||
from construct import Struct, Switch, this, Bytes, GreedyString
|
||||
from osmocom.utils import *
|
||||
from osmocom.tlv import *
|
||||
from osmocom.construct import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.utils import *
|
||||
from pySim.tlv import *
|
||||
from pySim.ts_51_011 import EF_AD, EF_SMS, EF_SMSS, EF_SMSR, EF_SMSP
|
||||
from pySim.ts_31_102 import ADF_USIM, EF_FromPreferred
|
||||
from pySim.ts_31_102_telecom import EF_UServiceTable
|
||||
from pySim.ts_31_103_shared import *
|
||||
import pySim.ts_102_221
|
||||
from pySim.ts_102_221 import EF_ARR
|
||||
from pySim.construct import *
|
||||
|
||||
# Mapping between ISIM Service Number and its description
|
||||
EF_IST_map = {
|
||||
|
||||
@@ -20,9 +20,9 @@ hence need to be in a separate python module to avoid circular dependencies.
|
||||
#
|
||||
|
||||
from construct import Struct, Switch, Bytes, GreedyString, GreedyBytes, Int8ub, Prefixed, Enum, Byte
|
||||
from pySim.construct import HexAdapter, Utf8Adapter
|
||||
from osmocom.tlv import BER_TLV_IE, TLV_IE_Collection
|
||||
from osmocom.construct import HexAdapter, Utf8Adapter
|
||||
from pySim.filesystem import *
|
||||
from pySim.tlv import BER_TLV_IE, TLV_IE_Collection
|
||||
|
||||
# TS 31.103 Section 4.2.16
|
||||
class EF_UICCIARI(LinFixedEF):
|
||||
|
||||
@@ -20,9 +20,9 @@ Support for 3GPP TS 31.104 V17.0.0
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from osmocom.utils import *
|
||||
from osmocom.tlv import *
|
||||
from pySim.filesystem import *
|
||||
from pySim.utils import *
|
||||
from pySim.tlv import *
|
||||
from pySim.ts_31_102 import ADF_USIM
|
||||
from pySim.ts_51_011 import EF_IMSI, EF_AD
|
||||
import pySim.ts_102_221
|
||||
|
||||
@@ -29,19 +29,23 @@ order to describe the files specified in the relevant ETSI + 3GPP specifications
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import enum
|
||||
from struct import pack, unpack
|
||||
from typing import Tuple
|
||||
|
||||
from construct import Optional as COptional
|
||||
from construct import *
|
||||
from osmocom.tlv import *
|
||||
from osmocom.utils import *
|
||||
from osmocom.construct import *
|
||||
|
||||
from pySim.utils import dec_iccid, enc_iccid, dec_imsi, enc_imsi, dec_plmn, enc_plmn, dec_xplmn_w_act
|
||||
from pySim.utils import dec_msisdn, enc_msisdn
|
||||
from pySim.profile import match_sim
|
||||
from pySim.profile import CardProfile, CardProfileAddon
|
||||
from pySim.filesystem import *
|
||||
from pySim.ts_31_102_telecom import DF_PHONEBOOK, DF_MULTIMEDIA, DF_MCS, DF_V2X
|
||||
from pySim.gsm_r import AddonGSMR
|
||||
import enum
|
||||
from pySim.construct import *
|
||||
from construct import Optional as COptional
|
||||
from construct import *
|
||||
from struct import pack, unpack
|
||||
from typing import Tuple
|
||||
from pySim.tlv import *
|
||||
from pySim.utils import *
|
||||
|
||||
# Mapping between SIM Service Number and its description
|
||||
EF_SST_map = {
|
||||
|
||||
486
pySim/utils.py
486
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,400 +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 bertlv_parse_one_rawtag(binary: bytes) -> Tuple[int, int, bytes, bytes]:
|
||||
"""Parse a single TLV IE at the start of the given binary data; return tag as raw integer.
|
||||
Args:
|
||||
binary : binary input data of BER-TLV length field
|
||||
Returns:
|
||||
Tuple of (tag:int, len:int, remainder:bytes)
|
||||
"""
|
||||
(tag, remainder) = bertlv_parse_tag_raw(binary)
|
||||
(length, remainder) = bertlv_parse_len(remainder)
|
||||
value = remainder[:length]
|
||||
remainder = remainder[length:]
|
||||
return (tag, length, value, remainder)
|
||||
|
||||
def bertlv_return_one_rawtlv(binary: bytes) -> Tuple[int, int, bytes, bytes]:
|
||||
"""Return one single [encoded] TLV IE at the start of the given binary data.
|
||||
Args:
|
||||
binary : binary input data of BER-TLV length field
|
||||
Returns:
|
||||
Tuple of (tag:int, len:int, tlv:bytes, remainder:bytes)
|
||||
"""
|
||||
(tag, remainder) = bertlv_parse_tag_raw(binary)
|
||||
(length, remainder) = bertlv_parse_len(remainder)
|
||||
tl_length = len(binary) - len(remainder)
|
||||
value = binary[:tl_length] + remainder[:length]
|
||||
remainder = remainder[length:]
|
||||
return (tag, 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:
|
||||
#
|
||||
@@ -437,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(
|
||||
@@ -809,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
|
||||
@@ -963,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
|
||||
@@ -1037,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
|
||||
@@ -1456,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
|
||||
|
||||
@@ -5,7 +5,7 @@ cmd2>=1.5
|
||||
jsonpath-ng
|
||||
construct>=2.9.51
|
||||
bidict
|
||||
gsm0338
|
||||
pyosmocom
|
||||
pyyaml>=5.1
|
||||
termcolor
|
||||
colorlog
|
||||
|
||||
2
setup.py
2
setup.py
@@ -24,7 +24,7 @@ setup(
|
||||
"jsonpath-ng",
|
||||
"construct >= 2.9.51",
|
||||
"bidict",
|
||||
"gsm0338",
|
||||
"pyosmocom",
|
||||
"pyyaml >= 5.1",
|
||||
"termcolor",
|
||||
"colorlog",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim.utils import h2b, b2h
|
||||
from pySim.construct import filter_dict
|
||||
from osmocom.utils import h2b, b2h
|
||||
from osmocom.construct import filter_dict
|
||||
|
||||
from pySim.apdu import Apdu
|
||||
from pySim.apdu.ts_31_102 import UsimAuthenticateEven
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim.utils import b2h, h2b
|
||||
from pySim.construct import *
|
||||
from construct import FlagsEnum
|
||||
|
||||
tests = [
|
||||
( b'\x80', 0x80 ),
|
||||
( b'\x80\x01', 0x8001 ),
|
||||
( b'\x80\x00\x01', 0x800001 ),
|
||||
( b'\x80\x23\x42\x01', 0x80234201 ),
|
||||
]
|
||||
|
||||
class TestGreedyInt(unittest.TestCase):
|
||||
def test_GreedyInt_decoder(self):
|
||||
gi = GreedyInteger()
|
||||
for t in tests:
|
||||
self.assertEqual(gi.parse(t[0]), t[1])
|
||||
def test_GreedyInt_encoder(self):
|
||||
gi = GreedyInteger()
|
||||
for t in tests:
|
||||
self.assertEqual(t[0], gi.build(t[1]))
|
||||
pass
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def test_filter_dict(self):
|
||||
inp = {'foo': 0xf00, '_bar' : 0xba5, 'baz': 0xba2 }
|
||||
out = {'foo': 0xf00, 'baz': 0xba2 }
|
||||
self.assertEqual(filter_dict(inp), out)
|
||||
|
||||
def test_filter_dict_nested(self):
|
||||
inp = {'foo': 0xf00, 'nest': {'_bar' : 0xba5}, 'baz': 0xba2 }
|
||||
out = {'foo': 0xf00, 'nest': {}, 'baz': 0xba2 }
|
||||
self.assertEqual(filter_dict(inp), out)
|
||||
|
||||
|
||||
class TestUcs2Adapter(unittest.TestCase):
|
||||
# the three examples from TS 102 221 Annex A
|
||||
EXAMPLE1 = b'\x80\x00\x30\x00\x31\x00\x32\x00\x33'
|
||||
EXAMPLE2 = b'\x81\x05\x13\x53\x95\xa6\xa6\xff\xff'
|
||||
EXAMPLE3 = b'\x82\x05\x05\x30\x2d\x82\xd3\x2d\x31'
|
||||
ad = Ucs2Adapter(GreedyBytes)
|
||||
|
||||
def test_example1_decode(self):
|
||||
dec = self.ad._decode(self.EXAMPLE1, None, None)
|
||||
self.assertEqual(dec, "0123")
|
||||
|
||||
def test_example2_decode(self):
|
||||
dec = self.ad._decode(self.EXAMPLE2, None, None)
|
||||
self.assertEqual(dec, "S\u0995\u09a6\u09a6\u09ff")
|
||||
|
||||
def test_example3_decode(self):
|
||||
dec = self.ad._decode(self.EXAMPLE3, None, None)
|
||||
self.assertEqual(dec, "-\u0532\u0583-1")
|
||||
|
||||
testdata = [
|
||||
# variant 2 with only GSM alphabet characters
|
||||
( "mahlzeit", '8108006d61686c7a656974' ),
|
||||
# variant 2 with mixed GSM alphabet + UCS2
|
||||
( "mahlzeit\u099523", '810b136d61686c7a656974953233' ),
|
||||
# variant 3 due to codepoint exceeding 8 bit
|
||||
( "mahl\u8023zeit", '820980236d61686c807a656974' ),
|
||||
# variant 1 as there is no common codepoint pointer / prefix
|
||||
( "\u3000\u2000\u1000", '80300020001000' ),
|
||||
]
|
||||
|
||||
def test_data_decode(self):
|
||||
for string, encoded_hex in self.testdata:
|
||||
encoded = h2b(encoded_hex)
|
||||
dec = self.ad._decode(encoded, None, None)
|
||||
self.assertEqual(dec, string)
|
||||
|
||||
def test_data_encode(self):
|
||||
for string, encoded_hex in self.testdata:
|
||||
encoded = h2b(encoded_hex)
|
||||
re_enc = self.ad._encode(string, None, None)
|
||||
self.assertEqual(encoded, re_enc)
|
||||
|
||||
class TestTrailerAdapter(unittest.TestCase):
|
||||
Privileges = FlagsEnum(StripTrailerAdapter(GreedyBytes, 3), security_domain=0x800000,
|
||||
dap_verification=0x400000,
|
||||
delegated_management=0x200000, card_lock=0x100000,
|
||||
card_terminate=0x080000, card_reset=0x040000,
|
||||
cvm_management=0x020000, mandated_dap_verification=0x010000,
|
||||
trusted_path=0x8000, authorized_management=0x4000,
|
||||
token_management=0x2000, global_delete=0x1000,
|
||||
global_lock=0x0800, global_registry=0x0400,
|
||||
final_application=0x0200, global_service=0x0100,
|
||||
receipt_generation=0x80, ciphered_load_file_data_block=0x40,
|
||||
contactless_activation=0x20, contactless_self_activation=0x10)
|
||||
examples = ['00', '80', '8040', '400010']
|
||||
def test_examples(self):
|
||||
for e in self.examples:
|
||||
dec = self.Privileges.parse(h2b(e))
|
||||
reenc = self.Privileges.build(dec)
|
||||
self.assertEqual(e, b2h(reenc))
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -18,8 +18,8 @@
|
||||
import unittest
|
||||
import logging
|
||||
import base64
|
||||
from osmocom.utils import b2h, h2b
|
||||
|
||||
from pySim.utils import b2h, h2b
|
||||
from pySim.esim.bsp import *
|
||||
import pySim.esim.rsp as rsp
|
||||
from pySim.esim import ActivationCode
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
import unittest
|
||||
import logging
|
||||
import base64
|
||||
from osmocom.utils import b2h, h2b
|
||||
|
||||
from pySim.utils import b2h, h2b
|
||||
from pySim.esim.bsp import *
|
||||
import pySim.esim.rsp as rsp
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
import unittest
|
||||
import logging
|
||||
import copy
|
||||
from osmocom.utils import h2b, b2h
|
||||
|
||||
from pySim.utils import h2b, b2h
|
||||
from pySim.esim.saip import *
|
||||
from pySim.esim.saip.personalization import *
|
||||
from pprint import pprint as pp
|
||||
|
||||
@@ -17,10 +17,9 @@
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
from osmocom.utils import *
|
||||
|
||||
from pySim.utils import *
|
||||
from pySim.filesystem import *
|
||||
|
||||
import pySim.iso7816_4
|
||||
import pySim.ts_102_221
|
||||
import pySim.ts_102_222
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
from osmocom.utils import b2h, h2b
|
||||
|
||||
from pySim.global_platform import *
|
||||
from pySim.global_platform.scp import *
|
||||
from pySim.utils import b2h, h2b
|
||||
|
||||
KIC = h2b('100102030405060708090a0b0c0d0e0f') # enc
|
||||
KID = h2b('101102030405060708090a0b0c0d0e0f') # MAC
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim.utils import h2b, b2h
|
||||
from osmocom.utils import h2b, b2h
|
||||
from pySim.sms import SMS_SUBMIT, SMS_DELIVER, AddressField
|
||||
from pySim.ota import *
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import unittest
|
||||
from pySim.utils import h2b, b2h
|
||||
from osmocom.utils import h2b, b2h
|
||||
from pySim.sms import *
|
||||
|
||||
class Test_SMS_UDH(unittest.TestCase):
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# (C) 2022 by Harald Welte <laforge@osmocom.org>
|
||||
# All Rights Reserved
|
||||
#
|
||||
# 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 unittest
|
||||
from construct import Int8ub, GreedyBytes
|
||||
from pySim.tlv import *
|
||||
from pySim.utils import h2b
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def test_camel_to_snake(self):
|
||||
cases = [
|
||||
('CamelCase', 'camel_case'),
|
||||
('CamelCaseUPPER', 'camel_case_upper'),
|
||||
('Camel_CASE_underSCORE', 'camel_case_under_score'),
|
||||
]
|
||||
for c in cases:
|
||||
self.assertEqual(camel_to_snake(c[0]), c[1])
|
||||
|
||||
def test_flatten_dict_lists(self):
|
||||
inp = [
|
||||
{ 'first': 1 },
|
||||
{ 'second': 2 },
|
||||
{ 'third': 3 },
|
||||
]
|
||||
out = { 'first': 1, 'second':2, 'third': 3}
|
||||
self.assertEqual(flatten_dict_lists(inp), out)
|
||||
|
||||
def test_flatten_dict_lists_nodict(self):
|
||||
inp = [
|
||||
{ 'first': 1 },
|
||||
{ 'second': 2 },
|
||||
{ 'third': 3 },
|
||||
4,
|
||||
]
|
||||
self.assertEqual(flatten_dict_lists(inp), inp)
|
||||
|
||||
def test_flatten_dict_lists_nested(self):
|
||||
inp = {'top': [
|
||||
{ 'first': 1 },
|
||||
{ 'second': 2 },
|
||||
{ 'third': 3 },
|
||||
] }
|
||||
out = {'top': { 'first': 1, 'second':2, 'third': 3 } }
|
||||
self.assertEqual(flatten_dict_lists(inp), out)
|
||||
|
||||
class TestTranscodable(unittest.TestCase):
|
||||
class XC_constr_class(Transcodable):
|
||||
_construct = Int8ub
|
||||
def __init__(self):
|
||||
super().__init__();
|
||||
|
||||
def test_XC_constr_class(self):
|
||||
"""Transcodable derived class with _construct class variable"""
|
||||
xc = TestTranscodable.XC_constr_class()
|
||||
self.assertEqual(xc.from_bytes(b'\x23'), 35)
|
||||
self.assertEqual(xc.to_bytes(), b'\x23')
|
||||
|
||||
class XC_constr_instance(Transcodable):
|
||||
def __init__(self):
|
||||
super().__init__();
|
||||
self._construct = Int8ub
|
||||
|
||||
def test_XC_constr_instance(self):
|
||||
"""Transcodable derived class with _construct instance variable"""
|
||||
xc = TestTranscodable.XC_constr_instance()
|
||||
self.assertEqual(xc.from_bytes(b'\x23'), 35)
|
||||
self.assertEqual(xc.to_bytes(), b'\x23')
|
||||
|
||||
class XC_method_instance(Transcodable):
|
||||
def __init__(self):
|
||||
super().__init__();
|
||||
def _from_bytes(self, do):
|
||||
return ('decoded', do)
|
||||
def _to_bytes(self):
|
||||
return self.decoded[1]
|
||||
|
||||
def test_XC_method_instance(self):
|
||||
"""Transcodable derived class with _{from,to}_bytes() methods"""
|
||||
xc = TestTranscodable.XC_method_instance()
|
||||
self.assertEqual(xc.to_bytes(), b'')
|
||||
self.assertEqual(xc.from_bytes(b''), None)
|
||||
self.assertEqual(xc.from_bytes(b'\x23'), ('decoded', b'\x23'))
|
||||
self.assertEqual(xc.to_bytes(), b'\x23')
|
||||
|
||||
class TestIE(unittest.TestCase):
|
||||
class MyIE(IE, tag=0x23, desc='My IE description'):
|
||||
_construct = Int8ub
|
||||
def to_ie(self):
|
||||
return self.to_bytes()
|
||||
|
||||
def test_IE_empty(self):
|
||||
ie = TestIE.MyIE()
|
||||
self.assertEqual(ie.to_dict(), {'my_ie': None})
|
||||
self.assertEqual(repr(ie), 'MyIE(None)')
|
||||
self.assertEqual(ie.is_constructed(), False)
|
||||
|
||||
def test_IE_from_bytes(self):
|
||||
ie = TestIE.MyIE()
|
||||
ie.from_bytes(b'\x42')
|
||||
self.assertEqual(ie.to_dict(), {'my_ie': 66})
|
||||
self.assertEqual(repr(ie), 'MyIE(66)')
|
||||
self.assertEqual(ie.is_constructed(), False)
|
||||
self.assertEqual(ie.to_bytes(), b'\x42')
|
||||
self.assertEqual(ie.to_ie(), b'\x42')
|
||||
|
||||
class TestCompact(unittest.TestCase):
|
||||
class IE_3(COMPACT_TLV_IE, tag=0x3):
|
||||
_construct = GreedyBytes
|
||||
class IE_7(COMPACT_TLV_IE, tag=0x7):
|
||||
_construct = GreedyBytes
|
||||
class IE_5(COMPACT_TLV_IE, tag=0x5):
|
||||
_construct = GreedyBytes
|
||||
# pylint: disable=undefined-variable
|
||||
class IE_Coll(TLV_IE_Collection, nested=[IE_3, IE_7, IE_5]):
|
||||
_construct = GreedyBytes
|
||||
def test_ATR(self):
|
||||
atr = h2b("31E073FE211F5745437531301365")
|
||||
c = self.IE_Coll()
|
||||
c.from_tlv(atr)
|
||||
self.assertEqual(c.children[0].tag, 3)
|
||||
self.assertEqual(c.children[0].to_bytes(), b'\xe0')
|
||||
self.assertEqual(c.children[1].tag, 7)
|
||||
self.assertEqual(c.children[1].to_bytes(), b'\xfe\x21\x1f')
|
||||
self.assertEqual(c.children[2].tag, 5)
|
||||
self.assertEqual(c.children[2].to_bytes(), b'\x45\x43\x75\x31\x30\x13\x65')
|
||||
self.assertEqual(c.to_tlv(), atr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -18,8 +18,8 @@
|
||||
import unittest
|
||||
import logging
|
||||
|
||||
from pySim.utils import b2h, h2b, all_subclasses
|
||||
from pySim.tlv import *
|
||||
from osmocom.utils import b2h, h2b, all_subclasses
|
||||
from osmocom.tlv import *
|
||||
|
||||
import pySim.iso7816_4
|
||||
import pySim.ts_102_221
|
||||
|
||||
@@ -172,70 +172,6 @@ class DecTestCase(unittest.TestCase):
|
||||
msisdn_decoded = utils.dec_msisdn("ffffffffffffffffffffffffffffffffffffffff0bb121436587092143658709ffff")
|
||||
self.assertEqual(msisdn_decoded, (1, 3, "12345678901234567890"))
|
||||
|
||||
class TestBerTlv(unittest.TestCase):
|
||||
def test_BerTlvTagDec(self):
|
||||
res = utils.bertlv_parse_tag(b'\x01')
|
||||
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class': 0}, b''))
|
||||
res = utils.bertlv_parse_tag(b'\x21')
|
||||
self.assertEqual(res, ({'tag':1, 'constructed':True, 'class': 0}, b''))
|
||||
res = utils.bertlv_parse_tag(b'\x81\x23')
|
||||
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class': 2}, b'\x23'))
|
||||
res = utils.bertlv_parse_tag(b'\x1f\x8f\x00\x23')
|
||||
self.assertEqual(res, ({'tag':0xf<<7, 'constructed':False, 'class': 0}, b'\x23'))
|
||||
|
||||
def test_BerTlvLenDec(self):
|
||||
self.assertEqual(utils.bertlv_encode_len(1), b'\x01')
|
||||
self.assertEqual(utils.bertlv_encode_len(127), b'\x7f')
|
||||
self.assertEqual(utils.bertlv_encode_len(128), b'\x81\x80')
|
||||
self.assertEqual(utils.bertlv_encode_len(0x123456), b'\x83\x12\x34\x56')
|
||||
|
||||
def test_BerTlvLenEnc(self):
|
||||
self.assertEqual(utils.bertlv_parse_len(b'\x01\x23'), (1, b'\x23'))
|
||||
self.assertEqual(utils.bertlv_parse_len(b'\x7f'), (127, b''))
|
||||
self.assertEqual(utils.bertlv_parse_len(b'\x81\x80'), (128, b''))
|
||||
self.assertEqual(utils.bertlv_parse_len(b'\x83\x12\x34\x56\x78'), (0x123456, b'\x78'))
|
||||
|
||||
def test_BerTlvParseOne(self):
|
||||
res = utils.bertlv_parse_one(b'\x81\x01\x01');
|
||||
self.assertEqual(res, ({'tag':1, 'constructed':False, 'class':2}, 1, b'\x01', b''))
|
||||
|
||||
class TestComprTlv(unittest.TestCase):
|
||||
def test_ComprTlvTagDec(self):
|
||||
res = utils.comprehensiontlv_parse_tag(b'\x12\x23')
|
||||
self.assertEqual(res, ({'tag': 0x12, 'comprehension': False}, b'\x23'))
|
||||
res = utils.comprehensiontlv_parse_tag(b'\x92')
|
||||
self.assertEqual(res, ({'tag': 0x12, 'comprehension': True}, b''))
|
||||
res = utils.comprehensiontlv_parse_tag(b'\x7f\x12\x34')
|
||||
self.assertEqual(res, ({'tag': 0x1234, 'comprehension': False}, b''))
|
||||
res = utils.comprehensiontlv_parse_tag(b'\x7f\x82\x34\x56')
|
||||
self.assertEqual(res, ({'tag': 0x234, 'comprehension': True}, b'\x56'))
|
||||
|
||||
def test_ComprTlvTagEnc(self):
|
||||
res = utils.comprehensiontlv_encode_tag(0x12)
|
||||
self.assertEqual(res, b'\x12')
|
||||
res = utils.comprehensiontlv_encode_tag({'tag': 0x12})
|
||||
self.assertEqual(res, b'\x12')
|
||||
res = utils.comprehensiontlv_encode_tag({'tag': 0x12, 'comprehension':True})
|
||||
self.assertEqual(res, b'\x92')
|
||||
res = utils.comprehensiontlv_encode_tag(0x1234)
|
||||
self.assertEqual(res, b'\x7f\x12\x34')
|
||||
res = utils.comprehensiontlv_encode_tag({'tag': 0x1234, 'comprehension':True})
|
||||
self.assertEqual(res, b'\x7f\x92\x34')
|
||||
|
||||
class TestDgiTlv(unittest.TestCase):
|
||||
def test_DgiTlvLenEnc(self):
|
||||
self.assertEqual(utils.dgi_encode_len(10), b'\x0a')
|
||||
self.assertEqual(utils.dgi_encode_len(254), b'\xfe')
|
||||
self.assertEqual(utils.dgi_encode_len(255), b'\xff\x00\xff')
|
||||
self.assertEqual(utils.dgi_encode_len(65535), b'\xff\xff\xff')
|
||||
with self.assertRaises(ValueError):
|
||||
utils.dgi_encode_len(65536)
|
||||
|
||||
def testDgiTlvLenDec(self):
|
||||
self.assertEqual(utils.dgi_parse_len(b'\x0a\x0b'), (10, b'\x0b'))
|
||||
self.assertEqual(utils.dgi_parse_len(b'\xfe\x0b'), (254, b'\x0b'))
|
||||
self.assertEqual(utils.dgi_parse_len(b'\xff\x00\xff\x0b'), (255, b'\x0b'))
|
||||
|
||||
class TestLuhn(unittest.TestCase):
|
||||
def test_verify(self):
|
||||
utils.verify_luhn('8988211000000530082')
|
||||
|
||||
Reference in New Issue
Block a user