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

в 12:55, , рубрики: aladdin, arduino, I2C, IoT, M2M, sci2c, Smart Card, информационная безопасность, криптография, Программирование, Производство и разработка электроники, Разработка для интернета вещей, разработка железа, сертификация, смарт-карта, смарт-карты

Введение

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

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

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

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

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

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

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

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

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

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

Смарткарточный микроконтроллер с 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 мм.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 <Wire.h>

#include <vector>

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


using namespace std;
typedef std::vector<uint8_t> 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<result.size()-2 && Wire.available(); i++)
    {
      result[i+2] = Wire.read();
    }
  }
  return result;
}


//--------------------------------------------------------------------------
// Read Status Code
uint8_t sci2c_status(const char * msg = nullptr)
{
  vect v = sci2c_exchange({0b0111}, 2);
  uint8_t status = v[1] >> 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<result.size() && Wire.available(); i++)
  {
    result[i] = Wire.read();
  }

  vect_dump("R-APDU <= ", result);

  return result;
}


//--------------------------------------------------------------------------
void setup(){
    Wire.begin();

    Serial.begin(9600);
    while (!Serial);
    Serial.println("");
    Serial.println("Smart Card I2C Protocol Arduino demo on A7001");
    Serial.println("");


    sci2c_exchange({0b00001111}, 2); //The bits b0 to b5 set to 001111b indicate the Wakeup command.
    sci2c_status("Status Wakeup: ");
    sci2c_exchange({0b00001111}, 2); //The bits b0 to b5 set to 001111b indicate the Wakeup command.
    sci2c_status("Status Wakeup: ");

    // Soft Reset
    sci2c_exchange({0b00011111}, 2); //The bits b0 to b5 set to 011111b indicate the Soft Reset command.
    delay(5); // Wait at least tRSTG  (time, ReSeT Guard)
    sci2c_status("Status SoftReset: ");

                  
    // Read ATR
    vect ATR = sci2c_exchange({0b101111}, 29+2); //The bits b0 to b5 set to 101111b indicate the Read Answer to Reset command.
    sci2c_status("Status ATR: ");
    vect_dump("ATR: ", ATR, 2);
    
    // Parameter Exchange
    // The bits b0 to b5 set to 111111b of the PCB send by the master device indicate the Parameter Exchange command.
    // The bits b6 and b7 of the PCB send by the master device code the CDBIsm,max(Command Data Bytes Integer, Slave to Master, MAXimum)
    vect CDB = sci2c_exchange({0b11111111}, 2); 
    sci2c_status("Status CDB: ");
    vect_dump("CDB: ", CDB, 1);


    // Further examples of the exchange of APDU

    // Exchanges APDU from exmaple chapter
    sci2c_apdu_send({0x00, 0xA4, 0x04, 0x04, 0x04, 0x54, 0x65, 0x73, 0x74, 0x00});
    sci2c_status("Status Test send: ");
    sci2c_apdu_recv(3+1); // R-APDU size + 1 byte PBC
    sci2c_status("Status Test recv: ");
    

    // Read Card Production Life Cycle
    sci2c_apdu_send({0x80, 0xCA, 0x9F, 0x7F, 0x00});
    sci2c_status("Status card LC send: ");
    sci2c_apdu_recv(0x30+1); // R-APDU size + 1 byte PBC
    sci2c_status("Status card LC recv: ");

    // Read Card Info
    sci2c_apdu_send({0x80, 0xCA, 0x00, 0x66, 0x00});
    sci2c_status("Status card info send: ");
    sci2c_apdu_recv(0x51+1); // R-APDU size + 1 byte PBC
    sci2c_status("Status card info recv: ");


    // Read Key Info
    sci2c_apdu_send({0x80, 0xCA, 0x00, 0xE0, 0x00});
    sci2c_status("Status key send: ");
    sci2c_apdu_recv(0x17+1); // R-APDU size + 1 byte PBC
    sci2c_status("Status key recv: ");


    // Again exchanges APDU from exmaple chapter
    sci2c_apdu_send({0x00, 0xA4, 0x04, 0x04, 0x04, 0x54, 0x65, 0x73, 0x74, 0x00});
    sci2c_status("Status Test send: ");
    sci2c_apdu_recv(3+1); // R-APDU size + 1 byte PBC
    sci2c_status("Status Test recv: ");
    
    
    Serial.println("Done!n");

} 

//--------------------------------------------------------------------------
void loop() 
{
  delay(100);
}

Для работы с портом 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 формате:

B8hLow level data object, размер 3 байта (11h 01h 05h);
B9hProtocol binding data object, размером 2 байта (01h 01h);
BAhHigher layer data object, размером 1 байт (01h);
BBhOperating 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 будет интересна, то попробую её раскрыть более подробно в следующих публикациях.

Ссылки:

Автор: rsashka

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js