2 Commits

Author SHA1 Message Date
Vadim Yanitskiy
f6a806494c shadysim.py: fix: do not apply redundant 8 * '00' padding
When the application message payload is encrypted with any variant
of DES, the length of the ciphertext has to be a multiple of 8 bytes
- hence if the plaintext length is not a multiple of 8 bytes, the
plaintext needs to be padded.

If the ciphertext is already aligned, the current logic would append
8 redundant padding octets.  The resulting encrypted message should
be considered malformed per standard specs, but sysmoUSIM-SJS1 cards
are liberal in what they accept in this instance thus the bug went
unnoticed.  The newer sysmoISIM-SJA2 cards do not accept such
malformed messages with invalid padding.

This bug was discovered and reported by the Mother Mychaela, see:
https://lists.osmocom.org/pipermail/openbsc/2021-February/013414.html
2021-02-22 22:35:45 +01:00
Vadim Yanitskiy
8ac76661ce shadysim.py: use string multiplication to add padding 2021-02-22 22:25:43 +01:00
3 changed files with 10 additions and 182 deletions

View File

@@ -31,6 +31,3 @@ class NoCardError(exceptions.Exception):
class ProtocolError(exceptions.Exception):
pass
class ReaderError(exceptions.Exception):
pass

View File

@@ -1,157 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" pySim: Transport Link for Calypso bases phones
"""
#
# Copyright (C) 2018 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 select
import struct
import socket
import os
from pySim.transport import LinkBase
from pySim.exceptions import *
from pySim.utils import h2b, b2h
class L1CTLMessage(object):
# Every (encoded) L1CTL message has the following structure:
# - msg_length (2 bytes, net order)
# - l1ctl_hdr (packed structure)
# - msg_type
# - flags
# - padding (2 spare bytes)
# - ... payload ...
def __init__(self, msg_type, flags = 0x00):
# Init L1CTL message header
self.data = struct.pack("BBxx", msg_type, flags)
def gen_msg(self):
return struct.pack("!H", len(self.data)) + self.data
class L1CTLMessageReset(L1CTLMessage):
# L1CTL message types
L1CTL_RESET_REQ = 0x0d
L1CTL_RESET_IND = 0x07
L1CTL_RESET_CONF = 0x0e
# Reset types
L1CTL_RES_T_BOOT = 0x00
L1CTL_RES_T_FULL = 0x01
L1CTL_RES_T_SCHED = 0x02
def __init__(self, type = L1CTL_RES_T_FULL):
super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
self.data += struct.pack("Bxxx", type)
class L1CTLMessageSIM(L1CTLMessage):
# SIM related message types
L1CTL_SIM_REQ = 0x16
L1CTL_SIM_CONF = 0x17
def __init__(self, pdu):
super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
self.data += pdu
class CalypsoSimLink(LinkBase):
def __init__(self, sock_path = "/tmp/osmocom_l2"):
# Make sure that a given socket path exists
if not os.path.exists(sock_path):
raise ReaderError("There is no such ('%s') UNIX socket" % sock_path)
print("Connecting to osmocon at '%s'..." % sock_path)
# Establish a client connection
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(sock_path)
def __del__(self):
self.sock.close()
def wait_for_rsp(self, exp_len = 128):
# Wait for incoming data (timeout is 3 seconds)
s, _, _ = select.select([self.sock], [], [], 3.0)
if not s:
raise ReaderError("Timeout waiting for card response")
# Receive expected amount of bytes from osmocon
rsp = self.sock.recv(exp_len)
return rsp
def reset_card(self):
# Request FULL reset
req_msg = L1CTLMessageReset()
self.sock.send(req_msg.gen_msg())
# Wait for confirmation
rsp = self.wait_for_rsp()
rsp_msg = struct.unpack_from("!HB", rsp)
if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF:
raise ReaderError("Failed to reset Calypso PHY")
def connect(self):
self.reset_card()
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):
"""see LinkBase.send_apdu_raw"""
# Request FULL reset
req_msg = L1CTLMessageSIM(h2b(pdu))
self.sock.send(req_msg.gen_msg())
# Read message length first
rsp = self.wait_for_rsp(struct.calcsize("!H"))
msg_len = struct.unpack_from("!H", rsp)[0]
if msg_len < struct.calcsize("BBxx"):
raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF")
# Read the whole message then
rsp = self.sock.recv(msg_len)
# Verify L1CTL header
hdr = struct.unpack_from("BBxx", rsp)
if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF:
raise ReaderError("Unexpected L1CTL message received")
# Verify the payload length
offset = struct.calcsize("BBxx")
if len(rsp) <= offset:
raise ProtocolError("Empty response from SIM?!?")
# Omit L1CTL header
rsp = rsp[offset:]
# Unpack data and SW
data = rsp[:-2]
sw = rsp[-2:]
return b2h(data), b2h(sw)

View File

@@ -114,10 +114,10 @@ class AppLoaderCommands(object):
# Padding if Ciphering is used
if ((spi_1 & 0x04) != 0): # check ciphering bit
len_cipher = 6 + len_sig + (len(data) / 2)
pad_cnt = 8 - (len_cipher % 8) # 8 Byte blocksize for DES-CBC (TODO: different padding)
# TODO: there is probably a better way to add "pad_cnt" padding bytes
for i in range(0, pad_cnt):
data = data + '00';
# 8 Byte blocksize for DES-CBC (TODO: different padding)
if len_cipher % 8 > 0:
pad_cnt = 8 - (len_cipher % 8)
data += '00' * pad_cnt
# CHL + SPI first octet
part_head = ('%02x' % (0x0D + len_sig)) + ('%02x' % (spi_1))
@@ -370,11 +370,8 @@ class AppLoaderCommands(object):
#------
parser = argparse.ArgumentParser(description='Tool for Toorcamp SIMs.')
parser.add_argument('-I', '--interface', default='pcsc',
choices = ['pcsc', 'serial', 'calypso', 'dummy'])
parser.add_argument('-s', '--serialport', default='/dev/ttyUSB0')
parser.add_argument('-p', '--pcsc', nargs='?', const=0, type=int, default=0)
parser.add_argument('-c', '--calypso', default='/tmp/osmocom_l2')
parser.add_argument('-s', '--serialport')
parser.add_argument('-p', '--pcsc', nargs='?', const=0, type=int)
parser.add_argument('-d', '--delete-app')
parser.add_argument('-l', '--load-app')
parser.add_argument('-i', '--install')
@@ -404,27 +401,18 @@ parser.add_argument('--smpp', action='store_true')
args = parser.parse_args()
if args.interface == "pcsc":
if args.pcsc is None:
raise argparse.ArgumentTypeError("You need to specify PC/SC reader using -p")
if args.pcsc is not None:
from pySim.transport.pcsc import PcscSimLink
sl = PcscSimLink(args.pcsc)
elif args.interface == "serial":
if args.serialport is None:
raise argparse.ArgumentTypeError("You need to specify serial port using -s")
elif args.serialport is not None:
from pySim.transport.serial import SerialSimLink
sl = SerialSimLink(device=args.serialport, baudrate=9600)
elif args.interface == "calypso":
if args.calypso is None:
raise argparse.ArgumentTypeError("You need to specify L1CTL socket path using -c")
from pySim.transport.calypso import CalypsoSimLink
sl = CalypsoSimLink(sock_path=args.calypso)
elif args.interface == "dummy":
elif args.smpp is not None:
class DummySL:
pass
sl = DummySL()
pass
else: # Shall not happen in general, parser.parse_args() would fail
else:
raise RuntimeError("Need to specify either --serialport, --pcsc or --smpp")
sc = SimCardCommands(sl)