aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVadim Yanitskiy <axilirator@gmail.com>2020-05-09 21:23:37 +0700
committerVadim Yanitskiy <vyanitskiy@sysmocom.de>2020-06-02 21:51:07 +0700
commit29ca8049d6ea5fa27f1f4ea805b2e0d4888e6e24 (patch)
treec2b3da777f89512340c2796d9ae2d1ee0565ed22
parenteb06b45d0edd7843551e78da468a4d907f906993 (diff)
Implement Generic SIM Access interface as per 3GPP TS 27.007
According to 3GPP TS 27.007, sections 8.17 and 8.18, the modem may *optionally* provide Generic and/or Restricted SIM Access to the TE (Terminal Equipment) by means of the AT commands. This basically means that a modem can act as a card reader. Generic SIM Access allows the TE to send raw PDUs in the format as described in 3GPP TS 51.011 directly to the SIM card, while Restricted SIM Access is more limited, and thus is not really interesting to us. This change implements a new transport called ModemATCommandLink, so using it a SIM card can be read and/or programmed without the need to remove it from the modem's socket. A downside of this approach is relatively slow I/O speed compared to PC/SC readers. Tested with Quectel EC20: $ ./pySim-read.py --modem-dev /dev/ttyUSB2 Change-Id: I20bc00315e2c7c298f46283852865c1416047bc6 Signed-off-by: Vadim Yanitskiy <axilirator@gmail.com>
-rwxr-xr-xpySim-prog.py8
-rwxr-xr-xpySim-read.py8
-rw-r--r--pySim/transport/modem_atcmd.py126
-rw-r--r--pySim/utils.py4
4 files changed, 146 insertions, 0 deletions
diff --git a/pySim-prog.py b/pySim-prog.py
index f707c57..601f980 100755
--- a/pySim-prog.py
+++ b/pySim-prog.py
@@ -61,6 +61,14 @@ def parse_options():
help="Which PC/SC reader number for SIM access",
default=None,
)
+ parser.add_option("--modem-device", dest="modem_dev", metavar="DEV",
+ help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)",
+ default=None,
+ )
+ parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD",
+ help="Baudrate used for modem's port [default: %default]",
+ default=115200,
+ )
parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",
help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",
default=None,
diff --git a/pySim-read.py b/pySim-read.py
index df21531..e49a907 100755
--- a/pySim-read.py
+++ b/pySim-read.py
@@ -53,6 +53,14 @@ def parse_options():
help="Which PC/SC reader number for SIM access",
default=None,
)
+ parser.add_option("--modem-device", dest="modem_dev", metavar="DEV",
+ help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)",
+ default=None,
+ )
+ parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD",
+ help="Baudrate used for modem's port [default: %default]",
+ default=115200,
+ )
parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",
help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",
default=None,
diff --git a/pySim/transport/modem_atcmd.py b/pySim/transport/modem_atcmd.py
new file mode 100644
index 0000000..742ae8d
--- /dev/null
+++ b/pySim/transport/modem_atcmd.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+""" pySim: Transport Link for 3GPP TS 27.007 compliant modems
+"""
+
+# Copyright (C) 2020 Vadim Yanitskiy <axilirator@gmail.com>
+#
+# 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/>.
+#
+
+from __future__ import absolute_import
+
+import logging as log
+import serial
+import time
+import re
+
+from pySim.transport import LinkBase
+from pySim.exceptions import *
+
+# HACK: if somebody needs to debug this thing
+# log.root.setLevel(log.DEBUG)
+
+class ModemATCommandLink(LinkBase):
+ def __init__(self, device='/dev/ttyUSB0', baudrate=115200):
+ self._sl = serial.Serial(device, baudrate, timeout=5)
+ self._device = device
+ self._atr = None
+
+ # Trigger initial reset
+ self.reset_card()
+
+ def __del__(self):
+ self._sl.close()
+
+ def send_at_cmd(self, cmd):
+ # Convert from string to bytes, if needed
+ bcmd = cmd if type(cmd) is bytes else cmd.encode()
+ bcmd += b'\r'
+
+ # Send command to the modem
+ log.debug('Sending AT command: %s' % cmd)
+ try:
+ wlen = self._sl.write(bcmd)
+ assert(wlen == len(bcmd))
+ except:
+ raise ReaderError('Failed to send AT command: %s' % cmd)
+
+ # Give the modem some time...
+ time.sleep(0.3)
+
+ # Read the response
+ try:
+ # Skip characters sent back
+ self._sl.read(wlen)
+ # Read the rest
+ rsp = self._sl.read_all()
+
+ # Strip '\r\n'
+ rsp = rsp.strip()
+ # Split into a list
+ rsp = rsp.split(b'\r\n\r\n')
+ except:
+ raise ReaderError('Failed parse response to AT command: %s' % cmd)
+
+ log.debug('Got response from modem: %s' % rsp)
+ return rsp
+
+ def reset_card(self):
+ # Make sure that we can talk to the modem
+ if self.send_at_cmd('AT') != [b'OK']:
+ raise ReaderError('Failed to connect to modem')
+
+ # Reset the modem, just to be sure
+ if self.send_at_cmd('ATZ') != [b'OK']:
+ raise ReaderError('Failed to reset the modem')
+
+ # Make sure that generic SIM access is supported
+ if self.send_at_cmd('AT+CSIM=?') != [b'OK']:
+ raise ReaderError('The modem does not seem to support SIM access')
+
+ log.info('Modem at \'%s\' is ready!' % self._device)
+
+ def connect(self):
+ pass # Nothing to do really ...
+
+ def disconnect(self):
+ pass # Nothing to do really ...
+
+ def wait_for_card(self, timeout=None, newcardonly=False):
+ pass # Nothing to do really ...
+
+ def send_apdu_raw(self, pdu):
+ # Prepare the command as described in 8.17
+ cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu)
+
+ # Send AT+CSIM command to the modem
+ # TODO: also handle +CME ERROR: <err>
+ rsp = self.send_at_cmd(cmd)
+ if len(rsp) != 2 or rsp[-1] != b'OK':
+ raise ReaderError('APDU transfer failed: %s' % str(rsp))
+ rsp = rsp[0] # Get rid of b'OK'
+
+ # Make sure that the response has format: b'+CSIM: %d,\"%s\"'
+ try:
+ result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp)
+ (rsp_pdu_len, rsp_pdu) = result.groups()
+ except:
+ raise ReaderError('Failed to parse response from modem: %s' % rsp)
+
+ # TODO: make sure we have at least SW
+ data = rsp_pdu[:-4].decode()
+ sw = rsp_pdu[-4:].decode()
+ return data, sw
diff --git a/pySim/utils.py b/pySim/utils.py
index 20eb5a8..496b918 100644
--- a/pySim/utils.py
+++ b/pySim/utils.py
@@ -485,6 +485,10 @@ def init_reader(opts):
print("Using Calypso-based (OsmocomBB) reader interface")
from pySim.transport.calypso import CalypsoSimLink
sl = CalypsoSimLink(sock_path=opts.osmocon_sock)
+ elif opts.modem_dev is not None:
+ print("Using modem for Generic SIM Access (3GPP TS 27.007)")
+ from pySim.transport.modem_atcmd import ModemATCommandLink
+ sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud)
else: # Serial reader is default
print("Using serial reader interface")
from pySim.transport.serial import SerialSimLink