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. + diff --git a/pteid/generate_pteid_card.py b/pteid/generate_pteid_card.py new file mode 100644 index 00000000..676ae1d5 --- /dev/null +++ b/pteid/generate_pteid_card.py @@ -0,0 +1,481 @@ +#!/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=3072, + 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(cert.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'] = {'name': b2a_base64(b"\x60\x46\x32\xff\x00\x00\x02") } + +data['3f00-0003'] = {'data': 'AAAgBAEB', + '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\x00\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 - 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\x00\xFF\x00')} + +# 5032 - EF CIA +""" + 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 - 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\x00\xFF\x00')} + +# EF0D - PrKD +data['3f00-5f00-ef0d'] = {'data': 'MDkwHwwaQ0lUSVpFTiBBVVRIRU5USUNBVElPTiBLRVkEAQEwCgQBRQMCBSACAQKhCjAIMAIEAAICDAAwODAdDBVDSVRJWkVOIFNJR05BVFVSRSBLRVkEAQICAQEwCwQBRgMDBgBAAgEBoQowCDACBAACAgwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} + +# EF0E - PuKD +data['3f00-5f00-ef0e'] = {'data': 'MEwwLgwaQ2l0aXplbiBBdXRoZW50aWNhdGlvbiBLZXkDAgeABAGBMAkwBwMCBSAEAYEwDgQBRQMCAkQDAgO4AgECoQowCDACBAACAgwAMEgwKQwVQ2l0aXplbiBTaWduYXR1cmUgS2V5AwIHgAQBgjAJMAcDAgUgBAGCMA8EAUYDAwYgQAMCA7gCAQGhCjAIMAIEAAICDAA=', + 'fci': b2a_base64(b'\x8C\x05\x1B\xFF\x00\xFF\x00')} + +# EF02 - ID +data['3f00-5f00-ef02'] = { 'data': data['3f00-4f00-5032']['data'], + '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': 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\x00\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\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)), + '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\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\x00\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/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 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", diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py b/virtualsmartcard/src/vpicc/virtualsmartcard/CardGenerator.py index 9f9736b1..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 '' - 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' @@ -665,6 +665,99 @@ 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() + + name, fci, fdata = ___get_fs_entry(data, '3f00') + self.mf = PTEID_MF(dfname=name) + + 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 +768,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) 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 = { diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SEutils.py index 36deb2db..13c36aac 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 @@ -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:] diff --git a/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py b/virtualsmartcard/src/vpicc/virtualsmartcard/SmartcardFilesystem.py index 3b4b6b57..395036d0 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 = 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"]) @@ -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)) + 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)) + logging.debug("file (%s=%r) not found in: %s" % + (attribute, value, hex(self.fid))) raise SwError(SW["ERR_FILENOTFOUND"]) def remove(self, file): @@ -678,31 +680,44 @@ def _selectFile(self, p1, p2, data): P1_DF_NAME = 0x04 P1_PATH_FROM_MF = 0x08 P1_PATH_FROM_CURRENTDF = 0x09 - - if (p1 >> 4) != 0 or p1 == P1_FILE: + logging.debug(f"P1: {p1} P2:{p2}") + #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. + #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: - selected = walk(self, data) + logging.debug("P1 PATH FROM MF") + selected = walk(self.getMF(), 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 +763,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 +775,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 +831,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 +1437,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 +1454,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 +1466,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..1418fbbd --- /dev/null +++ b/virtualsmartcard/src/vpicc/virtualsmartcard/cards/PTEID.py @@ -0,0 +1,321 @@ +# +# 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 +# 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 +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\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.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: + 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 + 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)} p1={hex(p1)} p2={hex(p2)} le={le} length={len(data)} sw={hex(sw)}") + + 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 + + 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"]) + + 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 + logger.warning("Data objects to be signed: 0xBC") + + 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): + """ + Hash the given data using the algorithm specified by the current + Security environment. + + :return: raw data (no TLV coding). + """ + 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_data + + return hash_data + +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 = 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)}") + 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): + PIN = PIN.replace(b"\0", b"").strip() # Strip NULL characters + PIN = PIN.replace(b"\xFF", b"") # Strip \xFF characters + logger.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.debug( + 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 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 + except Exception as e: + logger.exception(f"{e}") + + + def selectFile(self, 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}") + + 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") + + elif isinstance(file, DF): + logger.debug("IS DF") + + return 0x9000, extra + + 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 + 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 + + return fcid + except Exception as e: + logger.exception(f"get fci: {e}") + return b'' + +# }}}