[Из песочницы] STM32. Подключаем смарт-карты стандарта ISO7816
О смарт-картах сказано уже немало, но процесс взаимодействия с картами на физическом уровне до недавнего времени оставался для меня загадкой. В своей статье я хотел бы осветить вопрос работы со смарт-картами по интерфейсу, описанному в части 3 стандарта ISO7816. Признаюсь честно, что потратил немало времени добывая информацию, а все оказалось предельно просто. Если интересно, давай под кат. Сразу оговорюсь, что речь идет о процессоре с аппаратной поддержкой ISO7816 (например, STM32F4xx), написание программного эмулятора — это то еще маньячество, которое имеет место либо если очень сильно «прижало», либо если слишком много свободного времени.
ВЫВОДЫ И СХЕМА ВКЛЮЧЕНИЯ Итак, что мы имеем на входе? Камень с 3-вольтовым питанием и карточку формата ISO7816–2, вот такую: VCC — питание RST — вход сброса I/O — двунаправленная линия данных CLK — тактирование GND — земля VPP — вывод программирования Для входа VCC существует 3 варианта: 1.8 В, 3 В, 5 В (классы карт A, B, C, соответственно), RST служит для сброса машины состояний карты (активный уровень — низкий), I/O — это линия передачи данных, которая представляет собой обычный UART, CLK используется для тактирования процессора карты (если карта в неактивном состоянии, частоту подавать, соответственно, не нужно), вывод VPP используется для программирования карты.Так подключают карточки настоящие хацкеры:
ИНТЕРФЕЙС Интерфейс является синхронным режимом USART-драйвера, это значит, что передачу каждого бита информации мы синхронизируем частотой на выводе CLK, но здесь есть одно важное отличие от прочих синхронных интерфейсов (вроде того же SPI): для тактирования одного бита информации нужен не один импульс на CLK, а 372 импульса (это магическое число прописано в 3 части ISO7816, и носит название ETU (Elementary Time Unit)), т.е., один бит данных тактируется каждым 372-м (в идеальном случае) фронтом. Сама частота должна лежать в пределах от 1 до 5 МГц.Теперь разберемся с линией данных (I/O). Как я уже сказал, это обычный UART со следующими параметрами:
Бит данных: 8 Стоп-бит: 1,5 Бит паритета: Even (чет) Скорость (на старте): 9600 Бод В принципе, ничего более об аппаратных свойствах этого интерфейса, нам знать не нужно. Так что, переходим к настройке драйвера.НАСТРОЙКА ДРАЙВЕРА Здесь сразу кину кусок кода инициализации, написанный на Standard Peripheral Library: RCC_ClocksTypeDef RCC_Clocks; USART_InitTypeDef USART_InitStructure; USART_ClockInitTypeDef USART_ClockInitStructure; NVIC_InitTypeDef NVIC_InitStructure; /// Запросим частоту на шине RCC_GetClocksFreq (&RCC_Clocks); /// Включим тактирование драйвера SC_USART_APB_PERIPH_CLOCK (RCC_APB2Periph_USART1, ENABLE); /// Зададим предделитель USART_SetPrescaler (USART1, (RCC_Clocks.PCLK2_Frequency / CLK_FREQ) / 2); /// Зададим Guard Time USART_SetGuardTime (USART1, 16); /// Сконфигурируем синхронную часть (вывод CLK) USART_ClockInitStructure.USART_Clock = USART_Clock_Enable; USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low; USART_ClockInitStructure.USART_CPHA = USART_CPHA_1Edge; USART_ClockInitStructure.USART_LastBit = USART_LastBit_Enable; USART_ClockInit (USART1, &USART_ClockInitStructure); /// Сконфигурируем асинхронную часть (вывод I/O) USART_InitStructure.USART_BaudRate = CLK_FREQ / ETU; USART_InitStructure.USART_WordLength = USART_WordLength_9b; USART_InitStructure.USART_StopBits = USART_StopBits_1_5; USART_InitStructure.USART_Parity = USART_Parity_Even; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init (USART1, &USART_InitStructure); /// Разрешим передачу NACK USART_SmartCardNACKCmd (USART1, ENABLE); /// Включим режим работы со смарт-картами USART_SmartCardCmd (USART1, ENABLE); /// Подадим питание на драйвер USART_Cmd (USART1, ENABLE); /// Разрешим 2 прерывания (по приему и по ошибке паритета) USART_ITConfig (USART1, USART_IT_RXNE, ENABLE); USART_ITConfig (USART1, USART_IT_PE, ENABLE); /// Разрешим прерывания соответствующего канала NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init (&NVIC_InitStructure); Настройку выводов я опустил, чтобы не загромождать кодом, но здесь есть один важный момент, вывод I/O должен быть настроен как Open-Drain, поскольку стандартом предусмотрена возможность нахождения линии в Z-состоянии, когда карточка сама решает, куда ее подтягивать.
Здесь я хотел бы заострить внимание на двух моментах (предделитель и скорость обмена). Дело здесь в чем? С одной стороны нужно задать скорость 9600, а с другой — частоту, кратную системной. Пожалуй, в большинстве случаев, если не требуется сверхнизкое потребление, системная частота выбирается максимальной (в моем случае это 168 МГц), модуль USART, который я использую, тактируется от шины APB2, максимальная частота которой составляет 84 МГц, значит выбранная нами частота должна попадать в интервал от 1 до 5 МГц и быть кратной 84 МГц, но для скорости 9600 частота будет 9600×372 = 3,5712 МГц. Как же тут быть? Разработчики стандарта этот момент предусмотрели и заложили возможное отклонение от номинальных значений вплоть до 20%, т.о., мы можем спокойно округлить частоту, допустим, до 3,5 МГц и выбрать скорость 3500000 / 372 = 9409, расхождение здесь составит менее 2%, что вполне допустимо. Значение предделителя мы должны поделить на 2, поскольку оно задается с шагом 2 (т.е. значение 1 соответствует делению на 2, 2 — на 4, 3 — на 6, и т.д.). У нас получается (84 / 3,5) / 2 = 12: Частота (CLK): 3,5 МГц Скорость (I/O): 9409 Бод Presclaer: 12 Далее, на чем я хотел бы остановиться, — это обработка ошибок паритета. Для этого есть специально предусмотренный интервал времени, который зовется Guard Time (у нас он составляет 16 бит). Что такое Guard Time? Guard Time — это интервал времени, в течение которого приемник должен выставить низкий уровень на линии I/O в случае ошибки паритета (NACK), на что передатчик должен отправить тот же самый кадр еще раз. О полезности этой фичи я особо рассуждать не буду, хотя, сугубо мое мнение, если такие ошибки, в принципе, имеют место быть, то канал обмена можно считать ненадежным, и подобные меры, вряд ли, тут помогут.С настройкой драйвера, я думаю, все понятно, посему, перейдем к процессу инициализации обмена с карточкой.
СТАРТ Чтобы запустить карточку нужно выполнить «холодый» сброс. Он представляет собой следующую последовательность: Выставить на RST низкий уровень Подать питание на VCC Подать частоту на CLK Выждать интервал времени, равный 40000 циклам CLK Выставить на RST высокий уровень Ждать отклика в течение 40000 циклов
Все просто, выполняем сброс, ждем отклика. Если первый бит отклика не пришел в течение 40000 циклов (t3), необходимо выставить на RST низкий уровень и деактивировать I/O и CLK.
ATR Что представляет собой этот отклик? ATR (Answer-to-Reset) представляет собой следующую структуру (размер каждого поля составляет 1 байт): TS: Initial character TO: Format character TAi: Interface character [ codes FI, DI ] TBi: Interface character [ codes II, PI1 ] TCi: Interface character [ codes N ] TDi: Interface character [ codes Yi+1, T ] T1, …, TK: Historical characters (max,15) TCK: Check character 1. TS — инициирующий байт. Он может принимать одно из двух значений: 3Fh и 3Bh:3Fh — Inverse Convention — инверсная полярность, т.е. 0 передается высоким уровнем, а 1 — низким (важный момент, для контроля паритета здесь будет использоваться odd, т.е., нечет): 3Bh — Direct Convention — прямая полярность — то же самое, но с точностью до наоборот (паритет — even, т.е., чет) 2. T0 — байт формата. Состоит из 2-х октетов:
Y1 (старший октет) — битовая маска, которая показывает, какие поля следуют далее: b5 — TA1 передается b6 — TB1 передается b7 — TC1 передается b8 — TD1 передается K (младший октет) — число «исторических» байт 3. TA1. Содержит параметры для подстройки частоты: FI (старший октет) — делимое DI (младший октет) — делитель 4. TB1. Содержит характеристики вывода VPP: II (биты b7 — b6) — максимальный ток программирования PI (биты b5 — b1) — напряжение программирования 5. TC1. Содержит параметр N — дополнительное приращение Guard Time (задается в единицах ETU), может принимать значение от 0 до 254, значение 255 говорит о том, что интервал между первыми фронтами двух соседних кадров сокращен до 11 ETU.6. TD1. Здесь небольшая путаница, поскольку ISO7816 не раскрывает структуры этого байта, но в источнике [1] все довольно толково расписано. Состоит он из 2-х октетов:
Y2 (старший октет) — битовая маска, которая показывает, какие поля следуют далее: b5 — TA2 передается b6 — TB2 передается b7 — TC2 передается b8 — TD2 передается T (младший октет) — используемый протокол (0 — T0, 1 — T1, остальные значения зарезервированы) 7. TA2. Содержит только один значащий бит (старший), он указывает на возможность переключения на другую версию протокола (0 — переключение возможно, 1 — переключение невозможно), если байт не передается, он считается равным 08. T1, …, TK — исторические байты. Содержат информацию о карте, кем, когда она выпущена, и т.д., формат этого поля стандартом не регламентируется9. TCK — байт контрольной суммы. Вычисляется сложением по модулю 2 (xor) всех предшествующих байт (присутствует только в протоколе T1)
Теперь попробуем разобраться, что здесь для чего нужно. В наибольшей степени нас интересуют поля TA1 и TA2, они указывают нам на то, какие действия мы должны предпринять, а именно, выбрать один из двух режимов:
Режим «переговоров» (negotiation mode) Специфицированный режим (specific mode) Если старший бит TA2 = 0, то мы используем режим «переговоров», иначе — специфицированный режим.PTS Обмен в режиме «переговоров» представляет собой процесс, называемый PTS (Protocol Type Selection). Процесс этот состоит в отправке устройством сопряжения последовательности, которая говорит карте о том, что оно готово применить новые настройки. В свою очередь, карта должна ответить той же последовательностью, после чего и карта и устройство сопряжения могут начинать работать с новыми настройками. О том, какие настройки применить, нам говорит байт TA1 кадра ATR. Параметры Fi и Di это не сами значения, а номера в таблице. По таблице мы можем найти соответствующие этим номерам значения F (Clock rate conversion factor) и D (Bit rate adjustment factor): Таблица Fi-F.
FI 0000 0001 0010 0011 0100 0101 0110 0111 F internal clk 372 558 744 1116 1488 1860 RFU FI 1000 1001 1010 1011 1100 1101 1110 1111 F RFU 512 768 1024 1536 2048 RFU RFU Таблица Di-D.DI 0000 0001 0010 0011 0100 0101 0110 0111 D RFU 1 2 4 8 16 RFU RFU DI 1000 1001 1010 1011 1100 1101 1110 1111 D RFU RFU ½ ¼ 1/8 1/16 1/32 1/64 *RFU — зарезервировано для будущего использованияЧастное от деления F и D — это новое значение ETU, т.е. мы сможем выбрать любую частоту и скорость, но принимая во внимание, что соотношение между ними должно быть равно частному F / D.
Теперь подробнее о самом кадре PTS:
PTSS: Initial character (Mandatory) PTS0: Format character (Mandatory) PTS1 (Optional) PTS2 (Optional) PTS3 (Optional) PCK: Check character (Mandatory) 1. PTSS — инициирующий байт (всегда FFh)2. PTS0 — байт формата. Определяет, какие поля присутствуют в кадре, старший октет — битовая маска: b5 — PTS1 передается b6 — PTS2 передается b7 — PTS3 передается b8 — всегда 0, зарезервирован T (младший октет) — используемый протокол (0 — T0, 1 — T1, остальные значения зарезервированы) 3. PTS1. Содержит запрашиваемые значения Fi и Di, полученные в байте TA1 ATR, если байт не передается, то Fi и Di считаются равными 1.4. PTS2. Показывает, будет ли применяться параметр N, указанный в TC1 ATR5. PTS3. Зарезервирован.6. PCK — байт контрольной суммы. Вычисляется сложением по модулю 2 (xor) всех предшествующих байт.Все просто, формируем последовательность, отправляем, ждем ответа, сравниваем, если совпало, перестраиваем скорость на Fclk / (F / D).
Если карточка не поддерживает режим «переговоров», просто продолжаем работу.
ПРИМЕР Для закрепления материала, попробуем разобрать простенький пример. Это обычная Билайновская симка. Вот ATR, который она выкидывает: 3B 3B 94 00 9B 44 20 10 4D AD 40 00 33 90 00 3Bh (TS) — direct convention 3Bh (T0) (0011 1011) — ожидаем TA1 и TB1, число «исторических» байт = 11 94h (TA1) — Fi = 9, Di = 4, находим F и D по таблицам 1 и 2 (F = 512, D = 8), новый ETU = 512 / 8 = 64 00h (TB1) — VPP не поддерживается Кадр PTS, в этом случае, будет выглядеть следующим образом: FF 10 94 7B FFh (PTSS) — инициирующий байт 10h (PTS0) (0001 0000) — передаем PTS0, протокол T0 94h (PTS1) = TA1 7Bh (PCK) = xor (FF 10 94) ЗАКЛЮЧЕНИЕ В своей статье я опустил некоторые подробности, связанные, например, программированием смарт-карт, а также не стал рассматривать протоколы канального и прикладного уровней, но на это есть несколько причин. Во-первых, каждый из этих пунктов тянет на отдельную статью, если не больше, а во-вторых, информации по протоколу APDU в интернете, на мой взгляд, предостаточно.Ну что, я очень надеюсь, что мой труд не останется без внимания, или, по крайней мере, удовлетворит любопытство страждущих. Так или иначе, спасибо всем, кто осилил, буду рад ответить на вопросы, да и получить пару другую пинков за косяки. Напоследок, очень советую всем прочитать замечательный цикл статей о криптографических Java-картах. Всем добра! ССЫЛКИ