""" File: calypso-pki-example.py Author: springcard/johann.d Created: 2025-08-22 Description: Implentation of the Calypso TN #325 PkiModeExample It has been tested with Thales (Gemalto) Calypso Prime G3 cards only. This script is a free sample developed by SpringCard for demonstration purposes only. It is provided "as is", without support and without any warranty of any kind. Use it at your own risk. License: MIT License (see LICENSE file for details) Copyright (c) 2025 SpringCard SAS, France Dependencies: - Python 3.13+ - pyscard module (pip install pyscard) - cryptography module (pip install cryptography) Usage: Place a compliant Calypso Prime card on a SpringCard NFC/RFID HF PC/SC Coupler and run python calypso-pki-example.py """ import os import base64 import time from smartcard.scard import * import smartcard.util from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa, ec, utils from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicNumbers, BrainpoolP256R1 from cryptography.hazmat.backends import default_backend from cryptography.x509 import load_der_x509_certificate from cryptography.exceptions import InvalidSignature DEBUG = False BENCHMARK = False BENCH_APDUS_COUNT = 0 BENCH_COMMAND_BYTES = 0 BENCH_RESPONSE_BYTES = 0 BENCH_APDUS_TIME = 0 BENCH_CRYPTO_TIME = 0 """ All RSA keys use this public exponent """ RSA_PUB_EXP = 65537 """ ISO/IEC 7816-4 APDUs used to communicate with the card """ """ SELECT(AID of the Calypso instance with PKI enabled in the sample cards provided by Calypso) """ CALYPSO_SELECT_APPLICATION = [0x00, 0xA4, 0x04, 0x00, 0x08, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xFF, 0x91, 0x01, 0x00] """ GET DATA ( CardPub ) """ CALYPSO_GET_CARDPUB = [ 0x00, 0xCA, 0xDF, 0x2C, 0x00] """ GET DATA ( CACert ) """ CALYPSO_GET_CACERT_1 = [ 0x00, 0xCA, 0xDF, 0x4A, 0x00] CALYPSO_GET_CACERT_2 = [ 0x00, 0xCA, 0xDF, 0x4B, 0x00] """ GET DATA ( CardCert ) """ CALYPSO_GET_CARDCERT_1 = [ 0x00, 0xCA, 0xDF, 0x4C, 0x00] CALYPSO_GET_CARDCERT_2 = [ 0x00, 0xCA, 0xDF, 0x4D, 0x00] """ OPEN SESSION plus READ RECORD (Environment) """ CALYPSO_OPEN_SESSION_AND_READ_ENV_HEADER = [ 0x00, 0x8A, 0x0B, 0x3B, 0x09, 0x00 ] CALYPSO_OPEN_SESSION_AND_READ_ENV_TRAILER = [ 0x00 ] """ READ RECORD (Contract List) """ CALYPSO_READ_CTC_LIST = [ 0x00, 0xB2, 0x01, 0xF4, 0x1D ] """ READ RECORD (Contract) """ CALYPSO_READ_CONTRACT = [ 0x00, 0xB2, 0x01, 0x4C, 0x1D ] """ CLOSE SESSION """ CALYPSO_CLOSE_SESSION = [ 0x00, 0x8E, 0x00, 0x80, 0x40 ] """ Public keys from the Technical Note #021 """ CALYPSO_CERT_AID_PROD = [ 0xA0, 0x00, 0x00, 0x02, 0x91, 0xA0, 0x00, 0x01, 0x01, 0xB0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 ] CALYPSO_CERT_KEYID_PROD = [ 0x00, 0x00, 0x00, 0x01 ] CALYPSO_PUBKEY_PROD = [ 0xCE, 0x5E, 0xBA, 0xAF, 0xAA, 0x07, 0x8F, 0x59, 0x8C, 0x47, 0x0D, 0x06, 0x1E, 0xA8, 0xBD, 0x53, 0xBB, 0x92, 0x54, 0x24, 0x5B, 0x07, 0x62, 0x58, 0xEF, 0xA1, 0xD8, 0xBD, 0xBB, 0x4A, 0x3B, 0xF2, 0x34, 0x81, 0xEA, 0x68, 0x26, 0x38, 0x00, 0xB5, 0x0E, 0xD8, 0x3F, 0xEE, 0x81, 0xC0, 0x27, 0xBA, 0xB3, 0x82, 0x4C, 0x9D, 0x32, 0xF9, 0x34, 0x29, 0xE9, 0x4F, 0x26, 0x08, 0x71, 0x4B, 0xA7, 0x26, 0xA6, 0xDC, 0xBF, 0xA9, 0x39, 0x8C, 0x12, 0xD8, 0x0D, 0x08, 0x6F, 0x48, 0xF0, 0x05, 0x4A, 0x42, 0xFE, 0xE2, 0x90, 0x85, 0xE0, 0xF2, 0x48, 0x2A, 0x06, 0x76, 0xDD, 0xA3, 0xCF, 0xAE, 0xFE, 0x2F, 0xA4, 0xA8, 0x86, 0x35, 0x04, 0xEB, 0xB1, 0xF5, 0x28, 0x78, 0x7B, 0x99, 0xD4, 0x00, 0xB2, 0x5F, 0xC1, 0x27, 0x3C, 0x1B, 0xE7, 0xCC, 0x17, 0x77, 0x8C, 0xAC, 0x40, 0x75, 0xB7, 0xD9, 0x07, 0xDB, 0x8C, 0x7B, 0x0F, 0x8C, 0xC6, 0x5A, 0x5C, 0x81, 0xC9, 0x02, 0x1C, 0x3B, 0xE0, 0x2D, 0xA6, 0xFC, 0x1D, 0x91, 0xD6, 0x66, 0xD3, 0x8B, 0x7C, 0x85, 0x32, 0x3C, 0x0E, 0x6A, 0xE7, 0xC9, 0x17, 0x9D, 0x90, 0x62, 0xFA, 0x88, 0x82, 0x13, 0x37, 0x37, 0x04, 0x3E, 0x72, 0xBC, 0x50, 0x3C, 0x91, 0x61, 0xA0, 0xA9, 0xD8, 0xAB, 0x9A, 0x3F, 0xEF, 0xA3, 0xB9, 0x01, 0xEB, 0xE4, 0x11, 0x09, 0x97, 0xEF, 0x51, 0xFD, 0xB8, 0x98, 0x84, 0x03, 0x52, 0x8D, 0xAD, 0xB9, 0xC8, 0x71, 0xFA, 0x8C, 0x45, 0x86, 0x94, 0xDE, 0x29, 0x2E, 0xFB, 0xE3, 0x92, 0x58, 0x4F, 0x87, 0x55, 0xA0, 0x4F, 0xB5, 0xC4, 0x53, 0xF7, 0xBA, 0x2F, 0x5A, 0x36, 0x6A, 0xB2, 0x90, 0x48, 0xB5, 0xCC, 0x13, 0x3E, 0x60, 0xE0, 0xC8, 0xF2, 0x69, 0xD6, 0xB6, 0x77, 0xBC, 0xFA, 0xD6, 0x96, 0x18, 0x30, 0xDE, 0x46, 0x3F, 0x7E, 0x31 ] CALYPSO_CERT_AID_TEST = [ 0xA0, 0x00, 0x00, 0x02, 0x91, 0xA0, 0x00, 0x01, 0x01, 0xB0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 ] CALYPSO_CERT_KEYID_TEST = [ 0x00, 0x00, 0x00, 0x02 ] CALYPSO_PUBKEY_TEST = [ 0xC2, 0x49, 0x45, 0x57, 0xEC, 0xE5, 0x97, 0x9A, 0x49, 0x74, 0x24, 0x83, 0x34, 0x89, 0xCC, 0xCA, 0xCF, 0x4D, 0xEE, 0x3F, 0xD7, 0x57, 0x6A, 0x99, 0xC3, 0x99, 0x9D, 0x8F, 0x46, 0x81, 0x74, 0xE7, 0x6F, 0x39, 0x3D, 0x4E, 0x5C, 0x38, 0x02, 0xAC, 0x6C, 0x3C, 0xB1, 0x92, 0xEB, 0x68, 0x7F, 0x55, 0x05, 0xD2, 0x4E, 0xBA, 0x01, 0xFF, 0xC6, 0x0D, 0x57, 0x52, 0xCE, 0x69, 0x10, 0xD5, 0x0B, 0x4A, 0xDA, 0xC8, 0xC9, 0x31, 0x59, 0x16, 0x51, 0x09, 0xC3, 0x90, 0x1F, 0xCA, 0x38, 0x3A, 0x9F, 0x66, 0x03, 0xD5, 0x76, 0x39, 0x0F, 0xD5, 0x98, 0x99, 0xA1, 0x08, 0x73, 0x93, 0x6D, 0x3A, 0x36, 0x9B, 0x3E, 0xB8, 0x40, 0x3A, 0xDF, 0xF4, 0x76, 0x54, 0x7B, 0x03, 0x9A, 0xCC, 0x7D, 0xCB, 0x3C, 0x1F, 0xAF, 0x4F, 0x95, 0x4E, 0x29, 0xA8, 0xC2, 0xE2, 0xAE, 0xD7, 0x72, 0x12, 0x72, 0xAF, 0x5C, 0xDC, 0x0A, 0x3B, 0x29, 0x94, 0x71, 0x52, 0x61, 0xA4, 0x36, 0x4E, 0xC1, 0x25, 0x6D, 0x00, 0x00, 0x4E, 0x08, 0x49, 0x14, 0xDC, 0x47, 0x27, 0x34, 0x9D, 0x71, 0x5C, 0x38, 0x48, 0xD7, 0xC5, 0x4A, 0xD5, 0x8D, 0xB0, 0xF6, 0x90, 0x75, 0x49, 0xFE, 0xD5, 0x1D, 0x56, 0x4E, 0x3A, 0x85, 0x3D, 0x44, 0xF0, 0x71, 0xA8, 0x52, 0xAB, 0x53, 0x63, 0x56, 0xC7, 0x97, 0x4B, 0x16, 0xFC, 0x03, 0xE1, 0xFF, 0xE9, 0xDE, 0xE7, 0x52, 0x7F, 0xBA, 0xDD, 0xA5, 0xBC, 0x11, 0x16, 0x15, 0x6D, 0xBF, 0xA5, 0xC1, 0x3F, 0x06, 0xAC, 0xBB, 0xCD, 0xCE, 0xE3, 0xF9, 0xF4, 0x56, 0x40, 0x34, 0xA8, 0xAD, 0x20, 0xF4, 0x07, 0x32, 0xB2, 0xAB, 0x41, 0x48, 0x91, 0xD9, 0x40, 0xED, 0x96, 0xDA, 0x6D, 0xA6, 0xE9, 0x8F, 0x76, 0x6A, 0x1C, 0xDB, 0xC7, 0xFD, 0x0C, 0x17, 0xA7, 0x08, 0xBD, 0x5F, 0x68, 0xB8, 0x16, 0xAA, 0x47 ] """ Test vectors from the Technical Note #325 """ """ Close Session Input """ TN325_INPUT = [ 0x10, 0x0E, 0x00, 0x8A, 0x08, 0x3B, 0x09, 0x00, 0x35, 0xBB, 0xD6, 0x64, 0xA5, 0x85, 0xCF, 0x76, 0x63, 0x0B, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xF0, 0x01, 0x00, 0x01, 0x91, 0x01, 0x00, 0x00, 0x00, 0x12, 0xF3, 0x45, 0x67, 0x89, 0x00, 0xB5, 0x1D, 0x35, 0x7E, 0x24, 0x74, 0x98, 0xEA, 0x00, 0x00, 0x00, 0x40, 0x2B, 0xBB, 0xAD, 0x73, 0xCB, 0x5D, 0x06, 0x8F, 0x8C, 0x6C, 0x50, 0x50, 0x7D, 0x56, 0xA0, 0x07, 0xA3, 0x3C, 0x23, 0x0B, 0x21, 0xB4, 0x03, 0x2B, 0x68, 0x8E, 0x7C, 0x75, 0x6B, 0xEC, 0x49, 0xF9, 0x1B, 0xAB, 0x42, 0x34, 0xBF, 0x36, 0x90, 0xCB, 0x40, 0xF9, 0x86, 0xD7, 0x43, 0x3C, 0xCF, 0x41, 0xD8, 0x27, 0x59, 0xD6, 0xF1, 0x64, 0x6F, 0xF6, 0x34, 0x2A, 0x1B, 0xFE, 0x9B, 0xF7, 0xF0, 0xCB, 0x90, 0x00, 0x05, 0x00, 0xB2, 0x01, 0xF4, 0x1D, 0x1F, 0xB1, 0x49, 0xDB, 0x13, 0x30, 0xF0, 0xD8, 0x3A, 0x75, 0x63, 0x2D, 0x76, 0x3F, 0x40, 0xA6, 0x0F, 0x5A, 0x43, 0x11, 0x00, 0x14, 0xC7, 0x28, 0xAC, 0x9C, 0xA5, 0xAD, 0xFE, 0x8B, 0x90, 0x00, 0x05, 0x00, 0xB2, 0x01, 0x4C, 0x1D, 0x1F, 0x23, 0xC6, 0x0E, 0xA8, 0x6B, 0xED, 0x55, 0x3D, 0x16, 0x1B, 0x82, 0xE0, 0xA8, 0xD2, 0x9E, 0x44, 0x78, 0xA3, 0xF5, 0x98, 0xCD, 0xA1, 0xC8, 0xEC, 0x16, 0xB3, 0x25, 0xD4, 0x1C, 0x90, 0x00 ] TN325_INPUT_SHA256 = [ 0x8C, 0xFE, 0xD6, 0x03, 0x46, 0x2B, 0x25, 0x18, 0x36, 0x08, 0x9C, 0xC0, 0xEC, 0xCE, 0x74, 0x1D, 0xD6, 0xF6, 0x00, 0x43, 0xD5, 0x91, 0x0C, 0xAE, 0xDC, 0x4F, 0x45, 0x33, 0x73, 0x4E, 0x18, 0x39 ] """ Signature returned by the card """ TN325_SIGNATURE = [ 0x7D, 0x4E, 0xC3, 0x01, 0x7F, 0xBD, 0x0A, 0xFB, 0xB4, 0x67, 0x4E, 0x95, 0xC2, 0xC7, 0xF4, 0x38, 0x91, 0x8E, 0x0C, 0x8E, 0x58, 0x66, 0x82, 0x23, 0x13, 0xBA, 0x89, 0x95, 0x6D, 0x8B, 0x2A, 0x55, 0x30, 0xD6, 0x41, 0xFC, 0x04, 0xDC, 0x37, 0xFF, 0x58, 0x81, 0x06, 0xA6, 0xAC, 0x4B, 0x5D, 0x34, 0x06, 0xCC, 0x10, 0x7C, 0xB4, 0xE2, 0x4D, 0x1A, 0x0A, 0xCE, 0x37, 0xA2, 0xDA, 0x8E, 0xF8, 0xAA ] TN325_CARDPUB = [ 0x5D, 0x21, 0x9F, 0xAA, 0xA8, 0x00, 0xB3, 0x36, 0xB5, 0x91, 0x77, 0x5A, 0x79, 0xC7, 0x54, 0x49, 0xCE, 0x06, 0xF0, 0x84, 0x82, 0xAE, 0x6A, 0x7C, 0xC3, 0xA7, 0x69, 0xE2, 0xB3, 0x1B, 0xF7, 0xEA, 0xB1, 0x24, 0x29, 0xAA, 0x3E, 0xE6, 0xE1, 0xB3, 0xB0, 0xF8, 0xE7, 0x96, 0xE5, 0x38, 0xD4, 0x23, 0xAB, 0x51, 0x66, 0x0C, 0x2C, 0x6C, 0x54, 0xED, 0xE9, 0xAD, 0x71, 0x79, 0x8A, 0xFD, 0xF2, 0x6D ] TN325_CAPUB = [ 0x9B, 0xAC, 0x64, 0x44, 0x71, 0x98, 0x7C, 0x1E, 0xB7, 0x54, 0x64, 0x09, 0x6C, 0x61, 0xD8, 0x79, 0x28, 0xC3, 0x66, 0x4B, 0x8E, 0x8B, 0xF9, 0x1B, 0x86, 0xDA, 0x95, 0x26, 0x01, 0xA8, 0xB1, 0xF9, 0x61, 0xE9, 0xCC, 0xE1, 0x47, 0xB9, 0xEB, 0x8C, 0x0D, 0x65, 0x59, 0xD7, 0x61, 0x1A, 0xC5, 0xB7, 0xE3, 0xED, 0x72, 0x2F, 0xEA, 0x95, 0x01, 0xAD, 0x76, 0x0C, 0x6B, 0x21, 0x7E, 0xBC, 0xE3, 0xD5, 0x62, 0xF6, 0xBB, 0xD9, 0x6A, 0xB0, 0x88, 0x8E, 0xE1, 0xB9, 0xB7, 0x9B, 0x80, 0x6B, 0x2D, 0x15, 0xB9, 0x7A, 0xF0, 0x4D, 0x66, 0x58, 0x6F, 0xF5, 0xFB, 0x04, 0x7F, 0x7B, 0xB2, 0x64, 0x50, 0x62, 0x0F, 0xBB, 0x44, 0x73, 0x43, 0xFE, 0x23, 0x15, 0x78, 0xBA, 0x00, 0x80, 0x11, 0xD1, 0xBC, 0xA0, 0xA0, 0xC0, 0x2B, 0x59, 0xF8, 0xA6, 0xBC, 0xB4, 0x56, 0xE1, 0x52, 0xE9, 0x75, 0x0A, 0xD7, 0xEA, 0x40, 0xE4, 0xD4, 0x81, 0x9A, 0xCA, 0x1D, 0xDA, 0x58, 0x61, 0xF4, 0x9C, 0x88, 0x2B, 0x3A, 0xEC, 0x4D, 0x6B, 0xB1, 0x12, 0x54, 0x6E, 0xE1, 0x07, 0xD1, 0x42, 0x6A, 0x59, 0x38, 0x8C, 0x7E, 0xCD, 0x40, 0x43, 0xBE, 0xD4, 0x4A, 0xCE, 0x50, 0xE2, 0xCD, 0x8D, 0x61, 0xEC, 0x92, 0x9B, 0x52, 0xE2, 0x43, 0xB2, 0x36, 0x60, 0x0E, 0x57, 0xA0, 0xFB, 0xC4, 0x6F, 0xD3, 0xC3, 0x57, 0xBA, 0xEA, 0xAA, 0xC9, 0xDB, 0x64, 0x10, 0x07, 0x02, 0x68, 0xB4, 0x5F, 0xFE, 0xD8, 0x83, 0x6A, 0x01, 0xC6, 0x06, 0x47, 0x2B, 0xDE, 0x18, 0x0E, 0xB6, 0x30, 0xC8, 0x15, 0x7D, 0xC5, 0x1D, 0x28, 0x43, 0xC9, 0x23, 0xEC, 0xEC, 0xAA, 0x40, 0xB3, 0xE9, 0x0B, 0xA5, 0x22, 0x12, 0x3B, 0xBD, 0xD5, 0x67, 0xFB, 0x8F, 0xAA, 0xA4, 0x66, 0x1A, 0x4B, 0xE9, 0xA8, 0xD9, 0x52, 0x66, 0x3F, 0x3D, 0x83, 0xB2, 0x6C, 0x75, ] TN325_CACERT = [ 0x90, 0x01, 0x0B, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xA0, 0x00, 0x01, 0x01, 0xB0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x20, 0x19, 0x03, 0x31, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xF0, 0x01, 0x00, 0x01, 0x91, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9B, 0xAC, 0x64, 0x44, 0x71, 0x98, 0x7C, 0x1E, 0xB7, 0x54, 0x64, 0x09, 0x6C, 0x61, 0xD8, 0x79, 0x28, 0xC3, 0x66, 0x4B, 0x8E, 0x8B, 0xF9, 0x1B, 0x86, 0xDA, 0x95, 0x26, 0x01, 0xA8, 0xB1, 0xF9, 0x61, 0xE9, 0x8A, 0x9C, 0x7E, 0xD8, 0x96, 0x0E, 0xDA, 0xB7, 0xEE, 0x5D, 0x69, 0xE3, 0x47, 0xA8, 0x91, 0xB1, 0xF4, 0x48, 0x8D, 0xBC, 0x31, 0x05, 0xB4, 0x9C, 0x09, 0x3E, 0x46, 0x98, 0xD4, 0x96, 0xF3, 0xEA, 0xE8, 0xEE, 0xB7, 0x2E, 0x9E, 0x14, 0x5C, 0x61, 0x02, 0xE2, 0xCE, 0x95, 0x33, 0x07, 0x15, 0x58, 0x7D, 0x87, 0xD9, 0x36, 0xA7, 0x18, 0xB9, 0x46, 0x3A, 0xEA, 0x28, 0x1A, 0xDB, 0x12, 0xB0, 0x23, 0x3D, 0x4C, 0x5E, 0x1B, 0xE9, 0x82, 0xED, 0x2C, 0x1E, 0xA2, 0x00, 0x4F, 0x86, 0xBA, 0xFA, 0x85, 0x64, 0x8F, 0xE9, 0x9B, 0xB9, 0xD2, 0x9D, 0x95, 0xCF, 0xBB, 0xC3, 0xA6, 0x3F, 0x38, 0xB4, 0xD3, 0xB0, 0x07, 0x89, 0xF8, 0x0F, 0xCA, 0xFB, 0xB7, 0x62, 0x10, 0x43, 0x44, 0xC0, 0xC6, 0xA0, 0x68, 0x4D, 0x56, 0x14, 0x47, 0xB0, 0x56, 0x12, 0x64, 0x82, 0xBF, 0x2E, 0x56, 0xAB, 0xAC, 0x68, 0x94, 0xC7, 0xB9, 0x42, 0x7E, 0x1E, 0x7B, 0x6D, 0xCB, 0xC9, 0x09, 0x03, 0x1B, 0xFD, 0x21, 0x01, 0x5C, 0xC9, 0x0C, 0xE5, 0x6D, 0x92, 0x39, 0x3C, 0xC8, 0x5C, 0x3D, 0xA3, 0x33, 0x5A, 0x94, 0x5F, 0x89, 0x74, 0xDA, 0x24, 0xE6, 0x8F, 0x4E, 0xD3, 0xD6, 0x48, 0x85, 0xD2, 0xCB, 0x50, 0xC7, 0x59, 0xD1, 0x7E, 0xB9, 0xCE, 0x90, 0xEA, 0x07, 0x76, 0xCF, 0xE4, 0xBF, 0x81, 0x49, 0x4D, 0x0C, 0x00, 0x83, 0x93, 0x85, 0xE4, 0xFB, 0x68, 0xCF, 0xC8, 0x3B, 0xDB, 0xDC, 0x64, 0x40, 0x46, 0xE5, 0x9E, 0x51, 0xA5, 0x03, 0xF8, 0x68, 0x50, 0xF6, 0x04, 0x61, 0x4F, 0x62, 0xDB, 0x3D, 0x43, 0xF1, 0xF1, 0x1E, 0x88, 0xAD, 0xBE, 0x76, 0x07, 0x93, 0x7D, 0xCA, 0xA0, 0x51, 0xFC, 0xBF, 0x94, 0x65, 0xD1, 0x40, 0x1D, 0x0C, 0xF6, 0x1B, 0x48, 0x20, 0x9B, 0x6F, 0x21, 0x02, 0xD9, 0x45, 0xF5, 0x3D, 0xA6, 0x82 ] TN325_CARDCERT = [ 0x91, 0x01, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x0B, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xF0, 0x01, 0x00, 0x01, 0x91, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0xF3, 0x45, 0x67, 0x89, 0x00, 0x00, 0x00, 0x00, 0x21, 0x42, 0xDA, 0xCA, 0x8F, 0x40, 0x0F, 0x30, 0xC6, 0x22, 0xCA, 0xE1, 0xCA, 0x7E, 0x2C, 0x25, 0x20, 0xD3, 0xA2, 0xE0, 0x28, 0xE4, 0x90, 0xE2, 0x60, 0xC5, 0x7C, 0x9F, 0xBB, 0xB7, 0xA9, 0x7E, 0x7F, 0x7C, 0x98, 0x66, 0xF0, 0x72, 0x10, 0xEE, 0xF0, 0xEF, 0x5B, 0xB0, 0xE3, 0xCB, 0x88, 0x65, 0x1D, 0x32, 0xBA, 0x8A, 0x56, 0xED, 0x50, 0xEF, 0x18, 0xFE, 0x54, 0xE2, 0x09, 0xD4, 0xD0, 0x7A, 0x4D, 0xA1, 0x4A, 0xE5, 0x1B, 0xF8, 0x09, 0xFB, 0x17, 0x7C, 0x54, 0x5C, 0xE1, 0x41, 0xE3, 0xCB, 0xD6, 0xE3, 0x06, 0x3A, 0xEE, 0x2C, 0xE3, 0x50, 0x3F, 0xAC, 0x10, 0x32, 0x87, 0xAA, 0x75, 0x5D, 0xCA, 0x16, 0x60, 0x56, 0xD6, 0x8F, 0x53, 0xCB, 0x71, 0xAA, 0x23, 0x21, 0x31, 0xA7, 0x5C, 0x8C, 0xD0, 0xF0, 0xF2, 0x81, 0x65, 0x31, 0xF7, 0x56, 0xA7, 0x47, 0x4C, 0x61, 0xE7, 0x9A, 0xD5, 0x42, 0x66, 0x0A, 0xA9, 0xBD, 0xB2, 0xD1, 0x01, 0x0E, 0xC0, 0xFD, 0xF0, 0x6D, 0x4D, 0xAB, 0x1F, 0x90, 0x1B, 0x59, 0xBB, 0xB9, 0xB7, 0xA0, 0x02, 0x2E, 0x44, 0x58, 0xE5, 0xC1, 0xA3, 0x81, 0xBE, 0x82, 0x4F, 0x59, 0xA7, 0x07, 0xBD, 0x7D, 0xB2, 0xC5, 0x99, 0x6D, 0xAF, 0xD1, 0x43, 0x06, 0x4E, 0x02, 0x00, 0x26, 0xA4, 0xAE, 0xF7, 0x42, 0x27, 0x47, 0x02, 0xCD, 0xBD, 0xF2, 0x1A, 0x39, 0xD7, 0xB8, 0xF1, 0x51, 0x86, 0xCA, 0x3C, 0x04, 0x66, 0x57, 0x0E, 0x25, 0x40, 0x0E, 0x02, 0x23, 0x03, 0x80, 0x79, 0x8B, 0x00, 0x06, 0x2E, 0x81, 0x79, 0xB0, 0x9E, 0xB2, 0xB2, 0x06, 0x90, 0xB6, 0xBC, 0x68, 0x15, 0xFE, 0xE1, 0x7F, 0x3D, 0x1A, 0xE5, 0xFF, 0x0B, 0xEB, 0x21, 0xC9, 0x74, 0x4E, 0x0C, 0x99, 0x87, 0xE4, 0x71, 0xE0, 0x96, 0x7E, 0xE8, 0x87, 0x2E, 0x3B, 0xE5, 0x30, 0xBE, 0x7C, 0x72, 0x79 ] """ Data provided by the card at the first dry run """ """ Response to the GET DATA (DF2C) command (CardPub) """ FIRST_DF2C_RESP = [ 0xDF, 0x2C, 0x40, 0xCB, 0xD2, 0x0B, 0x97, 0x36, 0x61, 0xC3, 0x76, 0x7E, 0x44, 0xD0, 0xB3, 0x5F, 0x73, 0xB0, 0xDB, 0x4D, 0xB9, 0xE6, 0xD4, 0xF0, 0x1F, 0xDC, 0xFD, 0x0A, 0x0E, 0xC9, 0x07, 0x28, 0x72, 0x0A, 0x86, 0xE3, 0x79, 0xC7, 0xD0, 0x46, 0x86, 0x43, 0x87, 0x07, 0x35, 0x44, 0x18, 0x93, 0x1C, 0x0C, 0xB8, 0x8B, 0x76, 0xFB, 0x4A, 0xE6, 0x68, 0x19, 0xF9, 0xFC, 0x43, 0x70, 0x75, 0x9F, 0x0B, 0x20, 0x00 ] """ Response to the GET DATA (DF4A) command (CACert, part 1) """ FIRST_DF4A_RESP = [ 0xDF, 0x4A, 0x82, 0x01, 0x80, 0x90, 0x01, 0x0B, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xA0, 0x00, 0x01, 0x01, 0xB0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0B, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xA0, 0x01, 0x00, 0x02, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAE, 0xC8, 0xE0, 0xEA, 0x00, 0x00, 0x00, 0x00, 0x20, 0x24, 0x02, 0x22, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAE, 0x0E, 0x22, 0xFC, 0x13, 0xDA, 0x30, 0x3E, 0xDE, 0xC0, 0xB0, 0x2E, 0x89, 0xFC, 0x5B, 0xCD, 0xD1, 0xCE, 0xD8, 0x12, 0x3B, 0xAD, 0x38, 0x77, 0xC2, 0xC6, 0x8B, 0xDB, 0x16, 0x2C, 0x5C, 0x63, 0xDF, 0x6F, 0xA9, 0xBE, 0x45, 0x4A, 0xDD, 0x61, 0x5D, 0x42, 0xD1, 0xFD, 0x43, 0x72, 0xA8, 0x7F, 0x03, 0x68, 0xF0, 0xF2, 0x60, 0x3C, 0x6C, 0xB1, 0x2C, 0xFE, 0x35, 0x83, 0x89, 0x1D, 0x2D, 0xA7, 0x11, 0x85, 0xFC, 0x9E, 0x3E, 0xB9, 0x89, 0x4B, 0xD6, 0x04, 0x47, 0xCA, 0x88, 0x20, 0x0E, 0xD3, 0x5E, 0x42, 0xAB, 0x08, 0xEC, 0x86, 0x06, 0xE0, 0x78, 0x2D, 0x60, 0x05, 0xAE, 0xE9, 0xD2, 0x82, 0xEE, 0x1B, 0x98, 0x51, 0x0E, 0x39, 0xD7, 0x47, 0xC5, 0x07, 0x0E, 0x38, 0x3E, 0x85, 0x19, 0x72, 0x0C, 0xD7, 0x9F, 0x12, 0x3B, 0x58, 0x4E, 0x3D, 0xB3, 0x1E, 0x05, 0xA6, 0x34, 0x83, 0x69, 0x34, 0x7E, 0xF0, 0xD8, 0xC4, 0xE3, 0x8A, 0x45, 0x53, 0xC2, 0x6B, 0x51, 0x8F, 0x23, 0x5E, 0x44, 0x59, 0x53, 0x4A, 0x99, 0x0C, 0x68, 0x0F, 0x59 ] """ Response to the GET DATA (DF4B) command (CACert, part 2) """ FIRST_DF4B_RESP = [ 0x6A, 0x19, 0xDF, 0x87, 0xC0, 0x8F, 0x81, 0x24, 0xB8, 0xEA, 0x64, 0xE1, 0x24, 0x5A, 0x38, 0xBA, 0x31, 0xA2, 0xD4, 0x00, 0xB3, 0x6C, 0xEC, 0x7E, 0x72, 0xC5, 0xEE, 0x4E, 0xDD, 0x4C, 0x3F, 0xA7, 0xD2, 0xC8, 0xBB, 0x2A, 0x63, 0x16, 0x09, 0xC3, 0x41, 0xEF, 0x91, 0x87, 0xFF, 0x80, 0xD2, 0x1C, 0xF4, 0x17, 0xEB, 0xE9, 0x32, 0x8D, 0x07, 0xCA, 0x64, 0xF4, 0xAA, 0x40, 0x25, 0x0B, 0x28, 0x55, 0x59, 0x04, 0x1B, 0xC6, 0x4D, 0x24, 0xF5, 0xCC, 0xCC, 0x90, 0xB0, 0x6C, 0x8E, 0xFF, 0xF0, 0xC8, 0x0B, 0xAD, 0xAB, 0x4D, 0x2D, 0x2A, 0xBB, 0xD2, 0x12, 0x41, 0x49, 0x08, 0x05, 0xA2, 0x7A, 0xF1, 0xB4, 0x1A, 0x28, 0x2D, 0x67, 0xD6, 0x18, 0x85, 0xCB, 0xDD, 0x23, 0xF8, 0x72, 0x71, 0xAB, 0xD1, 0x98, 0x9C, 0x95, 0x4B, 0x31, 0x46, 0xAE, 0x38, 0xAE, 0x25, 0x81, 0xDE, 0xFE, 0x8D, 0x48, 0x84, 0x0F, 0x90, 0x75, 0xB9, 0x43, 0x0C, 0xDD, 0x8E, 0xCB, 0x19, 0x16 ] """ Response to the GET DATA (DF2C) command (CardPub) """ FIRST_DF2C_RESP = [ 0xDF, 0x2C, 0x40, 0xCB, 0xD2, 0x0B, 0x97, 0x36, 0x61, 0xC3, 0x76, 0x7E, 0x44, 0xD0, 0xB3, 0x5F, 0x73, 0xB0, 0xDB, 0x4D, 0xB9, 0xE6, 0xD4, 0xF0, 0x1F, 0xDC, 0xFD, 0x0A, 0x0E, 0xC9, 0x07, 0x28, 0x72, 0x0A, 0x86, 0xE3, 0x79, 0xC7, 0xD0, 0x46, 0x86, 0x43, 0x87, 0x07, 0x35, 0x44, 0x18, 0x93, 0x1C, 0x0C, 0xB8, 0x8B, 0x76, 0xFB, 0x4A, 0xE6, 0x68, 0x19, 0xF9, 0xFC, 0x43, 0x70, 0x75, 0x9F, 0x0B, 0x20, 0x00 ] """ Response to the GET DATA (DF4A) command (CACert, part 1) """ FIRST_DF4C_RESP = [ 0xDF, 0x4C, 0x82, 0x01, 0x3C, 0x91, 0x01, 0x0B, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xA0, 0x01, 0x00, 0x02, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAE, 0xC8, 0xE0, 0xEA, 0x00, 0x00, 0x00, 0x00, 0x08, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xFF, 0x91, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF9, 0xDA, 0xED, 0x00, 0x00, 0x00, 0x5B, 0x38, 0xDD, 0x08, 0xAB, 0xF2, 0x79, 0x32, 0x67, 0xE6, 0xC3, 0xD9, 0xA5, 0xE0, 0x15, 0xF4, 0x9E, 0x14, 0x00, 0xDC, 0xCC, 0x0C, 0x13, 0x8F, 0xED, 0x20, 0xE3, 0x16, 0x7D, 0x0E, 0x38, 0xC6, 0x1D, 0x07, 0x3E, 0x2E, 0xB4, 0x29, 0x97, 0x16, 0xA6, 0xB7, 0x5A, 0x2D, 0x38, 0x36, 0xD4, 0x6D, 0x23, 0xC8, 0xA7, 0xBD, 0x32, 0xDC, 0x2E, 0x9E, 0xD2, 0x40, 0x98, 0xA8, 0x8F, 0x35, 0xDF, 0xCF, 0x67, 0x34, 0x39, 0x1B, 0x0D, 0xC9, 0xB8, 0x3A, 0xAB, 0xFB, 0x73, 0x1F, 0x22, 0xA8, 0x97, 0xE6, 0x50, 0x3D, 0x76, 0x84, 0xBC, 0xA0, 0x9B, 0x1C, 0xE3, 0x9A, 0xA6, 0xB9, 0x1F, 0x2D, 0x57, 0xDE, 0x4C, 0x25, 0xB7, 0x89, 0x9D, 0x43, 0x64, 0x08, 0x67, 0x4B, 0xAC, 0x08, 0xDF, 0xFD, 0x00, 0xA7, 0x02, 0x6C, 0xA7, 0xB2, 0x11, 0x51, 0xC8, 0x3A, 0x48, 0x52, 0xBD, 0x5E, 0x48, 0x0F, 0xD0, 0x0D, 0x6A, 0xC2, 0x4F, 0x52, 0x6D, 0x22, 0x5A, 0xF8, 0x7F, 0x5E, 0x4C, 0x94, 0x61, 0x2D, 0xF3, 0xB7, 0xEE, 0xC6, 0x0C, 0x64, 0x6F, 0xCC, 0x9E, 0x32, 0x85, 0xB7, 0x76, 0x02, 0x64, 0xCA, 0x2B, 0x1D, 0x98, 0x45, 0x82, 0x29, 0xFF, 0x76, 0x96, 0x7C, 0x52, 0x86, 0x41, 0xDE, 0x55, 0xEA, 0x06, 0xFF, 0xFA, 0x65, 0x26, 0xFA, 0x1C, 0xB4, 0xE7, 0x97, 0x51, 0x02 ] """ Response to the GET DATA (DF4B) command (CACert, part 2) """ FIRST_DF4D_RESP = [ 0xF6, 0xB0, 0xE1, 0xFC, 0x2A, 0x43, 0xE5, 0x63, 0x82, 0xFD, 0x1E, 0xE3, 0x8E, 0x38, 0x3E, 0x3A, 0x7C, 0xEB, 0xA7, 0xA9, 0x7C, 0x31, 0xDA, 0x89, 0x0E, 0x49, 0x17, 0x26, 0xBA, 0x3B, 0x85, 0xD8, 0x61, 0x46, 0xB0, 0x01, 0x68, 0x5D, 0x72, 0x9F, 0x02, 0x87, 0x95, 0xE2, 0x99, 0x38, 0xFB, 0xED, 0xA4, 0x0D, 0x89, 0x86, 0x67, 0x4C, 0x24, 0x0F, 0x50, 0x51, 0x71, 0x9C, 0x4B, 0xCC, 0x24, 0xE9, 0x5A, 0xA9, 0x51, 0xBD, 0x58, 0x42, 0x0A ] """ Close Session Input """ FIRST_INPUT = [ 0x10, 0x0E, 0x00, 0x8A, 0x0B, 0x3B, 0x09, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x3D, 0x08, 0xA0, 0x00, 0x00, 0x02, 0x91, 0xFF, 0x91, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xF9, 0xDA, 0xED, 0x00, 0x03, 0x0D, 0xE2, 0xEB, 0xCA, 0x66, 0x28, 0x12, 0x00, 0x00, 0x00, 0x1D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0x50, 0x1E, 0xCA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x05, 0x00, 0xB2, 0x01, 0xF4, 0x1D, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x05, 0x00, 0xB2, 0x01, 0x4C, 0x1D, 0x1F, 0x01, 0x01, 0x16, 0x50, 0x16, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00 ] """ Signature returned by the card """ FIRST_SIGNATURE = [ 0xE5, 0xD6, 0x7C, 0x43, 0x8D, 0x81, 0xDE, 0xB5, 0x63, 0x8B, 0xDD, 0xF4, 0xC1, 0x14, 0x01, 0x07, 0xCA, 0xF1, 0xB6, 0xE4, 0x15, 0x4B, 0xBB, 0x4E, 0x28, 0xB0, 0x43, 0x4D, 0x41, 0x16, 0x91, 0x7B, 0xB5, 0x7B, 0x3C, 0xD4, 0x4A, 0xC6, 0x13, 0xE6, 0x2C, 0x99, 0x0A, 0x9B, 0xFC, 0xB0, 0xA1, 0x6A, 0x9A, 0xB9, 0xAA, 0x50, 0xD6, 0xD4, 0x37, 0x0B, 0x2F, 0xB1, 0x11, 0xFF, 0xD4, 0x3F, 0xBE, 0xC7 ] """ Quick'n'dirty ASN.1 DER parser """ def parse_tlv(data): i = 0 tlvs = {} raw_fields = {} while i < len(data): tag_start = i tag = data[i] i += 1 if tag in (0x5F, 0x7F, 0xDF): tag = (tag << 8) | data[i] i += 1 length = data[i] i += 1 if length & 0x80: # Long-form length num_len_bytes = length & 0x7F length = int.from_bytes(data[i:i+num_len_bytes], 'big') i += num_len_bytes value = data[i:i+length] i += length tlvs[tag] = value raw_fields[tag] = data[tag_start:i] return tlvs, raw_fields """ Parse a Calypso certificate """ def print_struct(cert): for k, v in cert.items(): if isinstance(v, int): print(f"\t{k}={v:02X}") else: print(f"\t{k}={bytes(v).hex().upper()}") def parse_cacert(data: bytes, silent: bool): if not BENCHMARK and not silent: print("Parsing CACert") print(f"\t{data.hex().upper()}") if len(data) != 384: print("Length of CA Certificate shall be 384") return None if data[0] != 0x90: print("Type of CA Certificate shall be 0x90") return None result = {} offset = 0 result["Type"] = data[offset] offset += 1 result["StructureVersion"] = data[offset] offset += 1 result["IssuerKeyReference"] = data[offset:offset+29] offset += 29 result["CaTargetKeyReference"] = data[offset:offset+29] offset += 29 result["StartDate"] = data[offset:offset+4] offset += 4 result["CaRfu1"] = data[offset:offset+4] offset += 4 result["CaRights"] = data[offset] offset += 1 result["CaScope"] = data[offset] offset += 1 result["EndDate"] = data[offset:offset+4] offset += 4 result["CaTargetAidSize"] = data[offset] offset += 1 result["CaTargetAidValue"] = data[offset:offset+16] offset += 16 result["CaTargetAid"] = result["CaTargetAidValue"][0:result["CaTargetAidSize"]] result["CaRfu2"] = data[offset:offset+3] offset += 3 result["CaPublicKeyHeader"] = data[offset:offset+34] offset += 34 result["Signature"] = data[offset:offset+256] result["Message"] = data[0:offset] if not BENCHMARK and not silent: print("CACert:") print_struct(result) return result def parse_cardcert(data: bytes, silent: bool): if not BENCHMARK and not silent: print("Parsing CardCert") print(f"\t{data.hex().upper()}") if len(data) != 316: print("Length of Card Certificate shall be 316") return None if data[0] != 0x91: print("Type of Card Certificate shall be 0x91") return None result = {} offset = 0 result["Type"] = data[offset] offset += 1 result["StructureVersion"] = data[offset] offset += 1 result["IssuerKeyReference"] = data[offset:offset+29] offset += 29 result["CardAidSize"] = data[offset] offset += 1 result["CardAidValue"] = data[offset:offset+16] offset += 16 result["CardAid"] = result["CardAidValue"][0:result["CardAidSize"]] result["CardSerialNumber"] = data[offset:offset+8] offset += 8 result["CardIndex"] = data[offset:offset+4] offset += 4 result["Signature"] = data[offset:offset+256] result["Message"] = data[0:offset] if not BENCHMARK and not silent: print("CardCert:") print_struct(result) return result def parse_cardcert_data(data: bytes, silent: bool): if len(data) != 222: print("Length of recoverable data shall be 222") return None result = {} offset = 0 result["StartDate"] = data[offset:offset+4] offset += 4 result["EndDate"] = data[offset:offset+4] offset += 4 result["CardRights"] = data[offset] offset += 1 result["CardInfo"] = data[offset:offset+7] offset += 7 result["CardRfu"] = data[offset:offset+18] offset += 18 result["EccPublicKey"] = data[offset:offset+64] offset += 64 result["EccRfu"] = data[offset:offset+124] if not BENCHMARK and not silent: print("CardCert data recovered from Signature:") print_struct(result) return result """ Take a BCD date and translate it to the YYYY-MM-DD format """ def bcd_to_date(b): return f"{b[0]>>4}{b[0]&0x0F}{b[1]>>4}{b[1]&0x0F}-{b[2]>>4}{b[2]&0x0F}-{b[3]>>4}{b[3]&0x0F}" """ HexToBytes """ def h(hexstr: str) -> bytes: return bytes.fromhex(''.join(hexstr.split())) """ SHA256 """ def sha256(data: bytes) -> bytes: hsh = hashes.Hash(hashes.SHA256()) hsh.update(data) return hsh.finalize() """ MGF1 """ def mgf1(seed: bytes, length: int) -> bytes: """MGF1-SHA256, comme dans le doc (7 hachages concaténés ici → tronqués à 223).""" out, c = b"", 0 while len(out) < length: out += sha256(seed + c.to_bytes(4, "big")) c += 1 return out[:length] """ Verify the signature returned by the Calypso card at the end of the session """ def verify_signature_of_session(data: bytes, signature: bytes, public_key: bytes, silent: bool): if not BENCHMARK and not silent: print("Verifying the Signature of the Session bytes") print("\tSession bytes: " + data.hex().upper()) print("\tSignature computed by the Card: " + signature.hex().upper()) print("\tPublic Key of the the Card: " + public_key.hex().upper()) """ Create the signature object """ if len(signature) != 64: raise ValueError("Signature (r||s) must be 64-byte long.") r = int.from_bytes(signature[:32], "big") s = int.from_bytes(signature[32:], "big") signature = utils.encode_dss_signature(r, s) """ Create the public key object """ if len(public_key) != 64: raise ValueError("Public Key (X||Y) must be 64-byte long.") public_key = ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), b"\x04" + public_key) """ Verify the signature """ signature_valid = False try: public_key.verify( signature, data, ec.ECDSA(hashes.SHA256()) ) if not BENCHMARK and not silent: print("The Signature of the Session bytes by the Card is OK") signature_valid = True except InvalidSignature: if not silent: print("ERROR: The Signature of the Session bytes by the Card is invalid") return signature_valid """ ISO9792-2 RSA verification, with message recovery Verify ISO/IEC 9796-2 (trailer 0xBC, MGF1-SHA256) and return the result and the recovered infos """ def verify_certificate_iso9796_2(message: bytes, signature: bytes, public_key: bytes, e: int, silent: bool): if not BENCHMARK and not silent: print("Verifying the Signature of the Certificate") print("\tCertificate Message: " + message.hex().upper()) print("\tSignature provided in the Certificate: " + signature.hex().upper()) print("\tPublic Key of the the CA: " + public_key.hex().upper()) n = int((public_key.hex()), 16) if DEBUG: print("M2: " + message.hex().upper()) hash_M2 = sha256(message) if DEBUG: print("SHA256(M2): " + hash_M2.hex().upper()) k = (n.bit_length() + 7) // 8 if len(signature) != k: raise ValueError("Signature length mismatch") # 1) « déchiffrement » RSA: Sr = sig^e mod n sur k octets s_int = int.from_bytes(signature, "big") m_int = pow(s_int, e, n) Sr = m_int.to_bytes(k, "big") if DEBUG: print("Sr: " + Sr.hex().upper()) # 2) Contrôles structurels if Sr[-1] != 0xBC or (Sr[0] >> 7) != 0: print("Sr is invalid") return False, {} # 3) Récupération H* (32 octets à gauche du trailer) H_star = Sr[-33:-1] if DEBUG: print("H*: " + H_star.hex().upper()) # 4) N* = MGF1(H*, 223), D’* = Sr[0:223], D* = N* XOR D’* N_star = mgf1(H_star, 223) if DEBUG: print("N*: " + N_star.hex().upper()) Dprime_star = Sr[:223] if DEBUG: print("D'*: " + Dprime_star.hex().upper()) D_star = bytes(a ^ b for a, b in zip(N_star, Dprime_star)) if DEBUG: print("D*: " + D_star.hex().upper()) # 5) Après mise à zéro du bit de poids fort, le 1er octet doit valoir 0x01 if (D_star[0] & 0x7F) != 0x01: print("D* is invalid") return False, {} # 6) M1* = D*[1:] (données récupérées), puis H' = SHA256(len(M1*)_bits || M1* || SHA256(M2*)) M1_star = D_star[1:] if DEBUG: print("M1*: " + M1_star.hex().upper()) data = (len(M1_star)*8).to_bytes(8, "big") + M1_star + hash_M2 if DEBUG: print("Input Data: " + data.hex().upper()) H_prime = sha256(data) if DEBUG: print("H': " + H_prime.hex().upper()) ok = (H_prime == H_star) info = { "Sr": Sr, "H*": H_star, "H'": H_prime, "M1*": M1_star, } return ok, info """ Verify CardCert against CAPub, and verify that it matches CardPub """ def verify_cardcert(cardcert, capub: bytes, silent: bool): ok, info = verify_certificate_iso9796_2( bytes(cardcert["Message"]), bytes(cardcert["Signature"]), capub, RSA_PUB_EXP, silent) if not ok: print("Failed to verify CardCert against CAPub") return False, None data_raw = info["M1*"] data_struct = parse_cardcert_data(data_raw, silent) if (data_struct["EccPublicKey"] is None) or (len(data_struct["EccPublicKey"]) != 64): print("CardPub recovered from RSA verification against CAPub is invalid") return False, None return True, data_struct["EccPublicKey"] """ Verify CACert against RootPub """ def verify_cacert(cacert, rootpub: bytes, silent: bool): ok, info = verify_certificate_iso9796_2( bytes(cacert["Message"]), bytes(cacert["Signature"]), rootpub, RSA_PUB_EXP, silent) if not ok: print("Failed to verify CACert against RootPub") return False, N data_raw = info["M1*"] N_left = bytes(cacert["CaPublicKeyHeader"]) N_right = data_raw N = N_left + N_right return True, N """ APDU exchange """ def transmit_apdu(hcard, protocol, command, label=""): global BENCH_APDUS_COUNT, BENCH_COMMAND_BYTES, BENCH_RESPONSE_BYTES if not BENCHMARK: print(f"{label} command:\n\t{bytes(command).hex().upper()}") hresult, response = SCardTransmit(hcard, protocol, command) if hresult != SCARD_S_SUCCESS: raise Exception(f"Failed to transmit {label}: " + SCardGetErrorMessage(hresult)) sw = (response[-2] << 8) + response[-1] if not BENCHMARK: print(f"{label} response:\n\t{bytes(response).hex().upper()} (SW={sw:04X})") BENCH_APDUS_COUNT += 1 BENCH_COMMAND_BYTES += len(command) BENCH_RESPONSE_BYTES += len(response) return bytes(response[:-2]), sw """ Self-test with the TN325 vectors """ """ Recorded Card transaction """ tn325_digest = hashes.Hash(hashes.SHA256()) tn325_digest.update(bytes(TN325_INPUT)) tn325_input_sha256 = tn325_digest.finalize() if tn325_input_sha256 != bytes(TN325_INPUT_SHA256): raise Exception("TN325_INPUT is corrupted or TN325_INPUT_SHA256 is wrong") if not verify_signature_of_session(bytes(TN325_INPUT), bytes(TN325_SIGNATURE), bytes(TN325_CARDPUB), True): raise Exception("TN325 self-test failed (Session)") """ Process CACert """ tn325_cacert = parse_cacert(bytes(TN325_CACERT), True) ok, tn325_capub = verify_cacert(tn325_cacert, bytes(CALYPSO_PUBKEY_TEST), True) if not ok: raise Exception("TN325 self-test failed (verify CACert against RootPub, retrieve CAPub)") if not tn325_capub == bytes(TN325_CAPUB): raise Exception("Retrieved CAPub is wrong") """ Process CardCert """ tn325_cardcert = parse_cardcert(bytes(TN325_CARDCERT), True) ok, tn325_cardpub = verify_cardcert(tn325_cardcert, tn325_capub, True) if not ok: raise Exception("TN325 self-test failed (verify CardCert against CAPub, retrieve CardPub)") if not tn325_cardpub == bytes(TN325_CARDPUB): raise Exception("Retrieved CardPub is wrong") """ Test with the sample card """ """ Get CardPub, CardCert and CACert """ tlvs, raw = parse_tlv(FIRST_DF2C_RESP) test_cardpub = tlvs.get(0xDF2C) if test_cardpub is None: raise Exception("Self-test failed (CAPub)") tlvs, raw = parse_tlv(FIRST_DF4A_RESP + FIRST_DF4B_RESP) test_cacert = tlvs.get(0xDF4A) if test_cacert is None: raise Exception("Self-test failed (Get CACert)") tlvs, raw = parse_tlv(FIRST_DF4C_RESP + FIRST_DF4D_RESP) test_cardcert = tlvs.get(0xDF4C) if test_cardcert is None: raise Exception("Self-test failed (Get CardCert)") """ Recorded Card transaction """ if not verify_signature_of_session(bytes(FIRST_INPUT), bytes(FIRST_SIGNATURE), bytes(test_cardpub), True): raise Exception("Self-test failed (Session)") """ Process CACert """ test_cacert = parse_cacert(bytes(test_cacert), True) ok, test_capub = verify_cacert(test_cacert, bytes(CALYPSO_PUBKEY_TEST), True) if not ok: raise Exception("Self-test failed (verify CACert against RootPub, retrieve CAPub)") """ Process CardCert """ test_cardcert = parse_cardcert(bytes(test_cardcert), True) ok, test_cardpub_prime = verify_cardcert(test_cardcert, test_capub, True) if not ok: raise Exception("Self-test failed (verify CardCert against CAPub, retrieve CardPub)") if not bytes(test_cardpub) == test_cardpub_prime: raise Exception("Retrieved CardPub is wrong") """ Main function """ try: hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER) if hresult != SCARD_S_SUCCESS: raise Exception("Failed to establish context : " + SCardGetErrorMessage(hresult)) print("Context established!") try: hresult, readers = SCardListReaders(hcontext, []) if hresult != SCARD_S_SUCCESS: raise Exception("Failed to list readers: " + SCardGetErrorMessage(hresult)) if len(readers) < 1: raise Exception("No smart card readers") contactless_reader = "" print("PCSC Readers:") for reader in readers: print("\t" + reader) if "springcard" in reader.lower(): if (" contactless " in reader.lower()) or (" nfc " in reader.lower()): contactless_reader = reader if contactless_reader == "": raise Exception("SpringCard contactless reader not found") print("Using reader: " + contactless_reader) CARD_PUBLIC_KEY = None CA_CERTIFICATE = None CARD_CERTIFICATE = None INPUT = None SIGNATURE = None try: hresult, hcard, dwActiveProtocol = SCardConnect(hcontext, contactless_reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1) if hresult != SCARD_S_SUCCESS: raise Exception("Unable to connect: " + SCardGetErrorMessage(hresult)) if not BENCHMARK: print(f"Connected with active protocol={dwActiveProtocol}") print() try: t0 = time.perf_counter() APPLICATION_FCI, SW = transmit_apdu(hcard, dwActiveProtocol, CALYPSO_SELECT_APPLICATION, "SelectApplication") if SW != 0x9000: raise Exception("SelectApplication failed") """ generate a random challenge to open the session """ CHALLENGE = list(os.urandom(8)) COMMAND1 = CALYPSO_OPEN_SESSION_AND_READ_ENV_HEADER + CHALLENGE + CALYPSO_OPEN_SESSION_AND_READ_ENV_TRAILER RESPONSE1, SW = transmit_apdu(hcard, dwActiveProtocol, COMMAND1, "OpenSession(Challenge) and Read Environment") if SW != 0x9000: raise Exception("ECDSASign failed") COMMAND2 = CALYPSO_READ_CTC_LIST RESPONSE2, SW = transmit_apdu(hcard, dwActiveProtocol, COMMAND2, "Read CTC List") if SW != 0x9000: raise Exception("Read CTC failed") COMMAND3 = CALYPSO_READ_CONTRACT RESPONSE3, SW = transmit_apdu(hcard, dwActiveProtocol, COMMAND3, "Read Contract") if SW != 0x9000: raise Exception("Read Contract failed") SIGNATURE, SW = transmit_apdu(hcard, dwActiveProtocol, CALYPSO_CLOSE_SESSION, "CloseSession") if SW != 0x9000: raise Exception("CloseSession failed") """ Assemble the input """ INPUT = [ 0x10 ] """ Add command, prefixed by its lenth, without its Le byte because its 00 """ INPUT = INPUT + [ len(COMMAND1) - 1 ] + list(COMMAND1[:-1]) """ Add response, prefixed by its lenth, including its SW """ INPUT = INPUT + [ len(RESPONSE1) + 2 ] + list(RESPONSE1) + [ 0x90, 0x00 ] """ Add command, prefixed by its lenth, including its Le byte """ INPUT = INPUT + [ len(COMMAND2) ] + list(COMMAND2) """ Add response, prefixed by its lenth, including its SW """ INPUT = INPUT + [ len(RESPONSE2) + 2 ] + list(RESPONSE2) + [ 0x90, 0x00 ] """ Add command, prefixed by its lenth, including its Le byte """ INPUT = INPUT + [ len(COMMAND3) ] + list(COMMAND3) """ Add response, prefixed by its lenth, including its SW """ INPUT = INPUT + [ len(RESPONSE3) + 2 ] + list(RESPONSE3) + [ 0x90, 0x00 ] CARD_PUBLIC_KEY, SW = transmit_apdu(hcard, dwActiveProtocol, CALYPSO_GET_CARDPUB, "GetData#CardPub") if SW != 0x9000: raise Exception("Failed to get CardPub") CA_CERTIFICATE_1, SW = transmit_apdu(hcard, dwActiveProtocol, CALYPSO_GET_CACERT_1, "GetData#CACert#1") if SW != 0x9000: raise Exception("Failed to get CACert (#1)") CA_CERTIFICATE_2, SW = transmit_apdu(hcard, dwActiveProtocol, CALYPSO_GET_CACERT_2, "GetData#CACert#2") if SW != 0x9000: raise Exception("Failed to get CACert (#2)") CA_CERTIFICATE = CA_CERTIFICATE_1 + CA_CERTIFICATE_2 CARD_CERTIFICATE_1, SW = transmit_apdu(hcard, dwActiveProtocol, CALYPSO_GET_CARDCERT_1, "GetData#CardCert#1") if SW != 0x9000: raise Exception("Failed to get CardCert (#1)") CARD_CERTIFICATE_2, SW = transmit_apdu(hcard, dwActiveProtocol, CALYPSO_GET_CARDCERT_2, "GetData#CardCert#2") if SW != 0x9000: raise Exception("Failed to get CardCert (#2)") CARD_CERTIFICATE = CARD_CERTIFICATE_1 + CARD_CERTIFICATE_2 t1 = time.perf_counter() BENCH_APDUS_TIME = t1 - t0 finally: hresult = SCardDisconnect(hcard, SCARD_RESET_CARD) if hresult != SCARD_S_SUCCESS: raise Exception("Failed to disconnect: " + SCardGetErrorMessage(hresult)) print("Disconnected") print() except Exception as e: print("Exception:", e) t0 = time.perf_counter() """ Parse the session data """ if (not CARD_PUBLIC_KEY is None) and (not INPUT is None) and (not SIGNATURE is None): """ Retrieve the public key """ tlvs, raw = parse_tlv(CARD_PUBLIC_KEY) CARD_PUBLIC_KEY = tlvs.get(0xDF2C) if CARD_PUBLIC_KEY is None: raise Exception("Format of CardPub is invalid") """ Verify that the public key is valid """ if not len(CARD_PUBLIC_KEY) == 64: raise Exception("Length of CardPub is invalid") """ Verify that the signature is valid """ if not len(SIGNATURE) == 64: raise Exception("Length of Signature is invalid") try: """ Verify the signature """ if not verify_signature_of_session(bytes(INPUT), bytes(SIGNATURE), bytes(CARD_PUBLIC_KEY), False): raise Exception("Failed to verify Card's Signature over Session data") if not BENCHMARK: print("The Signature computed by the Card is valid") except Exception as e: print("Exception:", e) """ Parse the certificates """ if (not CARD_PUBLIC_KEY is None) and (not CARD_CERTIFICATE is None) and (not CA_CERTIFICATE is None): if not BENCHMARK: print("Verify PKI") """ Process CaCert """ tlvs, raw = parse_tlv(CA_CERTIFICATE) CA_CERTIFICATE = tlvs.get(0xDF4A) if CA_CERTIFICATE is None: raise Exception("Format of CACert is invalid") CA_CERTIFICATE = parse_cacert(CA_CERTIFICATE, False) ok, CA_PUBLIC_KEY = verify_cacert(CA_CERTIFICATE, bytes(CALYPSO_PUBKEY_TEST), False) if not ok: raise Exception("Failed to Verify CACert against RootPub / Failed to retrieve CAPub") """ Process CardCert """ tlvs, raw = parse_tlv(CARD_CERTIFICATE) CARD_CERTIFICATE = tlvs.get(0xDF4C) if CARD_CERTIFICATE is None: raise Exception("Format of CardCert is invalid") CARD_CERTIFICATE = parse_cardcert(CARD_CERTIFICATE, False) ok, CARD_PUBLIC_KEY_PRIME = verify_cardcert(CARD_CERTIFICATE, CA_PUBLIC_KEY, False) if not ok: raise Exception("Failed to Verify CardCert against CAPub / Failed to retrieve CardPub") if not bytes(CARD_PUBLIC_KEY) == CARD_PUBLIC_KEY_PRIME: raise Exception("CardCert and retrieved CardPub don't match actual CardPub used for the transaction") if not BENCHMARK: print("The Card is authentic") t1 = time.perf_counter() BENCH_CRYPTO_TIME = t1 - t0 print(f"Bytes exchanged") print(f"\tPC to Card: {BENCH_COMMAND_BYTES}") print(f"\tCard to PC: {BENCH_RESPONSE_BYTES}") print(f"\tTotal: {BENCH_COMMAND_BYTES + BENCH_RESPONSE_BYTES}") print(f"Number of APDU: {BENCH_APDUS_COUNT}") print(f"Time elapsed") print(f"\tCard communication: {BENCH_APDUS_TIME}") print(f"\tPostponed Cryptography: {BENCH_CRYPTO_TIME}") finally: hresult = SCardReleaseContext(hcontext) if hresult != SCARD_S_SUCCESS: raise Exception("Failed to release context: " + SCardGetErrorMessage(hresult)) print("Released context.") except Exception as e: print("Exception:", e) import sys if "win32" == sys.platform: print("Press Enter to continue") sys.stdin.read(1)