mirror of https://gerrit.osmocom.org/pysim
Change-Id: I23cd37fc06b6e2d21964fd4f2694d9ee3c6012d4sysmocom/factory
parent
1e42420e57
commit
46bc6d25d6
@ -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}) |
@ -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 |
@ -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 |
@ -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) |
@ -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) |
Loading…
Reference in new issue