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.
This commit is contained in:
Harald Welte 2017-08-16 15:18:09 +02:00
parent 4086758393
commit dc45c05b15
4 changed files with 47 additions and 325 deletions

186
card.py
View File

@ -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 <http://www.gnu.org/licenses/>.
"""
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)

View File

@ -23,59 +23,9 @@ 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 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

View File

@ -24,7 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
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()

View File

@ -49,8 +49,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# +--[EF_KI 0x00FF]
import sys
from card import *
from simcard import *
from utils import *
# Files (propritary)
SYSMO_USIMSJS1_EF_KI = [0x00, 0xFF]