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

@@ -53,12 +53,18 @@ 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_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
@@ -102,13 +108,16 @@ SM_Class = {}
# '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)
# '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)
# '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)
# '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)
@@ -124,12 +133,14 @@ 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)
# '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)
# '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
@@ -142,7 +153,8 @@ SM_Class["DIGITAL_SIGNATURE"] = 0x9E
# 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)
SM_Class["AUTH_CRT"] = 0xA4
@@ -150,18 +162,22 @@ SM_Class["AUTH_CRT_ODD"] = 0xA5
# Control reference template for key agreement (KAT)
SM_Class["KEY_AGREEMENT_CRT"] = 0xA6
SM_Class["KEY_AGREEMENT_CRT_ODD"] = 0xA7
#Input template for the verification of a digital signature (the template is signed)
# Input template for the verification of a digital signature (the template is
# signed)
SM_Class[0xA8] = "SIGNATURE_VERIFICATION_TEMPLATE"
# Control reference template for hash-code (HT)
SM_Class["HASH_CRT"] = 0xAA
SM_Class["HASH_CRT_ODD"] = 0xAB
#Input template for the computation of a digital signature (the concatenated value fields are signed)
# 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
@@ -179,10 +195,12 @@ SM_Class["CONFIDENTIALITY_CRT_ODD"] = 0xB9
# Response descriptor template
SM_Class["RESPONSE_DESCRIPTOR_TEMPLATE"] = 0xBA
SM_Class["RESPONSE_DESCRIPTOR_TEMPLATE_ODD"] = 0xBB
#Input template for the computation of a digital signature (the template is signed)
# 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
# }}}
@@ -226,4 +244,3 @@ REF = {
"REFERENCE_CONTROL_RECORD": 0x07,
"REFERENCE_CONTROL_SELECT": 0x03,
}

View File

@@ -16,14 +16,14 @@
# 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)
@@ -37,24 +37,31 @@ except ImportError:
CYBERFLEX_IV = '\x00' * 8
## *******************************************************************
## * Generic methods *
## *******************************************************************
# *******************************************************************
# * 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,15 +71,18 @@ 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
# 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":
@@ -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
@@ -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,6 +171,7 @@ def crypto_checksum(algo, key, data, iv=None, ssc=None):
return checksum
def cipher(do_encrypt, cipherspec, key, data, iv=None):
"""Do a cryptographic operation.
operation = do_encrypt ? encrypt : decrypt,
@@ -172,22 +188,26 @@ def cipher(do_encrypt, cipherspec, key, data, iv = None):
del cipher
return result
def encrypt(cipherspec, key, data, iv=None):
return cipher(True, cipherspec, key, data, iv)
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
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,6 +216,7 @@ 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
@@ -205,7 +226,7 @@ def protect_string(string, password, cipherspec=None):
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)
@@ -213,7 +234,7 @@ def protect_string(string, password, cipherspec=None):
raise ValueError
# Encrypt the string, authenticate and format it
if cipherspec == None:
if cipherspec is None:
cipherspec = "AES-CBC"
else:
pass # TODO: Sanity check for cipher
@@ -225,16 +246,18 @@ 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
#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:]
@@ -253,15 +276,16 @@ def read_protected_string(string, password, cipherspec=None):
raise ValueError("Failed to authenticate data. Wrong password?")
# Decrypt data
if cipherspec == None:
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,11 +293,11 @@ 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:]
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,30 +15,32 @@
#
# 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
"""
@@ -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]
@@ -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 \
@@ -227,10 +230,12 @@ class Security_Environment(object):
# 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)
@@ -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
@@ -287,9 +291,11 @@ 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 = ""
@@ -307,7 +313,8 @@ class Security_Environment(object):
# 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))
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"]):
@@ -330,7 +337,8 @@ class Security_Environment(object):
# 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))
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.
@@ -339,14 +347,15 @@ class Security_Environment(object):
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')
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
@@ -372,9 +381,9 @@ class Security_Environment(object):
# 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 = vsCrypto.append_padding(self.cct.blocklength,
to_authenticate)
checksum = self.compute_cryptographic_checksum(0x8E, 0x80,
auth)
if checksum != value:
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
@@ -389,26 +398,28 @@ class Security_Environment(object):
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
# Form unprotected CAPDU
if cla == None:
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
# 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):
@@ -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)])
@@ -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,10 +525,10 @@ 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 = ""
@@ -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,7 +604,7 @@ class Security_Environment(object):
to_sign = ""
signature = ""
if key == None:
if key is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
structure = unpack(data)
@@ -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

View File

@@ -67,63 +67,106 @@ SW = {
SW_MESSAGES = {
0x9000: 'Normal processing (No further qualification)',
0x6200 : 'Warning processing (State of non-volatile memory is unchanged): No information given',
0x6281 : 'Warning processing (State of non-volatile memory is unchanged): Part of returned data may be corrupted',
0x6282 : 'Warning processing (State of non-volatile memory is unchanged): End of file or record reached before reading Ne bytes',
0x6283 : 'Warning processing (State of non-volatile memory is unchanged): Selected file deactivated',
0x6284 : 'Warning processing (State of non-volatile memory is unchanged): File control information not formatted according to 5.3.3',
0x6285 : 'Warning processing (State of non-volatile memory is unchanged): Selected file in termination state',
0x6286 : 'Warning processing (State of non-volatile memory is unchanged): No input data available from a sensor on the card',
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',
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)',
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

@@ -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,8 +108,9 @@ 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
@@ -165,18 +168,17 @@ def get_indexes(items, reference=REF["IDENTIFIER_FIRST"], index_current=0):
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,9 +198,11 @@ 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)) * " "
@@ -219,15 +223,21 @@ 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)")
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,
@@ -237,8 +247,9 @@ class File(object):
"""
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)
@@ -385,13 +398,16 @@ class File(object):
raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"])
class DF(File):
"""Class for a dedicated file"""
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"],
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=""):
"""
@@ -450,9 +466,11 @@ 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)
@@ -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,12 +506,16 @@ 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.")
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):
@@ -504,7 +529,6 @@ class MF(DF):
self.firstSFT = inttostring(MF.makeFirstSoftwareFunctionTable(), 1)
self.secondSFT = inttostring(MF.makeSecondSoftwareFunctionTable(), 1)
@staticmethod
def makeFirstSoftwareFunctionTable(
DFSelectionByFullDFName=True, DFSelectionByPartialDFName=True,
@@ -534,7 +558,6 @@ class MF(DF):
fsft |= 1
return fsft
@staticmethod
def makeSecondSoftwareFunctionTable(DCB=DCB["ONETIMEWRITE"] | 1):
"""
@@ -543,7 +566,6 @@ class MF(DF):
"""
return DCB
def currentDF(self):
"""Returns the current DF."""
if isinstance(self.current, EF):
@@ -599,8 +621,10 @@ class MF(DF):
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?
@@ -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
@@ -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,8 +1003,10 @@ 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"],
if reference not in [REF["IDENTIFIER_FIRST"],
REF["IDENTIFIER_LAST"],
REF["IDENTIFIER_NEXT"],
REF["IDENTIFIER_PREVIOUS"],
REF["NUMBER"]]:
# RFU
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
@@ -995,8 +1023,10 @@ 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"],
if reference not in [REF["IDENTIFIER_FIRST"],
REF["IDENTIFIER_LAST"],
REF["IDENTIFIER_NEXT"],
REF["IDENTIFIER_PREVIOUS"],
REF["NUMBER"]]:
# RFU
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
@@ -1019,25 +1049,31 @@ class MF(DF):
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],
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],
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],
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],
ef.writerecord(num_id, reference,
decodeOffsetDataObjects(tlv_data)[0],
decodeDiscretionaryDataObjects(tlv_data)[0],
DCB["PROPRIETARY"])
@@ -1085,12 +1121,12 @@ 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
@@ -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
@@ -1159,7 +1196,7 @@ class MF(DF):
# Otherwise, P1-P2 is a file identifier.
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):
"""
@@ -1242,26 +1285,33 @@ class MF(DF):
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["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"]])
[TAG["FILECONTROLINFORMATION"],
TAG["FILECONTROLPARAMETERS"]])
if not fcp_list:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
@@ -1272,10 +1322,12 @@ class MF(DF):
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
@@ -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,16 +1377,18 @@ 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,
@@ -1358,11 +1412,12 @@ class EF(File):
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"],
def __init__(self, parent, fid,
filedescriptor=FDB["EFSTRUCTURE_TRANSPARENT"],
lifecycle=LCB["ACTIVATED"],
simpletlv_data=None, bertlv_data=None,
datacoding=DCB["ONETIMEWRITE"], shortfid=0, data=""):
@@ -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.")
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,12 +1506,15 @@ 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).")
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,
@@ -1477,8 +1536,8 @@ class RecordStructureEF(EF):
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()
@@ -1504,7 +1563,8 @@ class RecordStructureEF(EF):
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)
@@ -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,7 +59,8 @@ 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
@@ -74,7 +77,7 @@ class SAM(object):
self.cardSecret = urandom(keylen)
else:
if len(cardSecret) != keylen:
raise ValueError("cardSecret has the wrong key length for: " +\
raise ValueError("cardSecret has the wrong key length for: " +
get_referenced_cipher(self.cipher))
else:
self.cardSecret = cardSecret
@@ -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
@@ -229,7 +231,7 @@ class SAM(object):
reference = vsCrypto.encrypt(cipher, key, reference)
if(reference == data):
# Invalidate last challenge
self.last_challenge = None
self.last_challenge is None
return SW["NORMAL"], ""
else:
raise SwError(SW["WARN_NOINFO63"])
@@ -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
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)
@@ -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
"""
@@ -315,16 +318,15 @@ class SAM(object):
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):
else:
# Interpret qualifier as an short fid (try to read the key from FS)
if self.mf == None:
if self.mf is None:
raise SwError(SW["ERR_REFNOTUSABLE"])
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"])
@@ -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

@@ -49,9 +49,11 @@ 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:
@@ -84,13 +86,14 @@ def tlv_find_tags(tlv_data, tags, num_results = None):
results will be returned."""
results = []
def find_recursive(tlv_data):
for d in tlv_data:
t, l, v = d[:3]
if t in tags:
results.append(d)
else:
if isinstance(v, list): # FIXME Refactor the whole TLV code into a class
if isinstance(v, list):
find_recursive(v)
if num_results is not None and len(results) >= num_results:
@@ -102,8 +105,10 @@ def tlv_find_tags(tlv_data, tags, num_results = None):
def tlv_find_tag(tlv_data, tag, num_results=None):
"""Find (and return) all instances of tag in the given tlv structure (as returned by unpack).
If num_results is specified then at most that many results will be returned."""
"""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)
@@ -180,7 +185,8 @@ def unpack(data, with_marks = None, offset = 0, include_filler=False):
if not constructed:
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
@@ -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
@@ -238,14 +245,15 @@ def simpletlv_unpack(data):
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,
@@ -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"""
@@ -62,9 +66,6 @@ class Iso7816OS(SmartcardOS):
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,10 +112,12 @@ class Iso7816OS(SmartcardOS):
self.lastCommandOffcut = ""
self.lastCommandSW = SW["NORMAL"]
card_capabilities = self.mf.firstSFT + self.mf.secondSFT + \
Iso7816OS.makeThirdSoftwareFunctionTable(extendedLe = extended_length)
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)) +
histChars=chr(0x80) +
chr(0x70 + len(card_capabilities)) +
card_capabilities)
def getATR(self):
@@ -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,7 +169,7 @@ 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
@@ -173,11 +177,11 @@ class Iso7816OS(SmartcardOS):
if i < maxTD:
args["TD"+str(i)] |= 1 << 7
if args.has_key("TA" + str(i+1)):
if "TA" + str(i+1) in args:
args["TD"+str(i)] |= 1 << 4
if args.has_key("TB" + str(i+1)):
if "TB" + str(i+1) in args:
args["TD"+str(i)] |= 1 << 5
if args.has_key("TC" + str(i+1)):
if "TC" + str(i+1) in args:
args["TD"+str(i)] |= 1 << 6
# initialize checksum
@@ -188,13 +192,13 @@ 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])
@@ -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.
@@ -227,7 +233,6 @@ class Iso7816OS(SmartcardOS):
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,7 +283,8 @@ 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))
@@ -312,8 +319,8 @@ class Iso7816OS(SmartcardOS):
header_authentication = 1
# If Bit 8,7 == 01 then further industry values are used
elif (class_byte & 0x0C == 0x0C):
#Bit 1 to 4 specify logical channel. 4 is added, value range is from
#four to nineteen
# 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
@@ -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,19 +365,18 @@ 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_RESET = 2
VPCD_CTRL_ATR = 4
class VirtualICC(object):
"""
This class is responsible for maintaining the communication of the virtual
@@ -378,7 +387,11 @@ 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,
@@ -389,7 +402,7 @@ class VirtualICC(object):
self.cardGenerator = CardGenerator(card_type)
# If a filename is specified, try to load the card from disk
if filename != None:
if filename is not None:
self.filename = filename
if exists(filename):
self.cardGenerator.loadCard(self.filename)
@@ -398,7 +411,7 @@ class VirtualICC(object):
self.filename)
# If a dataset file is specified, read the card's data groups from disk
if datasetfile != None:
if datasetfile is not None:
if exists(datasetfile):
logging.info("Reading Data Groups from file %s.",
datasetfile)
@@ -411,7 +424,11 @@ class VirtualICC(object):
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,8 +439,8 @@ 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
@@ -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)
@@ -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'
@@ -62,18 +63,27 @@ class ISO7816GeneratorTest(unittest.TestCase):
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'

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,7 +20,7 @@
import unittest
from virtualsmartcard.SmartcardSAM import *
#Unit Tests
class TestSmartcardSAM(unittest.TestCase):
def setUp(self):
@@ -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
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
# The API should be changed so that decipher() 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
# 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"])

View File

@@ -21,6 +21,7 @@
import unittest
from virtualsmartcard.utils import C_APDU, R_APDU, hexdump
class TestUtils(unittest.TestCase):
def test_CAPDU(self):
@@ -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,6 +45,8 @@ def inttostring(i, length=None):
_myprintable = " " + string.letters + string.digits + string.punctuation
def hexdump(data, indent=0, short=False, linelen=16, offset=0):
"""Generates a nice hexdump of data and returns it. Consecutive lines will
be indented with indent spaces. When short is true, will instead generate
@@ -79,18 +73,23 @@ def hexdump(data, indent = 0, short = False, linelen = 16, offset = 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",
0x07: "Card manager = Initialized; Applet instance / security "
"domain = Selectable",
0x0F: "Card manager = Secured; Applet instance / security "
"domain = Personalized",
0x7F: "Card manager = Locked; Applet instance / security "
"domain = Blocked",
0xFF: "Applet instance = Locked"}
def parse_status(data):
"""Parses the Response APDU of a GetStatus command."""
def parse_segment(segment):
@@ -124,8 +123,11 @@ def parse_status(data):
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("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,10 +136,12 @@ 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),
@@ -145,14 +149,16 @@ def _make_byte_property(prop):
lambda self: delattr(self, "_"+prop),
"The %s attribute of the APDU" % prop)
class APDU(object):
"Base class for an APDU"
def __init__(self, *args, **kwargs):
"""Creates a new APDU instance. Can be given positional parameters which
must be sequences of either strings (or strings themselves) or integers
specifying byte values that will be concatenated in order. Alternatively
you may give exactly one positional argument that is an APDU instance.
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!
@@ -185,7 +191,8 @@ 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)
@@ -195,28 +202,32 @@ 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")
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__"
@@ -228,7 +239,8 @@ class APDU(object):
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,12 +258,15 @@ 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
@@ -270,12 +285,15 @@ class C_APDU(APDU):
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)
@@ -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")
@@ -366,11 +395,14 @@ class R_APDU(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