SmartCard I2C Protocol. Обмен APDU командами через I2C интерфейс

Введение


Некоторое время назад я принимал участие в разработке устройства, в котором было необходимо реализовать российскую криптографию. Так как в дальнейшем предполагалось это решение сертифицировать, то к реализации криптографии выдвигались определенные требования. И как один из вариантов упрощения реализации этих требований, рассматривалась возможность встроить в устройство считыватель смарткарт или установить смарткарточный чип, в котором уже были реализованы многие необходимые сценарии работы с ключевой информацией.

К сожалению, тогда подобное решение не подошло по некоторым причинам, хотя если бы удалось использовать уже готовую российскую аппаратную криптографию, то это должно было значительно ускорить разработку и последующую сертификацию конечного изделия. А причины невозможности использования USB токенов или смарткарты были весьма банальны: устройство должно было быть довольно компактным (небольшой модуль для M2M или IoT устройств), эксплуатироваться преимущественно в необслуживаемом режиме и работать в широком температурном диапазоне.

В этой статье я хочу рассказать о возможном решении подобного кейса с использованием чипа A7001, который подключается к системе через I2C интерфейс.

e5bwlh2fhtuxpwj7cdmmt7ujtjc.png

Проблемы реализации криптографии в ПАК


Мне не хочется подробно останавливаться на проблемах сертификации криптографии. Кто с этим работает, тот и так в курсе, а остальным это вроде как и не нужно. Но про несколько важных моментов сказать все-таки стоит.

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

Проблемы начинаются, когда это решение требуется сертифицировать. И чисто программная реализация криптографии в прошивке превращает устройство в полноценный СКЗИ, требующий пристального изучения в испытательной лаборатории. Ведь разрабатывая решение с использованием криптографии, рано или поздно придется задумываться над такими вещами как ключевая схема, хранение ключей, генерация случайных чисел и прочие тонкие материи.

Существует элегантный метод реализации сертифицированных российских криптографических алгоритмов для некоторых решений, который позволяет немного упростить процесс создания конечных устройств и сократить сроки их разработки и последующей сертификации. Достаточно встроить в устройство смарт-карту или смарт-карточный чип, используя его как некий «корень доверия», и таким образом, решить существенное количество болезненных вопросов, требующих длительных исследований и подтверждения в испытательных лабораториях.

e2iq6i49c7ber2rkbgu6sv8fl3u.png

Смарткарточный микроконтроллер с I2C интерфейсом


Для написания данной статьи я использовал чип A7001, который подключается к конечному устройству по шине I2C, имеющейся практически в любом устройстве. Чип был предоставлен компанией Аладдин РД, в который уже установлена прошивка с поддержкой российской криптографии.

Микроконтроллер A7001AG (Secure authentication microcontroller) выпускается компанией NXP. Согласно datasheet`у на микросхему, A7001AG — это защищенный от несанкционированного доступа микроконтроллер на базе классической архитектуры 80C51 с криптографическим сопроцессором.

В режиме энергосбережения микроконтроллер потребляет 50 μA. Поддерживает напряжение питания в диапазоне от 1.62В до 5.5В и может эксплуатироваться при температурах от −25°C до +85°C.

Для взаимодействия с внешними устройствами используется интерфейс I2C slave со скоростью работы до 100 kbit/s.

Микроконтроллер выпускается в нескольких вариантах корпусов. У меня оказался в формате HVQFN32. Это пластиковый корпус размерами 5×5х0,85 мм с 32 контактами и шагом вывод 0,5 мм.

Внешний вид корпуса:

tn7rmzk8pbzw1ezupbdv7fxvqhm.png

Его распиновка:

trbo5rlj2_q0v-lxbs7wbqevlu0.png

Хост система для подключение чипа A7001


В качестве макета хост системы с интерфейсом I2C была взята плата ESP32 WiFi Kit 32 компании Heltec. Она стоит менее 1000 рублей, имеет все необходимые проводные и беспроводные интерфейсы, есть разъем для подключения литиевого аккумулятора со схемой зарядки, а так же OLED дисплей 0,96 дюйма.

ufkqlihfpi9w0692rq8junv0ybo.jpeg

Практически идеальная система для прототипирования различных IoT- и M2M-устройств, с которой я давно хотел поиграться.

Плату можно программировать как в родной среде разработки, так и в IDE Arduino. Примеров для работы с ней можно найти множество. Для простоты я остановился на стандартной для Arduino IDE.

Принципиальная схема


Принципиальная схема подключения чипа A7001 приведена на рисунке. Она немного отличается от рекомендованной в datasheet’е. По описанию производителя на выводе 22 (сигнал сброса RST_N) должен присутствовать высокий потенциал, но по такой схеме микросхема не завелась. В результате «научного тыка» работоспособность была достигнута подключением резистора подтяжки R4 к отрицательному проводнику питания.

udl3nd7nz2ck8gtwkb3dhdudzqy.png

Схема собрана на небольшой макетной плате. Питание и сигналы I2C подключены четырьмя соединительными проводами, а сам модуль ESP32 подключается к компьютеру через USB для получения питания всей схемы и заливки прошивки.

dexzw1kvlnoixvk0ko-9svuvkbg.png

Smart Card I2C Protocol


Когда я первый раз услышал про подключение смарт-карточных микроконтроллеров по шине I2C, мне объяснили, что физический уровень смарт-карточного интерфейса (ГОСТ Р ИСО/МЭК 7816–3–2013) заменен на I2C (SMBus), а все остальное работает как обычные смарт-карты по стандарту ГОСТ Р ИСО/МЭК 7816–4–2013 с использованием APDU команд.

Оказалось, все не совсем так, а точнее совсем не так. Взаимодействие с микроконтроллером на высоком уровне действительно идет с использованием обычных APDU команд, но не обошлось без некоторых «но».

  1. Интерфейс I2C (SMBus) ru.wikipedia.org/wiki/I%C2%B2C является шиной с адресацией ведомых устройств, что принципиально отличается от последовательного интерфейса UART, который предназначен для связи двух устройств по принципу «точка-точка» и не использующей адресации. А это значит, что все передаваемые данные (команды APDU) должны быть «упакованы» в формат данных шины I2C.
  2. Работа со смарт-картой начинается с её сброса, обычно с помощью выключения питания, например, физически извлекая карту из картридера. После сброса, смарт-карта первым делом отправляет блок данных ATR (Answer To Reset), в котором содержится конфигурационная информация, необходимая для настройки взаимодействия со смарт-картой.
    И чип на шине I2C не является исключением, но в случае, когда микроконтроллер должен быть припаян на печатную плату, у него может не быть схемы управления питанием микросхемы или программного управления выводом перезагрузки. Поэтому сброс микросхемы реализован, в том числе, и на уровне команд протокола I2C.


Эти и другие моменты решаются в рамках протокола Smart Card I2C Protocol, описание которого можно найти на сайте NXP www.nxp.com/docs/en/supporting-information/AN12207.pdf.

Программная часть


Поиск библиотеки с реализацией протокола Smart Card I2C Protocol результатов не принесли. Поэтому пришлось разбираться в спецификации и делать реализацию основных функций из того, что было под рукой.

Исходники скетча для Arduino IDE
#include 

#include 

// I2C address on chip A7001
#define ADDR_A7001 static_cast(0x48)


using namespace std;
typedef std::vector vect;


//--------------------------------------------------------------------------
// Output dump data by serial port
void vect_dump(const char * prefix, const vect & v, const size_t start = 0, const size_t count = 0)
{
  if(prefix)
  {
    Serial.print(prefix);
  }
  if(v.size() < start)
  {
    Serial.println("Empty");
    return;
  }
  for(size_t i=0; i < (v.size()-start) && (count == 0 || i < count); i++)
  {
    uint8_t b = v[start + i];

    // Format output HEX data
    if(i) Serial.print(" ");
    if(b < 0x0F) Serial.print("0");
 
    Serial.print(b, HEX);
  }
  Serial.println("");
}

//--------------------------------------------------------------------------
// Send array bytes by I2C to address A7001 and read response result_size bytes 
vect sci2c_exchange(const vect data, const uint8_t result_size)
{
  Wire.beginTransmission(ADDR_A7001);
  Wire.write(data.data(), data.size()); 
  Wire.endTransmission(false);
  Wire.requestFrom(ADDR_A7001, result_size, true);
  //delay(1);
  
  vect result(result_size, 0);
  if(result_size >= 2)
  {
    result[0] = Wire.read(); // Data size CDB
    result[1] = Wire.read(); // PCB 

    for(size_t i=2; i> 4;
  if(msg)
  {
    Serial.print(msg); // Prefix
  
    switch(status)
    {
      case 0b0000: Serial.println("OK (Ready)"); break;
      case 0b0001: Serial.println("OK (Busy)"); break;

      case 0b1000: Serial.println("ERROR (Exception raised)"); break;
      case 0b1001: Serial.println("ERROR (Over clocking)"); break;
      case 0b1010: Serial.println("ERROR (Unexpected Sequence)"); break;
      case 0b1011: Serial.println("ERROR (Invalid Data Length)"); break;
      case 0b1100: Serial.println("ERROR (Unexpected Command)"); break;
      case 0b1101: Serial.println("ERROR (Invalid EDC)"); break;
      default: 
        Serial.print("ERROR (Other Exception ");
        Serial.print(status, BIN);
        Serial.println("b)");
        break;
    }
  }
  return status;
}

static uint8_t apdu_master_sequence_counter = 0; // Sequence Counter Master, Master to Slave

//--------------------------------------------------------------------------
// Send APDU
void sci2c_apdu_send(const vect apdu)
{
  vect_dump("C-APDU => ", apdu);
  vect data(2, 0); // 0x00 - Master to Slave Data Transmission command + reserve to length
  data.insert(data.end(), std::begin(apdu), std::end(apdu));

  data[0] |= (apdu_master_sequence_counter << 4);
  if(++apdu_master_sequence_counter > 0b111)
  {
    apdu_master_sequence_counter = 0;
  }

  data[1] = data.size() - 2;
  sci2c_exchange(data, 2);
  delay(10);
  sci2c_status("");
}


//--------------------------------------------------------------------------
// Receive APDU
vect sci2c_apdu_recv(uint8_t result_size)
{
  Wire.beginTransmission(ADDR_A7001);
  Wire.write(0b0010); // 0010b - Slave to Master Data Transmission command
  Wire.endTransmission(false);
  Wire.requestFrom(ADDR_A7001, result_size, true);
  
  vect result(result_size, 0);
  for(size_t i=0; i


Для работы с портом I2C я использовал стандартную библиотеку Wire. Сразу скажу, что данная библиотека не подходит для полноценной реализации протокола Smart Card I2C Protocol, т.к. не позволяет управлять ACK и NACK при передаче и чтении отдельных байтов, что требуется для реализации корректного приема данных переменной длинны от смарт-карты.

Да и обычные примеры кода Wire заработали не с первого раза, но после некоторой пляски с бубном клавиатурой, нескольких литров кофе, гугления в яндексе и яндексения в гугле решение было найдено.

Wire.write (код команды);
Wire.endTransmission (false);
Wire.requestFrom (ADDR_A7001, 2, true);


Судя по документации на библиотеку, такая конструкция не отпускает шину I2C после вызова endTransmission. Но оказалось, что у используемого мной модуля на базе ESP32 передача данных физически происходит не во время вызова endTransmission (false), как написано в документации на библиотеку Wire, а во время вызова requestFrom (true), тогда как перед этим данные только ставятся в очередь на передачу.

При наличии подобных ограничений, пришлось делать некоторые «костыли», но мне очень хотелось запустить чип A7001 без переписывания стандартных библиотек. Из-за этого осталось не реализована обработка ошибок протокола, а также не удалось сделать прием данных переменной длины (т.е. всегда требуется указывать точное количество байт для чтения).

Подобные ограничения не допустимы в реальной системе, но не существенны для демонстрации использования APDU команд при работе по шине I2C. Поэтому, если при обмене данных через I2C порт возникает ошибка протокола обмена, тогда тумблер питания — наше все.
Другими словами, если во время повторения этих экспериментов все работало и вдруг перестало, прежде чем искать ошибку в коде, выключите и включите питание. С большой долей вероятности это может устранить возникшую проблему.

Примеры кода для работы с чипом A7001


В примерах я использую несколько вспомогательных функций:

vect_dump — вывести в отладочных порт дамп данных в HEX формате;
sci2c_exchange — передать по I2C массив данных и считать заданное количество ответных байт;
sci2c_status — считать статус ответа микросхемы и при необходимости вывести в отладочный порт её состояние;
sci2c_apdu_send — послать APDU команду;
sci2c_apdu_recv — прочитать ответ на APDU команду.

Инициализация микросхемы


Согласно описанию Smart Card I2C Protocol, перед началом работы с чипом следует выполнить последовательно три команды: Перезагрузка (Cold или Soft Reset), Чтение ATR (Read Answer to Reset) и Настройка параметров обмена (Master Device exchanges Parameter). И только после этого микросхема готова принимать APDU команды.

Soft Reset


Тут все просто, посылаем команду перезагрузки и ждем положенное время:

sci2c_exchange ({0b00011111}, 2);
delay(5); // Защитный интервал времени после перезагрузки (tRSTG, time, ReSeT Guard)


Read Answer to Reset


С чтением ATR немного сложнее, т.к. нужно не только послать команду, но и считывать ответные данные. Согласно описанию протокола, максимальный размер возвращаемых данных CDBATS, MAX (Command Data Bytes, Answer To Reset, MAXimum) может быть 29 байт.

vect ATR = sci2c_exchange({0b101111}, 29+2); //  29 байт + 1 байт PCB + 1 байт — размер данных
vect_dump("ATR: ", ATR);


Считанные данные ATR: 1E 00 00 00 B8 03 11 01 05 B9 02 01 01 BA 01 01 BB 0D 41 37 30 30 31 43 47 20 32 34 32 52 31

Где 1E — размер возвращаемых данных (29 байт + 1 байт PCB) и 00 — PCB (Protocol Control Byte), который должен быть равен 0 и, судя по всему, в данном примере данные считались не совсем корректно (должен быть один байт PCB, а тут их три).

Далее идут данные, закодированные в TLV формате:

B8h — Low level data object, размер 3 байта (11h 01h 05h);
B9h — Protocol binding data object, размером 2 байта (01h 01h);
BAh — Higher layer data object, размером 1 байт (01h);
BBh — Operating system data object, 13 байт (41 37 30 30 31 43 47 20 32 34 32 52 31).

Расшифровка считанной конфигурации микросхемы
Low level data object: 11h — старшая и младшая версии поддерживаемого протокола.

Error Detection Codes: 01h — поддержка обнаружения ошибок и контроля целостности передаваемых данных с помощью LRC (Longitudinal Redundancy Code).

Frame waiting integer (FWI): 05h — максимальная задержка между двумя командами. Диапазон значений может быть от от 10 мс до 5120 мс, по умолчанию 5120 мс. Значение вычисляется по формуле T = 10ms x 2^FWI. Что в данном случае дает нам задержку 320 мс (10 мс x 2^5).

Protocol binding data object — состоит из двух значений, 01h 01h, которые кодируют поддерживаемый протокол и протокол по умолчанию. Данные значения означают поддержку протокола APDU [ГОСТ Р ИСО/МЭК 7816–3–2013], и, как не трудно догадаться, этот же протокол установлен по умолчанию.

Higher layer data object — число 01h означает поддержку короткого и расширенного формата APDU.

Operating system data object — это идентификатор размером до 15 байт, как определено в стандарте [ГОСТ Р ИСО/МЭК 7816–4–2013]. В нашем случае это строка »A7001CG 242R1».

Master Device exchanges Parameter


Последней командой для инициализации настройка параметров обмена:

vect CDB = sci2c_exchange({0b11111111}, 2); 
sci2c_status("Status CDB: ");
vect_dump("CDB: ", CDB, 1);


Возвращенное значение: CCh — (11001100b) согласно datasheet«у, 4 и 5 биты должны быть побитовым отрицанием битов 2 и 3 (NNb codes the bitwise negated CDBIMS, MAX) и, согласно закодированному значению, микросхема поддерживает максимально возможный размер команд 252 байт CDBIMS, MAX (Command Data Bytes Integer, Master to Slave, MAXimum) value.

Согласно описанию протокола, после выполнения этих трех команд и именно в таком порядке, микросхема готова выполнять обычные APDU команды (хотя у меня вроде бы работало и без настройки параметров обмена, т.е. достаточно было сделать Soft Reset и прочитать ATR).

Выполнение APDU команд


Каждый цикл выполнения APDU команд состоит из следующих шагов:

  1. Отправить APDU (Master to Slave Data Transmission command).
  2. Подождать защитное время для приема и обработки команды.
  3. Ждать завершения обработки команды считывая статус (Status command).
  4. Считать ответные данные (Slave to Master Data Transmission command).


Данная логика реализована в функциях sci2c_apdu_send и sci2c_apdu_recv, причем тут есть важный момент: в формате протокола Smart Card I2C Protocol присутствуют счетчики передаваемых APDU команд. Эти счетчики должны контролировать как Master, так и Slave устройства и они предназначены для контроля последовательности передаваемых данных, что бы в случае ошибки приема можно было передать или запросить данные APDU команды повторно.

Примеры реализации этих функций можно посмотреть в коде, а ниже приведены только APDU команды и ответные данные.

Пример из datasheet’а:


C-APDU => 00 A4 04 04 04 54 65 73 74 00 — считать файл с именем «Test».
R-APDU 6A 86 — согласно datasheet’у ответ должен быть 64 82 (File or application not found), но в нашем случае в микросхему залита рабочая прошивка, и ответ отличается от примера, описанного в документации.

Чтение Card Production Life Cycle


C-APDU => 80 CA 9F 7F 00
R-APDU 9F 7F 2A 47 90 51 67 47 91 12 10 38 00 53 56 00 40 39 93 73 50 48 12 53 63 00 00 00 00 13 2C 19 30 34 30 33 39 00 00 00 00 00 00 00 00 90 00

Чтение Read Card Info

C-APDU => 80 CA 00 66 00
R-APDU <= 66 4C 73 4A 06 07 2A 86 48 86 FC 6B 01 60 0C 06 0A 2A 86 48 86 FC 6B 02 02 01 01 63 09 06 07 2A 86 48 86 FC 6B 03 64 0B 06 09 2A 86 48 86 FC 6B 04 02 55 65 0B 06 09 2B 85 10 86 48 64 02 01 03 66 0C 06 0A 2B 06 01 04 01 2A 02 6E 01 02 90 00

Чтение Read Key Info


C-APDU => 80 CA 00 E0 00
R-APDU E0 12 C0 04 01 FF 80 10 C0 04 02 FF 80 10 C0 04 03 FF 80 10 90 00

В заключении


Данный опыт реализации обмена APDU командами через I2C интерфейс оказался очень интересным. Я даже несколько раз ловил себя на мысли, что получаю удовольствие от решения различных вопросов из области схемотехники, да и от обычной пайки тоже, так как последний раз приходилось брать в руки паяльник более 5 лет назад.

Надеюсь, эта статья принесет пользу и поможет разобраться интересующимся данной темой. Пишите, если материал вас заинтересовал. Постараюсь ответить на все вопросы по данной статье, а если тема использования Smart Card I2C Protocol будет интересна, то попробую её раскрыть более подробно в следующих публикациях.

Ссылки:


© Habrahabr.ru