Merge pull request #74 from d0/pep8

Refactor code according to PEP8

Fixes https://github.com/frankmorgner/vsmartcard/issues/72
This commit is contained in:
Frank Morgner
2016-03-17 00:27:27 +01:00
14 changed files with 1969 additions and 1407 deletions

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -16,18 +16,18 @@
# You should have received a copy of the GNU General Public License along with # You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>. # virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
# #
import sys, binascii, random, logging
import logging
import re
from struct import pack from struct import pack
from binascii import b2a_hex, a2b_hex
from base64 import b64encode from base64 import b64encode
from hashlib import pbkdf2_hmac from hashlib import pbkdf2_hmac
from random import randint from random import randint
from virtualsmartcard.utils import inttostring, hexdump from virtualsmartcard.utils import inttostring
import string, re
try: try:
# Use PyCrypto (if available) # Use PyCrypto (if available)
from Crypto.Cipher import DES3, DES, AES, ARC4#@UnusedImport from Crypto.Cipher import DES3, DES, AES, ARC4 # @UnusedImport
from Crypto.Hash import HMAC, SHA as SHA1 from Crypto.Hash import HMAC, SHA as SHA1
except ImportError: except ImportError:
@@ -37,24 +37,31 @@ except ImportError:
CYBERFLEX_IV = '\x00' * 8 CYBERFLEX_IV = '\x00' * 8
## *******************************************************************
## * Generic methods * # *******************************************************************
## ******************************************************************* # * Generic methods *
def get_cipher(cipherspec, key, iv = None): # *******************************************************************
def get_cipher(cipherspec, key, iv=None):
cipherparts = cipherspec.split("-") cipherparts = cipherspec.split("-")
if len(cipherparts) > 2: if len(cipherparts) > 2:
raise ValueError('cipherspec must be of the form "cipher-mode" or "cipher"') raise ValueError('cipherspec must be of the form "cipher-mode" or'
'"cipher"')
elif len(cipherparts) == 1: elif len(cipherparts) == 1:
cipherparts[1] = "ecb" cipherparts[1] = "ecb"
c_class = globals().get(cipherparts[0].upper(), None) c_class = globals().get(cipherparts[0].upper(), None)
if c_class is 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()]))) validCiphers = ",".join([e.lower() for e in dir() if e.isupper()])
raise ValueError("Cipher '%s' not known, must be one of %s" %
(cipherparts[0], validCiphers))
mode = getattr(c_class, "MODE_" + cipherparts[1].upper(), None) mode = getattr(c_class, "MODE_" + cipherparts[1].upper(), None)
if mode is 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_")]))) validModes = ", ".join([e.split("_")[1].lower() for e in dir(c_class)
if e.startswith("MODE_")])
raise ValueError("Mode '%s' not known, must be one of %s" %
(cipherparts[1], validModes))
cipher = None cipher = None
if iv is None: if iv is None:
@@ -64,16 +71,19 @@ def get_cipher(cipherspec, key, iv = None):
return cipher return cipher
def get_cipher_keylen(cipherspec): def get_cipher_keylen(cipherspec):
cipherparts = cipherspec.split("-") cipherparts = cipherspec.split("-")
if len(cipherparts) > 2: if len(cipherparts) > 2:
raise ValueError('cipherspec must be of the form "cipher-mode" or "cipher"') raise ValueError('cipherspec must be of the form "cipher-mode" or'
'"cipher"')
cipher = cipherparts[0].upper() cipher = cipherparts[0].upper()
#cipher = globals().get(cipherparts[0].upper(), None) # cipher = globals().get(cipherparts[0].upper(), None)
#Note: return cipher.key_size does not work on Ubuntu, because e.g. AES.key_size == 0 # Note: return cipher.key_size does not work on Ubuntu, because e.g.
if cipher == "AES": #Pycrypto uses AES128 # AES.key_size == 0
if cipher == "AES": # Pycrypto uses AES128
return 16 return 16
elif cipher == "DES": elif cipher == "DES":
return 8 return 8
@@ -82,15 +92,18 @@ def get_cipher_keylen(cipherspec):
else: else:
raise ValueError("Unsupported Encryption Algorithm: " + cipher) raise ValueError("Unsupported Encryption Algorithm: " + cipher)
def get_cipher_blocklen(cipherspec): def get_cipher_blocklen(cipherspec):
cipherparts = cipherspec.split("-") cipherparts = cipherspec.split("-")
if len(cipherparts) > 2: if len(cipherparts) > 2:
raise ValueError('cipherspec must be of the form "cipher-mode" or "cipher"') raise ValueError('cipherspec must be of the form "cipher-mode" or'
'"cipher"')
cipher = globals().get(cipherparts[0].upper(), None) cipher = globals().get(cipherparts[0].upper(), None)
return cipher.block_size return cipher.block_size
def append_padding(blocklen, data, padding_class=0x01): def append_padding(blocklen, data, padding_class=0x01):
"""Append padding to the data. """Append padding to the data.
Length of padding depends on length of data and the block size of the Length of padding depends on length of data and the block size of the
@@ -98,7 +111,7 @@ def append_padding(blocklen, data, padding_class=0x01):
Different types of padding may be selected via the padding_class parameter Different types of padding may be selected via the padding_class parameter
""" """
if padding_class == 0x01: #ISO padding if padding_class == 0x01: # ISO padding
last_block_length = len(data) % blocklen last_block_length = len(data) % blocklen
padding_length = blocklen - last_block_length padding_length = blocklen - last_block_length
if padding_length == 0: if padding_length == 0:
@@ -108,6 +121,7 @@ def append_padding(blocklen, data, padding_class=0x01):
return data + padding return data + padding
def strip_padding(blocklen, data, padding_class=0x01): def strip_padding(blocklen, data, padding_class=0x01):
""" """
Strip the padding of decrypted data. Returns data without padding Strip the padding of decrypted data. Returns data without padding
@@ -119,6 +133,7 @@ def strip_padding(blocklen, data, padding_class=0x01):
tail = tail - 1 tail = tail - 1
return data[:tail] return data[:tail]
def crypto_checksum(algo, key, data, iv=None, ssc=None): def crypto_checksum(algo, key, data, iv=None, ssc=None):
""" """
Compute various types of cryptographic checksums. Compute various types of cryptographic checksums.
@@ -147,7 +162,7 @@ def crypto_checksum(algo, key, data, iv=None, ssc=None):
checksum = hmac.hexdigest() checksum = hmac.hexdigest()
del hmac del hmac
elif algo == "CC": elif algo == "CC":
if ssc != None: if ssc is not None:
data = inttostring(ssc) + data data = inttostring(ssc) + data
a = cipher(True, "des-cbc", key[:8], data) a = cipher(True, "des-cbc", key[:8], data)
b = cipher(False, "des-ecb", key[8:16], a[-8:]) b = cipher(False, "des-ecb", key[8:16], a[-8:])
@@ -156,7 +171,8 @@ def crypto_checksum(algo, key, data, iv=None, ssc=None):
return checksum return checksum
def cipher(do_encrypt, cipherspec, key, data, iv = None):
def cipher(do_encrypt, cipherspec, key, data, iv=None):
"""Do a cryptographic operation. """Do a cryptographic operation.
operation = do_encrypt ? encrypt : decrypt, operation = do_encrypt ? encrypt : decrypt,
cipherspec must be of the form "cipher-mode", or "cipher\"""" cipherspec must be of the form "cipher-mode", or "cipher\""""
@@ -172,22 +188,26 @@ def cipher(do_encrypt, cipherspec, key, data, iv = None):
del cipher del cipher
return result return result
def encrypt(cipherspec, key, data, iv = None):
def encrypt(cipherspec, key, data, iv=None):
return cipher(True, cipherspec, key, data, iv) return cipher(True, cipherspec, key, data, iv)
def decrypt(cipherspec, key, data, iv = None):
def decrypt(cipherspec, key, data, iv=None):
return cipher(False, cipherspec, key, data, iv) return cipher(False, cipherspec, key, data, iv)
def hash(hashmethod, data): def hash(hashmethod, data):
from Crypto.Hash import SHA, MD5#, RIPEMD from Crypto.Hash import SHA, MD5 # , RIPEMD
hash_class = locals().get(hashmethod.upper(), None) hash_class = locals().get(hashmethod.upper(), None)
if hash_class == None: if hash_class is None:
logging.error("Unknown Hash method %s" % hashmethod) logging.error("Unknown Hash method %s" % hashmethod)
raise ValueError raise ValueError
hash = hash_class.new() hash = hash_class.new()
hash.update(data) hash.update(data)
return hash.digest() return hash.digest()
def operation_on_string(string1, string2, op): def operation_on_string(string1, string2, op):
if len(string1) != len(string2): if len(string1) != len(string2):
raise ValueError("string1 and string2 must be of equal length") raise ValueError("string1 and string2 must be of equal length")
@@ -196,27 +216,28 @@ def operation_on_string(string1, string2, op):
result.append(chr(op(ord(string1[i]), ord(string2[i])))) result.append(chr(op(ord(string1[i]), ord(string2[i]))))
return "".join(result) return "".join(result)
def protect_string(string, password, cipherspec=None): def protect_string(string, password, cipherspec=None):
""" """
Encrypt and authenticate a string Encrypt and authenticate a string
""" """
#Derive a key and a salt from password # Derive a key and a salt from password
pbk = crypt(password) pbk = crypt(password)
regex = re.compile('\$p5k2\$(\d*)\$([\w./]*)\$([\w./]*)') regex = re.compile('\$p5k2\$(\d*)\$([\w./]*)\$([\w./]*)')
match = regex.match(pbk) match = regex.match(pbk)
if match != None: if match is not None:
iterations = match.group(1) iterations = match.group(1)
salt = match.group(2) salt = match.group(2)
key = match.group(3) key = match.group(3)
else: else:
raise ValueError raise ValueError
#Encrypt the string, authenticate and format it # Encrypt the string, authenticate and format it
if cipherspec == None: if cipherspec is None:
cipherspec = "AES-CBC" cipherspec = "AES-CBC"
else: else:
pass #TODO: Sanity check for cipher pass # TODO: Sanity check for cipher
blocklength = get_cipher_blocklen(cipherspec) blocklength = get_cipher_blocklen(cipherspec)
paddedString = append_padding(blocklength, string) paddedString = append_padding(blocklength, string)
cryptedString = cipher(True, cipherspec, key, paddedString) cryptedString = cipher(True, cipherspec, key, paddedString)
@@ -225,43 +246,46 @@ def protect_string(string, password, cipherspec=None):
return protectedString return protectedString
def read_protected_string(string, password, cipherspec=None): def read_protected_string(string, password, cipherspec=None):
""" """
Decrypt a protected string and verify the authentication data Decrypt a protected string and verify the authentication data
""" """
hmac_length = 32 #FIXME: Ugly hmac_length = 32 # FIXME: Ugly
#Check if the string has the structure, that is generated by protect_string # Check if the string has the structure, that is generated by
# protect_string
regex = re.compile('\$p5k2\$\$([\w./]*)\$') regex = re.compile('\$p5k2\$\$([\w./]*)\$')
match = regex.match(string) match = regex.match(string)
if match != None: if match is not None:
salt = match.group(1) salt = match.group(1)
crypted = string[match.end():len(string) - hmac_length] crypted = string[match.end():len(string) - hmac_length]
hmac = string[len(string) - hmac_length:] hmac = string[len(string) - hmac_length:]
else: else:
raise ValueError("Wrong string format") raise ValueError("Wrong string format")
#Derive key # Derive key
pbk = crypt(password, salt) pbk = crypt(password, salt)
match = regex.match(pbk) match = regex.match(pbk)
key = pbk[match.end():] key = pbk[match.end():]
#Verify HMAC # Verify HMAC
checksum = crypto_checksum("HMAC", key, crypted) checksum = crypto_checksum("HMAC", key, crypted)
if checksum != hmac: if checksum != hmac:
logging.error("Found HMAC %s expected %s" % (str(hmac), str(checksum))) logging.error("Found HMAC %s expected %s" % (str(hmac), str(checksum)))
raise ValueError("Failed to authenticate data. Wrong password?") raise ValueError("Failed to authenticate data. Wrong password?")
#Decrypt data # Decrypt data
if cipherspec == None: if cipherspec is None:
cipherspec = "AES-CBC" cipherspec = "AES-CBC"
decrypted = cipher(False, cipherspec, key, crypted) decrypted = cipher(False, cipherspec, key, crypted)
return strip_padding(get_cipher_blocklen(cipherspec), decrypted) return strip_padding(get_cipher_blocklen(cipherspec), decrypted)
## *******************************************************************
## * Cyberflex specific methods * # *******************************************************************
## ******************************************************************* # * Cyberflex specific methods *
# *******************************************************************
def calculate_MAC(session_key, message, iv=CYBERFLEX_IV): def calculate_MAC(session_key, message, iv=CYBERFLEX_IV):
""" """
Cyberflex MAC is the last Block of the input encrypted with DES3-CBC Cyberflex MAC is the last Block of the input encrypted with DES3-CBC
@@ -269,10 +293,10 @@ def calculate_MAC(session_key, message, iv=CYBERFLEX_IV):
cipher = DES3.new(session_key, DES3.MODE_CBC, iv) cipher = DES3.new(session_key, DES3.MODE_CBC, iv)
padded = append_padding(8, message, 0x01) padded = append_padding(8, message, 0x01)
block_count = len(padded) / cipher.block_size
crypted = cipher.encrypt(padded) crypted = cipher.encrypt(padded)
return crypted[len(padded) - cipher.block_size : ] return crypted[len(padded) - cipher.block_size:]
def crypt(word, salt=None, iterations=None): def crypt(word, salt=None, iterations=None):
"""PBKDF2-based unix crypt(3) replacement. """PBKDF2-based unix crypt(3) replacement.
@@ -293,7 +317,8 @@ def crypt(word, salt=None, iterations=None):
if not isinstance(salt, str): if not isinstance(salt, str):
raise TypeError("salt must be a string") raise TypeError("salt must be a string")
# word must be a string or unicode (in the latter case, we convert to UTF-8) # word must be a string or unicode (in the latter case, we convert to
# UTF-8)
if isinstance(word, unicode): if isinstance(word, unicode):
word = word.encode("UTF-8") word = word.encode("UTF-8")
if not isinstance(word, str): if not isinstance(word, str):
@@ -313,7 +338,8 @@ def crypt(word, salt=None, iterations=None):
raise ValueError("Invalid salt") raise ValueError("Invalid salt")
# Make sure the salt matches the allowed character set # Make sure the salt matches the allowed character set
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./" allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678"\
"9./"
for ch in salt: for ch in salt:
if ch not in allowed: if ch not in allowed:
raise ValueError("Illegal character %r in salt" % (ch,)) raise ValueError("Illegal character %r in salt" % (ch,))
@@ -326,6 +352,7 @@ def crypt(word, salt=None, iterations=None):
rawhash = pbkdf2_hmac('sha1', salt, word, iterations, 24) rawhash = pbkdf2_hmac('sha1', salt, word, iterations, 24)
return salt + "$" + b64encode(rawhash, "./") return salt + "$" + b64encode(rawhash, "./")
def _makesalt(): def _makesalt():
"""Return a 48-bit pseudorandom salt for crypt(). """Return a 48-bit pseudorandom salt for crypt().

View File

@@ -15,36 +15,38 @@
# #
# You should have received a copy of the GNU General Public License along with # You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>. # virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
#
from virtualsmartcard.TLVutils import pack, unpack, bertlv_pack
from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, ALGO_MAPPING, \
SM_Class, MAX_EXTENDED_LE
from virtualsmartcard.utils import inttostring, stringtoint, C_APDU
from virtualsmartcard.SWutils import SwError, SW
import virtualsmartcard.CryptoUtils as vsCrypto
from time import time from time import time
from random import seed, randint from random import seed, randint
from virtualsmartcard.TLVutils import pack, unpack, bertlv_pack
from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, ALGO_MAPPING
from virtualsmartcard.ConstantDefinitions import SM_Class, MAX_EXTENDED_LE
from virtualsmartcard.utils import inttostring, stringtoint, C_APDU
from virtualsmartcard.SWutils import SwError, SW
import virtualsmartcard.CryptoUtils as vsCrypto
class ControlReferenceTemplate: class ControlReferenceTemplate:
""" """
Control Reference Templates are used to configure the Security Environments. Control Reference Templates are used to configure the Security
They specifiy which algorithms to use in which mode of operation and with Environments. They specify which algorithms to use in which mode of
which keys. There are six different types of Control Reference Template: operation and with which keys. There are six different types of Control
Reference Template:
HT, AT, KT, CCT, DST, CT-sym, CT-asym. HT, AT, KT, CCT, DST, CT-sym, CT-asym.
""" """
def __init__(self, tag, config=""): def __init__(self, tag, config=""):
""" """
Generates a new CRT Generates a new CRT
:param tag: Indicates the type of the CRT (HT, AT, KT, CCT, DST, CT-sym, :param tag: Indicates the type of the CRT (HT, AT, KT, CCT, DST,
CT-asym) CT-sym, CT-asym)
:param config: A string containing TLV encoded Security Environment :param config: A string containing TLV encoded Security Environment
parameters parameters
""" """
if tag not in (CRT_TEMPLATE["AT"], CRT_TEMPLATE["HT"], if tag not in (CRT_TEMPLATE["AT"], CRT_TEMPLATE["HT"],
CRT_TEMPLATE["KAT"], CRT_TEMPLATE["CCT"], CRT_TEMPLATE["KAT"], CRT_TEMPLATE["CCT"],
CRT_TEMPLATE["DST"], CRT_TEMPLATE["CT"]): CRT_TEMPLATE["DST"], CRT_TEMPLATE["CT"]):
raise ValueError("Unknown control reference tag.") raise ValueError("Unknown control reference tag.")
else: else:
self.type = tag self.type = tag
@@ -99,7 +101,7 @@ class ControlReferenceTemplate:
:param data: reference to an algorithm :param data: reference to an algorithm
""" """
if not ALGO_MAPPING.has_key(data): if data not in ALGO_MAPPING:
raise SwError(SW["ERR_REFNOTUSABLE"]) raise SwError(SW["ERR_REFNOTUSABLE"])
else: else:
self.algorithm = ALGO_MAPPING[data] self.algorithm = ALGO_MAPPING[data]
@@ -121,7 +123,7 @@ class ControlReferenceTemplate:
if tag == 0x85: if tag == 0x85:
iv = 0x00 iv = 0x00
elif tag == 0x86: elif tag == 0x86:
pass #What is initial chaining block? pass # What is initial chaining block?
elif tag == 0x87 or tag == 0x93: elif tag == 0x87 or tag == 0x93:
if length != 0: if length != 0:
iv = value iv = value
@@ -144,7 +146,8 @@ class ControlReferenceTemplate:
""" """
Adjust the config string using a given tag, value combination. If the Adjust the config string using a given tag, value combination. If the
config string already contains a tag, value pair for the given tag, config string already contains a tag, value pair for the given tag,
replace it. Otherwise append tag, length and value to the config string. replace it. Otherwise append tag, length and value to the config
string.
""" """
position = 0 position = 0
while position < len(self.__config_string) and \ while position < len(self.__config_string) and \
@@ -152,12 +155,12 @@ class ControlReferenceTemplate:
length = stringtoint(self.__config_string[position+1]) length = stringtoint(self.__config_string[position+1])
position += length + 3 position += length + 3
if position < len(self.__config_string): #Replace Tag if position < len(self.__config_string): # Replace Tag
length = stringtoint(self.__config_string[position+1]) length = stringtoint(self.__config_string[position+1])
self.__config_string = self.__config_string[:position] +\ self.__config_string = self.__config_string[:position] +\
chr(tag) + inttostring(len(data)) + data +\ chr(tag) + inttostring(len(data)) + data +\
self.__config_string[position+2+length:] self.__config_string[position+2+length:]
else: #Add new tag else: # Add new tag
self.__config_string += chr(tag) + inttostring(len(data)) + data self.__config_string += chr(tag) + inttostring(len(data)) + data
def to_string(self): def to_string(self):
@@ -178,7 +181,7 @@ class Security_Environment(object):
self.SEID = None self.SEID = None
#Control Reference Tables # Control Reference Tables
self.at = ControlReferenceTemplate(CRT_TEMPLATE["AT"]) self.at = ControlReferenceTemplate(CRT_TEMPLATE["AT"])
self.kat = ControlReferenceTemplate(CRT_TEMPLATE["KAT"]) self.kat = ControlReferenceTemplate(CRT_TEMPLATE["KAT"])
self.ht = ControlReferenceTemplate(CRT_TEMPLATE["HT"]) self.ht = ControlReferenceTemplate(CRT_TEMPLATE["HT"])
@@ -221,20 +224,22 @@ class Security_Environment(object):
cmd = p1 & 0x0F cmd = p1 & 0x0F
se = p1 >> 4 se = p1 >> 4
if(cmd == 0x01): if(cmd == 0x01):
#Secure messaging in command data field # Secure messaging in command data field
if se & 0x01: if se & 0x01:
self.capdu_sm = True self.capdu_sm = True
#Secure messaging in response data field # Secure messaging in response data field
if se & 0x02: if se & 0x02:
self.rapdu_sm = True self.rapdu_sm = True
#Computation, decryption, internal authentication and key agreement # Computation, decryption, internal authentication and key
# agreement
if se & 0x04: if se & 0x04:
self.internal_auth = True self.internal_auth = True
#Verification, encryption, external authentication and key agreement # Verification, encryption, external authentication and key
# agreement
if se & 0x08: if se & 0x08:
self.external_auth = True self.external_auth = True
return self._set_SE(p2, data) return self._set_SE(p2, data)
elif(cmd== 0x02): elif(cmd == 0x02):
return self.sam.store_SE(p2) return self.sam.store_SE(p2)
elif(cmd == 0x03): elif(cmd == 0x03):
return self.sam.restore_SE(p2) return self.sam.restore_SE(p2)
@@ -243,7 +248,6 @@ class Security_Environment(object):
else: else:
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
def _set_SE(self, p2, data): def _set_SE(self, p2, data):
""" """
Manipulate the current Security Environment. P2 is the tag of a Manipulate the current Security Environment. P2 is the tag of a
@@ -278,7 +282,7 @@ class Security_Environment(object):
""" """
structure = unpack(CAPDU.data) structure = unpack(CAPDU.data)
return_data = ["",] return_data = ["", ]
cla = None cla = None
ins = None ins = None
@@ -287,28 +291,31 @@ class Security_Environment(object):
le = None le = None
if authenticate_header: if authenticate_header:
to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins)+\ to_authenticate = inttostring(CAPDU.cla) +\
inttostring(CAPDU.p1) + inttostring(CAPDU.p2) inttostring(CAPDU.ins) + inttostring(CAPDU.p1) +\
to_authenticate = vsCrypto.append_padding(self.cct.blocklength, to_authenticate) inttostring(CAPDU.p2)
to_authenticate = vsCrypto.append_padding(self.cct.blocklength,
to_authenticate)
else: else:
to_authenticate = "" to_authenticate = ""
for tlv in structure: for tlv in structure:
tag, length, value = tlv tag, length, value = tlv
if tag % 2 == 1: #Include object in checksum calculation if tag % 2 == 1: # Include object in checksum calculation
to_authenticate += bertlv_pack([[tag, length, value]]) to_authenticate += bertlv_pack([[tag, length, value]])
#SM data objects for encapsulating plain values # SM data objects for encapsulating plain values
if tag in (SM_Class["PLAIN_VALUE_NO_TLV"], if tag in (SM_Class["PLAIN_VALUE_NO_TLV"],
SM_Class["PLAIN_VALUE_NO_TLV_ODD"]): SM_Class["PLAIN_VALUE_NO_TLV_ODD"]):
return_data.append(value) #FIXME: Need TLV coding? return_data.append(value) # FIXME: Need TLV coding?
#Encapsulated SM objects. Parse them # Encapsulated SM objects. Parse them
#FIXME: Need to pack value into a dummy CAPDU # FIXME: Need to pack value into a dummy CAPDU
elif tag in (SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"], elif tag in (SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"],
SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"]): SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"]):
return_data.append(self.parse_SM_CAPDU(value, authenticate_header)) return_data.append(self.parse_SM_CAPDU(value,
#Encapsulated plaintext BER-TLV objects authenticate_header))
# Encapsulated plaintext BER-TLV objects
elif tag in (SM_Class["PLAIN_VALUE_TLV_NO_SM"], elif tag in (SM_Class["PLAIN_VALUE_TLV_NO_SM"],
SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"]): SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"]):
return_data.append(value) return_data.append(value)
@@ -323,45 +330,47 @@ class Security_Environment(object):
p1 = value[4:6] p1 = value[4:6]
p2 = value[6:8] p2 = value[6:8]
#SM data objects for confidentiality # SM data objects for confidentiality
if tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"], if tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"],
SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"]): SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"]):
#The cryptogram includes SM objects. # The cryptogram includes SM objects.
#We decrypt them and parse the objects. # We decrypt them and parse the objects.
plain = self.decipher(tag, 0x80, value) plain = self.decipher(tag, 0x80, value)
#TODO: Need Le = length # TODO: Need Le = length
return_data.append(self.parse_SM_CAPDU(plain, authenticate_header)) return_data.append(self.parse_SM_CAPDU(plain,
authenticate_header))
elif tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"], elif tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"],
SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"]): SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"]):
#The cryptogram includes BER-TLV encoded plaintext. # The cryptogram includes BER-TLV encoded plaintext.
#We decrypt them and return the objects. # We decrypt them and return the objects.
plain = self.decipher(tag, 0x80, value) plain = self.decipher(tag, 0x80, value)
return_data.append(plain) return_data.append(plain)
elif tag in (SM_Class["CRYPTOGRAM_PADDING_INDICATOR"], elif tag in (SM_Class["CRYPTOGRAM_PADDING_INDICATOR"],
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"]): SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"]):
#The first byte of the data field indicates the padding to use: # The first byte of the data field indicates the padding to use
""" """
Value Meaning Value Meaning
'00' No further indication '00' No further indication
'01' Padding as specified in 6.2.3.1 '01' Padding as specified in 6.2.3.1
'02' No padding '02' No padding
'1X' One to four secret keys for enciphering information, '1X' One to four secret keys for enciphering information,
not keys ('X' is a bitmap with any value from '0' to 'F') not keys ('X' is a bitmap with any value from '0' to
'11' indicates the first key (e.g., an "even" control word 'F')
in a pay TV system) '11' indicates the first key (e.g., an "even" control word
'12' indicates the second key (e.g., an "odd" control word in a pay TV system)
in a pay TV system) '12' indicates the second key (e.g., an "odd" control word
'13' indicates the first key followed by the second key in a pay TV system)
(e.g., a pair of control words in a pay TV system) '13' indicates the first key followed by the second key
'2X' Secret key for enciphering keys, not information (e.g., a pair of control words in a pay TV system)
('X' is a reference with any value from '0' to 'F') '2X' Secret key for enciphering keys, not information
(e.g., in a pay TV system, either an operational key ('X' is a reference with any value from '0' to 'F')
for enciphering control words, or a management key for (e.g., in a pay TV system, either an operational key
enciphering operational keys) for enciphering control words, or a management key for
'3X' Private key of an asymmetric key pair ('X' is a enciphering operational keys)
reference with any value from '0' to 'F') '3X' Private key of an asymmetric key pair ('X' is a
'4X' Password ('X' is a reference with any value from '0' to reference with any value from '0' to 'F')
'F') '4X' Password ('X' is a reference with any value from '0' to
'F')
'80' to '8E' Proprietary '80' to '8E' Proprietary
""" """
padding_indicator = stringtoint(value[0]) padding_indicator = stringtoint(value[0])
@@ -370,16 +379,16 @@ class Security_Environment(object):
padding_indicator) padding_indicator)
return_data.append(plain) return_data.append(plain)
#SM data objects for authentication # SM data objects for authentication
if tag == SM_Class["CHECKSUM"]: if tag == SM_Class["CHECKSUM"]:
auth = vsCrypto.append_padding(self.cct.blocklength, to_authenticate) auth = vsCrypto.append_padding(self.cct.blocklength,
checksum = self.compute_cryptographic_checksum(0x8E, to_authenticate)
0x80, checksum = self.compute_cryptographic_checksum(0x8E, 0x80,
auth) auth)
if checksum != value: if checksum != value:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
elif tag == SM_Class["DIGITAL_SIGNATURE"]: elif tag == SM_Class["DIGITAL_SIGNATURE"]:
auth = to_authenticate #FIXME: Need padding? auth = to_authenticate # FIXME: Need padding?
signature = self.compute_digital_signature(0x9E, 0x9A, auth) signature = self.compute_digital_signature(0x9E, 0x9A, auth)
if signature != value: if signature != value:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
@@ -388,27 +397,29 @@ class Security_Environment(object):
if hash != value: if hash != value:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
#Form unprotected CAPDU # Form unprotected CAPDU
if cla == None: if cla is None:
cla = CAPDU.cla cla = CAPDU.cla
if ins == None: if ins is None:
ins = CAPDU.ins ins = CAPDU.ins
if p1 == None: if p1 is None:
p1 = CAPDU.p1 p1 = CAPDU.p1
if p2 == None: if p2 is None:
p2 = CAPDU.p2 p2 = CAPDU.p2
# FIXME # FIXME:
#if expected != "": # if expected != "":
#raise SwError(SW["ERR_SECMESSOBJECTSMISSING"]) # raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
if isinstance(le, str): if isinstance(le, str):
# FIXME C_APDU only handles le with strings of length 1. Better patch utils.py to support extended length apdus # FIXME: C_APDU only handles le with strings of length 1.
# Better patch utils.py to support extended length apdus
le_int = stringtoint(le) le_int = stringtoint(le)
if le_int == 0 and len(le) > 1: if le_int == 0 and len(le) > 1:
le_int = MAX_EXTENDED_LE le_int = MAX_EXTENDED_LE
le = le_int le = le_int
c = C_APDU(cla=cla, ins=ins, p1=p1, p2=p2, le=le, data="".join(return_data)) c = C_APDU(cla=cla, ins=ins, p1=p1, p2=p2, le=le,
data="".join(return_data))
return c return c
def protect_response(self, sw, result): def protect_response(self, sw, result):
@@ -419,14 +430,14 @@ class Security_Environment(object):
""" """
return_data = "" return_data = ""
#if sw == SW["NORMAL"]: # if sw == SW["NORMAL"]:
# sw = inttostring(sw) # sw = inttostring(sw)
# length = len(sw) # length = len(sw)
# tag = SM_Class["PLAIN_PROCESSING_STATUS"] # tag = SM_Class["PLAIN_PROCESSING_STATUS"]
# tlv_sw = pack([(tag,length,sw)]) # tlv_sw = pack([(tag,length,sw)])
# return_data += tlv_sw # return_data += tlv_sw
if result != "": # Encrypt the data included in the RAPDU if result != "": # Encrypt the data included in the RAPDU
encrypted = self.encipher(0x82, 0x80, result) encrypted = self.encipher(0x82, 0x80, result)
encrypted = "\x01" + encrypted encrypted = "\x01" + encrypted
encrypted_tlv = pack([( encrypted_tlv = pack([(
@@ -436,11 +447,12 @@ class Security_Environment(object):
return_data += encrypted_tlv return_data += encrypted_tlv
if sw == SW["NORMAL"]: if sw == SW["NORMAL"]:
if self.cct.algorithm == None: if self.cct.algorithm is None:
raise SwError(SW["CONDITIONSNOTSATISFIED"]) raise SwError(SW["CONDITIONSNOTSATISFIED"])
elif self.cct.algorithm == "CC": elif self.cct.algorithm == "CC":
tag = SM_Class["CHECKSUM"] tag = SM_Class["CHECKSUM"]
padded = vsCrypto.append_padding(self.cct.blocklength, return_data) padded = vsCrypto.append_padding(self.cct.blocklength,
return_data)
auth = self.compute_cryptographic_checksum(0x8E, 0x80, padded) auth = self.compute_cryptographic_checksum(0x8E, 0x80, padded)
length = len(auth) length = len(auth)
return_data += pack([(tag, length, auth)]) return_data += pack([(tag, length, auth)])
@@ -453,7 +465,7 @@ class Security_Environment(object):
return sw, return_data return sw, return_data
#The following commands implement ISO 7816-8 {{{ # The following commands implement ISO 7816-8 {{{
def perform_security_operation(self, p1, p2, data): def perform_security_operation(self, p1, p2, data):
""" """
In the end this command is nothing but a big switch for all the other In the end this command is nothing but a big switch for all the other
@@ -489,7 +501,6 @@ class Security_Environment(object):
return SW["NORMAL"], response_data return SW["NORMAL"], response_data
def compute_cryptographic_checksum(self, p1, p2, data): def compute_cryptographic_checksum(self, p1, p2, data):
""" """
Compute a cryptographic checksum (e.g. MAC) for the given data. Compute a cryptographic checksum (e.g. MAC) for the given data.
@@ -497,7 +508,7 @@ class Security_Environment(object):
""" """
if p1 != 0x8E or p2 != 0x80: if p1 != 0x8E or p2 != 0x80:
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
if self.cct.key == None: if self.cct.key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key, checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
@@ -514,21 +525,21 @@ class Security_Environment(object):
is included in the data field. is included in the data field.
""" """
if p1 != 0x9E or not p2 in (0x9A, 0xAC, 0xBC): if p1 != 0x9E or p2 not in (0x9A, 0xAC, 0xBC):
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
if self.dst.key == None: if self.dst.key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
to_sign = "" to_sign = ""
if p2 == 0x9A: #Data to be signed if p2 == 0x9A: # Data to be signed
to_sign = data to_sign = data
elif p2 == 0xAC: #Data objects, sign values elif p2 == 0xAC: # Data objects, sign values
to_sign = "" to_sign = ""
structure = unpack(data) structure = unpack(data)
for tag, length, value in structure: for tag, length, value in structure:
to_sign += value to_sign += value
elif p2 == 0xBC: #Data objects to be signed elif p2 == 0xBC: # Data objects to be signed
pass pass
signature = self.dst.key.sign(to_sign, "") signature = self.dst.key.sign(to_sign, "")
@@ -541,10 +552,10 @@ class Security_Environment(object):
:return: raw data (no TLV coding). :return: raw data (no TLV coding).
""" """
if p1 != 0x90 or not p2 in (0x80, 0xA0): if p1 != 0x90 or p2 not in (0x80, 0xA0):
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
algo = self.ht.algorithm algo = self.ht.algorithm
if algo == None: if algo is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
try: try:
hash = vsCrypto.hash(algo, data) hash = vsCrypto.hash(algo, data)
@@ -565,7 +576,7 @@ class Security_Environment(object):
algo = self.cct.algorithm algo = self.cct.algorithm
key = self.cct.key key = self.cct.key
iv = self.cct.iv iv = self.cct.iv
if algo == None or key == None: if algo is None or key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
structure = unpack(data) structure = unpack(data)
@@ -593,14 +604,14 @@ class Security_Environment(object):
to_sign = "" to_sign = ""
signature = "" signature = ""
if key == None: if key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
structure = unpack(data) structure = unpack(data)
for tag, length, value in structure: for tag, length, value in structure:
if tag == 0x9E: if tag == 0x9E:
signature = value signature = value
elif tag == 0x9A: #FIXME: Correct treatment of all possible tags elif tag == 0x9A: # FIXME: Correct treatment of all possible tags
to_sign = value to_sign = value
elif tag == 0xAC: elif tag == 0xAC:
pass pass
@@ -636,10 +647,11 @@ class Security_Environment(object):
""" """
algo = self.ct.algorithm algo = self.ct.algorithm
key = self.ct.key key = self.ct.key
if key == None or algo == None: if key is None or algo is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
else: else:
padded = vsCrypto.append_padding(vsCrypto.get_cipher_blocklen(algo), data) blocklen = vsCrypto.get_cipher_blocklen(algo)
padded = vsCrypto.append_padding(blocklen, data)
crypted = vsCrypto.encrypt(algo, key, padded, self.ct.iv) crypted = vsCrypto.encrypt(algo, key, padded, self.ct.iv)
return crypted return crypted
@@ -652,7 +664,7 @@ class Security_Environment(object):
""" """
algo = self.ct.algorithm algo = self.ct.algorithm
key = self.ct.key key = self.ct.key
if key == None or algo == None: if key is None or algo is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
else: else:
plain = vsCrypto.decrypt(algo, key, data, self.ct.iv) plain = vsCrypto.decrypt(algo, key, data, self.ct.iv)
@@ -665,8 +677,10 @@ class Security_Environment(object):
card, or accesses a key pair previously generated in the card. card, or accesses a key pair previously generated in the card.
:param p1: should be 0x00 (generate new key) :param p1: should be 0x00 (generate new key)
:param p2: '00' (no information provided) or reference of the key to be generated :param p2: '00' (no information provided) or reference of the key to
:param data: One or more CRTs associated to the key generation if P1-P2 different from '0000' be generated
:param data: One or more CRTs associated to the key generation if
P1-P2 different from '0000'
""" """
from Crypto.PublicKey import RSA, DSA from Crypto.PublicKey import RSA, DSA
@@ -679,42 +693,42 @@ class Security_Environment(object):
if c_class is None: if c_class is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
if p1 & 0x01 == 0x00: #Generate key if p1 & 0x01 == 0x00: # Generate key
PublicKey = c_class.generate(self.dst.keylength, rnd.get_bytes) PublicKey = c_class.generate(self.dst.keylength, rnd.get_bytes)
self.dst.key = PublicKey self.dst.key = PublicKey
else: else:
pass #Read key pass # Read key
#Encode keys # Encode keys
if cipher == "RSA": if cipher == "RSA":
#Public key # Public key
n = str(PublicKey.__getstate__()['n']) n = str(PublicKey.__getstate__()['n'])
e = str(PublicKey.__getstate__()['e']) e = str(PublicKey.__getstate__()['e'])
pk = ((0x81, len(n), n), (0x82, len(e), e)) pk = ((0x81, len(n), n), (0x82, len(e), e))
result = bertlv_pack(pk) result = bertlv_pack(pk)
#Private key # Private key
d = PublicKey.__getstate__()['d'] d = PublicKey.__getstate__()['d']
elif cipher == "DSA": elif cipher == "DSA":
#DSAParams # DSAParams
p = str(PublicKey.__getstate__()['p']) p = str(PublicKey.__getstate__()['p'])
q = str(PublicKey.__getstate__()['q']) q = str(PublicKey.__getstate__()['q'])
g = str(PublicKey.__getstate__()['g']) g = str(PublicKey.__getstate__()['g'])
#Public key # Public key
y = str(PublicKey.__getstate__()['y']) y = str(PublicKey.__getstate__()['y'])
pk = ((0x81, len(p), p), (0x82, len(q), q), (0x83, len(g), g), pk = ((0x81, len(p), p), (0x82, len(q), q), (0x83, len(g), g),
(0x84, len(y), y)) (0x84, len(y), y))
#Private key # Private key
x = str(PublicKey.__getstate__()['x']) x = str(PublicKey.__getstate__()['x'])
#Add more algorithms here # Add more algorithms here
#elif cipher = "ECDSA": # elif cipher = "ECDSA":
else: else:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
result = bertlv_pack([[0x7F49, len(pk), pk]]) result = bertlv_pack([[0x7F49, len(pk), pk]])
#TODO: Internally store key pair # TODO: Internally store key pair
if p1 & 0x02 == 0x02: if p1 & 0x02 == 0x02:
#We do not support extended header lists yet # We do not support extended header lists yet
raise SwError["ERR_NOTSUPPORTED"] raise SwError["ERR_NOTSUPPORTED"]
else: else:
return SW["NORMAL"], result return SW["NORMAL"], result

View File

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

View File

@@ -17,8 +17,8 @@
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>. # virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
# #
#TODO: use bertlv_pack for fdm # TODO: use bertlv_pack for fdm
#TODO: zu lange daten abschneiden und trotzdem tlv laenge beibehalten # TODO: zu lange daten abschneiden und trotzdem tlv laenge beibehalten
from pickle import dumps, loads from pickle import dumps, loads
import logging import logging
@@ -27,6 +27,7 @@ from virtualsmartcard.TLVutils import *
from virtualsmartcard.SWutils import SW, SwError from virtualsmartcard.SWutils import SW, SwError
from virtualsmartcard.utils import stringtoint, inttostring, hexdump from virtualsmartcard.utils import stringtoint, inttostring, hexdump
def isEqual(list): def isEqual(list):
"""Returns True, if all items are equal, otherwise False""" """Returns True, if all items are equal, otherwise False"""
if len(list) > 1: if len(list) > 1:
@@ -74,7 +75,8 @@ def getfile_byrefdataobj(mf, refdataobjs):
elif length <= 2: elif length <= 2:
newvalue = stringtoint(newvalue) newvalue = stringtoint(newvalue)
if length == 1: if length == 1:
if newvalue & 5 != 0 or (newvalue >> 3) == 0 or (newvalue>>3) == (0xff>>3): if (newvalue & 5 != 0 or (newvalue >> 3) == 0 or
(newvalue >> 3) == (0xff >> 3)):
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
else: else:
file = mf.select('shortfid', newvalue >> 3) file = mf.select('shortfid', newvalue >> 3)
@@ -106,15 +108,16 @@ def write(old, newlist, offsets, datacoding, maxsize=None):
:param old: string of old data :param old: string of old data
:param newlist: a list of new data string :param newlist: a list of new data string
:param offsets: a list of offsets, each for one new data strings :param offsets: a list of offsets, each for one new data strings
:param datacoding: DCB["ONETIMEWRITE"] (replace) or DCB["WRITEOR"] (logical or) :param datacoding: DCB["ONETIMEWRITE"] (replace) or DCB["WRITEOR"]
or DCB["WRITEAND"] (logical and) or DCB["PROPRIETARY"] (logical xor) (logical or) or DCB["WRITEAND"] (logical and) or
DCB["PROPRIETARY"] (logical xor)
:param maxsize: the maximum number of bytes in the result :param maxsize: the maximum number of bytes in the result
""" """
result = old result = old
listindex = 0 listindex = 0
while listindex < len(offsets) and listindex < len(newlist): while listindex < len(offsets) and listindex < len(newlist):
offset = offsets[listindex] offset = offsets[listindex]
new = newlist[listindex] new = newlist[listindex]
writenow = len(new) writenow = len(new)
if offset > len(result): if offset > len(result):
@@ -123,15 +126,15 @@ def write(old, newlist, offsets, datacoding, maxsize=None):
raise SwError(SW["ERR_NOTENOUGHMEMORY"], old) raise SwError(SW["ERR_NOTENOUGHMEMORY"], old)
if datacoding == DCB["ONETIMEWRITE"]: if datacoding == DCB["ONETIMEWRITE"]:
result = (result[ 0 : offset ] + result = (result[0:offset] +
new[ 0 : writenow ] + new[0:writenow] +
result[ offset+writenow : len(result) ]) result[offset + writenow:len(result)])
else: else:
if offset + writenow > len(old): if offset + writenow > len(old):
raise SwError(SW["ERR_NOTENOUGHMEMORY"], old) raise SwError(SW["ERR_NOTENOUGHMEMORY"], old)
newindex = 0 newindex = 0
resultindex = offset + newindex resultindex = offset + newindex
while newindex < writenow: while newindex < writenow:
if datacoding == DCB["WRITEOR"]: if datacoding == DCB["WRITEOR"]:
@@ -145,8 +148,8 @@ def write(old, newlist, offsets, datacoding, maxsize=None):
newpiece = chr( newpiece = chr(
ord(result[resultindex]) ^ ord(new[newindex])) ord(result[resultindex]) ^ ord(new[newindex]))
result = (result[0:resultindex] + newpiece + result = (result[0:resultindex] + newpiece +
result[resultindex+1:len(result)]) result[resultindex+1:len(result)])
newindex = newindex + 1 newindex = newindex + 1
resultindex = resultindex + 1 resultindex = resultindex + 1
listindex = listindex + 1 listindex = listindex + 1
@@ -160,23 +163,22 @@ def get_indexes(items, reference=REF["IDENTIFIER_FIRST"], index_current=0):
the current index 'index_current' (-1 for no current item) in the correct the current index 'index_current' (-1 for no current item) in the correct
order. I. e.: order. I. e.:
REF["IDENTIFIER_FIRST"] : all indexes from first to the last item REF["IDENTIFIER_FIRST"]: all indexes from first to the last item
REF["IDENTIFIER_LAST"] : all indexes from the last to first item REF["IDENTIFIER_LAST"]: all indexes from the last to first item
REF["IDENTIFIER_NEXT"] : all indexes from the next to the last item REF["IDENTIFIER_NEXT"]: all indexes from the next to the last item
REF["IDENTIFIER_PREVIOUS"] : all indexes from the previous to the first item REF["IDENTIFIER_PREVIOUS"]: all indexes from the previous to the first item
""" """
if (reference in [REF["IDENTIFIER_FIRST"], if (reference in [REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"]] or
REF["IDENTIFIER_LAST"]] index_current == -1):
or index_current == -1):
# Read first occurrence OR # Read first occurrence OR
# Read last occurrence OR # Read last occurrence OR
# No current record (and next/previous occurrence) # No current record (and next/previous occurrence)
indexes = range(0, len(items)) indexes = range(0, len(items))
if (reference == REF["IDENTIFIER_LAST"] if (reference == REF["IDENTIFIER_LAST"] or
or reference == REF["IDENTIFIER_PREVIOUS"]): reference == REF["IDENTIFIER_PREVIOUS"]):
# Read last occurrence OR # Read last occurrence OR
# No current record and previous occurrence # No current record and previous occurrence
indexes.reverse(); indexes.reverse()
elif reference == REF["IDENTIFIER_PREVIOUS"]: elif reference == REF["IDENTIFIER_PREVIOUS"]:
# Read previous occurrence # Read previous occurrence
indexes = range(0, index_current) indexes = range(0, index_current)
@@ -196,12 +198,14 @@ def prettyprint_anything(indent, thing):
indent = indent + " " indent = indent + " "
for (attribute, newvalue) in thing.__dict__.items(): for (attribute, newvalue) in thing.__dict__.items():
if isinstance(newvalue, int): if isinstance(newvalue, int):
s = s + "\n" + indent + attribute + (16-len(attribute))*" " + "0x%x" % (newvalue) s = s + "\n" + indent + attribute + (16-len(attribute)) * " " +\
"0x%x" % (newvalue)
elif isinstance(newvalue, str): elif isinstance(newvalue, str):
s = s + "\n" + indent + attribute + (16-len(attribute))*" " + "length %d:" % len(newvalue) s = s + "\n" + indent + attribute + (16-len(attribute)) * " " +\
"length %d:" % len(newvalue)
s = s + "\n" + indent + hexdump(newvalue, len(indent)) s = s + "\n" + indent + hexdump(newvalue, len(indent))
elif isinstance(newvalue, list): elif isinstance(newvalue, list):
s = s + "\n" + indent + attribute + (16-len(attribute))*" " s = s + "\n" + indent + attribute + (16 - len(attribute)) * " "
for item in newvalue: for item in newvalue:
s = s + "\n" + prettyprint_anything(indent + " ", item) s = s + "\n" + prettyprint_anything(indent + " ", item)
return s return s
@@ -219,26 +223,33 @@ def make_property(prop, doc):
doc) doc)
class File(object): class File(object):
"""Template class for a smartcard file.""" """Template class for a smartcard file."""
bertlv_data = make_property("bertlv_data", "list of (tag, length, value)-tuples of BER-TLV coded data objects (encrypted)") bertlv_data = make_property("bertlv_data", "list of (tag, length, "
lifecycle = make_property("lifecycle", "life cycle byte") "value)-tuples of BER-TLV "
parent = make_property("parent ", "parent DF") "coded data objects "
fid = make_property("fid", "file identifier") "(encrypted)")
lifecycle = make_property("lifecycle", "life cycle byte")
parent = make_property("parent ", "parent DF")
fid = make_property("fid", "file identifier")
filedescriptor = make_property("filedescriptor", "file descriptor byte") filedescriptor = make_property("filedescriptor", "file descriptor byte")
simpletlv_data = make_property("simpletlv_data", "list of (tag, length, value)-tuples of SIMPLE-TLV coded data objects (encrypted)") simpletlv_data = make_property("simpletlv_data", "list of (tag, length, "
"value)-tuples of "
"SIMPLE-TLV coded data "
"objects (encrypted)")
def __init__(self, parent, fid, filedescriptor, def __init__(self, parent, fid, filedescriptor,
lifecycle=LCB["ACTIVATED"], lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, simpletlv_data=None,
bertlv_data=None, bertlv_data=None,
SAM=None, SAM=None,
extra_fci_data=''): extra_fci_data=''):
""" """
The constructor is supposed to be involved by creation of a DF or EF. The constructor is supposed to be involved by creation of a DF or EF.
""" """
if (fid>0xFFFF or fid<0 or fid in [FID["PATHSELECTION"], if (fid > 0xFFFF or fid < 0 or fid in
FID["RESERVED"]] or filedescriptor>0xFF or lifecycle>0xFF): [FID["PATHSELECTION"], FID["RESERVED"]] or
filedescriptor > 0xFF or lifecycle > 0xFF):
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
self.lifecycle = lifecycle self.lifecycle = lifecycle
self.parent = parent self.parent = parent
@@ -251,21 +262,23 @@ class File(object):
self.extra_fci_data = extra_fci_data self.extra_fci_data = extra_fci_data
if simpletlv_data: if simpletlv_data:
if not isinstance(simpletlv_data, list): if not isinstance(simpletlv_data, list):
raise TypeError("must be a list of (tag, length, value)-tuples") raise TypeError("must be a list of (tag, length, "
"value)-tuples")
self.simpletlv_data = simpletlv_data self.simpletlv_data = simpletlv_data
if bertlv_data: if bertlv_data:
if not isinstance(bertlv_data, list): if not isinstance(bertlv_data, list):
raise TypeError("must be a list of (tag, length, value)-tuples") raise TypeError("must be a list of (tag, length, "
"value)-tuples")
self.bertlv_data = bertlv_data self.bertlv_data = bertlv_data
def decrypt(self, path, data): def decrypt(self, path, data):
if self.SAM == None: #WARNING: Fails silent if self.SAM is None: # WARNING: Fails silent
return data return data
else: else:
return self.SAM.FSencrypt(path, data) return self.SAM.FSencrypt(path, data)
def encrypt(self, path, data): def encrypt(self, path, data):
if self.SAM == None: #WARNING: Fails silent if self.SAM is None: # WARNING: Fails silent
return data return data
else: else:
return self.SAM.FSdecrypt(path, data) return self.SAM.FSdecrypt(path, data)
@@ -277,7 +290,7 @@ class File(object):
def getpath(self): def getpath(self):
"""Returns the path to this file beginning with the MF's fid.""" """Returns the path to this file beginning with the MF's fid."""
if self.parent == None: if self.parent is None:
return inttostring(self.fid, 2) return inttostring(self.fid, 2)
else: else:
return self.parent.getpath() + inttostring(self.fid, 2) return self.parent.getpath() + inttostring(self.fid, 2)
@@ -341,7 +354,7 @@ class File(object):
if t == tag: if t == tag:
# TODO: what if multiple tags can be found? # TODO: what if multiple tags can be found?
value = write(oldvalue, [newvalue], [0], newlength, value = write(oldvalue, [newvalue], [0], newlength,
self.datacoding) self.datacoding)
tlv_data[i] = (tag, len(value), value) tlv_data[i] = (tag, len(value), value)
tagfound = True tagfound = True
if not tagfound: if not tagfound:
@@ -385,22 +398,25 @@ class File(object):
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
class DF(File): class DF(File):
"""Class for a dedicated file""" """Class for a dedicated file"""
data = make_property("data", "unknown") data = make_property("data", "unknown")
content = make_property("content", "list of files of the DF") content = make_property("content", "list of files of the DF")
dfname = make_property("dfname", "string with up to 16 bytes. DF name, which can also be used as application identifier.") dfname = make_property("dfname", "string with up to 16 bytes. DF name,"
def __init__(self, parent, fid, filedescriptor=FDB["NOTSHAREABLEFILE"]|FDB["DF"], "which can also be used as application"
lifecycle=LCB["ACTIVATED"], "identifier.")
simpletlv_data=None, bertlv_data=None, dfname=None, data=""):
def __init__(self, parent, fid,
filedescriptor=FDB["NOTSHAREABLEFILE"] | FDB["DF"],
lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, bertlv_data=None, dfname=None, data=""):
""" """
See File for more. See File for more.
""" """
File.__init__(self, parent, fid, filedescriptor, lifecycle, File.__init__(self, parent, fid, filedescriptor, lifecycle,
simpletlv_data, bertlv_data) simpletlv_data, bertlv_data)
if dfname: if dfname:
if len(dfname)>16: if len(dfname) > 16:
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
self.dfname = dfname self.dfname = dfname
self.content = [] self.content = []
@@ -450,15 +466,17 @@ class DF(File):
for f in self.content: for f in self.content:
if f.fid == file.fid: if f.fid == file.fid:
raise SwError(SW["ERR_FILEEXISTS"]) raise SwError(SW["ERR_FILEEXISTS"])
if hasattr(f, 'dfname') and hasattr(file, 'dfname') and f.dfname == file.dfname: if (hasattr(f, 'dfname') and hasattr(file, 'dfname') and
f.dfname == file.dfname):
raise SwError(SW["ERR_DFNAMEEXISTS"]) raise SwError(SW["ERR_DFNAMEEXISTS"])
if hasattr(f, 'shortfid') and hasattr(file, 'shortfid') and f.shortfid == file.shortfid: if (hasattr(f, 'shortfid') and hasattr(file, 'shortfid') and
f.shortfid == file.shortfid):
raise SwError(SW["ERR_FILEEXISTS"]) raise SwError(SW["ERR_FILEEXISTS"])
self.content.append(file) self.content.append(file)
def select(self, attribute, value, reference=REF["IDENTIFIER_FIRST"], def select(self, attribute, value, reference=REF["IDENTIFIER_FIRST"],
index_current=0): index_current=0):
""" """
Returns the first file of the DF, that has the 'attribute' with the Returns the first file of the DF, that has the 'attribute' with the
specified 'value'. For partial DF name selection you must specify the specified 'value'. For partial DF name selection you must specify the
@@ -469,15 +487,18 @@ class DF(File):
for i in indexes: for i in indexes:
file = self.content[i] file = self.content[i]
if (hasattr(file, attribute) and ((getattr(file, attribute)==value) if (hasattr(file, attribute) and
or (attribute == 'dfname' and getattr(file, ((getattr(file, attribute) == value) or
attribute).startswith(value)))): (attribute == 'dfname' and
getattr(file, attribute).startswith(value)))):
return file return file
# not found # not found
if isinstance(value, int): if isinstance(value, int):
logging.debug("file (%s=%x) not found in:\n%s" % (attribute, value, self)) logging.debug("file (%s=%x) not found in:\n%s" %
(attribute, value, self))
elif isinstance(value, str): elif isinstance(value, str):
logging.debug("file (%s=%r) not found in:\n%s" % (attribute, value, self)) logging.debug("file (%s=%r) not found in:\n%s" %
(attribute, value, self))
raise SwError(SW["ERR_FILENOTFOUND"]) raise SwError(SW["ERR_FILENOTFOUND"])
def remove(self, file): def remove(self, file):
@@ -485,26 +506,29 @@ class DF(File):
self.content.remove(file) self.content.remove(file)
class MF(DF): class MF(DF):
"""Class for a master file""" """Class for a master file"""
current = make_property("current", "the currently selected file") current = make_property("current", "the currently selected file")
firstSFT = make_property("firstSFT", "string of length 1. The first software function table from the historical bytes.") firstSFT = make_property("firstSFT", "string of length 1. The first"
secondSFT = make_property("secondSFT", "string of length 1. The second software function table from the historical bytes.") "software function table from the"
def __init__(self, filedescriptor=FDB["NOTSHAREABLEFILE"]|FDB["DF"], "historical bytes.")
lifecycle=LCB["ACTIVATED"], secondSFT = make_property("secondSFT", "string of length 1. The second"
simpletlv_data=None, bertlv_data=None, dfname=None): "software function table from the"
"historical bytes.")
def __init__(self, filedescriptor=FDB["NOTSHAREABLEFILE"] | FDB["DF"],
lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, bertlv_data=None, dfname=None):
"""The file identifier FID["MF"] is automatically added. """The file identifier FID["MF"] is automatically added.
See DF for more. See DF for more.
""" """
DF.__init__(self, None, FID["MF"], filedescriptor, lifecycle, DF.__init__(self, None, FID["MF"], filedescriptor, lifecycle,
simpletlv_data, bertlv_data, dfname) simpletlv_data, bertlv_data, dfname)
self.current = self self.current = self
self.firstSFT = inttostring(MF.makeFirstSoftwareFunctionTable(), 1) self.firstSFT = inttostring(MF.makeFirstSoftwareFunctionTable(), 1)
self.secondSFT = inttostring(MF.makeSecondSoftwareFunctionTable(), 1) self.secondSFT = inttostring(MF.makeSecondSoftwareFunctionTable(), 1)
@staticmethod @staticmethod
def makeFirstSoftwareFunctionTable( def makeFirstSoftwareFunctionTable(
DFSelectionByFullDFName=True, DFSelectionByPartialDFName=True, DFSelectionByFullDFName=True, DFSelectionByPartialDFName=True,
@@ -534,16 +558,14 @@ class MF(DF):
fsft |= 1 fsft |= 1
return fsft return fsft
@staticmethod @staticmethod
def makeSecondSoftwareFunctionTable(DCB=DCB["ONETIMEWRITE"]|1): def makeSecondSoftwareFunctionTable(DCB=DCB["ONETIMEWRITE"] | 1):
""" """
The second software function table from the historical bytes contains The second software function table from the historical bytes contains
the data coding byte. the data coding byte.
""" """
return DCB return DCB
def currentDF(self): def currentDF(self):
"""Returns the current DF.""" """Returns the current DF."""
if isinstance(self.current, EF): if isinstance(self.current, EF):
@@ -565,8 +587,8 @@ class MF(DF):
The result is not prepended with tag and length for neither TCP, FMD The result is not prepended with tag and length for neither TCP, FMD
nor FCI template. nor FCI template.
""" """
fdm = [ chr(TAG["FILEIDENTIFIER"])+"\x02"+inttostring(file.fid, 2), fdm = [chr(TAG["FILEIDENTIFIER"]) + "\x02" + inttostring(file.fid, 2),
chr(TAG["LIFECYCLESTATUS"])+"\x01"+chr(file.lifecycle) ] chr(TAG["LIFECYCLESTATUS"]) + "\x01" + chr(file.lifecycle)]
fdm.append(file.extra_fci_data) fdm.append(file.extra_fci_data)
# TODO filesize and data objects # TODO filesize and data objects
@@ -579,11 +601,11 @@ class MF(DF):
if isinstance(file, TransparentStructureEF): if isinstance(file, TransparentStructureEF):
l = inttostring(len(file.data)) l = inttostring(len(file.data))
fdm.append("%c%c%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"], fdm.append("%c%c%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"],
chr(len(l)), l)) chr(len(l)), l))
fdm.append("%c%c%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"], fdm.append("%c%c%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"],
chr(len(l)), l)) chr(len(l)), l))
fdm.append("%c\x02%c%c" % (TAG["FILEDISCRIPTORBYTE"], fdm.append("%c\x02%c%c" % (TAG["FILEDISCRIPTORBYTE"],
file.filedescriptor, file.datacoding)) file.filedescriptor, file.datacoding))
elif isinstance(file, RecordStructureEF): elif isinstance(file, RecordStructureEF):
l = 0 l = 0
@@ -594,21 +616,23 @@ class MF(DF):
else: else:
l += len(r.data) l += len(r.data)
fdm.append("%c\x02%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"], fdm.append("%c\x02%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"],
inttostring(l, 2))) inttostring(l, 2)))
fdm.append("%c\x02%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"], fdm.append("%c\x02%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"],
inttostring(l, 2))) inttostring(l, 2)))
l = len(records) l = len(records)
fdm.append("%c\x06%c%c%c%c%s" % (TAG["FILEDISCRIPTORBYTE"], fdm.append("%c\x06%c%c%c%c%s" % (TAG["FILEDISCRIPTORBYTE"],
file.filedescriptor, file.datacoding, file.maxrecordsize >> file.filedescriptor, file.datacoding,
8, file.maxrecordsize & 0x00ff, inttostring(l, 2))) file.maxrecordsize >> 8,
file.maxrecordsize & 0x00ff,
inttostring(l, 2)))
elif isinstance(file, DF): elif isinstance(file, DF):
# TODO number of files == number of data bytes? # TODO number of files == number of data bytes?
fdm.append("%c\x01%c" % (TAG["FILEDISCRIPTORBYTE"], fdm.append("%c\x01%c" % (TAG["FILEDISCRIPTORBYTE"],
file.filedescriptor)) file.filedescriptor))
if hasattr(file, 'dfname'): if hasattr(file, 'dfname'):
fdm.append("%c%c%s" % (TAG["DFNAME"], len(file.dfname), fdm.append("%c%c%s" % (TAG["DFNAME"], len(file.dfname),
file.dfname)) file.dfname))
else: else:
raise TypeError raise TypeError
@@ -620,15 +644,15 @@ class MF(DF):
Returns the file specified by 'p1' and 'data' from the select Returns the file specified by 'p1' and 'data' from the select
file command APDU. file command APDU.
""" """
P1_FILE = 0x00 P1_FILE = 0x00
P1_CHILD_DF = 0x01 P1_CHILD_DF = 0x01
P1_CHILD_EF = 0x02 P1_CHILD_EF = 0x02
P1_PARENT_DF = 0x03 P1_PARENT_DF = 0x03
P1_DF_NAME = 0x04 P1_DF_NAME = 0x04
P1_PATH_FROM_MF = 0x08 P1_PATH_FROM_MF = 0x08
P1_PATH_FROM_CURRENTDF = 0x09 P1_PATH_FROM_CURRENTDF = 0x09
if (p1>>4) != 0 or p1 == P1_FILE: if (p1 >> 4) != 0 or p1 == P1_FILE:
# RFU OR # RFU OR
# When P1='00', the card knows either because of a specific coding # When P1='00', the card knows either because of a specific coding
# of the file identifier or because of the context of execution of # of the file identifier or because of the context of execution of
@@ -657,13 +681,14 @@ class MF(DF):
index_current = -1 index_current = -1
else: else:
index_current = self.content.index(df) index_current = self.content.index(df)
selected = self.select('dfname', data, p2 & selected = self.select('dfname', data,
REF["REFERENCE_CONTROL_SELECT"], index_current) p2 & REF["REFERENCE_CONTROL_SELECT"],
index_current)
else: else:
logging.debug("unknown selection method: p1 =%s" % p1) logging.debug("unknown selection method: p1 =%s" % p1)
selected = None selected = None
if selected == None: if selected is None:
raise SwError(SW["ERR_FILENOTFOUND"]) raise SwError(SW["ERR_FILENOTFOUND"])
return selected return selected
@@ -676,9 +701,9 @@ class MF(DF):
:returns: the status bytes as two byte long integer and the response :returns: the status bytes as two byte long integer and the response
data as binary string. data as binary string.
""" """
P2_FCI = 0 P2_FCI = 0
P2_FCP = 1 << 2 P2_FCP = 1 << 2
P2_FMD = 2 << 2 P2_FMD = 2 << 2
P2_NONE = 3 << 2 P2_NONE = 3 << 2
file = self._selectFile(p1, p2, data) file = self._selectFile(p1, p2, data)
@@ -744,7 +769,7 @@ class MF(DF):
# or response data field, data shall be encapsulated in a # or response data field, data shall be encapsulated in a
# discretionary data object with tag '53' or '73'. # discretionary data object with tag '53' or '73'.
tlv_data = bertlv_unpack(data) tlv_data = bertlv_unpack(data)
offsets = decodeOffsetDataObjects(tlv_data) offsets = decodeOffsetDataObjects(tlv_data)
datalist = decodeDiscretionaryDataObjects(tlv_data) datalist = decodeDiscretionaryDataObjects(tlv_data)
if p1 == 0 and p2 >> 5 == 0: if p1 == 0 and p2 >> 5 == 0:
@@ -860,7 +885,8 @@ class MF(DF):
Function for instruction 0x0e. Takes the parameter bytes 'p1', 'p2' as Function for instruction 0x0e. Takes the parameter bytes 'p1', 'p2' as
integers and 'data' as binary string. integers and 'data' as binary string.
:returns: the status bytes as two byte long integer and the response data as binary string. :returns: the status bytes as two byte long integer and the response
data as binary string.
""" """
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
# If INS = '0E', then, if present, the command data field encodes # If INS = '0E', then, if present, the command data field encodes
@@ -926,7 +952,7 @@ class MF(DF):
shortfid = p2 >> 3 shortfid = p2 >> 3
if shortfid == 0: if shortfid == 0:
ef = self.currentEF() ef = self.currentEF()
if ef == None: if ef is None:
raise SwError(SW["ERR_NOCURRENTEF"]) raise SwError(SW["ERR_NOCURRENTEF"])
elif shortfid == 0x1f: elif shortfid == 0x1f:
# RFU # RFU
@@ -977,9 +1003,11 @@ class MF(DF):
data as binary string. data as binary string.
""" """
ef, num_id, reference = self.recordHandlingDecode(p1, p2) ef, num_id, reference = self.recordHandlingDecode(p1, p2)
if reference not in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"], if reference not in [REF["IDENTIFIER_FIRST"],
REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"], REF["IDENTIFIER_LAST"],
REF["NUMBER"]]: REF["IDENTIFIER_NEXT"],
REF["IDENTIFIER_PREVIOUS"],
REF["NUMBER"]]:
# RFU # RFU
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
ef.writerecord(num_id, reference, 1, data) ef.writerecord(num_id, reference, 1, data)
@@ -995,9 +1023,11 @@ class MF(DF):
data as binary string. data as binary string.
""" """
ef, num_id, reference = self.recordHandlingDecode(p1, p2) ef, num_id, reference = self.recordHandlingDecode(p1, p2)
if reference not in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"], if reference not in [REF["IDENTIFIER_FIRST"],
REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"], REF["IDENTIFIER_LAST"],
REF["NUMBER"]]: REF["IDENTIFIER_NEXT"],
REF["IDENTIFIER_PREVIOUS"],
REF["NUMBER"]]:
# RFU # RFU
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
ef.updaterecord(num_id, reference, 0, data) ef.updaterecord(num_id, reference, 0, data)
@@ -1015,31 +1045,37 @@ class MF(DF):
ef, num_id, reference = self.recordHandlingDecode(p1, p2) ef, num_id, reference = self.recordHandlingDecode(p1, p2)
P2_REPLACE = 0x04 P2_REPLACE = 0x04
P2_AND = 0x05 P2_AND = 0x05
P2_OR = 0x06 P2_OR = 0x06
#P2_XOR = 0x07 # P2_XOR = 0x07
tlv_data = bertlv_unpack(data) tlv_data = bertlv_unpack(data)
if reference in [ REF["IDENTIFIER_FIRST"], REF["IDENTIFIER_LAST"], if reference in [REF["IDENTIFIER_FIRST"],
REF["IDENTIFIER_NEXT"], REF["IDENTIFIER_PREVIOUS"]]: REF["IDENTIFIER_LAST"],
REF["IDENTIFIER_NEXT"],
REF["IDENTIFIER_PREVIOUS"]]:
# RFU # RFU
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
elif reference == P2_REPLACE: elif reference == P2_REPLACE:
ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0], ef.writerecord(num_id, reference,
decodeDiscretionaryDataObjects(tlv_data)[0], decodeOffsetDataObjects(tlv_data)[0],
DCB["ONETIMEWRITE"]) decodeDiscretionaryDataObjects(tlv_data)[0],
DCB["ONETIMEWRITE"])
elif reference == P2_AND: elif reference == P2_AND:
ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0], ef.writerecord(num_id, reference,
decodeDiscretionaryDataObjects(tlv_data)[0], decodeOffsetDataObjects(tlv_data)[0],
DCB["WRITEAND"]) decodeDiscretionaryDataObjects(tlv_data)[0],
DCB["WRITEAND"])
elif reference == P2_OR: elif reference == P2_OR:
ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0], ef.writerecord(num_id, reference,
decodeDiscretionaryDataObjects(tlv_data)[0], decodeOffsetDataObjects(tlv_data)[0],
DCB["WRITEOR"]) decodeDiscretionaryDataObjects(tlv_data)[0],
DCB["WRITEOR"])
else: else:
# reference == P2_XOR: # reference == P2_XOR:
ef.writerecord(num_id, reference, decodeOffsetDataObjects(tlv_data)[0], ef.writerecord(num_id, reference,
decodeDiscretionaryDataObjects(tlv_data)[0], decodeOffsetDataObjects(tlv_data)[0],
DCB["PROPRIETARY"]) decodeDiscretionaryDataObjects(tlv_data)[0],
DCB["PROPRIETARY"])
return SW["NORMAL"], "" return SW["NORMAL"], ""
@@ -1085,17 +1121,17 @@ class MF(DF):
SIMPLE-TLV data objects False otherwise and a list of SIMPLE-TLV data objects False otherwise and a list of
(tag, length, value)-tuples. (tag, length, value)-tuples.
""" """
if self.current == None: if self.current is None:
raise SwError(SW["ERR_NOCURRENTEF"]) raise SwError(SW["ERR_NOCURRENTEF"])
file = self.current file = self.current
if (p1 == 0 and 0x40 <= p2 and p2 <= 0xfe) or (0x40 <= p1 and p2 != 0 if ((p1 == 0 and 0x40 <= p2 and p2 <= 0xfe) or
and p2 != 0xff): (0x40 <= p1 and p2 != 0 and p2 != 0xff)):
# If bit 1 of INS is set to 0 and P1 to '00', then P2 from '40' to # If bit 1 of INS is set to 0 and P1 to '00', then P2 from '40' to
# 'FE' shall be a BER-TLV tag on a single byte. OR # 'FE' shall be a BER-TLV tag on a single byte. OR
# If bit 1 of INS is set to 0 and if P1-P2 lies from '4000' to # If bit 1 of INS is set to 0 and if P1-P2 lies from '4000' to
# 'FFFF', then they shall be a BER-TLV tag on two bytes. # 'FFFF', then they shall be a BER-TLV tag on two bytes.
tlv_data = [((p1<<8) + p2, len(data), data)] tlv_data = [((p1 << 8) + p2, len(data), data)]
isSimpleTlv = False isSimpleTlv = False
elif p1 == 0x02 and 0x01 <= p2 and p2 <= 0xfe: elif p1 == 0x02 and 0x01 <= p2 and p2 <= 0xfe:
# If bit 1 of INS is set to 0 and P1 to '02', then P2 from '01' to # If bit 1 of INS is set to 0 and P1 to '02', then P2 from '01' to
@@ -1141,10 +1177,11 @@ class MF(DF):
# data field provides a file reference data object (tag '51', see # data field provides a file reference data object (tag '51', see
# 5.3.1.2) for identifying a file. # 5.3.1.2) for identifying a file.
file = getfile_byrefdataobj(self, file = getfile_byrefdataobj(self,
tlv_find_tag(tlv_data, TAG["FILE_REFERENCE"])[0]) tlv_find_tag(tlv_data,
if file == None: TAG["FILE_REFERENCE"])[0])
if file is None:
file = self.currentEF() file = self.currentEF()
if file == None: if file is None:
raise SwError(SW["ERR_NOCURRENTEF"]) raise SwError(SW["ERR_NOCURRENTEF"])
elif p1 == 0 and (p2 >> 5) == 0: elif p1 == 0 and (p2 >> 5) == 0:
# If the first eleven bits of P1-P2 are set to 0 and if bits 5 to 1 # If the first eleven bits of P1-P2 are set to 0 and if bits 5 to 1
@@ -1157,9 +1194,9 @@ class MF(DF):
file = self.currentDF() file = self.currentDF()
else: else:
# Otherwise, P1-P2 is a file identifier. # Otherwise, P1-P2 is a file identifier.
file = self.currentDF().select('fid', p1<<8 + p2) file = self.currentDF().select('fid', p1 << 8 + p2)
if file == None: if file is None:
raise SwError(SW["ERR_FILENOTFOUND"]) raise SwError(SW["ERR_FILENOTFOUND"])
self.current = file self.current = file
@@ -1173,10 +1210,13 @@ class MF(DF):
:returns: the status bytes as two byte long integer and the response :returns: the status bytes as two byte long integer and the response
data as binary string. data as binary string.
""" """
file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1, p2, data) file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1,
p2,
data)
# TODO oversized answers # TODO oversized answers
if len(tlvlist) > 0: if len(tlvlist) > 0:
return SW["NORMAL"], file.getdata(isSimpleTlv, [(tlvlist[0][0], 0)]) return SW["NORMAL"], file.getdata(isSimpleTlv,
[(tlvlist[0][0], 0)])
else: else:
return SW["NORMAL"], file.getdata(isSimpleTlv, []) return SW["NORMAL"], file.getdata(isSimpleTlv, [])
@@ -1188,7 +1228,9 @@ class MF(DF):
:returns: the status bytes as two byte long integer and the response :returns: the status bytes as two byte long integer and the response
data as binary string. data as binary string.
""" """
file, tlv_data = self.dataObjectHandlingDecodeEncapsulated(p1, p2, data) file, tlv_data = self.dataObjectHandlingDecodeEncapsulated(p1,
p2,
data)
# TODO oversized answers # TODO oversized answers
requestedTL = decodeTagList(tlv_data) requestedTL = decodeTagList(tlv_data)
if requestedTL == []: if requestedTL == []:
@@ -1206,7 +1248,9 @@ class MF(DF):
:returns: the status bytes as two byte long integer and the response :returns: the status bytes as two byte long integer and the response
data as binary string. data as binary string.
""" """
file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1, p2, data) file, isSimpleTlv, tlvlist = self.dataObjectHandlingDecodePlain(p1,
p2,
data)
file.putdata(isSimpleTlv, tlvlist) file.putdata(isSimpleTlv, tlvlist)
return SW["NORMAL"], "" return SW["NORMAL"], ""
@@ -1224,7 +1268,6 @@ class MF(DF):
return SW["NORMAL"], "" return SW["NORMAL"], ""
@staticmethod @staticmethod
def create(p1, p2, data): def create(p1, p2, data):
""" """
@@ -1237,45 +1280,54 @@ class MF(DF):
# TODO number of records on one or two bytes # TODO number of records on one or two bytes
raise SwError(SW["ERR_NOTSUPPORTED"]) raise SwError(SW["ERR_NOTSUPPORTED"])
if l >= 3: if l >= 3:
args["maxrecordsize"] = stringtoint(value[2:]) args["maxrecordsize"] = stringtoint(value[2:])
if l >= 2: if l >= 2:
args["datacoding"] = ord(value[1]) args["datacoding"] = ord(value[1])
if l >= 1: if l >= 1:
args["filedescriptor"] = ord(value[0]) args["filedescriptor"] = ord(value[0])
def shortfid2args(value, args): def shortfid2args(value, args):
s = stringtoint(value) s = stringtoint(value)
if (s & 7) == 0: if (s & 7) == 0:
shortfid = s >> 3 shortfid = s >> 3
if shortfid != 0: if shortfid != 0:
args["shortfid"] = shortfid args["shortfid"] = shortfid
def unknown(tag, value): def unknown(tag, value):
logging.debug("unknown tag 0x%x with %r" % (tag, value)) logging.debug("unknown tag 0x%x with %r" % (tag, value))
tag2cmd = { tag2cmd = {
# TODO support other tags # TODO support other tags
TAG["FILEDISCRIPTORBYTE"] : 'fdb2args(value, args)', TAG["FILEDISCRIPTORBYTE"]: 'fdb2args(value, args)',
TAG["FILEIDENTIFIER"] : 'args["fid"] = stringtoint(value)', TAG["FILEIDENTIFIER"]: 'args["fid"] = stringtoint(value)',
TAG["DFNAME"] : 'args["dfname"] = value', TAG["DFNAME"]: 'args["dfname"] = value',
TAG["SHORTFID"] : 'shortfid2args(value, args)', TAG["SHORTFID"]: 'shortfid2args(value, args)',
TAG["LIFECYCLESTATUS"] : 'args["lifecycle"] = stringtoint(value)', TAG["LIFECYCLESTATUS"]: 'args["lifecycle"] = ' + \
TAG["BYTES_EXCLUDINGSTRUCTURE"] : 'args["data"] = chr(0)*stringtoint(value)', 'stringtoint(value)',
TAG["BYTES_INCLUDINGSTRUCTURE"] : 'args["data"] = chr(0)*stringtoint(value)', TAG["BYTES_EXCLUDINGSTRUCTURE"]: 'args["data"] = chr(0) ' + \
'* stringtoint(value)',
TAG["BYTES_INCLUDINGSTRUCTURE"]: 'args["data"] = chr(0) ' + \
'* stringtoint(value)',
} }
fcp_list = tlv_find_tags( bertlv_unpack(data), fcp_list = tlv_find_tags(bertlv_unpack(data),
[TAG["FILECONTROLINFORMATION"], TAG["FILECONTROLPARAMETERS"]]) [TAG["FILECONTROLINFORMATION"],
TAG["FILECONTROLPARAMETERS"]])
if not fcp_list: if not fcp_list:
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
files = [] files = []
args = { "parent": None } args = {"parent": None}
if p1 != 0: if p1 != 0:
args["filedescriptor"] = p1 args["filedescriptor"] = p1
if (p2 >> 3) != 0: if (p2 >> 3) != 0:
args["shortfid"] = p2 >> 3 args["shortfid"] = p2 >> 3
for T, _, tlv_data in fcp_list: for T, _, tlv_data in fcp_list:
if T != TAG["FILECONTROLPARAMETERS"] and T != TAG["FILECONTROLINFORMATION"]: if (T != TAG["FILECONTROLPARAMETERS"] and
T != TAG["FILECONTROLINFORMATION"]):
raise ValueError raise ValueError
for tag, _, value in tlv_data: for tag, _, value in tlv_data:
exec tag2cmd.get(tag, 'unknown(tag, value)') in locals(), globals() exec tag2cmd.get(tag, 'unknown(tag, value)') in locals(),\
globals()
if (args["filedescriptor"] & FDB["DF"]) == FDB["DF"]: if (args["filedescriptor"] & FDB["DF"]) == FDB["DF"]:
# FIXME: data for DF # FIXME: data for DF
@@ -1287,8 +1339,8 @@ class MF(DF):
[FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"], [FDB["EFSTRUCTURE_NOINFORMATIONGIVEN"],
FDB["EFSTRUCTURE_TRANSPARENT"]]): FDB["EFSTRUCTURE_TRANSPARENT"]]):
file = TransparentStructureEF(**args) file = TransparentStructureEF(**args)
file.writebinary( decodeOffsetDataObjects(tlv_data), file.writebinary(decodeOffsetDataObjects(tlv_data),
decodeDiscretionaryDataObjects(tlv_data) ) decodeDiscretionaryDataObjects(tlv_data))
else: else:
file = RecordStructureEF(**args) file = RecordStructureEF(**args)
@@ -1305,7 +1357,7 @@ class MF(DF):
data as binary string. data as binary string.
""" """
df = self.currentDF() df = self.currentDF()
if df == None: if df is None:
raise SwError(SW["ERR_NOCURRENTEF"]) raise SwError(SW["ERR_NOCURRENTEF"])
for file in self.create(p1, p2, data): for file in self.create(p1, p2, data):
@@ -1325,20 +1377,22 @@ class MF(DF):
""" """
file = self._selectFile(p1, p2, data) file = self._selectFile(p1, p2, data)
file.parent.content.remove(file) file.parent.content.remove(file)
# FIXME: free memory of file and remove its content from the security device # FIXME: free memory of file and remove its content from the security
# device
return SW["NORMAL"], "" return SW["NORMAL"], ""
class EF(File): class EF(File):
"""Template class for an elementary file.""" """Template class for an elementary file."""
shortfid = make_property("shortfid", "integer with 1<=shortfid<=30. Short EF identifier.") shortfid = make_property("shortfid", "integer with 1<=shortfid<=30."
"Short EF identifier.")
datacoding = make_property("datacoding", "integer. Data coding byte.") datacoding = make_property("datacoding", "integer. Data coding byte.")
def __init__(self, parent, fid, filedescriptor, def __init__(self, parent, fid, filedescriptor,
lifecycle=LCB["ACTIVATED"], lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, bertlv_data=None, simpletlv_data=None, bertlv_data=None,
datacoding=DCB["ONETIMEWRITE"], shortfid=0): datacoding=DCB["ONETIMEWRITE"], shortfid=0):
""" """
The constructor is supposed to be involved creation of a by creation of The constructor is supposed to be involved creation of a by creation of
a TransparentStructureEF or RecordStructureEF. a TransparentStructureEF or RecordStructureEF.
@@ -1353,26 +1407,27 @@ class EF(File):
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
self.shortfid = shortfid self.shortfid = shortfid
File.__init__(self, parent, fid, filedescriptor, lifecycle, File.__init__(self, parent, fid, filedescriptor, lifecycle,
simpletlv_data, simpletlv_data,
bertlv_data) bertlv_data)
self.datacoding = datacoding self.datacoding = datacoding
class TransparentStructureEF(EF): class TransparentStructureEF(EF):
"""Class for an elementary file with transparent structure.""" """Class for an elementary file with transparent structure."""
data = make_property("data", "string (encrypted). The file's data.") data = make_property("data", "string (encrypted). The file's data.")
def __init__(self, parent, fid, filedescriptor=FDB["EFSTRUCTURE_TRANSPARENT"],
lifecycle=LCB["ACTIVATED"], def __init__(self, parent, fid,
simpletlv_data=None, bertlv_data=None, filedescriptor=FDB["EFSTRUCTURE_TRANSPARENT"],
datacoding=DCB["ONETIMEWRITE"], shortfid=0, data=""): lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, bertlv_data=None,
datacoding=DCB["ONETIMEWRITE"], shortfid=0, data=""):
""" """
See EF for more. See EF for more.
""" """
EF.__init__(self, parent, fid, EF.__init__(self, parent, fid,
filedescriptor, lifecycle, filedescriptor, lifecycle,
simpletlv_data, bertlv_data, simpletlv_data, bertlv_data,
datacoding, shortfid) datacoding, shortfid)
self.data = data self.data = data
def readbinary(self, offset): def readbinary(self, offset):
@@ -1405,7 +1460,8 @@ class TransparentStructureEF(EF):
def updatebinary(self, offsets, datalist): def updatebinary(self, offsets, datalist):
""" """
x.updatebinary(offsets, datalist) <==> x.writebinary(offsets, datalist, DCB["ONETIMEWRITE"]) x.updatebinary(offsets, datalist) <==>
x.writebinary(offsets, datalist, DCB["ONETIMEWRITE"])
""" """
return self.writebinary(offsets, datalist, DCB["ONETIMEWRITE"]) return self.writebinary(offsets, datalist, DCB["ONETIMEWRITE"])
@@ -1415,9 +1471,9 @@ class TransparentStructureEF(EF):
sequentially starting from 'erasefrom' ending at 'eraseto'. sequentially starting from 'erasefrom' ending at 'eraseto'.
""" """
data = self.data data = self.data
if erasefrom == None: if erasefrom is None:
erasefrom = 0 erasefrom = 0
if eraseto == None: if eraseto is None:
eraseto = len(data) eraseto = len(data)
if erasefrom > len(data): if erasefrom > len(data):
@@ -1429,10 +1485,10 @@ class TransparentStructureEF(EF):
self.data = data self.data = data
class Record(object): class Record(object):
data = make_property("data", "string. The record's data.") data = make_property("data", "string. The record's data.")
identifier = make_property("identifier", "integer with 1<= identifier< = 0xfe. The record's identifier.") identifier = make_property("identifier", "integer with 1 <= identifier <="
" 0xfe. The record's identifier.")
"""Class for a Record of an elementary of record structure""" """Class for a Record of an elementary of record structure"""
def __init__(self, identifier=None, data=""): def __init__(self, identifier=None, data=""):
""" """
@@ -1450,17 +1506,20 @@ class Record(object):
__repr__ = __str__ __repr__ = __str__
class RecordStructureEF(EF): class RecordStructureEF(EF):
"""Class for an elementary file with record structure.""" """Class for an elementary file with record structure."""
records = make_property("records", "list of records (encrypted)") records = make_property("records", "list of records (encrypted)")
maxrecordsize = make_property("maxrecordsize", "integer. maximum length of a record's data.") maxrecordsize = make_property("maxrecordsize", "integer. maximum length of"
recordpointer = make_property("recordpointer", "integer. Points to the current record (i. e. index of records).") " a record's data.")
recordpointer = make_property("recordpointer", "integer. Points to the "
"current record (i. e. "
"index of records).")
def __init__(self, parent, fid, filedescriptor, def __init__(self, parent, fid, filedescriptor,
lifecycle=LCB["ACTIVATED"], lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, simpletlv_data=None,
bertlv_data=None, datacoding=DCB["ONETIMEWRITE"], shortfid=0, bertlv_data=None, datacoding=DCB["ONETIMEWRITE"], shortfid=0,
maxrecordsize=0xffff, records=[]): maxrecordsize=0xffff, records=[]):
""" """
You should specify the appropriate file descriptor byte to specify You should specify the appropriate file descriptor byte to specify
which kind of record structured file you want to create (i. e. which kind of record structured file you want to create (i. e.
@@ -1474,11 +1533,11 @@ class RecordStructureEF(EF):
if not isinstance(records, list): if not isinstance(records, list):
raise TypeError("must be a list of Records") raise TypeError("must be a list of Records")
EF.__init__(self, parent, fid, filedescriptor, lifecycle, EF.__init__(self, parent, fid, filedescriptor, lifecycle,
simpletlv_data, bertlv_data, simpletlv_data, bertlv_data,
datacoding, shortfid) datacoding, shortfid)
for r in records: for r in records:
if len(r.data) > maxrecordsize or (self.hasFixedRecordSize() and if (len(r.data) > maxrecordsize or (self.hasFixedRecordSize() and
len(r.data) < maxrecordsize): len(r.data) < maxrecordsize)):
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
self.records = records self.records = records
self.resetRecordPointer() self.resetRecordPointer()
@@ -1491,8 +1550,8 @@ class RecordStructureEF(EF):
def isCyclic(self): def isCyclic(self):
"""Returns True if the EF is of cyclic structure, False otherwise.""" """Returns True if the EF is of cyclic structure, False otherwise."""
attr = self.filedescriptor & 0x07 attr = self.filedescriptor & 0x07
if (attr==FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] or if (attr == FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] or
attr==FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"]): attr == FDB["EFSTRUCTURE_CYCLIC_SIMPLETLV"]):
return True return True
else: else:
return False return False
@@ -1500,11 +1559,12 @@ class RecordStructureEF(EF):
def hasSimpleTlv(self): def hasSimpleTlv(self):
"""Returns True if the EF is of TLV structure, False otherwise.""" """Returns True if the EF is of TLV structure, False otherwise."""
attr = self.filedescriptor & 0x03 attr = self.filedescriptor & 0x03
if (attr==FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] or if (attr == FDB["EFSTRUCTURE_LINEAR_FIXED_SIMPLETLV"] or
attr==FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] or attr == FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"] or
attr==FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"]): attr == FDB["EFSTRUCTURE_LINEAR_VARIABLESIMPLETLV"]):
return True return True
else: return False else:
return False
def hasFixedRecordSize(self): def hasFixedRecordSize(self):
"""Returns True if the records are of fixed size, False otherwise.""" """Returns True if the records are of fixed size, False otherwise."""
@@ -1520,7 +1580,8 @@ class RecordStructureEF(EF):
Returns a list of records. Returns a list of records.
:param num_id: The requested record's number or identifier :param num_id: The requested record's number or identifier
:param reference: Specifies which record to select (usually the last 3 bits of 'p1' of a record handling command) :param reference: Specifies which record to select (usually the last 3
bits of 'p1' of a record handling command)
""" """
if (reference >> 2) == 1: if (reference >> 2) == 1:
return self.__getRecordsByNumber(num_id, reference) return self.__getRecordsByNumber(num_id, reference)
@@ -1535,7 +1596,7 @@ class RecordStructureEF(EF):
:param reference: Specifies which record to select (usually the last 3 :param reference: Specifies which record to select (usually the last 3
bits of 'p1' of a record handling command) bits of 'p1' of a record handling command)
""" """
result = [] result = []
records = self.records records = self.records
if number == 0: if number == 0:
@@ -1569,12 +1630,12 @@ class RecordStructureEF(EF):
:param reference: Specifies which record to select (usually the last 3 :param reference: Specifies which record to select (usually the last 3
bits of 'p1' of a record handling command) bits of 'p1' of a record handling command)
""" """
result = [] result = []
records = self.records records = self.records
indexes = get_indexes(records, reference, self.recordpointer) indexes = get_indexes(records, reference, self.recordpointer)
for i in indexes: for i in indexes:
if (not self.hasSimpleTlv()) or records[i].identifier==id: if (not self.hasSimpleTlv()) or records[i].identifier == id:
if result == []: if result == []:
self.recordpointer = i self.recordpointer = i
result.append(records[i]) result.append(records[i])
@@ -1590,8 +1651,8 @@ class RecordStructureEF(EF):
Returns a data string from the given 'offset'. 'num_id' and 'reference' Returns a data string from the given 'offset'. 'num_id' and 'reference'
specify the record (see __getRecords). specify the record (see __getRecords).
""" """
records = self.__getRecords(num_id, reference) records = self.__getRecords(num_id, reference)
result = [] result = []
for r in records: for r in records:
if offset == 0: if offset == 0:
result.append(r.data) result.append(r.data)
@@ -1605,7 +1666,7 @@ class RecordStructureEF(EF):
return result return result
def writerecord(self, num_id, reference, offset, data, def writerecord(self, num_id, reference, offset, data,
datacoding=None): datacoding=None):
""" """
Writes a data string to the 'offset' of a record using the given data Writes a data string to the 'offset' of a record using the given data
coding byte. 'num_id' and 'reference' specify the record (see coding byte. 'num_id' and 'reference' specify the record (see
@@ -1618,10 +1679,10 @@ class RecordStructureEF(EF):
if datacoding: if datacoding:
records[0].data = write(records[0].data, [data], [0], datacoding, records[0].data = write(records[0].data, [data], [0], datacoding,
self.maxrecordsize) self.maxrecordsize)
else: else:
records[0].data = write(records[0].data, [data], [0], records[0].data = write(records[0].data, [data], [0],
self.datacoding, self.maxrecordsize) self.datacoding, self.maxrecordsize)
if self.hasSimpleTlv(): if self.hasSimpleTlv():
# identifier/tag could have changed # identifier/tag could have changed
@@ -1629,9 +1690,11 @@ class RecordStructureEF(EF):
def updaterecord(self, num_id, reference, offset, data): def updaterecord(self, num_id, reference, offset, data):
""" """
x.updaterecord(num_id, reference, offset, data) <==> x.writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"]) x.updaterecord(num_id, reference, offset, data) <==>
x.writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"])
""" """
return self.writerecord(num_id, reference, offset, data, DCB["ONETIMEWRITE"]) return self.writerecord(num_id, reference, offset, data,
DCB["ONETIMEWRITE"])
def appendrecord(self, data): def appendrecord(self, data):
""" """
@@ -1669,4 +1732,3 @@ class RecordStructureEF(EF):
r.data = "" r.data = ""
r.identifier = None r.identifier = None
return SW["NORMAL"] return SW["NORMAL"]

View File

@@ -26,6 +26,7 @@ from virtualsmartcard.SWutils import SwError, SW
from virtualsmartcard.utils import inttostring, stringtoint, hexdump from virtualsmartcard.utils import inttostring, stringtoint, hexdump
from virtualsmartcard.SEutils import Security_Environment from virtualsmartcard.SEutils import Security_Environment
def get_referenced_cipher(p1): def get_referenced_cipher(p1):
""" """
P1 defines the algorithm and mode to use. We dispatch it and return a P1 defines the algorithm and mode to use. We dispatch it and return a
@@ -44,11 +45,12 @@ def get_referenced_cipher(p1):
0x08: "DSA" 0x08: "DSA"
} }
if (ciphertable.has_key(p1)): if (p1 in ciphertable):
return ciphertable[p1] return ciphertable[p1]
else: else:
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
class SAM(object): class SAM(object):
""" """
This class is used to store the data needed by the SAM. This class is used to store the data needed by the SAM.
@@ -57,29 +59,30 @@ class SAM(object):
indexed via the path to the corresponding container. indexed via the path to the corresponding container.
""" """
def __init__(self, PIN, cardNumber, mf=None, cardSecret=None, default_se=Security_Environment): def __init__(self, PIN, cardNumber, mf=None, cardSecret=None,
default_se=Security_Environment):
self.PIN = PIN self.PIN = PIN
self.mf = mf self.mf = mf
self.cardNumber = cardNumber self.cardNumber = cardNumber
self.last_challenge = None #Will contain non-readable binary string self.last_challenge = None # Will contain non-readable binary string
self.counter = 3 #Number of tries for PIN validation self.counter = 3 # Number of tries for PIN validation
self.cipher = 0x01 self.cipher = 0x01
self.asym_key = None self.asym_key = None
keylen = vsCrypto.get_cipher_keylen(get_referenced_cipher(self.cipher)) keylen = vsCrypto.get_cipher_keylen(get_referenced_cipher(self.cipher))
if cardSecret is None: #Generate a random card secret if cardSecret is None: # Generate a random card secret
self.cardSecret = urandom(keylen) self.cardSecret = urandom(keylen)
else: else:
if len(cardSecret) != keylen: if len(cardSecret) != keylen:
raise ValueError("cardSecret has the wrong key length for: " +\ raise ValueError("cardSecret has the wrong key length for: " +
get_referenced_cipher(self.cipher)) get_referenced_cipher(self.cipher))
else: else:
self.cardSecret = cardSecret self.cardSecret = cardSecret
#Security Environments may be saved to/retrieved from this dictionary # Security Environments may be saved to/retrieved from this dictionary
self.saved_SEs = {} self.saved_SEs = {}
self.default_se = default_se self.default_se = default_se
self.current_SE = default_se(self.mf, self) self.current_SE = default_se(self.mf, self)
@@ -109,8 +112,8 @@ class SAM(object):
def store_SE(self, SEID): def store_SE(self, SEID):
""" """
Stores the current Security environment in the secure access module. The Stores the current Security environment in the secure access module.
SEID is used as a reference to identify the SE. The SEID is used as a reference to identify the SE.
""" """
SEstr = dumps(self.current_SE) SEstr = dumps(self.current_SE)
self.saved_SEs[SEID] = SEstr self.saved_SEs[SEID] = SEstr
@@ -118,11 +121,11 @@ class SAM(object):
def restore_SE(self, SEID): def restore_SE(self, SEID):
""" """
Restores a Security Environment from the SAM and replaces the current SE Restores a Security Environment from the SAM and replaces the current
with it SE with it.
""" """
if (not self.saved_SEs.has_key(SEID)): if (SEID not in self.saved_SEs):
raise SwError(SW["ERR_REFNOTUSABLE"]) raise SwError(SW["ERR_REFNOTUSABLE"])
else: else:
SEstr = self.saved_SEs[SEID] SEstr = self.saved_SEs[SEID]
@@ -134,12 +137,11 @@ class SAM(object):
return SW["NORMAL"], "" return SW["NORMAL"], ""
def erase_SE(self, SEID): def erase_SE(self, SEID):
""" """
Erases a Security Environment stored under SEID from the SAM Erases a Security Environment stored under SEID from the SAM
""" """
if (not self.saved_SEs.has_key(SEID)): if (SEID not in self.saved_SEs):
raise SwError(SW["ERR_REFNOTUSABLE"]) raise SwError(SW["ERR_REFNOTUSABLE"])
else: else:
del self.saved_SEs[SEID] del self.saved_SEs[SEID]
@@ -151,7 +153,7 @@ class SAM(object):
:param cipher: Public/private key object from used for encryption :param cipher: Public/private key object from used for encryption
:param keytype: Type of the public key (e.g. RSA, DSA) :param keytype: Type of the public key (e.g. RSA, DSA)
""" """
if not keytype in range(0x07, 0x08): if keytype not in range(0x07, 0x08):
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
else: else:
self.cipher = type self.cipher = type
@@ -165,7 +167,7 @@ class SAM(object):
""" """
logging.debug("Received PIN: %s", PIN.strip()) logging.debug("Received PIN: %s", PIN.strip())
PIN = PIN.replace("\0","") #Strip NULL characters PIN = PIN.replace("\0", "") # Strip NULL characters
if p1 != 0x00: if p1 != 0x00:
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
@@ -185,7 +187,7 @@ class SAM(object):
Change the specified referenced data (e.g. CHV) of the card Change the specified referenced data (e.g. CHV) of the card
""" """
data = data.replace("\0","") #Strip NULL characters data = data.replace("\0", "") # Strip NULL characters
self.PIN = data self.PIN = data
return SW["NORMAL"], "" return SW["NORMAL"], ""
@@ -195,13 +197,13 @@ class SAM(object):
to prove key posession to prove key posession
""" """
if p1 == 0x00: #No information given if p1 == 0x00: # No information given
cipher = get_referenced_cipher(self.cipher) cipher = get_referenced_cipher(self.cipher)
else: else:
cipher = get_referenced_cipher(p1) cipher = get_referenced_cipher(p1)
if cipher == "RSA" or cipher == "DSA": if cipher == "RSA" or cipher == "DSA":
crypted_challenge = self.asym_key.sign(data,"") crypted_challenge = self.asym_key.sign(data, "")
crypted_challenge = crypted_challenge[0] crypted_challenge = crypted_challenge[0]
crypted_challenge = inttostring(crypted_challenge) crypted_challenge = inttostring(crypted_challenge)
else: else:
@@ -219,7 +221,7 @@ class SAM(object):
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
key = self._get_referenced_key(p1, p2) key = self._get_referenced_key(p1, p2)
if p1 == 0x00: #No information given if p1 == 0x00: # No information given
cipher = get_referenced_cipher(self.cipher) cipher = get_referenced_cipher(self.cipher)
else: else:
cipher = get_referenced_cipher(p1) cipher = get_referenced_cipher(p1)
@@ -228,12 +230,12 @@ class SAM(object):
reference = vsCrypto.append_padding(blocklen, self.last_challenge) reference = vsCrypto.append_padding(blocklen, self.last_challenge)
reference = vsCrypto.encrypt(cipher, key, reference) reference = vsCrypto.encrypt(cipher, key, reference)
if(reference == data): if(reference == data):
#Invalidate last challenge # Invalidate last challenge
self.last_challenge = None self.last_challenge is None
return SW["NORMAL"], "" return SW["NORMAL"], ""
else: else:
raise SwError(SW["WARN_NOINFO63"]) raise SwError(SW["WARN_NOINFO63"])
#TODO: Counter for external authenticate? # TODO: Counter for external authenticate?
def mutual_authenticate(self, p1, p2, mutual_challenge): def mutual_authenticate(self, p1, p2, mutual_challenge):
""" """
@@ -247,14 +249,14 @@ class SAM(object):
key = self._get_referenced_key(p1, p2) key = self._get_referenced_key(p1, p2)
card_number = self.get_card_number() card_number = self.get_card_number()
if (key == None): if (key is None):
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
if p1 == 0x00: #No information given if p1 == 0x00: # No information given
cipher = get_referenced_cipher(self.cipher) cipher = get_referenced_cipher(self.cipher)
else: else:
cipher = get_referenced_cipher(p1) cipher = get_referenced_cipher(p1)
if (cipher == None): if (cipher is None):
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
plain = vsCrypto.decrypt(cipher, key, mutual_challenge) plain = vsCrypto.decrypt(cipher, key, mutual_challenge)
@@ -275,10 +277,10 @@ class SAM(object):
""" """
Generate a random number of maximum 8 Byte and return it. Generate a random number of maximum 8 Byte and return it.
""" """
if (p1 != 0x00 or p2 != 0x00): #RFU if (p1 != 0x00 or p2 != 0x00): # RFU
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
length = 8 #Length of the challenge in Byte length = 8 # Length of the challenge in Byte
self.last_challenge = urandom(length) self.last_challenge = urandom(length)
logging.debug("Generated challenge: %s", self.last_challenge) logging.debug("Generated challenge: %s", self.last_challenge)
@@ -292,17 +294,18 @@ class SAM(object):
This method returns the key specified by the p2 parameter. The key may This method returns the key specified by the p2 parameter. The key may
be stored on the cards filesystem. be stored on the cards filesystem.
:param p1: Specifies the algorithm to use. Needed to know the keylength. :param p1: Specifies the algorithm to use.
:param p2: Specifies a reference to the key to be used for encryption :param p2: Specifies a reference to the key to be used for encryption.
== == == == == == == == ============================================= == == == == == == == == ===========================================
b8 b7 b6 b5 b4 b3 b2 b1 Meaning b8 b7 b6 b5 b4 b3 b2 b1 Meaning
== == == == == == == == ============================================= == == == == == == == == ===========================================
0 0 0 0 0 0 0 0 No information is given 0 0 0 0 0 0 0 0 No information is given
0 - - - - - - - Global reference data(e.g. MF specific key) 0 - - - - - - - Global reference data(e.g. MF specific key)
1 - - - - - - - Specific reference data(e.g. DF specific key) 1 - - - - - - - Specific reference data(e.g. DF specific
key)
- - - x x x x x Number of the secret - - - x x x x x Number of the secret
== == == == == == == == ============================================= == == == == == == == == ===========================================
Any other value RFU Any other value RFU
""" """
@@ -312,24 +315,23 @@ class SAM(object):
algo = get_referenced_cipher(p1) algo = get_referenced_cipher(p1)
keylength = vsCrypto.get_cipher_keylen(algo) keylength = vsCrypto.get_cipher_keylen(algo)
if (p2 == 0x00): #No information given, use the global card key if (p2 == 0x00): # No information given, use the global card key
key = self.cardSecret key = self.cardSecret
#We treat global and specific reference data alike # We treat global and specific reference data alike
#elif ((p2 >> 7) == 0x01 or (p2 >> 7) == 0x00):
else: else:
#Interpret qualifier as an short fid (try to read the key from FS) # Interpret qualifier as an short fid (try to read the key from FS)
if self.mf == None: if self.mf is None:
raise SwError(SW["ERR_REFNOTUSABLE"]) raise SwError(SW["ERR_REFNOTUSABLE"])
df = self.mf.currentDF() df = self.mf.currentDF()
fid = df.select("fid", stringtoint(qualifier)) fid = df.select("fid", stringtoint(qualifier))
key = fid.readbinary(keylength) key = fid.readbinary(keylength)
if key != None: if key is not None:
return key return key
else: else:
raise SwError(SW["ERR_REFNOTUSABLE"]) raise SwError(SW["ERR_REFNOTUSABLE"])
#The following commands define the Secure Messaging interface # The following commands define the Secure Messaging interface
def generate_public_key_pair(self, p1, p2, data): def generate_public_key_pair(self, p1, p2, data):
return self.current_SE.generate_public_key_pair(p1, p2, data) return self.current_SE.generate_public_key_pair(p1, p2, data)
@@ -347,7 +349,8 @@ class SAM(object):
""" """
Protect a plain response APDU by Secure Messaging Protect a plain response APDU by Secure Messaging
""" """
logging.info("Unprotected Response Data:\n"+hexdump(unprotected_result)) logging.info("Unprotected Response Data:\n" +
hexdump(unprotected_result))
return self.current_SE.protect_response(sw, unprotected_result) return self.current_SE.protect_response(sw, unprotected_result)
def perform_security_operation(self, p1, p2, data): def perform_security_operation(self, p1, p2, data):
@@ -355,4 +358,3 @@ class SAM(object):
def manage_security_environment(self, p1, p2, data): def manage_security_environment(self, p1, p2, data):
return self.current_SE.manage_security_environment(p1, p2, data) return self.current_SE.manage_security_environment(p1, p2, data)

View File

@@ -20,38 +20,40 @@
from virtualsmartcard.utils import stringtoint from virtualsmartcard.utils import stringtoint
TAG = {} TAG = {}
TAG["FILECONTROLPARAMETERS"] = 0x62 TAG["FILECONTROLPARAMETERS"] = 0x62
TAG["FILEMANAGEMENTDATA"] = 0x64 TAG["FILEMANAGEMENTDATA"] = 0x64
TAG["FILECONTROLINFORMATION"] = 0x6F TAG["FILECONTROLINFORMATION"] = 0x6F
TAG["BYTES_EXCLUDINGSTRUCTURE"] = 0x80 TAG["BYTES_EXCLUDINGSTRUCTURE"] = 0x80
TAG["BYTES_INCLUDINGSTRUCTURE"] = 0x81 TAG["BYTES_INCLUDINGSTRUCTURE"] = 0x81
TAG["FILEDISCRIPTORBYTE"] = 0x82 TAG["FILEDISCRIPTORBYTE"] = 0x82
TAG["FILEIDENTIFIER"] = 0x83 TAG["FILEIDENTIFIER"] = 0x83
TAG["DFNAME"] = 0x84 TAG["DFNAME"] = 0x84
TAG["PROPRIETARY_NOTBERTLV"] = 0x85 TAG["PROPRIETARY_NOTBERTLV"] = 0x85
TAG["PROPRIETARY_SECURITY"] = 0x86 TAG["PROPRIETARY_SECURITY"] = 0x86
TAG["FIDEF_CONTAININGFCI"] = 0x87 TAG["FIDEF_CONTAININGFCI"] = 0x87
TAG["SHORTFID"] = 0x88 TAG["SHORTFID"] = 0x88
TAG["LIFECYCLESTATUS"] = 0x8A TAG["LIFECYCLESTATUS"] = 0x8A
TAG["SA_EXPANDEDFORMAT"] = 0x8B TAG["SA_EXPANDEDFORMAT"] = 0x8B
TAG["SA_COMPACTFORMAT"] = 0x8C TAG["SA_COMPACTFORMAT"] = 0x8C
TAG["FIDEF_CONTAININGSET"] = 0x8D TAG["FIDEF_CONTAININGSET"] = 0x8D
TAG["CHANNELSECURITY"] = 0x8E TAG["CHANNELSECURITY"] = 0x8E
TAG["SA_DATAOBJECTS"] = 0xA0 TAG["SA_DATAOBJECTS"] = 0xA0
TAG["PROPRIETARY_SECURITYTEMP"] = 0xA1 TAG["PROPRIETARY_SECURITYTEMP"] = 0xA1
TAG["PROPRIETARY_BERTLV"] = 0xA5 TAG["PROPRIETARY_BERTLV"] = 0xA5
TAG["SA_EXPANDEDFORMAT_TEMP"] = 0xAB TAG["SA_EXPANDEDFORMAT_TEMP"] = 0xAB
TAG["CRYPTIDENTIFIER_TEMP"] = 0xAC TAG["CRYPTIDENTIFIER_TEMP"] = 0xAC
TAG["DISCRETIONARY_DATA"] = 0x53 TAG["DISCRETIONARY_DATA"] = 0x53
TAG["DISCRETIONARY_TEMPLATE"] = 0x73 TAG["DISCRETIONARY_TEMPLATE"] = 0x73
TAG["OFFSET_DATA"] = 0x54 TAG["OFFSET_DATA"] = 0x54
TAG["TAG_LIST"] = 0x5C TAG["TAG_LIST"] = 0x5C
TAG["HEADER_LIST"] = 0x5D TAG["HEADER_LIST"] = 0x5D
TAG["EXTENDED_HEADER_LIST"] = 0x4D TAG["EXTENDED_HEADER_LIST"] = 0x4D
def tlv_unpack(data): def tlv_unpack(data):
ber_class = (ord(data[0]) & 0xC0) >> 6 ber_class = (ord(data[0]) & 0xC0) >> 6
constructed = (ord(data[0]) & 0x20) != 0 ## 0 = primitive, 0x20 = constructed # 0 = primitive, 0x20 = constructed
constructed = (ord(data[0]) & 0x20) != 0
tag = ord(data[0]) tag = ord(data[0])
data = data[1:] data = data[1:]
if (tag & 0x1F) == 0x1F: if (tag & 0x1F) == 0x1F:
@@ -67,7 +69,7 @@ def tlv_unpack(data):
elif length & 0x80 == 0x80: elif length & 0x80 == 0x80:
length_ = 0 length_ = 0
data = data[1:] data = data[1:]
for i in range(0,length & 0x7F): for i in range(0, length & 0x7F):
length_ = length_ * 256 + ord(data[0]) length_ = length_ * 256 + ord(data[0])
data = data[1:] data = data[1:]
length = length_ length = length_
@@ -78,19 +80,20 @@ def tlv_unpack(data):
return ber_class, constructed, tag, length, value, rest return ber_class, constructed, tag, length, value, rest
def tlv_find_tags(tlv_data, tags, num_results = None): def tlv_find_tags(tlv_data, tags, num_results=None):
"""Find (and return) all instances of tags in the given tlv structure (as """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 returned by unpack). If num_results is specified then at most that many
results will be returned.""" results will be returned."""
results = [] results = []
def find_recursive(tlv_data): def find_recursive(tlv_data):
for d in tlv_data: for d in tlv_data:
t,l,v = d[:3] t, l, v = d[:3]
if t in tags: if t in tags:
results.append(d) results.append(d)
else: else:
if isinstance(v, list): # FIXME Refactor the whole TLV code into a class if isinstance(v, list):
find_recursive(v) find_recursive(v)
if num_results is not None and len(results) >= num_results: if num_results is not None and len(results) >= num_results:
@@ -101,20 +104,22 @@ def tlv_find_tags(tlv_data, tags, num_results = None):
return results return results
def tlv_find_tag(tlv_data, tag, num_results = None): 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). """Find (and return) all instances of tag in the given tlv structure (as
If num_results is specified then at most that many results will be returned.""" 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) return tlv_find_tags(tlv_data, [tag], num_results)
def pack(tlv_data, recalculate_length = False): def pack(tlv_data, recalculate_length=False):
result = [] result = []
for data in tlv_data: for data in tlv_data:
tag, length, value = data[:3] tag, length, value = data[:3]
if tag in (0xff, 0x00): if tag in (0xff, 0x00):
result.append( chr(tag) ) result.append(chr(tag))
continue continue
if not isinstance(value, str): if not isinstance(value, str):
@@ -125,7 +130,7 @@ def pack(tlv_data, recalculate_length = False):
t = "" t = ""
while tag > 0: while tag > 0:
t = chr( tag & 0xff ) + t t = chr(tag & 0xff) + t
tag = tag >> 8 tag = tag >> 8
if length < 0x7F: if length < 0x7F:
@@ -133,10 +138,10 @@ def pack(tlv_data, recalculate_length = False):
else: else:
l = "" l = ""
while length > 0: while length > 0:
l = chr( length & 0xff ) + l l = chr(length & 0xff) + l
length = length >> 8 length = length >> 8
assert len(l) < 0x7f assert len(l) < 0x7f
l = chr( 0x80 | len(l) ) + l l = chr(0x80 | len(l)) + l
result.append(t) result.append(t)
result.append(l) result.append(l)
@@ -150,15 +155,15 @@ def bertlv_pack(data):
return pack(data) return pack(data)
def unpack(data, with_marks = None, offset = 0, include_filler=False): def unpack(data, with_marks=None, offset=0, include_filler=False):
result = [] result = []
while len(data) > 0: while len(data) > 0:
if ord(data[0]) in (0x00, 0xFF): if ord(data[0]) in (0x00, 0xFF):
if include_filler: if include_filler:
if with_marks is None: if with_marks is None:
result.append( (ord(data[0]), None, None) ) result.append((ord(data[0]), None, None))
else: else:
result.append( (ord(data[0]), None, None, () ) ) result.append((ord(data[0]), None, None, ()))
data = data[1:] data = data[1:]
offset = offset + 1 offset = offset + 1
continue continue
@@ -178,9 +183,10 @@ def unpack(data, with_marks = None, offset = 0, include_filler=False):
marks = () marks = ()
if not constructed: if not constructed:
result.append( (tag, length, value) + marks ) result.append((tag, length, value) + marks)
else: else:
result.append( (tag, length, unpack(value, with_marks, offset = start)) + marks ) result.append((tag, length,
unpack(value, with_marks, offset=start)) + marks)
offset = stop offset = stop
@@ -193,7 +199,7 @@ def bertlv_unpack(data):
return unpack(data) return unpack(data)
def simpletlv_pack(tlv_data, recalculate_length = False): def simpletlv_pack(tlv_data, recalculate_length=False):
result = "" result = ""
for tag, length, value in tlv_data: for tag, length, value in tlv_data:
@@ -210,7 +216,8 @@ def simpletlv_pack(tlv_data, recalculate_length = False):
if length < 0xff: if length < 0xff:
result += chr(tag) + chr(length) + value result += chr(tag) + chr(length) + value
else: else:
result += chr(tag) + chr(0xff)+chr(length>>8)+chr(length&0xff) + value result += chr(tag) + chr(0xff) + chr(length >> 8) + \
chr(length & 0xff) + value
return result return result
@@ -227,29 +234,30 @@ def simpletlv_unpack(data):
length = ord(rest[1]) length = ord(rest[1])
if length == 0xff: if length == 0xff:
length = (ord(rest[2])<<8) + ord(rest[3]) length = (ord(rest[2]) << 8) + ord(rest[3])
newvalue = rest[4:4+length] newvalue = rest[4:4+length]
rest = rest[4+length:] rest = rest[4+length:]
else: else:
newvalue = rest[2:2+length] newvalue = rest[2:2+length]
rest = rest[2+length:] rest = rest[2+length:]
result.append((tag, length, newvalue)) result.append((tag, length, newvalue))
return result return result
def decodeDiscretionaryDataObjects(tlv_data): def decodeDiscretionaryDataObjects(tlv_data):
datalist = [] datalist = []
for (tag, length, newvalue) in (tlv_find_tags(tlv_data, tlv_tags = (tlv_find_tags(tlv_data, [TAG["DISCRETIONARY_DATA"],
[TAG["DISCRETIONARY_DATA"], TAG["DISCRETIONARY_TEMPLATE"]])): TAG["DISCRETIONARY_TEMPLATE"]]))
for (tag, length, newvalue) in tlv_tags:
datalist.append(newvalue) datalist.append(newvalue)
return datalist return datalist
def decodeOffsetDataObjects(tlv_data): def decodeOffsetDataObjects(tlv_data):
offsets = [] offsets = []
for (tag, length, newvalue) in tlv_find_tag(tlv_data, for (tag, length, newvalue) in tlv_find_tag(tlv_data,
TAG["OFFSET_DATA"]): TAG["OFFSET_DATA"]):
offsets.append(stringtoint(newvalue)) offsets.append(stringtoint(newvalue))
return offsets return offsets
@@ -289,7 +297,7 @@ def decodeHeaderList(tlv_data):
elif length & 0x80 == 0x80: elif length & 0x80 == 0x80:
length_ = 0 length_ = 0
data = data[1:] data = data[1:]
for i in range(0,length & 0x7F): for i in range(0, length & 0x7F):
length_ = length_ * 256 + ord(data[0]) length_ = length_ * 256 + ord(data[0])
data = data[1:] data = data[1:]
length = length_ length = length_
@@ -309,9 +317,10 @@ def encodebertlvDatalist(tag, datalist):
tlvlist.append((tag, len(data), data)) tlvlist.append((tag, len(data), data))
return bertlv_pack(tlvlist) return bertlv_pack(tlvlist)
def encodeDiscretionaryDataObjects(datalist): def encodeDiscretionaryDataObjects(datalist):
return encodebertlvDatalist(TAG["DISCRETIONARY_DATA"], datalist) return encodebertlvDatalist(TAG["DISCRETIONARY_DATA"], datalist)
def encodeDataOffsetObjects(datalist): def encodeDataOffsetObjects(datalist):
return encodebertlvDatalist(TAG["OFFSET_DATA"], datalist) return encodebertlvDatalist(TAG["OFFSET_DATA"], datalist)

View File

@@ -17,14 +17,18 @@
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>. # virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
# #
import atexit
import logging
import signal
import socket
import struct
import sys
from virtualsmartcard.ConstantDefinitions import MAX_EXTENDED_LE, MAX_SHORT_LE from virtualsmartcard.ConstantDefinitions import MAX_EXTENDED_LE, MAX_SHORT_LE
from virtualsmartcard.SWutils import SwError, SW from virtualsmartcard.SWutils import SwError, SW
from virtualsmartcard.SmartcardFilesystem import make_property from virtualsmartcard.SmartcardFilesystem import make_property
from virtualsmartcard.utils import C_APDU, R_APDU, hexdump, inttostring from virtualsmartcard.utils import C_APDU, R_APDU, hexdump, inttostring
from virtualsmartcard.CardGenerator import CardGenerator from virtualsmartcard.CardGenerator import CardGenerator
import socket, struct, sys, signal, atexit, logging
class SmartcardOS(object): class SmartcardOS(object):
"""Base class for a smart card OS""" """Base class for a smart card OS"""
@@ -55,16 +59,13 @@ class SmartcardOS(object):
class Iso7816OS(SmartcardOS): class Iso7816OS(SmartcardOS):
mf = make_property("mf", "master file") mf = make_property("mf", "master file")
SAM = make_property("SAM", "secure access module") SAM = make_property("SAM", "secure access module")
def __init__(self, mf, sam, ins2handler=None, extended_length=False): def __init__(self, mf, sam, ins2handler=None, extended_length=False):
self.mf = mf self.mf = mf
self.SAM = sam self.SAM = sam
#if self.mf == None and self.SAM == None:
# self.mf, self.SAM = generate_iso_card()
if not ins2handler: if not ins2handler:
self.ins2handler = { self.ins2handler = {
0x0c: self.mf.eraseRecord, 0x0c: self.mf.eraseRecord,
@@ -111,11 +112,13 @@ class Iso7816OS(SmartcardOS):
self.lastCommandOffcut = "" self.lastCommandOffcut = ""
self.lastCommandSW = SW["NORMAL"] self.lastCommandSW = SW["NORMAL"]
card_capabilities = self.mf.firstSFT + self.mf.secondSFT + \ el = extended_length # only needed to keep following line short
Iso7816OS.makeThirdSoftwareFunctionTable(extendedLe = extended_length) tsft = Iso7816OS.makeThirdSoftwareFunctionTable(extendedLe=el)
self.atr = Iso7816OS.makeATR(T=1, directConvention = True, TA1=0x13, card_capabilities = self.mf.firstSFT + self.mf.secondSFT + tsft
histChars = chr(0x80) + chr(0x70 + len(card_capabilities)) + self.atr = Iso7816OS.makeATR(T=1, directConvention=True, TA1=0x13,
card_capabilities) histChars=chr(0x80) +
chr(0x70 + len(card_capabilities)) +
card_capabilities)
def getATR(self): def getATR(self):
return self.atr return self.atr
@@ -135,8 +138,8 @@ class Iso7816OS(SmartcardOS):
Note that if T is set, TAi/TBi/TCi for i>T are Note that if T is set, TAi/TBi/TCi for i>T are
omitted. omitted.
- histChars (optional): Bitstring with 0 <= len(histChars) <= 15. - histChars (optional): Bitstring with 0 <= len(histChars) <= 15.
Historical Characters T1 to T15 (for meaning Historical Characters T1 to T15 (for
see ISO 7816-4). meaning see ISO 7816-4).
T0, TDi and TCK are automatically calculated. T0, TDi and TCK are automatically calculated.
""" """
@@ -146,7 +149,7 @@ class Iso7816OS(SmartcardOS):
else: else:
atr = "\x3f" atr = "\x3f"
if args.has_key("T"): if "T" in args:
T = args["T"] T = args["T"]
else: else:
T = 0 T = 0
@@ -155,7 +158,8 @@ class Iso7816OS(SmartcardOS):
maxTD = 0 maxTD = 0
i = 15 i = 15
while i > 0: while i > 0:
if args.has_key("TA" + str(i)) or args.has_key("TB" + str(i)) or args.has_key("TC" + str(i)): if ("TA" + str(i) in args or "TB" + str(i) in args or
"TC" + str(i) in args):
maxTD = i-1 maxTD = i-1
break break
i -= 1 i -= 1
@@ -165,20 +169,20 @@ class Iso7816OS(SmartcardOS):
# insert TDi into args (TD0 is actually T0) # insert TDi into args (TD0 is actually T0)
for i in range(0, maxTD+1): for i in range(0, maxTD+1):
if i == 0 and args.has_key("histChars"): if i == 0 and "histChars" in args:
args["TD0"] = len(args["histChars"]) args["TD0"] = len(args["histChars"])
else: else:
args["TD"+str(i)] = T args["TD"+str(i)] = T
if i < maxTD: if i < maxTD:
args["TD"+str(i)] |= 1<<7 args["TD"+str(i)] |= 1 << 7
if args.has_key("TA" + str(i+1)): if "TA" + str(i+1) in args:
args["TD"+str(i)] |= 1<<4 args["TD"+str(i)] |= 1 << 4
if args.has_key("TB" + str(i+1)): if "TB" + str(i+1) in args:
args["TD"+str(i)] |= 1<<5 args["TD"+str(i)] |= 1 << 5
if args.has_key("TC" + str(i+1)): if "TC" + str(i+1) in args:
args["TD"+str(i)] |= 1<<6 args["TD"+str(i)] |= 1 << 6
# initialize checksum # initialize checksum
TCK = 0 TCK = 0
@@ -188,16 +192,16 @@ class Iso7816OS(SmartcardOS):
atr = atr + "%c" % args["TD" + str(i)] atr = atr + "%c" % args["TD" + str(i)]
TCK ^= args["TD" + str(i)] TCK ^= args["TD" + str(i)]
for j in ["A", "B", "C"]: for j in ["A", "B", "C"]:
if args.has_key("T" + j + str(i+1)): if "T" + j + str(i+1) in args:
atr += "%c" % args["T" + j + str(i+1)] atr += "%c" % args["T" + j + str(i+1)]
# calculate checksum for all bytes from T0 to the end # calculate checksum for all bytes from T0 to the end
TCK ^= args["T" + j + str(i+1)] TCK ^= args["T" + j + str(i+1)]
# add historical characters # add historical characters
if args.has_key("histChars"): if "histChars" in args:
atr += args["histChars"] atr += args["histChars"]
for i in range(0, len(args["histChars"])): for i in range(0, len(args["histChars"])):
TCK ^= ord( args["histChars"][i] ) TCK ^= ord(args["histChars"][i])
# checksum is omitted for T=0 # checksum is omitted for T=0
if T > 0: if T > 0:
@@ -207,7 +211,9 @@ class Iso7816OS(SmartcardOS):
@staticmethod @staticmethod
def makeThirdSoftwareFunctionTable(commandChainging=False, def makeThirdSoftwareFunctionTable(commandChainging=False,
extendedLe=False, assignLogicalChannel=0, maximumChannels=0): extendedLe=False,
assignLogicalChannel=0,
maximumChannels=0):
""" """
Returns a byte according to the third software function table from the Returns a byte according to the third software function table from the
historical bytes of the card capabilities. historical bytes of the card capabilities.
@@ -218,16 +224,15 @@ class Iso7816OS(SmartcardOS):
if extendedLe: if extendedLe:
tsft |= 1 << 6 tsft |= 1 << 6
if assignLogicalChannel: if assignLogicalChannel:
if not (0<=assignLogicalChannel and assignLogicalChannel<=3): if not (0 <= assignLogicalChannel and assignLogicalChannel <= 3):
raise ValueError raise ValueError
tsft |= assignLogicalChannel << 3 tsft |= assignLogicalChannel << 3
if maximumChannels: if maximumChannels:
if not (0<=maximumChannels and maximumChannels<=7): if not (0 <= maximumChannels and maximumChannels <= 7):
raise ValueError raise ValueError
tsft |= maximumChannels tsft |= maximumChannels
return inttostring(tsft) return inttostring(tsft)
def formatResult(self, seekable, le, data, sw, sm): def formatResult(self, seekable, le, data, sw, sm):
if not seekable: if not seekable:
self.lastCommandOffcut = data[le:] self.lastCommandOffcut = data[le:]
@@ -241,7 +246,7 @@ class Iso7816OS(SmartcardOS):
if le > len(data): if le > len(data):
sw = SW["WARN_EOFBEFORENEREAD"] sw = SW["WARN_EOFBEFORENEREAD"]
if le != None: if le is not None:
result = data[:le] result = data[:le]
else: else:
result = data[:0] result = data[:0]
@@ -252,7 +257,8 @@ class Iso7816OS(SmartcardOS):
@staticmethod @staticmethod
def seekable(ins): def seekable(ins):
if ins in [0xb0, 0xb1, 0xd0, 0xd1, 0xd6, 0xd7, 0xa0, 0xa1, 0xb2, 0xb3, 0xdc, 0xdd ]: if ins in [0xb0, 0xb1, 0xd0, 0xd1, 0xd6, 0xd7, 0xa0, 0xa1, 0xb2, 0xb3,
0xdc, 0xdd]:
return True return True
else: else:
return False return False
@@ -277,46 +283,47 @@ class Iso7816OS(SmartcardOS):
c = C_APDU(msg) c = C_APDU(msg)
except ValueError as e: except ValueError as e:
logging.warning(str(e)) logging.warning(str(e))
return self.formatResult(False, 0, "", SW["ERR_INCORRECTPARAMETERS"], False) return self.formatResult(False, 0, "",
SW["ERR_INCORRECTPARAMETERS"], False)
logging.info("Parsed APDU:\n%s", str(c)) logging.info("Parsed APDU:\n%s", str(c))
#Handle Class Byte # Handle Class Byte
#{{{ # {{{
class_byte = c.cla class_byte = c.cla
SM_STATUS = None SM_STATUS = None
logical_channel = 0 logical_channel = 0
command_chaining = 0 command_chaining = 0
header_authentication = 0 header_authentication = 0
#Ugly Hack for OpenSC-explorer # Ugly Hack for OpenSC-explorer
if(class_byte == 0xb0): if(class_byte == 0xb0):
logging.debug("Open SC APDU") logging.debug("Open SC APDU")
SM_STATUS = "No SM" SM_STATUS = "No SM"
#If Bit 8,7,6 == 0 then first industry values are used # If Bit 8,7,6 == 0 then first industry values are used
if (class_byte & 0xE0 == 0x00): if (class_byte & 0xE0 == 0x00):
#Bit 1 and 2 specify the logical channel # Bit 1 and 2 specify the logical channel
logical_channel = class_byte & 0x03 logical_channel = class_byte & 0x03
#Bit 3 and 4 specify secure messaging # Bit 3 and 4 specify secure messaging
secure_messaging = class_byte >> 2 secure_messaging = class_byte >> 2
secure_messaging &= 0x03 secure_messaging &= 0x03
if (secure_messaging == 0x00): if (secure_messaging == 0x00):
SM_STATUS = "No SM" SM_STATUS = "No SM"
elif (secure_messaging == 0x01): elif (secure_messaging == 0x01):
SM_STATUS = "Proprietary SM" # Not supported ? SM_STATUS = "Proprietary SM" # Not supported ?
elif (secure_messaging == 0x02): elif (secure_messaging == 0x02):
SM_STATUS = "Standard SM" SM_STATUS = "Standard SM"
elif (secure_messaging == 0x03): elif (secure_messaging == 0x03):
SM_STATUS = "Standard SM" SM_STATUS = "Standard SM"
header_authentication = 1 header_authentication = 1
#If Bit 8,7 == 01 then further industry values are used # If Bit 8,7 == 01 then further industry values are used
elif (class_byte & 0x0C == 0x0C): elif (class_byte & 0x0C == 0x0C):
#Bit 1 to 4 specify logical channel. 4 is added, value range is from # Bit 1 to 4 specify logical channel. 4 is added, value range is
#four to nineteen # from four to nineteen
logical_channel = class_byte & 0x0f logical_channel = class_byte & 0x0f
logical_channel += 4 logical_channel += 4
#Bit 6 indicates secure messaging # Bit 6 indicates secure messaging
secure_messaging = class_byte >> 6 secure_messaging = class_byte >> 6
if (secure_messaging == 0x00): if (secure_messaging == 0x00):
SM_STATUS = "No SM" SM_STATUS = "No SM"
@@ -325,10 +332,10 @@ class Iso7816OS(SmartcardOS):
else: else:
# Bit 8 is set to 1, which is not specified by ISO 7816-4 # Bit 8 is set to 1, which is not specified by ISO 7816-4
SM_STATUS = "Proprietary SM" SM_STATUS = "Proprietary SM"
#In both cases Bit 5 specifies command chaining # In both cases Bit 5 specifies command chaining
command_chaining = class_byte >> 5 command_chaining = class_byte >> 5
command_chaining &= 0x01 command_chaining &= 0x01
#}}} # }}}
sm = False sm = False
try: try:
@@ -336,8 +343,11 @@ class Iso7816OS(SmartcardOS):
c = self.SAM.parse_SM_CAPDU(c, header_authentication) c = self.SAM.parse_SM_CAPDU(c, header_authentication)
logging.info("Decrypted APDU:\n%s", str(c)) logging.info("Decrypted APDU:\n%s", str(c))
sm = True sm = True
sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, c.p2, c.data) sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1,
answer = self.formatResult(Iso7816OS.seekable(c.ins), c.effective_Le, result, sw, sm) c.p2,
c.data)
answer = self.formatResult(Iso7816OS.seekable(c.ins),
c.effective_Le, result, sw, sm)
except SwError as e: except SwError as e:
logging.info(e.message) logging.info(e.message)
import traceback import traceback
@@ -355,18 +365,17 @@ class Iso7816OS(SmartcardOS):
self.mf.current = self.mf self.mf.current = self.mf
# sizeof(int) taken from asizof-package {{{ # sizeof(int) taken from asizof-package {{{
_Csizeof_short = len(struct.pack('h', 0)) _Csizeof_short = len(struct.pack('h', 0))
# }}} # }}}
VPCD_CTRL_LEN = 1 VPCD_CTRL_LEN = 1
VPCD_CTRL_OFF = 0
VPCD_CTRL_OFF = 0 VPCD_CTRL_ON = 1
VPCD_CTRL_ON = 1
VPCD_CTRL_RESET = 2 VPCD_CTRL_RESET = 2
VPCD_CTRL_ATR = 4 VPCD_CTRL_ATR = 4
class VirtualICC(object): class VirtualICC(object):
""" """
@@ -378,40 +387,48 @@ class VirtualICC(object):
the vpcd, which forwards it to the application. the vpcd, which forwards it to the application.
""" """
def __init__(self, filename, datasetfile, card_type, host, port, readernum=None, ef_cardsecurity=None, ef_cardaccess=None, ca_key=None, cvca=None, disable_checks=False, esign_key=None, esign_ca_cert=None, esign_cert=None, logginglevel=logging.INFO): def __init__(self, filename, datasetfile, card_type, host, port,
readernum=None, ef_cardsecurity=None, ef_cardaccess=None,
ca_key=None, cvca=None, disable_checks=False, esign_key=None,
esign_ca_cert=None, esign_cert=None,
logginglevel=logging.INFO):
from os.path import exists from os.path import exists
logging.basicConfig(level = logginglevel, logging.basicConfig(level=logginglevel,
format = "%(asctime)s [%(levelname)s] %(message)s", format="%(asctime)s [%(levelname)s] %(message)s",
datefmt = "%d.%m.%Y %H:%M:%S") datefmt="%d.%m.%Y %H:%M:%S")
self.filename = None self.filename = None
self.cardGenerator = CardGenerator(card_type) self.cardGenerator = CardGenerator(card_type)
#If a filename is specified, try to load the card from disk # If a filename is specified, try to load the card from disk
if filename != None: if filename is not None:
self.filename = filename self.filename = filename
if exists(filename): if exists(filename):
self.cardGenerator.loadCard(self.filename) self.cardGenerator.loadCard(self.filename)
else: else:
logging.info("Creating new card which will be saved in %s.", logging.info("Creating new card which will be saved in %s.",
self.filename) self.filename)
#If a dataset file is specified, read the card's data groups from disk # If a dataset file is specified, read the card's data groups from disk
if datasetfile != None: if datasetfile is not None:
if exists(datasetfile): if exists(datasetfile):
logging.info("Reading Data Groups from file %s.", logging.info("Reading Data Groups from file %s.",
datasetfile) datasetfile)
self.cardGenerator.readDatagroups(datasetfile) self.cardGenerator.readDatagroups(datasetfile)
MF, SAM = self.cardGenerator.getCard() MF, SAM = self.cardGenerator.getCard()
#Generate an OS object of the correct card_type # Generate an OS object of the correct card_type
if card_type == "iso7816" or card_type == "ePass": if card_type == "iso7816" or card_type == "ePass":
self.os = Iso7816OS(MF, SAM) self.os = Iso7816OS(MF, SAM)
elif card_type == "nPA": elif card_type == "nPA":
from virtualsmartcard.cards.nPA import NPAOS from virtualsmartcard.cards.nPA import NPAOS
self.os = NPAOS(MF, SAM, ef_cardsecurity=ef_cardsecurity, ef_cardaccess=ef_cardaccess, ca_key=ca_key, cvca=cvca, disable_checks=disable_checks, esign_key=esign_key, esign_ca_cert=esign_ca_cert, esign_cert=esign_cert) self.os = NPAOS(MF, SAM, ef_cardsecurity=ef_cardsecurity,
ef_cardaccess=ef_cardaccess, ca_key=ca_key,
cvca=cvca, disable_checks=disable_checks,
esign_key=esign_key, esign_ca_cert=esign_ca_cert,
esign_cert=esign_cert)
elif card_type == "cryptoflex": elif card_type == "cryptoflex":
from virtualsmartcard.cards.cryptoflex import CryptoflexOS from virtualsmartcard.cards.cryptoflex import CryptoflexOS
self.os = CryptoflexOS(MF, SAM) self.os = CryptoflexOS(MF, SAM)
@@ -422,13 +439,13 @@ class VirtualICC(object):
from virtualsmartcard.cards.HandlerTest import HandlerTestOS from virtualsmartcard.cards.HandlerTest import HandlerTestOS
self.os = HandlerTestOS() self.os = HandlerTestOS()
else: else:
logging.warning("Unknown cardtype %s. Will use standard card_type (ISO 7816)", logging.warning("Unknown cardtype %s. Will use standard card_type \
card_type) (ISO 7816)", card_type)
card_type = "iso7816" card_type = "iso7816"
self.os = Iso7816OS(MF, SAM) self.os = Iso7816OS(MF, SAM)
self.type = card_type self.type = card_type
#Connect to the VPCD # Connect to the VPCD
self.host = host self.host = host
self.port = port self.port = port
if host: if host:
@@ -439,8 +456,8 @@ class VirtualICC(object):
self.server_sock = None self.server_sock = None
except socket.error as e: except socket.error as e:
logging.error("Failed to open socket: %s", str(e)) logging.error("Failed to open socket: %s", str(e))
logging.error("Is pcscd running at %s? Is vpcd loaded? Is a firewall blocking port %u?", logging.error("Is pcscd running at %s? Is vpcd loaded? Is a \
host, port) firewall blocking port %u?", host, port)
sys.exit() sys.exit()
else: else:
# use reversed connection mode # use reversed connection mode
@@ -449,8 +466,9 @@ class VirtualICC(object):
self.sock.settimeout(None) self.sock.settimeout(None)
except socket.error as e: except socket.error as e:
logging.error("Failed to open socket: %s", str(e)) logging.error("Failed to open socket: %s", str(e))
logging.error("Is pcscd running? Is vpcd loaded and in reversed connection mode? Is a firewall blocking port %u?", logging.error("Is pcscd running? Is vpcd loaded and in \
port) reversed connection mode? Is a firewall \
blocking port %u?", port)
sys.exit() sys.exit()
logging.info("Connected to virtual PCD at %s:%u", host, port) logging.info("Connected to virtual PCD at %s:%u", host, port)
@@ -512,7 +530,7 @@ class VirtualICC(object):
vpcd, dispatches them to the emulated smartcard and sends the resulting vpcd, dispatches them to the emulated smartcard and sends the resulting
respsonse APDU back to the vpcd. respsonse APDU back to the vpcd.
""" """
while True : while True:
try: try:
(size, msg) = self.__recvFromVPICC() (size, msg) = self.__recvFromVPICC()
except socket.error as e: except socket.error as e:
@@ -524,7 +542,8 @@ class VirtualICC(object):
sys.exit() sys.exit()
if not size: if not size:
logging.warning("Error in communication protocol (missing size parameter)") logging.warning("Error in communication protocol (missing \
size parameter)")
elif size == VPCD_CTRL_LEN: elif size == VPCD_CTRL_LEN:
if msg == chr(VPCD_CTRL_OFF): if msg == chr(VPCD_CTRL_OFF):
logging.info("Power Down") logging.info("Power Down")
@@ -553,7 +572,6 @@ class VirtualICC(object):
self.sock.close() self.sock.close()
if self.server_sock: if self.server_sock:
self.server_sock.close() self.server_sock.close()
if self.filename != None: if self.filename is not None:
self.cardGenerator.setCard(self.os.mf, self.os.SAM) self.cardGenerator.setCard(self.os.mf, self.os.SAM)
self.cardGenerator.saveCard(self.filename) self.cardGenerator.saveCard(self.filename)

View File

@@ -24,6 +24,7 @@ import unittest
from virtualsmartcard.CardGenerator import CardGenerator from virtualsmartcard.CardGenerator import CardGenerator
class ISO7816GeneratorTest(unittest.TestCase): class ISO7816GeneratorTest(unittest.TestCase):
card_type = 'iso7816' card_type = 'iso7816'
@@ -44,7 +45,7 @@ class ISO7816GeneratorTest(unittest.TestCase):
def test_load_card_from_file(self): def test_load_card_from_file(self):
self.card_generator.generateCard() self.card_generator.generateCard()
self.card_generator.saveCard(self.filename) self.card_generator.saveCard(self.filename)
local_generator= CardGenerator(self.card_type) local_generator = CardGenerator(self.card_type)
local_generator.password = self.card_generator.password local_generator.password = self.card_generator.password
local_generator.loadCard(self.filename) local_generator.loadCard(self.filename)
mf, sam = local_generator.getCard() mf, sam = local_generator.getCard()
@@ -59,21 +60,30 @@ class ISO7816GeneratorTest(unittest.TestCase):
def test_get_and_set_card(self): def test_get_and_set_card(self):
self.card_generator.generateCard() self.card_generator.generateCard()
mf, sam = self.card_generator.getCard() mf, sam = self.card_generator.getCard()
local_generator= CardGenerator(self.card_type) local_generator = CardGenerator(self.card_type)
local_generator.setCard(mf, sam) local_generator.setCard(mf, sam)
class TestNPACardGenerator(ISO7816GeneratorTest): class TestNPACardGenerator(ISO7816GeneratorTest):
card_type = 'nPA' card_type = 'nPA'
def setUp(self):
self.filename = tempfile.mktemp()
self.card_generator = CardGenerator(self.card_type)
self.card_generator.password = "TestPassword"
self.test_readDatagroups_file = "/../../../../npa-example-data/"\
"Example_Dataset_Mueller_Gertrud.txt"
def test_readDatagroups(self): def test_readDatagroups(self):
path = os.path.dirname(__file__) path = os.path.dirname(__file__)
datagroupsFile = path + "/../../../../npa-example-data/Example_Dataset_Mueller_Gertrud.txt" datagroupsFile = path + self.test_readDatagroups_file
self.card_generator.readDatagroups(datagroupsFile) self.card_generator.readDatagroups(datagroupsFile)
mf, sam = self.card_generator.getCard() mf, sam = self.card_generator.getCard()
self.assertIsNotNone(mf) self.assertIsNotNone(mf)
self.assertIsNotNone(sam) self.assertIsNotNone(sam)
class CryptoflexGeneratorTest(ISO7816GeneratorTest): class CryptoflexGeneratorTest(ISO7816GeneratorTest):
card_type = 'cryptoflex' card_type = 'cryptoflex'
@@ -81,7 +91,7 @@ class CryptoflexGeneratorTest(ISO7816GeneratorTest):
# Not tested because an ePass card currently cannot be generated without user # Not tested because an ePass card currently cannot be generated without user
# interaction. # interaction.
# #
#class ePassGeneratorTest(ISO7816GeneratorTest): # class ePassGeneratorTest(ISO7816GeneratorTest):
# #
# card_type = 'ePass' # card_type = 'ePass'

View File

@@ -20,20 +20,28 @@
import unittest import unittest
from virtualsmartcard.CryptoUtils import * from virtualsmartcard.CryptoUtils import *
class TestCryptoUtils(unittest.TestCase): class TestCryptoUtils(unittest.TestCase):
def setUp(self): def setUp(self):
self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsgohsav8wress" self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsg"\
"ohsav8wress"
self.testpass = "SomeRandomPassphrase" self.testpass = "SomeRandomPassphrase"
# The following string was generated using the proteced string method and # The following string was generated using the proteced string method
# is used as regression test. # and is used as a regression test.
# The data generated by protect_string should actually consist of # The data generated by protect_string should actually consist of
# printable characters only but that would break backwards # printable characters only but that would break backwards
# compatibility with the (buggy) legacy implementation # compatibility with the (buggy) legacy implementation
self.protectedTestString = "2470356b32242478424f50746f6d712448b8f6285ac8462fffc6aef921f2ad84855219c5aaafb39c4cc9e54d1634c60cfc9347c67fa55967c5b0130469c96a44f0b73c53f5ddfc43cd8c1ef68965ebb23330393265383732333937353465653838643135363830666637336134316532".decode('hex') self.protectedTestString = "2470356b32242478424f50746f6d712448b8f6285"\
"ac8462fffc6aef921f2ad84855219c5aaafb39c4c"\
"c9e54d1634c60cfc9347c67fa55967c5b0130469c"\
"96a44f0b73c53f5ddfc43cd8c1ef68965ebb23330"\
"39326538373233393735346565383864313536383"\
"0666637336134316532".decode('hex')
self.salt = "POcwYIHr" self.salt = "POcwYIHr"
self.cryptedWord = "$p5k2$$POcwYIHr$SPaWqD3NpmLZc6gXbeybnAoCxo7Oc//K" self.cryptedWord = "$p5k2$$POcwYIHr$SPaWqD3NpmLZc6gXbeybnAoCxo7Oc//K"
self.cryptedWordThousandIterations = "$p5k2$3e8$POcwYIHr$f/mEOCulo6v7Nq2ooS3480xTet6zdGbI" self.cryptedWordThousandIterations = "$p5k2$3e8$POcwYIHr$f/mEOCulo6v7"\
"Nq2ooS3480xTet6zdGbI"
def test_padding(self): def test_padding(self):
padded = append_padding(16, self.teststring) padded = append_padding(16, self.teststring)
@@ -42,11 +50,13 @@ class TestCryptoUtils(unittest.TestCase):
def test_protect_string(self): def test_protect_string(self):
protectedString = protect_string(self.teststring, self.testpass) protectedString = protect_string(self.teststring, self.testpass)
unprotectedString = read_protected_string(protectedString, self.testpass) unprotectedString = read_protected_string(protectedString,
self.testpass)
self.assertEqual(self.teststring, unprotectedString) self.assertEqual(self.teststring, unprotectedString)
def test_unprotect_string(self): def test_unprotect_string(self):
unprotectedString = read_protected_string(self.protectedTestString, self.testpass) unprotectedString = read_protected_string(self.protectedTestString,
self.testpass)
self.assertEqual(unprotectedString, self.teststring) self.assertEqual(unprotectedString, self.teststring)
def test_crypt(self): def test_crypt(self):

View File

@@ -20,13 +20,13 @@
import unittest import unittest
from virtualsmartcard.SmartcardSAM import * from virtualsmartcard.SmartcardSAM import *
#Unit Tests
class TestSmartcardSAM(unittest.TestCase): class TestSmartcardSAM(unittest.TestCase):
def setUp(self): def setUp(self):
self.password = "DUMMYKEYDUMMYKEY" self.password = "DUMMYKEYDUMMYKEY"
self.myCard = SAM("1234", "1234567890") self.myCard = SAM("1234", "1234567890")
self.secEnv = Security_Environment(None, self.myCard) #TODO: Set CRTs self.secEnv = Security_Environment(None, self.myCard) # TODO: Set CRTs
self.secEnv.ht.algorithm = "SHA" self.secEnv.ht.algorithm = "SHA"
self.secEnv.ct.algorithm = "AES-CBC" self.secEnv.ct.algorithm = "AES-CBC"
@@ -54,20 +54,24 @@ class TestSmartcardSAM(unittest.TestCase):
blocklen = vsCrypto.get_cipher_blocklen("DES3-ECB") blocklen = vsCrypto.get_cipher_blocklen("DES3-ECB")
padded = vsCrypto.append_padding(blocklen, challenge) padded = vsCrypto.append_padding(blocklen, challenge)
sw, result_data = self.myCard.internal_authenticate(0x00, 0x00, padded) sw, result_data = self.myCard.internal_authenticate(0x00, 0x00, padded)
sw, result_data = self.myCard.external_authenticate(0x00, 0x00, result_data) sw, result_data = self.myCard.external_authenticate(0x00, 0x00,
result_data)
self.assertEquals(sw, SW["NORMAL"]) self.assertEquals(sw, SW["NORMAL"])
def test_security_environment(self): def test_security_environment(self):
hash = self.secEnv.hash(0x90, 0x80, self.password) hash = self.secEnv.hash(0x90, 0x80, self.password)
#The API should be changed so that the hash function returns SW_NORMAL # The API should be changed so that the hash function returns SW_NORMAL
self.secEnv.ct.key = hash[:16] self.secEnv.ct.key = hash[:16]
crypted = self.secEnv.encipher(0x00, 0x00, self.password) crypted = self.secEnv.encipher(0x00, 0x00,
#The API should be changed so that the encipher function returns SW_NORMAL self.password)
# The API should be changed so that encipher() returns SW_NORMAL
plain = self.secEnv.decipher(0x00, 0x00, crypted) plain = self.secEnv.decipher(0x00, 0x00, crypted)
#The API should be changed so that the decipher function returns SW_NORMAL # The API should be changed so that decipher() returns SW_NORMAL
#self.assertEqual(plain, self.password) # self.assertEqual(plain, self.password)
#secEnv.decipher doesn't strip padding. Should it? # secEnv.decipher doesn't strip padding. Should it?
self.secEnv.ct.algorithm = "RSA" #should this really be secEnv.ct? probably rather secEnv.dst
# should this really be secEnv.ct? probably rather secEnv.dst
self.secEnv.ct.algorithm = "RSA"
self.secEnv.dst.keylength = 1024 self.secEnv.dst.keylength = 1024
sw, pk = self.secEnv.generate_public_key_pair(0x00, 0x00, "") sw, pk = self.secEnv.generate_public_key_pair(0x00, 0x00, "")
self.assertEquals(sw, SW["NORMAL"]) self.assertEquals(sw, SW["NORMAL"])
@@ -76,6 +80,6 @@ class TestSmartcardSAM(unittest.TestCase):
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
#CF = CryptoflexSE(None) # CF = CryptoflexSE(None)
#print CF.generate_public_key_pair(0x00, 0x80, "\x01\x00\x01\x00") # print CF.generate_public_key_pair(0x00, 0x80, "\x01\x00\x01\x00")
#print MyCard._get_referenced_key(0x01) # print MyCard._get_referenced_key(0x01)

View File

@@ -21,13 +21,14 @@
import unittest import unittest
from virtualsmartcard.utils import C_APDU, R_APDU, hexdump from virtualsmartcard.utils import C_APDU, R_APDU, hexdump
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):
def test_CAPDU(self): def test_CAPDU(self):
a = C_APDU(1, 2, 3, 4) # case 1 a = C_APDU(1, 2, 3, 4) # case 1
b = C_APDU(1, 2, 3, 4, 5) # case 2 b = C_APDU(1, 2, 3, 4, 5) # case 2
c = C_APDU((1, 2, 3), cla = 0x23, data = "hallo") # case 3 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 d = C_APDU(1, 2, 3, 4, 2, 4, 6, 0) # case 4
print() print()
print(a) print(a)
@@ -44,15 +45,41 @@ class TestUtils(unittest.TestCase):
for i in a, b, c, d: for i in a, b, c, d:
print(hexdump(i.render())) print(hexdump(i.render()))
# case 2 extended length
print() print()
g = C_APDU(0x00, 0xb0, 0x9c, 0x00, 0x00, 0x00, 0x00) #case 2 extended length g = C_APDU(0x00, 0xb0, 0x9c, 0x00, 0x00, 0x00, 0x00)
print() print()
print(g) print(g)
print(repr(g)) print(repr(g))
print(hexdump(g.render())) print(hexdump(g.render()))
h = C_APDU('\x0c\x2a\x00\xbe\x00\x01\x5f\x87\x82\x01\x51\x01\xf0\xa2\x21\xa1\x36\x27\xb1\x30\x31\x3e\xd0\x97\x09\xb5\xde\x73\x5e\x29\x90\xce\xf1\x3d\x8a\xfd\xe7\x92\xe5\xa4\x70\xb9\x5d\x31\xe2\x34\xe7\xe2\x06\x13\x17\x7a\x3e\xca\x06\x39\x24\x2e\x75\x8c\x29\x6d\xd8\xa3\x1b\x1a\x68\x58\xd0\x1a\x98\xd4\xd8\x19\x50\xe9\x1b\x3c\xd1\xfd\x10\x53\x5b\xf2\x3b\xff\x4a\xf6\x05\xd0\x72\xad\xae\xaa\x93\x1a\x0a\x90\xc8\xa1\xb1\xf1\x0a\xba\x5b\xd2\x23\x38\xf8\x9a\x38\x9e\xa2\x04\x8b\xcb\x8b\x8b\xc0\x80\xd9\x2a\x04\x47\x26\x83\xda\xfe\x57\x68\x6b\x00\xb9\xa2\xea\x96\xf2\x07\x7f\xc5\x9c\xee\xbe\xf3\x81\xbf\x24\x19\x1e\x49\x1e\x9a\x85\x8f\x34\xcb\x1a\x23\xae\x6d\x7f\xa4\xb6\x7b\x60\x5d\x56\x79\x1c\xec\x18\xcc\x09\xdb\xb2\xbb\xf4\x31\xee\x08\x54\x26\xd5\xde\x99\xfa\x43\xa2\x49\x8e\x60\xc0\xaa\x4f\xfd\xf7\xe5\xc8\x89\x43\x5e\x11\xa2\x28\xc4\x92\x11\xda\xba\xe4\x91\xec\x04\xc9\x2c\xbd\x91\x6a\x5e\x7e\xb9\x85\xa2\xfa\x07\xc9\x47\x24\xa4\x3b\x63\xef\x75\x65\xef\xaf\xac\x22\x75\x99\x8b\x19\xde\x95\x76\xc9\xc8\xbc\x30\x23\x48\x07\x28\x19\x1e\x49\x9e\xcb\x99\xc3\x48\xdd\x1d\x0f\x44\x62\x64\x2a\x19\x7b\xeb\xee\xdf\xa1\xa6\xae\x87\x6d\x93\x36\x2d\x35\x8f\xd9\x61\x73\xef\x2d\x39\xb5\xc5\xe2\x75\x4b\x63\x0b\x41\x94\x8c\xbb\x55\xf6\x98\x5f\x9c\x07\xca\xe3\x15\xe4\xe6\x93\xd0\xa3\x9b\x22\xfa\x62\x18\xc5\x63\xfa\x2d\x57\xbb\x29\x2d\x57\x10\xd3\x0c\x05\x80\x15\x27\x4b\xc0\x84\x23\x62\x22\x6b\xae\x39\xa2\x8f\x55\xac\x8e\x08\x34\x46\xcc\x83\xf9\x9d\x2a\x75\x00\x00') h = C_APDU('\x0c\x2a\x00\xbe\x00\x01\x5f\x87\x82\x01\x51\x01\xf0\xa2'
'\x21\xa1\x36\x27\xb1\x30\x31\x3e\xd0\x97\x09\xb5\xde\x73'
'\x5e\x29\x90\xce\xf1\x3d\x8a\xfd\xe7\x92\xe5\xa4\x70\xb9'
'\x5d\x31\xe2\x34\xe7\xe2\x06\x13\x17\x7a\x3e\xca\x06\x39'
'\x24\x2e\x75\x8c\x29\x6d\xd8\xa3\x1b\x1a\x68\x58\xd0\x1a'
'\x98\xd4\xd8\x19\x50\xe9\x1b\x3c\xd1\xfd\x10\x53\x5b\xf2'
'\x3b\xff\x4a\xf6\x05\xd0\x72\xad\xae\xaa\x93\x1a\x0a\x90'
'\xc8\xa1\xb1\xf1\x0a\xba\x5b\xd2\x23\x38\xf8\x9a\x38\x9e'
'\xa2\x04\x8b\xcb\x8b\x8b\xc0\x80\xd9\x2a\x04\x47\x26\x83'
'\xda\xfe\x57\x68\x6b\x00\xb9\xa2\xea\x96\xf2\x07\x7f\xc5'
'\x9c\xee\xbe\xf3\x81\xbf\x24\x19\x1e\x49\x1e\x9a\x85\x8f'
'\x34\xcb\x1a\x23\xae\x6d\x7f\xa4\xb6\x7b\x60\x5d\x56\x79'
'\x1c\xec\x18\xcc\x09\xdb\xb2\xbb\xf4\x31\xee\x08\x54\x26'
'\xd5\xde\x99\xfa\x43\xa2\x49\x8e\x60\xc0\xaa\x4f\xfd\xf7'
'\xe5\xc8\x89\x43\x5e\x11\xa2\x28\xc4\x92\x11\xda\xba\xe4'
'\x91\xec\x04\xc9\x2c\xbd\x91\x6a\x5e\x7e\xb9\x85\xa2\xfa'
'\x07\xc9\x47\x24\xa4\x3b\x63\xef\x75\x65\xef\xaf\xac\x22'
'\x75\x99\x8b\x19\xde\x95\x76\xc9\xc8\xbc\x30\x23\x48\x07'
'\x28\x19\x1e\x49\x9e\xcb\x99\xc3\x48\xdd\x1d\x0f\x44\x62'
'\x64\x2a\x19\x7b\xeb\xee\xdf\xa1\xa6\xae\x87\x6d\x93\x36'
'\x2d\x35\x8f\xd9\x61\x73\xef\x2d\x39\xb5\xc5\xe2\x75\x4b'
'\x63\x0b\x41\x94\x8c\xbb\x55\xf6\x98\x5f\x9c\x07\xca\xe3'
'\x15\xe4\xe6\x93\xd0\xa3\x9b\x22\xfa\x62\x18\xc5\x63\xfa'
'\x2d\x57\xbb\x29\x2d\x57\x10\xd3\x0c\x05\x80\x15\x27\x4b'
'\xc0\x84\x23\x62\x22\x6b\xae\x39\xa2\x8f\x55\xac\x8e\x08'
'\x34\x46\xcc\x83\xf9\x9d\x2a\x75\x00\x00')
print() print()
print(h) print(h)
print(repr(h)) print(repr(h))
@@ -71,4 +98,3 @@ class TestUtils(unittest.TestCase):
print() print()
for i in e, f: for i in e, f:
print(hexdump(i.render())) print(hexdump(i.render()))

View File

@@ -16,26 +16,18 @@
# You should have received a copy of the GNU General Public License along with # You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>. # virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
# #
import string, binascii import binascii
import string
from ConstantDefinitions import MAX_SHORT_LE, MAX_EXTENDED_LE from ConstantDefinitions import MAX_SHORT_LE, MAX_EXTENDED_LE
def stringtoint(str): 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: if str:
return int(str.encode('hex'), 16) return int(str.encode('hex'), 16)
return 0 return 0
def inttostring(i, length=None): def inttostring(i, length=None):
#str = ""
#while i > 0:
#str = chr(i & 0xff) + str
#i >>= 8
str = "%x" % i str = "%x" % i
if len(str) % 2 == 0: if len(str) % 2 == 0:
str = str.decode('hex') str = str.decode('hex')
@@ -53,7 +45,9 @@ def inttostring(i, length=None):
_myprintable = " " + string.letters + string.digits + string.punctuation _myprintable = " " + string.letters + string.digits + string.punctuation
def hexdump(data, indent = 0, short = False, linelen = 16, offset = 0):
def hexdump(data, indent=0, short=False, linelen=16, offset=0):
"""Generates a nice hexdump of data and returns it. Consecutive lines will """Generates a nice hexdump of data and returns it. Consecutive lines will
be indented with indent spaces. When short is true, will instead generate be indented with indent spaces. When short is true, will instead generate
hexdump without adresses and on one line. hexdump without adresses and on one line.
@@ -72,24 +66,29 @@ def hexdump(data, indent = 0, short = False, linelen = 16, offset = 0):
if short: if short:
return "%s (%s)" % (hexable(data), printable(data)) return "%s (%s)" % (hexable(data), printable(data))
FORMATSTRING = "%04x: %-"+ str(linelen*3) +"s %-"+ str(linelen) +"s" FORMATSTRING = "%04x: %-" + str(linelen*3) + "s %-" + str(linelen) + "s"
result = "" result = ""
(head, tail) = (data[:linelen], data[linelen:]) (head, tail) = (data[:linelen], data[linelen:])
pos = 0 pos = 0
while len(head) > 0: while len(head) > 0:
if pos > 0: if pos > 0:
result = result + "\n%s" % (' ' * indent) result = result + "\n%s" % (' ' * indent)
result = result + FORMATSTRING % (pos+offset, hexable(head), printable(head)) result = result + FORMATSTRING % (pos+offset, hexable(head),
printable(head))
pos = pos + len(head) pos = pos + len(head)
(head, tail) = (tail[:linelen], tail[linelen:]) (head, tail) = (tail[:linelen], tail[linelen:])
return result return result
LIFE_CYCLES = {0x01: "Load file = loaded", LIFE_CYCLES = {0x01: "Load file = loaded",
0x03: "Applet instance / security domain = Installed", 0x03: "Applet instance / security domain = Installed",
0x07: "Card manager = Initialized; Applet instance / security domain = Selectable", 0x07: "Card manager = Initialized; Applet instance / security "
0x0F: "Card manager = Secured; Applet instance / security domain = Personalized", "domain = Selectable",
0x7F: "Card manager = Locked; Applet instance / security domain = Blocked", 0x0F: "Card manager = Secured; Applet instance / security "
0xFF: "Applet instance = Locked"} "domain = Personalized",
0x7F: "Card manager = Locked; Applet instance / security "
"domain = Blocked",
0xFF: "Applet instance = Locked"}
def parse_status(data): def parse_status(data):
"""Parses the Response APDU of a GetStatus command.""" """Parses the Response APDU of a GetStatus command."""
@@ -99,21 +98,21 @@ def parse_status(data):
return "N/A" return "N/A"
else: else:
privs = [] privs = []
if privileges & (1<<7): if privileges & (1 << 7):
privs.append("security domain") privs.append("security domain")
if privileges & (1<<6): if privileges & (1 << 6):
privs.append("DAP DES verification") privs.append("DAP DES verification")
if privileges & (1<<5): if privileges & (1 << 5):
privs.append("delegated management") privs.append("delegated management")
if privileges & (1<<4): if privileges & (1 << 4):
privs.append("card locking") privs.append("card locking")
if privileges & (1<<3): if privileges & (1 << 3):
privs.append("card termination") privs.append("card termination")
if privileges & (1<<2): if privileges & (1 << 2):
privs.append("default selected") privs.append("default selected")
if privileges & (1<<1): if privileges & (1 << 1):
privs.append("global PIN modification") privs.append("global PIN modification")
if privileges & (1<<0): if privileges & (1 << 0):
privs.append("mandated DAP verification") privs.append("mandated DAP verification")
return ", ".join(privs) return ", ".join(privs)
@@ -123,9 +122,12 @@ def parse_status(data):
privileges = ord(segment[1+lgth+1]) privileges = ord(segment[1+lgth+1])
print("aid length: %i (%x)" % (lgth, lgth)) print("aid length: %i (%x)" % (lgth, lgth))
print("aid: %s" % hexdump(aid, indent = 18, short=True)) 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("life cycle state: %x (%s)" %
print("privileges: %x (%s)\n" % (privileges, parse_privileges(privileges))) (lifecycle, LIFE_CYCLES.get(lifecycle,
"unknown or invalid state")))
print("privileges: %x (%s)\n" %
(privileges, parse_privileges(privileges)))
pos = 0 pos = 0
while pos < len(data): while pos < len(data):
@@ -134,16 +136,19 @@ def parse_status(data):
parse_segment(segment) parse_segment(segment)
pos = pos + lgth pos = pos + lgth
def _unformat_hexdump(dump): def _unformat_hexdump(dump):
hexdump = " ".join([line[7:54] for line in dump.splitlines()]) hexdump = " ".join([line[7:54] for line in dump.splitlines()])
return binascii.a2b_hex("".join([e != " " and e or "" for e in hexdump])) return binascii.a2b_hex("".join([e != " " and e or "" for e in hexdump]))
def _make_byte_property(prop): def _make_byte_property(prop):
"Make a byte property(). This is meta code." "Make a byte property(). This is meta code."
return property(lambda self: getattr(self, "_"+prop, None), return property(lambda self: getattr(self, "_"+prop, None),
lambda self, value: self._setbyte(prop, value), lambda self, value: self._setbyte(prop, value),
lambda self: delattr(self, "_"+prop), lambda self: delattr(self, "_"+prop),
"The %s attribute of the APDU" % prop) "The %s attribute of the APDU" % prop)
class APDU(object): class APDU(object):
"Base class for an APDU" "Base class for an APDU"
@@ -151,8 +156,9 @@ class APDU(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Creates a new APDU instance. Can be given positional parameters which """Creates a new APDU instance. Can be given positional parameters which
must be sequences of either strings (or strings themselves) or integers must be sequences of either strings (or strings themselves) or integers
specifying byte values that will be concatenated in order. Alternatively specifying byte values that will be concatenated in order.
you may give exactly one positional argument that is an APDU instance. Alternatively you may give exactly one positional argument that is an
APDU instance.
After all the positional arguments have been concatenated they must After all the positional arguments have been concatenated they must
form a valid APDU! form a valid APDU!
@@ -166,7 +172,7 @@ class APDU(object):
initbuff = list() initbuff = list()
if len(args) == 1 and isinstance(args[0], self.__class__): if len(args) == 1 and isinstance(args[0], self.__class__):
self.parse( args[0].render() ) self.parse(args[0].render())
else: else:
for arg in args: for arg in args:
if type(arg) == str: if type(arg) == str:
@@ -185,9 +191,10 @@ class APDU(object):
if t == str: if t == str:
initbuff[index] = ord(value) initbuff[index] = ord(value)
elif t != int: elif t != int:
raise TypeError("APDU must consist of ints or one-byte strings, not %s (index %s)" % (t, index)) raise TypeError("APDU must consist of ints or one-byte "
"strings, not %s (index %s)" % (t, index))
self.parse( initbuff ) self.parse(initbuff)
for (name, value) in kwargs.items(): for (name, value) in kwargs.items():
if value is not None: if value is not None:
@@ -195,40 +202,45 @@ class APDU(object):
def _getdata(self): def _getdata(self):
return self._data return self._data
def _setdata(self, value): def _setdata(self, value):
if isinstance(value, str): if isinstance(value, str):
self._data = "".join([e for e in value]) self._data = "".join([e for e in value])
elif isinstance(value, list): elif isinstance(value, list):
self._data = "".join([chr(int(e)) for e in value]) self._data = "".join([chr(int(e)) for e in value])
else: else:
raise ValueError("'data' attribute can only be a str or a list of int, not %s" % type(value)) raise ValueError("'data' attribute can only be a str or a list of "
"int, not %s" % type(value))
self.Lc = len(value) self.Lc = len(value)
def _deldata(self): def _deldata(self):
del self._data; self.data = "" del self._data
self.data = ""
data = property(_getdata, _setdata, None, data = property(_getdata, _setdata, None,
"The data contents of this APDU") "The data contents of this APDU")
def _setbyte(self, name, value): def _setbyte(self, name, value):
#print "setbyte(%r, %r)" % (name, value)
if isinstance(value, int): if isinstance(value, int):
setattr(self, "_"+name, value) setattr(self, "_"+name, value)
elif isinstance(value, str): elif isinstance(value, str):
setattr(self, "_"+name, ord(value)) setattr(self, "_"+name, ord(value))
else: else:
raise ValueError("'%s' attribute can only be a byte, that is: int or str, not %s" % (name, type(value))) raise ValueError("'%s' attribute can only be a byte, that is: int "
"or str, not %s" % (name, type(value)))
def _format_parts(self, fields): def _format_parts(self, fields):
"utility function to be used in __str__ and __repr__" "utility function to be used in __str__ and __repr__"
parts = [] parts = []
for i in fields: for i in fields:
parts.append( "%s=0x%02X" % (i, getattr(self, i)) ) parts.append("%s=0x%02X" % (i, getattr(self, i)))
return parts return parts
def __str__(self): def __str__(self):
result = "%s(%s)" % (self.__class__.__name__, ", ".join(self._format_fields())) result = "%s(%s)" % (self.__class__.__name__,
", ".join(self._format_fields()))
if len(self.data) > 0: if len(self.data) > 0:
result = result + " with %i (0x%02x) bytes of data" % ( result = result + " with %i (0x%02x) bytes of data" % (
@@ -246,36 +258,42 @@ class APDU(object):
return "%s(%s)" % (self.__class__.__name__, ", ".join(parts)) return "%s(%s)" % (self.__class__.__name__, ", ".join(parts))
class C_APDU(APDU): class C_APDU(APDU):
"Class for a command APDU" "Class for a command APDU"
def parse(self, apdu): def parse(self, apdu):
"Parse a full command APDU and assign the values to our object, overwriting whatever there was." """Parse a full command APDU and assign the values to our object,
apdu = map( lambda a: (isinstance(a, str) and (ord(a),) or (a,))[0], apdu) 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) apdu = apdu + [0] * max(4-len(apdu), 0)
self.CLA, self.INS, self.P1, self.P2 = apdu[:4] # case 1, 2, 3, 4 self.CLA, self.INS, self.P1, self.P2 = apdu[:4] # case 1, 2, 3, 4
self.__extended_length = False self.__extended_length = False
if len(apdu) == 4: # case 1 if len(apdu) == 4: # case 1
self.data = "" self.data = ""
elif (len(apdu) >= 7) and (apdu[4] == 0): # extended length apdu elif (len(apdu) >= 7) and (apdu[4] == 0): # extended length apdu
self.__extended_length = True self.__extended_length = True
if len(apdu) == 7: # case 2 extended length if len(apdu) == 7: # case 2 extended length
self.Le = (apdu[-2]<<8) + apdu[-1] self.Le = (apdu[-2] << 8) + apdu[-1]
self.data = "" self.data = ""
else: # case 3, 4 extended length else: # case 3, 4 extended length
self.Lc = (apdu[5]<<8) + apdu[6] self.Lc = (apdu[5] << 8) + apdu[6]
if len(apdu) == 7 + self.Lc: # case 3 extended length if len(apdu) == 7 + self.Lc: # case 3 extended length
self.data = apdu[7:] self.data = apdu[7:]
elif len(apdu) == 7 + self.Lc + 3: # case 4 extended length elif len(apdu) == 7 + self.Lc + 3: # case 4 extended length
self.Le = (apdu[-2]<<8) + apdu[-1] self.Le = (apdu[-2] << 8) + apdu[-1]
self.data = apdu[7:-3] self.data = apdu[7:-3]
elif len(apdu) == 7 + self.Lc + 2 and apdu[-2:] == [0, 0]: # case 4 extended length with max le
# case 4 extended length with max le
elif len(apdu) == 7 + self.Lc + 2 and apdu[-2:] == [0, 0]:
self.Le = 0 self.Le = 0
self.data = apdu[7:-2] self.data = apdu[7:-2]
else: else:
raise ValueError("Invalid Lc value. Is %s, should be %s or %s" raise ValueError("Invalid Lc value. Is %s, should be %s "
% ( self.Lc, 7 + self.Lc, 7 + self.Lc + 3)) "or %s" % (self.Lc, 7 + self.Lc,
7 + self.Lc + 3))
else: # short apdu else: # short apdu
if len(apdu) == 5: # case 2 short apdu if len(apdu) == 5: # case 2 short apdu
self.Le = apdu[-1] self.Le = apdu[-1]
@@ -288,15 +306,22 @@ class C_APDU(APDU):
self.data = apdu[5:-1] self.data = apdu[5:-1]
self.Le = apdu[-1] self.Le = apdu[-1]
else: else:
raise ValueError("Invalid Lc value. Is %s, should be %s or %s" % (self.Lc, raise ValueError("Invalid Lc value. Is %s, should be %s "
5 + self.Lc, 5 + self.Lc + 1)) "or %s" % (self.Lc, 5 + self.Lc,
5 + self.Lc + 1))
CLA = _make_byte_property("CLA"); cla = CLA CLA = _make_byte_property("CLA")
INS = _make_byte_property("INS"); ins = INS cla = CLA
P1 = _make_byte_property("P1"); p1 = P1 INS = _make_byte_property("INS")
P2 = _make_byte_property("P2"); p2 = P2 ins = INS
Lc = _make_byte_property("Lc"); lc = Lc P1 = _make_byte_property("P1")
Le = _make_byte_property("Le"); le = Le p1 = P1
P2 = _make_byte_property("P2")
p2 = P2
Lc = _make_byte_property("Lc")
lc = Lc
Le = _make_byte_property("Le")
le = Le
@property @property
def effective_Le(self): def effective_Le(self):
@@ -312,7 +337,9 @@ class C_APDU(APDU):
fields = ["CLA", "INS", "P1", "P2"] fields = ["CLA", "INS", "P1", "P2"]
if self.Lc > 0: if self.Lc > 0:
fields.append("Lc") fields.append("Lc")
if hasattr(self, "_Le"): ## There's a difference between "Le = 0" and "no Le"
# There's a difference between "Le = 0" and "no Le"
if hasattr(self, "_Le"):
fields.append("Le") fields.append("Le")
return self._format_parts(fields) return self._format_parts(fields)
@@ -331,8 +358,8 @@ class C_APDU(APDU):
if hasattr(self, "_Le"): if hasattr(self, "_Le"):
if self.__extended_length: if self.__extended_length:
buffer.append(chr(0x00)) buffer.append(chr(0x00))
buffer.append(chr(self.Le>>8)) buffer.append(chr(self.Le >> 8))
buffer.append(chr(self.Le - self.Le>>8)) buffer.append(chr(self.Le - self.Le >> 8))
else: else:
buffer.append(chr(self.Le)) buffer.append(chr(self.Le))
@@ -355,7 +382,9 @@ class C_APDU(APDU):
class R_APDU(APDU): class R_APDU(APDU):
"Class for a response APDU" "Class for a response APDU"
def _getsw(self): return chr(self.SW1) + chr(self.SW2) def _getsw(self):
return chr(self.SW1) + chr(self.SW2)
def _setsw(self, value): def _setsw(self, value):
if len(value) != 2: if len(value) != 2:
raise ValueError("SW must be exactly two bytes") raise ValueError("SW must be exactly two bytes")
@@ -363,14 +392,17 @@ class R_APDU(APDU):
self.SW2 = value[1] self.SW2 = value[1]
SW = property(_getsw, _setsw, None, SW = property(_getsw, _setsw, None,
"The Status Word of this response APDU") "The Status Word of this response APDU")
sw = SW sw = SW
SW1 = _make_byte_property("SW1"); sw1 = SW1 SW1 = _make_byte_property("SW1")
SW2 = _make_byte_property("SW2"); sw2 = SW2 sw1 = SW1
SW2 = _make_byte_property("SW2")
sw2 = SW2
def parse(self, apdu): def parse(self, apdu):
"Parse a full response APDU and assign the values to our object, overwriting whatever there was." """Parse a full response APDU and assign the values to our object,
overwriting whatever there was."""
self.SW = apdu[-2:] self.SW = apdu[-2:]
self.data = apdu[:-2] self.data = apdu[:-2]
@@ -381,4 +413,3 @@ class R_APDU(APDU):
def render(self): def render(self):
"Return this APDU as a binary string" "Return this APDU as a binary string"
return self.data + self.sw return self.data + self.sw