added more Python 3 compatibility

fixes https://github.com/frankmorgner/vsmartcard/issues/71
This commit is contained in:
Frank Morgner
2018-05-03 16:30:08 +02:00
parent ae0d04f8b7
commit 3b7c04c535
15 changed files with 244 additions and 276 deletions

View File

@@ -58,8 +58,8 @@ class CardGenerator(object):
self.datagroups = {} self.datagroups = {}
def __generate_iso_card(self): def __generate_iso_card(self):
default_pin = "1234" default_pin = "1234".encode('ascii')
default_cardno = "1234567890" default_cardno = "1234567890".encode('ascii')
logging.warning("Using default SAM parameters. PIN=%s, Card Nr=%s" logging.warning("Using default SAM parameters. PIN=%s, Card Nr=%s"
% (default_pin, default_cardno)) % (default_pin, default_cardno))
@@ -603,10 +603,10 @@ class CardGenerator(object):
esign.append(TransparentStructureEF(parent=esign, fid=0xC001, data='')) esign.append(TransparentStructureEF(parent=esign, fid=0xC001, data=''))
self.mf.append(esign) self.mf.append(esign)
self.sam = nPA_SAM(eid_pin="111111", can="222222", self.sam = nPA_SAM(eid_pin="111111".encode('ascii'), can="222222".encode('ascii'),
mrz="IDD<<T220001293<<<<<<<<<<<<<<<6408125<2010315D" mrz="IDD<<T220001293<<<<<<<<<<<<<<<6408125<2010315D"
"<<<<<<<<<<<<<<MUSTERMANN<<ERIKA<<<<<<<<<<<<<", "<<<<<<<<<<<<<<MUSTERMANN<<ERIKA<<<<<<<<<<<<<".encode('ascii'),
puk="3333333333", qes_pin="444444", mf=self.mf) puk="3333333333".encode('ascii'), qes_pin="444444".encode('ascii'), mf=self.mf)
# FIXME: add CVCA for inspection systems and signature terminals. Here # FIXME: add CVCA for inspection systems and signature terminals. Here
# we only add the eID CVCA. # we only add the eID CVCA.
self.sam.current_SE.cvca = "\x7f\x21\x82\x01\xb6\x7f\x4e\x82\x01\x6e"\ self.sam.current_SE.cvca = "\x7f\x21\x82\x01\xb6\x7f\x4e\x82\x01\x6e"\

View File

@@ -34,7 +34,7 @@ except ImportError:
import hmac as HMAC import hmac as HMAC
import sha as SHA1 import sha as SHA1
CYBERFLEX_IV = '\x00' * 8 CYBERFLEX_IV = b'\x00' * 8
# ******************************************************************* # *******************************************************************
@@ -63,8 +63,10 @@ def get_cipher(cipherspec, key, iv=None):
(cipherparts[1], validModes)) (cipherparts[1], validModes))
cipher = None cipher = None
if iv is None: if cipherparts[1].upper() == "ECB":
cipher = c_class.new(key, mode, '\x00'*get_cipher_blocklen(cipherspec)) cipher = c_class.new(key, mode)
elif iv is None:
cipher = c_class.new(key, mode, b'\x00'*get_cipher_blocklen(cipherspec))
else: else:
cipher = c_class.new(key, mode, iv) cipher = c_class.new(key, mode, iv)
@@ -114,9 +116,9 @@ def append_padding(blocklen, data, padding_class=0x01):
last_block_length = len(data) % blocklen last_block_length = len(data) % blocklen
padding_length = blocklen - last_block_length padding_length = blocklen - last_block_length
if padding_length == 0: if padding_length == 0:
padding = '\x80' + '\x00' * (blocklen - 1) padding = b'\x80' + b'\x00' * (blocklen - 1)
else: else:
padding = '\x80' + '\x00' * (blocklen - last_block_length - 1) padding = b'\x80' + b'\x00' * (blocklen - last_block_length - 1)
return data + padding return data + padding
@@ -127,8 +129,11 @@ def strip_padding(blocklen, data, padding_class=0x01):
""" """
if padding_class == 0x01: if padding_class == 0x01:
tail = len(data) - 1 b = data
while data[tail] != '\x80': if isinstance(b, str):
b = map(ord, b)
tail = len(b) - 1
while b[tail] != 0x80:
tail = tail - 1 tail = tail - 1
return data[:tail] return data[:tail]

View File

@@ -158,10 +158,10 @@ class ControlReferenceTemplate:
if position < len(self.__config_string): # Replace Tag if position < len(self.__config_string): # Replace Tag
length = stringtoint(self.__config_string[position+1]) length = stringtoint(self.__config_string[position+1])
self.__config_string = self.__config_string[:position] +\ self.__config_string = self.__config_string[:position] +\
chr(tag) + inttostring(len(data)) + data +\ inttostring(tag) + inttostring(len(data)) + data +\
self.__config_string[position+2+length:] self.__config_string[position+2+length:]
else: # Add new tag else: # Add new tag
self.__config_string += chr(tag) + inttostring(len(data)) + data self.__config_string += inttostring(tag) + inttostring(len(data)) + data
def to_string(self): def to_string(self):
""" """

View File

@@ -138,14 +138,14 @@ def write(old, newlist, offsets, datacoding, maxsize=None):
resultindex = offset + newindex resultindex = offset + newindex
while newindex < writenow: while newindex < writenow:
if datacoding == DCB["WRITEOR"]: if datacoding == DCB["WRITEOR"]:
newpiece = chr( newpiece = inttostring(
ord(result[resultindex]) | ord(new[newindex])) ord(result[resultindex]) | ord(new[newindex]))
elif datacoding == DCB["WRITEAND"]: elif datacoding == DCB["WRITEAND"]:
newpiece = chr( newpiece = inttostring(
ord(result[resultindex]) & ord(new[newindex])) ord(result[resultindex]) & ord(new[newindex]))
elif datacoding == DCB["PROPRIETARY"]: elif datacoding == DCB["PROPRIETARY"]:
# we use it for XOR # we use it for XOR
newpiece = chr( newpiece = inttostring(
ord(result[resultindex]) ^ ord(new[newindex])) ord(result[resultindex]) ^ ord(new[newindex]))
result = (result[0:resultindex] + newpiece + result = (result[0:resultindex] + newpiece +
result[resultindex+1:len(result)]) result[resultindex+1:len(result)])
@@ -614,24 +614,24 @@ class MF(DF):
The result is not prepended with tag and length for neither TCP, FMD The result is not prepended with tag and length for neither TCP, FMD
nor FCI template. nor FCI template.
""" """
fdm = [chr(TAG["FILEIDENTIFIER"]) + "\x02" + inttostring(file.fid, 2), fdm = [inttostring(TAG["FILEIDENTIFIER"]) + b"\x02" + inttostring(file.fid, 2),
chr(TAG["LIFECYCLESTATUS"]) + "\x01" + chr(file.lifecycle)] inttostring(TAG["LIFECYCLESTATUS"]) + b"\x01" + inttostring(file.lifecycle)]
fdm.append(file.extra_fci_data) fdm.append(file.extra_fci_data)
# TODO filesize and data objects # TODO filesize and data objects
if isinstance(file, EF): if isinstance(file, EF):
if hasattr(file, 'shortfid'): if hasattr(file, 'shortfid'):
fdm.append("%c\x01%c" % (TAG["SHORTFID"], file.shortfid << 3)) fdm.append(b"%c\x01%c" % (TAG["SHORTFID"], file.shortfid << 3))
else: else:
fdm.append("%c\x00" % TAG["SHORTFID"]) fdm.append(b"%c\x00" % TAG["SHORTFID"])
if isinstance(file, TransparentStructureEF): if isinstance(file, TransparentStructureEF):
l = inttostring(len(file.data), 2, True) l = inttostring(len(file.data), 2, True)
fdm.append("%c%c%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"], fdm.append(b"%c%c%s" % (TAG["BYTES_EXCLUDINGSTRUCTURE"],
chr(len(l)), l)) inttostring(len(l)), l))
fdm.append("%c%c%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"], fdm.append(b"%c%c%s" % (TAG["BYTES_INCLUDINGSTRUCTURE"],
chr(len(l)), l)) inttostring(len(l)), l))
fdm.append("%c\x02%c%c" % (TAG["FILEDISCRIPTORBYTE"], fdm.append(b"%c\x02%c%c" % (TAG["FILEDISCRIPTORBYTE"],
file.filedescriptor, file.datacoding)) file.filedescriptor, file.datacoding))
elif isinstance(file, RecordStructureEF): elif isinstance(file, RecordStructureEF):
@@ -664,7 +664,7 @@ class MF(DF):
else: else:
raise TypeError raise TypeError
return "".join(fdm) return b"".join(fdm)
def _selectFile(self, p1, p2, data): def _selectFile(self, p1, p2, data):
""" """
@@ -735,10 +735,10 @@ class MF(DF):
file = self._selectFile(p1, p2, data) file = self._selectFile(p1, p2, data)
if p2 == P2_NONE: if p2 == P2_NONE:
data = "" data = b""
elif p2 == P2_FMD: elif p2 == P2_FMD:
# TODO # TODO
data = "" data = b""
else: else:
if p2 == P2_FCP: if p2 == P2_FCP:
tag = TAG["FILECONTROLPARAMETERS"] tag = TAG["FILECONTROLPARAMETERS"]
@@ -846,7 +846,7 @@ class MF(DF):
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
ef.writebinary(offsets, datalist) ef.writebinary(offsets, datalist)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def writeBinaryEncapsulated(self, p1, p2, data): def writeBinaryEncapsulated(self, p1, p2, data):
""" """
@@ -857,7 +857,7 @@ class MF(DF):
ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data) ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data)
ef.writebinary(offsets, datalist) ef.writebinary(offsets, datalist)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def updateBinaryPlain(self, p1, p2, data): def updateBinaryPlain(self, p1, p2, data):
""" """
@@ -870,7 +870,7 @@ class MF(DF):
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
ef.updatebinary(offsets, datalist) ef.updatebinary(offsets, datalist)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def updateBinaryEncapsulated(self, p1, p2, data): def updateBinaryEncapsulated(self, p1, p2, data):
""" """
@@ -883,7 +883,7 @@ class MF(DF):
ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data) ef, offsets, datalist = self.dataUnitsDecodeEncapsulated(p1, p2, data)
ef.updatebinary(offsets, datalist) ef.updatebinary(offsets, datalist)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def searchBinaryPlain(self, p1, p2, data): def searchBinaryPlain(self, p1, p2, data):
ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data)
@@ -929,7 +929,7 @@ class MF(DF):
eraseto = None eraseto = None
ef.erasebinary(erasefrom, eraseto) ef.erasebinary(erasefrom, eraseto)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def eraseBinaryEncapsulated(self, p1, p2, data): def eraseBinaryEncapsulated(self, p1, p2, data):
""" """
@@ -959,7 +959,7 @@ class MF(DF):
erasefrom = None erasefrom = None
ef.erasebinary(erasefrom, eraseto) ef.erasebinary(erasefrom, eraseto)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def recordHandlingDecode(self, p1, p2): def recordHandlingDecode(self, p1, p2):
""" """
@@ -1002,7 +1002,7 @@ class MF(DF):
ef, num_id, reference = self.recordHandlingDecode(p1, p2) ef, num_id, reference = self.recordHandlingDecode(p1, p2)
result = ef.readrecord(0, num_id, reference) result = ef.readrecord(0, num_id, reference)
r = "" r = b""
for item in result: for item in result:
r += item r += item
@@ -1039,7 +1039,7 @@ class MF(DF):
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
ef.writerecord(num_id, reference, 1, data) ef.writerecord(num_id, reference, 1, data)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def updateRecordPlain(self, p1, p2, data): def updateRecordPlain(self, p1, p2, data):
""" """
@@ -1059,7 +1059,7 @@ class MF(DF):
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
ef.updaterecord(num_id, reference, 0, data) ef.updaterecord(num_id, reference, 0, data)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def updateRecordEncapsulated(self, p1, p2, data): def updateRecordEncapsulated(self, p1, p2, data):
""" """
@@ -1104,7 +1104,7 @@ class MF(DF):
decodeDiscretionaryDataObjects(tlv_data)[0], decodeDiscretionaryDataObjects(tlv_data)[0],
DCB["PROPRIETARY"]) DCB["PROPRIETARY"])
return SW["NORMAL"], "" return SW["NORMAL"], b""
def appendRecord(self, p1, p2, data): def appendRecord(self, p1, p2, data):
""" """
@@ -1119,7 +1119,7 @@ class MF(DF):
ef, num_id, reference = self.recordHandlingDecode(p1, p2) ef, num_id, reference = self.recordHandlingDecode(p1, p2)
sw = ef.appendrecord(data) sw = ef.appendrecord(data)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def eraseRecord(self, p1, p2, data): def eraseRecord(self, p1, p2, data):
""" """
@@ -1137,7 +1137,7 @@ class MF(DF):
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
ef.eraserecord(num_id, reference) ef.eraserecord(num_id, reference)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def dataObjectHandlingDecodePlain(self, p1, p2, data): def dataObjectHandlingDecodePlain(self, p1, p2, data):
""" """
@@ -1280,7 +1280,7 @@ class MF(DF):
data) data)
file.putdata(isSimpleTlv, tlvlist) file.putdata(isSimpleTlv, tlvlist)
return SW["NORMAL"], "" return SW["NORMAL"], b""
def putDataEncapsulated(self, p1, p2, data): def putDataEncapsulated(self, p1, p2, data):
""" """
@@ -1293,7 +1293,7 @@ class MF(DF):
file, tlvlist = self.dataObjectHandlingDecodeEncapsulated(p1, p2, data) file, tlvlist = self.dataObjectHandlingDecodeEncapsulated(p1, p2, data)
file.putdata(False, tlvlist) file.putdata(False, tlvlist)
return SW["NORMAL"], "" return SW["NORMAL"], b""
@staticmethod @staticmethod
def create(p1, p2, data): def create(p1, p2, data):
@@ -1331,9 +1331,9 @@ class MF(DF):
TAG["SHORTFID"]: 'shortfid2args(value, args)', TAG["SHORTFID"]: 'shortfid2args(value, args)',
TAG["LIFECYCLESTATUS"]: 'args["lifecycle"] = ' + \ TAG["LIFECYCLESTATUS"]: 'args["lifecycle"] = ' + \
'stringtoint(value)', 'stringtoint(value)',
TAG["BYTES_EXCLUDINGSTRUCTURE"]: 'args["data"] = chr(0) ' + \ TAG["BYTES_EXCLUDINGSTRUCTURE"]: 'args["data"] = bytes(0) ' + \
'* stringtoint(value)', '* stringtoint(value)',
TAG["BYTES_INCLUDINGSTRUCTURE"]: 'args["data"] = chr(0) ' + \ TAG["BYTES_INCLUDINGSTRUCTURE"]: 'args["data"] = bytes(0) ' + \
'* stringtoint(value)', '* stringtoint(value)',
} }
fcp_list = tlv_find_tags(bertlv_unpack(data), fcp_list = tlv_find_tags(bertlv_unpack(data),
@@ -1392,7 +1392,7 @@ class MF(DF):
df.append(file) df.append(file)
self.current = file self.current = file
return SW["NORMAL"], "" return SW["NORMAL"], b""
def deleteFile(self, p1, p2, data): def deleteFile(self, p1, p2, data):
""" """
@@ -1407,7 +1407,7 @@ class MF(DF):
# FIXME: free memory of file and remove its content from the security # FIXME: free memory of file and remove its content from the security
# device # device
return SW["NORMAL"], "" return SW["NORMAL"], b""
class EF(File): class EF(File):
@@ -1738,7 +1738,7 @@ class RecordStructureEF(EF):
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
if self.hasFixedRecordSize(): if self.hasFixedRecordSize():
data = chr(0)*(self.maxrecordsize) + data data = bytes(0)*(self.maxrecordsize) + data
records = self.records records = self.records
if self.isCyclic(): if self.isCyclic():
@@ -1756,6 +1756,6 @@ class RecordStructureEF(EF):
""" """
records = self.__getRecords(num_id, reference) records = self.__getRecords(num_id, reference)
for r in records: for r in records:
r.data = "" r.data = b""
r.identifier = None r.identifier = None
return SW["NORMAL"] return SW["NORMAL"]

View File

@@ -167,7 +167,7 @@ class SAM(object):
""" """
logging.debug("Received PIN: %s", PIN.strip()) logging.debug("Received PIN: %s", PIN.strip())
PIN = PIN.replace("\0", "") # Strip NULL characters PIN = PIN.replace(b"\0", b"") # Strip NULL characters
if p1 != 0x00: if p1 != 0x00:
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])

View File

@@ -51,26 +51,26 @@ TAG["EXTENDED_HEADER_LIST"] = 0x4D
def tlv_unpack(data): def tlv_unpack(data):
ber_class = (ord(data[0]) & 0xC0) >> 6 ber_class = (data[0] & 0xC0) >> 6
# 0 = primitive, 0x20 = constructed # 0 = primitive, 0x20 = constructed
constructed = (ord(data[0]) & 0x20) != 0 constructed = (data[0] & 0x20) != 0
tag = ord(data[0]) tag = data[0]
data = data[1:] data = data[1:]
if (tag & 0x1F) == 0x1F: if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | data[0]
while ord(data[0]) & 0x80 == 0x80: while data[0] & 0x80 == 0x80:
data = data[1:] data = data[1:]
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | data[0]
data = data[1:] data = data[1:]
length = ord(data[0]) length = data[0]
if length < 0x80: if length < 0x80:
data = data[1:] data = data[1:]
elif length & 0x80 == 0x80: elif length & 0x80 == 0x80:
length_ = 0 length_ = 0
data = data[1:] data = data[1:]
for i in range(0, length & 0x7F): for i in range(0, length & 0x7F):
length_ = length_ * 256 + ord(data[0]) length_ = length_ * 256 + data[0]
data = data[1:] data = data[1:]
length = length_ length = length_
@@ -157,13 +157,15 @@ def bertlv_pack(data):
def unpack(data, with_marks=None, offset=0, include_filler=False): def unpack(data, with_marks=None, offset=0, include_filler=False):
result = [] result = []
if isinstance(data, str):
data = map(ord, data)
while len(data) > 0: while len(data) > 0:
if ord(data[0]) in (0x00, 0xFF): if data[0] in (0x00, 0xFF):
if include_filler: if include_filler:
if with_marks is None: if with_marks is None:
result.append((ord(data[0]), None, None)) result.append((data[0], None, None))
else: else:
result.append((ord(data[0]), None, None, ())) result.append((data[0], None, None, ()))
data = data[1:] data = data[1:]
offset = offset + 1 offset = offset + 1
continue continue
@@ -226,15 +228,17 @@ def simpletlv_unpack(data):
"""Unpacks a simpletlv coded string into a list of 3-tuples (tag, length, """Unpacks a simpletlv coded string into a list of 3-tuples (tag, length,
newvalue).""" newvalue)."""
result = [] result = []
if isinstance(data, str):
data = map(ord, data)
rest = data rest = data
while rest != '': while rest:
tag = ord(rest[0]) tag = rest[0]
if tag == 0 or tag == 0xff: if tag == 0 or tag == 0xff:
raise ValueError raise ValueError
length = ord(rest[1]) length = rest[1]
if length == 0xff: if length == 0xff:
length = (ord(rest[2]) << 8) + ord(rest[3]) length = (rest[2] << 8) + rest[3]
newvalue = rest[4:4+length] newvalue = rest[4:4+length]
rest = rest[4+length:] rest = rest[4+length:]
else: else:
@@ -265,14 +269,14 @@ def decodeOffsetDataObjects(tlv_data):
def decodeTagList(tlv_data): def decodeTagList(tlv_data):
taglist = [] taglist = []
for (t, l, data) in tlv_find_tag(tlv_data, TAG["TAG_LIST"]): for (t, l, data) in tlv_find_tag(tlv_data, TAG["TAG_LIST"]):
while data != "": while data:
tag = ord(data[0]) tag = data[0]
data = data[1:] data = data[1:]
if (tag & 0x1F) == 0x1F: if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | data[0]
while ord(data[0]) & 0x80 == 0x80: while data[0] & 0x80 == 0x80:
data = data[1:] data = data[1:]
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | data[0]
data = data[1:] data = data[1:]
taglist.append((tag, 0)) taglist.append((tag, 0))
return taglist return taglist
@@ -281,24 +285,24 @@ def decodeTagList(tlv_data):
def decodeHeaderList(tlv_data): def decodeHeaderList(tlv_data):
headerlist = [] headerlist = []
for (t, l, data) in tlv_find_tag(tlv_data, TAG["HEADER_LIST"]): for (t, l, data) in tlv_find_tag(tlv_data, TAG["HEADER_LIST"]):
while data != "": while data:
tag = ord(data[0]) tag = data[0]
data = data[1:] data = data[1:]
if (tag & 0x1F) == 0x1F: if (tag & 0x1F) == 0x1F:
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | data[0]
while ord(data[0]) & 0x80 == 0x80: while data[0] & 0x80 == 0x80:
data = data[1:] data = data[1:]
tag = (tag << 8) | ord(data[0]) tag = (tag << 8) | data[0]
data = data[1:] data = data[1:]
length = ord(data[0]) length = data[0]
if length < 0x80: if length < 0x80:
data = data[1:] data = data[1:]
elif length & 0x80 == 0x80: elif length & 0x80 == 0x80:
length_ = 0 length_ = 0
data = data[1:] data = data[1:]
for i in range(0, length & 0x7F): for i in range(0, length & 0x7F):
length_ = length_ * 256 + ord(data[0]) length_ = length_ * 256 + data[0]
data = data[1:] data = data[1:]
length = length_ length = length_

View File

@@ -32,10 +32,9 @@ from virtualsmartcard.CardGenerator import CardGenerator
class SmartcardOS(object): class SmartcardOS(object):
"""Base class for a smart card OS""" """Base class for a smart card OS"""
def getATR(self): def getATR(self):
"""Returns the ATR of the card as string of characters""" """Returns the ATR of the card as string of characters"""
return "" return b""
def powerUp(self): def powerUp(self):
"""Powers up the card""" """Powers up the card"""
@@ -54,7 +53,7 @@ class SmartcardOS(object):
:param msg: the APDU as string of characters :param msg: the APDU as string of characters
""" """
return "" return b""
class Iso7816OS(SmartcardOS): class Iso7816OS(SmartcardOS):
@@ -110,14 +109,14 @@ class Iso7816OS(SmartcardOS):
else: else:
self.maxle = MAX_SHORT_LE self.maxle = MAX_SHORT_LE
self.lastCommandOffcut = "" self.lastCommandOffcut = b""
self.lastCommandSW = SW["NORMAL"] self.lastCommandSW = SW["NORMAL"]
el = extended_length # only needed to keep following line short el = extended_length # only needed to keep following line short
tsft = Iso7816OS.makeThirdSoftwareFunctionTable(extendedLe=el) tsft = Iso7816OS.makeThirdSoftwareFunctionTable(extendedLe=el)
card_capabilities = self.mf.firstSFT + self.mf.secondSFT + tsft card_capabilities = self.mf.firstSFT + self.mf.secondSFT + tsft
self.atr = Iso7816OS.makeATR(T=1, directConvention=True, TA1=0x13, self.atr = Iso7816OS.makeATR(T=1, directConvention=True, TA1=0x13,
histChars=chr(0x80) + histChars=inttostring(0x80) +
chr(0x70 + len(card_capabilities)) + inttostring(0x70 + len(card_capabilities)) +
card_capabilities) card_capabilities)
def getATR(self): def getATR(self):
@@ -145,9 +144,9 @@ class Iso7816OS(SmartcardOS):
""" """
# first byte TS # first byte TS
if args["directConvention"]: if args["directConvention"]:
atr = "\x3b" atr = b"\x3b"
else: else:
atr = "\x3f" atr = b"\x3f"
if "T" in args: if "T" in args:
T = args["T"] T = args["T"]
@@ -189,11 +188,11 @@ class Iso7816OS(SmartcardOS):
# add TDi, TAi, TBi and TCi to ATR (TD0 is actually T0) # add TDi, TAi, TBi and TCi to ATR (TD0 is actually T0)
for i in range(0, maxTD+1): for i in range(0, maxTD+1):
atr = atr + "%c" % args["TD" + str(i)] atr = atr + b"%c" % args["TD" + str(i)]
TCK ^= args["TD" + str(i)] TCK ^= args["TD" + str(i)]
for j in ["A", "B", "C"]: for j in ["A", "B", "C"]:
if "T" + j + str(i+1) in args: if "T" + j + str(i+1) in args:
atr += "%c" % args["T" + j + str(i+1)] atr += b"%c" % args["T" + j + str(i+1)]
# calculate checksum for all bytes from T0 to the end # calculate checksum for all bytes from T0 to the end
TCK ^= args["T" + j + str(i+1)] TCK ^= args["T" + j + str(i+1)]
@@ -201,11 +200,15 @@ class Iso7816OS(SmartcardOS):
if "histChars" in args: if "histChars" in args:
atr += args["histChars"] atr += args["histChars"]
for i in range(0, len(args["histChars"])): for i in range(0, len(args["histChars"])):
TCK ^= ord(args["histChars"][i]) byte = args["histChars"][i]
if isinstance(byte, str):
TCK ^= ord(byte)
else:
TCK ^= byte
# checksum is omitted for T=0 # checksum is omitted for T=0
if T > 0: if T > 0:
atr += "%c" % TCK atr += b"%c" % TCK
return atr return atr
@@ -283,7 +286,7 @@ class Iso7816OS(SmartcardOS):
c = C_APDU(msg) c = C_APDU(msg)
except ValueError as e: except ValueError as e:
logging.warning(str(e)) logging.warning(str(e))
return self.formatResult(False, 0, "", return self.formatResult(False, 0, b"",
SW["ERR_INCORRECTPARAMETERS"], False) SW["ERR_INCORRECTPARAMETERS"], False)
logging.info("Parsed APDU:\n%s", str(c)) logging.info("Parsed APDU:\n%s", str(c))
@@ -353,7 +356,7 @@ class Iso7816OS(SmartcardOS):
import traceback import traceback
traceback.print_exception(*sys.exc_info()) traceback.print_exception(*sys.exc_info())
sw = e.sw sw = e.sw
result = "" result = b""
answer = self.formatResult(False, 0, result, sw, sm) answer = self.formatResult(False, 0, result, sw, sm)
return answer return answer
@@ -454,7 +457,7 @@ class VirtualICC(object):
try: try:
local_ip = [(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1] local_ip = [(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]
custom_url = 'vicc://%s:%d' % (local_ip, port) custom_url = 'vicc://%s:%d' % (local_ip, port)
print('VICC hostname: %s' % local_ip); print('VICC hostname: %s' % local_ip)
print('VICC port: %d' % port) print('VICC port: %d' % port)
print('On your NFC phone with the Android Smart Card Emulator app scan this code:') print('On your NFC phone with the Android Smart Card Emulator app scan this code:')
try: try:
@@ -553,16 +556,16 @@ class VirtualICC(object):
logging.warning("Error in communication protocol (missing \ logging.warning("Error in communication protocol (missing \
size parameter)") size parameter)")
elif size == VPCD_CTRL_LEN: elif size == VPCD_CTRL_LEN:
if msg == chr(VPCD_CTRL_OFF): if msg == inttostring(VPCD_CTRL_OFF):
logging.info("Power Down") logging.info("Power Down")
self.os.powerDown() self.os.powerDown()
elif msg == chr(VPCD_CTRL_ON): elif msg == inttostring(VPCD_CTRL_ON):
logging.info("Power Up") logging.info("Power Up")
self.os.powerUp() self.os.powerUp()
elif msg == chr(VPCD_CTRL_RESET): elif msg == inttostring(VPCD_CTRL_RESET):
logging.info("Reset") logging.info("Reset")
self.os.reset() self.os.reset()
elif msg == chr(VPCD_CTRL_ATR): elif msg == inttostring(VPCD_CTRL_ATR):
self.__sendToVPICC(self.os.getATR()) self.__sendToVPICC(self.os.getATR())
else: else:
logging.warning("unknown control command") logging.warning("unknown control command")

View File

@@ -37,7 +37,7 @@ class HandlerTestOS(SmartcardOS):
def __output_from_le(self, msg): def __output_from_le(self, msg):
le = (ord(msg[2]) << 8) + ord(msg[3]) le = (ord(msg[2]) << 8) + ord(msg[3])
return ''.join([chr(num & 0xff) for num in xrange(le)]) return ''.join([bytes(num & 0xff) for num in xrange(le)])
def execute(self, msg): def execute(self, msg):
ok = '\x90\x00' ok = '\x90\x00'
@@ -67,7 +67,7 @@ class HandlerTestOS(SmartcardOS):
self.lastCommandOffcut = self.__output_from_le(msg) self.lastCommandOffcut = self.__output_from_le(msg)
if len(self.lastCommandOffcut) > 0xFF: if len(self.lastCommandOffcut) > 0xFF:
return '\x61\x00' return '\x61\x00'
return '' + chr(len(self.lastCommandOffcut) & 0xFF) return '' + bytes(len(self.lastCommandOffcut) & 0xFF)
elif len(msg) == 6+ord(msg[4]): elif len(msg) == 6+ord(msg[4]):
logging.info('Case 4, APDU') logging.info('Case 4, APDU')
return self.__output_from_le(msg) + ok return self.__output_from_le(msg) + ok

View File

@@ -83,7 +83,7 @@ class RelayOS(SmartcardOS):
logging.error("Error getting ATR: %s", e.message) logging.error("Error getting ATR: %s", e.message)
sys.exit() sys.exit()
return "".join([chr(b) for b in atr]) return b"".join([bytes(b) for b in atr])
def powerUp(self): def powerUp(self):
# When powerUp is called multiple times the session is valid (and the # When powerUp is called multiple times the session is valid (and the
@@ -131,4 +131,4 @@ class RelayOS(SmartcardOS):
rapdu = rapdu + [sw1, sw2] rapdu = rapdu + [sw1, sw2]
# return the response APDU as string # return the response APDU as string
return "".join([chr(b) for b in rapdu]) return "".join([bytes(b) for b in rapdu])

View File

@@ -198,7 +198,7 @@ class CryptoflexMF(MF): # {{{
"fid": stringtoint(data[4:6]), "fid": stringtoint(data[4:6]),
} }
if data[6] == "\x01": if data[6] == "\x01":
args["data"] = chr(0)*stringtoint(data[2:4]) args["data"] = bytes(0)*stringtoint(data[2:4])
args["filedescriptor"] = FDB["EFSTRUCTURE_TRANSPARENT"] args["filedescriptor"] = FDB["EFSTRUCTURE_TRANSPARENT"]
new_file = TransparentStructureEF(**args) new_file = TransparentStructureEF(**args)
elif data[6] == "\x02": elif data[6] == "\x02":
@@ -259,11 +259,11 @@ class CryptoflexMF(MF): # {{{
if isinstance(file, EF): if isinstance(file, EF):
# File size (body only) # File size (body only)
size = inttostring(min(0xffff, len(file.getenc('data'))), 2) size = inttostring(min(0xffff, len(file.getenc('data'))), 2)
extra = chr(0) # RFU extra = bytes(0) # RFU
if (isinstance(file, RecordStructureEF) and if (isinstance(file, RecordStructureEF) and
file.hasFixedRecordSize() and not file.isCyclic()): file.hasFixedRecordSize() and not file.isCyclic()):
# Length of records # Length of records
extra += chr(0) + chr(min(file.records, 0xff)) extra += bytes(0) + bytes(min(file.records, 0xff))
elif isinstance(file, DF): elif isinstance(file, DF):
# Number of unused EEPROM bytes available in the DF # Number of unused EEPROM bytes available in the DF
size = inttostring(0xffff, 2) size = inttostring(0xffff, 2)
@@ -285,27 +285,27 @@ class CryptoflexMF(MF): # {{{
if isinstance(f, DF): if isinstance(f, DF):
dfcount += 1 dfcount += 1
if chv1: if chv1:
extra = chr(1) # TODO LSB correct? extra = bytes(1) # TODO LSB correct?
else: else:
extra = chr(0) # TODO LSB correct? extra = bytes(0) # TODO LSB correct?
extra += chr(efcount) extra += bytes(efcount)
extra += chr(dfcount) extra += bytes(dfcount)
extra += chr(0) # TODO Number of PINs and unblock CHV PINs extra += bytes(0) # TODO Number of PINs and unblock CHV PINs
extra += chr(0) # RFU extra += bytes(0) # RFU
if chv1: if chv1:
extra += chr(0) # TODO remaining CHV1 attempts extra += bytes(0) # TODO remaining CHV1 attempts
extra += chr(0) # TODO remaining unblock CHV1 attempts extra += bytes(0) # TODO remaining unblock CHV1 attempts
if chv2: if chv2:
extra += chr(0) # TODO CHV2 key status extra += bytes(0) # TODO CHV2 key status
extra += chr(0) # TODO CHV2 unblocking key status extra += bytes(0) # TODO CHV2 unblocking key status
data = inttostring(0, 2) # RFU data = inttostring(0, 2) # RFU
data += size data += size
data += inttostring(file.fid, 2) data += inttostring(file.fid, 2)
data += inttostring(file.filedescriptor) # File type data += inttostring(file.filedescriptor) # File type
data += inttostring(0, 4) # ACs TODO data += inttostring(0, 4) # ACs TODO
data += chr(file.lifecycle & 1) # File status data += bytes(file.lifecycle & 1) # File status
data += chr(len(extra)) data += bytes(len(extra))
data += extra data += extra
self.current = file self.current = file

View File

@@ -48,12 +48,12 @@ class NPAOS(Iso7816OS):
# different ATR (Answer To Reset) values depending on used Chip version # different ATR (Answer To Reset) values depending on used Chip version
# It's just a playground, because in past one of all those eID clients # It's just a playground, because in past one of all those eID clients
# did not recognize the card correctly with newest ATR values # did not recognize the card correctly with newest ATR values
self.atr = '\x3B\x8A\x80\x01\x80\x31\xF8\x73\xF7\x41\xE0\x82\x90' + \ self.atr = b'\x3B\x8A\x80\x01\x80\x31\xF8\x73\xF7\x41\xE0\x82\x90' + \
'\x00\x75' b'\x00\x75'
# self.atr = '\x3B\x8A\x80\x01\x80\x31\xB8\x73\x84\x01\xE0\x82\x90' + \ # self.atr = b'\x3B\x8A\x80\x01\x80\x31\xB8\x73\x84\x01\xE0\x82\x90' + \
# '\x00\x06' # b'\x00\x06'
# self.atr = '\x3B\x88\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x09' # self.atr = b'\x3B\x88\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x09'
# self.atr = '\x3B\x87\x80\x01\x80\x31\xB8\x73\x84\x01\xE0\x19' # self.atr = b'\x3B\x87\x80\x01\x80\x31\xB8\x73\x84\x01\xE0\x19'
self.SAM.current_SE.disable_checks = disable_checks self.SAM.current_SE.disable_checks = disable_checks
if ef_cardsecurity: if ef_cardsecurity:
@@ -67,7 +67,7 @@ class NPAOS(Iso7816OS):
if ca_key: if ca_key:
self.SAM.current_SE.ca_key = ca_key self.SAM.current_SE.ca_key = ca_key
esign = self.mf.select('dfname', esign = self.mf.select('dfname',
'\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E') b'\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E')
if esign_ca_cert: if esign_ca_cert:
ef = esign.select('fid', 0xC000) ef = esign.select('fid', 0xC000)
ef.data = esign_ca_cert ef.data = esign_ca_cert
@@ -95,7 +95,7 @@ class NPAOS(Iso7816OS):
logging.info(e.message) logging.info(e.message)
traceback.print_exception(*sys.exc_info()) traceback.print_exception(*sys.exc_info())
sw = e.sw sw = e.sw
result = "" result = b""
answer = self.formatResult(False, 0, result, sw, False) answer = self.formatResult(False, 0, result, sw, False)
return R_APDU(result, inttostring(sw)).render() return R_APDU(result, inttostring(sw)).render()
@@ -116,22 +116,22 @@ class nPA_AT_CRT(ControlReferenceTemplate):
CommunityID = None CommunityID = None
def keyref_is_mrz(self): def keyref_is_mrz(self):
if self.keyref_secret_key == '%c' % self.PACE_MRZ: if self.keyref_secret_key == b'%c' % self.PACE_MRZ:
return True return True
return False return False
def keyref_is_can(self): def keyref_is_can(self):
if self.keyref_secret_key == '%c' % self.PACE_CAN: if self.keyref_secret_key == b'%c' % self.PACE_CAN:
return True return True
return False return False
def keyref_is_pin(self): def keyref_is_pin(self):
if self.keyref_secret_key == '%c' % self.PACE_PIN: if self.keyref_secret_key == b'%c' % self.PACE_PIN:
return True return True
return False return False
def keyref_is_puk(self): def keyref_is_puk(self):
if self.keyref_secret_key == '%c' % self.PACE_PUK: if self.keyref_secret_key == b'%c' % self.PACE_PUK:
return True return True
return False return False
@@ -174,7 +174,7 @@ class nPA_AT_CRT(ControlReferenceTemplate):
else: else:
raise SwError(SW["ERR_REFNOTUSABLE"]) raise SwError(SW["ERR_REFNOTUSABLE"])
return r, "" return r, b""
class nPA_SE(Security_Environment): class nPA_SE(Security_Environment):
@@ -201,10 +201,10 @@ class nPA_SE(Security_Environment):
if self.at.algorithm == "PACE": if self.at.algorithm == "PACE":
if self.at.keyref_is_pin(): if self.at.keyref_is_pin():
if self.sam.counter <= 0: if self.sam.counter <= 0:
print "Must use PUK to unblock" print("Must use PUK to unblock")
return 0x63c0, "" return 0x63c0, ""
if self.sam.counter == 1 and not self.sam.active: if self.sam.counter == 1 and not self.sam.active:
print "Must use CAN to activate" print("Must use CAN to activate")
return 0x63c1, "" return 0x63c1, ""
self.eac_step = 0 self.eac_step = 0
elif self.at.algorithm == "TA": elif self.at.algorithm == "TA":
@@ -233,10 +233,10 @@ class nPA_SE(Security_Environment):
elif self.eac_step == 6: elif self.eac_step == 6:
# TODO implement RI # TODO implement RI
# "\x7c\x22\x81\x20\" is some prefix and the rest is our RI # "\x7c\x22\x81\x20\" is some prefix and the rest is our RI
return SW["NORMAL"], "\x7c\x22\x81\x20\x48\x1e\x58\xd1\x7c\x12" + \ return SW["NORMAL"], b"\x7c\x22\x81\x20\x48\x1e\x58\xd1\x7c\x12" + \
"\x9a\x0a\xb4\x63\x7d\x43\xc7\xf7\xeb\x2b" + \ b"\x9a\x0a\xb4\x63\x7d\x43\xc7\xf7\xeb\x2b" + \
"\x06\x10\x6f\x26\x90\xe3\x00\xc4\xe7\x03" + \ b"\x06\x10\x6f\x26\x90\xe3\x00\xc4\xe7\x03" + \
"\x54\xa0\x41\xf0\xd3\x90" b"\x54\xa0\x41\xf0\xd3\x90"
raise SwError(SW["ERR_INCORRECTPARAMETERS"]) raise SwError(SW["ERR_INCORRECTPARAMETERS"])
@@ -268,11 +268,11 @@ class nPA_SE(Security_Environment):
self.PACE_SEC = PACE_SEC(self.sam.can, eac.PACE_CAN) self.PACE_SEC = PACE_SEC(self.sam.can, eac.PACE_CAN)
elif self.at.keyref_is_pin(): elif self.at.keyref_is_pin():
if self.sam.counter <= 0: if self.sam.counter <= 0:
print "Must use PUK to unblock" print("Must use PUK to unblock")
return 0x63c0, "" return 0x63c0, b""
if self.sam.counter == 1 and not self.sam.active: if self.sam.counter == 1 and not self.sam.active:
print "Must use CAN to activate" print("Must use CAN to activate")
return 0x63c1, "" return 0x63c1, b""
self.PACE_SEC = PACE_SEC(self.sam.eid_pin, eac.PACE_PIN) self.PACE_SEC = PACE_SEC(self.sam.eid_pin, eac.PACE_PIN)
self.sam.counter -= 1 self.sam.counter -= 1
if self.sam.counter <= 1: if self.sam.counter <= 1:
@@ -384,7 +384,7 @@ class nPA_SE(Security_Environment):
my_token = \ my_token = \
eac.PACE_STEP3D_compute_authentication_token(self.eac_ctx, eac.PACE_STEP3D_compute_authentication_token(self.eac_ctx,
self.pace_opp_pub_key) self.pace_opp_pub_key)
token = "" token = b""
for tag, length, value in tlv_data: for tag, length, value in tlv_data:
if tag == 0x85: if tag == 0x85:
token = value token = value
@@ -396,19 +396,19 @@ class nPA_SE(Security_Environment):
eac.print_ossl_err() eac.print_ossl_err()
raise SwError(SW["WARN_NOINFO63"]) raise SwError(SW["WARN_NOINFO63"])
print "Established PACE channel" print("Established PACE channel")
if self.at.keyref_is_can(): if self.at.keyref_is_can():
if (self.sam.counter == 1): if (self.sam.counter == 1):
self.sam.active = True self.sam.active = True
print "PIN resumed" print("PIN resumed")
elif self.at.keyref_is_pin(): elif self.at.keyref_is_pin():
self.sam.active = True self.sam.active = True
self.sam.counter = 3 self.sam.counter = 3
elif self.at.keyref_is_puk(): elif self.at.keyref_is_puk():
self.sam.active = True self.sam.active = True
self.sam.counter = 3 self.sam.counter = 3
print "PIN unblocked" print("PIN unblocked")
self.eac_step += 1 self.eac_step += 1
self.at.algorithm = "TA" self.at.algorithm = "TA"
@@ -449,7 +449,7 @@ class nPA_SE(Security_Environment):
self.eac_step += 1 self.eac_step += 1
print "Generated Nonce and Authentication Token for CA" print("Generated Nonce and Authentication Token for CA")
# TODO activate SM # TODO activate SM
self.new_encryption_ctx = eac.EAC_ID_CA self.new_encryption_ctx = eac.EAC_ID_CA
@@ -467,9 +467,9 @@ class nPA_SE(Security_Environment):
eac.print_ossl_err() eac.print_ossl_err()
raise SwError(SW["ERR_NOINFO69"]) raise SwError(SW["ERR_NOINFO69"])
print "Imported Certificate" print("Imported Certificate")
return "" return b""
def external_authenticate(self, p1, p2, data): def external_authenticate(self, p1, p2, data):
""" """
@@ -489,14 +489,14 @@ class nPA_SE(Security_Environment):
if 1 != eac.TA_STEP6_verify(self.eac_ctx, self.at.iv, id_picc, if 1 != eac.TA_STEP6_verify(self.eac_ctx, self.at.iv, id_picc,
auxiliary_data, data): auxiliary_data, data):
eac.print_ossl_err() eac.print_ossl_err()
print "Could not verify Terminal's signature" print("Could not verify Terminal's signature")
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
print "Terminal's signature verified" print("Terminal's signature verified")
self.eac_step += 1 self.eac_step += 1
return 0x9000, "" return 0x9000, b""
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
@@ -532,12 +532,12 @@ class nPA_SE(Security_Environment):
:returns: the protected data and the SW bytes :returns: the protected data and the SW bytes
""" """
return_data = "" return_data = b""
if result != "": if result:
# Encrypt the data included in the RAPDU # Encrypt the data included in the RAPDU
encrypted = self.encipher(0x82, 0x80, result) encrypted = self.encipher(0x82, 0x80, result)
encrypted = "\x01" + encrypted encrypted = b"\x01" + encrypted
encrypted_tlv = bertlv_pack([( encrypted_tlv = bertlv_pack([(
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"], SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"],
len(encrypted), len(encrypted),
@@ -570,11 +570,11 @@ class nPA_SE(Security_Environment):
def compute_digital_signature(self, p1, p2, data): def compute_digital_signature(self, p1, p2, data):
# TODO Signing with brainpoolP256r1 or any other key needs some more # TODO Signing with brainpoolP256r1 or any other key needs some more
# effort ;-) # effort ;-)
return '\x0D\xB2\x9B\xB9\x5E\x97\x7D\x42\x73\xCF\xA5\x45\xB7\xED' + \ return b'\x0D\xB2\x9B\xB9\x5E\x97\x7D\x42\x73\xCF\xA5\x45\xB7\xED' + \
'\x5C\x39\x3F\xCE\xCD\x4A\xDE\xDC\x2B\x85\x23\x9F\x66\x52' + \ b'\x5C\x39\x3F\xCE\xCD\x4A\xDE\xDC\x2B\x85\x23\x9F\x66\x52' + \
'\x10\xC2\x67\xDC\xA6\x35\x94\x2D\x24\xED\xEB\xC8\x34\x6C' + \ b'\x10\xC2\x67\xDC\xA6\x35\x94\x2D\x24\xED\xEB\xC8\x34\x6C' + \
'\x4B\xD1\xA1\x15\xB4\x48\x3A\xA4\x4A\xCE\xFF\xED\x97\x0E' + \ b'\x4B\xD1\xA1\x15\xB4\x48\x3A\xA4\x4A\xCE\xFF\xED\x97\x0E' + \
'\x07\xF3\x72\xF0\xFB\xA3\x62\x8C' b'\x07\xF3\x72\xF0\xFB\xA3\x62\x8C'
class nPA_SAM(SAM): class nPA_SAM(SAM):
@@ -602,14 +602,14 @@ class nPA_SAM(SAM):
# change secret # change secret
if p2 == self.current_SE.at.PACE_CAN: if p2 == self.current_SE.at.PACE_CAN:
self.can = data self.can = data
print "Changed CAN to %r" % self.can print("Changed CAN to %r" % self.can)
elif p2 == self.current_SE.at.PACE_PIN: elif p2 == self.current_SE.at.PACE_PIN:
# TODO: allow terminals to change the PIN with permission # TODO: allow terminals to change the PIN with permission
# "CAN allowed" # "CAN allowed"
if not self.current_SE.at.keyref_is_pin(): if not self.current_SE.at.keyref_is_pin():
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
self.eid_pin = data self.eid_pin = data
print "Changed PIN to %r" % self.eid_pin print("Changed PIN to %r" % self.eid_pin)
else: else:
raise SwError(SW["ERR_DATANOTFOUND"]) raise SwError(SW["ERR_DATANOTFOUND"])
elif p1 == 0x03: elif p1 == 0x03:
@@ -620,14 +620,14 @@ class nPA_SAM(SAM):
elif p2 == self.current_SE.at.PACE_PIN: elif p2 == self.current_SE.at.PACE_PIN:
if self.current_SE.at.keyref_is_can(): if self.current_SE.at.keyref_is_can():
self.active = True self.active = True
print "Resumed PIN" print("Resumed PIN")
elif self.current_SE.at.keyref_is_pin(): elif self.current_SE.at.keyref_is_pin():
# PACE was successful with PIN, nothing to do # PACE was successful with PIN, nothing to do
# resume/unblock # resume/unblock
pass pass
elif self.current_SE.at.keyref_is_puk(): elif self.current_SE.at.keyref_is_puk():
# TODO unblock PIN for signature # TODO unblock PIN for signature
print "Unblocked PIN" print("Unblocked PIN")
self.active = True self.active = True
self.counter = 3 self.counter = 3
else: else:
@@ -637,7 +637,7 @@ class nPA_SAM(SAM):
else: else:
raise SwError(SW["ERR_INCORRECTP1P2"]) raise SwError(SW["ERR_INCORRECTP1P2"])
return 0x9000, "" return 0x9000, b""
def external_authenticate(self, p1, p2, data): def external_authenticate(self, p1, p2, data):
return self.current_SE.external_authenticate(p1, p2, data) return self.current_SE.external_authenticate(p1, p2, data)
@@ -665,8 +665,8 @@ class nPA_SAM(SAM):
[(tag, _, value)] = structure = unpack(data) [(tag, _, value)] = structure = unpack(data)
if tag == 6: if tag == 6:
mapped_algo = ALGO_MAPPING[value] mapped_algo = ALGO_MAPPING[value]
eid = self.mf.select('dfname', '\xe8\x07\x04\x00\x7f\x00' eid = self.mf.select('dfname', b'\xe8\x07\x04\x00\x7f\x00'
'\x07\x03\x02') b'\x07\x03\x02')
if mapped_algo == "DateOfExpiry": if mapped_algo == "DateOfExpiry":
[(_, _, [(_, _, mine)])] = \ [(_, _, [(_, _, mine)])] = \
unpack(eid.select('fid', 0x0103).data) unpack(eid.select('fid', 0x0103).data)
@@ -708,11 +708,11 @@ class nPA_SAM(SAM):
str(self.current_SE.at.CommunityID)) str(self.current_SE.at.CommunityID))
if mine.startswith(self.current_SE.at.CommunityID): if mine.startswith(self.current_SE.at.CommunityID):
print("Community ID verified (living there)") print("Community ID verified (living there)")
return SW["NORMAL"], "" return SW["NORMAL"], b""
else: else:
print("Community ID not verified (not living" print("Community ID not verified (not living"
"there)") "there)")
return SW["WARN_NOINFO63"], "" return SW["WARN_NOINFO63"], b""
else: else:
return SwError(SW["ERR_DATANOTFOUND"]) return SwError(SW["ERR_DATANOTFOUND"])
else: else:

View File

@@ -24,8 +24,8 @@ from virtualsmartcard.CryptoUtils import *
class TestCryptoUtils(unittest.TestCase): class TestCryptoUtils(unittest.TestCase):
def setUp(self): def setUp(self):
self.teststring = "DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsg"\ self.teststring = b"DEADBEEFistatsyksdvhwohfwoehcowc8hw8rogfq8whv75tsg"\
"ohsav8wress" b"ohsav8wress"
def test_padding(self): def test_padding(self):
padded = append_padding(16, self.teststring) padded = append_padding(16, self.teststring)

View File

@@ -24,33 +24,33 @@ from virtualsmartcard.SmartcardSAM import *
class TestSmartcardSAM(unittest.TestCase): class TestSmartcardSAM(unittest.TestCase):
def setUp(self): def setUp(self):
self.password = "DUMMYKEYDUMMYKEY" self.password = "DUMMYKEYDUMMYKEY".encode('ascii')
self.myCard = SAM("1234", "1234567890") self.myCard = SAM("1234".encode('ascii'), "1234567890".encode('ascii'))
self.secEnv = Security_Environment(None, self.myCard) # TODO: Set CRTs self.secEnv = Security_Environment(None, self.myCard) # TODO: Set CRTs
self.secEnv.ht.algorithm = "SHA" self.secEnv.ht.algorithm = "SHA"
self.secEnv.ct.algorithm = "AES-CBC" self.secEnv.ct.algorithm = "AES-CBC"
def test_incorrect_pin(self): def test_incorrect_pin(self):
with self.assertRaises(SwError): with self.assertRaises(SwError):
self.myCard.verify(0x00, 0x00, "5678") self.myCard.verify(0x00, 0x00, "5678".encode('ascii'))
def test_counter_decrement(self): def test_counter_decrement(self):
ctr1 = self.myCard.counter ctr1 = self.myCard.counter
try: try:
self.myCard.verify(0x00, 0x00, "3456") self.myCard.verify(0x00, 0x00, "3456".encode('ascii'))
except SwError as e: except SwError as e:
pass pass
self.assertEquals(self.myCard.counter, ctr1 - 1) self.assertEquals(self.myCard.counter, ctr1 - 1)
def test_internal_authenticate(self): def test_internal_authenticate(self):
sw, challenge = self.myCard.get_challenge(0x00, 0x00, "") sw, challenge = self.myCard.get_challenge(0x00, 0x00, b"")
blocklen = vsCrypto.get_cipher_blocklen("DES3-ECB") blocklen = vsCrypto.get_cipher_blocklen("DES3-ECB")
padded = vsCrypto.append_padding(blocklen, challenge) padded = vsCrypto.append_padding(blocklen, challenge)
sw, result_data = self.myCard.internal_authenticate(0x00, 0x00, padded) sw, result_data = self.myCard.internal_authenticate(0x00, 0x00, padded)
self.assertEquals(sw, SW["NORMAL"]) self.assertEquals(sw, SW["NORMAL"])
def test_external_authenticate(self): def test_external_authenticate(self):
sw, challenge = self.myCard.get_challenge(0x00, 0x00, "") sw, challenge = self.myCard.get_challenge(0x00, 0x00, b"")
blocklen = vsCrypto.get_cipher_blocklen("DES3-ECB") blocklen = vsCrypto.get_cipher_blocklen("DES3-ECB")
padded = vsCrypto.append_padding(blocklen, challenge) padded = vsCrypto.append_padding(blocklen, challenge)
sw, result_data = self.myCard.internal_authenticate(0x00, 0x00, padded) sw, result_data = self.myCard.internal_authenticate(0x00, 0x00, padded)
@@ -73,7 +73,7 @@ class TestSmartcardSAM(unittest.TestCase):
# 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.ct.algorithm = "RSA"
self.secEnv.dst.keylength = 1024 self.secEnv.dst.keylength = 1024
sw, pk = self.secEnv.generate_public_key_pair(0x00, 0x00, "") sw, pk = self.secEnv.generate_public_key_pair(0x00, 0x00, b"")
self.assertEquals(sw, SW["NORMAL"]) self.assertEquals(sw, SW["NORMAL"])

View File

@@ -27,7 +27,7 @@ class TestUtils(unittest.TestCase):
def test_CAPDU(self): def test_CAPDU(self):
a = C_APDU(1, 2, 3, 4) # case 1 a = C_APDU(1, 2, 3, 4) # case 1
b = C_APDU(1, 2, 3, 4, 5) # case 2 b = C_APDU(1, 2, 3, 4, 5) # case 2
c = C_APDU((1, 2, 3), cla=0x23, data="hallo") # case 3 c = C_APDU((1, 2, 3), cla=0x23, data=b"hallo") # case 3
d = C_APDU(1, 2, 3, 4, 2, 4, 6, 0) # case 4 d = C_APDU(1, 2, 3, 4, 2, 4, 6, 0) # case 4
print() print()
@@ -54,39 +54,39 @@ class TestUtils(unittest.TestCase):
print(repr(g)) print(repr(g))
print(hexdump(g.render())) print(hexdump(g.render()))
h = C_APDU('\x0c\x2a\x00\xbe\x00\x01\x5f\x87\x82\x01\x51\x01\xf0\xa2' h = C_APDU(b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\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' b'\xc0\x84\x23\x62\x22\x6b\xae\x39\xa2\x8f\x55\xac\x8e\x08'
'\x34\x46\xcc\x83\xf9\x9d\x2a\x75\x00\x00') b'\x34\x46\xcc\x83\xf9\x9d\x2a\x75\x00\x00')
print() print()
print(h) print(h)
print(repr(h)) print(repr(h))
def test_RAPDU(self): def test_RAPDU(self):
e = R_APDU(0x90, 0) e = R_APDU(0x90, 0)
f = R_APDU("foo\x67\x00") f = R_APDU(b"foo\x67\x00")
print() print()
print(e) print(e)

View File

@@ -18,6 +18,7 @@
# #
import binascii import binascii
import string import string
import struct
from virtualsmartcard.ConstantDefinitions import MAX_SHORT_LE, MAX_EXTENDED_LE from virtualsmartcard.ConstantDefinitions import MAX_SHORT_LE, MAX_EXTENDED_LE
@@ -28,23 +29,24 @@ def stringtoint(str):
def inttostring(i, length=None, len_extendable=False): def inttostring(i, length=None, len_extendable=False):
str = "%x" % i str = b""
if len(str) % 2 == 0: while True:
str = str.decode('hex') str = struct.pack('B', i & 0xFF) + str
else: i = i >> 8
str = ("0"+str).decode('hex') if i <= 0:
break
if length: if length:
l = len(str) l = len(str)
if l > length and not len_extendable: if l > length and not len_extendable:
raise ValueError("i too big for the specified stringlength") raise ValueError("i too big for the specified stringlength")
else: else:
str = chr(0)*(length-l) + str str = b'\x00'*(length-l) + str
return str return str
_myprintable = " " + string.ascii_letters + string.digits + string.punctuation _myprintable = list(" " + string.ascii_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):
@@ -53,12 +55,14 @@ def hexdump(data, indent=0, short=False, linelen=16, offset=0):
hexdump without adresses and on one line. hexdump without adresses and on one line.
Examples: Examples:
hexdump('\x00\x41') -> \ hexdump(b'\x00\x41') -> \
'0000: 00 41 .A ' '0000: 00 41 .A '
hexdump('\x00\x41', short=True) -> '00 41 (.A)'""" hexdump(b'\x00\x41', short=True) -> '00 41 (.A)'"""
def hexable(data): def hexable(data):
return " ".join([binascii.b2a_hex(a) for a in data]) if isinstance(data, str):
data = map(ord, data)
return " ".join(map("{0:0>2X}".format, data))
def printable(data): def printable(data):
return "".join([e in _myprintable and e or "." for e in data]) return "".join([e in _myprintable and e or "." for e in data])
@@ -66,6 +70,8 @@ def hexdump(data, indent=0, short=False, linelen=16, offset=0):
if short: if short:
return "%s (%s)" % (hexable(data), printable(data)) return "%s (%s)" % (hexable(data), printable(data))
if isinstance(data, str):
data = map(ord, data)
FORMATSTRING = "%04x: %-" + str(linelen*3) + "s %-" + str(linelen) + "s" FORMATSTRING = "%04x: %-" + str(linelen*3) + "s %-" + str(linelen) + "s"
result = "" result = ""
(head, tail) = (data[:linelen], data[linelen:]) (head, tail) = (data[:linelen], data[linelen:])
@@ -90,58 +96,6 @@ LIFE_CYCLES = {0x01: "Load file = loaded",
0xFF: "Applet instance = Locked"} 0xFF: "Applet instance = Locked"}
def parse_status(data):
"""Parses the Response APDU of a GetStatus command."""
def parse_segment(segment):
def parse_privileges(privileges):
if privileges == 0x0:
return "N/A"
else:
privs = []
if privileges & (1 << 7):
privs.append("security domain")
if privileges & (1 << 6):
privs.append("DAP DES verification")
if privileges & (1 << 5):
privs.append("delegated management")
if privileges & (1 << 4):
privs.append("card locking")
if privileges & (1 << 3):
privs.append("card termination")
if privileges & (1 << 2):
privs.append("default selected")
if privileges & (1 << 1):
privs.append("global PIN modification")
if privileges & (1 << 0):
privs.append("mandated DAP verification")
return ", ".join(privs)
lgth = ord(segment[0])
aid = segment[1:1+lgth]
lifecycle = ord(segment[1+lgth])
privileges = ord(segment[1+lgth+1])
print("aid length: %i (%x)" % (lgth, lgth))
print("aid: %s" % hexdump(aid, indent=18, short=True))
print("life cycle state: %x (%s)" %
(lifecycle, LIFE_CYCLES.get(lifecycle,
"unknown or invalid state")))
print("privileges: %x (%s)\n" %
(privileges, parse_privileges(privileges)))
pos = 0
while pos < len(data):
lgth = ord(data[pos])+3
segment = data[pos:pos+lgth]
parse_segment(segment)
pos = pos + lgth
def _unformat_hexdump(dump):
hexdump = " ".join([line[7:54] for line in dump.splitlines()])
return binascii.a2b_hex("".join([e != " " and e or "" for e in hexdump]))
def _make_byte_property(prop): def _make_byte_property(prop):
"Make a byte property(). This is meta code." "Make a byte property(). This is meta code."
return property(lambda self: getattr(self, "_"+prop, None), return property(lambda self: getattr(self, "_"+prop, None),
@@ -205,9 +159,11 @@ class APDU(object):
def _setdata(self, value): def _setdata(self, value):
if isinstance(value, str): if isinstance(value, str):
self._data = "".join([e for e in value]) self._data = b"".join([e for e in value])
elif isinstance(value, list): elif isinstance(value, list):
self._data = "".join([chr(int(e)) for e in value]) self._data = b"".join([inttostring(int(e)) for e in value])
elif isinstance(value, bytes):
self._data = value
else: else:
raise ValueError("'data' attribute can only be a str or a list of " raise ValueError("'data' attribute can only be a str or a list of "
"int, not %s" % type(value)) "int, not %s" % type(value))
@@ -265,8 +221,8 @@ class C_APDU(APDU):
def parse(self, apdu): def parse(self, apdu):
"""Parse a full command APDU and assign the values to our object, """Parse a full command APDU and assign the values to our object,
overwriting whatever there was.""" overwriting whatever there was."""
apdu = map(lambda a: (isinstance(a, str) and (ord(a),) or apdu = list(map(lambda a: (isinstance(a, str) and (ord(a),) or
(a,))[0], apdu) (a,))[0], apdu))
apdu = apdu + [0] * max(4-len(apdu), 0) apdu = apdu + [0] * max(4-len(apdu), 0)
self.CLA, self.INS, self.P1, self.P2 = apdu[:4] # case 1, 2, 3, 4 self.CLA, self.INS, self.P1, self.P2 = apdu[:4] # case 1, 2, 3, 4
@@ -349,21 +305,21 @@ class C_APDU(APDU):
buffer = [] buffer = []
for i in self.CLA, self.INS, self.P1, self.P2: for i in self.CLA, self.INS, self.P1, self.P2:
buffer.append(chr(i)) buffer.append(inttostring(i))
if len(self.data) > 0: if len(self.data) > 0:
buffer.append(chr(self.Lc)) buffer.append(inttostring(self.Lc))
buffer.append(self.data) buffer.append(self.data)
if hasattr(self, "_Le"): if hasattr(self, "_Le"):
if self.__extended_length: if self.__extended_length:
buffer.append(chr(0x00)) buffer.append(inttostring(0x00))
buffer.append(chr(self.Le >> 8)) buffer.append(inttostring(self.Le >> 8))
buffer.append(chr(self.Le - self.Le >> 8)) buffer.append(inttostring(self.Le - self.Le >> 8))
else: else:
buffer.append(chr(self.Le)) buffer.append(inttostring(self.Le))
return "".join(buffer) return b"".join(buffer)
def case(self): def case(self):
"Return 1, 2, 3 or 4, depending on which ISO case we represent." "Return 1, 2, 3 or 4, depending on which ISO case we represent."
@@ -383,7 +339,7 @@ class R_APDU(APDU):
"Class for a response APDU" "Class for a response APDU"
def _getsw(self): def _getsw(self):
return chr(self.SW1) + chr(self.SW2) return inttostring(self.SW1) + inttostring(self.SW2)
def _setsw(self, value): def _setsw(self, value):
if len(value) != 2: if len(value) != 2: