Refactor code according to PEP8

This commit is contained in:
Dominik
2016-03-05 21:15:48 +01:00
parent 08f91685fc
commit c0bea2cd25
14 changed files with 1969 additions and 1407 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,201 +1,219 @@
# #
# Copyright (C) 2011 Frank Morgner, Dominik Oepen # Copyright (C) 2011 Frank Morgner, Dominik Oepen
# #
# This file is part of virtualsmartcard. # This file is part of virtualsmartcard.
# #
# virtualsmartcard is free software: you can redistribute it and/or modify it # virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free # under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any # Software Foundation, either version 3 of the License, or (at your option) any
# later version. # later version.
# #
# virtualsmartcard is distributed in the hope that it will be useful, but # virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. # more details.
# #
# 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/>.
MAX_SHORT_LE = 0xff+1 MAX_SHORT_LE = 0xff+1
MAX_EXTENDED_LE = 0xffff+1 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
SM_Class[0xA8] = "SIGNATURE_VERIFICATION_TEMPLATE" # signed)
#Control reference template for hash-code (HT) SM_Class[0xA8] = "SIGNATURE_VERIFICATION_TEMPLATE"
SM_Class["HASH_CRT"] = 0xAA # Control reference template for hash-code (HT)
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
SM_Class["SIGNATURE_COMPUTATION_TEMPLATE"] = 0xAC # value fields are signed)
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
SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"] = 0xB0 # field
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
SM_Class["CERTIFICATE_VERIFICATION_TEMPLATE"] = 0xBE # certified)
#}}} 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"
@@ -212,18 +230,17 @@ ALGO_MAPPING[0x0B] = "CC"
ALGO_MAPPING[0x0C] = "RSA" ALGO_MAPPING[0x0C] = "RSA"
ALGO_MAPPING[0x0D] = "DSA" 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

@@ -1,33 +1,33 @@
# #
# Copyright (C) 2014 Dominik Oepen # Copyright (C) 2014 Dominik Oepen
# #
# This file is part of virtualsmartcard. # This file is part of virtualsmartcard.
# #
# virtualsmartcard is free software: you can redistribute it and/or modify it # virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free # under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any # Software Foundation, either version 3 of the License, or (at your option) any
# later version. # later version.
# #
# virtualsmartcard is distributed in the hope that it will be useful, but # virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. # more details.
# #
# 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,25 +37,32 @@ 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:
cipher = c_class.new(key, mode, '\x00'*get_cipher_blocklen(cipherspec)) cipher = c_class.new(key, mode, '\x00'*get_cipher_blocklen(cipherspec))
@@ -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,23 +92,26 @@ 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
specified encryption algorithm. specified encryption algorithm.
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,21 +121,23 @@ 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
""" """
if padding_class == 0x01: if padding_class == 0x01:
tail = len(data) - 1 tail = len(data) - 1
while data[tail] != '\x80': while data[tail] != '\x80':
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.
:param algo: :param algo:
A string specifying the algorithm to use. Currently supported A string specifying the algorithm to use. Currently supported
algorithms are \"MAX\" \"HMAC\" and \"CC\" (Meaning a cryptographic algorithms are \"MAX\" \"HMAC\" and \"CC\" (Meaning a cryptographic
@@ -134,12 +149,12 @@ def crypto_checksum(algo, key, data, iv=None, ssc=None):
:param ssc: :param ssc:
Optional. A send sequence counter to be prepended to the data. Optional. A send sequence counter to be prepended to the data.
Only used by the \"CC\" algorithm Only used by the \"CC\" algorithm
""" """
if algo not in ("HMAC", "MAC", "CC"): if algo not in ("HMAC", "MAC", "CC"):
raise ValueError("Unknown Algorithm %s" % algo) raise ValueError("Unknown Algorithm %s" % algo)
if algo == "MAC": if algo == "MAC":
checksum = calculate_MAC(key, data, iv) checksum = calculate_MAC(key, data, iv)
elif algo == "HMAC": elif algo == "HMAC":
@@ -147,47 +162,52 @@ 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:])
c = cipher(True, "des-ecb", key[:8], b) c = cipher(True, "des-ecb", key[:8], b)
checksum = c checksum = c
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\""""
cipher = get_cipher(cipherspec, key, iv) cipher = get_cipher(cipherspec, key, iv)
result = None result = None
if do_encrypt: if do_encrypt:
result = cipher.encrypt(data) result = cipher.encrypt(data)
else: else:
result = cipher.decrypt(data) result = cipher.decrypt(data)
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,93 +216,97 @@ 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)
hmac = crypto_checksum("HMAC", key, cryptedString) hmac = crypto_checksum("HMAC", key, cryptedString)
protectedString = "$p5k2$$" + salt + "$" + cryptedString + hmac protectedString = "$p5k2$$" + salt + "$" + cryptedString + hmac
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
""" """
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.
The number of iterations specified in the salt overrides the 'iterations' The number of iterations specified in the salt overrides the 'iterations'
parameter. parameter.
The effective hash length is 192 bits. The effective hash length is 192 bits.
""" """
# Generate a (pseudo-)random salt if the user hasn't provided one. # Generate a (pseudo-)random salt if the user hasn't provided one.
if salt is None: if salt is None:
salt = _makesalt() salt = _makesalt()
@@ -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):
@@ -311,9 +336,10 @@ def crypt(word, salt=None, iterations=None):
iterations = converted iterations = converted
if not (iterations >= 1): if not (iterations >= 1):
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,9 +352,10 @@ 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().
This function is not suitable for generating cryptographic secrets. This function is not suitable for generating cryptographic secrets.
""" """
binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)]) binarysalt = "".join([pack("@H", randint(0, 0xffff)) for i in range(3)])

View File

@@ -1,50 +1,52 @@
# #
# Copyright (C) 2011 Dominik Oepen # Copyright (C) 2011 Dominik Oepen
# #
# This file is part of virtualsmartcard. # This file is part of virtualsmartcard.
# #
# virtualsmartcard is free software: you can redistribute it and/or modify it # virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free # under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any # Software Foundation, either version 3 of the License, or (at your option) any
# later version. # later version.
# #
# virtualsmartcard is distributed in the hope that it will be useful, but # virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. # more details.
# #
# 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
@@ -63,17 +65,17 @@ class ControlReferenceTemplate:
if config != "": if config != "":
self.parse_SE_config(config) self.parse_SE_config(config)
self.__config_string = config self.__config_string = config
def parse_SE_config(self, config): def parse_SE_config(self, config):
""" """
Parse a control reference template as given e.g. in an MSE APDU. Parse a control reference template as given e.g. in an MSE APDU.
:param config: a TLV string containing the configuration for the CRT. :param config: a TLV string containing the configuration for the CRT.
""" """
error = False error = False
structure = unpack(config) structure = unpack(config)
for tlv in structure: for tlv in structure:
tag, length, value = tlv tag, length, value = tlv
if tag == 0x80: if tag == 0x80:
self.__set_algo(value) self.__set_algo(value)
elif tag in (0x81, 0x82, 0x83, 0x84): elif tag in (0x81, 0x82, 0x83, 0x84):
@@ -84,27 +86,27 @@ class ControlReferenceTemplate:
self.usage_qualifier = value self.usage_qualifier = value
else: else:
error = True error = True
if error: if error:
raise SwError(SW["ERR_REFNOTUSABLE"]) raise SwError(SW["ERR_REFNOTUSABLE"])
else: else:
return SW["NORMAL"], "" return SW["NORMAL"], ""
def __set_algo(self, data): def __set_algo(self, data):
""" """
Set the algorithm to be used by this CRT. The algorithms are specified Set the algorithm to be used by this CRT. The algorithms are specified
in a global dictionary. New cards may add or modify this table in order in a global dictionary. New cards may add or modify this table in order
to support new or different algorithms. to support new or different algorithms.
: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]
self.__replace_tag(0x80, data) self.__replace_tag(0x80, data)
def __set_key(self, tag, value): def __set_key(self, tag, value):
if tag == 0x81: if tag == 0x81:
self.fileref = value self.fileref = value
@@ -115,13 +117,13 @@ class ControlReferenceTemplate:
self.keyref_public_key = value self.keyref_public_key = value
elif tag == 0x84: elif tag == 0x84:
self.keyref_private_key = value self.keyref_private_key = value
def __set_iv(self, tag, length, value): def __set_iv(self, tag, length, value):
iv = None iv = None
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
@@ -139,46 +141,47 @@ class ControlReferenceTemplate:
else: else:
iv = int(time()) iv = int(time())
self.iv = iv self.iv = iv
def __replace_tag(self, tag, data): def __replace_tag(self, tag, data):
""" """
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 \
self.__config_string[position] != tag: self.__config_string[position] != tag:
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):
""" """
Return the content of the CRT, encoded as TLV data in a string Return the content of the CRT, encoded as TLV data in a string
""" """
return self.__config_string return self.__config_string
def __str__(self): def __str__(self):
return self.__config_string return self.__config_string
class Security_Environment(object): class Security_Environment(object):
def __init__(self, MF, SAM): def __init__(self, MF, SAM):
self.sam = SAM self.sam = SAM
self.mf = MF self.mf = MF
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"])
@@ -197,7 +200,7 @@ class Security_Environment(object):
or to manipulate the various parameters of the current SE. or to manipulate the various parameters of the current SE.
P1 specifies the operation to perform, p2 is either the SEID for the P1 specifies the operation to perform, p2 is either the SEID for the
referred SE or the tag of a control reference template referred SE or the tag of a control reference template
:param p1: :param p1:
Bitmask according to this table Bitmask according to this table
@@ -206,7 +209,7 @@ class Security_Environment(object):
== == == == == == == == ======================================= == == == == == == == == =======================================
- - - 1 - - - - Secure messaging in command data field - - - 1 - - - - Secure messaging in command data field
- - 1 - - - - - Secure messaging in response data field - - 1 - - - - - Secure messaging in response data field
- 1 - - - - - - Computation, decipherment, internal - 1 - - - - - - Computation, decipherment, internal
authentication and key agreement authentication and key agreement
1 - - - - - - - Verification, encipherment, external 1 - - - - - - - Verification, encipherment, external
authentication and key agreement authentication and key agreement
@@ -217,24 +220,26 @@ 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
if se & 0x04: # agreement
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)
@@ -242,14 +247,13 @@ class Security_Environment(object):
return self.sam.erase_SE(p2) return self.sam.erase_SE(p2)
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
control reference template, data contains control reference objects control reference template, data contains control reference objects
""" """
if p2 == 0xA4: if p2 == 0xA4:
return self.at.parse_SE_config(data) return self.at.parse_SE_config(data)
elif p2 == 0xA6: elif p2 == 0xA6:
@@ -264,51 +268,54 @@ class Security_Environment(object):
return self.ct.parse_SE_config(data) return self.ct.parse_SE_config(data)
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
def parse_SM_CAPDU(self, CAPDU, authenticate_header): def parse_SM_CAPDU(self, CAPDU, authenticate_header):
""" """
This methods parses a data field including Secure Messaging objects. This methods parses a data field including Secure Messaging objects.
SM_header indicates whether or not the header of the message shall be SM_header indicates whether or not the header of the message shall be
authenticated. It returns an unprotected command APDU authenticated. It returns an unprotected command APDU
:param CAPDU: The protected CAPDU to be parsed :param CAPDU: The protected CAPDU to be parsed
:param authenticate_header: Whether or not the header should be :param authenticate_header: Whether or not the header should be
included in authentication mechanisms included in authentication mechanisms
:returns: Unprotected command APDU :returns: Unprotected command APDU
""" """
structure = unpack(CAPDU.data) structure = unpack(CAPDU.data)
return_data = ["",] return_data = ["", ]
cla = None cla = None
ins = None ins = None
p1 = None p1 = None
p2 = None p2 = None
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"])
@@ -387,60 +396,63 @@ class Security_Environment(object):
hash = self.hash(p1, p2, to_authenticate) hash = self.hash(p1, p2, to_authenticate)
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):
""" """
This method protects a response APDU using secure messaging mechanisms This method protects a response APDU using secure messaging mechanisms
:returns: the protected data and the SW bytes :returns: the protected data and the SW bytes
""" """
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([(
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"], SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"],
len(encrypted), len(encrypted),
encrypted)]) encrypted)])
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)])
@@ -450,25 +462,25 @@ class Security_Environment(object):
auth = self.compute_digital_signature(0x9E, 0x9A, hash) auth = self.compute_digital_signature(0x9E, 0x9A, hash)
length = len(auth) length = len(auth)
return_data += pack([(tag, length, auth)]) return_data += pack([(tag, length, auth)])
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
commands in ISO 7816-8. It will invoke the appropriate command and commands in ISO 7816-8. It will invoke the appropriate command and
return its result return its result
""" """
allowed_P1P2 = ((0x90, 0x80), (0x90, 0xA0), (0x9E, 0x9A), (0x9E, 0xAC), allowed_P1P2 = ((0x90, 0x80), (0x90, 0xA0), (0x9E, 0x9A), (0x9E, 0xAC),
(0x9E, 0xBC), (0x00, 0xA2), (0x00, 0xA8), (0x00, 0x92), (0x9E, 0xBC), (0x00, 0xA2), (0x00, 0xA8), (0x00, 0x92),
(0x00, 0xAE), (0x00, 0xBE), (0x82, 0x80), (0x84, 0x80), (0x00, 0xAE), (0x00, 0xBE), (0x82, 0x80), (0x84, 0x80),
(0x86, 0x80), (0x80, 0x82), (0x80, 0x84), (0x80, 0x86)) (0x86, 0x80), (0x80, 0x82), (0x80, 0x84), (0x80, 0x86))
if (p1, p2) not in allowed_P1P2: if (p1, p2) not in allowed_P1P2:
raise SwError(SW["INCORRECTP1P2"]) raise SwError(SW["INCORRECTP1P2"])
if((p2 in (0x80, 0xA0)) and (p1 == 0x90)): if((p2 in (0x80, 0xA0)) and (p1 == 0x90)):
response_data = self.hash(p1, p2, data) response_data = self.hash(p1, p2, data)
elif(p2 in (0x9A, 0xAC, 0xBC) and p1 == 0x9E): elif(p2 in (0x9A, 0xAC, 0xBC) and p1 == 0x9E):
@@ -483,13 +495,12 @@ class Security_Environment(object):
response_data = self.encipher(p1, p2, data) response_data = self.encipher(p1, p2, data)
elif (p2 in (0x82, 0x84, 0x86) and p1 == 0x80): elif (p2 in (0x82, 0x84, 0x86) and p1 == 0x80):
response_data = self.decipher(p1, p2, data) response_data = self.decipher(p1, p2, data)
if p1 == 0x00: if p1 == 0x00:
assert response_data == "" assert response_data == ""
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,54 +508,54 @@ 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,
data, self.cct.iv) data, self.cct.iv)
return checksum return checksum
def compute_digital_signature(self, p1, p2, data): def compute_digital_signature(self, p1, p2, data):
""" """
Compute a digital signature for the given data. Compute a digital signature for the given data.
Algorithm and key are specified in the current SE Algorithm and key are specified in the current SE
:param p1: Must be 0x9E = Secure Messaging class for digital signatures :param p1: Must be 0x9E = Secure Messaging class for digital signatures
:param p2: Must be one of 0x9A, 0xAC, 0xBC. Indicates what kind of data :param p2: Must be one of 0x9A, 0xAC, 0xBC. Indicates what kind of data
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, "")
return signature return signature
def hash(self, p1, p2, data): def hash(self, p1, p2, data):
""" """
Hash the given data using the algorithm specified by the current Hash the given data using the algorithm specified by the current
Security environment. Security environment.
: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
@@ -631,15 +642,16 @@ class Security_Environment(object):
""" """
Encipher data using key, algorithm, IV and Padding specified Encipher data using key, algorithm, IV and Padding specified
by the current Security environment. by the current Security environment.
:returns: raw data (no TLV coding). :returns: raw data (no TLV coding).
""" """
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
@@ -647,12 +659,12 @@ class Security_Environment(object):
""" """
Decipher data using key, algorithm, IV and Padding specified Decipher data using key, algorithm, IV and Padding specified
by the current Security environment. by the current Security environment.
:returns: raw data (no TLV coding). Padding is not removed!!! :returns: raw data (no TLV coding). Padding is not removed!!!
""" """
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)
@@ -660,15 +672,17 @@ class Security_Environment(object):
def generate_public_key_pair(self, p1, p2, data): def generate_public_key_pair(self, p1, p2, data):
""" """
The GENERATE PUBLIC-KEY PAIR command either initiates the generation The GENERATE PUBLIC-KEY PAIR command either initiates the generation
and storing of a key pair, i.e., a public key and a private key, in the and storing of a key pair, i.e., a public key and a private key, in the
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
from Crypto.Util.randpool import RandomPool from Crypto.Util.randpool import RandomPool
rnd = RandomPool() rnd = RandomPool()
@@ -676,45 +690,45 @@ class Security_Environment(object):
cipher = self.ct.algorithm cipher = self.ct.algorithm
c_class = locals().get(cipher, None) c_class = locals().get(cipher, None)
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

@@ -1,129 +1,172 @@
# #
# Copyright (C) 2011 Frank Morgner # Copyright (C) 2011 Frank Morgner
# #
# This file is part of virtualsmartcard. # This file is part of virtualsmartcard.
# #
# virtualsmartcard is free software: you can redistribute it and/or modify it # virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free # under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any # Software Foundation, either version 3 of the License, or (at your option) any
# later version. # later version.
# #
# virtualsmartcard is distributed in the hope that it will be useful, but # virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. # more details.
# #
# 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/>.
# #
# 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

@@ -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,43 +45,45 @@ 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.
It includes the PIN, the master key of the SAM and a hashmap containing all It includes the PIN, the master key of the SAM and a hashmap containing all
the keys used by the file encryption system. The keys in the hashmap are the keys used by the file encryption system. The keys in the hashmap are
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)
@@ -90,7 +93,7 @@ class SAM(object):
needs a reference to the filesystem in order to store/retrieve keys. needs a reference to the filesystem in order to store/retrieve keys.
""" """
self.mf = mf self.mf = mf
def FSencrypt(self, data): def FSencrypt(self, data):
""" """
Encrypt the given data, using the parameters stored in the SAM. Encrypt the given data, using the parameters stored in the SAM.
@@ -106,23 +109,23 @@ class SAM(object):
might not be added in a future version. might not be added in a future version.
""" """
return data return data
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
return SW["NORMAL"], "" return SW["NORMAL"], ""
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]
@@ -131,45 +134,44 @@ class SAM(object):
self.current_SE = SE self.current_SE = SE
else: else:
raise SwError(SW["ERR_REFNOTUSABLE"]) raise SwError(SW["ERR_REFNOTUSABLE"])
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]
return SW["NORMAL"], "" return SW["NORMAL"], ""
def set_asym_algorithm(self, cipher, keytype): def set_asym_algorithm(self, cipher, keytype):
""" """
: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
self.asym_key = cipher self.asym_key = cipher
def verify(self, p1, p2, PIN): def verify(self, p1, p2, PIN):
""" """
Authenticate the card user. Check if he entered a valid PIN. Authenticate the card user. Check if he entered a valid PIN.
If the PIN is invalid decrement retry counter. If retry counter If the PIN is invalid decrement retry counter. If retry counter
equals zero, block the card until reset with correct PUK equals zero, block the card until reset with correct PUK
""" """
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"])
if self.counter > 0: if self.counter > 0:
if self.PIN == PIN: if self.PIN == PIN:
self.counter = 3 self.counter = 3
@@ -184,32 +186,32 @@ 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"], ""
def internal_authenticate(self, p1, p2, data): def internal_authenticate(self, p1, p2, data):
""" """
Authenticate card to terminal. Encrypt the challenge of the terminal Authenticate card to terminal. Encrypt the challenge of the terminal
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:
key = self._get_referenced_key(p1, p2) key = self._get_referenced_key(p1, p2)
crypted_challenge = vsCrypto.encrypt(cipher, key, data) crypted_challenge = vsCrypto.encrypt(cipher, key, data)
return SW["NORMAL"], crypted_challenge return SW["NORMAL"], crypted_challenge
def external_authenticate(self, p1, p2, data): def external_authenticate(self, p1, p2, data):
""" """
Authenticate the terminal to the card. Check whether Terminal correctly Authenticate the terminal to the card. Check whether Terminal correctly
@@ -217,44 +219,44 @@ class SAM(object):
""" """
if self.last_challenge is None: if self.last_challenge is None:
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)
blocklen = vsCrypto.get_cipher_blocklen(cipher) blocklen = vsCrypto.get_cipher_blocklen(cipher)
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):
""" """
Takes an encrypted challenge in the form Takes an encrypted challenge in the form
'Terminal Challenge | Card Challenge | Card number' 'Terminal Challenge | Card Challenge | Card number'
and checks it for validity. If the challenge is successful and checks it for validity. If the challenge is successful
the card encrypts 'Card Challenge | Terminal challenge' and the card encrypts 'Card Challenge | Terminal challenge' and
returns this value returns this value
""" """
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)
@@ -262,74 +264,74 @@ class SAM(object):
terminal_challenge = plain[:last_challenge_len-1] terminal_challenge = plain[:last_challenge_len-1]
card_challenge = plain[last_challenge_len:-len(card_number)-1] card_challenge = plain[last_challenge_len:-len(card_number)-1]
serial_number = plain[-len(card_number):] serial_number = plain[-len(card_number):]
if terminal_challenge != self.last_challenge: if terminal_challenge != self.last_challenge:
raise SwError(SW["WARN_NOINFO63"]) raise SwError(SW["WARN_NOINFO63"])
elif serial_number != card_number: elif serial_number != card_number:
raise SwError(SW["WARN_NOINFO63"]) raise SwError(SW["WARN_NOINFO63"])
result = card_challenge + terminal_challenge result = card_challenge + terminal_challenge
return SW["NORMAL"], vsCrypto.encrypt(cipher, key, result) return SW["NORMAL"], vsCrypto.encrypt(cipher, key, result)
def get_challenge(self, p1, p2, data): def get_challenge(self, p1, p2, data):
""" """
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)
return SW["NORMAL"], self.last_challenge return SW["NORMAL"], self.last_challenge
def get_card_number(self): def get_card_number(self):
return SW["NORMAL"], inttostring(self.cardNumber) return SW["NORMAL"], inttostring(self.cardNumber)
def _get_referenced_key(self, p1, p2): def _get_referenced_key(self, p1, p2):
""" """
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
""" """
key = None key = None
qualifier = p2 & 0x1F qualifier = p2 & 0x1F
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 is None:
if self.mf == 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)
@@ -342,17 +344,17 @@ class SAM(object):
return self.current_SE.parse_SM_CAPDU(CAPDU, header_authentication) return self.current_SE.parse_SM_CAPDU(CAPDU, header_authentication)
except: except:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
def protect_result(self, sw, unprotected_result): def protect_result(self, sw, unprotected_result):
""" """
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):
return self.current_SE.perform_security_operation(p1, p2, data) return self.current_SE.perform_security_operation(p1, p2, data)
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

@@ -1,18 +1,18 @@
# #
# Copyright (C) 2011 Frank Morgner # Copyright (C) 2011 Frank Morgner
# #
# This file is part of virtualsmartcard. # This file is part of virtualsmartcard.
# #
# virtualsmartcard is free software: you can redistribute it and/or modify it # virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free # under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any # Software Foundation, either version 3 of the License, or (at your option) any
# later version. # later version.
# #
# virtualsmartcard is distributed in the hope that it will be useful, but # virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. # more details.
# #
# 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/>.
# #
@@ -20,39 +20,41 @@
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
tag = ord(data[0]) constructed = (ord(data[0]) & 0x20) != 0
tag = ord(data[0])
data = data[1:] data = data[1:]
if (tag & 0x1F) == 0x1F: if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | ord(data[0])
@@ -60,114 +62,117 @@ def tlv_unpack(data):
data = data[1:] data = data[1:]
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | ord(data[0])
data = data[1:] data = data[1:]
length = ord(data[0]) length = ord(data[0])
if length < 0x80: if length < 0x80:
data = data[1:] data = data[1:]
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_
value = data[:length] value = data[:length]
rest = data[length:] rest = data[length:]
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:
return return
find_recursive(tlv_data) find_recursive(tlv_data)
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):
value = pack(value, recalculate_length) value = pack(value, recalculate_length)
if recalculate_length: if recalculate_length:
length = len(value) length = len(value)
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:
l = chr(length) l = chr(length)
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)
result.append(value) result.append(value)
return "".join(result) return "".join(result)
def bertlv_pack(data): def bertlv_pack(data):
"""Packs a bertlv list of 3-tuples (tag, length, newvalue) into a string""" """Packs a bertlv list of 3-tuples (tag, length, newvalue) into a string"""
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
l = len(data) l = len(data)
ber_class, constructed, tag, length, value, data = tlv_unpack(data) ber_class, constructed, tag, length, value, data = tlv_unpack(data)
stop = offset + (l - len(data)) stop = offset + (l - len(data))
start = stop - length start = stop - length
if with_marks is not None: if with_marks is not None:
marks = [] marks = []
for type, mark_start, mark_stop in with_marks: for type, mark_start, mark_stop in with_marks:
@@ -176,24 +181,25 @@ def unpack(data, with_marks = None, offset = 0, include_filler=False):
marks = (marks, ) marks = (marks, )
else: else:
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
return result return result
def bertlv_unpack(data): def bertlv_unpack(data):
"""Unpacks a bertlv coded string into a list of 3-tuples (tag, length, """Unpacks a bertlv coded string into a list of 3-tuples (tag, length,
newvalue).""" newvalue)."""
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,12 +216,13 @@ 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
def simpletlv_unpack(data): def simpletlv_unpack(data):
"""Unpacks a simpletlv coded string into a list of 3-tuples (tag, length, """Unpacks a simpletlv coded string into a list of 3-tuples (tag, length,
newvalue).""" newvalue)."""
result = [] result = []
@@ -227,38 +234,39 @@ 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
def decodeTagList(tlv_data): def decodeTagList(tlv_data):
taglist = [] taglist = []
for (t, l, data) in tlv_find_tag(tlv_data, TAG["TAG_LIST"]): for (t, l, data) in tlv_find_tag(tlv_data, TAG["TAG_LIST"]):
while data != "": while data != "":
tag = ord(data[0]) tag = ord(data[0])
data = data[1:] data = data[1:]
if (tag & 0x1F) == 0x1F: if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | ord(data[0])
@@ -270,11 +278,11 @@ def decodeTagList(tlv_data):
return taglist return taglist
def decodeHeaderList(tlv_data): def decodeHeaderList(tlv_data):
headerlist = [] headerlist = []
for (t, l, data) in tlv_find_tag(tlv_data, TAG["HEADER_LIST"]): for (t, l, data) in tlv_find_tag(tlv_data, TAG["HEADER_LIST"]):
while data != "": while data != "":
tag = ord(data[0]) tag = ord(data[0])
data = data[1:] data = data[1:]
if (tag & 0x1F) == 0x1F: if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | ord(data[0])
@@ -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_
@@ -298,20 +306,21 @@ def decodeHeaderList(tlv_data):
return headerlist return headerlist
def decodeExtendedHeaderList(tlv_data): def decodeExtendedHeaderList(tlv_data):
# TODO # TODO
return [] return []
def encodebertlvDatalist(tag, datalist): def encodebertlvDatalist(tag, datalist):
tlvlist = [] tlvlist = []
for data in datalist: for data in 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):
return encodebertlvDatalist(TAG["OFFSET_DATA"], datalist)
def encodeDataOffsetObjects(datalist):
return encodebertlvDatalist(TAG["OFFSET_DATA"], datalist)

View File

@@ -1,38 +1,42 @@
# #
# Copyright (C) 2011 Frank Morgner, Dominik Oepen # Copyright (C) 2011 Frank Morgner, Dominik Oepen
# #
# This file is part of virtualsmartcard. # This file is part of virtualsmartcard.
# #
# virtualsmartcard is free software: you can redistribute it and/or modify it # virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free # under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any # Software Foundation, either version 3 of the License, or (at your option) any
# later version. # later version.
# #
# virtualsmartcard is distributed in the hope that it will be useful, but # virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. # more details.
# #
# 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 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"""
def getATR(self): def getATR(self):
"""Returns the ATR of the card as string of characters""" """Returns the ATR of the card as string of characters"""
return "" return ""
def powerUp(self): def powerUp(self):
"""Powers up the card""" """Powers up the card"""
pass pass
@@ -47,24 +51,21 @@ class SmartcardOS(object):
def execute(self, msg): def execute(self, msg):
"""Returns response to the given APDU as string of characters """Returns response to the given APDU as string of characters
:param msg: the APDU as string of characters :param msg: the APDU as string of characters
""" """
return "" return ""
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,33 +112,35 @@ 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
@staticmethod @staticmethod
def makeATR(**args): def makeATR(**args):
"""Calculate Answer to Reset (ATR) and returns the bitstring. """Calculate Answer to Reset (ATR) and returns the bitstring.
- directConvention (bool): Whether to use direct convention or - directConvention (bool): Whether to use direct convention or
inverse convention. inverse convention.
- TAi, TBi, TCi (optional): Value between 0 and 0xff. Interface - TAi, TBi, TCi (optional): Value between 0 and 0xff. Interface
Characters (for meaning see ISO 7816-3). Note that Characters (for meaning see ISO 7816-3). Note that
if no transmission protocol is given, it is if no transmission protocol is given, it is
automatically selected with T=max{j-1|TAj in args automatically selected with T=max{j-1|TAj in args
OR TBj in args OR TCj in args}. OR TBj in args OR TCj in args}.
- T (optional): Value between 0 and 15. Transmission Protocol. - T (optional): Value between 0 and 15. Transmission Protocol.
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.
""" """
# first byte TS # first byte TS
@@ -145,8 +148,8 @@ class Iso7816OS(SmartcardOS):
atr = "\x3b" atr = "\x3b"
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,59 +158,62 @@ 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
if maxTD == 0 and T > 0: if maxTD == 0 and T > 0:
maxTD = 2 maxTD = 2
# 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 "TA" + str(i+1) in args:
args["TD"+str(i)] |= 1 << 4
if "TB" + str(i+1) in args:
args["TD"+str(i)] |= 1 << 5
if "TC" + str(i+1) in args:
args["TD"+str(i)] |= 1 << 6
if args.has_key("TA" + str(i+1)):
args["TD"+str(i)] |= 1<<4
if args.has_key("TB" + str(i+1)):
args["TD"+str(i)] |= 1<<5
if args.has_key("TC" + str(i+1)):
args["TD"+str(i)] |= 1<<6
# initialize checksum # initialize checksum
TCK = 0 TCK = 0
# add TDi, TAi, TBi and TCi to ATR (TD0 is actually T0) # add TDi, TAi, TBi and TCi to ATR (TD0 is actually T0)
for i in range(0, maxTD+1): for i in range(0, maxTD+1):
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:
atr += "%c" % TCK atr += "%c" % TCK
return atr return atr
@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,15 +224,14 @@ 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:
@@ -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
@@ -266,8 +272,8 @@ class Iso7816OS(SmartcardOS):
def execute(self, msg): def execute(self, msg):
def notImplemented(*argz, **args): def notImplemented(*argz, **args):
""" """
If an application tries to use a function which is not implemented If an application tries to use a function which is not implemented
by the currently emulated smartcard we raise an exception which by the currently emulated smartcard we raise an exception which
should result in an appropriate response APDU being passed to the should result in an appropriate response APDU being passed to the
application. application.
""" """
@@ -277,67 +283,71 @@ 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"
elif (secure_messaging == 0x01): elif (secure_messaging == 0x01):
SM_STATUS = "Standard SM" SM_STATUS = "Standard SM"
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:
if SM_STATUS == "Standard SM" or SM_STATUS == "Proprietary SM": if SM_STATUS == "Standard SM" or SM_STATUS == "Proprietary SM":
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,20 +365,19 @@ 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):
""" """
This class is responsible for maintaining the communication of the virtual This class is responsible for maintaining the communication of the virtual
PCD and the emulated smartcard. vpicc and vpcd communicate via a socket. PCD and the emulated smartcard. vpicc and vpcd communicate via a socket.
@@ -376,42 +385,50 @@ class VirtualICC(object):
vicc. The vicc passes these CAPDUs on to an emulated smartcard, which vicc. The vicc passes these CAPDUs on to an emulated smartcard, which
produces a response APDU. This RAPDU is then passed back by the vicc to produces a response APDU. This RAPDU is then passed back by the vicc to
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,15 +466,16 @@ 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)
signal.signal(signal.SIGINT, self.signalHandler) signal.signal(signal.SIGINT, self.signalHandler)
atexit.register(self.stop) atexit.register(self.stop)
def signalHandler(self, sig=None, frame=None): def signalHandler(self, sig=None, frame=None):
"""Basically this signal handler just surpresses a traceback from being """Basically this signal handler just surpresses a traceback from being
printed when the user presses crtl-c""" printed when the user presses crtl-c"""
@@ -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

@@ -1,41 +1,33 @@
# #
# Copyright (C) 2006 Henryk Ploetz # Copyright (C) 2006 Henryk Ploetz
# #
# This file is part of virtualsmartcard. # This file is part of virtualsmartcard.
# #
# virtualsmartcard is free software: you can redistribute it and/or modify it # virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free # under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any # Software Foundation, either version 3 of the License, or (at your option) any
# later version. # later version.
# #
# virtualsmartcard is distributed in the hope that it will be useful, but # virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. # more details.
# #
# 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):
#i = len(str) - 1 def stringtoint(str):
#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,43 +45,50 @@ 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):
"""Generates a nice hexdump of data and returns it. Consecutive lines will
be indented with indent spaces. When short is true, will instead generate def hexdump(data, indent=0, short=False, linelen=16, offset=0):
"""Generates a nice hexdump of data and returns it. Consecutive lines will
be indented with indent spaces. When short is true, will instead generate
hexdump without adresses and on one line. hexdump without adresses and on one line.
Examples: Examples:
hexdump('\x00\x41') -> \ hexdump('\x00\x41') -> \
'0000: 00 41 .A ' '0000: 00 41 .A '
hexdump('\x00\x41', short=True) -> '00 41 (.A)'""" hexdump('\x00\x41', short=True) -> '00 41 (.A)'"""
def hexable(data): def hexable(data):
return " ".join([binascii.b2a_hex(a) for a in data]) return " ".join([binascii.b2a_hex(a) for a in data])
def printable(data): def printable(data):
return "".join([e in _myprintable and e or "." for e in data]) return "".join([e in _myprintable and e or "." for e in data])
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,33 +98,36 @@ 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)
lgth = ord(segment[0]) lgth = ord(segment[0])
aid = segment[1:1+lgth] aid = segment[1:1+lgth]
lifecycle = ord(segment[1+lgth]) lifecycle = ord(segment[1+lgth])
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,39 +136,43 @@ 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"
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!
The keyword arguments can then be used to override those values. The keyword arguments can then be used to override those values.
Keywords recognized are: Keywords recognized are:
- C_APDU: cla, ins, p1, p2, lc, le, data - C_APDU: cla, ins, p1, p2, lc, le, data
- R_APDU: sw, sw1, sw2, data - R_APDU: sw, sw1, sw2, data
""" """
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:
@@ -179,103 +185,115 @@ class APDU(object):
initbuff.append(elem) initbuff.append(elem)
else: else:
initbuff.append(arg) initbuff.append(arg)
for (index, value) in enumerate(initbuff): for (index, value) in enumerate(initbuff):
t = type(value) t = type(value)
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:
setattr(self, name, value) setattr(self, name, value)
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" % (
len(self.data), len(self.data) len(self.data), len(self.data)
) )
return result + ":\n" + hexdump(self.data) return result + ":\n" + hexdump(self.data)
else: else:
return result return result
def __repr__(self): def __repr__(self):
parts = self._format_fields() parts = self._format_fields()
if len(self.data) > 0: if len(self.data) > 0:
parts.append("data=%r" % self.data) parts.append("data=%r" % self.data)
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,21 +306,28 @@ 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
INS = _make_byte_property("INS"); ins = INS CLA = _make_byte_property("CLA")
P1 = _make_byte_property("P1"); p1 = P1 cla = CLA
P2 = _make_byte_property("P2"); p2 = P2 INS = _make_byte_property("INS")
Lc = _make_byte_property("Lc"); lc = Lc ins = INS
Le = _make_byte_property("Le"); le = Le P1 = _make_byte_property("P1")
p1 = P1
P2 = _make_byte_property("P2")
p2 = P2
Lc = _make_byte_property("Lc")
lc = Lc
Le = _make_byte_property("Le")
le = Le
@property @property
def effective_Le(self): def effective_Le(self):
if hasattr(self, "_Le") and (self.Le == 0): if hasattr(self, "_Le") and (self.Le == 0):
if self.__extended_length: if self.__extended_length:
return MAX_EXTENDED_LE return MAX_EXTENDED_LE
else: else:
return MAX_SHORT_LE return MAX_SHORT_LE
else: else:
@@ -312,32 +337,34 @@ 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)
def render(self): def render(self):
"Return this APDU as a binary string" "Return this APDU as a binary string"
buffer = [] buffer = []
for i in self.CLA, self.INS, self.P1, self.P2: for i in self.CLA, self.INS, self.P1, self.P2:
buffer.append(chr(i)) buffer.append(chr(i))
if len(self.data) > 0: if len(self.data) > 0:
buffer.append(chr(self.Lc)) buffer.append(chr(self.Lc))
buffer.append(self.data) buffer.append(self.data)
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))
return "".join(buffer) return "".join(buffer)
def case(self): def case(self):
"Return 1, 2, 3 or 4, depending on which ISO case we represent." "Return 1, 2, 3 or 4, depending on which ISO case we represent."
if self.Lc == 0: if self.Lc == 0:
@@ -351,34 +378,38 @@ class C_APDU(APDU):
else: else:
return 4 return 4
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")
self.SW1 = value[0] self.SW1 = value[0]
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]
def _format_fields(self): def _format_fields(self):
fields = ["SW1", "SW2"] fields = ["SW1", "SW2"]
return self._format_parts(fields) return self._format_parts(fields)
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