Merge pull request #74 from d0/pep8

Refactor code according to PEP8

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,7 @@ import unittest
from virtualsmartcard.CardGenerator import CardGenerator
class ISO7816GeneratorTest(unittest.TestCase):
card_type = 'iso7816'
@@ -44,7 +45,7 @@ class ISO7816GeneratorTest(unittest.TestCase):
def test_load_card_from_file(self):
self.card_generator.generateCard()
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.loadCard(self.filename)
mf, sam = local_generator.getCard()
@@ -59,21 +60,30 @@ class ISO7816GeneratorTest(unittest.TestCase):
def test_get_and_set_card(self):
self.card_generator.generateCard()
mf, sam = self.card_generator.getCard()
local_generator= CardGenerator(self.card_type)
local_generator = CardGenerator(self.card_type)
local_generator.setCard(mf, sam)
class TestNPACardGenerator(ISO7816GeneratorTest):
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):
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)
mf, sam = self.card_generator.getCard()
self.assertIsNotNone(mf)
self.assertIsNotNone(sam)
class CryptoflexGeneratorTest(ISO7816GeneratorTest):
card_type = 'cryptoflex'
@@ -81,7 +91,7 @@ class CryptoflexGeneratorTest(ISO7816GeneratorTest):
# Not tested because an ePass card currently cannot be generated without user
# interaction.
#
#class ePassGeneratorTest(ISO7816GeneratorTest):
# class ePassGeneratorTest(ISO7816GeneratorTest):
#
# card_type = 'ePass'

View File

@@ -20,20 +20,28 @@
import unittest
from virtualsmartcard.CryptoUtils import *
class TestCryptoUtils(unittest.TestCase):
def setUp(self):
self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsgohsav8wress"
self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsg"\
"ohsav8wress"
self.testpass = "SomeRandomPassphrase"
# The following string was generated using the proteced string method and
# is used as regression test.
# The following string was generated using the proteced string method
# and is used as a regression test.
# The data generated by protect_string should actually consist of
# printable characters only but that would break backwards
# 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.cryptedWord = "$p5k2$$POcwYIHr$SPaWqD3NpmLZc6gXbeybnAoCxo7Oc//K"
self.cryptedWordThousandIterations = "$p5k2$3e8$POcwYIHr$f/mEOCulo6v7Nq2ooS3480xTet6zdGbI"
self.cryptedWordThousandIterations = "$p5k2$3e8$POcwYIHr$f/mEOCulo6v7"\
"Nq2ooS3480xTet6zdGbI"
def test_padding(self):
padded = append_padding(16, self.teststring)
@@ -42,11 +50,13 @@ class TestCryptoUtils(unittest.TestCase):
def test_protect_string(self):
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)
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)
def test_crypt(self):

View File

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

View File

@@ -21,13 +21,14 @@
import unittest
from virtualsmartcard.utils import C_APDU, R_APDU, hexdump
class TestUtils(unittest.TestCase):
def test_CAPDU(self):
a = C_APDU(1, 2, 3, 4) # case 1
b = C_APDU(1, 2, 3, 4, 5) # case 2
c = C_APDU((1, 2, 3), cla = 0x23, data = "hallo") # case 3
d = C_APDU(1, 2, 3, 4, 2, 4, 6, 0) # case 4
a = C_APDU(1, 2, 3, 4) # case 1
b = C_APDU(1, 2, 3, 4, 5) # case 2
c = C_APDU((1, 2, 3), cla=0x23, data="hallo") # case 3
d = C_APDU(1, 2, 3, 4, 2, 4, 6, 0) # case 4
print()
print(a)
@@ -44,15 +45,41 @@ class TestUtils(unittest.TestCase):
for i in a, b, c, d:
print(hexdump(i.render()))
# case 2 extended length
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(g)
print(repr(g))
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(h)
print(repr(h))
@@ -71,4 +98,3 @@ class TestUtils(unittest.TestCase):
print()
for i in e, f:
print(hexdump(i.render()))

View File

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