mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-16 18:38:32 +03:00
pySim: Add support for PC/SC readers
Support for PC/SC can be used by simply specifying the '-p 0' option at the command line (indicating PC/SC reader 0, i.e. the first)
This commit is contained in:
172
pySim.py
172
pySim.py
@@ -8,6 +8,7 @@
|
||||
#
|
||||
#
|
||||
# Copyright (C) 2009 Sylvain Munaut <tnt@246tNt.com>
|
||||
# Copyright (C) 2010 Harald Welte <laforge@gnumonks.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
|
||||
@@ -27,6 +28,11 @@ import time
|
||||
import serial
|
||||
import sys
|
||||
|
||||
from smartcard.Exceptions import NoCardException
|
||||
from smartcard.System import readers
|
||||
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver
|
||||
from smartcard.util import toBytes
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Utils
|
||||
@@ -38,6 +44,9 @@ def h2b(s):
|
||||
def b2h(s):
|
||||
return ''.join(['%02x'%ord(x) for x in s])
|
||||
|
||||
def i2h(s):
|
||||
return ''.join(['%02x'%(x) for x in s])
|
||||
|
||||
def swap_nibbles(s):
|
||||
return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
|
||||
|
||||
@@ -52,7 +61,7 @@ def scan_all(base, start=0, end=65536):
|
||||
s.select_file(base)
|
||||
for v in range(start, end):
|
||||
try:
|
||||
data, sw = s.send_apdu('a0a4000002%04x' % v)
|
||||
data, sw = s._tp.send_apdu('a0a4000002%04x' % v)
|
||||
if sw == '9000':
|
||||
rv.append( (v, data, sw) )
|
||||
s.select_file(base)
|
||||
@@ -66,7 +75,7 @@ def scan_all(base, start=0, end=65536):
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Main serial communication
|
||||
# Transport Link for serial (RS232) based readers included with simcard
|
||||
# -------------------------------------------------------------------------{{{
|
||||
|
||||
import exceptions
|
||||
@@ -78,7 +87,6 @@ class ProtocolError(exceptions.Exception):
|
||||
|
||||
|
||||
class SerialSimLink(object):
|
||||
|
||||
def __init__(self, device='/dev/ttyUSB0', baudrate=9600, rst='-rts', debug=False):
|
||||
self._sl = serial.Serial(
|
||||
port = device,
|
||||
@@ -260,10 +268,98 @@ class SerialSimLink(object):
|
||||
raise RuntimeError("SW match failed ! Expected %s and got %s." % (sw.lower(), rv[1]))
|
||||
return rv
|
||||
|
||||
# }}}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Transport Link for a standard PC/SC reader
|
||||
# -------------------------------------------------------------------------{{{
|
||||
|
||||
class PcscSimLink(object):
|
||||
def __init__(self, reader_number=0, observer=0):
|
||||
r = readers();
|
||||
try:
|
||||
self._con = r[reader_number].createConnection()
|
||||
if (observer):
|
||||
observer = ConsoleCardConnectionObserver()
|
||||
self._con.addObserver(observer)
|
||||
self._con.connect()
|
||||
#print r[reader_number], b2h(self._con.getATR())
|
||||
except NoCardException:
|
||||
raise NoCardError()
|
||||
|
||||
def __del__(self):
|
||||
self._con.disconnect()
|
||||
return
|
||||
|
||||
def reset_card(self):
|
||||
self._con.disconnect()
|
||||
try:
|
||||
self._con.connect()
|
||||
except NoCardException:
|
||||
raise NoCardError()
|
||||
return 1
|
||||
|
||||
def send_apdu_raw(self, pdu):
|
||||
"""send_apdu_raw(pdu): Sends an APDU with minimal processing
|
||||
|
||||
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
|
||||
return : tuple(data, sw), where
|
||||
data : string (in hex) of returned data (ex. "074F4EFFFF")
|
||||
sw : string (in hex) of status word (ex. "9000")
|
||||
"""
|
||||
apdu = toBytes(pdu)
|
||||
|
||||
data, sw1, sw2 = self._con.transmit(apdu)
|
||||
|
||||
sw = [sw1, sw2]
|
||||
|
||||
# Return value
|
||||
return i2h(data), i2h(sw)
|
||||
|
||||
def send_apdu(self, pdu):
|
||||
"""send_apdu(pdu): Sends an APDU and auto fetch response data
|
||||
|
||||
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
|
||||
return : tuple(data, sw), where
|
||||
data : string (in hex) of returned data (ex. "074F4EFFFF")
|
||||
sw : string (in hex) of status word (ex. "9000")
|
||||
"""
|
||||
data, sw = self.send_apdu_raw(pdu)
|
||||
|
||||
if (sw is not None) and (sw[0:2] == '9f'):
|
||||
pdu_gr = pdu[0:2] + 'c00000' + sw[2:4]
|
||||
data, sw = self.send_apdu_raw(pdu_gr)
|
||||
|
||||
return data, sw
|
||||
|
||||
def send_apdu_checksw(self, pdu, sw="9000"):
|
||||
"""send_apdu_checksw(pdu,sw): Sends an APDU and check returned SW
|
||||
|
||||
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
|
||||
sw : string of 4 hexadecimal characters (ex. "9000")
|
||||
return : tuple(data, sw), where
|
||||
data : string (in hex) of returned data (ex. "074F4EFFFF")
|
||||
sw : string (in hex) of status word (ex. "9000")
|
||||
"""
|
||||
rv = self.send_apdu(pdu)
|
||||
if sw.lower() != rv[1]:
|
||||
raise RuntimeError("SW match failed ! Expected %s and got %s." % (sw.lower(), rv[1]))
|
||||
return rv
|
||||
|
||||
# }}}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# SIM Card commands according to ISO 7816-4 and TS 11.11
|
||||
# -------------------------------------------------------------------------{{{
|
||||
|
||||
class SimCardCommands(object):
|
||||
def __init__(self, transport):
|
||||
self._tp = transport;
|
||||
|
||||
def select_file(self, dir_list):
|
||||
rv = []
|
||||
for i in dir_list:
|
||||
data, sw = self.send_apdu_checksw("a0a4000002" + i)
|
||||
data, sw = self._tp.send_apdu_checksw("a0a4000002" + i)
|
||||
rv.append(data)
|
||||
return rv
|
||||
|
||||
@@ -274,14 +370,14 @@ class SerialSimLink(object):
|
||||
if length is None:
|
||||
length = int(r[-1][4:8], 16) - offset
|
||||
pdu = 'a0b0%04x%02x' % (offset, (min(256, length) & 0xff))
|
||||
return self.send_apdu(pdu)
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def update_binary(self, ef, data, offset=0):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
ef = [ef]
|
||||
self.select_file(ef)
|
||||
pdu = 'a0d6%04x%02x' % (offset, len(data)/2) + data
|
||||
return self.send_apdu(pdu)
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def read_record(self, ef, rec_no):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
@@ -289,7 +385,7 @@ class SerialSimLink(object):
|
||||
r = self.select_file(ef)
|
||||
rec_length = int(r[-1][28:30], 16)
|
||||
pdu = 'a0b2%02x04%02x' % (rec_no, rec_length)
|
||||
return self.send_apdu(pdu)
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def update_record(self, ef, rec_no, data, force_len=False):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
@@ -302,7 +398,7 @@ class SerialSimLink(object):
|
||||
else:
|
||||
rec_length = len(data)/2
|
||||
pdu = ('a0dc%02x04%02x' % (rec_no, rec_length)) + data
|
||||
return self.send_apdu(pdu)
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def record_size(self, ef):
|
||||
r = self.select_file(ef)
|
||||
@@ -316,8 +412,10 @@ class SerialSimLink(object):
|
||||
if len(rand) != 32:
|
||||
raise ValueError('Invalid rand')
|
||||
self.select_file(['3f00', '7f20'])
|
||||
return self.send_apdu('a088000010' + rand)
|
||||
return self._tp.send_apdu('a088000010' + rand)
|
||||
|
||||
def reset_card(self):
|
||||
return self._tp.reset_card()
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -327,8 +425,8 @@ class SerialSimLink(object):
|
||||
|
||||
class Card(object):
|
||||
|
||||
def __init__(self, sl):
|
||||
self._sl = sl
|
||||
def __init__(self, scc):
|
||||
self._scc = scc
|
||||
|
||||
def _e_iccid(self, iccid):
|
||||
return swap_nibbles(iccid)
|
||||
@@ -345,7 +443,7 @@ class Card(object):
|
||||
return swap_nibbles(lpad('%d' % mcc, 3) + lpad('%d' % mnc, 3))
|
||||
|
||||
def reset(self):
|
||||
self._sl.reset_card()
|
||||
self._scc.reset_card()
|
||||
|
||||
|
||||
class _MagicSimBase(Card):
|
||||
@@ -369,17 +467,17 @@ class _MagicSimBase(Card):
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, sl):
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
for p, l, t in kls._files.values():
|
||||
if not t:
|
||||
continue
|
||||
if sl.record_size(['3f00', '7f4d', p]) != l:
|
||||
if scc.record_size(['3f00', '7f4d', p]) != l:
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
return kls(sl)
|
||||
return kls(scc)
|
||||
|
||||
def _get_count(self):
|
||||
"""
|
||||
@@ -388,7 +486,7 @@ class _MagicSimBase(Card):
|
||||
"""
|
||||
f = self._files['name']
|
||||
|
||||
r = self._sl.select_file(['3f00', '7f4d', f[0]])
|
||||
r = self._scc.select_file(['3f00', '7f4d', f[0]])
|
||||
rec_len = int(r[-1][28:30], 16)
|
||||
tlen = int(r[-1][4:8],16)
|
||||
rec_cnt = (tlen / rec_len) - 1;
|
||||
@@ -400,13 +498,13 @@ class _MagicSimBase(Card):
|
||||
|
||||
def program(self, p):
|
||||
# Go to dir
|
||||
self._sl.select_file(['3f00', '7f4d'])
|
||||
self._scc.select_file(['3f00', '7f4d'])
|
||||
|
||||
# Home PLMN in PLMN_Sel format
|
||||
hplmn = self._e_plmn(p['mcc'], p['mnc'])
|
||||
|
||||
# Operator name ( 3f00/7f4d/8f0c )
|
||||
self._sl.update_record(self._files['name'][0], 2,
|
||||
self._scc.update_record(self._files['name'][0], 2,
|
||||
rpad(b2h(p['name']), 32) + ('%02x' % len(p['name'])) + '01'
|
||||
)
|
||||
|
||||
@@ -430,7 +528,7 @@ class _MagicSimBase(Card):
|
||||
# PLMN_Sel
|
||||
v+= '6f30' + '18' + rpad(hplmn, 36)
|
||||
|
||||
self._sl.update_record(self._files['b_ef'][0], 1,
|
||||
self._scc.update_record(self._files['b_ef'][0], 1,
|
||||
rpad(v, self._files['b_ef'][1]*2)
|
||||
)
|
||||
|
||||
@@ -438,11 +536,11 @@ class _MagicSimBase(Card):
|
||||
# FIXME
|
||||
|
||||
# Write PLMN_Sel forcefully as well
|
||||
r = self._sl.select_file(['3f00', '7f20', '6f30'])
|
||||
r = self._scc.select_file(['3f00', '7f20', '6f30'])
|
||||
tl = int(r[-1][4:8], 16)
|
||||
|
||||
hplmn = self._e_plmn(p['mcc'], p['mnc'])
|
||||
self._sl.update_binary('6f30', hplmn + 'ff' * (tl-3))
|
||||
self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3))
|
||||
|
||||
def erase(self):
|
||||
# Dummy
|
||||
@@ -458,7 +556,7 @@ class _MagicSimBase(Card):
|
||||
# Write
|
||||
for n in range(0,self._get_count()):
|
||||
for k, (msg, ofs) in df.iteritems():
|
||||
self._sl.update_record(['3f00', '7f4d', k], n + ofs, msg)
|
||||
self._scc.update_record(['3f00', '7f4d', k], n + ofs, msg)
|
||||
|
||||
|
||||
class SuperSim(_MagicSimBase):
|
||||
@@ -497,14 +595,14 @@ class FakeMagicSim(Card):
|
||||
name = 'fakemagicsim'
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, sl):
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
if sl.record_size(['3f00', '000c']) != 0x5a:
|
||||
if scc.record_size(['3f00', '000c']) != 0x5a:
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
return kls(sl)
|
||||
return kls(scc)
|
||||
|
||||
def _get_infos(self):
|
||||
"""
|
||||
@@ -512,7 +610,7 @@ class FakeMagicSim(Card):
|
||||
and entry size
|
||||
"""
|
||||
|
||||
r = self._sl.select_file(['3f00', '000c'])
|
||||
r = self._scc.select_file(['3f00', '000c'])
|
||||
rec_len = int(r[-1][28:30], 16)
|
||||
tlen = int(r[-1][4:8],16)
|
||||
rec_cnt = (tlen / rec_len) - 1;
|
||||
@@ -524,11 +622,11 @@ class FakeMagicSim(Card):
|
||||
|
||||
def program(self, p):
|
||||
# Home PLMN
|
||||
r = self._sl.select_file(['3f00', '7f20', '6f30'])
|
||||
r = self._scc.select_file(['3f00', '7f20', '6f30'])
|
||||
tl = int(r[-1][4:8], 16)
|
||||
|
||||
hplmn = self._e_plmn(p['mcc'], p['mnc'])
|
||||
self._sl.update_binary('6f30', hplmn + 'ff' * (tl-3))
|
||||
self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3))
|
||||
|
||||
# Get total number of entries and entry size
|
||||
rec_cnt, rec_len = self._get_infos()
|
||||
@@ -544,7 +642,7 @@ class FakeMagicSim(Card):
|
||||
rpad(p['smsp'], 20) + # 10b SMSP (padded with ff if needed)
|
||||
10*'f' # 5b (unknown ...)
|
||||
)
|
||||
self._sl.update_record('000c', 1, entry)
|
||||
self._scc.update_record('000c', 1, entry)
|
||||
|
||||
def erase(self):
|
||||
# Get total number of entries and entry size
|
||||
@@ -553,7 +651,7 @@ class FakeMagicSim(Card):
|
||||
# Erase all entries
|
||||
entry = 'ff' * rec_len
|
||||
for i in range(0, rec_cnt):
|
||||
self._sl.update_record('000c', 1+i, entry)
|
||||
self._scc.update_record('000c', 1+i, entry)
|
||||
|
||||
|
||||
# In order for autodetection ...
|
||||
@@ -580,6 +678,10 @@ def parse_options():
|
||||
help="Serial Device for SIM access [default: %default]",
|
||||
default="/dev/ttyUSB0",
|
||||
)
|
||||
parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", metavar="PCSC",
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
|
||||
help="Baudrate used for SIM access [default: %default]",
|
||||
default=9600,
|
||||
@@ -777,7 +879,11 @@ if __name__ == '__main__':
|
||||
print_parameters(cp)
|
||||
|
||||
# Connect to the card
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
if opts.pcsc_dev is None:
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
sl = PcscSimLink(0, observer=0)
|
||||
scc = SimCardCommands(transport=sl)
|
||||
|
||||
# Detect type if needed
|
||||
card = None
|
||||
@@ -785,7 +891,7 @@ if __name__ == '__main__':
|
||||
|
||||
if opts.type == "auto":
|
||||
for kls in _cards_classes:
|
||||
card = kls.autodetect(sl)
|
||||
card = kls.autodetect(scc)
|
||||
if card:
|
||||
print "Autodetected card type %s" % card.name
|
||||
card.reset()
|
||||
@@ -796,7 +902,7 @@ if __name__ == '__main__':
|
||||
sys.exit(-1)
|
||||
|
||||
elif opts.type in ctypes:
|
||||
card = ctypes[opts.type](sl)
|
||||
card = ctypes[opts.type](scc)
|
||||
|
||||
else:
|
||||
print "Unknown card type %s" % opts.type
|
||||
|
||||
Reference in New Issue
Block a user