From 673c3d51786ababd97506447ab484ed3645d6f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Fri, 3 Sep 2021 16:51:12 +0000 Subject: [PATCH 01/11] Added PTEID to CardGenerator --- .../vpicc/virtualsmartcard/CardGenerator.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py index 9f9736b1..957e0917 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py @@ -665,6 +665,97 @@ def __generate_cryptoflex(self): data=b"\x00\x00\x00\x01\x00\x01\x00\x00")) # EF.ICCSN self.sam = CryptoflexSAM(self.mf) + def __generate_PTEID(self): + def ___get_fs_entry(data, fid): + + obj = data.get(fid, {}) + fci = obj.get('fci', b'') + + if len(fci) != 0: + fci = a2b_base64(fci) + + name = obj.get('name', b'') + if len(name) != 0: + name = a2b_base64(name) + + fdata = obj.get('data', b'') + if len(fdata) != 0: + fdata = a2b_base64(fdata) + + return name, fci, fdata + + """Generate the Filesystem and SAM of a cryptoflex card""" + from virtualsmartcard.cards.PTEID import PTEID_MF + from virtualsmartcard.cards.PTEID import PTEID_SAM + import json + from binascii import a2b_base64 + logging.info("Opening card.json") + + f = open('card.json', 'r') + data = json.loads(f.read()) + f.close() + + self.mf = PTEID_MF() + name, fci, fdata = ___get_fs_entry(data, '3f00-0001') + fk = DF(parent=self.mf, fid=0x0001, + dfname=name, + extra_fci_data=fci) + self.mf.append(fk) + + name, fci, fdata = ___get_fs_entry(data, '3f00-4f01') + info_a = DF(parent=self.mf, fid=0x4f01, + dfname=name, + extra_fci_data=fci) + + self.mf.append(info_a) + + # OK + + # EF DIR + for fid in [0x2f00, 0x0003]: + path = '3f00-%04x' % (fid,) + name, fci, fdata = ___get_fs_entry(data, path) + self.mf.append(TransparentStructureEF(parent=self.mf, fid=fid, + data=fdata, + extra_fci_data=fci)) + + # TRACE + + + # ADF CIA PKI + name, fci, fdata = ___get_fs_entry(data, '3f00-4f00') + adf_cia = DF(parent=self.mf, fid=0x4F00, + dfname=name, + extra_fci_data=fci) + + for fid in [0x5031, 0x5032]: + path = '3f00-4f00-%04x' % (fid, ) + name, fci, fdata = ___get_fs_entry(data, path) + adf_cia.append(TransparentStructureEF(parent=adf_cia, fid=fid, + data=fdata, + extra_fci_data=fci)) + + self.mf.append(adf_cia) + + # ADF PKI + name, fci, fdata = ___get_fs_entry(data, '3f00-5f00') + adf = DF(parent=self.mf, fid=0x5f00, dfname=name, extra_fci_data=fci) + + for fid in [0x4401, 0xef0c, 0xEF02, + 0xEF05, 0xEF06, 0xEF07, + 0xEF08, 0xEF09, 0xEF0D, + 0xEF0E, 0xEF0F, 0xEF10, + 0xEF11, 0xEF12]: + path = '3f00-5f00-%04x' % (fid, ) + name, fci, fdata = ___get_fs_entry(data, path) + adf.append(TransparentStructureEF(parent=adf, fid=fid, + data=fdata, + extra_fci_data=fci)) + + self.mf.append(adf) + private_key = binascii.a2b_base64(data.get('auth-private-key', {}).get('data', None)) + self.sam = PTEID_SAM(self.mf, private_key=private_key) + def generateCard(self): """Generate a new card""" if self.type == 'iso7816': @@ -675,6 +766,8 @@ def generateCard(self): self.__generate_cryptoflex() elif self.type == 'nPA': self.__generate_nPA() + elif self.type == 'PTEID': + self.__generate_PTEID() else: return (None, None) From b7ae69f1dba7831264f195a53a51e3f8553f6985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Fri, 3 Sep 2021 17:00:32 +0000 Subject: [PATCH 02/11] Fixes to support python3 --- .../src/vpicc/virtualsmartcard/SEutils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py index 36deb2db..224afd4e 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py @@ -25,7 +25,7 @@ from virtualsmartcard.utils import inttostring, stringtoint, C_APDU from virtualsmartcard.SWutils import SwError, SW import virtualsmartcard.CryptoUtils as vsCrypto - +import logging class ControlReferenceTemplate: """ @@ -35,7 +35,7 @@ class ControlReferenceTemplate: Reference Template: HT, AT, KT, CCT, DST, CT-sym, CT-asym. """ - def __init__(self, tag, config=""): + def __init__(self, tag, config=b""): """ Generates a new CRT @@ -62,7 +62,7 @@ def __init__(self, tag, config=""): self.algorithm = None self.blocklength = None self.usage_qualifier = None - if config != "": + if config != b'': self.parse_SE_config(config) self.__config_string = config @@ -101,10 +101,10 @@ def __set_algo(self, data): :param data: reference to an algorithm """ - if data not in ALGO_MAPPING: + if data[0] not in ALGO_MAPPING: raise SwError(SW["ERR_REFNOTUSABLE"]) else: - self.algorithm = ALGO_MAPPING[data] + self.algorithm = ALGO_MAPPING[data[0]] self.__replace_tag(0x80, data) def __set_key(self, tag, value): @@ -149,6 +149,7 @@ def __replace_tag(self, tag, data): replace it. Otherwise append tag, length and value to the config string. """ + position = 0 while position < len(self.__config_string) and \ self.__config_string[position] != tag: @@ -156,7 +157,7 @@ def __replace_tag(self, tag, data): position += length + 3 if position < len(self.__config_string): # Replace Tag - length = stringtoint(self.__config_string[position+1]) + length = stringtoint(chr(self.__config_string[position+1])) self.__config_string = self.__config_string[:position] +\ inttostring(tag) + inttostring(len(data)) + data +\ self.__config_string[position+2+length:] From f82bda333c5917edb74df96c35daab9a13b8dcc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Fri, 3 Sep 2021 17:01:53 +0000 Subject: [PATCH 03/11] Added preliminary support for PTEID cards --- pteid/generate_pteid_card.py | 398 ++++++++++++++++++ .../virtualsmartcard/SmartcardFilesystem.py | 60 ++- .../virtualsmartcard/VirtualSmartcard.py | 3 + .../src/vpicc/virtualsmartcard/cards/PTEID.py | 285 +++++++++++++ 4 files changed, 723 insertions(+), 23 deletions(-) create mode 100644 pteid/generate_pteid_card.py create mode 100644 virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py diff --git a/pteid/generate_pteid_card.py b/pteid/generate_pteid_card.py new file mode 100644 index 00000000..9b3e4fe6 --- /dev/null +++ b/pteid/generate_pteid_card.py @@ -0,0 +1,398 @@ +#!/usr/bin/python3 + +# Generates a PTEID SmartCard +# Author: João Paulo Barraca + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.x509.oid import NameOID +import json +from binascii import b2a_base64 +import datetime +import uuid + + +def generate_ec(subject, duration=365 * 15, issuer=None, signing_key=None): + + one_day = datetime.timedelta(1, 0, 0) + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=4096, + backend=default_backend() + ) + + public_key = private_key.public_key() + builder = x509.CertificateBuilder() + builder = builder.subject_name(subject) + + if issuer is None: + issuer = subject + + builder = builder.issuer_name(issuer) + + builder = builder.not_valid_before( + datetime.datetime.today() - datetime.timedelta(1, 0, 0)) + builder = builder.not_valid_after( + datetime.datetime.today() + datetime.timedelta(duration, 0, 0)) + builder = builder.serial_number(int(uuid.uuid4())) + builder = builder.public_key(public_key) + builder = builder.add_extension( + x509.BasicConstraints(ca=True, path_length=None), critical=True, + ) + + builder = builder.add_extension( + x509.KeyUsage( + key_cert_sign=True, + crl_sign=True, + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + encipher_only=False, + decipher_only=False), + critical=False + ) + + if signing_key is None: + signing_key = private_key + + certificate = builder.sign( + private_key=signing_key, algorithm=hashes.SHA256(), + backend=default_backend() + ) + + return certificate, private_key + + +def generate_user(subject, duration=365 * 15, issuer=None, signing_key=None, + digital_signature=False, key_agreement=False, + content_commitment=True): + one_day = datetime.timedelta(1, 0, 0) + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=1024, + backend=default_backend() + ) + + public_key = private_key.public_key() + builder = x509.CertificateBuilder() + builder = builder.subject_name(subject) + + if issuer is None: + issuer = subject + + builder = builder.issuer_name(subject) + + builder = builder.not_valid_before( + datetime.datetime.today() - datetime.timedelta(1, 0, 0)) + builder = builder.not_valid_after( + datetime.datetime.today() + datetime.timedelta(duration, 0, 0)) + builder = builder.serial_number(int(uuid.uuid4())) + builder = builder.public_key(public_key) + builder = builder.add_extension( + x509.BasicConstraints(ca=False, path_length=None), critical=False, + ) + + builder = builder.add_extension( + x509.KeyUsage( + key_cert_sign=False, + crl_sign=False, + digital_signature=digital_signature, + content_commitment=content_commitment, + key_encipherment=False, + data_encipherment=False, + key_agreement=key_agreement, + encipher_only=False, + decipher_only=False), + critical=False + ) + + if signing_key is None: + signing_key = private_key + + certificate = builder.sign( + private_key=signing_key, algorithm=hashes.SHA256(), + backend=default_backend() + ) + + return certificate, private_key + +def load_x509_certificate(fname): + try: + return x509.load_pem_x509_certificate(open(fname + ".pem", 'rb').read()) + except: + pass + return None + +def load_private_key(fname): + try: + return serialization.load_pem_private_key(open(fname + ".key", 'rb').read()) + except: + pass + return None + +def dump_certificate(fname, cert): + with open(fname + '.pem', 'wb') as f: + f.write(ec_raizestado.public_bytes(serialization.Encoding.PEM)) + +def dump_private_key(fname, pkey): + with open(fname + '.key', 'wb') as f: + data = pkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + ) + + f.write(data) + +def dump_cert_key(fname, cert, private_key): + dump_certificate(fname, cert) + dump_private_key(fname, private_key) + +def load_cert_key(fname): + cert = load_x509_certificate(fname) + key = load_private_key(fname) + return cert, key + +##### Starts here + +force_create = False + +ec_raizestado, ec_raizestado_private_key = load_cert_key('ec_raizestado') + +if ec_raizestado is None or ec_raizestado_private_key is None: + print("Creating EC Raiz Estado") + force_create = True + subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'ECRaizEstado VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'Sistema de Certificação Electrónica do Estado VIRTUAL'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'PT') + ]) + + ec_raizestado, ec_raizestado_private_key = generate_ec(subject, duration=365 * 25) + dump_cert_key('ec_raizestado', ec_raizestado, ec_raizestado_private_key) + + +ec_cc, ecc_private_key = load_cert_key('ec_cc') if not force_create else (None, None) + +if ec_cc is None or ec_cc_private_key is None: + print("Creating EC CC") + force_create = True + subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'Cartão de Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'SCEE - Sistema de Certificação Electrónica do Estado VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'ECEstado VIRTUAL'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'PT') + ]) + + ec_cc, ec_cc_private_key = generate_ec( + subject, duration=365 * 15, issuer=ec_raizestado.subject, signing_key=ec_raizestado_private_key) + + dump_cert_key('ec_cc', ec_raizestado, ec_raizestado_private_key) + + + +ec_auth, ec_auth_private_key = load_cert_key('ec_auth') if not force_create else (None, None) + +if ec_auth is None or ec_auth_private_key is None: + print("Creating EC Auth") + force_create = True + subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, + u'EC de Autenticação do Cartão de Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'Instituto dos Registos e do Notariado I.P. VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'subECEstado VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'Cartão de Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'PT') + ]) + ec_auth, ec_auth_private_key = generate_ec( + subject, duration=365 * 12, issuer=ec_cc.subject, signing_key=ec_cc_private_key) + + dump_cert_key('ec_auth', ec_auth, ec_auth_private_key) + +user_auth, user_auth_private_key = load_cert_key('user_auth') if not force_create else (None, None) + +if user_auth is None or user_auth_private_key is None: + print("Creating User Auth") + force_create = True + subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'PAULO VIRTUAL'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'BI123123123'), + x509.NameAttribute(NameOID.SURNAME, u'VIRTUAL'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'PAULO'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'Cartão de Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'Cidadão Português VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'Autentidação do Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'PT') + ]) + + user_auth, user_auth_private_key = generate_user( + subject, duration=365 * 10, issuer=ec_auth.subject, signing_key=ec_auth_private_key, digital_signature=True, key_agreement=True) + dump_cert_key('user_auth', user_auth, user_auth_private_key) + + + +ec_sign, ec_sign_private_key = load_cert_key('ec_sign') if not force_create else (None, None) + +if ec_sign is None or ec_sign_private_key is None: + print("Creating EC Sign") + force_create = True + subject = x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u'EC de Assinatura Digital Qualificada do Cartão de Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'Instituto dos Registos e do Notariado I.P. VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'subECEstado VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'Cartão de Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'PT') + ]) + ec_sign, ec_sign_private_key = generate_ec(subject, duration=365 * 12, issuer=ec_cc.subject, signing_key=ec_cc_private_key) + + dump_cert_key('ec_sign', ec_sign, ec_sign_private_key) + +user_sign, user_sign_private_key = load_cert_key('user_sign') if not force_create else (None, None) + +if user_sign is None or user_sign_private_key is None: + print("Creating User Sign") + force_create = True + subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'PAULO VIRTUAL'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'BI123123123'), + x509.NameAttribute(NameOID.SURNAME, u'VIRTUAL'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'PAULO'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'Cartão de Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'Cidadão Português VIRTUAL'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, + u'Assinatura Qualificada do Cidadão VIRTUAL'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'PT') + ]) + + user_sign, user_sign_private_key = generate_user( + subject, duration=365 * 10, issuer=ec_sign.subject, signing_key=ec_sign_private_key, content_commitment=True) + + dump_cert_key('user_sign', user_sign, user_sign_private_key) + + +print("Creating Card Filesystem") + +data = {} + +# Files have different security attributes: tag 8C. Using RAW FCI now. +# TODO: Use proper flags for security attributes +# TODO: SOD and other objects are copied from devel cards and need to be created from scratch + +# 3F00 +data['3f00-0001'] = {'name': b2a_base64(b'\x60\x46\x32\xFF\x00\x00\x02')} + +data['3f00-0003'] = {'data': 'AAAgBAEB', + 'fci': b2a_base64(b'\x8C\x05\x1B\x14\xFF\x14\x00')} + +data['3f00-2f00'] = {'data': 'YR1PCURGIGlzc3VlclAMUE9SVFVHQUwgRUlEUQJPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# 4F00 - ADF CIA +data['3f00-4f00'] = {'data': '', 'name': b2a_base64(b'\x44\x46\x20\x69\x73\x73\x75\x65\x72'), + 'fci': b2a_base64(b'\x8C\x03\x06\x00\x00')} + +# 5031 - EF OD +data['3f00-4f00-5031'] = {'data': 'oAowCAQGPwBfAO8NoQowCAQGPwBfAO8OpAowCAQGPwBfAO8MqAowCAQGPwBfAEQBAAAAAAAAAAAAAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# 5032 - EF CIA +data['3f00-4f00-5032'] = {'data': 'MCwCAQEECJmZAAAAA1U5DAdHRU1BTFRPgBFDQVJUQU8gREUgQ0lEQURBTwMBAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# 5F00 ADF PKI +data['3f00-5f00'] = {'name': b2a_base64(b'\x44\x46\x20\x69\x73\x73\x75\x65\x73'), + 'fci': b2a_base64(b'\x8C\x03\x06\x00\x00')} +# 4401 - EF AOD +data['3f00-5f00-4401'] = {'data': 'MDowFwwVUElOIGRhIEF1dGVudGljYcOnw6NvMAcEAQECAgCBoRYwFAMCA4gKAQECAQQCAQiAAgCBBAH/MDYwEwwRUElOIGRhIEFzc2luYXR1cmEwBwQBAgICAIKhFjAUAwIDiAoBAQIBBAIBCIACAIIEAf8wMjAPDA1QSU4gZGEgTW9yYWRhMAcEAQMCAgCDoRYwFAMCA4gKAQECAQQCAQiAAgCDBAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} +# EF0C - CertID +data['3f00-5f00-ef0c'] = {'data': 'MDkwJAwiQ0lUSVpFTiBBVVRIRU5USUNBVElPTiBDRVJUSUZJQ0FURTADBAFFoQwwCjAIBAY/AF8A7wkwNDAfDB1DSVRJWkVOIFNJR05BVFVSRSBDRVJUSUZJQ0FURTADBAFGoQwwCjAIBAY/AF8A7wgwJzASDBBTSUdOQVRVUkUgU1VCIENBMAMEAVGhDDAKMAgEBj8AXwDvDzAsMBcMFUFVVEhFTlRJQ0FUSU9OIFNVQiBDQTADBAFSoQwwCjAIBAY/AF8A7xAwHjAJDAdST09UIENBMAMEAVChDDAKMAgEBj8AXwDvEQ==', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF12 - CertUserD +data['3f00-5f00-ef12'] = {'data': 'MEYwMQwiQ2l0aXplbiBBdXRoZW50aWNhdGlvbiBDZXJ0aWZpY2F0ZQMBADAIMAYDAgeABQAwAwQBRaEMMAowCAQGPwBfAO8JMEEwLAwdQ2l0aXplbiBTaWduYXR1cmUgQ2VydGlmaWNhdGUDAQAwCDAGAwIHgAUAMAMEAUahDDAKMAgEBj8AXwDvCA==', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF0D - PrKD +data['3f00-5f00-ef0d'] = {'data': 'MDkwHwwaQ0lUSVpFTiBBVVRIRU5USUNBVElPTiBLRVkEAQEwCgQBRQMCBSACAQKhCjAIMAIEAAICBAAwODAdDBVDSVRJWkVOIFNJR05BVFVSRSBLRVkEAQICAQEwCwQBRgMDBgBAAgEBoQowCDACBAACAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF0E - PuKD +data['3f00-5f00-ef0e'] = {'data': 'MEwwLgwaQ2l0aXplbiBBdXRoZW50aWNhdGlvbiBLZXkDAgeABAGBMAkwBwMCBSAEAYEwDgQBRQMCAkQDAgO4AgECoQowCDACBAACAgQAMEgwKQwVQ2l0aXplbiBTaWduYXR1cmUgS2V5AwIHgAQBgjAJMAcDAgUgBAGCMA8EAUYDAwYgQAMCA7gCAQGhCjAIMAIEAAICBAA=', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF02 - ID +data['3f00-5f00-ef02'] = {'data': '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', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF05 - Addr +data['3f00-5f00-ef05'] = {'data': 'TgBQVAAAMTEAAExpc2JvYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxMTA2AAAAAExpc2JvYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxMTA2MjMAAAAAAABOb3NzYSBTZW5ob3JhIGRlIEbDoXRpbWEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQVYAAAAAAAAAAAAAAAAAAAAAAABBdmVuaWRhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANSBkZSBPdXR1YnJvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyMDIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAExpc2JvYQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxMDUwAAAAADA2NQAAAExJU0JPQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwODAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF06 - SOD +data['3f00-5f00-ef06'] = {'data': 'd4IJzjCCCcoGCSqGSIb3DQEHAqCCCbswggm3AgEDMQ8wDQYJYIZIAWUDBAIBBQAwgcIGBmeBCAEBAaCBtwSBtDCBsQIBADANBglghkgBZQMEAgEFADCBnDAlAgEBBCDd5/mynI3R1aRbVZgVk+jD1C+AjJNoNKtAjvf9B3x8DzAlAgECBCDJAvR7Sqh5wjLqqgjuP86UHE+yloTQPSvSCCmYdKIBZDAlAgEDBCAh677FKrecKfQ8hT0HB2Q9lwcGKVpkZbHIx3mu4r3e1zAlAgEEBCCcuukEAJ/zIUw4YueB51GssQwODHGuaRghkxeAZAjH76CCBwAwggb8MIIE5KADAgECAghixyCzOBmdrzANBgkqhkiG9w0BAQUFADBVMSQwIgYDVQQDDBsoVGVzdDAyKUNhcnTDo28gZGUgQ2lkYWTDo28xETAPBgNVBAsMCEVDRXN0YWRvMQ0wCwYDVQQKDARTQ0VFMQswCQYDVQQGEwJQVDAeFw0xNDAyMDYxNjAxMTdaFw0xOTA0MjExNjExMTdaMIHaMVMwUQYDVQQDDEooVGVzdGUpIEVudGlkYWRlIENlcnRpZmljYWRvcmEgZGUgRG9jdW1lbnRvcyBkbyBDYXJ0w6NvIGRlIENpZGFkw6NvIDAwMDAxNDEtMCsGA1UECwwkRW50aWRhZGUgQ2VydGlmaWNhZG9yYSBkZSBEb2N1bWVudG9zMSkwJwYDVQQLDCBTZXJ2acOnb3MgZG8gQ2FydMOjbyBkZSBDaWRhZMOjbzEcMBoGA1UECgwTQ2FydMOjbyBkZSBDaWRhZMOjbzELMAkGA1UEBhMCUFQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSUE99kux+h2/pJpvcgEn0WVTi93bwcpP+AjyCkLlah9zXhvIV7FwsyP60nXbIqM7g1WUUEqxbWoqpHajmoctJF/Tg+eg6ZKQY+grwxcemCAC0gm7NO5dfLzObt/a1hdes1AXff5aOZtjmZwDTx9mUxvAmTcIxPd/Uj/8uONq//PW6TrfVUfQafn3h0bpwnwS/kTHnzBTbwd0oEwyaBn884GHZuh2r/utkVKlml3QRu33+Ae+L94SURFZTlqPWtzuClyYqggfn764Heu1gtaR2J1fA6M6Ey7332XGdNt6DGMmFykAfrugZKQKfGpOX/9CnG2Xr9qklFEsXtt0U2WeVAgMBAAGjggJIMIICRDAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQUwJg6yAe8OGE5Ph75atcK/7a4FGUwHwYDVR0jBBgwFoAUbEvUyUBV2JPc/xE8tabBhJ+l9QkwggEvBgNVHSAEggEmMIIBIjCBsQYLYIRsAQEBAgQAAQUwgaEwgZ4GCCsGAQUFBwICMIGRHoGOAGgAdAB0AHAAOgAvAC8AcABrAGkALgB0AGUAcwB0AGUALgBjAGEAcgB0AGEAbwBkAGUAYwBpAGQAYQBkAGEAbwAuAHAAdAAvAHAAdQBiAGwAaQBjAG8ALwBwAG8AbABpAHQAaQBjAGEAcwAvAHAAYwAvAGMAYwBfAEUAQwBEAF8AcABjAC4AaAB0AG0AbDBsBgpghGwBAQECBAAHMF4wXAYIKwYBBQUHAgEWUGh0dHA6Ly9wa2kudGVzdGUuY2FydGFvZGVjaWRhZGFvLnB0L3B1YmxpY28vcG9saXRpY2FzL2RwYy9jY19lY19jaWRhZGFvX2RwYy5odG1sMF0GA1UdHwRWMFQwUqBQoE6GTGh0dHA6Ly9wa2kudGVzdGUuY2FydGFvZGVjaWRhZGFvLnB0L3B1YmxpY28vbHJjL2NjX2VjX2NpZGFkYW9fY3JsdDAyX2NybC5jcmwwUgYIKwYBBQUHAQEERjBEMEIGCCsGAQUFBzABhjZodHRwOi8vb2NzcC5yb290LnRlc3RlLmNhcnRhb2RlY2lkYWRhby5wdC9wdWJsaWNvL29jc3AwDQYJKoZIhvcNAQEFBQADggIBAHdTkX741I2FGzds0qxVIepKollBNrMGNiumJM+zNRyjdsdnVaZQbYuSEs/vBIZ8ahWNUFugB4WsQSXr05Ee4QD5K7XcUukkAfFJSgv9JNZZw3DYHLfUmAiSWndsjJ2kK4hXwCcqMIBkqpncViN2D54D37VMB8LfPaajJwYJd+Vr5PJMmkGNwka+Et7cnuyXjdK+nH/wJZLo6ojAIMzUzw0Zvhjq/H4zzJHV+E2Eq9zJhwTbGn/FUuoNChPGNitzWEi16M1gPR6+TadA67RJskqTyDZyXsSQGX34zg03+l3Ez5i3xUSsXN+btQY6oezPNNi3OjBB7RA9kEhOcPqrlKOZgXRM6c/3IBJSAEoEqqCnrrVGVZsAWLcZgtY+fa1A61xxjOeCZnAZjKTWITbYi9jDvKtkF6FQia96q4bd2i36gDM30cCjF96naCOQqMLIjcrbSp85KiLmJzT7JMqJQ0+4JII2EaRpMa/kKPOWMcWZicPxE1FNf+XMjeLAk8721C5ciciLQTPUEEu216bISGS6Rmt2PlMPWM+rdMIkbqUkIVyuIVNCLFXWV/SuquPHloLfeXdtdBMn80XSV6ADvVtJ1dD/HLH/UV07p04wLFOoH8Z5WhM/Tm8sHorqsxVyctk4F26hvEld5h8r44PEWH5kKVafcUTNwRtN+OM4hRqeMYIB1jCCAdICAQEwYTBVMSQwIgYDVQQDDBsoVGVzdDAyKUNhcnTDo28gZGUgQ2lkYWTDo28xETAPBgNVBAsMCEVDRXN0YWRvMQ0wCwYDVQQKDARTQ0VFMQswCQYDVQQGEwJQVAIIYscgszgZna8wDQYJYIZIAWUDBAIBBQCgSDAVBgkqhkiG9w0BCQMxCAYGZ4EIAQEBMC8GCSqGSIb3DQEJBDEiBCBHmTqFNcIaJWXURbilQYBwJpVN6uR7FKrwG+UVCHPLhDANBgkqhkiG9w0BAQsFAASCAQCINsGudhOSW1QdF1xOIVjKWZN5z28ZHiYhVgwn7uuCKrN+eVmGokmLpi0iunHlnzsO2NJ5wB2d7nzqrgen+sXT2HsmXMmLaDzarKu2xabCZCuRLIYtIKmYtSbfF52LzAnNm44AV139ZwoCipSEInicRlIw3OAwue8hdrUPkhBgMhzc4AlrMU7zZ+f5h/QNf0b68qNhu3KFzCSykW46yctLGRY+M0pL2w0Flc5KWuGL8U46t4qRlkG6e9+gOwRuIqczNOXqpRgok7uQLIWr3GiFmcnYzay3f+w/+k2fMIy5rFdKe/1SUSnleluTQG/peXx2934eGt2kV9onoDtMUP1+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF07 - Perso Data +data["3f00-5f00-ef07"] = {'data': b2a_base64(bytes([0] * 1000)), + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF09 - Cert Auth +data["3f00-5f00-ef09"] = {'data': b2a_base64(user_auth.public_bytes(encoding=serialization.Encoding.DER)), + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +data["auth-private-key"] = {'data': b2a_base64(user_auth_private_key.private_bytes(encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption()))} + +# EF08 - Cert Sign +data["3f00-5f00-ef08"] = {'data': b2a_base64(user_sign.public_bytes(encoding=serialization.Encoding.DER)), + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +data["sign-private-key"] = {'data': b2a_base64(user_sign_private_key.private_bytes(encoding=serialization.Encoding.DER, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption()))} + +# EF11 - Cert Root +data["3f00-5f00-ef11"] = {'data': b2a_base64(ec_raizestado.public_bytes(encoding=serialization.Encoding.DER))} + +# EF10 - Cert Root Auth +data["3f00-5f00-ef10"] = {'data': b2a_base64(ec_auth.public_bytes(encoding=serialization.Encoding.DER)), + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + +# EF0F - Cert Root Sign +data["3f00-5f00-ef0f"] = {'data': b2a_base64(ec_sign.public_bytes(encoding=serialization.Encoding.DER)), + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + + +print("Creating Final Card Object") +for k in data: + for kk in data[k]: + if isinstance(data[k][kk], bytes): + data[k][kk] = data[k][kk].decode().strip() + +with open('card.json', 'w') as f: + content = json.dumps(data, indent=4, default=str) + f.write(content) + f.close() + +print("Card Generated to card.json") diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py index 3b4b6b57..6cf0418e 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py @@ -225,7 +225,6 @@ def make_property(prop, doc): lambda self: delattr(self, "_"+prop), doc) - class File(object): """Template class for a smartcard file.""" bertlv_data = make_property("bertlv_data", "list of (tag, length, " @@ -412,19 +411,20 @@ class DF(File): """Class for a dedicated file""" data = make_property("data", "unknown") content = make_property("content", "list of files of the DF") - dfname = make_property("dfname", "string with up to 16 bytes. DF name," - "which can also be used as application" - "identifier.") + dfname = b"" # make_property("dfname", "string with up to 16 bytes. DF name," + # "which can also be used as application" + # "identifier.") def __init__(self, parent, fid, filedescriptor=FDB["NOTSHAREABLEFILE"] | FDB["DF"], lifecycle=LCB["ACTIVATED"], - simpletlv_data=None, bertlv_data=None, dfname=None, data=""): + simpletlv_data=None, bertlv_data=None, dfname=None, data="", + extra_fci_data=b''): """ See File for more. """ File.__init__(self, parent, fid, filedescriptor, lifecycle, - simpletlv_data, bertlv_data) + simpletlv_data, bertlv_data, None, extra_fci_data) if dfname: if len(dfname) > 16: raise SwError(SW["ERR_INCORRECTPARAMETERS"]) @@ -515,10 +515,10 @@ def select(self, attribute, value, reference=REF["IDENTIFIER_FIRST"], # not found if isinstance(value, int): logging.debug("file (%s=%x) not found in:\n%s" % - (attribute, value, self)) + (attribute, value, self.fid)) elif isinstance(value, str): logging.debug("file (%s=%r) not found in:\n%s" % - (attribute, value, self)) + (attribute, value, self.fid)) raise SwError(SW["ERR_FILENOTFOUND"]) def remove(self, file): @@ -679,30 +679,43 @@ def _selectFile(self, p1, p2, data): P1_PATH_FROM_MF = 0x08 P1_PATH_FROM_CURRENTDF = 0x09 - if (p1 >> 4) != 0 or p1 == P1_FILE: + #if (p1 >> 4) != 0 or + if p1 == P1_FILE: + import binascii + logging.debug(f"p1 >> 4 or P1_FILE: {binascii.hexlify(data)}") # RFU OR # When P1='00', the card knows either because of a specific coding # of the file identifier or because of the context of execution of # the command if the file to select is the MF, a DF or an EF. - if data[:2] == inttostring(self.fid): - selected = walk(self, data[2:]) - elif data[:2] == inttostring(self.currentDF().fid): - selected = walk(self.currentDF(), data[2:]) - else: - selected = walk(self.currentDF(), data) + try: + if data[:2] == inttostring(self.fid): + selected = walk(self, data[2:]) + elif data[:2] == inttostring(self.currentDF().fid): + selected = walk(self.currentDF(), data[2:]) + else: + selected = walk(self.currentDF(), data) + except SwError as e: + # If everything fails, look at MF + selected = walk(self, data) + elif p1 == P1_CHILD_DF or p1 == P1_CHILD_EF: + logging.debug("P1_CHILD_DF or P1_CHILD_EF") selected = self.currentDF().select('fid', stringtoint(data)) if ((p1 == P1_CHILD_DF and not isinstance(selected, DF)) or (p1 == P1_CHILD_EF and not isinstance(selected, EF))): # Command incompatible with file structure raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) elif p1 == P1_PATH_FROM_MF: + logging.debug("P1 PATH FROM MF") selected = walk(self, data) elif p1 == P1_PATH_FROM_CURRENTDF: + logging.debug("P1 PATH FROM CURRENT DF") selected = walk(self.currentDF(), data) elif p1 == P1_PARENT_DF: + logging.debug("P1 PARENT DF") selected = self.current.parent elif p1 == P1_DF_NAME: + logging.debug("P1 DF NAME") df = self.currentDF() if df == self or df not in self.content: index_current = -1 @@ -748,7 +761,6 @@ def selectFile(self, p1, p2, data): data = bertlv_pack([(tag, len(fdm), fdm)]) self.current = file - logging.info("Selected %s" % file) return SW["NORMAL"], data @@ -761,8 +773,8 @@ def dataUnitsDecodePlain(self, p1, p2, data): list of data strings. """ if p1 >> 7: - # If bit 1 of INS is set to 0 and bit 8 of P1 to 1, then bits 7 - # and 6 of P1 are set to 00 (RFU), bits 5 to 1 of P1 encode a + # If bit 8 of INS is set to 1, then bits 7 + # and 6 of P1 are set to 00 (RFU), bits 3 to 1 of P1 encode a # short EF identifier and P2 (eight bits) encodes an offset # from zero to 255. ef = self.currentDF().select('shortfid', p1 & 0x1f) @@ -817,8 +829,8 @@ def readBinaryPlain(self, p1, p2, data): data as binary string. """ ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) - result = ef.readbinary(offsets[0]) + result = ef.readbinary(offsets[0]) return SW["NORMAL"], result def readBinaryEncapsulated(self, p1, p2, data): @@ -1423,7 +1435,8 @@ class EF(File): def __init__(self, parent, fid, filedescriptor, lifecycle=LCB["ACTIVATED"], simpletlv_data=None, bertlv_data=None, - datacoding=DCB["ONETIMEWRITE"], shortfid=0): + datacoding=DCB["ONETIMEWRITE"], shortfid=0, + extra_fci_data=b''): """ The constructor is supposed to be involved creation of a by creation of a TransparentStructureEF or RecordStructureEF. @@ -1439,7 +1452,7 @@ def __init__(self, parent, fid, filedescriptor, self.shortfid = shortfid File.__init__(self, parent, fid, filedescriptor, lifecycle, simpletlv_data, - bertlv_data) + bertlv_data, extra_fci_data=extra_fci_data) self.datacoding = datacoding @@ -1451,14 +1464,15 @@ def __init__(self, parent, fid, filedescriptor=FDB["EFSTRUCTURE_TRANSPARENT"], lifecycle=LCB["ACTIVATED"], simpletlv_data=None, bertlv_data=None, - datacoding=DCB["ONETIMEWRITE"], shortfid=0, data=""): + datacoding=DCB["ONETIMEWRITE"], shortfid=0, data="", + extra_fci_data=b''): """ See EF for more. """ EF.__init__(self, parent, fid, filedescriptor, lifecycle, simpletlv_data, bertlv_data, - datacoding, shortfid) + datacoding, shortfid, extra_fci_data) self.data = data def readbinary(self, offset): diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py b/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py index 41858863..4479795f 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/VirtualSmartcard.py @@ -451,6 +451,9 @@ def __init__(self, datasetfile, card_type, host, port, elif card_type == "cryptoflex": from virtualsmartcard.cards.cryptoflex import CryptoflexOS self.os = CryptoflexOS(MF, SAM) + elif card_type == "PTEID": + from virtualsmartcard.cards.PTEID import PTEIDOS + self.os = PTEIDOS(MF, SAM) elif card_type == "relay": from virtualsmartcard.cards.Relay import RelayOS from virtualsmartcard.cards.RelayMiddleman import RelayMiddleman diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py new file mode 100644 index 00000000..23d9d556 --- /dev/null +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py @@ -0,0 +1,285 @@ +# +# 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 . + +from virtualsmartcard.SmartcardSAM import SAM +from virtualsmartcard.SWutils import SwError, SW +from virtualsmartcard.SEutils import ControlReferenceTemplate, \ + Security_Environment +from virtualsmartcard.utils import C_APDU, hexdump +from virtualsmartcard.VirtualSmartcard import Iso7816OS +from virtualsmartcard.SmartcardFilesystem import MF, DF, EF +from virtualsmartcard.ConstantDefinitions import MAX_SHORT_LE +import virtualsmartcard.CryptoUtils as vsCrypto +from virtualsmartcard.utils import inttostring, stringtoint, C_APDU, R_APDU +import logging +from binascii import hexlify, b2a_base64, a2b_base64 +import sys +from Crypto.Cipher import PKCS1_v1_5 +from Crypto.PublicKey import RSA +from Crypto.Hash import SHA +import json + +logger = logging.getLogger('pteid') +logger.setLevel(logging.DEBUG) + +class PTEIDOS(Iso7816OS): + def __init__(self, mf, sam, ins2handler=None, maxle=MAX_SHORT_LE): + Iso7816OS.__init__(self, mf, sam, ins2handler, maxle) + self.atr = '\x3B\x7D\x95\x00\x00\x80\x31\x80\x65\xB0\x83\x11\x00\xC8\x83\x00\x90\x00' + + def execute(self, msg): + def notImplemented(*argz, **args): + raise SwError(SW["ERR_INSNOTSUPPORTED"]) + + logger.info("Command APDU (%d bytes):\n %s", len(msg), + hexdump(msg, indent=2)) + + try: + c = C_APDU(msg) + except ValueError as e: + logger.exception(f"Failed to parse {e} APDU {msg}") + return self.formatResult(False, 0, 0, "", + SW["ERR_INCORRECTPARAMETERS"]) + + try: + if c.ins == 0x80: + logger.info("Handle 0x80") + sw, result = self.sam.handle_0x80(c.p1, c.p2, c.data) + else: + sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, + c.p2, + c.data) + except SwError as e: + #logger.error(self.ins2handler.get(c.ins, None)) + logger.exception("SWERROR") + sw = e.sw + result = "" + except Exception as e: + logger.exception(f"ERROR: {e}") + + r = self.formatResult(c.ins, c.p1, c.p2, c.le, result, sw) + #logger.debug(f"End Result: SW:{hexlify(bytes(r[-2:])).decode('latin')} data={hexlify(result)[:12].decode('latin')}...") + return r + + def formatResult(self, ins, p1, p2, le, data, sw): + logger.debug( + f"FormatResult: ins={hex(ins)} le={le} length={len(data)} sw={hex(sw)}") + + if ins == 0x2a and p1 == 0x9e and p2 == 0x9a and le != len(data): + r = R_APDU(inttostring(0x6C00 + len(data))).render() + else: + if ins == 0xb0 and le == 0 or ins == 0xa4: + le = min(255, len(data)) + + if ins == 0xa4 and len(data): + self.lastCommandSW = sw + self.lastCommandOffcut = data + r = R_APDU(inttostring(SW["NORMAL_REST"] + + min(0xff, len(data) ))).render() + else: + r = Iso7816OS.formatResult(self, Iso7816OS.seekable(ins), le, + data, sw, False) + return r + + + +class PTEID_SE(Security_Environment): + + def __init__(self, MF, SAM): + Security_Environment.__init__(self, MF, SAM) + logger.debug("Using PTEID SE") + self.at.algorithm = 'SHA' + self.data_to_sign = b'' + self.signature = b'' + + logger.debug(f"AT: {self.at.algorithm}") + + def compute_digital_signature(self, p1, p2, data): + + """ + Compute a digital signature for the given data. + Algorithm and key are specified in the current SE + """ + if self.data_to_sign == b'': + return self.signature + + logging.info(f"Compute digital signature {hex(p1)} {hex(p2)} {len(self.data_to_sign)} {hexlify(self.data_to_sign)}") + + if p1 != 0x9E or p2 not in (0x9A, 0xAC, 0xBC): + raise SwError(SW["ERR_INCORRECTP1P2"]) + + if self.dst.key is None: + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + + if self.data_to_sign is None: + raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) + + to_sign = b'' + if p2 == 0x9A: # Data to be signed + to_sign = self.data_to_sign + elif p2 == 0xAC: # Data objects, sign values + to_sign = b'' + structure = unpack(data) + for tag, length, value in structure: + to_sign += value + elif p2 == 0xBC: # Data objects to be signed + logging.warning("Data objects to be signed: 0xBC") + pass + + logging.debug(f"Actual data signed: {hexlify(to_sign)}") + signature = self.dst.key.sign(to_sign, "") + return signature + + def hash(self, p1, p2, data): + """ + Hash the given data using the algorithm specified by the current + Security environment. + + :return: raw data (no TLV coding). + """ + logging.info(f"Compute Hash {hex(p1)} {hex(p2)} {data[2:]}") + hash = super().hash(p1, p2, data[2:]) + + self.data_to_sign = hash + + return hash + +class PTEID_SAM(SAM): + def __init__(self, mf=None, private_key=None): + SAM.__init__(self, None, None, mf, default_se=PTEID_SE) + self.current_SE = self.default_se(self.mf, self) + self.PIN = b'1111' + + self.current_SE.ht.algorithm = "SHA" + self.current_SE.algorithm = "AES-CBC" + + + self.current_SE.dst.key = RSA.importKey(private_key) + + def handle_0x80(self, p1, p2, data): + logger.debug(f"Handle 0x80 {hex(p1)} {hex(p2)} {hex(data)}") + sys.exit(-1) + return 0x9000, b'' + + def parse_SE_config(self, config): + r = 0x9000 + logger.debug(type(config)) + logger.debug(f"Parse SE config {hexlify(config)}") + + try: + ControlReferenceTemplate.parse_SE_config(self, config) + except SwError as e: + logger.exception("e") + + return r, b'' + + def verify(self, p1, p2, PIN): + logging.debug("Received PIN: %s", PIN.strip()) + PIN = PIN.replace(b"\0", b"").strip() # Strip NULL characters + PIN = PIN.replace(b"\xFF", b"") # Strip \xFF characters + logging.debug("PIN to use: %s", PIN) + + if len(PIN) == 0: + raise SwError(SW["WARN_NOINFO63"]) + + return super().verify(p1, p2, PIN) + +class PTEID_MF(MF): # {{{ + def getDataPlain(self, p1, p2, data): + + logger.info( + f"GetData Plain {hex(p1)} {hex(p2)} {hexlify(data)}") + + tag = (p1 << 8) + p2 + if tag == 0xDF03: + return 0x6E00, b'' + + sys.exit(0) + return 0x9000, b'' + + def readBinaryPlain(self, p1, p2, data): + logger.debug(f"Read Binary {hex(p1)} {hex(p2)}") + if p2 != 0: + p1 = 0 + p2 = 0 + try: + sw, result = super().readBinaryPlain(p1, p2, data) + return 0x9000, result + except Exception as e: + logger.exception(f"{e}") + + + def selectFile(self, p1, p2, data): + # return super().selectFile(p1, p2, data) + """ + Function for instruction 0xa4. Takes the parameter bytes 'p1', 'p2' as + integers and 'data' as binary string. Returns the status bytes as two + byte long integer and the response data as binary string. + """ + logger.debug(f"SelectFile: fid=0x{hexlify(data).decode('latin')} p2={p2}") + + P2_FCI = p2 & (3 << 2) == 0 + P2_FCP = p2 & 4 != 0 + P2_FMD = p2 & 8 != 0 + P2_NONE = 3 << 2 + + + logger.debug(f"FCI: {P2_FCI}, FCP: {P2_FCP} FMD:{P2_FMD}") + + # Will fail with exception if File Not Found + file = self._selectFile(p1, p2, data) + self.current = file + + if isinstance(file, EF): + logger.debug("IS EF") + fci = self.get_fci(file) + return 0x9000, fci + + elif isinstance(file, DF): + # Search by Name + if p1 == 0x04: + return 0x9000, b'' + + logger.debug("IS DF") + fci = self.get_fci(file) + return 0x9000, fci + + def get_fci(self, file): + try: + if isinstance(file, EF): + fcid = b'\x6F\x15' + \ + b'\x81\x02' + len(file.data).to_bytes(2, byteorder='big') + \ + b'\x82\x01\x01' + \ + b'\x8a\x01\x05' +\ + b'\x83\x02' + file.fid.to_bytes(2, byteorder='big')+ \ + file.extra_fci_data + else: + fcid = b'\x6F\x14' + \ + b'\x83\x02' + file.fid.to_bytes(2, byteorder='big') + \ + file.extra_fci_data + print(file.extra_fci_data) + name = getattr(file, 'dfname') + if len(name) > 0: + fcid = fcid + b'\x84' + len(name).to_bytes(1, byteorder='big') + name + + return fcid + except Exception as e: + logger.exception(f"get fci: {e}") + return b'' + +# }}} From 419b3cd4346e32af2d034ac3739e061499f7fa6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Fri, 3 Sep 2021 17:06:25 +0000 Subject: [PATCH 04/11] Added PTEID README.md --- pteid/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pteid/README.md diff --git a/pteid/README.md b/pteid/README.md new file mode 100644 index 00000000..8fa13474 --- /dev/null +++ b/pteid/README.md @@ -0,0 +1,9 @@ +# Portuguese Electronic Identity Cards + +Basic support for PTEID cards is added. +The cards cannot be used with the standard PKI and tools as the objects are signed by a self generated CA. +The Object structure, permissions and certificates tries to follow the same structure as original cards. + +This is useful for development purposes, especially when using the PKCS11 interface. +Some objects may be inconsistent as they were copied from a development card. Further developments will focus on properly emulating PTEID cards. + From 78a1e3056f5d355def51e84ef49c2cb086b0ab84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Fri, 3 Sep 2021 18:20:52 +0100 Subject: [PATCH 05/11] Update PTEID.py --- virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py index 23d9d556..77089029 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py @@ -1,6 +1,6 @@ # -# Copyright (C) 2011 Dominik Oepen -# +# Copyright (C) 2021 João Paulo Barraca +# # This file is part of virtualsmartcard. # # virtualsmartcard is free software: you can redistribute it and/or modify it @@ -73,7 +73,6 @@ def notImplemented(*argz, **args): logger.exception(f"ERROR: {e}") r = self.formatResult(c.ins, c.p1, c.p2, c.le, result, sw) - #logger.debug(f"End Result: SW:{hexlify(bytes(r[-2:])).decode('latin')} data={hexlify(result)[:12].decode('latin')}...") return r def formatResult(self, ins, p1, p2, le, data, sw): From 3ccab73527d8c0e442d864e90cb1a65c49223c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Mon, 6 Dec 2021 18:09:10 +0000 Subject: [PATCH 06/11] Added PTEID to arguments --- virtualsmartcard/src/vpicc/vicc.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtualsmartcard/src/vpicc/vicc.in b/virtualsmartcard/src/vpicc/vicc.in index 983a61d9..fedaf563 100644 --- a/virtualsmartcard/src/vpicc/vicc.in +++ b/virtualsmartcard/src/vpicc/vicc.in @@ -35,7 +35,7 @@ Report bugs to @PACKAGE_BUGREPORT@ parser.add_argument("-t", "--type", action="store", - choices=['iso7816', 'cryptoflex', 'ePass', 'nPA', 'relay', 'handler_test'], + choices=['iso7816', 'cryptoflex', 'ePass', 'nPA', 'PTEID', 'relay', 'handler_test'], default='iso7816', help="type of smart card to emulate (default: %(default)s)") parser.add_argument("-v", "--verbose", From b98566b8c9b3b26b1a44f946d895c7ce28fa1175 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Mon, 6 Dec 2021 18:15:41 +0000 Subject: [PATCH 07/11] Use binary strings in ConstantDefinitions --- .../virtualsmartcard/ConstantDefinitions.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/ConstantDefinitions.py b/virtualsmartcard/src/vpicc/virtualsmartcard/ConstantDefinitions.py index d81e2210..eba77192 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/ConstantDefinitions.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/ConstantDefinitions.py @@ -215,20 +215,20 @@ # This table maps algorithms to numbers. It is used in the security environment ALGO_MAPPING = {} -ALGO_MAPPING[0x00] = "DES3-ECB" -ALGO_MAPPING[0x01] = "DES3-CBC" -ALGO_MAPPING[0x02] = "AES-ECB" -ALGO_MAPPING[0x03] = "AES-CBC" -ALGO_MAPPING[0x04] = "DES-ECB" -ALGO_MAPPING[0x05] = "DES-CBC" -ALGO_MAPPING[0x06] = "MD5" -ALGO_MAPPING[0x07] = "SHA" -ALGO_MAPPING[0x08] = "SHA256" -ALGO_MAPPING[0x09] = "MAC" -ALGO_MAPPING[0x0A] = "HMAC" -ALGO_MAPPING[0x0B] = "CC" -ALGO_MAPPING[0x0C] = "RSA" -ALGO_MAPPING[0x0D] = "DSA" +ALGO_MAPPING[b'\x00'] = "DES3-ECB" +ALGO_MAPPING[b'\x01'] = "DES3-CBC" +ALGO_MAPPING[b'\x02'] = "AES-ECB" +ALGO_MAPPING[b'\x03'] = "AES-CBC" +ALGO_MAPPING[b'\x04'] = "DES-ECB" +ALGO_MAPPING[b'\x05'] = "DES-CBC" +ALGO_MAPPING[b'\x06'] = "MD5" +ALGO_MAPPING[b'\x07'] = "SHA" +ALGO_MAPPING[b'\x08'] = "SHA256" +ALGO_MAPPING[b'\x09'] = "MAC" +ALGO_MAPPING[b'\x0a'] = "HMAC" +ALGO_MAPPING[b'\x0b'] = "CC" +ALGO_MAPPING[b'\x0c'] = "RSA" +ALGO_MAPPING[b'\x0d'] = "DSA" # Recerence control for select command, and record handling commands REF = { From 0065c16a8288c90cb38cdeee36e5a2798f3f60dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Mon, 6 Dec 2021 18:54:39 +0000 Subject: [PATCH 08/11] Updated SEutils to match the ConstantDefinitions --- virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py index 224afd4e..13c36aac 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py @@ -101,10 +101,10 @@ def __set_algo(self, data): :param data: reference to an algorithm """ - if data[0] not in ALGO_MAPPING: + if data not in ALGO_MAPPING: raise SwError(SW["ERR_REFNOTUSABLE"]) else: - self.algorithm = ALGO_MAPPING[data[0]] + self.algorithm = ALGO_MAPPING[data] self.__replace_tag(0x80, data) def __set_key(self, tag, value): From 8d09ad91d945e23cdbdb1e3e09a6d13ee85f76bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Mon, 6 Dec 2021 19:09:43 +0000 Subject: [PATCH 09/11] Added reference to PTeID --- virtualsmartcard/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/virtualsmartcard/README.md b/virtualsmartcard/README.md index 02da17a7..b6fe021f 100644 --- a/virtualsmartcard/README.md +++ b/virtualsmartcard/README.md @@ -8,6 +8,7 @@ Currently the Virtual Smart Card supports the following types of smart cards: (PACE, TA, CA) - Electronic passport (ePass/MRTD) with support for BAC - Cryptoflex smart card (incomplete) +- Portuguese eID (PTeID) The vpcd is a smart card reader driver for [PCSC-Lite](https://pcsclite.apdu.fr/) and the windows smart card service. It allows smart card applications to access the vpicc through From bd2cb223f46ae8cb61eed1d7ae6ea85246dd5b0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Tue, 8 Nov 2022 10:52:12 +0000 Subject: [PATCH 10/11] Update to handle proper signature creation and verification with OpenSC --- pteid/generate_pteid_card.py | 139 ++++++++++++---- .../vpicc/virtualsmartcard/CardGenerator.py | 8 +- .../virtualsmartcard/SmartcardFilesystem.py | 46 +++--- .../src/vpicc/virtualsmartcard/cards/PTEID.py | 151 +++++++++++------- 4 files changed, 234 insertions(+), 110 deletions(-) diff --git a/pteid/generate_pteid_card.py b/pteid/generate_pteid_card.py index 9b3e4fe6..676ae1d5 100644 --- a/pteid/generate_pteid_card.py +++ b/pteid/generate_pteid_card.py @@ -73,7 +73,7 @@ def generate_user(subject, duration=365 * 15, issuer=None, signing_key=None, one_day = datetime.timedelta(1, 0, 0) private_key = rsa.generate_private_key( public_exponent=65537, - key_size=1024, + key_size=3072, backend=default_backend() ) @@ -136,7 +136,7 @@ def load_private_key(fname): def dump_certificate(fname, cert): with open(fname + '.pem', 'wb') as f: - f.write(ec_raizestado.public_bytes(serialization.Encoding.PEM)) + f.write(cert.public_bytes(serialization.Encoding.PEM)) def dump_private_key(fname, pkey): with open(fname + '.key', 'wb') as f: @@ -298,50 +298,131 @@ def load_cert_key(fname): # TODO: SOD and other objects are copied from devel cards and need to be created from scratch # 3F00 -data['3f00-0001'] = {'name': b2a_base64(b'\x60\x46\x32\xFF\x00\x00\x02')} +data['3f00'] = {'name': b2a_base64(b"\x60\x46\x32\xff\x00\x00\x02") } data['3f00-0003'] = {'data': 'AAAgBAEB', - 'fci': b2a_base64(b'\x8C\x05\x1B\x14\xFF\x14\x00')} - + 'fci': b2a_base64(b'\x8C\x05\x1B\x14\x00\x14\x00')} + +# 2F00 EF.DIR +""" + 0:d=0 hl=2 l= 29 cons: appl [ 1 ] + 2:d=1 hl=2 l= 9 prim: appl [ 15 ] + 13:d=1 hl=2 l= 12 prim: appl [ 16 ] + 27:d=1 hl=2 l= 2 prim: appl [ 17 ] + 31:d=0 hl=2 l= 0 prim: EOC +""" data['3f00-2f00'] = {'data': 'YR1PCURGIGlzc3VlclAMUE9SVFVHQUwgRUlEUQJPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # 4F00 - ADF CIA -data['3f00-4f00'] = {'data': '', 'name': b2a_base64(b'\x44\x46\x20\x69\x73\x73\x75\x65\x72'), +data['3f00-4f00'] = {'data': '', + 'name': b2a_base64(b'\x44\x46\x20\x69\x73\x73\x75\x65\x72'), 'fci': b2a_base64(b'\x8C\x03\x06\x00\x00')} -# 5031 - EF OD +# 5031 - EF OD - Defines 4 CIO EFs: PuKD, PrKD, AOD, 5F004401 +""" + 0:d=0 hl=2 l= 10 cons: cont [ 0 ] # PRIVATE KEYS + 2:d=1 hl=2 l= 8 cons: SEQUENCE + 4:d=2 hl=2 l= 6 prim: OCTET STRING + 0000 - 3f 00 5f 00 ef 0d ?._... + 12:d=0 hl=2 l= 10 cons: cont [ 1 ] # PUBLIC KEYS + 14:d=1 hl=2 l= 8 cons: SEQUENCE + 16:d=2 hl=2 l= 6 prim: OCTET STRING + 0000 - 3f 00 5f 00 ef 0e ?._... + 24:d=0 hl=2 l= 10 cons: cont [ 4 ] # CERTIFICATES + 26:d=1 hl=2 l= 8 cons: SEQUENCE + 28:d=2 hl=2 l= 6 prim: OCTET STRING + 0000 - 3f 00 5f 00 ef 0c ?._... + 36:d=0 hl=2 l= 10 cons: cont [ 8 ] # AUTH OBJECTS + 38:d=1 hl=2 l= 8 cons: SEQUENCE + 40:d=2 hl=2 l= 6 prim: OCTET STRING + 0000 - 3f 00 5f 00 44 01 ?._.D. + 48:d=0 hl=2 l= 0 prim: EOC +""" data['3f00-4f00-5031'] = {'data': 'oAowCAQGPwBfAO8NoQowCAQGPwBfAO8OpAowCAQGPwBfAO8MqAowCAQGPwBfAEQBAAAAAAAAAAAAAAAA', - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # 5032 - EF CIA -data['3f00-4f00-5032'] = {'data': 'MCwCAQEECJmZAAAAA1U5DAdHRU1BTFRPgBFDQVJUQU8gREUgQ0lEQURBTwMBAAAA', - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} +""" + 0:d=0 hl=2 l= 44 cons: SEQUENCE + 2:d=1 hl=2 l= 1 prim: INTEGER :01 + 5:d=1 hl=2 l= 8 prim: OCTET STRING + 0000 - 99 99 00 00 00 03 55 39- ......U9 + 15:d=1 hl=2 l= 7 prim: UTF8STRING :GEMALTO + 24:d=1 hl=2 l= 17 prim: cont [ 0 ] + 43:d=1 hl=2 l= 1 prim: BIT STRING + 0000 - 00 . + 46:d=0 hl=2 l= 0 prim: EOC +""" +data['3f00-4f00-5032'] = {'data': 'MCwCAQEECAAABQg5OVZZDAdHRU1BTFRPgBFDQVJUQU8gREUgQ0lEQURBTwMBAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # 5F00 ADF PKI data['3f00-5f00'] = {'name': b2a_base64(b'\x44\x46\x20\x69\x73\x73\x75\x65\x73'), 'fci': b2a_base64(b'\x8C\x03\x06\x00\x00')} -# 4401 - EF AOD -data['3f00-5f00-4401'] = {'data': 'MDowFwwVUElOIGRhIEF1dGVudGljYcOnw6NvMAcEAQECAgCBoRYwFAMCA4gKAQECAQQCAQiAAgCBBAH/MDYwEwwRUElOIGRhIEFzc2luYXR1cmEwBwQBAgICAIKhFjAUAwIDiAoBAQIBBAIBCIACAIIEAf8wMjAPDA1QSU4gZGEgTW9yYWRhMAcEAQMCAgCDoRYwFAMCA4gKAQECAQQCAQiAAgCDBAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} -# EF0C - CertID + +# 4401 - EF AOD - AUTHENTICATION OBJECTS +""" + 4:d=2 hl=2 l= 21 prim: UTF8STRING :PIN da Autenticação + 64:d=2 hl=2 l= 17 prim: UTF8STRING :PIN da Assinatura + 120:d=2 hl=2 l= 13 prim: UTF8STRING :PIN da Morada +""" +data['3f00-5f00-4401'] = {'data': 'MDowFwwVUElOIGRhIEF1dGVudGljYcOnw6NvMAcEAQECAgCBoRYwFAMCA4gKAQECAQQCAQiAAgCBBAH/MDYwEwwRUElOIGRhIEFzc2luYXR1cmEwBwQBAgICAIKhFjAUAwIDiAoBAQIBBAIBCIACAIIEAf8wMjAPDA1QSU4gZGEgTW9yYWRhMAcEAQMCAgCDoRYwFAMCA4gKAQECAQQCAQiAAgCDBAH/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} + + +# EF0C - CertID - CERTIFICATES +""" + 0:d=0 hl=2 l= 57 cons: SEQUENCE + 2:d=1 hl=2 l= 31 cons: SEQUENCE + 4:d=2 hl=2 l= 26 prim: UTF8STRING :CITIZEN AUTHENTICATION KEY + 32:d=2 hl=2 l= 1 prim: OCTET STRING + 0000 - 01 . + 35:d=1 hl=2 l= 10 cons: SEQUENCE + 37:d=2 hl=2 l= 1 prim: OCTET STRING :E + 40:d=2 hl=2 l= 2 prim: BIT STRING + 0000 - 05 20 . + 44:d=2 hl=2 l= 1 prim: INTEGER :02 + 47:d=1 hl=2 l= 10 cons: cont [ 1 ] + 49:d=2 hl=2 l= 8 cons: SEQUENCE + 51:d=3 hl=2 l= 2 cons: SEQUENCE + 53:d=4 hl=2 l= 0 prim: OCTET STRING + 55:d=3 hl=2 l= 2 prim: INTEGER :0400 + 59:d=0 hl=2 l= 56 cons: SEQUENCE + 61:d=1 hl=2 l= 29 cons: SEQUENCE + 63:d=2 hl=2 l= 21 prim: UTF8STRING :CITIZEN SIGNATURE KEY + 86:d=2 hl=2 l= 1 prim: OCTET STRING + 0000 - 02 . + 89:d=2 hl=2 l= 1 prim: INTEGER :01 + 92:d=1 hl=2 l= 11 cons: SEQUENCE + 94:d=2 hl=2 l= 1 prim: OCTET STRING :F + 97:d=2 hl=2 l= 3 prim: BIT STRING + 0000 - 06 00 40 ..@ + 102:d=2 hl=2 l= 1 prim: INTEGER :01 + 105:d=1 hl=2 l= 10 cons: cont [ 1 ] + 107:d=2 hl=2 l= 8 cons: SEQUENCE + 109:d=3 hl=2 l= 2 cons: SEQUENCE + 111:d=4 hl=2 l= 0 prim: OCTET STRING + 113:d=3 hl=2 l= 2 prim: INTEGER :0400 + 117:d=0 hl=2 l= 0 prim: EOC +""" data['3f00-5f00-ef0c'] = {'data': 'MDkwJAwiQ0lUSVpFTiBBVVRIRU5USUNBVElPTiBDRVJUSUZJQ0FURTADBAFFoQwwCjAIBAY/AF8A7wkwNDAfDB1DSVRJWkVOIFNJR05BVFVSRSBDRVJUSUZJQ0FURTADBAFGoQwwCjAIBAY/AF8A7wgwJzASDBBTSUdOQVRVUkUgU1VCIENBMAMEAVGhDDAKMAgEBj8AXwDvDzAsMBcMFUFVVEhFTlRJQ0FUSU9OIFNVQiBDQTADBAFSoQwwCjAIBAY/AF8A7xAwHjAJDAdST09UIENBMAMEAVChDDAKMAgEBj8AXwDvEQ==', 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} # EF12 - CertUserD data['3f00-5f00-ef12'] = {'data': 'MEYwMQwiQ2l0aXplbiBBdXRoZW50aWNhdGlvbiBDZXJ0aWZpY2F0ZQMBADAIMAYDAgeABQAwAwQBRaEMMAowCAQGPwBfAO8JMEEwLAwdQ2l0aXplbiBTaWduYXR1cmUgQ2VydGlmaWNhdGUDAQAwCDAGAwIHgAUAMAMEAUahDDAKMAgEBj8AXwDvCA==', - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # EF0D - PrKD -data['3f00-5f00-ef0d'] = {'data': 'MDkwHwwaQ0lUSVpFTiBBVVRIRU5USUNBVElPTiBLRVkEAQEwCgQBRQMCBSACAQKhCjAIMAIEAAICBAAwODAdDBVDSVRJWkVOIFNJR05BVFVSRSBLRVkEAQICAQEwCwQBRgMDBgBAAgEBoQowCDACBAACAgQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} +data['3f00-5f00-ef0d'] = {'data': 'MDkwHwwaQ0lUSVpFTiBBVVRIRU5USUNBVElPTiBLRVkEAQEwCgQBRQMCBSACAQKhCjAIMAIEAAICDAAwODAdDBVDSVRJWkVOIFNJR05BVFVSRSBLRVkEAQICAQEwCwQBRgMDBgBAAgEBoQowCDACBAACAgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # EF0E - PuKD -data['3f00-5f00-ef0e'] = {'data': 'MEwwLgwaQ2l0aXplbiBBdXRoZW50aWNhdGlvbiBLZXkDAgeABAGBMAkwBwMCBSAEAYEwDgQBRQMCAkQDAgO4AgECoQowCDACBAACAgQAMEgwKQwVQ2l0aXplbiBTaWduYXR1cmUgS2V5AwIHgAQBgjAJMAcDAgUgBAGCMA8EAUYDAwYgQAMCA7gCAQGhCjAIMAIEAAICBAA=', - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} +data['3f00-5f00-ef0e'] = {'data': 'MEwwLgwaQ2l0aXplbiBBdXRoZW50aWNhdGlvbiBLZXkDAgeABAGBMAkwBwMCBSAEAYEwDgQBRQMCAkQDAgO4AgECoQowCDACBAACAgwAMEgwKQwVQ2l0aXplbiBTaWduYXR1cmUgS2V5AwIHgAQBgjAJMAcDAgUgBAGCMA8EAUYDAwYgQAMCA7gCAQGhCjAIMAIEAAICDAA=', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # EF02 - ID -data['3f00-5f00-ef02'] = {'data': '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', +data['3f00-5f00-ef02'] = { 'data': data['3f00-4f00-5032']['data'], 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} # EF05 - Addr @@ -349,12 +430,12 @@ def load_cert_key(fname): 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} # EF06 - SOD -data['3f00-5f00-ef06'] = {'data': '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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==', - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} +data['3f00-5f00-ef06'] = {'data': data['3f00-4f00-5032']['data'], + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # EF07 - Perso Data data["3f00-5f00-ef07"] = {'data': b2a_base64(bytes([0] * 1000)), - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # EF09 - Cert Auth data["3f00-5f00-ef09"] = {'data': b2a_base64(user_auth.public_bytes(encoding=serialization.Encoding.DER)), @@ -366,22 +447,24 @@ def load_cert_key(fname): # EF08 - Cert Sign data["3f00-5f00-ef08"] = {'data': b2a_base64(user_sign.public_bytes(encoding=serialization.Encoding.DER)), - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} data["sign-private-key"] = {'data': b2a_base64(user_sign_private_key.private_bytes(encoding=serialization.Encoding.DER, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()))} # EF11 - Cert Root -data["3f00-5f00-ef11"] = {'data': b2a_base64(ec_raizestado.public_bytes(encoding=serialization.Encoding.DER))} +data["3f00-5f00-ef11"] = {'data': b2a_base64(ec_raizestado.public_bytes(encoding=serialization.Encoding.DER)), + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} + # EF10 - Cert Root Auth data["3f00-5f00-ef10"] = {'data': b2a_base64(ec_auth.public_bytes(encoding=serialization.Encoding.DER)), - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} # EF0F - Cert Root Sign data["3f00-5f00-ef0f"] = {'data': b2a_base64(ec_sign.public_bytes(encoding=serialization.Encoding.DER)), - 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\xFF\xFF\x00')} + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} print("Creating Final Card Object") diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py index 957e0917..3aebeb54 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py @@ -340,7 +340,7 @@ def __generate_nPA(self): "ReligiousArtisticName" in self.datagroups else '' AcademicTitle = self.datagroups["AcademicTitle"] if \ "AcademicTitle" in self.datagroups else '' - DateOfBirth = self.datagroups["DateOfBirth"] if "DateOfBirth" in \ + aDateOfBirth = self.datagroups["DateOfBirth"] if "DateOfBirth" in \ self.datagroups else '19640812' PlaceOfBirth = self.datagroups["PlaceOfBirth"] if "PlaceOfBirth" in \ self.datagroups else 'BERLIN' @@ -690,12 +690,14 @@ def ___get_fs_entry(data, fid): import json from binascii import a2b_base64 logging.info("Opening card.json") - + f = open('card.json', 'r') data = json.loads(f.read()) f.close() + + name, fci, fdata = ___get_fs_entry(data, '3f00') + self.mf = PTEID_MF(dfname=name) - self.mf = PTEID_MF() name, fci, fdata = ___get_fs_entry(data, '3f00-0001') fk = DF(parent=self.mf, fid=0x0001, dfname=name, diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py index 6cf0418e..395036d0 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py @@ -411,9 +411,9 @@ class DF(File): """Class for a dedicated file""" data = make_property("data", "unknown") content = make_property("content", "list of files of the DF") - dfname = b"" # make_property("dfname", "string with up to 16 bytes. DF name," - # "which can also be used as application" - # "identifier.") + dfname = make_property("dfname", "string with up to 16 bytes. DF name," + "which can also be used as application" + "identifier.") def __init__(self, parent, fid, filedescriptor=FDB["NOTSHAREABLEFILE"] | FDB["DF"], @@ -507,18 +507,20 @@ def select(self, attribute, value, reference=REF["IDENTIFIER_FIRST"], for i in indexes: file = search_in[i] - if (hasattr(file, attribute) and - ((getattr(file, attribute) == value) or + if hasattr(file, attribute): + data = getattr(file, attribute) + + if (data == value) or \ (attribute == 'dfname' and - getattr(file, attribute).startswith(value)))): - return file + data[:min(len(data), len(value))] == value): + return file # not found if isinstance(value, int): - logging.debug("file (%s=%x) not found in:\n%s" % - (attribute, value, self.fid)) + logging.debug("file (%s=%x) not found in: %s" % + (attribute, value, hex(self.fid))) elif isinstance(value, str): - logging.debug("file (%s=%r) not found in:\n%s" % - (attribute, value, self.fid)) + logging.debug("file (%s=%r) not found in: %s" % + (attribute, value, hex(self.fid))) raise SwError(SW["ERR_FILENOTFOUND"]) def remove(self, file): @@ -678,7 +680,7 @@ def _selectFile(self, p1, p2, data): P1_DF_NAME = 0x04 P1_PATH_FROM_MF = 0x08 P1_PATH_FROM_CURRENTDF = 0x09 - + logging.debug(f"P1: {p1} P2:{p2}") #if (p1 >> 4) != 0 or if p1 == P1_FILE: import binascii @@ -687,16 +689,16 @@ def _selectFile(self, p1, p2, data): # When P1='00', the card knows either because of a specific coding # of the file identifier or because of the context of execution of # the command if the file to select is the MF, a DF or an EF. - try: - if data[:2] == inttostring(self.fid): - selected = walk(self, data[2:]) - elif data[:2] == inttostring(self.currentDF().fid): - selected = walk(self.currentDF(), data[2:]) - else: - selected = walk(self.currentDF(), data) - except SwError as e: + #try: + if data[:2] == inttostring(self.fid): + selected = walk(self, data[2:]) + elif data[:2] == inttostring(self.currentDF().fid): + selected = walk(self.currentDF(), data[2:]) + else: + selected = walk(self.currentDF(), data) + #except SwError as e: # If everything fails, look at MF - selected = walk(self, data) + #selected = walk(self, data) elif p1 == P1_CHILD_DF or p1 == P1_CHILD_EF: logging.debug("P1_CHILD_DF or P1_CHILD_EF") @@ -707,7 +709,7 @@ def _selectFile(self, p1, p2, data): raise SwError(SW["ERR_INCOMPATIBLEWITHFILE"]) elif p1 == P1_PATH_FROM_MF: logging.debug("P1 PATH FROM MF") - selected = walk(self, data) + selected = walk(self.getMF(), data) elif p1 == P1_PATH_FROM_CURRENTDF: logging.debug("P1 PATH FROM CURRENT DF") selected = walk(self.currentDF(), data) diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py index 77089029..1418fbbd 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py @@ -29,36 +29,41 @@ import logging from binascii import hexlify, b2a_base64, a2b_base64 import sys -from Crypto.Cipher import PKCS1_v1_5 -from Crypto.PublicKey import RSA -from Crypto.Hash import SHA import json logger = logging.getLogger('pteid') logger.setLevel(logging.DEBUG) +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.serialization import load_der_private_key +from cryptography.hazmat.backends import default_backend + class PTEIDOS(Iso7816OS): def __init__(self, mf, sam, ins2handler=None, maxle=MAX_SHORT_LE): Iso7816OS.__init__(self, mf, sam, ins2handler, maxle) - self.atr = '\x3B\x7D\x95\x00\x00\x80\x31\x80\x65\xB0\x83\x11\x00\xC8\x83\x00\x90\x00' + self.atr = '\x3B\xFF\x96\x00\x00\x81\x31\xFE\x43\x80\x31\x80\x65\xB0\x85\x04\x01\x20\x12\x0F\xFF\x82\x90\x00\xD0' def execute(self, msg): def notImplemented(*argz, **args): raise SwError(SW["ERR_INSNOTSUPPORTED"]) - logger.info("Command APDU (%d bytes):\n %s", len(msg), + logger.debug("Command APDU (%d bytes):\n %s", len(msg), hexdump(msg, indent=2)) - + try: c = C_APDU(msg) except ValueError as e: logger.exception(f"Failed to parse {e} APDU {msg}") return self.formatResult(False, 0, 0, "", SW["ERR_INCORRECTPARAMETERS"]) + + result = '' + sw = 0x900 try: + logger.debug(f"Handle {hex(c.ins)}") if c.ins == 0x80: - logger.info("Handle 0x80") sw, result = self.sam.handle_0x80(c.p1, c.p2, c.data) else: sw, result = self.ins2handler.get(c.ins, notImplemented)(c.p1, @@ -68,30 +73,30 @@ def notImplemented(*argz, **args): #logger.error(self.ins2handler.get(c.ins, None)) logger.exception("SWERROR") sw = e.sw - result = "" except Exception as e: logger.exception(f"ERROR: {e}") + if isinstance(result, str): + result = result.encode() + + logger.debug(f"Result: {hexlify(result)} {hex(sw)}") r = self.formatResult(c.ins, c.p1, c.p2, c.le, result, sw) return r def formatResult(self, ins, p1, p2, le, data, sw): logger.debug( - f"FormatResult: ins={hex(ins)} le={le} length={len(data)} sw={hex(sw)}") + f"FormatResult: ins={hex(ins)} p1={hex(p1)} p2={hex(p2)} le={le} length={len(data)} sw={hex(sw)}") - if ins == 0x2a and p1 == 0x9e and p2 == 0x9a and le != len(data): - r = R_APDU(inttostring(0x6C00 + len(data))).render() - else: - if ins == 0xb0 and le == 0 or ins == 0xa4: - le = min(255, len(data)) + if ins == 0xb0 and le == 0 or ins == 0xa4: + le = min(255, len(data)) - if ins == 0xa4 and len(data): - self.lastCommandSW = sw - self.lastCommandOffcut = data - r = R_APDU(inttostring(SW["NORMAL_REST"] + + if ins == 0xa4 and len(data): + self.lastCommandSW = sw + self.lastCommandOffcut = data + r = R_APDU(inttostring(SW["NORMAL_REST"] + min(0xff, len(data) ))).render() - else: - r = Iso7816OS.formatResult(self, Iso7816OS.seekable(ins), le, + else: + r = Iso7816OS.formatResult(self, Iso7816OS.seekable(ins), le, data, sw, False) return r @@ -114,10 +119,11 @@ def compute_digital_signature(self, p1, p2, data): Compute a digital signature for the given data. Algorithm and key are specified in the current SE """ + if self.data_to_sign == b'': return self.signature - logging.info(f"Compute digital signature {hex(p1)} {hex(p2)} {len(self.data_to_sign)} {hexlify(self.data_to_sign)}") + logger.debug(f"Compute digital signature p1={hex(p1)} p2={hex(p2)} dsl={len(self.data_to_sign)} ds={hexlify(self.data_to_sign)} d={data}") if p1 != 0x9E or p2 not in (0x9A, 0xAC, 0xBC): raise SwError(SW["ERR_INCORRECTP1P2"]) @@ -129,20 +135,28 @@ def compute_digital_signature(self, p1, p2, data): raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) to_sign = b'' + if p2 == 0x9A: # Data to be signed to_sign = self.data_to_sign + elif p2 == 0xAC: # Data objects, sign values to_sign = b'' structure = unpack(data) for tag, length, value in structure: to_sign += value elif p2 == 0xBC: # Data objects to be signed - logging.warning("Data objects to be signed: 0xBC") - pass + logger.warning("Data objects to be signed: 0xBC") - logging.debug(f"Actual data signed: {hexlify(to_sign)}") - signature = self.dst.key.sign(to_sign, "") - return signature + logger.debug(f"Actual data signed: {hexlify(to_sign)}") + self.signature = bytes(self.dst.key.sign( + to_sign, + padding.PKCS1v15(), + hashes.SHA1() + )) + + logger.debug(f"Signature: {self.signature}") + return self.signature + def hash(self, p1, p2, data): """ @@ -151,12 +165,15 @@ def hash(self, p1, p2, data): :return: raw data (no TLV coding). """ - logging.info(f"Compute Hash {hex(p1)} {hex(p2)} {data[2:]}") - hash = super().hash(p1, p2, data[2:]) + logger.debug("Compute Hash {hex(p1)} {hex(p2)} {hexlify(data[17:])}") + + ## OpenSC driver will add data to beginning. Must remove it. + hash_data = data[-20:] #super().hash(p1, p2, data[17:]) + logger.debug(f"Hash_data: {hexlify(hash_data)}") - self.data_to_sign = hash + self.data_to_sign = hash_data - return hash + return hash_data class PTEID_SAM(SAM): def __init__(self, mf=None, private_key=None): @@ -167,8 +184,7 @@ def __init__(self, mf=None, private_key=None): self.current_SE.ht.algorithm = "SHA" self.current_SE.algorithm = "AES-CBC" - - self.current_SE.dst.key = RSA.importKey(private_key) + self.current_SE.dst.key = load_der_private_key(private_key, password=None, backend=default_backend()) def handle_0x80(self, p1, p2, data): logger.debug(f"Handle 0x80 {hex(p1)} {hex(p2)} {hex(data)}") @@ -188,10 +204,9 @@ def parse_SE_config(self, config): return r, b'' def verify(self, p1, p2, PIN): - logging.debug("Received PIN: %s", PIN.strip()) PIN = PIN.replace(b"\0", b"").strip() # Strip NULL characters PIN = PIN.replace(b"\xFF", b"") # Strip \xFF characters - logging.debug("PIN to use: %s", PIN) + logger.debug("PIN to use: %s", PIN) if len(PIN) == 0: raise SwError(SW["WARN_NOINFO63"]) @@ -201,7 +216,7 @@ def verify(self, p1, p2, PIN): class PTEID_MF(MF): # {{{ def getDataPlain(self, p1, p2, data): - logger.info( + logger.debug( f"GetData Plain {hex(p1)} {hex(p2)} {hexlify(data)}") tag = (p1 << 8) + p2 @@ -212,10 +227,10 @@ def getDataPlain(self, p1, p2, data): return 0x9000, b'' def readBinaryPlain(self, p1, p2, data): - logger.debug(f"Read Binary {hex(p1)} {hex(p2)}") - if p2 != 0: - p1 = 0 - p2 = 0 + logger.debug(f"Read Binary P1={hex(p1)} P2={hex(p2)} {data}") + ef, offsets, datalist = self.dataUnitsDecodePlain(p1, p2, data) + logger.debug(f"EF={ef} offsets={offsets} datalist={datalist}") + try: sw, result = super().readBinaryPlain(p1, p2, data) return 0x9000, result @@ -224,7 +239,6 @@ def readBinaryPlain(self, p1, p2, data): def selectFile(self, p1, p2, data): - # return super().selectFile(p1, p2, data) """ Function for instruction 0xa4. Takes the parameter bytes 'p1', 'p2' as integers and 'data' as binary string. Returns the status bytes as two @@ -232,31 +246,54 @@ def selectFile(self, p1, p2, data): """ logger.debug(f"SelectFile: fid=0x{hexlify(data).decode('latin')} p2={p2}") - P2_FCI = p2 & (3 << 2) == 0 - P2_FCP = p2 & 4 != 0 - P2_FMD = p2 & 8 != 0 - P2_NONE = 3 << 2 - - - logger.debug(f"FCI: {P2_FCI}, FCP: {P2_FCP} FMD:{P2_FMD}") + P1_MF_DF_EF = p1 == 0 + P1_CHILD_DF = p1 & 0x01 == 0x01 + P1_EF_IN_DF = p1 & 0x02 == 0x02 + P1_PARENT_DF = p1 & 0x03 == 0x03 + P1_BY_DFNAME = p1 & 0x4 != 0 + P1_DIRECT_BY_DFNAME = p1 & 0x7 == 0x04 + P1_BY_PATH = p1 & 0x8 != 0 + P1_FROM_MF = p1 & 0xf == 0x08 + P1_FROM_CURR_DF = p1 & 0xf == 0x09 + + P2_FCI = p2 & 0x0C == 0 + P2_FCP = p2 & 0x04 != 0 + P2_FMD = p2 & 0x08 != 0 + P2_NONE = p2 & 0x0C != 0 + + + logger.debug(f"P1 - MF_DF_EF:{P1_MF_DF_EF} CHILD_DF:{P1_CHILD_DF} EF_IN_DF:{P1_EF_IN_DF} PARENT_DF:{P1_PARENT_DF} BY_DFNAME:{P1_BY_DFNAME} DIR_BY_DFNAME:{P1_DIRECT_BY_DFNAME} BY_PATH:{P1_BY_PATH} FROM_MF:{P1_FROM_MF} FROM_CUR_DF:{P1_FROM_CURR_DF}") + logger.debug(f"P2 - FCI:{P2_FCI}, FCP:{P2_FCP} FMD:{P2_FMD} NONE:{P2_NONE}") + + # Patch instruction to Find MF to replicate PTEID behavior + if data == b'\x4f\x00': + p1 |= 8 + + # Patch Applet AID + if data == b'\x60\x46\x32\xFF\x00\x00\x02': + return 0x9000, b'' # Will fail with exception if File Not Found file = self._selectFile(p1, p2, data) self.current = file - + extra = b'' + + if P2_NONE: + pass + elif P2_FCI: + extra = self.get_fci(file) + logger.debug(f"FCI: {extra}") + else: + extra = b'' + + if isinstance(file, EF): logger.debug("IS EF") - fci = self.get_fci(file) - return 0x9000, fci elif isinstance(file, DF): - # Search by Name - if p1 == 0x04: - return 0x9000, b'' - logger.debug("IS DF") - fci = self.get_fci(file) - return 0x9000, fci + + return 0x9000, extra def get_fci(self, file): try: @@ -271,7 +308,7 @@ def get_fci(self, file): fcid = b'\x6F\x14' + \ b'\x83\x02' + file.fid.to_bytes(2, byteorder='big') + \ file.extra_fci_data - print(file.extra_fci_data) + logger.debug(f"FCI Data: {file.extra_fci_data}") name = getattr(file, 'dfname') if len(name) > 0: fcid = fcid + b'\x84' + len(name).to_bytes(1, byteorder='big') + name From e5124d4b03acb97e32101c073cd87e8b74d2894a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Barraca?= Date: Tue, 8 Nov 2022 10:55:13 +0000 Subject: [PATCH 11/11] fixed identation --- virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py index 3aebeb54..fed3882a 100644 --- a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py @@ -340,7 +340,7 @@ def __generate_nPA(self): "ReligiousArtisticName" in self.datagroups else '' AcademicTitle = self.datagroups["AcademicTitle"] if \ "AcademicTitle" in self.datagroups else '' - aDateOfBirth = self.datagroups["DateOfBirth"] if "DateOfBirth" in \ + aDateOfBirth = self.datagroups["DateOfBirth"] if "DateOfBirth" in \ self.datagroups else '19640812' PlaceOfBirth = self.datagroups["PlaceOfBirth"] if "PlaceOfBirth" in \ self.datagroups else 'BERLIN'