aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@gnumonks.org>2010-11-23 01:28:44 +0100
committerHarald Welte <laforge@gnumonks.org>2010-11-23 01:28:44 +0100
commit6dc5145663b4bc53e775c0bbe5c4d6fef16c56cf (patch)
treec9923e26b12844090e723982d4046345c4121dc2
parent63722fd99bdd303ca1c549fd9c67963c35f12d29 (diff)
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)
-rwxr-xr-xpySim.py172
1 files changed, 139 insertions, 33 deletions
diff --git a/pySim.py b/pySim.py
index 14de30b..9bd3a2c 100755
--- a/pySim.py
+++ b/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