From dc45c05b15acce2320f9ae19ee7d91d3d49ce6c6 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Wed, 16 Aug 2017 15:18:09 +0200 Subject: [PATCH] Migration to python 'card' from Benoit Michau There are quite complete python utilities already out there as part of Benoit Micuhau's 'card' python library. They support plenty of SIM, UICC and USIM related bits. Let's use them rather than re- inventing the wheel here. --- card.py | 186 ---------------------------------------- simcard.py | 179 ++++++++++---------------------------- sysmo-usim-tool.sjs1.py | 5 +- sysmo_usimsjs1.py | 2 + 4 files changed, 47 insertions(+), 325 deletions(-) delete mode 100644 card.py diff --git a/card.py b/card.py deleted file mode 100644 index 748a7ff..0000000 --- a/card.py +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -""" -Smartcard Terminal IO Class - -(C) 2017 by Sysmocom s.f.m.c. GmbH -All Rights Reserved - -Author: Philipp Maier - -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 . -""" - -from smartcard.scard import * -import smartcard.util -from utils import * - -# The following class abstract the terminal, however we reference it as "Card", -# because the terminal itsslef is not in the users interest, we are focussed -# on the card all the time. The abstraction done here is very simple and -# implements only the very basic functionality to communicate with a smartcard -# on APDU level. -# -# The classes Card_res_apdu and Card_apdu are helper classes in order to make -# passing the parameters/results simpler. They are not meant to be created -# anwhere else in the code. All handling is done through the Card class. -# -# The method apdu formats an APDU with its basic features (CLA, INS etc..) -# for the user. The user also may set an expected status word (Default is -# 0x9000). The status word is then checked when the transaction is performed -# using the transact method. -# -# The expected status word is a list of two bytes, each of the two bytes may be -# set to None. If this is the case the byte is not checked. If the whole list -# is set to none, the status word is not checked at all. If the status word -# check fails, the swok flag inside the repsonse apdu is set to false and an -# exception is thwron unless the strict parameter is not set to False. -# -# The user may set a dry-flag when calling the transact, then the transaction -# is not performed. Only the log line is printed. This is to verify if the -# transaction would be sent correctly, as some smartcad operations might -# be risky. - - -# Note: Programmed with the help of a blogpost from Ludovic Rousseau: -# https://ludovicrousseau.blogspot.de/2010/04/pcsc-sample-in-python.html - -# A class to make handling of the responses a little simpler -# Note: Do not use directly, Card object will return responses. -class Card_res_apdu(): - - apdu = None - sw = None - swok = True - def __init__(self, apdu, sw): - self.apdu = apdu - self.sw = sw - - def __str__(self): - dump = "APDU:" + hexdump(self.apdu) - dump += " SW:" + hexdump(self.sw) - return dump - - -# A class to make handling of the card input a little simpler -# Note: Do not use directly, use Card.apdu(...) instead -class Card_apdu(): - - apdu = None - sw = None - def __init__(self, cla, ins, p1, p2, p3, data, sw): - self.apdu = [cla, ins, p1, p2, p3] - if data: - self.apdu += data - self.sw = sw - - def __str__(self): - dump = "APDU:" + hexdump(self.apdu) - return dump - - -# A class to abstract smartcard and terminal -class Card(): - - card = None - protocol = None - verbose = None - - # Constructor: Set up connection to smartcard - def __init__(self, verbose = False): - - self.verbose = verbose - - # Eestablish a smartcard terminal context - hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER) - if hresult != SCARD_S_SUCCESS: - print " * Error: Unable to establish smartcard terminal context -- exiting" - exit(1) - - # Select the next available smartcard terminal - hresult, terminals = SCardListReaders(hcontext, []) - if hresult != SCARD_S_SUCCESS or len(terminals) < 1: - print " * Error: No smartcard terminal found -- exiting" - exit(1) - terminal = terminals[0] - print " * Terminal:", terminal - - # Establish connection to smartcard - hresult, hcard, dwActiveProtocol = SCardConnect(hcontext, - terminal, SCARD_SHARE_SHARED, - SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1) - if hresult != SCARD_S_SUCCESS: - print " * Error: No smartcard detected -- exiting" - exit(1) - print " * Protocol: " + str(dwActiveProtocol) - self.card = hcard - self.protocol = dwActiveProtocol - - - # Print debug info - def __debug_print(self, message): - if self.verbose: - print message - - - # Perform smartcard transaction - def transact(self, apdu, dry = False, strict = True): - - # Dry run - if (dry): - self.__debug_print(" Card transaction: " + str(apdu) - + " ==> Dry run: Transaction not executed") - return Card_res_apdu(None,None) - - # Perform transaction - hresult, response = SCardTransmit(self.card, - self.protocol, apdu.apdu) - if hresult != SCARD_S_SUCCESS: - self.__debug_print(" * Card transaction: " + str(apdu) - + " ==> Error: Smartcard transaction failed") - return Card_res_apdu(None,None) - res = Card_res_apdu(response[:-2],response[-2:]) - - self.__debug_print(" Card transaction: " + str(apdu) - + " ==> " + str(res)) - - # Check status word - if apdu.sw: - if apdu.sw[0] and apdu.sw[0] == res.sw[0]: - res.swok = True - elif apdu.sw[0]: - res.swok = False - self.__debug_print(" * Warning: Unexpected status word (SW1)!") - - if apdu.sw[1] and apdu.sw[1] == res.sw[1]: - res.swok = True - elif apdu.sw[1]: - res.swok = False - self.__debug_print(" * Warning: Unexpected status word (SW2)!") - - # If strict mode is turned on, the status word must match, - # otherwise an exception is thrown - if strict and res.swok == False: - raise ValueError('Transaction failed!') - - return res - - - # set up a new APDU - def apdu(self, cla, ins, p1 = 0x00, p2 = 0x00, p3 = 0x00, - data = None, sw = [0x90, 0x00]): - return Card_apdu(cla, ins, p1, p2, p3, data, sw) - - diff --git a/simcard.py b/simcard.py index f617ca1..915aa94 100644 --- a/simcard.py +++ b/simcard.py @@ -23,59 +23,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ -from card import * - -# This is an abstraction which offers a set of tools to handle the most basic -# operations that can be performed on a sim card. The implementation is not -# simcard model specific - -# Classes -GSM_SIM_CLA = 0xA0 -GSM_USIM_CLA = 0x00 - -# Instructions, see also GSM 11.11, Table 9 Coding of the commands -GSM_SIM_INS_SELECT = 0xA4 -GSM_SIM_INS_STATUS = 0xF2 -GSM_SIM_INS_READ_BINARY = 0xB0 -GSM_SIM_INS_UPDATE_BINARY = 0xD6 -GSM_SIM_INS_READ_RECORD = 0xB2 -GSM_SIM_INS_UPDATE_RECORD = 0xDC -GSM_SIM_INS_SEEK = 0xA2 -GSM_SIM_INS_INCREASE = 0x32 -GSM_SIM_INS_VERIFY_CHV = 0x20 -GSM_SIM_INS_CHANGE_CHV = 0x24 -GSM_SIM_INS_DISABLE_CHV = 0x26 -GSM_SIM_INS_ENABLE_CHV = 0x28 -GSM_SIM_INS_UNBLOCK_CHV = 0x2C -GSM_SIM_INS_INVALIDATE = 0x04 -GSM_SIM_INS_REHABILITATE = 0x44 -GSM_SIM_INS_RUN_GSM_ALGORITHM = 0x88 -GSM_SIM_INS_SLEEP = 0xFA -GSM_SIM_INS_GET_RESPONSE = 0xC0 -GSM_SIM_INS_TERMINAL_PROFILE = 0x10 -GSM_SIM_INS_ENVELOPE = 0xC2 -GSM_SIM_INS_FETCH = 0x12 -GSM_SIM_INS_TERMINAL_RESPONSE = 0x14 - -# Partial File tree: -# The following tree is incomplete, it just contains the files we have been -# interested in so far. A full SIM card file tree can be found in: -# GSM TS 11.11, Figure 8: "File identifiers and directory structures of GSM" -# 3GPP TS 31.102, cap. 4.7: "Files of USIM" -# -# [MF 0x3F00] -# | -# +--[EF_DIR 0x2F00] -# | -# +--[EF_ICCID 0x2FE2] -# | -# +--[DF_TELECOM 0x7F10] -# | | -# | +-[EF_ADN 0x7F20] -# | -# +--[DF_GSM 0x7F20] -# | -# +-[EF_IMSI 0x6F07] +from card.USIM import USIM +from card.SIM import SIM +from card.utils import * # Files GSM_SIM_MF = [0x3F, 0x00] @@ -104,124 +54,81 @@ GSM_SIM_INS_UPDATE_RECORD_NEXT = 0x02 GSM_SIM_INS_UPDATE_RECORD_PREV = 0x03 GSM_SIM_INS_UPDATE_RECORD_ABS = 0x04 +class Card_res_apdu(): + apdu = None + sw = None + swok = True + + # convert Benoit Michau style result to sysmocom style result + def from_mich(self, mich): + self.apdu = mich[3] + self.sw = [ mich[2][0], mich[2][1] ] + + def __str__(self): + dump = "APDU: " + hexdump(self.apdu) + dump += " SW: " + hexdump(self.sw) + return dump + # A class to abstract a simcard. class Simcard(): card = None - usim = None # Constructor: Create a new simcard object - def __init__(self, terminal, cardtype = GSM_USIM): - - self.card = terminal + def __init__(self, cardtype = GSM_USIM): if cardtype == GSM_USIM: + self.card = USIM() self.usim = True else: - self.usim = True - + self.card = SIM() + self.usim = False # Find the right class byte, depending on the simcard type def __get_cla(self, usim): - - if (usim): - return GSM_USIM_CLA - else: - return GSM_SIM_CLA - + return self.card.CLA # Select a file def select(self, fid, dry = False, strict = True): - - cla = self.__get_cla(self.usim) - ins = GSM_SIM_INS_SELECT - length = 0x02 - - apdu = self.card.apdu(cla, ins, p2 = 0x0C, - p3 = length, data = fid) - return self.card.transact(apdu, dry, strict) - + res = Card_res_apdu() + res.from_mich(self.card.SELECT_FILE(P2 = 0x0C, Data = fid)) + return res # Perform card holder verification def verify_chv(self, chv, chv_no, dry = False, strict = True): - - cla = self.__get_cla(self.usim) - ins = GSM_SIM_INS_VERIFY_CHV - length = len(chv) - apdu = self.card.apdu(cla, ins, p2 = chv_no, - p3 = length, data = chv) - return self.card.transact(apdu, dry, strict) - + res = Card_res_apdu() + res.from_mich(self.card.VERIFY(P2 = chv_no, Data = chv)) + return res # Read CHV retry counter def chv_retrys(self, chv_no, dry = False, strict = True): - - cla = self.__get_cla(self.usim) - ins = GSM_SIM_INS_VERIFY_CHV - length = 0 - apdu = self.card.apdu(cla, ins, p2 = chv_no, - p3 = length, sw=[0x63, None]) - res = self.card.transact(apdu, dry, strict) - return res.sw[1] & 0x0F - + res = self.card.VERIFY(P2 = chv_no) + return res[2][1] & 0x0F # Perform file operation (Write) def update_binary(self, data, offset = 0, dry = False, strict = True): - - cla = self.__get_cla(self.usim) - ins = GSM_SIM_INS_UPDATE_BINARY - length = len(data) offs_high = (offset >> 8) & 0xFF offs_low = offset & 0xFF - - apdu = self.card.apdu(cla, ins, p1 = offs_high, p2 = offs_low, - p3 = length, data = data) - return self.card.transact(apdu, dry, strict) - + res = Card_res_apdu() + res.from_mich(self.card.UPDATE_BINARY(offs_high, offs_low, data)) + return res # Perform file operation (Read, byte oriented) def read_binary(self, length, offset = 0, dry = False, strict = True): - - cla = self.__get_cla(self.usim) - ins = GSM_SIM_INS_READ_BINARY offs_high = (offset >> 8) & 0xFF offs_low = offset & 0xFF - - apdu = self.card.apdu(cla, ins, p1 = offs_high, - p2 = offs_low, p3 = length) - return self.card.transact(apdu, dry, strict) - + res = Card_res_apdu() + res.from_mich(self.card.READ_BINARY(offs_high, offs_low, length)) + return res # Perform file operation (Read, record oriented) def read_record(self, length, rec_no = 0, dry = False, strict = True): - - cla = self.__get_cla(self.usim) - ins = GSM_SIM_INS_READ_RECORD - - apdu = self.card.apdu(cla, ins, p1 = rec_no, - p2 = GSM_SIM_INS_READ_RECORD_ABS, p3 = length) - return self.card.transact(apdu, dry, strict) + res = Card_res_apdu() + res.from_mich(self.card.READ_RECORD(rec_no, GSM_SIM_INS_READ_RECORD_ABS, length)) + return res # Perform file operation (Read, record oriented) def update_record(self, data, rec_no = 0, dry = False, strict = True): - - cla = self.__get_cla(self.usim) - ins = GSM_SIM_INS_UPDATE_RECORD - length = len(data) - - apdu = self.card.apdu(cla, ins, p1 = rec_no, - p2 = GSM_SIM_INS_UPDATE_RECORD_ABS, - p3 = length, data = data) - return self.card.transact(apdu, dry, strict) - - - - - - - - - - - - + res = Card_res_apdu() + res.from_mich(self.card.UPDATE_RECORD(rec_no, GSM_SIM_INS_UPDATE_RECORD_ABS, data)) + return res diff --git a/sysmo-usim-tool.sjs1.py b/sysmo-usim-tool.sjs1.py index dd2af92..1ba436d 100755 --- a/sysmo-usim-tool.sjs1.py +++ b/sysmo-usim-tool.sjs1.py @@ -24,7 +24,7 @@ along with this program. If not, see . """ import sys, getopt -from card import * +from utils import * from simcard import * from sysmo_usimsjs1 import * @@ -131,8 +131,7 @@ def main(argv): # Claim terminal print "Initializing smartcard terminal..." - c = Card(getopt_verbose) - sim = Simcard(c) + sim = Simcard() print("") print "Detected Card ICCID: ", sim.card.get_ICCID() diff --git a/sysmo_usimsjs1.py b/sysmo_usimsjs1.py index 063af57..baf1d82 100644 --- a/sysmo_usimsjs1.py +++ b/sysmo_usimsjs1.py @@ -49,8 +49,10 @@ along with this program. If not, see . # +--[EF_KI 0x00FF] +import sys from card import * from simcard import * +from utils import * # Files (propritary) SYSMO_USIMSJS1_EF_KI = [0x00, 0xFF]