switched to autotools

git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@103 96b47cad-a561-4643-ad3b-153ac7d7599c
This commit is contained in:
frankmorgner
2010-05-08 20:58:58 +00:00
parent 53cf8b684c
commit 0b60b64cb5
22 changed files with 38 additions and 131 deletions

View File

@@ -0,0 +1,215 @@
#
# Copyright (C) 2009 Dominik Oepen
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard 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 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard 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
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
import sys, getpass, anydbm, readline, os, dircache
from pickle import loads, dumps
from TLVutils import pack
from utils import inttostring
from SmartcardFilesystem import *
# pgp directory
#self.mf.append(DF(parent=self.mf,
#fid=4, dfname='\xd2\x76\x00\x01\x24\x01', bertlv_data=[]))
# pkcs-15 directories
#self.mf.append(DF(parent=self.mf,
#fid=1, dfname='\xa0\x00\x00\x00\x01'))
#self.mf.append(DF(parent=self.mf,
#fid=2, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00'))
#self.mf.append(DF(parent=self.mf,
#fid=3, dfname='\xa0\x00\x00\x03\x08\x00\x00\x10\x00\x01\x00'))
class CardGenerator(object):
def __init__(self, type='iso7816', sam=None, mf=None):
types = ['iso7816', 'ePass', 'cryptoflex']
if not type in types:
raise ValueError, "Unsupported type " % type
self.type = type
self.mf = None
self.sam = None
def __generate_iso_card(self):
from SmartcardSAM import SAM
print "Using default SAM. Insecure!!!"
self.sam = SAM("1234", "1234567890") #FIXME: Use user provided data
self.mf = MF(filedescriptor=FDB["DF"], lifecycle=LCB["ACTIVATED"], dfname=None)
self.sam.set_MF(self.mf)
def __generate_ePass(self):
from PIL import Image
from SmartcardSAM import PassportSAM
MRZ = raw_input("Please enter the MRZ as one string: ") #TODO: Sanity checks
readline.set_completer_delims("")
readline.parse_and_bind("tab: complete")
picturepath = raw_input("Please enter the path to an image: ")
picturepath = picturepath.rstrip() #FIXME
#MRZ1 = "P<UTOERIKSSON<<ANNA<MARIX<<<<<<<<<<<<<<<<<<<"
#MRZ2 = "L898902C<3UTO6908061F9406236ZE184226B<<<<<14"
#MRZ = MRZ1 + MRZ2
try:
im = Image.open(picturepath)
pic_width, pic_height = im.size
fd = open(picturepath,"rb")
picture = fd.read()
fd.close()
except IOError:
print "Failed to open file: " + picturepath
pic_width = 0
pic_height = 0
picture = None
mf = MF()
#We need a MF with Application DF \xa0\x00\x00\x02G\x10\x01
mf.append(DF(parent=mf, fid=4, dfname='\xa0\x00\x00\x02G\x10\x01', bertlv_data=[]))
df = mf.currentDF()
mf.append(TransparentStructureEF(parent=df, fid=0x011E, filedescriptor=0, data=""))#EF.COM
mf.append(TransparentStructureEF(parent=df, fid=0x0101, filedescriptor=0, data=""))#EF.DG1
mf.append(TransparentStructureEF(parent=df, fid=0x0102, filedescriptor=0, data=""))#EF.DG2
mf.append(TransparentStructureEF(parent=df, fid=0x010D, filedescriptor=0, data=""))#EF.SOD
#EF.COM
COM = pack([(0x5F01,4,"0107"),(0x5F36,6,"040000"),(0x5C,2,"6175")])
COM = pack(((0x60,len(COM),COM),))
fid = df.select("fid",0x011E)
fid.writebinary([0],[COM])
#EF.DG1
DG1 = pack([(0x5F1F,len(MRZ),MRZ)])
DG1 = pack([(0x61,len(DG1),DG1)])
fid = df.select("fid",0x0101)
fid.writebinary([0],[DG1])
#EF.DG2
if picture != None:
IIB = "\x00\x01" + inttostring(pic_width,2) + inttostring(pic_height,2) + 6 * "\x00"
length = 32 + len(picture) #32 is the length of IIB + FIB
FIB = inttostring(length,4) + 16 * "\x00"
FRH = "FAC" + "\x00" + "010" + "\x00" + inttostring(14+length,4) + inttostring(1,2)
picture = FRH + FIB + IIB + picture
DG2 = pack([(0xA1,8,"\x87\x02\x01\x01\x88\x02\x05\x01"),(0x5F2E,len(picture),picture)])
DG2 = pack([(0x02,1,"\x01"),(0x7F60,len(DG2),DG2)])
DG2 = pack([(0x7F61,len(DG2),DG2)])
fid = df.select("fid",0x0102)
fid.writebinary([0],[DG2])
self.mf = mf
self.sam = PassportSAM(self.mf)
def __generate_cryptoflex(self):
from SmartcardSAM import CryptoflexSAM
self.mf = CryptoflexMF()
self.mf.append(TransparentStructureEF(parent=self.mf, fid=0x0002, filedescriptor=0x01,
data="\x00\x00\x00\x01\x00\x01\x00\x00")) #EF.ICCSN
self.sam = CryptoflexSAM(self.mf)
def generateCard(self):
if self.type == 'iso7816':
self.__generate_iso_card()
elif self.type == 'ePass':
self.__generate_ePass()
elif self.type == 'cryptoflex':
self.__generate_cryptoflex()
else:
raise ValueError, "Unsupported card type " % self.type
def getCard(self):
if self.sam is None or self.mf is None:
self.generateCard()
return self.mf, self.sam
def setCard(self, mf=None, sam=None):
if mf != None:
self.mf = mf
if sam != None:
self.sam = sam
def loadCard(self, filename, password=None):
from CryptoUtils import read_protected_string
try:
db = anydbm.open(filename, 'r')
except anydbm.error:
print "Failed to open " + filename
if password is None:
password = getpass.getpass("Please enter your password.")
serializedMF = read_protected_string(db["mf"], password)
serializedSAM = read_protected_string(db["sam"], password)
self.sam = loads(serializedSAM)
self.mf = loads(serializedMF)
self.type = db["type"]
def saveCard(self, filename, password=None):
from CryptoUtils import protect_string
if password is None:
passwd1 = getpass.getpass("Please enter a password.")
passwd2 = getpass.getpass("Please retype your password.")
if (passwd1 != passwd2):
raise ValueError, "Passwords did not match. Will now exit"
else:
password = passwd1
if self.mf == None or self.sam == None: #TODO: Sanity checks
raise ValueError, "Card Generator was not set up properly (missing mf or sam)"
mf_string = dumps(self.mf)
sam_string = dumps(self.sam)
protectedMF = protect_string(mf_string, password)
protectedSAM = protect_string(sam_string, password)
try:
db = anydbm.open(filename, 'c')
db["mf"] = protectedMF
db["sam"] = protectedSAM
db["type"] = self.type
db["version"] = "0.1"
db.close()
except anydbm.error:
print "Failed to write data to disk"
if __name__ == "__main__":
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-t", "--type", action="store", type="choice",
default='iso7816',
choices=['iso7816', 'cryptoflex', 'ePass'],
help="Type of Smartcard [default: %default]")
parser.add_option("-f", "--file", action="store", type="string",
dest="filename", default=None,
help="Where to save the generated card")
(options, args) = parser.parse_args()
if options.filename == None:
print "You have to provide a filename using the -f option"
sys.exit()
generator = CardGenerator(options.type)
generator.generateCard()
generator.saveCard(options.filename)

View File

@@ -0,0 +1,225 @@
#
# Copyright (C) 2009 Frank Morgner, Dominik Oepen
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard 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 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard 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
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
# Life cycle status byte {{{
LCB = {}
LCB["NOINFORMATION"] = 0x00
LCB["CREATION"] = 0x01
LCB["INITIALISATION"] = 0x03
LCB["ACTIVATED"] = 0x05
LCB["DEACTIVATED"] = 0x04
LCB["TERMINATION"] = 0x0C
# }}}
# Channel security attribute {{{
CS = {}
CS["NOTSHAREABLE"] = 0x01
CS["SECURED"] = 0x02
CS["USERAUTHENTICATED"] = 0x03
# }}}
# Security attribute {{{
# Security attribute, Access mode byte for DFs/EFs {{{
SA = {}
SA["AM_DF_DELETESELF"] = SA["AM_EF_DELETEFILE"] = 0x40
SA["AM_DF_TERMINATEDF"] = SA["AM_EF_TERMINATEFILE"] = 0x20
SA["AM_DF_ACTIVATEFILE"] = SA["AM_EF_ACTIVATEFILE"] = 0x10
SA["AM_DF_DEACTIVATEFILE"] = SA["AM_EF_DEACTIVATEFILE"] = 0x08
SA["AM_DF_CREATEDF"] = SA["AM_EF_WRITEBINARY"] = 0x04
SA["AM_DF_CREATEEF"] = SA["AM_EF_UPDATEBINARY"] = 0x02
SA["AM_DF_DELETECHILD"] = SA["AM_EF_READBINARY"] = 0x01
# }}}
# Security attribute in compact format, Security condition byte {{{
SA["CF_SC_NOCONDITION"] = 0x00
SA["CF_SC_NEVER"] = 0xFF
SA["__CF_SC_ATLEASTONE"] = 0x80
SA["__CF_SC_ALLCONDITIONS"] = 0x00
SA["__CF_SC_SECUREMESSAGING"] = 0x40
SA["__CF_SC_EXTERNALAUTHENTICATION"] = 0x40
SA["__CF_SC_USERAUTHENTICATION"] = 0x40
SA["CF_SC_ONE_SECUREMESSAGING"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_SECUREMESSAGING"]
SA["CF_SC_ONE_EXTERNALAUTHENTICATION"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_EXTERNALAUTHENTICATION"]
SA["CF_SC_ONE_USERAUTHENTICATION"] = SA["__CF_SC_ATLEASTONE"]|SA["__CF_SC_USERAUTHENTICATION"]
SA["CF_SC_ALL_SECUREMESSAGING"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_SECUREMESSAGING"]
SA["CF_SC_ALL_EXTERNALAUTHENTICATION"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_EXTERNALAUTHENTICATION"]
SA["CF_SC_ALL_USERAUTHENTICATION"] = SA["__CF_SC_ALLCONDITIONS"]|SA["__CF_SC_USERAUTHENTICATION"]
# }}}
# }}}
# Data coding byte {{{
DCB = {}
DCB["ONETIMEWRITE"] = 0x00
DCB["PROPRIETARY"] = 0x20 # we use it for XOR
DCB["WRITEOR"] = 0x40
DCB["WRITEAND"] = 0x60
DCB["BERTLV_FFVALID"] = 0x10
DCB["BERTLV_FFINVALID"] = 0x10
# }}}
# File descriptor byte {{{
FDB = {}
FDB["NOTSHAREABLEFILE"] = 0x00
FDB["SHAREABLEFILE"] = 0x40
FDB["WORKINGEF"] = 0x00
FDB["INTERNALEF"] = 0x80
FDB["DF"] = 0x38
FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"] = 0x00
FDB["EFSTRUCTURE_TRANSPARENT"] = 0x01
FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"] = 0x02
FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] = 0x03
FDB["EFSTRUCTURE_LINEAR_VARIABLE_NOFURTHERINFO"] = 0x04
FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] = 0x05
FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] = 0x06
FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"] = 0x07
# }}}
# File identifier {{{
FID = {}
FID["EFDIR"] = 0x2F00
FID["EFATR"] = 0x2F00
FID["MF"] = 0x3F00
FID["PATHSELECTION"] = 0x3FFF
FID["RESERVED"] = 0x3FFF
# }}}
#SM constants {{{
SM_Class = {}
#Basic secure messaging objects
#'80', '81' Plain value not encoded in BER-TLV
SM_Class["PLAIN_VALUE_NO_TLV"] = 0x80
SM_Class["PLAIN_VALUE_NO_TLV_ODD"] = 0x81
# '82', '83' Cryptogram (plain value encoded in BER-TLV and including SM data objects, i.e., an SM field)
SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"] = 0x82
SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"] = 0x83
#'84', '85' Cryptogram (plain value encoded in BER-TLV, but not including SM data objects)
SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"] = 0x84
SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"] = 0x85
#'86', '87' Padding-content indicator byte followed by cryptogram (plain value not encoded in BER-TLV)
SM_Class["CRYPTOGRAM_PADDING_INDICATOR"] = 0x86
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"] = 0x87
#'89' Command header (CLA INS P1 P2, four bytes)
SM_Class["PLAIN_COMMAND_HEADER"] = 0x89
# '8E' Cryptographic checksum (at least four bytes)
SM_Class["CHECKSUM"] = 0x8E
# '90', '91' Hash-code
SM_Class["HASH_CODE"] = 0x90
SM_Class["HASH_CODE_ODD"] = 0x91
#'92', '93' Certificate (data not encoded in BER-TLV)
SM_Class["CERTIFICATE"] = 0x92
SM_Class["CERTIFICATE_ODD"] = 0x93
#'94', '95' Security environment identifier (SEID byte)
SM_Class["SECURITY_ENIVRONMENT_ID"] = 0x94
SM_Class["SECURITY_ENIVRONMENT_ID_ODD"] = 0x95
#'96', '97' One or two bytes encoding Ne in the unsecured command-response pair (possibly empty)
SM_Class["Ne"] = 0x96
SM_Class["Ne_ODD"] = 0x97
#'99' Processing status (SW1-SW2, two bytes; possibly empty)
SM_Class["PLAIN_PROCESSING_STATUS"] = 0x99
# '9A', '9B' Input data element for the computation of a digital signature (the value field is signed)
SM_Class["INPUT_DATA_SIGNATURE_COMPUTATION"] = 0x9A
SM_Class["INPUT_DATA_SIGNATURE_COMPUTATION_ODD"] = 0x9B
# '9C', '9D' Public key
SM_Class["PUBLIC_KEY"] = 0x9C
SM_Class["PUBLIC_KEY_ODD"] = 0x9D
# '9E' Digital signature
SM_Class["DIGITAL_SIGNATURE"] = 0x9E
#Auxiliary secure messaging objects
#Input template for the computation of a hash-code (the template is hashed)
SM_Class["HASH_COMPUTATION_TEMPLATE"] = 0xA0
SM_Class["HASH_COMPUTATION_TEMPLATE_ODD"] = 0xA1
#Input template for the verification of a cryptographic checksum (the template is included)
SM_Class["CHECKSUM_VERIFICATION_TEMPLATE"] = 0xA2
#Control reference template for authentication (AT)
SM_Class["AUTH_CRT"] = 0xA4
SM_Class["AUTH_CRT_ODD"] = 0xA5
#Control reference template for key agreement (KAT)
SM_Class["KEY_AGREEMENT_CRT"] = 0xA6
SM_Class["KEY_AGREEMENT_CRT_ODD"] = 0xA7
#Input template for the verification of a digital signature (the template is signed)
SM_Class[0xA8] = "SIGNATURE_VERIFICATION_TEMPLATE"
#Control reference template for hash-code (HT)
SM_Class["HASH_CRT"] = 0xAA
SM_Class["HASH_CRT_ODD"] = 0xAB
#Input template for the computation of a digital signature (the concatenated value fields are signed)
SM_Class["SIGNATURE_COMPUTATION_TEMPLATE"] = 0xAC
SM_Class["SIGNATURE_COMPUTATION_TEMPLATE_ODD"] = 0xAD
#Input template for the verification of a certificate (the concatenated value fields are certified)
SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE"] = 0xAE
SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE_ODD"] = 0xAF
#Plain value encoded in BER-TLV and including SM data objects, i.e., an SM field
SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"] = 0xB0
SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"] = 0xB1
#Plain value encoded in BER-TLV, but not including SM data objects
SM_Class["PLAIN_VALUE_TLV_NO_SM"] = 0xB2
SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"] = 0xB3
#Control reference template for cryptographic checksum (CCT)
SM_Class["CHECKSUM_CRT"] =0xB4
SM_Class["CHECKSUM_CRT_ODD"] =0xB5
#Control reference template for digital signature (DST)
SM_Class["SIGNATURE_CRT"] = 0xB6
SM_Class["SIGNATURE_CRT_ODD"] = 0xB7
#Control reference template for confidentiality (CT)
SM_Class["CONFIDENTIALITY_CRT"] = 0xB8
SM_Class["CONFIDENTIALITY_CRT_ODD"] = 0xB9
#Response descriptor template
SM_Class["RESPONSE_DESCRIPTOR_TEMPLATE"] = 0xBA
SM_Class["RESPONSE_DESCRIPTOR_TEMPLATE_ODD"] = 0xBB
#Input template for the computation of a digital signature (the template is signed)
SM_Class["SIGNATURE_COMPUTATION_TEMPLATE"] = 0xBC
SM_Class["SIGNATURE_COMPUTATION_TEMPLATE_ODD"] = 0xBD
#Input template for the verification of a certificate (the template is certified)
SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE"] = 0xBE
#}}}
#This table maps algorithms to numbers. It is used in the security environment
ALGO_MAPPING = {}
ALGO_MAPPING[0x00] = "DES3-ECB"
ALGO_MAPPING[0x01] = "DES3-CBC"
ALGO_MAPPING[0x02] = "AES-ECB"
ALGO_MAPPING[0x03] = "AES-CBC"
ALGO_MAPPING[0x04] = "DES-ECB"
ALGO_MAPPING[0x05] = "DES-CBC"
ALGO_MAPPING[0x06] = "MD5"
ALGO_MAPPING[0x07] = "SHA"
ALGO_MAPPING[0x08] = "SHA256"
ALGO_MAPPING[0x09] = "MAC"
ALGO_MAPPING[0x0A] = "HMAC"
ALGO_MAPPING[0x0B] = "CC"
ALGO_MAPPING[0x0C] = "RSA"
ALGO_MAPPING[0x0D] = "DSA"
#Tags of control reference templates (CRT)
TEMPLATE_AT = 0xA4
TEMPLATE_KAT = 0xA6
TEMPLATE_HT = 0xAA
TEMPLATE_CCT = 0xB4 # Template for Cryptographic Checksum
TEMPLATE_DST = 0xB6
TEMPLATE_CT = 0xB8 # Template for Confidentiality
# Recerence control for select command, and record handling commands {{{
REF = {
"IDENTIFIER_FIRST" : 0x00,
"IDENTIFIER_LAST" : 0x01,
"IDENTIFIER_NEXT" : 0x02,
"IDENTIFIER_PREVIOUS" : 0x03,
"IDENTIFIER_CONTROL" : 0x03,
"NUMBER" : 0x04,
"NUMBER_TO_LAST" : 0x05,
"NUMBER_FROM_LAST" : 0x06,
"NUMBER_CONTROL" : 0x07,
"REFERENCE_CONTROL" : 0x07,
}
# }}}

View File

@@ -0,0 +1,648 @@
#
# Copyright (C) 2009 Dominik Oepen
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard 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 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard 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
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
import sys, binascii, utils, random
from Crypto.Cipher import DES3, DES, AES, ARC4 #,IDEA no longer present in python-crypto?
from struct import pack
from binascii import b2a_hex
from random import randint
from utils import inttostring, stringtoint, hexdump
import string
import re
try:
# Use PyCrypto (if available)
from Crypto.Hash import HMAC, SHA as SHA1
except ImportError:
# PyCrypto not available. Use the Python standard library.
import hmac as HMAC
import sha as SHA1
CYBERFLEX_IV = '\x00' * 8
## *******************************************************************
## * Generic methods *
## *******************************************************************
def get_cipher(cipherspec, key, iv = None):
cipherparts = cipherspec.split("-")
if len(cipherparts) > 2:
raise ValueError, 'cipherspec must be of the form "cipher-mode" or "cipher"'
elif len(cipherparts) == 1:
cipherparts[1] = "ecb"
c_class = globals().get(cipherparts[0].upper(), None)
if c_class is None:
raise ValueError, "Cipher '%s' not known, must be one of %s" % (cipherparts[0], ", ".join([e.lower() for e in dir() if e.isupper()]))
mode = getattr(c_class, "MODE_" + cipherparts[1].upper(), None)
if mode is None:
raise ValueError, "Mode '%s' not known, must be one of %s" % (cipherparts[1], ", ".join([e.split("_")[1].lower() for e in dir(c_class) if e.startswith("MODE_")]))
cipher = None
if iv is None:
cipher = c_class.new(key, mode)
else:
cipher = c_class.new(key, mode, iv)
return cipher
def get_cipher_keylen(cipherspec):
cipherparts = cipherspec.split("-")
if len(cipherparts) > 2:
raise ValueError, 'cipherspec must be of the form "cipher-mode" or "cipher"'
cipher = cipherparts[0].upper()
#cipher = globals().get(cipherparts[0].upper(), None)
#Note: return cipher.key_size does not work on Ubuntu, because e.g. AES.key_size == 0
if cipher == "AES": #Pycrypto uses AES128
return 16
elif cipher == "DES":
return 8
elif cipher == "DES3":
return 16
else:
raise ValueError, "Unsupported Encryption Algorithm: " + cipher
def get_cipher_blocklen(cipherspec):
cipherparts = cipherspec.split("-")
if len(cipherparts) > 2:
raise ValueError, 'cipherspec must be of the form "cipher-mode" or "cipher"'
cipher = globals().get(cipherparts[0].upper(), None)
return cipher.block_size
def append_padding(cipherspec, data, padding_class=0x01):
"""Append padding to the data.
Length of padding depends on length of data and the block size of the
specified encryption algorithm.
Different types of padding may be selected via the padding_class parameter
"""
blocklen = get_cipher_blocklen(cipherspec)
if padding_class == 0x01: #ISO padding
last_block_length = len(data) % blocklen
padding_length = blocklen - last_block_length
if padding_length == 0:
padding = '\x80' + '\x00' * (blocklen - 1)
else:
padding = '\x80' + '\x00' * (blocklen - last_block_length - 1)
return data + padding
def strip_padding(cipherspec,data,padding_class=0x01):
"""
Strip the padding of decrypted data. Returns data without padding
"""
#TODO: Sanity check
if padding_class == 0x01:
tail = len(data) - 1
while data[tail] != '\x80':
tail = tail - 1
return data[:tail]
def crypto_checksum(algo, key, data, iv=None, ssc=None):
if algo not in ("HMAC","MAC","CC"):
raise ValueError, "Unknown Algorithm %s" % algo
if algo == "MAC":
checksum = calculate_MAC(key,data,0x00,iv) #FIXME: IV?
elif algo == "HMAC":
hmac = HMAC.new(key,data)
checksum = hmac.hexdigest()
del hmac
elif algo == "CC":
if ssc != None:
data = inttostring(ssc) + data
a = cipher(True, "des-cbc", key[:8], data)
b = cipher(False, "des-ecb", key[8:16], a[-8:])
c = cipher(True, "des-ecb", key[:8], b)
checksum = c
return checksum
def cipher(do_encrypt, cipherspec, key, data, iv = None):
"""Do a cryptographic operation.
operation = do_encrypt ? encrypt : decrypt,
cipherspec must be of the form "cipher-mode", or "cipher\""""
cipher = get_cipher(cipherspec, key, iv)
result = None
if do_encrypt:
result = cipher.encrypt(data)
else:
result = cipher.decrypt(data)
del cipher
return result
def hash(hashmethod,data):
from Crypto.Hash import SHA, MD5#, RIPEMD
hash_class = locals().get(hashmethod.upper(), None)
if hash_class == None:
print "Unknown Hash method %s" % hashmethod
raise ValueError
hash = hash_class.new()
hash.update(data)
return hash.digest()
def operation_on_string(string1, string2, op):
if len(string1) != len(string2):
raise ValueError, "string1 and string2 must be of equal length"
result = []
for i in range(len(string1)):
result.append( chr(op(ord(string1[i]),ord(string2[i]))) )
return "".join(result)
def protect_string(string, password, cipherspec=None):
"""
Encrypt and authenticate a string
"""
#Derive a key and a salt from password
pbk = crypt(password)
regex = re.compile('\$p5k2\$\$[\w./]*\$')
match = regex.match(pbk)
if match != None:
salt = pbk[7:match.end()-1]
key = pbk[match.end():]
else:
raise ValueError
#Encrypt the string, authenticate and format it
if cipherspec == None:
cipherspec = "AES-CBC"
else:
pass #TODO: Sanity check for cipher
paddedString = append_padding(cipherspec, string)
cryptedString = cipher(True, cipherspec, key, paddedString)
hmac = crypto_checksum("HMAC", key, cryptedString)
protectedString = "$p5k2$$" + salt + "$" + cryptedString + hmac
return protectedString
def read_protected_string(string, password, cipherspec=None):
"""
Decrypt a protected string and verify the authentication data
"""
hmac_length = 32 #FIXME: Ugly
#Check if the string has the structure, that is generated by protect_string
regex = re.compile('\$p5k2\$\$[\w./]*\$') #TODO: Ensure the right format!
match = regex.match(string)
if match != None:
crypted = string[match.end():len(string) - hmac_length]
salt = string[7:match.end() - 1]
hmac = string[len(string) - hmac_length:]
else:
raise ValueError, "Wrong string format"
#Derive key
pbk = crypt(password, salt)
match = regex.match(pbk)
key = pbk[match.end():]
#Verify HMAC
checksum = crypto_checksum("HMAC", key, crypted)
if checksum != hmac:
print "Found HMAC %s expected %s" % (str(hmac), str(checksum))
raise ValueError, "Failed to authenticate data. Wrong password?"
#Decrypt data
if cipherspec == None:
cipherspec = "AES-CBC"
decrypted = cipher(False, cipherspec, key, crypted)
return strip_padding(cipherspec, decrypted)
## *******************************************************************
## * Cyberflex specific methods *
## *******************************************************************
def verify_card_cryptogram(session_key, host_challenge, card_challenge, card_cryptogram):
message = host_challenge + card_challenge
expected = calculate_MAC(session_key, message, CYBERFLEX_IV)
return card_cryptogram == expected
def calculate_host_cryptogram(session_key, card_challenge, host_challenge):
message = card_challenge + host_challenge
return calculate_MAC(session_key, message, CYBERFLEX_IV)
def calculate_MAC(session_key, message, iv=CYBERFLEX_IV):
""""
Cyberflex MAC is the last Block of the input encrypted with DES3-CBC
"""
cipher = DES3.new(session_key, DES3.MODE_CBC, iv)
padded = append_padding("DES3", message, 0x01)
block_count = len(padded) / cipher.block_size
crypted = cipher.encrypt(padded)
return crypted[len(padded) - cipher.block_size : ]
def get_derivation_data(host_challenge, card_challenge):
return card_challenge[4:8] + host_challenge[:4] + \
card_challenge[:4] + host_challenge[4:8]
def get_session_key(auth_key, host_challenge, card_challenge):
cipher = DES3.new(auth_key, DES3.MODE_ECB)
return cipher.encrypt(get_derivation_data(host_challenge, card_challenge))
def generate_host_challenge():
random.seed()
return "".join([chr(random.randint(0,255)) for e in range(8)])
def andstring(string1, string2):
return operation_on_string(string1, string2, lambda a,b: a & b)
###########################################################################
# PBKDF2.py - PKCS#5 v2.0 Password-Based Key Derivation
#
# Copyright (C) 2007, 2008 Dwayne C. Litzenberger <dlitz@dlitz.net>
# All rights reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation.
#
# THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Country of origin: Canada
#
###########################################################################
# Sample PBKDF2 usage:
# from Crypto.Cipher import AES
# from PBKDF2 import PBKDF2
# import os
#
# salt = os.urandom(8) # 64-bit salt
# key = PBKDF2("This passphrase is a secret.", salt).read(32) # 256-bit key
# iv = os.urandom(16) # 128-bit IV
# cipher = AES.new(key, AES.MODE_CBC, iv)
# ...
#
# Sample crypt() usage:
# from PBKDF2 import crypt
# pwhash = crypt("secret")
# alleged_pw = raw_input("Enter password: ")
# if pwhash == crypt(alleged_pw, pwhash):
# print "Password good"
# else:
# print "Invalid password"
#
###########################################################################
# History:
#
# 2007-07-27 Dwayne C. Litzenberger <dlitz@dlitz.net>
# - Initial Release (v1.0)
#
# 2007-07-31 Dwayne C. Litzenberger <dlitz@dlitz.net>
# - Bugfix release (v1.1)
# - SECURITY: The PyCrypto XOR cipher (used, if available, in the _strxor
# function in the previous release) silently truncates all keys to 64
# bytes. The way it was used in the previous release, this would only be
# problem if the pseudorandom function that returned values larger than
# 64 bytes (so SHA1, SHA256 and SHA512 are fine), but I don't like
# anything that silently reduces the security margin from what is
# expected.
#
# 2008-06-17 Dwayne C. Litzenberger <dlitz@dlitz.net>
# - Compatibility release (v1.2)
# - Add support for older versions of Python (2.2 and 2.3).
#
###########################################################################
__version__ = "1.2"
def strxor(a, b):
return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)])
def b64encode(data, chars="+/"):
tt = string.maketrans("+/", chars)
return data.encode('base64').replace("\n", "").translate(tt)
class PBKDF2(object):
"""PBKDF2.py : PKCS#5 v2.0 Password-Based Key Derivation
This implementation takes a passphrase and a salt (and optionally an
iteration count, a digest module, and a MAC module) and provides a
file-like object from which an arbitrarily-sized key can be read.
If the passphrase and/or salt are unicode objects, they are encoded as
UTF-8 before they are processed.
The idea behind PBKDF2 is to derive a cryptographic key from a
passphrase and a salt.
PBKDF2 may also be used as a strong salted password hash. The
'crypt' function is provided for that purpose.
Remember: Keys generated using PBKDF2 are only as strong as the
passphrases they are derived from.
"""
def __init__(self, passphrase, salt, iterations=1000,
digestmodule=SHA1, macmodule=HMAC):
self.__macmodule = macmodule
self.__digestmodule = digestmodule
self._setup(passphrase, salt, iterations, self._pseudorandom)
def _pseudorandom(self, key, msg):
"""Pseudorandom function. e.g. HMAC-SHA1"""
return self.__macmodule.new(key=key, msg=msg,
digestmod=self.__digestmodule).digest()
def read(self, bytes):
"""Read the specified number of key bytes."""
if self.closed:
raise ValueError("file-like object is closed")
size = len(self.__buf)
blocks = [self.__buf]
i = self.__blockNum
while size < bytes:
i += 1
if i > 0xffffffffL or i < 1:
# We could return "" here, but
raise OverflowError("derived key too long")
block = self.__f(i)
blocks.append(block)
size += len(block)
buf = "".join(blocks)
retval = buf[:bytes]
self.__buf = buf[bytes:]
self.__blockNum = i
return retval
def __f(self, i):
# i must fit within 32 bits
assert 1 <= i <= 0xffffffffL
U = self.__prf(self.__passphrase, self.__salt + pack("!L", i))
result = U
for j in xrange(2, 1+self.__iterations):
U = self.__prf(self.__passphrase, U)
result = strxor(result, U)
return result
def hexread(self, octets):
"""Read the specified number of octets. Return them as hexadecimal.
Note that len(obj.hexread(n)) == 2*n.
"""
return b2a_hex(self.read(octets))
def _setup(self, passphrase, salt, iterations, prf):
# Sanity checks:
# passphrase and salt must be str or unicode (in the latter
# case, we convert to UTF-8)
if isinstance(passphrase, unicode):
passphrase = passphrase.encode("UTF-8")
if not isinstance(passphrase, str):
raise TypeError("passphrase must be str or unicode")
if isinstance(salt, unicode):
salt = salt.encode("UTF-8")
if not isinstance(salt, str):
raise TypeError("salt must be str or unicode")
# iterations must be an integer >= 1
if not isinstance(iterations, (int, long)):
raise TypeError("iterations must be an integer")
if iterations < 1:
raise ValueError("iterations must be at least 1")
# prf must be callable
if not callable(prf):
raise TypeError("prf must be callable")
self.__passphrase = passphrase
self.__salt = salt
self.__iterations = iterations
self.__prf = prf
self.__blockNum = 0
self.__buf = ""
self.closed = False
def close(self):
"""Close the stream."""
if not self.closed:
del self.__passphrase
del self.__salt
del self.__iterations
del self.__prf
del self.__blockNum
del self.__buf
self.closed = True
def crypt(word, salt=None, iterations=None):
"""PBKDF2-based unix crypt(3) replacement.
The number of iterations specified in the salt overrides the 'iterations'
parameter.
The effective hash length is 192 bits.
"""
# Generate a (pseudo-)random salt if the user hasn't provided one.
if salt is None:
salt = _makesalt()
# salt must be a string or the us-ascii subset of unicode
if isinstance(salt, unicode):
salt = salt.encode("us-ascii")
if not isinstance(salt, str):
raise TypeError("salt must be a string")
# word must be a string or unicode (in the latter case, we convert to UTF-8)
if isinstance(word, unicode):
word = word.encode("UTF-8")
if not isinstance(word, str):
raise TypeError("word must be a string or unicode")
# Try to extract the real salt and iteration count from the salt
if salt.startswith("$p5k2$"):
(iterations, salt, dummy) = salt.split("$")[2:5]
if iterations == "":
iterations = 400
else:
converted = int(iterations, 16)
if iterations != "%x" % converted: # lowercase hex, minimum digits
raise ValueError("Invalid salt")
iterations = converted
if not (iterations >= 1):
raise ValueError("Invalid salt")
# Make sure the salt matches the allowed character set
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
for ch in salt:
if ch not in allowed:
raise ValueError("Illegal character %r in salt" % (ch,))
if iterations is None or iterations == 400:
iterations = 400
salt = "$p5k2$$" + salt
else:
salt = "$p5k2$%x$%s" % (iterations, salt)
rawhash = PBKDF2(word, salt, iterations).read(24)
# return salt + "$" + b64encode(rawhash, "./") DO: Original return line
return salt + "$" + rawhash
# Add crypt as a static method of the PBKDF2 class
# This makes it easier to do "from PBKDF2 import PBKDF2" and still use
# crypt.
PBKDF2.crypt = staticmethod(crypt)
def _makesalt():
"""Return a 48-bit pseudorandom salt for crypt().
This function is not suitable for generating cryptographic secrets.
"""
binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])
return b64encode(binarysalt, "./")
def test_pbkdf2():
"""Module self-test"""
from binascii import a2b_hex
#
# Test vectors from RFC 3962
#
# Test 1
result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1).read(16)
expected = a2b_hex("cdedb5281bb2f801565a1122b2563515")
if result != expected:
raise RuntimeError("self-test failed")
# Test 2
result = PBKDF2("password", "ATHENA.MIT.EDUraeburn", 1200).hexread(32)
expected = ("5c08eb61fdf71e4e4ec3cf6ba1f5512b"
"a7e52ddbc5e5142f708a31e2e62b1e13")
if result != expected:
raise RuntimeError("self-test failed")
# Test 3
result = PBKDF2("X"*64, "pass phrase equals block size", 1200).hexread(32)
expected = ("139c30c0966bc32ba55fdbf212530ac9"
"c5ec59f1a452f5cc9ad940fea0598ed1")
if result != expected:
raise RuntimeError("self-test failed")
# Test 4
result = PBKDF2("X"*65, "pass phrase exceeds block size", 1200).hexread(32)
expected = ("9ccad6d468770cd51b10e6a68721be61"
"1a8b4d282601db3b36be9246915ec82a")
if result != expected:
raise RuntimeError("self-test failed")
#
# Other test vectors
#
# Chunked read
f = PBKDF2("kickstart", "workbench", 256)
result = f.read(17)
result += f.read(17)
result += f.read(1)
result += f.read(2)
result += f.read(3)
expected = PBKDF2("kickstart", "workbench", 256).read(40)
if result != expected:
raise RuntimeError("self-test failed")
#
# crypt() test vectors
#
# crypt 1
result = crypt("cloadm", "exec")
expected = '$p5k2$$exec$r1EWMCMk7Rlv3L/RNcFXviDefYa0hlql'
if result != expected:
raise RuntimeError("self-test failed")
# crypt 2
result = crypt("gnu", '$p5k2$c$u9HvcT4d$.....')
expected = '$p5k2$c$u9HvcT4d$Sd1gwSVCLZYAuqZ25piRnbBEoAesaa/g'
if result != expected:
raise RuntimeError("self-test failed")
# crypt 3
result = crypt("dcl", "tUsch7fU", iterations=13)
expected = "$p5k2$d$tUsch7fU$nqDkaxMDOFBeJsTSfABsyn.PYUXilHwL"
if result != expected:
raise RuntimeError("self-test failed")
# crypt 4 (unicode)
result = crypt(u'\u0399\u03c9\u03b1\u03bd\u03bd\u03b7\u03c2',
'$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ')
expected = '$p5k2$$KosHgqNo$9mjN8gqjt02hDoP0c2J0ABtLIwtot8cQ'
if result != expected:
raise RuntimeError("self-test failed")
print "PBKDF2 self test successfull"
if __name__ == "__main__":
default_key = binascii.a2b_hex("404142434445464748494A4B4C4D4E4F")
host_chal = binascii.a2b_hex("".join("89 45 19 BF BC 1A 5B D8".split()))
card_chal = binascii.a2b_hex("".join("27 4D B7 EA CA 66 CE 44".split()))
card_crypto = binascii.a2b_hex("".join("8A D4 A9 2D 9B 6B 24 E0".split()))
session_key = get_session_key(default_key, host_chal, card_chal)
print "Session-Key: ", utils.hexdump(session_key)
print verify_card_cryptogram(session_key, host_chal, card_chal, card_crypto)
host_crypto = calculate_host_cryptogram(session_key, card_chal, host_chal)
print "Host-Crypto: ", utils.hexdump( host_crypto )
external_authenticate = binascii.a2b_hex("".join("84 82 01 00 10".split())) + host_crypto
print utils.hexdump(calculate_MAC(session_key, external_authenticate))
too_short = binascii.a2b_hex("".join("89 45 19 BF".split()))
padded = append_padding("DES3-ECB", too_short)
print "Padded data: " + utils.hexdump(padded)
unpadded = strip_padding("DES3-ECB", padded)
print "Without padding: " + utils.hexdump(unpadded)
teststring = "DEADBEEFistatsyksdvhihewohfwoehcowc8hw8rogfq8whv75tsgohsav8wress"
foo = append_padding("AES", teststring)
print len(foo)
print strip_padding("AES", foo)
testpass = "SomeRandomPassphrase"
protectedString = protect_string(teststring, testpass)
unprotectedString = read_protected_string(protectedString, testpass)
if teststring != unprotectedString:
print "protect_string test failed"
#test_pbkdf2()

View File

@@ -0,0 +1,123 @@
#
# Copyright (C) 2009 Dominik Oepen
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard 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 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard 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
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
import TLVutils
from time import time
from random import seed, randint
from ConstantDefinitions import *
from utils import inttostring, stringtoint
class ControlReferenceTemplate:
def __init__(self,type,config=""):
"""
Generates a new CRT
@param type: Type of the CRT (HT, AT, KT, CCT, DST, CT-sym, CT-asym)
@param config: A string containing TLV encoded Security Environment parameters
"""
if type not in (TEMPLATE_AT, TEMPLATE_HT, TEMPLATE_KAT, TEMPLATE_CCT, TEMPLATE_DST, TEMPLATE_CT):
raise ValueError, "Unknown control reference tag."
else:
self.type = type
self.iv = None
self.keyref = None
self.key = None
self.fileref = None
self.DFref = None
self.keylength = None
self.algorithm = None
self.usage_qualifier = None
if config != "":
self.parse_SE_config(config)
self.__config_string = config
def parse_SE_config(self,config):
structure = TLVutils.unpack(config)
for object in structure:
tag, length, value = object
if tag == 0x80:
self.__set_algo(data)
elif tag in (0x81,0x82,0x83,0x84):
self.__set_key(tag,length,value)
elif tag in range(0x85, 0x93):
self.__set_iv(tag,length,value)
elif tag == 0x95:
self.usage_qualifier = data
def __set_algo(self,data):
if not ALGO_MAPPING.has_key(data):
raise ValueError
else:
#TODO: Sanity checking
self.algorithm = ALGO_MAPPING[data]
self.__replace_tag(0x80,data)
def __set_key(self,tag,length,value):
if tag == 0x81:
self.fileref = value
elif tag == 0x82:
self.DFref = value
elif tag in (0x83,0x84):
#Todo: Resolve reference
self.keyref = value
def __set_iv(self,tag,length,value):
iv = None
if tag == 0x85:
iv = 0x00
elif tag == 0x86:
pass #What is initial chaining block?
elif tag == 0x87 or tag == 0x93:
if length != 0:
iv = value
else:
iv = self.iv + 1
elif tag == 0x91:
if length != 0:
iv = value
else:
seed(time())
iv = hex(randint(0,255))
elif tag == 0x92:
if length != 0:
iv = value
else:
iv = int(time())
self.iv = iv
def __replace_tag(self,tag,data):
position = 0
while self.__config_string[position:position+1] != tag and position < len(self.__config_string):
length = inttostring(self.__config_string[position+1:position+2])
position += length + 3
if position < len(self.__config_string): #Replace Tag
length = inttostring(self.__config_string[position+1:position+2])
self.__config_string = self.__config_string[:position] + tag + inttostring(len(data)) + data + self.__config_string[position+2+length:]
else: #Add new tag
self.__config_string += tag + inttostring(len(data)) + data
def to_string(self):
"""
Return the content of the CRT, encoded as TLV data in a string
"""
return self.__config_string
def __str__(self):
return self.__config_string

View File

@@ -0,0 +1,133 @@
#
# Copyright (C) 2009 Frank Morgner
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard 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 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard 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
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
# Meaning of the interindustry values of SW1-SW2 {{{
SW = {
"NORMAL" : 0x9000,
"NORMAL_REST" : 0x6100,
"WARN_NOINFO62" : 0x6200,
"WARN_DATACORRUPTED" : 0x6281,
"WARN_EOFBEFORENEREAD" : 0x6282,
"WARN_DEACTIVATED" : 0x6283,
"WARN_FCIFORMATTING" : 0x6284,
"WARN_TERMINATIONSTATE" : 0x6285,
"WARN_NOINPUTSENSOR" : 0x6286,
"WARN_NOINFO63" : 0x6300,
"WARN_FILEFILLED" : 0x6381,
"ERR_EXECUTION" : 0x6400,
"ERR_RESPONSEREQUIRED" : 0x6401,
"ERR_NOINFO65" : 0x6500,
"ERR_MEMFAILURE" : 0x6581,
"ERR_WRONGLENGTH" : 0x6700,
"ERR_NOINFO68" : 0x6800,
"ERR_CHANNELNOTSUPPORTED" : 0x6881,
"ERR_SECMESSNOTSUPPORTED" : 0x6882,
"ERR_LASTCMDEXPECTED" : 0x6883,
"ERR_CHAININGNOTSUPPORTED" : 0x6884,
"ERR_NOINFO69" : 0x6900,
"ERR_INCOMPATIBLEWITHFILE" : 0x6981,
"ERR_SECSTATUS" : 0x6982,
"ERR_AUTHBLOCKED" : 0x6983,
"ERR_REFNOTUSABLE" : 0x6984,
"ERR_CONDITIONNOTSATISFIED" : 0x6985,
"ERR_NOCURRENTEF" : 0x6986,
"ERR_SECMESSOBJECTSMISSING" : 0x6987,
"ERR_SECMESSOBJECTSINCORRECT" : 0x6988,
"ERR_NOINFO6A" : 0x6A00,
"ERR_INCORRECTPARAMETERS" : 0x6A80,
"ERR_NOTSUPPORTED" : 0x6A81,
"ERR_FILENOTFOUND" : 0x6A82,
"ERR_RECORDNOTFOUND" : 0x6A83,
"ERR_NOTENOUGHMEMORY" : 0x6A84,
"ERR_NCINCONSISTENTWITHTLV" : 0x6A85,
"ERR_INCORRECTP1P2" : 0x6A86,
"ERR_NCINCONSISTENTP1P2" : 0x6A87,
"ERR_DATANOTFOUND" : 0x6A88,
"ERR_FILEEXISTS" : 0x6A89,
"ERR_DFNAMEEXISTS" : 0x6A8A,
"ERR_OFFSETOUTOFFILE" : 0x6B00,
"ERR_INSNOTSUPPORTED" : 0x6D00,
}
SW_MESSAGES = {
0x9000 : 'Normal processing (No further qualification)',
0x6200 : 'Warning processing (State of non-volatile memory is unchanged): No information given',
0x6281 : 'Warning processing (State of non-volatile memory is unchanged): Part of returned data may be corrupted',
0x6282 : 'Warning processing (State of non-volatile memory is unchanged): End of file or record reached before reading Ne bytes',
0x6283 : 'Warning processing (State of non-volatile memory is unchanged): Selected file deactivated',
0x6284 : 'Warning processing (State of non-volatile memory is unchanged): File control information not formatted according to 5.3.3',
0x6285 : 'Warning processing (State of non-volatile memory is unchanged): Selected file in termination state',
0x6286 : 'Warning processing (State of non-volatile memory is unchanged): No input data available from a sensor on the card',
0x6300 : 'Warning processing (State of non-volatile memory has changed): No information given',
0x6381 : 'Warning processing (State of non-volatile memory has changed): File filled up by the last write',
0x6400 : 'Execution error (State of non-volatile memory is unchanged): Execution error',
0x6401 : 'Execution error (State of non-volatile memory is unchanged): Immediate response required by the card',
0x6500 : 'Execution error (State of non-volatile memory has changed): No information given',
0x6581 : 'Execution error (State of non-volatile memory has changed): Memory failure',
0x6700 : 'Checking error: Wrong length; no further indication',
0x6800 : 'Checking error (Functions in CLA not supported): No information given',
0x6881 : 'Checking error (Functions in CLA not supported): Logical channel not supported',
0x6882 : 'Checking error (Functions in CLA not supported): Secure messaging not supported',
0x6883 : 'Checking error (Functions in CLA not supported): Last command of the chain expected',
0x6884 : 'Checking error (Functions in CLA not supported): Command chaining not supported',
0x6900 : 'Checking error (Command not allowed): No information given',
0x6981 : 'Checking error (Command not allowed): Command incompatible with file structure',
0x6982 : 'Checking error (Command not allowed): Security status not satisfied',
0x6983 : 'Checking error (Command not allowed): Authentication method blocked',
0x6984 : 'Checking error (Command not allowed): Reference data not usable',
0x6985 : 'Checking error (Command not allowed): Conditions of use not satisfied',
0x6986 : 'Checking error (Command not allowed): Command not allowed (no current EF)',
0x6987 : 'Checking error (Command not allowed): Expected secure messaging data objects missing',
0x6988 : 'Checking error (Command not allowed): Incorrect secure messaging data objects',
0x6A00 : 'Checking error (Wrong parameters P1-P2): No information given',
0x6A80 : 'Checking error (Wrong parameters P1-P2): Incorrect parameters in the command data field',
0x6A81 : 'Checking error (Wrong parameters P1-P2): Function not supported',
0x6A82 : 'Checking error (Wrong parameters P1-P2): File or application not found',
0x6A83 : 'Checking error (Wrong parameters P1-P2): Record not found',
0x6A84 : 'Checking error (Wrong parameters P1-P2): Not enough memory space in the file',
0x6A85 : 'Checking error (Wrong parameters P1-P2): Nc inconsistent with TLV structure',
0x6A86 : 'Checking error (Wrong parameters P1-P2): Incorrect parameters P1-P2',
0x6A87 : 'Checking error (Wrong parameters P1-P2): Nc inconsistent with parameters P1-P2',
0x6A88 : 'Checking error (Wrong parameters P1-P2): Referenced data or reference data not found (exact meaning depending on the command)',
0x6A89 : 'Checking error (Wrong parameters P1-P2): File already exists',
0x6A8A : 'Checking error (Wrong parameters P1-P2): DF name already exists',
0x6B00 : 'Checking error (Wrong parameters P1-P2): Wrong parameters (offset outside the EF)',
0x6D00 : 'Checking error (Instruction code not supported or invalid)',
0x6E00 : 'Checking error (Class not supported)',
0x6F00 : 'Checking error (No precise diagnosis)',
}
for i in range(0, 0xff):
SW_MESSAGES[0x6100 + i] = 'Normal Processing (%d data bytes still available)' % i
SW_MESSAGES[0x6600 + i] = 'Execution error (Security-related issues)'
SW_MESSAGES[0x6C00 + i] = 'Checking error (Wrong Le field; %d available data bytes)' % i
# }}}
class SwError(Exception):
def __init__(self, sw):
self.sw = sw
self.message = SW_MESSAGES[sw]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,316 @@
#
# Copyright (C) 2009 Frank Morgner
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard 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 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard 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
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
TAG = {}
TAG["FILECONTROLPARAMETERS"] = 0x62
TAG["FILEMANAGEMENTDATA"] = 0x64
TAG["FILECONTROLINFORMATION"] = 0x6F
TAG["BYTES_EXCLUDINGSTRUCTURE"] = 0x80
TAG["BYTES_INCLUDINGSTRUCTURE"] = 0x81
TAG["FILEDISCRIPTORBYTE"] = 0x82
TAG["FILEIDENTIFIER"] = 0x83
TAG["DFNAME"] = 0x84
TAG["PROPRIETARY_NOTBERTLV"] = 0x85
TAG["PROPRIETARY_SECURITY"] = 0x86
TAG["FIDEF_CONTAININGFCI"] = 0x87
TAG["SHORTFID"] = 0x88
TAG["LIFECYCLESTATUS"] = 0x8A
TAG["SA_EXPANDEDFORMAT"] = 0x8B
TAG["SA_COMPACTFORMAT"] = 0x8C
TAG["FIDEF_CONTAININGSET"] = 0x8D
TAG["CHANNELSECURITY"] = 0x8E
TAG["SA_DATAOBJECTS"] = 0xA0
TAG["PROPRIETARY_SECURITYTEMP"] = 0xA1
TAG["PROPRIETARY_BERTLV"] = 0xA5
TAG["SA_EXPANDEDFORMAT_TEMP"] = 0xAB
TAG["CRYPTIDENTIFIER_TEMP"] = 0xAC
TAG["DISCRETIONARY_DATA"] = 0x53
TAG["DISCRETIONARY_TEMPLATE"] = 0x73
TAG["OFFSET_DATA"] = 0x54
TAG["TAG_LIST"] = 0x5C
TAG["HEADER_LIST"] = 0x5D
TAG["EXTENDED_HEADER_LIST"] = 0x4D
MAX_EXTENDED_LE = 0xffff
MAX_SHORT_LE = 0xff
def tlv_unpack(data): # {{{
ber_class = (ord(data[0]) & 0xC0) >> 6
constructed = (ord(data[0]) & 0x20) != 0 ## 0 = primitive, 0x20 = constructed
tag = ord(data[0])
data = data[1:]
if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0])
while ord(data[0]) & 0x80 == 0x80:
data = data[1:]
tag = (tag << 8) | ord(data[0])
data = data[1:]
length = ord(data[0])
if length < 0x80:
data = data[1:]
elif length & 0x80 == 0x80:
length_ = 0
data = data[1:]
for i in range(0,length & 0x7F):
length_ = length_ * 256 + ord(data[0])
data = data[1:]
length = length_
value = data[:length]
rest = data[length:]
return ber_class, constructed, tag, length, value, rest
# }}}
def tlv_find_tags(tlv_data, tags, num_results = None): # {{{
"""Find (and return) all instances of tags in the given tlv structure (as
returned by unpack). If num_results is specified then at most that many
results will be returned."""
results = []
def find_recursive(tlv_data):
for d in tlv_data:
t,l,v = d[:3]
if t in tags:
results.append(d)
else:
if isinstance(v, list): # FIXME Refactor the whole TLV code into a class
find_recursive(v)
if num_results is not None and len(results) >= num_results:
return
find_recursive(tlv_data)
return results
# }}}
def tlv_find_tag(tlv_data, tag, num_results = None): # {{{
"""Find (and return) all instances of tag in the given tlv structure (as returned by unpack).
If num_results is specified then at most that many results will be returned."""
return tlv_find_tags(tlv_data, [tag], num_results)
# }}}
def pack(tlv_data, recalculate_length = False): # {{{
result = []
for data in tlv_data:
tag, length, value = data[:3]
if tag in (0xff, 0x00):
result.append( chr(tag) )
continue
if not isinstance(value, str):
value = pack(value, recalculate_length)
if recalculate_length:
length = len(value)
t = ""
while tag > 0:
t = chr( tag & 0xff ) + t
tag = tag >> 8
if length < 0x7F:
l = chr(length)
else:
l = ""
while length > 0:
l = chr( length & 0xff ) + l
length = length >> 8
assert len(l) < 0x7f
l = chr( 0x80 | len(l) ) + l
result.append(t)
result.append(l)
result.append(value)
return "".join(result)
# }}}
def bertlv_pack(data): # {{{
"""Packs a bertlv list of 3-tuples (tag, length, newvalue) into a string"""
return pack(data)
# }}}
def unpack(data, with_marks = None, offset = 0, include_filler=False): # {{{
result = []
while len(data) > 0:
if ord(data[0]) in (0x00, 0xFF):
if include_filler:
if with_marks is None:
result.append( (ord(data[0]), None, None) )
else:
result.append( (ord(data[0]), None, None, () ) )
data = data[1:]
offset = offset + 1
continue
l = len(data)
ber_class, constructed, tag, length, value, data = tlv_unpack(data)
stop = offset + (l - len(data))
start = stop - length
if with_marks is not None:
marks = []
for type, mark_start, mark_stop in with_marks:
if (mark_start, mark_stop) == (start, stop):
marks.append(type)
marks = (marks, )
else:
marks = ()
if not constructed:
result.append( (tag, length, value) + marks )
else:
result.append( (tag, length, unpack(value, with_marks, offset = start)) + marks )
offset = stop
return result
# }}}
def bertlv_unpack(data): # {{{
"""Unpacks a bertlv coded string into a list of 3-tuples (tag, length,
newvalue)."""
return unpack(data)
# }}}
def simpletlv_pack(tlv_data, recalculate_length = False): # {{{
result = ""
for tag, length, value in tlv_data:
if tag >= 0xff or tag <= 0x00:
# invalid
continue
if recalculate_length:
length = len(value)
if length > 0xffff or length < 0:
# invalid
continue
if length < 0xff:
result += chr(tag) + chr(length) + value
else:
result += chr(tag) + chr(0xff)+chr(length>>8)+chr(length&0xff) + value
return result
# }}}
def simpletlv_unpack(data): # {{{
"""Unpacks a simpletlv coded string into a list of 3-tuples (tag, length,
newvalue)."""
result = []
rest = data
while rest != '':
tag = ord(rest[0])
if tag == 0 or tag == 0xff:
raise ValueError
length = ord(rest[1])
if length == 0xff:
length = (ord(rest[2])<<8) + ord(rest[3])
newvalue = rest[4:4+length]
rest = rest[4+length:]
else:
newvalue = rest[2:2+length]
rest = rest[2+length:]
result.append((tag, length, newvalue))
return result
# }}}
def decodeDiscretionaryDataObjects(tlv_data): # {{{
datalist = []
for (tag, length, newvalue) in (tlv_find_tags(tlv_data,
[TAG["DISCRETIONARY_DATA"], TAG["DISCRETIONARY_TEMPLATE"]])):
datalist.append(newvalue)
return datalist
# }}}
def decodeOffsetDataObjects(tlv_data): # {{{
offsets = []
for (tag, length, newvalue) in tlv_find_tag(tlv_data,
TAG["OFFSET_DATA"]):
offsets.append(stringtoint(newvalue))
return offsets
# }}}
def decodeTagList(tlv_data): # {{{
taglist = []
for (t, l, data) in tlv_find_tag(tlv_data, TAG["TAG_LIST"]):
while data != "":
tag = ord(data[0])
data = data[1:]
if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0])
while ord(data[0]) & 0x80 == 0x80:
data = data[1:]
tag = (tag << 8) | ord(data[0])
data = data[1:]
taglist.append((tag, 0))
return taglist
# }}}
def decodeHeaderList(tlv_data): # {{{
headerlist = []
for (t, l, data) in tlv_find_tag(tlv_data, TAG["HEADER_LIST"]):
while data != "":
tag = ord(data[0])
data = data[1:]
if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0])
while ord(data[0]) & 0x80 == 0x80:
data = data[1:]
tag = (tag << 8) | ord(data[0])
data = data[1:]
length = ord(data[0])
if length < 0x80:
data = data[1:]
elif length & 0x80 == 0x80:
length_ = 0
data = data[1:]
for i in range(0,length & 0x7F):
length_ = length_ * 256 + ord(data[0])
data = data[1:]
length = length_
headerlist.append((tag, length))
return headerlist
# }}}
def decodeExtendedHeaderList(tlv_data): # {{{
# TODO
return []
# }}}
def encodebertlvDatalist(tag, datalist): # {{{
tlvlist = []
for data in datalist:
tlvlist.append((tag, len(data), data))
return bertlv_pack(tlvlist)
# }}}
def encodeDiscretionaryDataObjects(datalist): # {{{
return encodebertlvDatalist(TAG["DISCRETIONARY_DATA"], datalist)
# }}}
def encodeDataOffsetObjects(datalist): # {{{
return encodebertlvDatalist(TAG["OFFSET_DATA"], datalist)
# }}}

View File

@@ -0,0 +1,466 @@
#
# Copyright (C) 2009 Frank Morgner, Dominik Oepen
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard 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 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard 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
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
from ConstantDefinitions import *
from TLVutils import *
from SWutils import SwError, SW
from SmartcardFilesystem import prettyprint_anything, MF, DF, CryptoflexMF, TransparentStructureEF
from utils import C_APDU, R_APDU, hexdump, inttostring
from SmartcardSAM import SAM, PassportSAM, CryptoflexSAM
import CardGenerator
from pickle import dumps, loads
import socket, struct, sys, signal, atexit, traceback
import struct, getpass, anydbm
class SmartcardOS(object): # {{{
def __init__(self, mf, sam, ins2handler=None, maxle=MAX_SHORT_LE):
self.mf = mf
self.SAM = sam
#if self.mf == None and self.SAM == None:
# self.mf, self.SAM = generate_iso_card()
if not ins2handler:
self.ins2handler = {
0x0c: self.mf.eraseRecord,
0x0e: self.mf.eraseBinaryPlain,
0x0f: self.mf.eraseBinaryEncapsulated,
0x2a: self.SAM.perform_security_operation,
0x20: self.SAM.verify,
0x22: self.SAM.manage_security_environment,
0x24: self.SAM.change_reference_data,
0x46: self.SAM.generate_public_key_pair,
0x82: self.SAM.external_authenticate,
0x84: self.SAM.get_challenge,
0x88: self.SAM.internal_authenticate,
0xa0: self.mf.searchBinaryPlain,
0xa1: self.mf.searchBinaryEncapsulated,
0xa4: self.mf.selectFile,
0xb0: self.mf.readBinaryPlain,
0xb1: self.mf.readBinaryEncapsulated,
0xb2: self.mf.readRecordPlain,
0xb3: self.mf.readRecordEncapsulated,
0xc0: self.getResponse,
0xca: self.mf.getDataPlain,
0xcb: self.mf.getDataEncapsulated,
0xd0: self.mf.writeBinaryPlain,
0xd1: self.mf.writeBinaryEncapsulated,
0xd2: self.mf.writeRecord,
0xd6: self.mf.updateBinaryPlain,
0xd7: self.mf.updateBinaryEncapsulated,
0xda: self.mf.putDataPlain,
0xdb: self.mf.putDataEncapsulated,
0xdc: self.mf.updateRecordPlain,
0xdd: self.mf.updateRecordEncapsulated,
0xe0: self.mf.createFile,
0xe2: self.mf.appendRecord,
0xe4: self.mf.deleteFile,
}
else:
self.ins2handler = ins2handler
self.maxle = maxle
self.lastCommandOffcut = ""
self.lastCommandSW = SW["NORMAL"]
card_capabilities = self.mf.firstSFT + self.mf.secondSFT + SmartcardOS.makeThirdSoftwareFunctionTable()
self.atr = SmartcardOS.makeATR(T=1, directConvention = True, TA1=0x13,
histChars = chr(0x80) + chr(0x70 + len(card_capabilities)) +
card_capabilities)
def powerUp(self):
pass
def powerDown(self):
pass
def reset(self):
pass
@staticmethod
def makeATR(**args): # {{{
"""Calculate Answer to Reset (ATR) and returns the bitstring.
directConvention -- Bool. Whether to use direct convention or inverse convention.
TAi, TBi, TCi -- (optional) Value between 0 and 0xff. Interface Characters (for meaning see ISO 7816-3). Note that if no transmission protocol is given, it is automatically selected with T=max{j-1|TAj in args OR TBj in args OR TCj in args}.
T -- (optional) Value between 0 and 15. Transmission Protocol. Note that if T is set, TAi/TBi/TCi for i>T are omitted.
histChars -- (optional) Bitstring with 0 <= len(histChars) <= 15. Historical Characters T1 to T15 (for meaning see ISO 7816-4).
T0, TDi and TCK are automatically calculated.
"""
# first byte TS
if args["directConvention"]:
atr = "\x3b"
else:
atr = "\x3f"
if args.has_key("T"):
T = args["T"]
else:
T = 0
# find maximum i of TAi/TBi/TCi in args
maxTD = 0
i = 15
while i > 0:
if args.has_key("TA" + str(i)) or args.has_key("TB" + str(i)) or args.has_key("TC" + str(i)):
maxTD = i-1
break
i -= 1
if maxTD == 0 and T > 0:
maxTD = 2
# insert TDi into args (TD0 is actually T0)
for i in range(0, maxTD+1):
if i == 0 and args.has_key("histChars"):
args["TD0"] = len(args["histChars"])
else:
args["TD"+str(i)] = T
if i < maxTD:
args["TD"+str(i)] |= 1<<7
if args.has_key("TA" + str(i+1)):
args["TD"+str(i)] |= 1<<4
if args.has_key("TB" + str(i+1)):
args["TD"+str(i)] |= 1<<5
if args.has_key("TC" + str(i+1)):
args["TD"+str(i)] |= 1<<6
# initialize checksum
TCK = 0
# add TDi, TAi, TBi and TCi to ATR (TD0 is actually T0)
for i in range(0, maxTD+1):
atr = atr + "%c" % args["TD" + str(i)]
TCK ^= args["TD" + str(i)]
for j in ["A", "B", "C"]:
if args.has_key("T" + j + str(i+1)):
atr += "%c" % args["T" + j + str(i+1)]
# calculate checksum for all bytes from T0 to the end
TCK ^= args["T" + j + str(i+1)]
# add historical characters
if args.has_key("histChars"):
atr += args["histChars"]
for i in range(0, len(args["histChars"])):
TCK ^= ord( args["histChars"][i] )
# checksum is omitted for T=0
if T > 0:
atr += "%c" % TCK
return atr
# }}}
@staticmethod
def makeThirdSoftwareFunctionTable(commandChainging=False,
extendedLe=False, assignLogicalChannel=0, maximumChannels=0): # {{{
"""
Returns a byte according to the third software function table from the
historical bytes of the card capabilities.
"""
tsft = 0
if commandChainging:
tsft |= 1 << 7
if extendedLe:
tsft |= 1 << 6
if assignLogicalChannel:
if not (0<=assignLogicalChannel and assignLogicalChannel<=3):
raise ValueError
tsft |= assignLogicalChannel << 3
if maximumChannels:
if not (0<=maximumChannels and maximumChannels<=7):
raise ValueError
tsft |= maximumChannels
return inttostring(tsft)
# }}}
def formatResult(self, le, data, sw, sm):
if le == None:
count = 0
elif le == 0:
count = self.maxle
else:
count = le
self.lastCommandOffcut = data[count:]
l = len(self.lastCommandOffcut)
if l == 0:
self.lastCommandSW = SW["NORMAL"]
else:
self.lastCommandSW = sw
sw = SW["NORMAL_REST"] + min(0xff, l)
result = data[:count]
if sm:
sw, result = self.SAM.protect_result(sw,result)
return R_APDU(result, inttostring(sw)).render()
def getResponse(self, p1, p2, data):
if not (p1 == 0 and p2 == 0):
raise SwError(SW["ERR_INCORRECTP1P2"])
return self.lastCommandSW, self.lastCommandOffcut
def execute(self, msg):
def notImplemented(*argz, **args):
raise SwError(SW["ERR_INSNOTSUPPORTED"])
try:
c = C_APDU(msg)
except ValueError, e:
print e
return self.formatResult(0, 0, "", SW["ERR_INCORRECTPARAMETERS"])
#Handle Class Byte{{{
class_byte = c.cla
SM_STATUS = None
logical_channel = 0
command_chaining = 0
header_authentication = 0
#Ugly Hack for OpenSC-explorer
if(class_byte == 0xb0):
print "Open SC APDU"
SM_STATUS = "No SM"
#If Bit 8,7,6 == 0 then first industry values are used
if (class_byte & 0xE0 == 0x00):
#Bit 1 and 2 specify the logical channel
logical_channel = class_byte & 0x03
#Bit 3 and 4 specify secure messaging
secure_messaging = class_byte >> 2
secure_messaging &= 0x03
if (secure_messaging == 0x00):
SM_STATUS = "No SM"
elif (secure_messaging == 0x01):
SM_STATUS = "Propietary SM" # Not supported ?
elif (secure_messaging == 0x02):
SM_STATUS = "Standard SM"
elif (secure_messaging == 0x03):
SM_STATUS = "Standard SM"
header_authentication = 1
#If Bit 8,7 == 01 then further industry values are used
elif (class_byte & 0x0C == 0x0C):
#Bit 1 to 4 specify logical channel. 4 is added, value range is from four to nineteen
logical_channel = class_byte & 0x0f
logical_channel += 4
#Bit 6 indicates secure messaging
secure_messaging = class_byte >> 5
secure_messaging &= 0x01
if (secure_messaging == 0x00):
SM_STATUS = "No SM"
elif (secure_messaging == 0x01):
SM_STATUS = "Standard SM"
#In both cases Bit 5 specifiys command chaining
command_chaining = class_byte >> 5
command_chaining &= 0x01
#}}}
try:
if SM_STATUS == "Standard SM":
c = self.SAM.parse_SM_CAPDU(c,header_authentication)
elif SM_STATUS == "Propietary SM":
raise SwError("ERR_SECMESSNOTSUPPORTED")
sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, c.p2, c.data)
if SM_STATUS == "Standard SM":
answer = self.formatResult(c.le, result, sw, True)
else:
answer = self.formatResult(c.le, result, sw, False)
except SwError, e:
print e.message
#traceback.print_exception(*sys.exc_info())
sw = e.sw
result = ""
answer = self.formatResult(c.le, result, sw, False)
return answer
# }}}
class CryptoflexOS(SmartcardOS): # {{{
def __init__(self, mf, sam, ins2handler=None, maxle=MAX_SHORT_LE):
SmartcardOS.__init__(self, mf, sam, ins2handler, maxle)
self.atr = '\x3B\xE2\x00\x00\x40\x20\x49\x06'
def execute(self, msg):
def notImplemented(*argz, **args):
raise SwError(SW["ERR_INSNOTSUPPORTED"])
try:
c = C_APDU(msg)
except ValueError, e:
print e
return self.formatResult(0, 0, "", SW["ERR_INCORRECTPARAMETERS"])
try:
sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, c.p2, c.data)
#print type(result)
except SwError, e:
print e.message
#traceback.print_exception(*sys.exc_info())
sw = e.sw
result = ""
r = self.formatResult(c.ins, c.le, result, sw)
return r
def formatResult(self, ins, le, data, sw):
if le == 0 and len(data):
# cryptoflex does not inpterpret le==0 as maxle
self.lastCommandSW = sw
self.lastCommandOffcut = data
r = R_APDU(inttostring(SW["ERR_WRONGLENGTH"] + min(0xff,len(data)))).render()
else:
if ins == 0xa4 and len(data):
# get response should be followed by select file
self.lastCommandSW = sw
self.lastCommandOffcut = data
r = R_APDU(inttostring(SW["NORMAL_REST"] + min(0xff, len(data)))).render()
else:
r = SmartcardOS.formatResult(self, le, data, sw, False)
return r
# }}}
# sizeof(int) taken from asizof-package {{{
_Csizeof_short = len(struct.pack('h', 0))
# }}}
VPCD_CTRL_LEN = 1
VPCD_CTRL_OFF = 0
VPCD_CTRL_ON = 1
VPCD_CTRL_RESET = 2
VPCD_CTRL_ATR = 4
class VirtualICC(object): # {{{
def __init__(self, filename, type, lenlen=3, host="localhost", port=35963):
from os.path import exists
self.filename = None
self.cardGenerator = CardGenerator.CardGenerator(type)
#If a filename is specified, try to load the card from disk
if filename == None:
print "No filename specified. The card will not be safed!"
else:
self.filename = filename
if exists(filename):
self.cardGenerator.loadCard(self.filename)
else:
print "No file " + self.filename + " found. Will create new file at termination of program."
MF, SAM = self.cardGenerator.getCard()
#Generate an OS object of the correct type
if type == "iso7816" or type == "ePass":
self.os = SmartcardOS(MF, SAM)
elif type == "cryptoflex":
self.os = CryptoflexOS(MF, SAM)
else:
print "Unknown cardtype " + type + ". Will use standard ISO 7816 cardtype"
type = "iso7816"
self.os = SmartcardOS(MF, SAM)
self.type = type
#Connect to the VPCD
try:
self.sock = self.connectToPort(host, port)
self.sock.settimeout(None)
except socket.error as e:
print "Failed to open socket: " + str(e) + ". Is pcscd running? Is vpcd installed?"
sys.exit()
self.lenlen = lenlen
signal.signal(signal.SIGINT, self.signalHandler)
#atexit.register(self.signalHandler)
#atexit.register(saveCard)
def signalHandler(self, signal=None, frame=None):
self.sock.close()
if self.filename != None:
self.cardGenerator.setCard(self.os.mf, self.os.SAM)
self.cardGenerator.saveCard(self.filename)
sys.exit()
@staticmethod
def connectToPort(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
return sock
def __sendToVPICC(self, msg):
#size = inttostring(len(msg), self.lenlen)
self.sock.send(struct.pack('!H', len(msg)) + msg)
def __recvFromVPICC(self):
# receive message size
size = struct.unpack('!H', self.sock.recv(_Csizeof_short))[0]
# receive and return message
if size:
msg = self.sock.recv(size)
else:
msg = None
return size, msg
def run(self):
while True :
(size, msg) = self.__recvFromVPICC()
if not size:
print "error in communication protocol"
elif size == VPCD_CTRL_LEN:
if msg == chr(VPCD_CTRL_OFF):
print "Power Down"
self.os.powerDown()
elif msg == chr(VPCD_CTRL_ON):
print "Power Up"
self.os.powerUp()
elif msg == chr(VPCD_CTRL_RESET):
print "Reset"
self.os.reset()
elif msg == chr(VPCD_CTRL_ATR):
self.__sendToVPICC(self.os.atr)
else:
print "unknown control command"
else:
print "APDU (%d Bytes):\n%s" % (len(msg),hexdump(msg, short=True))
answer = self.os.execute(msg)
print "RESP (%d Bytes):\n%s\n" % (len(answer),hexdump(answer, short=True))
self.__sendToVPICC(answer)
# }}}
if __name__ == "__main__":
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-t", "--type", action="store", type="choice",
default='iso7816',
choices=['iso7816', 'cryptoflex', 'ePass'],
help="Type of Smartcard [default: %default]")
parser.add_option("-f", "--file", action="store", type="string",
dest="filename", default=None,
help="Name of a smartcard stored in the filesystem. The card will be loaded")
(options, args) = parser.parse_args()
vicc = VirtualICC(options.filename, options.type)
vicc.run()

View File

@@ -0,0 +1,593 @@
#
# Copyright (C) 2006 Henryk Ploetz
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard 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 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard 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
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
import string, binascii, sys, re, getopt
def stringtoint(str): # {{{
#i = len(str) - 1
#int = 0
#while i >= 0:
#int = (int << i*8) + ord(str[i])
#i = i - 1
#return int
if str:
return int(str.encode('hex'), 16)
return 0
# }}}
def inttostring(i, length=None): # {{{
#str = ""
#while i > 0:
#str = chr(i & 0xff) + str
#i >>= 8
str = "%x" % i
if len(str) % 2 == 0:
str = str.decode('hex')
else:
str = ("0"+str).decode('hex')
if length:
l = len(str)
if l > length:
raise ValueError, "i too big for the specified stringlength"
else:
str = chr(0)*(length-l) + str
return str
# }}}
def represent_binary_fancy(len, value, mask = 0):
result = []
for i in range(len):
if i%4 == 0:
result.append( " " )
if i%8 == 0:
result.append( " " )
if mask & 0x01:
result.append( str(value & 0x01) )
else:
result.append( "." )
mask = mask >> 1
value = value >> 1
result.reverse()
return "".join(result).strip()
def parse_binary(value, bytemasks, verbose = False, value_len = 8):
## Parses a binary structure and gives information back
## bytemasks is a sequence of (mask, value, string_if_no_match, string_if_match) tuples
result = []
for mask, byte, nonmatch, match in bytemasks:
if verbose:
prefix = represent_binary_fancy(value_len, value, mask) + ": "
else:
prefix = ""
if (value & mask) == (byte & mask):
if match is not None:
result.append(prefix + match)
else:
if nonmatch is not None:
result.append(prefix + nonmatch)
return result
_myprintable = " " + string.letters + string.digits + string.punctuation
def hexdump(data, indent = 0, short = False, linelen = 16, offset = 0):
"""Generates a nice hexdump of data and returns it. Consecutive lines will
be indented with indent spaces. When short is true, will instead generate
hexdump without adresses and on one line.
Examples:
hexdump('\x00\x41') -> \
'0000: 00 41 .A '
hexdump('\x00\x41', short=True) -> '00 41 (.A)'"""
def hexable(data):
return " ".join([binascii.b2a_hex(a) for a in data])
def printable(data):
return "".join([e in _myprintable and e or "." for e in data])
if short:
return "%s (%s)" % (hexable(data), printable(data))
FORMATSTRING = "%04x: %-"+ str(linelen*3) +"s %-"+ str(linelen) +"s"
result = ""
(head, tail) = (data[:linelen], data[linelen:])
pos = 0
while len(head) > 0:
if pos > 0:
result = result + "\n%s" % (' ' * indent)
result = result + FORMATSTRING % (pos+offset, hexable(head), printable(head))
pos = pos + len(head)
(head, tail) = (tail[:linelen], tail[linelen:])
return result
LIFE_CYCLES = {0x01: "Load file = loaded",
0x03: "Applet instance / security domain = Installed",
0x07: "Card manager = Initialized; Applet instance / security domain = Selectable",
0x0F: "Card manager = Secured; Applet instance / security domain = Personalized",
0x7F: "Card manager = Locked; Applet instance / security domain = Blocked",
0xFF: "Applet instance = Locked"}
def parse_status(data):
"""Parses the Response APDU of a GetStatus command."""
def parse_segment(segment):
def parse_privileges(privileges):
if privileges == 0x0:
return "N/A"
else:
privs = []
if privileges & (1<<7):
privs.append("security domain")
if privileges & (1<<6):
privs.append("DAP DES verification")
if privileges & (1<<5):
privs.append("delegated management")
if privileges & (1<<4):
privs.append("card locking")
if privileges & (1<<3):
privs.append("card termination")
if privileges & (1<<2):
privs.append("default selected")
if privileges & (1<<1):
privs.append("global PIN modification")
if privileges & (1<<0):
privs.append("mandated DAP verification")
return ", ".join(privs)
lgth = ord(segment[0])
aid = segment[1:1+lgth]
lifecycle = ord(segment[1+lgth])
privileges = ord(segment[1+lgth+1])
print "aid length: %i (%x)" % (lgth, lgth)
print "aid: %s" % hexdump(aid, indent = 18, short=True)
print "life cycle state: %x (%s)" % (lifecycle, LIFE_CYCLES.get(lifecycle, "unknown or invalid state"))
print "privileges: %x (%s)\n" % (privileges, parse_privileges(privileges))
pos = 0
while pos < len(data):
lgth = ord(data[pos])+3
segment = data[pos:pos+lgth]
parse_segment(segment)
pos = pos + lgth
def _unformat_hexdump(dump):
hexdump = " ".join([line[7:54] for line in dump.splitlines()])
return binascii.a2b_hex("".join([e != " " and e or "" for e in hexdump]))
def _make_byte_property(prop):
"Make a byte property(). This is meta code."
return property(lambda self: getattr(self, "_"+prop, None),
lambda self, value: self._setbyte(prop, value),
lambda self: delattr(self, "_"+prop),
"The %s attribute of the APDU" % prop)
class APDU(object):
"Base class for an APDU"
def __init__(self, *args, **kwargs):
"""Creates a new APDU instance. Can be given positional parameters which
must be sequences of either strings (or strings themselves) or integers
specifying byte values that will be concatenated in order. Alternatively
you may give exactly one positional argument that is an APDU instance.
After all the positional arguments have been concatenated they must
form a valid APDU!
The keyword arguments can then be used to override those values.
Keywords recognized are:
C_APDU: cla, ins, p1, p2, lc, le, data
R_APDU: sw, sw1, sw2, data
"""
initbuff = list()
if len(args) == 1 and isinstance(args[0], self.__class__):
self.parse( args[0].render() )
else:
for arg in args:
if type(arg) == str:
initbuff.extend(arg)
elif hasattr(arg, "__iter__"):
for elem in arg:
if hasattr(elem, "__iter__"):
initbuff.extend(elem)
else:
initbuff.append(elem)
else:
initbuff.append(arg)
for (index, value) in enumerate(initbuff):
t = type(value)
if t == str:
initbuff[index] = ord(value)
elif t != int:
raise TypeError, "APDU must consist of ints or one-byte strings, not %s (index %s)" % (t, index)
self.parse( initbuff )
for (name, value) in kwargs.items():
if value is not None:
setattr(self, name, value)
def _getdata(self):
return self._data
def _setdata(self, value):
if isinstance(value, str):
self._data = "".join([e for e in value])
elif isinstance(value, list):
self._data = "".join([chr(int(e)) for e in value])
else:
raise ValueError, "'data' attribute can only be a str or a list of int, not %s" % type(value)
self.Lc = len(value)
def _deldata(self):
del self._data; self.data = ""
data = property(_getdata, _setdata, None,
"The data contents of this APDU")
def _setbyte(self, name, value):
#print "setbyte(%r, %r)" % (name, value)
if isinstance(value, int):
setattr(self, "_"+name, value)
elif isinstance(value, str):
setattr(self, "_"+name, ord(value))
else:
raise ValueError, "'%s' attribute can only be a byte, that is: int or str, not %s" % (namelower, type(value))
def _format_parts(self, fields):
"utility function to be used in __str__ and __repr__"
parts = []
for i in fields:
parts.append( "%s=0x%02X" % (i, getattr(self, i)) )
return parts
def __str__(self):
result = "%s(%s)" % (self.__class__.__name__, ", ".join(self._format_fields()))
if len(self.data) > 0:
result = result + " with %i (0x%02x) bytes of data" % (
len(self.data), len(self.data)
)
return result + ":\n" + hexdump(self.data)
else:
return result
def __repr__(self):
parts = self._format_fields()
if len(self.data) > 0:
parts.append("data=%r" % self.data)
return "%s(%s)" % (self.__class__.__name__, ", ".join(parts))
class C_APDU(APDU):
"Class for a command APDU"
def parse(self, apdu):
"Parse a full command APDU and assign the values to our object, overwriting whatever there was."
apdu = map( lambda a: (isinstance(a, str) and (ord(a),) or (a,))[0], apdu)
apdu = apdu + [0] * max(4-len(apdu), 0)
self.CLA, self.INS, self.P1, self.P2 = apdu[:4] # case 1, 2, 3, 4
if len(apdu) == 5: # case 2
self.Le = apdu[-1]
self.data = ""
elif len(apdu) > 5: # case 3, 4
self.Lc = apdu[4]
if len(apdu) == 5 + self.Lc: # case 3
self.data = apdu[5:]
elif len(apdu) == 5 + self.Lc + 1: # case 4
self.data = apdu[5:-1]
self.Le = apdu[-1]
else:
raise ValueError, "Invalid Lc value. Is %s, should be %s or %s" % (self.Lc,
5 + self.Lc, 5 + self.Lc + 1)
else: # case 1
self.data = ""
CLA = _make_byte_property("CLA"); cla = CLA
INS = _make_byte_property("INS"); ins = INS
P1 = _make_byte_property("P1"); p1 = P1
P2 = _make_byte_property("P2"); p2 = P2
Lc = _make_byte_property("Lc"); lc = Lc
Le = _make_byte_property("Le"); le = Le
def _format_fields(self):
fields = ["CLA", "INS", "P1", "P2"]
if self.Lc > 0:
fields.append("Lc")
if hasattr(self, "_Le"): ## There's a difference between "Le = 0" and "no Le"
fields.append("Le")
return self._format_parts(fields)
def render(self):
"Return this APDU as a binary string"
buffer = []
for i in self.CLA, self.INS, self.P1, self.P2:
buffer.append(chr(i))
if len(self.data) > 0:
buffer.append(chr(self.Lc))
buffer.append(self.data)
if hasattr(self, "_Le"):
buffer.append(chr(self.Le))
return "".join(buffer)
def case(self):
"Return 1, 2, 3 or 4, depending on which ISO case we represent."
if self.Lc == 0:
if not hasattr(self, "_Le"):
return 1
else:
return 2
else:
if not hasattr(self, "_Le"):
return 3
else:
return 4
_apduregex = re.compile(r'^\s*([0-9a-f]{2}\s*){4,}$', re.I)
_fancyapduregex = re.compile(r'^\s*([0-9a-f]{2}\s*){4,}\s*((xx|yy)\s*)?(([0-9a-f]{2}|:|\)|\(|\[|\])\s*)*$', re.I)
@staticmethod
def parse_fancy_apdu(*args):
apdu_string = " ".join(args)
if not C_APDU._fancyapduregex.match(apdu_string):
raise ValueError
apdu_string = apdu_string.lower()
have_le = False
pos = apdu_string.find("xx")
if pos == -1:
pos = apdu_string.find("yy")
have_le = True
apdu_head = ""
apdu_tail = apdu_string
if pos != -1:
apdu_head = apdu_string[:pos]
apdu_tail = apdu_string[pos+2:]
if apdu_head.strip() != "" and not C_APDU._apduregex.match(apdu_head):
raise ValueError
class Node(list):
def __init__(self, parent = None, type = None):
list.__init__(self)
self.parent = parent
self.type = type
def make_binary(self):
"Recursively transform hex strings to binary"
for index, child in enumerate(self):
if isinstance(child,str):
child = "".join( ("".join(child.split())).split(":") )
assert len(child) % 2 == 0
self[index] = binascii.a2b_hex(child)
else:
child.make_binary()
def calculate_lengths(self):
"Recursively calculate lengths and insert length counts"
self.length = 0
index = 0
while index < len(self): ## Can't use enumerate() due to the insert() below
child = self[index]
if isinstance(child,str):
self.length = self.length + len(child)
else:
child.calculate_lengths()
formatted_len = binascii.a2b_hex("%02x" % child.length) ## FIXME len > 255?
self.length = self.length + len(formatted_len) + child.length
self.insert(index, formatted_len)
index = index + 1
index = index + 1
def flatten(self, offset = 0, ignore_types=["("]):
"Recursively flatten, gather list of marks"
string_result = []
mark_result = []
for child in self:
if isinstance(child,str):
string_result.append(child)
offset = offset + len(child)
else:
start = offset
child_string, child_mark = child.flatten(offset, ignore_types)
string_result.append(child_string)
offset = end = offset + len(child_string)
if not child.type in ignore_types:
mark_result.append( (child.type, start, end) )
mark_result.extend(child_mark)
return "".join(string_result), mark_result
tree = Node()
current = tree
allowed_parens = {"(": ")", "[":"]"}
for pos,char in enumerate(apdu_tail):
if char in (" ", "a", "b", "c", "d", "e", "f",":") or char.isdigit():
if len(current) > 0 and isinstance(current[-1],str):
current[-1] = current[-1] + char
else:
current.append(str(char))
elif char in allowed_parens.values():
if current.parent is None:
raise ValueError
if allowed_parens[current.type] != char:
raise ValueError
current = current.parent
elif char in allowed_parens.keys():
current.append( Node(current, char) )
current = current[-1]
else:
raise ValueError
if current != tree:
raise ValueError
tree.make_binary()
tree.calculate_lengths()
apdu_head = apdu_head.strip()
if apdu_head != "":
l = tree.length
if have_le:
l = l - 1 ## FIXME Le > 255?
formatted_len = "%02x" % l ## FIXME len > 255?
apdu_head = binascii.a2b_hex("".join( (apdu_head + formatted_len).split() ))
apdu_tail, marks = tree.flatten(offset=0)
apdu = C_APDU(apdu_head + apdu_tail, marks = marks)
return apdu
class R_APDU(APDU):
"Class for a response APDU"
def _getsw(self): return chr(self.SW1) + chr(self.SW2)
def _setsw(self, value):
if len(value) != 2:
raise ValueError, "SW must be exactly two bytes"
self.SW1 = value[0]
self.SW2 = value[1]
SW = property(_getsw, _setsw, None,
"The Status Word of this response APDU")
sw = SW
SW1 = _make_byte_property("SW1"); sw1 = SW1
SW2 = _make_byte_property("SW2"); sw2 = SW2
def parse(self, apdu):
"Parse a full response APDU and assign the values to our object, overwriting whatever there was."
self.SW = apdu[-2:]
self.data = apdu[:-2]
def _format_fields(self):
fields = ["SW1", "SW2"]
return self._format_parts(fields)
def render(self):
"Return this APDU as a binary string"
return self.data + self.sw
if __name__ == "__main__":
response = """
0000: 07 A0 00 00 00 03 00 00 07 00 07 A0 00 00 00 62 ...............b
0010: 00 01 01 00 07 A0 00 00 00 62 01 01 01 00 07 A0 .........b......
0020: 00 00 00 62 01 02 01 00 07 A0 00 00 00 62 02 01 ...b.........b..
0030: 01 00 07 A0 00 00 00 03 00 00 01 00 0E A0 00 00 ................
0040: 00 30 00 00 90 07 81 32 10 00 00 01 00 0E A0 00 .0.....2........
0050: 00 00 30 00 00 90 07 81 42 10 00 00 01 00 0E A0 ..0.....B.......
0060: 00 00 00 30 00 00 90 07 81 41 10 00 00 07 00 0E ...0.....A......
0070: A0 00 00 00 30 00 00 90 07 81 12 10 00 00 01 00 ....0...........
0080: 09 53 4C 42 43 52 59 50 54 4F 07 00 90 00 .SLBCRYPTO....
""" # 64kv1 vorher
response = """
0000: 07 A0 00 00 00 03 00 00 0F 00 07 A0 00 00 00 62 ...............b
0010: 00 01 01 00 07 A0 00 00 00 62 01 01 01 00 07 A0 .........b......
0020: 00 00 00 62 01 02 01 00 07 A0 00 00 00 62 02 01 ...b.........b..
0030: 01 00 07 A0 00 00 00 03 00 00 01 00 08 A0 00 00 ................
0040: 00 30 00 CA 10 01 00 0E A0 00 00 00 30 00 00 90 .0..........0...
0050: 07 81 32 10 00 00 01 00 0E A0 00 00 00 30 00 00 ..2..........0..
0060: 90 07 81 42 10 00 00 01 00 0E A0 00 00 00 30 00 ...B..........0.
0070: 00 90 07 81 41 10 00 00 07 00 0E A0 00 00 00 30 ....A..........0
0080: 00 00 90 07 81 12 10 00 00 01 00 09 53 4C 42 43 ............SLBC
0090: 52 59 50 54 4F 07 00 90 00 RYPTO....
""" # komische Karte
response = """
0000: 07 A0 00 00 00 03 00 00 07 00 07 A0 00 00 00 62 ...............b
0010: 00 01 01 00 07 A0 00 00 00 62 01 01 01 00 07 A0 .........b......
0020: 00 00 00 62 01 02 01 00 07 A0 00 00 00 62 02 01 ...b.........b..
0030: 01 00 07 A0 00 00 00 03 00 00 01 00 0E A0 00 00 ................
0040: 00 30 00 00 90 07 81 32 10 00 00 01 00 0E A0 00 .0.....2........
0050: 00 00 30 00 00 90 07 81 42 10 00 00 01 00 0E A0 ..0.....B.......
0060: 00 00 00 30 00 00 90 07 81 41 10 00 00 07 00 0E ...0.....A......
0070: A0 00 00 00 30 00 00 90 07 81 12 10 00 00 01 00 ....0...........
0080: 09 53 4C 42 43 52 59 50 54 4F 07 00 05 A0 00 00 .SLBCRYPTO......
0090: 00 01 01 00 90 00 ......
""" # 64kv1 nachher
response = """
0000: 07 A0 00 00 00 03 00 00 07 00 07 A0 00 00 00 62 ...............b
0010: 00 01 01 00 07 A0 00 00 00 62 01 01 01 00 07 A0 .........b......
0020: 00 00 00 62 01 02 01 00 07 A0 00 00 00 62 02 01 ...b.........b..
0030: 01 00 07 A0 00 00 00 03 00 00 01 00 0E A0 00 00 ................
0040: 00 30 00 00 90 07 81 32 10 00 00 01 00 0E A0 00 .0.....2........
0050: 00 00 30 00 00 90 07 81 42 10 00 00 01 00 0E A0 ..0.....B.......
0060: 00 00 00 30 00 00 90 07 81 41 10 00 00 07 00 0E ...0.....A......
0070: A0 00 00 00 30 00 00 90 07 81 12 10 00 00 01 00 ....0...........
0080: 09 53 4C 42 43 52 59 50 54 4F 07 00 05 A0 00 00 .SLBCRYPTO......
0090: 00 01 01 00 06 A0 00 00 00 01 01 07 02 90 00 ...............
""" # 64k1 nach setup
#response = sys.stdin.read()
#parse_status(_unformat_hexdump(response)[:-2])
a = C_APDU(1,2,3,4) # case 1
b = C_APDU(1,2,3,4,5) # case 2
c = C_APDU((1,2,3), cla=0x23, data="hallo") # case 3
d = C_APDU(1,2,3,4,2,4,6,0) # case 4
print
print a
print b
print c
print d
print
print repr(a)
print repr(b)
print repr(c)
print repr(d)
print
for i in a, b, c, d:
print hexdump(i.render())
print
e = R_APDU(0x90,0)
f = R_APDU("foo\x67\x00")
print
print e
print f
print
print repr(e)
print repr(f)
print
for i in e, f:
print hexdump(i.render())

View File

@@ -0,0 +1,4 @@
#!/bin/sh
cd "PYTHONDIR"
PYTHONBIN "PYTHONDIR/VirtualSmartcard.py" "$@"