Когда начинаешь изучать смарт карты, то все выглядит достаточно понятно и логично: команды APDU несложны и описаны в ISO 7816-4, интерфейс winscard описан в MSDN. В глубины PCSC погружаться особо и не приходится. Первые программы можно написать довольно быстро. Непонятные особенности вылезают чуть позже, и выясняется, что известные два протокола T=0 и T=1 на самом деле совсем разные протоколы, и они просто похожи друг на друга. Поначалу возникает соблазн работать с ними одинаково, а это может доставить много хлопот вплоть до сбоя коммуникации в самый неожиданный момент.
Найти совсем вводную статью про смарт-карты и APDU или работающий «Hello World» не так сложно, поэтому я понадеюсь, что читатель уже как-то знаком с этой темой, и попробую рассказать то, что поможет понять смарт-карты чуть глубже. Для этого погрузимся на уровень ниже.
Протокол Т=1 оставим в стороне, потому что он устроен сложнее, чем Т=0, и в добавок в PCSC под ним прячутся два разных протокола: Т=1 для контактных карт (ISO 7816-3) и Т=1 для бесконтактных карт (NFC совместимые), а этот последний протокол существует в трех типах: ISO14443А, ISO14443В и FeliCa. Всё это на уровне PCSC считывателей отображается просто как T=1.
Протокол T=0, о котором пойдет речь, описан в стандартах:
ISO 7816-3 “Identification cards — Integrated circuit cards — Part 3: Cards with contacts — Electrical interface and transmission protocols”;
ETSI TS 102 221 Smart Cards; UICC-Terminal interface; Physical and logical characteristics;
EMV 4.3 Book 1, Application Independent ICC to Terminal Interface Requirements.
В переводном ГОСТ Р ИСО/МЭК 7816-3 он называется протоколом полудуплексной передачи асинхронных знаков. Ключевое слово в этой фразе — это «знаков», потому что тут минимальная единица обмена — это знак (байт), а не блок как в T=1.
Структура TPDU
На самом деле APDU — это абстракция, унифицированный интерфейс. В Т=0 данные пары «команда-ответ» оборачиваются в структуру TPDU, а не APDU. Это вещи разные. Кстати, почему-то все три стандарта расшифровывают эту аббревиатуру чуть по-разному:
ISO 7816-3: transmission protocol data unit;
ETSI TS 102 221: Transfer Protocol Data Unit;
EMV Book 1: Transport Protocol Data Unit.
Команда TPDU (C-TPDU) состоит из заголовка и данных. Заголовок всегда состоит из пяти байтов CLA-INS-P1-P2-P3. Данные опциональны (сравни с APDU: CLA-INS-P1-P2-Lc-Data-Le, где заголовок — это только 4 из них).
Байт CLA — класс команды. Может быть любым значением, кроме FF, которое используется при инициализации протокола для согласования параметров обмена (PPS). Зная это, производители PCSC считывателей часто встраивают в драйвер поддержку APDU команд с классом FF для выполнения специфических функций управления самим считывателем или для доступа к картам памяти. Команды в таком виде, конечно, в карту не попадают никогда, а интерпретируются самим драйвером. В мануале на конкретный считыватель про это можно прочитать. Такие команды часто называют псевдо APDU (Pseudo APDU, PPDU).
Байт INS — инструкция, идентификатор действия, которое карта должна выполнить. Старший ниббл (4 бита) не может быть равен 6 или 9, потому что эти значения используются для статуса SW (кода ответа). Обратите внимание, что до сих пор в четвертой части ISO 7816 ни одна инструкция не начинается с 6 или 9.
Для одной команды можно передать поле данных или только от терминала карте (т.н. case 3 команда) или обратно (т.н. case 2 команда), а можно данные вообще не передавать (т.н. case 1 команда). Невозможно предать данные в обоих направлениях за один раз. Направление передачи для каждой инструкции обязательно должно быть известно заранее. Только так каждый из участников протокола может понять: надо ли ждать данные или передавать самому. Например, в ISO 7816-4 описаны некоторые команды, поэтому все, кто этому стандарту следует, знают априори, что GET CHALLENGE (генерация случайного числа) получает данные от карты, а UPDATE BINARY (запись данных файл) — передает данные карте.
Можно заметить, что зачастую (но не всегда) значение INS четное. Это историческое наследие от первой версии этого протокола, когда в далекие времена для записи данных в EEPROM или их удаления требовалось внешнее высокое напряжение. Если карта в ответ на получение заголовка TPDU отвечала значением INS+1 (т.е. взводила младший бит), то это значило требование передать данные и подать искомое напряжение на контакт С6 (Vpp). Сегодня каждая контактная микросхема умеет сама накачивать необходимое напряжение программирования, поэтому этот механизм из стандартов давно выведен. Некоторое время четные INS соблюдали ради совместимости со старыми микросхемами, а теперь это только традиция.
P1, P2 — параметры команды, трактуются в зависимости от INS.
P3 — размер передаваемых или получаемых данных. Для case 2 команд — это размер ожидаемых данных, а для case 3 — передаваемых.
Ответ TPDU (R-TPDU) состоит, как и в случае с APDU, из опциональных данных и обязательного статуса SW=SW1||SW2 (2 байта). Обязательные условия:
Байт SW1 может иметь старший ниббл только 6 или 9.
Значение SW1=0x60 запрещено.
Процедура обмена
Обмен всегда начинает терминал (устройство сопряжения, terminal, interface device, IFD) с отправки заголовка TPDU, а карта контролирует этот процесс, отвечая процедурным байтом (procedure byte, PB). Именно из-за этого PB в протоколе и есть требования касательно наличия/отсутствия 6 и 9 в инструкции и SW1.
PB | Трактовка |
INS | Байт инструкции из полученного заголовка TPDU. Готов к обмену данными. Передавать данные надо сразу все за один раз. |
INS xor FF | Инвертированный байт инструкции из полученного заголовка TPDU. Готов к обмену следующим байтом данных. Режим обмена данными по одному байту. |
60 | Идет обработка, жди следующего PB. На время обработки команды отводится определенный таймаут, в течение которого карта обязана что-то ответить. Если команда вычислительно сложная и требуется больше времени, то карта продляет таймаут (wait time extension, WTX) с помощью этого байта. |
6X или 9X | Первый байт SW (SW1), за котором должен следовать SW2. Выполнение команды закончено. |
Обратите внимание, что 9 = 6 xor F. Это значит, что если INS не может начинаться с 6 или 9, то и INS xor FF тоже не может начинаться с 6 и 9. Так протокол никогда не перепутает INS и INS xor FF со значениями SW1.
Поскольку APDU — это своего рода абстракция для унификации и упрощения обмена с картой, то далее рассмотрим несколько основных сценариев обмена по протоколу T=0, чтобы стало понятно, как APDU и TPDU соотносятся друг с другом. Обратите внимание, что если разрешить любые значения INS и SW1, то это запутает обе стороны настолько, что обмен станет невозможным.
Команда case 1 APDU — это самый простой сценарий. Передаем APDU вида CLA-INS-P1-P2.
IFD Card
CLA-INS-P1-P2-00 ==>
<== SW1, SW2
Команда case 2 APDU — команда получает данные от карты. Передаем APDU вида CLA-INS-P1-P2-Le, где если Le=0, то это значит 256 байт.
IFD Card
CLA-INS-P1-P2-Le ==>
<== INS
<== Data (Le байт), SW1, SW2
Сценарий, если команда case 2 APDU завершается с ошибкой.
IFD Card
CLA-INS-P1-P2-Le ==>
<== SW1,SW2 != 90 00
Команда case 3 APDU — команда передает данные карте. Передаем APDU вида CLA-INS-P1-P2-Lc-Data, где Lc > 0.
IFD Card
CLA-INS-P1-P2-Lc ==>
<== INS
Data ==>
<== SW1, SW2
Сценарий, если команда передает данные карте и долго выполняется.
IFD Card
CLA-INS-P1-P2-Lc ==>
<== INS
Data ==>
<== 60
<== 60
. . .
<== SW1, SW2
Сценарий, если команда передает данные карте по одному байту.
IFD Card
CLA-INS-P1-P2-Lc ==>
<== INS xor FF
1й байт данных ==>
<== INS xor FF
2й байт данных ==>
<== INS xor FF
. . .
n-й байт данных ==>
<== SW1, SW2
Команда case 4 APDU — команда и передает данные, и получает. Передаем APDU вида CLA-INS-P1-P2-Lc-Data-Le. Обмен данными в обоих направлениях достигается с помощью комбинации команд case 3 (она передает данные) и case 2 (она забирает данные).
В протоколе Т=0 есть особый SW=61 XX. Он значит, что у карты подготовлено XX байт данных, которые можно забрать командой GET RESPONSE (INS=C0): 00-C0-00-00-XX. Причем, если XX=00, то это значит 256 байт. Обмен будет выглядеть так:
IFD Card
CLA-INS-P1-P2-Lc ==>
<== INS
Data ==>
<== SW1=61 SW2=Le
00-C0-00-00-Le ==>
<== INS=C0
<== Data (Le байт), SW1, SW2
На заметку любителям истории: в 90-е гг. в GSM 11.11 использовался чуть другой T=0: там, например, вместо SW1=61 было задействовано SW1=9F. Сегодня этой разницы нет.
Extended APDU
То, о чем говорится выше, применимо только к стандартным APDU, которые могут передавать не более 256 байт в каждом направлении, но ведь есть еще и Extended APDU, которые позволяют передавать данные размером до 65535 байт в каждом направлении. Стандарт ISO 7816-3 предусматривает адаптацию T=0 и к такому варианту.
Команды Extended APDU, где данных менее 256 байт, представляются тривиальным образом в виде уже описанных TPDU, и обмен тогда происходит по сценариям, приведенным выше. Далее предполагаем, у нас передаются данные больше 256 байт.
Команда case 2e APDU — команда получает данные от карты. Пусть Le = XXZZ, тогда мы должны передать APDU вида CLA-INS-P1-P2-00-XX-ZZ. Обмен по Т=0 будет таким:
Терминал посылает TPDU CLA-INS-P1-P2-00, а в ответ получает SW=61 00. Это значит, что у карты есть 256 байт данных.
Терминал посылает GET RESPONSE: 00-C0-00-00-00, и в ответ получает 256 байт данных и снова SW=61 00. И этот шаг повторяется до тех пор, пока не придет последняя порция данных и ответ SW=90 00 (или по крайней мере ответ, отличный от SW1=61).
IFD Card
CLA-INS-P1-P2-00 ==>
<== SW=6100
00-C0-00-00-00 ==>
<== INS=C0
<== Data (256 байт), SW=6100
. . .
00-C0-00-00-00 ==>
<== INS=C0
<== Data (256 байт), SW=61XX
Считываем последнюю порцию данных
00-C0-00-00-XX ==>
<== INS=C0
<== Data (XX байт), SW1, SW2
Все полученные порции данных терминал должен склеить в той последовательности, как он их получал, и это будет ответом.
Команда case 3e APDU — команда передает данные карте. Пусть Lc = XXZZ, тогда мы должны передать APDU вида CLA-INS-P1-P2-00-XX-ZZ-Data общей длиной Lc+7 байт. Передать большой объем данных (более 256 байт) за один раз по протоколу Т=0 невозможно, поэтому их передают частями при помощи команды ENVELOPE (INS=C2). Это case 3 команда, которая играет роль конверта.
Бинарное представление APDU разбивают на части размером менее 256 байт, и каждую часть вкладывают как данные в свою команду ENVELOPE. Всю получившуюся последовательность команд ENVELOPE передают карте одну за другой, соблюдая порядок. Окончанием передачи по частям служит ENVELOPE с нулевым Lc. Карта из всех полученных команд ENVELOPE извлекает данные, склеивает их и получает исходную APDU. Если карта этот механизм не поддерживает, то на первую команду ENVELOPE должно вернуться SW=6D00.
Команда case 4e APDU реализуется аналогично стандартному случаю с помощью комбинации case 3e и case 2e.
Вместо выводов
И по сей день написанное актуально, и применяется в ряде стандартов и спецификаций. Это полезно помнить, чтобы хотя бы не пытаться передать микросхеме по протоколу T=0 полноценную case 4 APDU команду со всеми полями.
При написании апплетов под Java Card, например, надо помнить о тех протоколах, по которым карта должна будет работать. Если она должна поддерживать Т=0, то вот несколько простых советов:
Не придумывайте и не реализуйте инструкции INS со значением 6X или 9X. Если этот пул значений не задействован в ISO 7816-4, то это не значит, что он свободен.
Помните о направлении передачи данных для каждой инструкции. Вызывайте метод apdu.setIncomingAndReceive(); только тогда, когда данные действительно должны прийти. В противном случае будет сбой коммуникации.
Используйте только SW вида 9XXX или SW=6XXX. Как правило, стандартных значений более, чем достаточно.
Учитывать написанное надо и тогда, когда решили составить спецификацию на какое-то карточное приложение и возможен сценарий, что некоторые карты будут её реализовывать, работая по протоколу Т=0, а иначе надо сделать оговорку, что вся коммуникация должна проходить только по протоколу Т=1.
Особенно это нужно знать, когда встает необходимость воспользоваться анализатором протокола, чтобы понять в трудных случаях, а как обмен с картой проходит на самом деле.