aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@osmocom.org>2020-03-27 00:28:53 +0100
committerHarald Welte <laforge@osmocom.org>2020-09-15 10:47:45 +0200
commit46bc6d25d62321b5bac0c6b8615e684f14ae293f (patch)
treefebd0e27324ef265d65edba4831405fff63c35b6
parent1e42420e576cf8f1626ee4d1500aa46890937135 (diff)
HACK: support for SIM factory file formatssysmocom/factory
-rwxr-xr-xexecute_ipr.py141
-rw-r--r--format_ipr.py55
-rw-r--r--format_ldr.py74
-rwxr-xr-xldr_to_ipr.py84
-rw-r--r--script_format.py13
5 files changed, 367 insertions, 0 deletions
diff --git a/execute_ipr.py b/execute_ipr.py
new file mode 100755
index 0000000..715ad18
--- /dev/null
+++ b/execute_ipr.py
@@ -0,0 +1,141 @@
+#!/usr/bin/python3
+
+from pySim.transport.pcsc import PcscSimLink
+from pySim.utils import enc_iccid, enc_imsi
+
+from smartcard.Exceptions import NoCardException, CardRequestTimeoutException
+from smartcard.System import readers
+
+from lark import Lark, Transformer, Token, Tree
+import sys
+
+from format_ipr import ScriptFormatIPR
+
+class DataTransform():
+ ''' Transform raw/logical input data into the format use on the SIM card,
+ like encoding the PIN from '1234' -> 3132334 or IMSI encoding'''
+ def transform(self, inp):
+ outp = {}
+ for k in inp.keys():
+ f = getattr(self, 'xfrm_'+k, None)
+ if f != None:
+ outp[k] = f(inp[k])
+ else:
+ outp[k] = inp[k]
+ return outp
+
+ def xfrm_PIN(self, pin):
+ ret = ''
+ for c in str(pin):
+ ret += '3%c' % c
+ return ret
+ def xfrm_PIN1(self, pin):
+ return self.xfrm_PIN(pin)
+ def xfrm_PIN2(self, pin):
+ return self.xfrm_PIN(pin)
+ def xfrm_PUK1(self, pin):
+ return self.xfrm_PIN(pin)
+ def xfrm_PUK2(self, pin):
+ return self.xfrm_PIN(pin)
+ def xfrm_ADM1(self, pin):
+ return self.xfrm_PIN(pin)
+ def xfrm_ADM2(self, pin):
+ return self.xfrm_PIN(pin)
+ def xfrm_IMSI(self, imsi):
+ return enc_imsi(imsi)
+ def xfrm_ICCID(self, iccid):
+ # TODO: calculate luhn check digit
+ return enc_iccid(iccid)
+
+
+def expand_cmd_template(cmd, templates):
+ ''' Take a single command, supstituting all [] template keys with data from 'template' '''
+ ret = ""
+ for e in cmd:
+ if e[0] == 'hexstr':
+ ret += e[1]
+ if e[0] == 'key':
+ ret += templates[e[1]]
+ return ret
+
+def match_sw(actual_sw, sw_match):
+ ''' Check if actual_sw matches any of the templates given in sw_match'''
+ def match_sw_single(actual_sw, match):
+ match = match.lower()
+ if 'x' in match:
+ FIXME
+ else:
+ if actual_sw.lower() == match:
+ return True
+ return False
+
+ if sw_match == []:
+ return True
+
+ for m in sw_match:
+ if match_sw_single(actual_sw, m):
+ return True
+
+ return False
+
+
+def execute_ipr_raw(s, sl, dynamic_data_raw = {}):
+ """ translate a single LDR statement to IPR format. """
+ if s == None:
+ None
+ elif s == 'reset':
+ print("RESET")
+ sl.reset_card()
+ elif s[0] == 'rem':
+ print("REM %s" % (s[1]))
+ elif s[0] == 'cmd':
+ d = s[1]
+ req = expand_cmd_template(d['req'], dynamic_data_raw)
+ rsp = d['rsp']
+ print("\tREQ: %s, EXP: %s" % (req, rsp))
+ (data, sw) = sl.send_apdu_raw(req)
+ if not match_sw(sw, rsp):
+ raise ValueError("SW %s doesn't match expected %s" % (sw, rsp))
+ print("\tRSP: %s\n" % (sw))
+
+def execute_ipr(s, sl, dynamic_data = {}):
+ """ translate a single LDR statement to IPR format; optionally substitute dynamic_data. """
+ xf = DataTransform()
+ return execute_ipr_raw(s, sl, xf.transform(dynamic_data))
+
+
+'''Dictionaries like this must be generated for each card to be programmed'''
+demo_dict = {
+ 'PIN1': '1234',
+ 'PIN2': '1234',
+ 'PUK1': '12345678',
+ 'PUK2': '12345678',
+ 'ADM1': '11111111',
+
+ 'KIC1': '100102030405060708090a0b0c0d0e0f',
+ 'KID1': '101102030405060708090a0b0c0d0e0f',
+ 'KIK1': '102102030405060708090a0b0c0d0e0f',
+
+ 'KIC2': '200102030405060708090a0b0c0d0e0f',
+ 'KID2': '201102030405060708090a0b0c0d0e0f',
+ 'KIK2': '202102030405060708090a0b0c0d0e0f',
+
+ 'KIC3': '300102030405060708090a0b0c0d0e0f',
+ 'KID3': '301102030405060708090a0b0c0d0e0f',
+ 'KIK3': '302102030405060708090a0b0c0d0e0f',
+
+ 'ICCID': '012345678901234567',
+ 'IMSI': '001010123456789',
+ 'ACC': '0200',
+ 'KI': '000102030405060708090a0b0c0d0e0f',
+ 'OPC': '101112131415161718191a1b1c1d1e1f',
+ 'VERIFY_ICCID': '0001020304050608090a0b0c0d0e0f',
+ }
+
+
+sl = PcscSimLink(0)
+
+infile_name = sys.argv[1]
+
+fmt = ScriptFormatIPR()
+fmt.parse_process_file(infile_name, execute_ipr, {'sl':sl, 'dynamic_data':demo_dict})
diff --git a/format_ipr.py b/format_ipr.py
new file mode 100644
index 0000000..940e318
--- /dev/null
+++ b/format_ipr.py
@@ -0,0 +1,55 @@
+from lark import Lark, Transformer, Token, Tree
+from script_format import ScriptFormat
+from format_ldr import LdrXfrm
+
+class IprXfrm(LdrXfrm):
+ """ transform the parse tree into a more easily consumable form """
+ def key(self, items):
+ return ('key', ''.join(list(items)))
+ def req(self, items):
+ return items[:-1]
+ def rsp(self, items):
+ return items[:-1]
+ #def NEWLINE(self, items):
+ #return None
+
+
+class ScriptFormatIPR(ScriptFormat):
+ # parser for the IPR file format as used by the SIM card factory
+ ipr_parser = Lark(r"""
+ script: statement*
+ ?statement: cmd | rst | rem | NEWLINE
+
+ NONL: /[^\n]/+
+ rem: "//" NONL? NEWLINE
+
+ ALNUM: DIGIT | LETTER | "_"
+ key: "[" ALNUM+ "]"
+
+ cmd: req rsp
+
+ req: "I:" [hexstr|key]+ NEWLINE
+ hexstr: HEX_ITEM+
+ HEX_ITEM: HEXDIGIT ~ 2
+
+ rsp: "O:" swpattern? NEWLINE
+ swpattern: HEX_OR_X ~ 4
+ HEX_OR_X: HEXDIGIT | "X" | "x"
+
+ rst: "RESET" NEWLINE
+
+ %import common.ESCAPED_STRING -> STRING
+ %import common.WS_INLINE
+ %import common.HEXDIGIT
+ %import common.DIGIT
+ %import common.LETTER
+ %import common.NEWLINE
+ %ignore WS_INLINE
+
+ """, start='script', parser='lalr')#, lexer='standard')
+
+ def parse_xform(self, text):
+ tree = self.ipr_parser.parse(text)
+ #print(tree.pretty())
+ p = IprXfrm().transform(tree)
+ return p
diff --git a/format_ldr.py b/format_ldr.py
new file mode 100644
index 0000000..7b4bb1f
--- /dev/null
+++ b/format_ldr.py
@@ -0,0 +1,74 @@
+from lark import Lark, Transformer, Token, Tree
+from script_format import ScriptFormat
+
+class LdrXfrm(Transformer):
+ """ transform the parse tree into a more easily consumable form """
+ def rst(self, items):
+ return ('reset')
+ def NONL(self, items):
+ return ''.join([i for i in items.value])
+ def rem(self, items):
+ return ('rem', items[0])
+ def swmatch(self, items):
+ return ('swmatch', items)
+ def cmd(self, items):
+ return ('cmd', {'req': items[0], 'rsp': items[1]})
+
+ def todigit(self, item):
+ """ convert from Token/Tree to raw hex-digit """
+ if isinstance(item, Token):
+ return item.value
+ elif isinstance(item, Tree):
+ return item.data
+ def hex_item(self, items):
+ """ return one byte as two-digit HEX string """
+ return "%s%s" % (items[0].value, items[1].value)
+ def hexstr(self, items):
+ """ return list of two-digit HEX strings """
+ return ('hexstr', ''.join(list(items)))
+ def swpattern(self, items):
+ """ return list of four HEX nibbles (or 'x' as wildcard) """
+ arr = [self.todigit(x) for x in items]
+ return ''.join(arr)
+
+class ScriptFormatLDR(ScriptFormat):
+ # parser for the LDR file format as generated by Simulity Profile Editor
+ ldr_parser = Lark(r"""
+ script: statement*
+ ?statement: cmd | rst | rem | NEWLINE
+
+ BSLASH: "\\\n"
+ %ignore BSLASH
+
+ NONL: /[^\n]/+
+ rem: "REM" NONL? NEWLINE
+
+ ALNUM: DIGIT | LETTER | "_"
+ key: "[" ALNUM+ "]"
+
+ cmd: "CMD" hexstr [swmatch] NEWLINE
+ cmd_item: hexstr | key
+ hexstr: hex_item+
+ hex_item: HEXDIGIT HEXDIGIT
+
+ swmatch: "(" swpattern ("," swpattern)* ")"
+ swpattern: hex_or_x hex_or_x hex_or_x hex_or_x
+ ?hex_or_x: HEXDIGIT | "X" -> x | "x" -> x
+
+ rst: "RST" NEWLINE
+
+ %import common.ESCAPED_STRING -> STRING
+ %import common.WS_INLINE
+ %import common.HEXDIGIT
+ %import common.DIGIT
+ %import common.LETTER
+ %import common.NEWLINE
+ %ignore WS_INLINE
+
+ """, start='script')
+
+ def parse_xform(self, text):
+ tree = self.ldr_parser.parse(text)
+ #print(tree.pretty())
+ p = LdrXfrm().transform(tree)
+ return p
diff --git a/ldr_to_ipr.py b/ldr_to_ipr.py
new file mode 100755
index 0000000..dbbd911
--- /dev/null
+++ b/ldr_to_ipr.py
@@ -0,0 +1,84 @@
+#!/usr/bin/python3
+
+from lark import Lark, Transformer, Token, Tree
+import sys
+
+from format_ldr import ScriptFormatLDR
+from format_ipr import ScriptFormatIPR
+
+def split_hex(value):
+ """ split a string of hex digits into groups (bytes) of two digits. """
+ return ' '.join(value[i:i+2] for i in range(0, len(value), 2))
+
+def expand_cmd(cmd):
+ ret = ""
+ for e in cmd:
+ if e[0] == 'hexstr':
+ ret += e[1]
+ else:
+ raise ValueError("Unsupported '%s'" % (e[0]))
+ return ret
+
+
+def ldr_stmt_to_ipr(s):
+ """ translate a single LDR statement to IPR format. """
+ if s == None:
+ None
+ elif s == 'reset':
+ print("RESET")
+ print("")
+ elif s[0] == 'rem':
+ print("//\t%s" % s[1])
+ elif s[0] == 'cmd':
+ cmd = s[1]
+ req = cmd['req']
+ rsp = cmd['rsp']
+ print("I: %s" % split_hex(expand_cmd([req])))
+ if rsp != None and len(rsp) != 1:
+ if rsp[0] != 'swmatch' or len(rsp[1]) != 1:
+ raise ValueError("Unsupported '%s'" % (rsp))
+ print("O: %s" % rsp[1][0])
+ else:
+ print("O:")
+ print("")
+ else:
+ print("Unknown %s" % (s.pretty()))
+ raise ValueError()
+
+
+test_text = '''
+RST
+CMD E0 CA DF 1F 13
+CMD E0 CA DF 1F (90 00)
+CMD E0 CA DF 1F (61 XX, 90 00)
+REM foo bar
+CMD E4 DA DF 20 09 EA 53 F8 D7 64 1E D9 88 00 \\
+ (90 00 , 6B 00)
+'''
+
+
+def run_statement(s):
+ print(s)
+
+def fii(s):
+ if s.data == 'rst':
+ print("=> RESET")
+ # FIXME: actually perform card reset
+ elif s.data == 'rem':
+ print(s)
+ elif s.data == 'cmd':
+ #print(s)
+ cmd = s.children[0]
+ print(s.pretty())
+ # FIXME: if swmatch: match all contained swpattern
+ else:
+ print("Unknown %s" % (s.pretty()))
+ raise ValueError()
+
+
+#process_ldr(test_text, run_statement)
+#process_ldr(test_text, ldr_stmt_to_ipr)
+
+fmt = ScriptFormatLDR()
+fmt.parse_process_file(sys.argv[1], ldr_stmt_to_ipr)
+#fmt.parse_process_file(sys.argv[1], run_statement)
diff --git a/script_format.py b/script_format.py
new file mode 100644
index 0000000..18d4c1c
--- /dev/null
+++ b/script_format.py
@@ -0,0 +1,13 @@
+
+class ScriptFormat():
+
+ def parse_process(self, text, stmt_cb, stmt_cb_kwargs={}):
+ p = self.parse_xform(text)
+ #print(p.pretty())
+ for stmt in p.children:
+ stmt_cb(stmt, **stmt_cb_kwargs)
+
+ def parse_process_file(self, fname, stmt_cb, stmt_cb_kwargs={}):
+ f = open(fname, "r")
+ text = f.read()
+ return self.parse_process(text, stmt_cb, stmt_cb_kwargs)