aboutsummaryrefslogtreecommitdiffstats
path: root/pySim/transport/__init__.py
blob: 14f5aafd59c687e721ba4af1732f71977af564ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# -*- coding: utf-8 -*-

""" pySim: PCSC reader transport link base
"""

import argparse
from typing import Optional

from pySim.exceptions import *
from pySim.construct import filter_dict
from pySim.utils import sw_match, b2h, h2b, i2h

#
# Copyright (C) 2009-2010  Sylvain Munaut <tnt@246tNt.com>
# Copyright (C) 2021 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 ApduTracer:
	def trace_command(self, cmd):
		pass

	def trace_response(self, cmd, sw, resp):
		pass


class LinkBase(object):
	"""Base class for link/transport to card."""

	def __init__(self, sw_interpreter=None, apdu_tracer=None):
		self.sw_interpreter = sw_interpreter
		self.apdu_tracer = apdu_tracer

	def set_sw_interpreter(self, interp):
		"""Set an (optional) status word interpreter."""
		self.sw_interpreter = interp

	def wait_for_card(self, timeout:int=None, newcardonly:bool=False):
		"""Wait for a card and connect to it

		Args:
		   timeout : Maximum wait time in seconds (None=no timeout)
		   newcardonly : Should we wait for a new card, or an already inserted one ?
		"""
		pass

	def connect(self):
		"""Connect to a card immediately
		"""
		pass

	def disconnect(self):
		"""Disconnect from card
		"""
		pass

	def reset_card(self):
		"""Resets the card (power down/up)
		"""
		pass

	def send_apdu_raw(self, pdu:str):
		"""Sends an APDU with minimal processing

		Args:
		   pdu : string of hexadecimal characters (ex. "A0A40000023F00")
		Returns:
		   tuple(data, sw), where
				data : string (in hex) of returned data (ex. "074F4EFFFF")
				sw   : string (in hex) of status word (ex. "9000")
		"""
		if self.apdu_tracer:
			self.apdu_tracer.trace_command(pdu)
		(data, sw) = self._send_apdu_raw(pdu)
		if self.apdu_tracer:
			self.apdu_tracer.trace_response(pdu, sw, data)
		return (data, sw)

	def send_apdu(self, pdu):
		"""Sends an APDU and auto fetch response data

		Args:
		   pdu : string of hexadecimal characters (ex. "A0A40000023F00")
		Returns:
		   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)

		# When whe have sent the first APDU, the SW may indicate that there are response bytes
		# available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
		# xx is the number of response bytes available.
		# See also:
		# SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed
		# SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2
		if (sw is not None) and ((sw[0:2] == '9f') or (sw[0:2] == '61')):
			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"):
		"""Sends an APDU and check returned SW

		Args:
		   pdu : string of hexadecimal characters (ex. "A0A40000023F00")
		   sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain
				digits using a '?' to add some ambiguity if needed.
		Returns:
			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 not sw_match(rv[1], sw):
			raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter)
		return rv

	def send_apdu_constr(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr):
		"""Build and sends an APDU using a 'construct' definition; parses response.

		Args:
			cla : string (in hex) ISO 7816 class byte
			ins : string (in hex) ISO 7816 instruction byte
			p1 : string (in hex) ISO 7116 Parameter 1 byte
			p2 : string (in hex) ISO 7116 Parameter 2 byte
			cmd_cosntr : defining how to generate binary APDU command data
			cmd_data : command data passed to cmd_constr
			resp_cosntr : defining how to decode  binary APDU response data
		Returns:
			Tuple of (decoded_data, sw)
		"""
		cmd = cmd_constr.build(cmd_data) if cmd_data else ''
		p3 = i2h([len(cmd)])
		pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)])
		(data, sw) = self.send_apdu(pdu)
		if data:
			# filter the resulting dict to avoid '_io' members inside
			rsp = filter_dict(resp_constr.parse(h2b(data)))
		else:
			rsp = None
		return (rsp, sw)

	def send_apdu_constr_checksw(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr,
								 sw_exp="9000"):
		"""Build and sends an APDU using a 'construct' definition; parses response.

		Args:
			cla : string (in hex) ISO 7816 class byte
			ins : string (in hex) ISO 7816 instruction byte
			p1 : string (in hex) ISO 7116 Parameter 1 byte
			p2 : string (in hex) ISO 7116 Parameter 2 byte
			cmd_cosntr : defining how to generate binary APDU command data
			cmd_data : command data passed to cmd_constr
			resp_cosntr : defining how to decode  binary APDU response data
			exp_sw : string (in hex) of status word (ex. "9000")
		Returns:
			Tuple of (decoded_data, sw)
		"""
		(rsp, sw) = self.send_apdu_constr(cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr)
		if not sw_match(sw, sw_exp):
			raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter)
		return (rsp, sw)

def argparse_add_reader_args(arg_parser):
	"""Add all reader related arguments to the given argparse.Argumentparser instance."""
	serial_group = arg_parser.add_argument_group('Serial Reader')
	serial_group.add_argument('-d', '--device', metavar='DEV', default='/dev/ttyUSB0',
							  help='Serial Device for SIM access')
	serial_group.add_argument('-b', '--baud', dest='baudrate', type=int, metavar='BAUD', default=9600,
							  help='Baud rate used for SIM access')

	pcsc_group = arg_parser.add_argument_group('PC/SC Reader')
	pcsc_group.add_argument('-p', '--pcsc-device', type=int, dest='pcsc_dev', metavar='PCSC', default=None,
							help='PC/SC reader number to use for SIM access')

	modem_group = arg_parser.add_argument_group('AT Command Modem Reader')
	modem_group.add_argument('--modem-device', dest='modem_dev', metavar='DEV', default=None,
							 help='Serial port of modem for Generic SIM Access (3GPP TS 27.007)')
	modem_group.add_argument('--modem-baud', type=int, metavar='BAUD', default=115200,
							 help='Baud rate used for modem port')

	osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader')
	osmobb_group.add_argument('--osmocon', dest='osmocon_sock', metavar='PATH', default=None,
							  help='Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)')

	return arg_parser

def init_reader(opts, **kwargs) -> Optional[LinkBase]:
	"""
	Init card reader driver
	"""
	sl = None # type : :Optional[LinkBase]
	try:
		if opts.pcsc_dev is not None:
			print("Using PC/SC reader interface (dev_number=%u)" % opts.pcsc_dev)
			from pySim.transport.pcsc import PcscSimLink
			sl = PcscSimLink(opts.pcsc_dev, **kwargs)
		elif opts.osmocon_sock is not None:
			print("Using Calypso-based (OsmocomBB) reader interface (sock='%s')" % opts.osmocon_sock)
			from pySim.transport.calypso import CalypsoSimLink
			sl = CalypsoSimLink(sock_path=opts.osmocon_sock, **kwargs)
		elif opts.modem_dev is not None:
			print("Using modem for Generic SIM Access (3GPP TS 27.007) (dev='%s', baud=%u)" %
					(opts.modem_dev, opts.modem_baud))
			from pySim.transport.modem_atcmd import ModemATCommandLink
			sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs)
		else: # Serial reader is default
			print("Using serial reader interface (dev='%s', baud=%u)" % (opts.device, opts.baudrate))
			from pySim.transport.serial import SerialSimLink
			sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate, **kwargs)
		return sl
	except Exception as e:
		print("Card reader initialization failed with exception:\n" + str(e))
		return None