diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardSAM.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardSAM.py index 875154b..f841807 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardSAM.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardSAM.py @@ -153,10 +153,10 @@ 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 keytype not in range(0x07, 0x08): + if keytype not in (0x07, 0x08): raise SwError(SW["ERR_INCORRECTP1P2"]) else: - self.cipher = type + self.cipher = keytype self.asym_key = cipher def verify(self, p1, p2, PIN): diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/HandlerTest.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/HandlerTest.py index 8131efa..1dae405 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/HandlerTest.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/HandlerTest.py @@ -1,18 +1,18 @@ # # Copyright (C) 2011-2015 Frank Morgner -# +# # This file is part of virtualsmartcard. -# +# # virtualsmartcard is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. -# +# # virtualsmartcard is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. -# +# # You should have received a copy of the GNU General Public License along with # virtualsmartcard. If not, see . # @@ -32,16 +32,18 @@ class HandlerTestOS(SmartcardOS): lastCommandOffcut = '' def getATR(self): - return '\x3B\xD6\x18\x00\x80\xB1\x80\x6D\x1F\x03\x80\x51\x00\x61\x10\x30\x9E' + return '\x3B\xD6\x18\x00\x80\xB1\x80\x6D\x1F\x03\x80\x51\x00\x61' + \ + '\x10\x30\x9E' def __output_from_le(self, msg): - le = (ord(msg[2])<<8)+ord(msg[3]) - return ''.join([chr(num&0xff) for num in xrange(le)]) + le = (ord(msg[2]) << 8) + ord(msg[3]) + return ''.join([chr(num & 0xff) for num in xrange(le)]) def execute(self, msg): ok = '\x90\x00' error = '\x6d\x00' - if msg == '\x00\xA4\x04\x00\x06\xA0\x00\x00\x00\x18\x50' or msg == '\x00\xA4\x04\x00\x06\xA0\x00\x00\x00\x18\xFF': + if (msg == '\x00\xA4\x04\x00\x06\xA0\x00\x00\x00\x18\x50' or + msg == '\x00\xA4\x04\x00\x06\xA0\x00\x00\x00\x18\xFF'): logging.info('Select applet') return ok elif msg.startswith('\x80\x38\x00'): @@ -65,7 +67,7 @@ class HandlerTestOS(SmartcardOS): self.lastCommandOffcut = self.__output_from_le(msg) if len(self.lastCommandOffcut) > 0xFF: return '\x61\x00' - return '' + chr(len(self.lastCommandOffcut)&0xFF) + return '' + chr(len(self.lastCommandOffcut) & 0xFF) elif len(msg) == 6+ord(msg[4]): logging.info('Case 4, APDU') return self.__output_from_le(msg) + ok diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/Relay.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/Relay.py index 114e560..69c27b3 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/Relay.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/Relay.py @@ -1,24 +1,27 @@ # # Copyright (C) 2015 Frank Morgner -# +# # This file is part of virtualsmartcard. -# +# # virtualsmartcard is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. -# +# # virtualsmartcard is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. -# +# # You should have received a copy of the GNU General Public License along with # virtualsmartcard. If not, see . # +import atexit +import logging +import smartcard +import sys from virtualsmartcard.VirtualSmartcard import SmartcardOS -import smartcard, logging, sys, atexit class RelayOS(SmartcardOS): @@ -30,7 +33,8 @@ class RelayOS(SmartcardOS): """ def __init__(self, readernum): """ - Initialize the connection to the (physical) smart card via a given reader + Initialize the connection to the (physical) smart card via a given + reader """ # See which readers are available @@ -80,7 +84,7 @@ class RelayOS(SmartcardOS): sys.exit() return "".join([chr(b) for b in atr]) - + def powerUp(self): # When powerUp is called multiple times the session is valid (and the # card is implicitly powered) we can check for an ATR. But when diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py index 4f50b1d..7d04fae 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/cryptoflex.py @@ -1,18 +1,18 @@ # # Copyright (C) 2011 Dominik Oepen -# +# # This file is part of virtualsmartcard. -# +# # virtualsmartcard is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. -# +# # virtualsmartcard is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. -# +# # You should have received a copy of the GNU General Public License along with # virtualsmartcard. If not, see . @@ -27,10 +27,11 @@ from virtualsmartcard.SmartcardFilesystem import MF, TransparentStructureEF, \ RecordStructureEF, DF, EF from virtualsmartcard.ConstantDefinitions import FDB, MAX_SHORT_LE -import struct, logging +import struct +import logging -class CryptoflexOS(Iso7816OS): +class CryptoflexOS(Iso7816OS): def __init__(self, mf, sam, ins2handler=None, maxle=MAX_SHORT_LE): Iso7816OS.__init__(self, mf, sam, ins2handler, maxle) self.atr = '\x3B\xE2\x00\x00\x40\x20\x49\x06' @@ -43,14 +44,17 @@ class CryptoflexOS(Iso7816OS): c = C_APDU(msg) except ValueError as e: logging.debug("Failed to parse APDU %s", msg) - return self.formatResult(False, 0, 0, "", SW["ERR_INCORRECTPARAMETERS"]) + return self.formatResult(False, 0, 0, "", + SW["ERR_INCORRECTPARAMETERS"]) try: - sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, c.p2, c.data) - #print type(result) + sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, + c.p2, + c.data) + # print type(result) except SwError as e: logging.info(e.message) - #traceback.print_exception(*sys.exc_info()) + # traceback.print_exception(*sys.exc_info()) sw = e.sw result = "" @@ -62,20 +66,22 @@ class CryptoflexOS(Iso7816OS): # cryptoflex does not inpterpret le==0 as maxle self.lastCommandSW = sw self.lastCommandOffcut = data - r = R_APDU(inttostring(SW["ERR_WRONGLENGTH"] +\ - min(0xff, len(data)))).render() + r = R_APDU(inttostring(SW["ERR_WRONGLENGTH"] + + min(0xff, len(data)))).render() else: if ins == 0xa4 and len(data): # get response should be followed by select file self.lastCommandSW = sw self.lastCommandOffcut = data - r = R_APDU(inttostring(SW["NORMAL_REST"] +\ - min(0xff, len(data)))).render() + r = R_APDU(inttostring(SW["NORMAL_REST"] + + min(0xff, len(data)))).render() else: - r = Iso7816OS.formatResult(self, Iso7816OS.seekable(ins), le, data, sw, False) + r = Iso7816OS.formatResult(self, Iso7816OS.seekable(ins), le, + data, sw, False) return r + class CryptoflexSE(Security_Environment): def __init__(self, MF, SAM): Security_Environment.__init__(self, MF, SAM) @@ -95,11 +101,11 @@ class CryptoflexSE(Security_Environment): from Crypto.PublicKey import RSA from Crypto.Util.randpool import RandomPool - keynumber = p1 #TODO: Check if key exists + keynumber = p1 # TODO: Check if key exists keylength_dict = {0x40: 256, 0x60: 512, 0x80: 1024} - if not keylength_dict.has_key(p2): + if p2 not in keylength_dict: raise SwError(SW["ERR_INCORRECTP1P2"]) else: keylength = keylength_dict[p2] @@ -110,34 +116,34 @@ class CryptoflexSE(Security_Environment): e_in = struct.unpack("16: + if len(data) > 16: args["maxrecordsize"] = stringtoint(data[16]) elif p2: # if given a number of records - args["maxrecordsize"] = (stringtoint(data[2:4])/p2) - args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_FIXED_NOFURTHERINFO"] + args["maxrecordsize"] = (stringtoint(data[2:4]) / p2) + args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_FIXED_" + "NOFURTHERINFO"] new_file = RecordStructureEF(**args) elif data[6] == "\x03": - args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_VARIABLE_NOFURTHERINFO"] + args["filedescriptor"] = FDB["EFSTRUCTURE_LINEAR_VARIABLE_" + "NOFURTHERINFO"] new_file = RecordStructureEF(**args) elif data[6] == "\x04": args["filedescriptor"] = FDB["EFSTRUCTURE_CYCLIC_NOFURTHERINFO"] @@ -216,7 +225,6 @@ class CryptoflexMF(MF): # {{{ logging.error("unknown type: 0x%x" % ord(data[6])) raise SwError(SW["ERR_INCORRECTPARAMETERS"]) - return [new_file] def recordHandlingDecode(self, p1, p2): @@ -225,7 +233,6 @@ class CryptoflexMF(MF): # {{{ return MF.recordHandlingDecode(self, p1, p2) - def dataUnitsDecodePlain(self, p1, p2, data): ef = self.currentEF() # FIXME: Cyberflex Access TM Software Development Kit Release 3C @@ -243,19 +250,23 @@ class CryptoflexMF(MF): # {{{ integers and 'data' as binary string. Returns the status bytes as two byte long integer and the response data as binary string. """ - P2_FCI = 0 - P2_FCP = 1 << 2 - P2_FMD = 2 << 2 + P2_FCI = 0 + P2_FCP = 1 << 2 + P2_FMD = 2 << 2 P2_NONE = 3 << 2 file = self._selectFile(p1, p2, data) if isinstance(file, EF): - size = inttostring(min(0xffff, len(file.getenc('data'))), 2) # File size (body only) - extra = chr(0) # RFU - if isinstance(file, RecordStructureEF) and file.hasFixedRecordSize() and not file.isCyclic(): - extra += chr(0) + chr(min(file.records, 0xff)) # Length of records + # File size (body only) + size = inttostring(min(0xffff, len(file.getenc('data'))), 2) + extra = chr(0) # RFU + if (isinstance(file, RecordStructureEF) and + file.hasFixedRecordSize() and not file.isCyclic()): + # Length of records + extra += chr(0) + chr(min(file.records, 0xff)) elif isinstance(file, DF): - size = inttostring(0xffff, 2) # Number of unused EEPROM bytes available in the DF + # Number of unused EEPROM bytes available in the DF + size = inttostring(0xffff, 2) efcount = 0 dfcount = 0 chv1 = None @@ -274,26 +285,26 @@ class CryptoflexMF(MF): # {{{ if isinstance(f, DF): dfcount += 1 if chv1: - extra = chr(1) # TODO LSB correct? + extra = chr(1) # TODO LSB correct? else: - extra = chr(0) # TODO LSB correct? + extra = chr(0) # TODO LSB correct? extra += chr(efcount) extra += chr(dfcount) - extra += chr(0) # TODO Number of PINs and unblock CHV PINs - extra += chr(0) # RFU + extra += chr(0) # TODO Number of PINs and unblock CHV PINs + extra += chr(0) # RFU if chv1: - extra += chr(0) # TODO Number of remaining CHV1 attempts - extra += chr(0) # TODO Number of remaining unblock CHV1 attempts + extra += chr(0) # TODO remaining CHV1 attempts + extra += chr(0) # TODO remaining unblock CHV1 attempts if chv2: - extra += chr(0) # TODO CHV2 key status - extra += chr(0) # TODO CHV2 unblocking key status + extra += chr(0) # TODO CHV2 key status + extra += chr(0) # TODO CHV2 unblocking key status - data = inttostring(0, 2) # RFU + data = inttostring(0, 2) # RFU data += size data += inttostring(file.fid, 2) - data += inttostring(file.filedescriptor) # File type - data += inttostring(0, 4) # ACs TODO - data += chr(file.lifecycle & 1) # File status + data += inttostring(file.filedescriptor) # File type + data += inttostring(0, 4) # ACs TODO + data += chr(file.lifecycle & 1) # File status data += chr(len(extra)) data += extra diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/ePass.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/ePass.py index 7916231..7e7fb4d 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/ePass.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/ePass.py @@ -1,18 +1,18 @@ # # Copyright (C) 2011 Dominik Oepen -# +# # This file is part of virtualsmartcard. -# +# # virtualsmartcard is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. -# +# # virtualsmartcard is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. -# +# # You should have received a copy of the GNU General Public License along with # virtualsmartcard. If not, see . @@ -22,9 +22,11 @@ import virtualsmartcard.CryptoUtils as vsCrypto from virtualsmartcard.SWutils import SwError, SW from virtualsmartcard.utils import stringtoint -import hashlib, struct +import hashlib +import struct from os import urandom + class ePass_SE(Security_Environment): """This class implements the Security Environment of the ICAO Passports. It is required in order to use the send sequence counter for secure messaging. @@ -35,7 +37,7 @@ class ePass_SE(Security_Environment): self.cct.algorithm = "CC" self.cct.blocklength = 8 self.ct.algorithm = "DES3-CBC" - + def compute_cryptographic_checksum(self, p1, p2, data): """ Compute a cryptographic checksum (e.g. MAC) for the given data. @@ -43,45 +45,46 @@ class ePass_SE(Security_Environment): """ if p1 != 0x8E or p2 != 0x80: raise SwError(SW["ERR_INCORRECTP1P2"]) - + self.ssc += 1 checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key, - data, self.cct.iv, self.ssc) + data, self.cct.iv, self.ssc) return checksum -class PassportSAM(SAM): + +class PassportSAM(SAM): """ SAM for ICAO ePassport. Implements Basic access control and key derivation - for Secure Messaging. + for Secure Messaging. """ def __init__(self, mf): - import virtualsmartcard.SmartcardFilesystem as vsFS + import virtualsmartcard.SmartcardFilesystem as vsFS - SAM.__init__(self, None, None, mf, default_se = ePass_SE) + SAM.__init__(self, None, None, mf, default_se=ePass_SE) ef_dg1 = vsFS.walk(mf, "\x00\x04\x01\x01") dg1 = ef_dg1.readbinary(5) self.mrz1 = dg1[:43] self.mrz2 = dg1[44:] - self.KSeed = None + self.KSeed = None self.KEnc = None self.KMac = None self.__computeKeys() def __computeKeys(self): """ - Computes the keys depending on the machine readable - zone of the passport according to TR-PKI mrtds ICC read-only + Computes the keys depending on the machine readable + zone of the passport according to TR-PKI mrtds ICC read-only access v1.1 annex E.1. """ MRZ_information = self.mrz2[0:10] + self.mrz2[13:20] + self.mrz2[21:28] - H = hashlib.sha1(MRZ_information).digest() #pylint: disable=E1101 + H = hashlib.sha1(MRZ_information).digest() # pylint: disable=E1101 self.KSeed = H[:16] self.KEnc = self.derive_key(self.KSeed, 1) self.KMac = self.derive_key(self.KSeed, 2) - + @staticmethod def derive_key(seed, c): """ @@ -91,39 +94,41 @@ class PassportSAM(SAM): Returns: Ka + Kb Note: Does not adjust parity. Nobody uses that anyway ...""" D = seed + struct.pack(">i", c) - H = hashlib.sha1(D).digest() #pylint: disable=E1101 + H = hashlib.sha1(D).digest() # pylint: disable=E1101 Ka = H[0:8] Kb = H[8:16] return Ka + Kb - + def external_authenticate(self, p1, p2, resp_data): """Performs the basic access control protocol as defined in the ICAO MRTD standard""" rnd_icc = self.last_challenge - - #Receive Mutual Authenticate APDU from terminal - #Decrypt data and check MAC + + # Receive Mutual Authenticate APDU from terminal + # Decrypt data and check MAC Eifd = resp_data[:-8] - padded_Eifd = vsCrypto.append_padding(self.current_SE.cct.blocklength, Eifd) + padded_Eifd = vsCrypto.append_padding(self.current_SE.cct.blocklength, + Eifd) Mifd = vsCrypto.crypto_checksum("CC", self.KMac, padded_Eifd) - #Check the MAC + # Check the MAC if not Mifd == resp_data[-8:]: raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"]) - #Decrypt the data + # Decrypt the data plain = vsCrypto.decrypt("DES3-CBC", self.KEnc, resp_data[:-8]) if plain[8:16] != rnd_icc: raise SwError(SW["WARN_NOINFO63"]) - #Extract keying material from IFD, generate ICC keying material + # Extract keying material from IFD, generate ICC keying material Kifd = plain[16:] rnd_ifd = plain[:8] Kicc = urandom(16) - #Generate Answer + # Generate Answer data = plain[8:16] + plain[:8] + Kicc Eicc = vsCrypto.encrypt("DES3-CBC", self.KEnc, data) - padded_Eicc = vsCrypto.append_padding(self.current_SE.cct.blocklength, Eicc) + padded_Eicc = vsCrypto.append_padding(self.current_SE.cct.blocklength, + Eicc) Micc = vsCrypto.crypto_checksum("CC", self.KMac, padded_Eicc) - #Derive the final keys and set the current SE - KSseed = vsCrypto.operation_on_string(Kicc, Kifd, lambda a, b: a^b) + # Derive the final keys and set the current SE + KSseed = vsCrypto.operation_on_string(Kicc, Kifd, lambda a, b: a ^ b) self.current_SE.ct.key = self.derive_key(KSseed, 1) self.current_SE.cct.key = self.derive_key(KSseed, 2) self.current_SE.ssc = stringtoint(rnd_icc[-4:] + rnd_ifd[-4:]) diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/nPA.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/nPA.py index 2c960bb..1c2e25d 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/nPA.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/nPA.py @@ -1,47 +1,59 @@ # # Copyright (C) 2011 Dominik Oepen, Frank Morgner -# +# # This file is part of virtualsmartcard. -# +# # virtualsmartcard is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) any # later version. -# +# # virtualsmartcard is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. -# +# # You should have received a copy of the GNU General Public License along with # virtualsmartcard. If not, see . from virtualsmartcard.SmartcardSAM import SAM from virtualsmartcard.VirtualSmartcard import Iso7816OS -from virtualsmartcard.SEutils import ControlReferenceTemplate, Security_Environment +from virtualsmartcard.SEutils import ControlReferenceTemplate, \ + Security_Environment from virtualsmartcard.SWutils import SwError, SW -from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, SM_Class, ALGO_MAPPING, MAX_EXTENDED_LE, MAX_SHORT_LE -from virtualsmartcard.TLVutils import unpack, bertlv_pack, decodeDiscretionaryDataObjects, tlv_find_tag +from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, SM_Class, \ + ALGO_MAPPING, MAX_EXTENDED_LE, MAX_SHORT_LE +from virtualsmartcard.TLVutils import unpack, bertlv_pack, \ + decodeDiscretionaryDataObjects, tlv_find_tag from virtualsmartcard.SmartcardFilesystem import make_property from virtualsmartcard.utils import inttostring, R_APDU import virtualsmartcard.CryptoUtils as vsCrypto from chat import CHAT, CVC, PACE_SEC, EAC_CTX +import binascii import eac -import logging, binascii +import logging +import sys +import traceback class NPAOS(Iso7816OS): - def __init__(self, mf, sam, ins2handler=None, maxle=MAX_EXTENDED_LE, ef_cardsecurity=None, ef_cardaccess=None, ca_key=None, cvca=None, disable_checks=False, esign_key=None, esign_ca_cert=None, esign_cert=None): + def __init__(self, mf, sam, ins2handler=None, maxle=MAX_EXTENDED_LE, + ef_cardsecurity=None, ef_cardaccess=None, ca_key=None, + cvca=None, disable_checks=False, esign_key=None, + esign_ca_cert=None, esign_cert=None): Iso7816OS.__init__(self, mf, sam, ins2handler, maxle) self.ins2handler[0x86] = self.SAM.general_authenticate self.ins2handler[0x2c] = self.SAM.reset_retry_counter # 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 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\x00\x75' - #self.atr = '\x3B\x8A\x80\x01\x80\x31\xB8\x73\x84\x01\xE0\x82\x90\x00\x06' - #self.atr = '\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' + # It's just a playground, because in past one of all those eID clients + # 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' + \ + '\x00\x75' + # self.atr = '\x3B\x8A\x80\x01\x80\x31\xB8\x73\x84\x01\xE0\x82\x90' + \ + # '\x00\x06' + # self.atr = '\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.SAM.current_SE.disable_checks = disable_checks if ef_cardsecurity: @@ -54,7 +66,8 @@ class NPAOS(Iso7816OS): self.SAM.current_SE.cvca = cvca if ca_key: self.SAM.current_SE.ca_key = ca_key - esign = self.mf.select('dfname', '\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E') + esign = self.mf.select('dfname', + '\xA0\x00\x00\x01\x67\x45\x53\x49\x47\x4E') if esign_ca_cert: ef = esign.select('fid', 0xC000) ef.data = esign_ca_cert @@ -62,7 +75,6 @@ class NPAOS(Iso7816OS): ef = esign.select('fid', 0xC001) ef.data = esign_cert - def formatResult(self, seekable, le, data, sw, sm): if seekable: # when le = 0 then we want to have 0x9000. here we only have the @@ -72,7 +84,7 @@ class NPAOS(Iso7816OS): if le > len(data) and le != MAX_EXTENDED_LE and le != MAX_SHORT_LE: sw = SW["WARN_EOFBEFORENEREAD"] - if le != None: + if le is not None: result = data[:le] else: result = data[:0] @@ -81,7 +93,6 @@ class NPAOS(Iso7816OS): sw, result = self.SAM.protect_result(sw, result) except SwError as e: logging.info(e.message) - import traceback, sys traceback.print_exception(*sys.exc_info()) sw = e.sw result = "" @@ -105,22 +116,22 @@ class nPA_AT_CRT(ControlReferenceTemplate): CommunityID = None def keyref_is_mrz(self): - if self.keyref_secret_key == '%c'% self.PACE_MRZ: + if self.keyref_secret_key == '%c' % self.PACE_MRZ: return True return False def keyref_is_can(self): - if self.keyref_secret_key == '%c'% self.PACE_CAN: + if self.keyref_secret_key == '%c' % self.PACE_CAN: return True return False def keyref_is_pin(self): - if self.keyref_secret_key == '%c'% self.PACE_PIN: + if self.keyref_secret_key == '%c' % self.PACE_PIN: return True return False def keyref_is_puk(self): - if self.keyref_secret_key == '%c'% self.PACE_PUK: + if self.keyref_secret_key == '%c' % self.PACE_PUK: return True return False @@ -145,13 +156,16 @@ class nPA_AT_CRT(ControlReferenceTemplate): mapped_algo = ALGO_MAPPING[oidvalue] if mapped_algo == "DateOfBirth": self.DateOfBirth = int(reference) - logging.info("Found reference DateOfBirth: " + str(self.DateOfBirth)) + logging.info("Found reference DateOfBirth: " + + str(self.DateOfBirth)) elif mapped_algo == "DateOfExpiry": self.DateOfExpiry = int(reference) - logging.info("Found reference DateOfExpiry: " + str(self.DateOfExpiry)) + logging.info("Found reference DateOfExpiry: " + + str(self.DateOfExpiry)) elif mapped_algo == "CommunityID": self.CommunityID = binascii.hexlify(reference) - logging.info("Found reference CommunityID: " + str(self.CommunityID)) + logging.info("Found reference CommunityID: " + + str(self.CommunityID)) except: pass elif tag == 0x80 or tag == 0x84 or tag == 0x83 or tag == 0x91: @@ -169,8 +183,8 @@ class nPA_SE(Security_Environment): def __init__(self, MF, SAM): Security_Environment.__init__(self, MF, SAM) - self.at = nPA_AT_CRT() - #This breaks support for 3DES + self.at = nPA_AT_CRT() + # This breaks support for 3DES self.cct.blocklength = 16 self.cct.algorithm = "CC" self.eac_step = 0 @@ -183,7 +197,7 @@ class nPA_SE(Security_Environment): def _set_SE(self, p2, data): sw, resp = Security_Environment._set_SE(self, p2, data) - + if self.at.algorithm == "PACE": if self.at.keyref_is_pin(): if self.sam.counter <= 0: @@ -206,7 +220,7 @@ class nPA_SE(Security_Environment): if (p1, p2) != (0x00, 0x00): raise SwError(SW["ERR_INCORRECTPARAMETERS"]) - if self.eac_step == 0 and self.at.algorithm == "PACE": + if self.eac_step == 0 and self.at.algorithm == "PACE": return self.__eac_pace_step1(data) elif self.eac_step == 1 and self.at.algorithm == "PACE": return self.__eac_pace_step2(data) @@ -217,8 +231,12 @@ class nPA_SE(Security_Environment): elif self.eac_step == 5 and self.at.algorithm == "CA": return self.__eac_ca(data) elif self.eac_step == 6: - # TODO implement 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\x9a\x0a\xb4\x63\x7d\x43\xc7\xf7\xeb\x2b\x06\x10\x6f\x26\x90\xe3\x00\xc4\xe7\x03\x54\xa0\x41\xf0\xd3\x90" + # TODO implement 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" + \ + "\x9a\x0a\xb4\x63\x7d\x43\xc7\xf7\xeb\x2b" + \ + "\x06\x10\x6f\x26\x90\xe3\x00\xc4\xe7\x03" + \ + "\x54\xa0\x41\xf0\xd3\x90" raise SwError(SW["ERR_INCORRECTPARAMETERS"]) @@ -241,7 +259,7 @@ class nPA_SE(Security_Environment): def __eac_pace_step1(self, data): tlv_data = nPA_SE.__unpack_general_authenticate(data) - if tlv_data != []: + if tlv_data != []: raise SwError(SW["WARN_NOINFO63"]) if self.at.keyref_is_mrz(): @@ -277,27 +295,33 @@ class nPA_SE(Security_Environment): ef_card_security = self.mf.select('fid', 0x011d) ef_card_security_data = ef_card_security.data - eac.EAC_CTX_init_ef_cardsecurity(ef_card_security_data, self.eac_ctx) + eac.EAC_CTX_init_ef_cardsecurity(ef_card_security_data, + self.eac_ctx) if self.ca_key: - ca_pubkey = eac.CA_get_pubkey(self.eac_ctx, ef_card_security_data) + ca_pubkey = eac.CA_get_pubkey(self.eac_ctx, + ef_card_security_data) if 1 != eac.CA_set_key(self.eac_ctx, self.ca_key, ca_pubkey): eac.print_ossl_err() raise SwError(SW["WARN_NOINFO63"]) else: - # we don't have a good CA key, so we simply generate an ephemeral one + # we don't have a good CA key, so we simply generate an + # ephemeral one comp_pubkey = eac.TA_STEP3_generate_ephemeral_key(self.eac_ctx) pubkey = eac.CA_STEP2_get_eph_pubkey(self.eac_ctx) if not comp_pubkey or not pubkey: eac.print_ossl_err() raise SwError(SW["WARN_NOINFO63"]) - # save public key in EF.CardSecurity (and invalidate the signature) + # save public key in EF.CardSecurity (and invalidate the + # signature) # FIXME this only works for the default EF.CardSecurity. # Better use an ASN.1 parser to do this manipulation ef_card_security = self.mf.select('fid', 0x011d) ef_card_security_data = ef_card_security.data - ef_card_security_data = ef_card_security_data[:61+4+239+2+1] + pubkey + ef_card_security_data[61+4+239+2+1+len(pubkey):] + ef_card_security_data = \ + ef_card_security_data[:61+4+239+2+1] + pubkey + \ + ef_card_security_data[61+4+239+2+1+len(pubkey):] ef_card_security.data = ef_card_security_data nonce = eac.PACE_STEP1_enc_nonce(self.eac_ctx, self.sec) @@ -327,12 +351,14 @@ class nPA_SE(Security_Environment): self.eac_step += 1 - return 0x9000, nPA_SE.__pack_general_authenticate([[0x82, len(pubkey), pubkey]]) + return 0x9000, \ + nPA_SE.__pack_general_authenticate([[0x82, len(pubkey), pubkey]]) def __eac_pace_step3(self, data): tlv_data = nPA_SE.__unpack_general_authenticate(data) - self.my_pace_eph_pubkey = eac.PACE_STEP3B_generate_ephemeral_key(self.eac_ctx) + self.my_pace_eph_pubkey = \ + eac.PACE_STEP3B_generate_ephemeral_key(self.eac_ctx) if not self.my_pace_eph_pubkey: eac.print_ossl_err() raise SwError(SW["WARN_NOINFO63"]) @@ -341,18 +367,23 @@ class nPA_SE(Security_Environment): for tag, length, value in tlv_data: if tag == 0x83: self.pace_opp_pub_key = value - eac.PACE_STEP3B_compute_shared_secret(self.eac_ctx, self.pace_opp_pub_key) + eac.PACE_STEP3B_compute_shared_secret(self.eac_ctx, + self.pace_opp_pub_key) else: raise SwError(SW["ERR_INCORRECTPARAMETERS"]) self.eac_step += 1 - return 0x9000, nPA_SE.__pack_general_authenticate([[0x84, len(eph_pubkey), eph_pubkey]]) + return 0x9000, \ + nPA_SE.__pack_general_authenticate([[0x84, len(eph_pubkey), + eph_pubkey]]) def __eac_pace_step4(self, data): tlv_data = nPA_SE.__unpack_general_authenticate(data) eac.PACE_STEP3C_derive_keys(self.eac_ctx) - my_token = eac.PACE_STEP3D_compute_authentication_token(self.eac_ctx, self.pace_opp_pub_key) + my_token = \ + eac.PACE_STEP3D_compute_authentication_token(self.eac_ctx, + self.pace_opp_pub_key) token = "" for tag, length, value in tlv_data: if tag == 0x85: @@ -360,7 +391,8 @@ class nPA_SE(Security_Environment): else: raise SwError(SW["ERR_INCORRECTPARAMETERS"]) - if not my_token or 1 != eac.PACE_STEP3D_verify_authentication_token(self.eac_ctx, token): + ver = eac.PACE_STEP3D_verify_authentication_token(self.eac_ctx, token) + if not my_token or ver != 1: eac.print_ossl_err() raise SwError(SW["WARN_NOINFO63"]) @@ -394,7 +426,6 @@ class nPA_SE(Security_Environment): eac.print_ossl_err() raise SwError(SW["WARN_NOINFO63"]) - return 0x9000, nPA_SE.__pack_general_authenticate(result) def __eac_ca(self, data): @@ -409,7 +440,7 @@ class nPA_SE(Security_Environment): if eac.CA_STEP4_compute_shared_secret(self.eac_ctx, pubkey) != 1: eac.print_ossl_err() - raise SwError(SW["ERR_NOINFO69"]) + raise SwError(SW["ERR_NOINFO69"]) nonce, token = eac.CA_STEP5_derive_keys(self.eac_ctx, pubkey) if not nonce or not token: @@ -423,8 +454,9 @@ class nPA_SE(Security_Environment): # TODO activate SM self.new_encryption_ctx = eac.EAC_ID_CA - return 0x9000, nPA_SE.__pack_general_authenticate([[0x81, - len(nonce), nonce], [0x82, len(token), token]]) + return 0x9000, \ + nPA_SE.__pack_general_authenticate([[0x81, len(nonce), nonce], + [0x82, len(token), token]]) def verify_certificate(self, p1, p2, data): if (p1, p2) != (0x00, 0xbe): @@ -433,7 +465,7 @@ class nPA_SE(Security_Environment): cert = bertlv_pack([[0x7f21, len(data), data]]) if 1 != eac.TA_STEP2_import_certificate(self.eac_ctx, cert): eac.print_ossl_err() - raise SwError(SW["ERR_NOINFO69"]) + raise SwError(SW["ERR_NOINFO69"]) print "Imported Certificate" @@ -444,8 +476,9 @@ class nPA_SE(Security_Environment): Authenticate the terminal to the card. Check whether Terminal correctly encrypted the given challenge or not """ - if self.dst.keyref_public_key: # TODO check if this is the correct CAR - id_picc = eac.EAC_Comp(self.eac_ctx, eac.EAC_ID_PACE, self.my_pace_eph_pubkey) + if self.dst.keyref_public_key: # TODO check if this is the correct CAR + id_picc = eac.EAC_Comp(self.eac_ctx, eac.EAC_ID_PACE, + self.my_pace_eph_pubkey) # FIXME auxiliary_data might be from an older run of PACE if hasattr(self.at, "auxiliary_data"): @@ -454,7 +487,7 @@ class nPA_SE(Security_Environment): auxiliary_data = None if 1 != eac.TA_STEP6_verify(self.eac_ctx, self.at.iv, id_picc, - auxiliary_data, data): + auxiliary_data, data): eac.print_ossl_err() print "Could not verify Terminal's signature" raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) @@ -471,7 +504,7 @@ class nPA_SE(Security_Environment): checksum = eac.EAC_authenticate(self.eac_ctx, data) if not checksum: eac.print_ossl_err() - raise SwError(SW["ERR_NOINFO69"]) + raise SwError(SW["ERR_NOINFO69"]) return checksum @@ -480,7 +513,7 @@ class nPA_SE(Security_Environment): cipher = eac.EAC_encrypt(self.eac_ctx, padded) if not cipher: eac.print_ossl_err() - raise SwError(SW["ERR_NOINFO69"]) + raise SwError(SW["ERR_NOINFO69"]) return cipher @@ -488,14 +521,14 @@ class nPA_SE(Security_Environment): plain = eac.EAC_decrypt(self.eac_ctx, data) if not plain: eac.print_ossl_err() - raise SwError(SW["ERR_NOINFO69"]) + raise SwError(SW["ERR_NOINFO69"]) return plain def protect_response(self, sw, result): """ This method protects a response APDU using secure messaging mechanisms - + :returns: the protected data and the SW bytes """ @@ -509,15 +542,15 @@ class nPA_SE(Security_Environment): SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"], len(encrypted), encrypted)]) - return_data += encrypted_tlv + return_data += encrypted_tlv sw_str = inttostring(sw) - length = len(sw_str) + length = len(sw_str) tag = SM_Class["PLAIN_PROCESSING_STATUS"] tlv_sw = bertlv_pack([(tag, length, sw_str)]) return_data += tlv_sw - - if self.cct.algorithm == None: + + if self.cct.algorithm is None: raise SwError(SW["CONDITIONSNOTSATISFIED"]) elif self.cct.algorithm == "CC": tag = SM_Class["CHECKSUM"] @@ -531,17 +564,22 @@ class nPA_SE(Security_Environment): auth = self.compute_digital_signature(0x9E, 0x9A, hash) length = len(auth) return_data += bertlv_pack([(tag, length, auth)]) - + return sw, return_data def compute_digital_signature(self, p1, p2, data): - # TODO Signing with brainpoolP256r1 or any other key needs some more effort ;-) - return '\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\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\x07\xF3\x72\xF0\xFB\xA3\x62\x8C' + # TODO Signing with brainpoolP256r1 or any other key needs some more + # effort ;-) + return '\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' + \ + '\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' + \ + '\x07\xF3\x72\xF0\xFB\xA3\x62\x8C' class nPA_SAM(SAM): - def __init__(self, eid_pin, can, mrz, puk, qes_pin, mf, default_se = nPA_SE): + def __init__(self, eid_pin, can, mrz, puk, qes_pin, mf, default_se=nPA_SE): SAM.__init__(self, qes_pin, None, mf) self.active = True self.current_SE = default_se(self.mf, self) @@ -557,18 +595,19 @@ class nPA_SAM(SAM): def reset_retry_counter(self, p1, p2, data): # check if PACE was successful if self.current_SE.eac_step < 4: - raise SwError(SW["ERR_SECSTATUS"]) + raise SwError(SW["ERR_SECSTATUS"]) - # TODO check CAN and PIN for the correct character set + # TODO: check CAN and PIN for the correct character set if p1 == 0x02: # change secret if p2 == self.current_SE.at.PACE_CAN: self.can = data print "Changed CAN to %r" % self.can elif p2 == self.current_SE.at.PACE_PIN: - # TODO allow terminals to change the PIN with permission "CAN allowed" + # TODO: allow terminals to change the PIN with permission + # "CAN allowed" if not self.current_SE.at.keyref_is_pin(): - raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) self.eid_pin = data print "Changed PIN to %r" % self.eid_pin else: @@ -583,7 +622,8 @@ class nPA_SAM(SAM): self.active = True print "Resumed PIN" elif self.current_SE.at.keyref_is_pin(): - # PACE was successful with PIN, nothing to do resume/unblock + # PACE was successful with PIN, nothing to do + # resume/unblock pass elif self.current_SE.at.keyref_is_puk(): # TODO unblock PIN for signature @@ -591,7 +631,7 @@ class nPA_SAM(SAM): self.active = True self.counter = 3 else: - raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) else: raise SwError(SW["ERR_DATANOTFOUND"]) else: @@ -607,8 +647,9 @@ class nPA_SAM(SAM): # TA if (p1 != 0x00 or p2 != 0x00): raise SwError(SW["ERR_INCORRECTP1P2"]) - - self.last_challenge = eac.TA_STEP4_get_nonce(self.current_SE.eac_ctx) + + self.last_challenge = \ + eac.TA_STEP4_get_nonce(self.current_SE.eac_ctx) if not self.last_challenge: eac.print_ossl_err() raise SwError(SW["ERR_NOINFO69"]) @@ -624,11 +665,14 @@ class nPA_SAM(SAM): [(tag, _, value)] = structure = unpack(data) if tag == 6: mapped_algo = ALGO_MAPPING[value] - eid = self.mf.select('dfname', '\xe8\x07\x04\x00\x7f\x00\x07\x03\x02') + eid = self.mf.select('dfname', '\xe8\x07\x04\x00\x7f\x00' + '\x07\x03\x02') if mapped_algo == "DateOfExpiry": - [(_, _, [(_, _, mine)])] = unpack(eid.select('fid', 0x0103).data) - logging.info("DateOfExpiry: " + str(mine) - + "; reference: " + str(self.current_SE.at.DateOfExpiry)) + [(_, _, [(_, _, mine)])] = \ + unpack(eid.select('fid', 0x0103).data) + logging.info("DateOfExpiry: " + str(mine) + + "; reference: " + + str(self.current_SE.at.DateOfExpiry)) if self.current_SE.at.DateOfExpiry < mine: print("Date of expiry verified") return SW["NORMAL"], "" @@ -636,16 +680,19 @@ class nPA_SAM(SAM): print("Date of expiry not verified (expired)") return SW["WARN_NOINFO63"], "" elif mapped_algo == "DateOfBirth": - [(_, _, [(_, _, mine)])] = unpack(eid.select('fid', 0x0108).data) + [(_, _, [(_, _, mine)])] = \ + unpack(eid.select('fid', 0x0108).data) # case1: YYYYMMDD -> good - # case2: YYYYMM -> mapped to last day of given month, i.e. YYYYMM31 ;-) + # case2: YYYYMM -> mapped to last day of given month, + # i.e. YYYYMM31 ;-) # case3: YYYY -> mapped to YYYY-12-31 if len(str(mine)) == 6: mine = int(str(mine) + "31") elif len(str(mine)) == 4: mine = int(str(mine) + "1231") - logging.info("DateOfBirth: " + str(mine) - + "; reference: " + str(self.current_SE.at.DateOfExpiry)) + logging.info("DateOfBirth: " + str(mine) + + "; reference: " + + str(self.current_SE.at.DateOfExpiry)) if self.current_SE.at.DateOfBirth < mine: print("Date of birth verified (old enough)") return SW["NORMAL"], "" @@ -653,15 +700,18 @@ class nPA_SAM(SAM): print("Date of birth not verified (too young)") return SW["WARN_NOINFO63"], "" elif mapped_algo == "CommunityID": - [(_, _, [(_, _, mine)])] = unpack(eid.select('fid', 0x0112).data) + [(_, _, [(_, _, mine)])] = \ + unpack(eid.select('fid', 0x0112).data) mine = binascii.hexlify(mine) - logging.info("CommunityID: " + str(mine) - + "; reference: " + str(self.current_SE.at.CommunityID)) + logging.info("CommunityID: " + str(mine) + + "; reference: " + + str(self.current_SE.at.CommunityID)) if mine.startswith(self.current_SE.at.CommunityID): print("Community ID verified (living there)") return SW["NORMAL"], "" else: - print("Community ID not verified (not living there)") + print("Community ID not verified (not living" + "there)") return SW["WARN_NOINFO63"], "" else: return SwError(SW["ERR_DATANOTFOUND"]) @@ -677,10 +727,12 @@ class nPA_SAM(SAM): protocol = "PACE" else: protocol = "CA" - logging.info("switching to new encryption context established in %s:" % protocol) + logging.info("switching to new encryption context established in " + "%s:" % protocol) logging.info(eac.EAC_CTX_print_private(self.current_SE.eac_ctx, 4)) - eac.EAC_CTX_set_encryption_ctx(self.current_SE.eac_ctx, self.current_SE.new_encryption_ctx) + eac.EAC_CTX_set_encryption_ctx(self.current_SE.eac_ctx, + self.current_SE.new_encryption_ctx) delattr(self.current_SE, "new_encryption_ctx")