Всем привет!

Традиционное вступление в стиле "плач Ярославны": GlobalPlatform, ISO 7816, JavaCard и прочие смежные стандарты - боль. Тонна материала написанная сухим языком так и навивает мысль, что авторы всего этого не инженеры, а юристы. Для тех немногих счастливчиков, кому не приходится иметь с этим дело, в качестве примера скажу, что каждый стандарт ETSI начинается со смысловых определений и толкований глаголов "shall", "shall not", "should", "should not" и т.д. Нет, в канцелярском стиле ничего плохого нет. Плохо становится когда он плавно переходит в поросячью латынь - и это одно из самых терпимых определений. Это же додуматься надо "размазать" требования к несчастной SMS-ске между пятью стандартами (ETSI 102-223/-225/-226 и 131-111/-115).

Ну вот ты преодолел пучины стандартов, затем засел за написание JavaCard-апплета, с чем тоже успешно справился, ну а дальше начинается квест "найди тулзы". Инструментарий от Oracle для сборки .cap-файлов недоступен из России, благо есть один удобный в открытом доступе. Там же рядышком лежит тулза для установки/удаления апплетов (да и вообще управления жизненным циклом карты).

Итак, ты скомпилировал и загрузил апплет на карту. Классно! А дальше? А дальше поговорим в статье.

Что есть на рынке?

Вообще, инструментов для взаимодействия с картой крайне мало. Из известных мне это JCShell от NXP Semiconductors и PCOM от Oberthur. Судя по синтаксису можно заключить, что авторы, как бы помягче выразиться, как никто другой пострадали от чтения отраслевых стандартов.

Вот скрипт на JCShell:


DEFUN External_authenticate_session
	@includeJCipherPlugin
	
	/send "0084 0000 08" *9000
	/set-var RAND_IC ${last.response.data}
	/echo ${RAND_IC}

	/set-var data $(enc -m DES3/CBC -k ${KENC} -p NOPAD ${RAND_IC})

	## send EXT AUTH
	/send "0082 00${KIDX} #(${data})" *9000
END

А вот на PCOM:

;; Prepare data for EXT AUTH
.SET_DATA %RAND_IFD %RAND_IC %K_IFD
.SET_KEY %KENC
.SET_VECT_INI 0000000000000000
.DES3CBC L FF
.DISPLAY L

;;Compute MAC
.SET_DATA L
.SET_KEY %KMAC
.SET_VECT_INI 0000000000000000
.MAC3 J 80 /P
.DISPLAY J

Всякий раз как сложность сценария выходит за рамки "Status Word" читаемость скриптов скатывается на уровень регулярных выражений. К тому же оба инструмента проприетарные и в открытом доступе их не найти.

Попытка упростить себе жизнь.

За последние годы я четко определил видение своего будущего - стать миллионером! На худой конец освою python. Штош, как человек далекий от питона с удивлением обнаружил, насколько он удобен: операции со списками, функциями, объектами и прочим-прочим - все максимально абстрагировано в угоду быстрой разработки и читаемости кода (тут главное не увлекаться). Самым любознательным советую сравнить исходники FunGP и GlobalPlatformPro в части, отвечающей за установление защищенного соединения.

В общем и целом, результат получился следующим:

isd.mutual_auth()

Это всё, что нужно написать тестеру, чтобы осуществить процедуру MUTUAL AUTHENTICATE. Ладно, шучу - не всё, просто привел в качестве примера на контрасте с предыдущими монстрами. Но вот скрипт, который считывает данные с карты:

from fun_gp import SmartCard
from fun_gp.utils import lv

known_readers = ['ACS ACR39U ICC Reader 0', 'ACS ACR39U ICC Reader 00 00']
isd_default_keys = ["404142434445464748494A4B4C4D4E4F",
                    "404142434445464748494A4B4C4D4E4F",
                    "404142434445464748494A4B4C4D4E4F"]

isd = SmartCard(known_readers, isd_default_keys)

isd.apdu_plain('00A4 0400' + lv('a000000151000000'))
#

isd.apdu_plain('80CA 2F00 02 5C00', name='GET DATA: list of applications')
isd.apdu_plain('80CA 00E0 00', expected_sw=0x9000)
isd.apdu_plain('80CA 0042 00', expected_sw=0x6A88, name='GET DATA: Issuer Identification Number')

Тут обращаем внимание на следующие элементы:

known_readers[] - узнать имя ридера можно разными путями, но самый простой - это запустить этот скрипт. Он рухнет, но перед этим выдаст список доступных ридеров, откуда и возьмете название своего:

Context established.
Available PCSC readers:
    ACS ACR39U ICC Reader 0
>> 00A40400 08 A000000151000000
Traceback (most recent call last):
--//--

isd_default_keys[] - святая-святых. Убедитесь, что они корректны.

SmartCard(known_readers, isd_default_keys)- обратит�� внимание, что переменная, хранящая указатель на экземпляр этого класса называется isd. На всякий случай уточню, что ISD (Issuer Security Domain) - это самый главный апплет на карте, который отвечает за установку/удаление других апплетов, а также обладает правом окирпичить вашу карту.

Банального общения с картой мне показалось недостаточным, потому-то проект перерос из тулзы для коммуникации по протоколу APDU в легковесный (пока) фреймворк, способный накатить апплет на карту:

from fun_gp import SmartCard
from fun_gp.utils import lv

known_readers = ['ACS ACR39U ICC Reader 0', 'ACS ACR39U ICC Reader 00 00']
isd_default_keys = ["404142434445464748494A4B4C4D4E4F",
                    "404142434445464748494A4B4C4D4E4F",
                    "404142434445464748494A4B4C4D4E4F"]

applets = [
    "../../resources/SimpleApplet.cap", # 'A000000082'
]

isd = SmartCard(known_readers, isd_default_keys)

isd.apdu_plain('00A4 0400' + lv('a000000151000000'), expected_sw=0x9000, name='SELECT: isd')
isd.mutual_auth()

for cap in applets:
    isd.install_app_scp02(cap)

Тулза GlobalPlatformPro (да и беспокойные JCShell с PCOM'ом) требует явного указания AID пакета и AID апплета. Я решил поручить эту грязную работенку методу install_app_scp02(), который извлекает эту инфу из .cap-файла. Важное уточнение - после вызова метода isd.mutual_auth() вся последующая коммуникация с картой через методы, оканчивающиеся на scp02(): apdu_scp02(), install_app_scp02() и т.д.

Удалить апплет можно так:

from fun_gp import SmartCard
from fun_gp.utils import lv

known_readers = ['ACS ACR39U ICC Reader 0', 'ACS ACR39U ICC Reader 00 00']
isd_default_keys = ["404142434445464748494A4B4C4D4E4F",
                    "404142434445464748494A4B4C4D4E4F",
                    "404142434445464748494A4B4C4D4E4F"]

packages = [  
    'A000000082', # 'SimpleApplet.cap'
]

isd = SmartCard(known_readers, isd_default_keys)

isd.apdu_plain('00A4 0400' + lv('a000000151000000'), expected_sw=0x9000, name='SELECT: isd')
isd.mutual_auth()

for aid in packages:
    isd.uninstall_app_scp02(package_aid=aid)

Надеюсь, те немногие, кто занят в области разработки под смарт-карты дадут обратную связь. Отдельное обращение к python-лордам и миледи: "не стреляйте в пианиста, он играет как умеет".

Всем пока!

Исходники.