В предыдущей статье мы воспользовались встроенными в смарт-карту механизмами безопасной персонализации апплета, тем самым нивелировали две проблемы:
1. городить свой ЗОС;
2. самоотверженно отлаживать его.
Однако это решение создает еще большую угрозу, т.к. ключи ISD дают полную власть над картой и если они попадут к человеку с горячим сердцем, холодной головой и грязными мыслишками, то наш офис – его офис.
Но долой панику, у нас очередная гениальная идея: создаем Supplementary Security Domain!

Исходники: ISD_secured_appet, FunGP.

New world order
New world order

Обновленная архитектура

Как видно из приведенной схемы, SSD встраивается в нынешнюю архитектуру аккурат между апплетом и ISD. При этом роли строго разграничены: ISD как и прежде управляет содержимым всей карты, но лишен возможности взаимодействовать с апплетом – за это отвечает SSD. Таким образом, даже если злоумышленник заполучит ключи от вспомогательного домена, его привилегии будут жестко ограничены.
Давайте перейдем непосредственно к коду. Апплет остался неизменным, зато скриптовая база FunGP существенно разрослась.

FunGP: создаем SSD

Начнем с мантры: скачайте и установите библиотеку FunGP (README вам в помощь), затем в консоли перейдите в папку ./tests/SSD_secured_applet в которой хранятся пять скриптов и два вспомогательных модуля.
Для лучшего восприятия здесь будут выкладываться логически связанные блоки кода. Первой на очереди у нас процедура установки SSD.

Установка SSD

##### 01_install_ssd.py #####

def main():
    with Reader() as reader:
        isd = SmartCard(reader.plain_apdu, SCP02(isd_keyset), CCM())
        isd.transmit('00a4 0400', 0x90, 0x00, 'Select ISD')
        isd.mutual_auth(security_level=SecurityLevel.C_MAC)
        install_ssd(isd)

Если вы читали предыдущие статьи, то для вас тут ничего нового: типовая инициализация кард-ридера и взаимная аутентификация со смарт-картой по протоколу SCP02. Из свежего - вызов функции install_ssd():

def install_ssd(isd:SmartCard):
    for_install = isd._ccm.make_cmd_install_for_install(
        load_file_aid  = 'A0000001515350',
        module_aid     = ssd_pkg,
        instance_aid   = ssd_aid, # my SSD
        install_params = InstallParams(
            # GP CIC, 3.3.1.3:
            # Tag '81' may be omitted only if the card supports only a single
            # SCP (i.e. obvious default value) or if the card otherwise supports a
            # default value or specific policy defined by the Card Issuer,
            # which remains out of scope of this document.
            '8102 0255' # SCP02, i=55
            '8201 20'   # accept extradition from ISD
        ),
        # the simulator accepts 800000, while real cards fail with SW 6A80.
        privileges = lv_list('80'),
    )
    isd.transmit(for_install, 0x90, 0x00, 'INSTALL[for install and make selectable]')

Здесь начинается интересное. Перво-наперво нам надо уяснить логику создания SSD: мы не пишем его как стандартный апплет, а создаем экземпляр. Нет, написать его можно при помощи той же org.globalplatform, но функционал будет ограничен. Так вот, инстанциирование должно быть от некоего пакета, который уже на борту вашей карты. Давайте проведем небольшое расследование, чтобы узнать, от какого именно нам надо отталкиваться:

$ java -jar gp.jar --list
# Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F
ISD: A000000151000000 (SECURED)
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement, TrustedPath, AuthorizedManagement, TokenVerification, GlobalDelete, GlobalLock, GlobalRegistry, FinalApplication, ReceiptGeneration

PKG: A0000001515350 (LOADED)
     Parent:   A000000151000000
     Version:  255.255
     Applet:   A000000151535041

PKG: A0000000620204 (LOADED)
     Parent:   A000000151000000
     Version:  1.0

PKG: A0000000620202 (LOADED)
     Parent:   A000000151000000
     Version:  1.3

Выше представлен лог утилиты GlobalPlatformPro с информацией о содержимом карты. В первых строках сказано, что ISD инстанциирован от пакета с AID «A0000001515350», а значит нам надо поступить также в отношении SSD. Далее вниз по списку, находим этот пакет и видим, что внутри него есть модуль с AID «A000000151535041». От этого модуля нам и нужно отпочковать свой экземпляр. А можно не исполнять Эркюля Пуаро и как нормальный человек заглянуть в GP CIC v2.0, где в параграфе 3.3.1.1 «Executable Load File AID and Module AID» дается четкое определение нужных AID’ов.
Так, от чего отталкиваться разобрались, приступаем к формированию параметров инсталляции. Их описание для команды install [for install & make selectable] представлено в GPCS 2.3:

GlobalPlatform Card Specification 2.3
GlobalPlatform Card Specification 2.3

Нас интересуют поля «Install parameters» и «Privileges». Первое поле регулируется спецификацией GlobalPlatform Common Implementation Configuration 2.0:

GlobalPlatform Common Implemntation Configuration 2.0
GlobalPlatform Common Implemntation Configuration 2.0

В скрипте мы указываем лишь первые два тега:
- '8102 0255' # SCP02, i=55
- '8201 20'   # accept extradition from ISD
Тег 0х81 передает идентификатор протокола и конфигурацию применяемых механизмов безопасности. За разъяснениями можете обратиться к предыдущей статье, здесь же скажу, что несмотря на значение «0 to N» в поле «Presence» представленной таблицы, GP CIC настоятельно рекомендует вставить этот тег хотя бы один раз. Более того, мои карты падают с ошибкой если этого не сделать.
Тег 0х82 кодирует право нашего SSD на прием апплетов под свою иерархию. Значение 0x20 означает «Принимать под крыло можно только от ISD»:

Ну и на последок разберем поле «Privileges»:

Мои карты принимают только один байт, несмотря на то, что здесь в поле "Privileges" дается выбор «1, 3». Я не стал заморачиваться, т.к. для текущей задачи привилегии «Security Domain» более чем достаточно.
Ну все, команда сформирована, можно передавать:

isd.transmit(for_install, 0x90, 0x00, 'INSTALL[for install and make selectable]')

Для наглядности можем сравнить логи утилиты GlobalPlatformPro до и после установки SSD:

ДО:

telman@Ubuntu-24-04-3:~/develop/GlobalPlatformPro$ java -jar gp.jar --list
# Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F
ISD: A000000151000000 (SECURED)
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement, TrustedPath, AuthorizedManagement, TokenVerification, GlobalDelete, GlobalLock, GlobalRegistry, FinalApplication, ReceiptGeneration

PKG: A0000001515350 (LOADED)
     Parent:   A000000151000000
     Version:  255.255
     Applet:   A000000151535041

PKG: A0000000620204 (LOADED)
     Parent:   A000000151000000
     Version:  1.0

PKG: A0000000620202 (LOADED)
     Parent:   A000000151000000
     Version:  1.3

ПОСЛЕ:

telman@Ubuntu-24-04-3:~/develop/GlobalPlatformPro$ java -jar gp.jar --list
# Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F
ISD: A000000151000000 (SECURED)
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement, TrustedPath, AuthorizedManagement, TokenVerification, GlobalDelete, GlobalLock, GlobalRegistry, FinalApplication, ReceiptGeneration

DOM: A0000001515350416D7920535344 (SELECTABLE) <<<=== НАШ СЛОНЯРА ===>>>
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, TrustedPath

PKG: A0000001515350 (LOADED)
     Parent:   A000000151000000
     Version:  255.255
     Applet:   A000000151535041

PKG: A0000000620204 (LOADED)
     Parent:   A000000151000000
     Version:  1.0

PKG: A0000000620202 (LOADED)
     Parent:   A000000151000000
     Version:  1.3

Сейчас он находится на жизненном цикле «SELECTABLE». Согласно GP CS v2.3,  п. 5.3.2, всего есть четыре состояния:
1. INSTALLED
2. SELECTABLE
3. PERSONALIZED
4. LOCKED
SELECTABLE значит, что он готов к персонализации (получению ключей) но не в состоянии взять под свою иерархию апплеты. Давайте это исправим!

Персонализация SSD

Итак, в методе main() сразу за вызовом install_ssd() следуют две функции:

def install_for_perso(isd:SmartCard, ssd_aid:str):
    isd.transmit(
        '80e6 2000' + lv_hex('0000' + lv_hex(ssd_aid) + '000000'),
        0x90, 0x00,
        'INSTALL [for personalization] my SSD'
    )

Согласно п. 11.11 «STORE DATA command» спецификации GP CS v2.3, запись ключей в SSD осуществляется в два этапа:
1. Команда install [for personalization] – уведомляем ОС карты, что последующая команда STORE DATA предназначена не для ISD (чьими ключами мы провели взаимную аутентификацию), на другому домену.
2. Команда STORE DATA – сохранить ключи в целевом SSD.

GlobalPlatform Card Specification 2.3
GlobalPlatform Card Specification 2.3

В поле CDATA команды install [for personalization] передается AID SSD-адресата, а именно "A0000001515350416D7920535344":

Мы на финишной прямой - непосредственно персонализация:

def personalize_ssd(isd:SmartCard):
    
    key_enc = __encrypt_key(isd, ssd_keyset[0])
    kcv_enc = __calculate_kcv(isd, ssd_keyset[0])
    
    key_mac = __encrypt_key(isd, ssd_keyset[1])
    kcv_mac = __calculate_kcv(isd, ssd_keyset[1])

    key_dek = __encrypt_key(isd, ssd_keyset[2])
    kcv_dek = __calculate_kcv(isd, ssd_keyset[2])

    crt_enc = __compile_key_crt('18', '01', '20', kcv_enc) # C-ENC: 18
    crt_mac = __compile_key_crt('14', '02', '20', kcv_mac) # C-MAC: 14
    crt_dek = __compile_key_crt('48', '03', '20', kcv_dek) # C-DEK: 48

    # GPCS 2.3, 11.11.4
    key_info_data = '00b9' + lv_hex(crt_enc + crt_mac + crt_dek) 

    # GPCS 2.3, 11.11.4.1.1
    sym_key_scheme = '8113' + lv_hex(key_enc) + '8113' + lv_hex(key_mac) + '8113' + lv_hex(key_dek)
    
    cmd = lv_hex(key_info_data + sym_key_scheme)
    # print(cmd)
    
    isd.transmit('80E2 8800' + cmd, 0x90, 0x00, 'Store data')

def personalize_ssd(isd:SmartCard) формирует DGI (Data Group Information) в соответствии с GP CS v2.3, параграф 11.11.4:

Т.е. в поле CDATA мы должны передать DGI «00B9», внутри которого три TLV структуры, описывающие ключи ENC, MAC и DEK. Следом идут три DGI «8183», в поле value которых непосредственно сами ключи, зашифрованные ключом DEK корневого домена безопасности.

Все поля очень важны и имеют конкретные значения, однако, во избежание чрезмерного раздувания статьи, здесь я ограничусь лишь ссылками к тем или иным параграфам/таблицам соответствующих спецификаций, которые вы сможете найти в комментариях к коду скрипта 01_install_ssd.py.

После успешной персонализации лог утилиты GlobalPlatformPro выдает следующее:

$ java -jar gp.jar --list
# Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F
ISD: A000000151000000 (SECURED)
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement, TrustedPath, AuthorizedManagement, TokenVerification, GlobalDelete, GlobalLock, GlobalRegistry, FinalApplication, ReceiptGeneration

DOM: A0000001515350416D7920535344 (PERSONALIZED)  <<<=== ПЕРЕХОД С SELECTABLE НА PERSONALIZED ===>>>
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, TrustedPath

PKG: A0000001515350 (LOADED)
     Parent:   A000000151000000
     Version:  255.255
     Applet:   A000000151535041

PKG: A0000000620204 (LOADED)
     Parent:   A000000151000000
     Version:  1.0

PKG: A0000000620202 (LOADED)
     Parent:   A000000151000000
     Version:  1.3

Теперь наш SSD готов выполнить свою миссию. Давайте же снабдим его апплетом!

Установка апплета

В том же скрипте 01_install_ssd.py, в методе main() осталось разобрать две последние строки:

isd.install_app_scp02(applet_cap_path, InstallParams(),exp_sw1=0x90, exp_sw2=0x00)
install_for_extradition(isd, ssd_aid)

Первая нам знакома еще с предыдущих статей - она устанавливает апплет под ISD. Помните, что лишь главный домен безопасности вправе накатывать/удалять их.

Давайте глянем на лог после установки ISD_secured_applet.cap:

$ java -jar gp.jar --list
# Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F
ISD: A000000151000000 (SECURED)
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement, TrustedPath, AuthorizedManagement, TokenVerification, GlobalDelete, GlobalLock, GlobalRegistry, FinalApplication, ReceiptGeneration

DOM: A0000001515350416D7920535344 (PERSONALIZED)
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, TrustedPath

APP: A0000000864953442053656375726564 (SELECTABLE) <=== НАШ АППЛЕТ ===>
     Parent:   A000000151000000           <=== ISD ЯВЛЯЕТСЯ ЕГО РОДИТЕЛЕМ ===>
     From:     A000000086

PKG: A0000001515350 (LOADED)
     Parent:   A000000151000000
     Version:  255.255
     Applet:   A000000151535041

PKG: A0000000620204 (LOADED)
     Parent:   A000000151000000
     Version:  1.0

PKG: A0000000620202 (LOADED)
     Parent:   A000000151000000
     Version:  1.3

PKG: A000000086 (LOADED)                   <=== МОДУЛЬ НАШЕГО АППЛЕТА ===>
     Parent:   A000000151000000
     Version:  1.0
     Applet:   A0000000864953442053656375726564

Второй строкой идет функция, которая выполняет целевую задачу - перемещает апплет под наш SSD.

def install_for_extradition(isd:SmartCard, ssd_aid:str):
    extradition_params = ''
    isd.transmit( # GP CS v.2.3, clause 11.5.2.3.4
        '80e6 1000' + lv_hex(lv_hex(ssd_aid) + '00' + lv_hex(isd_secured_applet_aid) + '00' + lv_hex(extradition_params) + '00'),
        0x90, 0x00,
        'INSTALL [for extradition] my SSD'
    )

Она передает карте команду install[for extradition] с параметрами, описанными в GPCS 2.3:

GPCS 2.3, clause 11.5 INSTALL Command
GPCS 2.3, clause 11.5 INSTALL Command

В частности, мы указываем AID домена безопасности, под чью юрисдикцию собираемся передать апплет, а также AID последнего. Поле 'Extradition Parameters field' оставляем пустым т.к. дополнительная мера защиты в виде верифицирующего токена не применяется.

После того, как эта команда будет обработана картой, лог утилиты gp.exe покажет следующее:

$ java -jar gp.jar --list
# Warning: no keys given, defaulting to 404142434445464748494A4B4C4D4E4F
ISD: A000000151000000 (SECURED)
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, CardLock, CardTerminate, CardReset, CVMManagement, TrustedPath, AuthorizedManagement, TokenVerification, GlobalDelete, GlobalLock, GlobalRegistry, FinalApplication, ReceiptGeneration

DOM: A0000001515350416D7920535344 (PERSONALIZED) <=== НАШ SSD ===>
     Parent:   A000000151000000
     From:     A0000001515350
     Privs:    SecurityDomain, TrustedPath

APP: A0000000864953442053656375726564 (SELECTABLE) <=== НАШ АППЛЕТ ===>
     Parent:   A0000001515350416D7920535344 <=== ТЕПЕРЬ РОДИТЕЛЕМ ЯВЛЯЕТСЯ НАШ SSD ===>
     From:     A000000086

PKG: A0000001515350 (LOADED)
     Parent:   A000000151000000
     Version:  255.255
     Applet:   A000000151535041

PKG: A0000000620204 (LOADED)
     Parent:   A000000151000000
     Version:  1.0

PKG: A0000000620202 (LOADED)
     Parent:   A000000151000000
     Version:  1.3

PKG: A000000086 (LOADED)        <=== МОДУЛЬ НАШЕГО АППЛЕТА ===>
     Parent:   A000000151000000 <=== ЕГО РОДИТЕЛЬ ОСТАЛСЯ ПРЕЖНИМ  ===>
     Version:  1.0
     Applet:   A0000000864953442053656375726564

На этом все, дорогой читатель. Чтобы не раздувать статью, здесь я ограничился лишь первым из пяти скриптов. Если тебе интересно как работают остальные, то за разъяснениями ты можешь смело обратиться к предыдущей статье.
Всем пока!