Protocolo SmartCard I2C. Intercambie comandos APDU a través de la interfaz I2C

Introduccion


Hace algún tiempo, participé en el desarrollo de un dispositivo en el que era necesario implementar la criptografía rusa. Como en el futuro se suponía que certificaría esta decisión, se presentaron ciertos requisitos para la implementación de la criptografía. Y como una de las opciones para simplificar la implementación de estos requisitos, consideramos la posibilidad de integrar un lector de tarjetas inteligentes en el dispositivo o instalar un chip de tarjeta inteligente, en el que ya se han implementado muchos escenarios necesarios para trabajar con información clave.

Desafortunadamente, tal solución no encajó por alguna razón, aunque si hubiera sido posible utilizar la criptografía de hardware rusa ya preparada, esto debería haber acelerado significativamente el desarrollo y la posterior certificación del producto final. Y las razones de la imposibilidad de usar tokens USB o tarjetas inteligentes eran bastante comunes: el dispositivo debería haber sido bastante compacto (un pequeño módulo para dispositivos M2M o IoT), operado principalmente en modo libre de mantenimiento y operado en un amplio rango de temperatura.

En este artículo, quiero hablar sobre una posible solución para tal caso utilizando el chip A7001, que está conectado al sistema a través de la interfaz I2C.



Problemas de implementación de criptografía en el PAC


No quiero detenerme en los problemas de la certificación de criptografía. Quien trabaja con esto lo sabe de todos modos, pero el resto no parece necesitarlo. Pero aún vale la pena mencionar algunos puntos importantes.

En teoría, no debería haber ningún problema especial con la criptografía. Después de todo, es suficiente tomar una de las bibliotecas criptográficas, por ejemplo, OpenSSL, o cualquier otra de las muchas existentes.

Los problemas comienzan cuando se requiere que esta solución esté certificada. Y la implementación puramente de software de criptografía en el firmware convierte el dispositivo en un dispositivo de protección de información criptográfica completo, que requiere un estudio minucioso en un laboratorio de pruebas. Después de todo, cuando desarrolle una solución usando criptografía, tarde o temprano tendrá que pensar en cosas como un esquema clave, almacenar claves, generar números aleatorios y otros asuntos sutiles.

Existe un método elegante para implementar algoritmos criptográficos rusos certificados para algunas soluciones, que nos permite simplificar ligeramente el proceso de creación de dispositivos finales y reducir el tiempo para su desarrollo y posterior certificación. Es suficiente incrustar una tarjeta inteligente o un chip de tarjeta inteligente en el dispositivo, utilizándolo como una especie de "raíz de confianza", y así resolver un número significativo de problemas dolorosos que requieren una larga investigación y confirmación en los laboratorios de pruebas.



Microcontrolador de tarjeta inteligente con interfaz I2C


Para escribir este artículo, utilicé el chip A7001, que se conecta al dispositivo final a través del bus I2C, que está disponible en casi cualquier dispositivo. El chip fue proporcionado por Aladdin RD , que ya tiene un firmware instalado que admite la criptografía rusa.

El microcontrolador A7001AG (Microcontrolador de autenticación segura) es fabricado por NXP. De acuerdo con la hoja de datos del chip, el A7001AG es un microcontrolador protegido contra el acceso no autorizado basado en la arquitectura clásica 80C51 con un coprocesador criptográfico.

En modo de ahorro de energía, el microcontrolador consume 50 μA. Admite voltaje de alimentación en el rango de 1.62V a 5.5V y puede funcionar a temperaturas de −25 ° C a + 85 ° C.

Para interactuar con dispositivos externos, la interfaz esclava I2C se utiliza con una velocidad de hasta 100 kbit / s.

El microcontrolador está disponible en varias opciones de alojamiento. Terminé en el formato HVQFN32. Esta es una caja de plástico que mide 5x5x0.85 mm con 32 contactos y un paso de 0.5 mm.

Apariencia del caso:



Su pinout:



Sistema host para conectar el chip A7001


La placa ESP32 WiFi Kit 32 de Heltec se tomó como el diseño del sistema host con la interfaz I2C. Cuesta menos de 1000 rublos, tiene todas las interfaces cableadas e inalámbricas necesarias, hay un conector para conectar una batería de litio con un circuito de carga, así como una pantalla OLED de 0.96 pulgadas.



Un sistema casi perfecto para la creación de prototipos de varios dispositivos IoT y M2M, con los que siempre he querido jugar.

La placa se puede programar tanto en el entorno de desarrollo nativo como en el IDE de Arduino. Hay muchos ejemplos para trabajar con él. Por simplicidad, me decidí por el IDE Arduino estándar.

Diagrama de circuito


El diagrama del circuito para conectar el chip A7001 se muestra en la figura. Es ligeramente diferente de la hoja de datos recomendada. Según la descripción del fabricante, el terminal 22 (señal de reinicio RST_N) debería tener un alto potencial, pero el circuito no se inició de acuerdo con este esquema. Como resultado del "golpe científico", la operabilidad se logró conectando una resistencia pull-up R4 al conductor de potencia negativo.

ACTUALIZACIÓN: Como se sugiere en los comentarios, el esquema corresponde a la hoja de datos, mientras que la descripción de salida me confundió
RST_N - Restablecer entrada, activo BAJO




El circuito está montado en una pequeña placa de pruebas. Las señales de alimentación e I2C están conectadas por cuatro cables de conexión, y el módulo ESP32 está conectado a la computadora a través de USB para recibir alimentación en todo el circuito y llenar el firmware.



Protocolo de tarjeta inteligente I2C


Cuando escuché por primera vez sobre la conexión de microcontroladores de tarjetas inteligentes a través del bus I2C, me explicaron que la capa física de la interfaz de la tarjeta inteligente (GOST R ISO / IEC 7816-3-2013) fue reemplazada por I2C (SMBus), y todo lo demás funcionó como de costumbre. Tarjetas inteligentes según GOST R ISO / IEC 7816-4-2013 utilizando comandos APDU.

Resultó que esto no es del todo cierto, o más bien no lo es en absoluto. La interacción con el microcontrolador a un alto nivel ocurre usando comandos APDU convencionales, pero también hubo algunos "peros".

  1. La interfaz I2C (SMBus) ru.wikipedia.org/wiki/I%C2%B2C es un bus con direccionamiento esclavo, que es fundamentalmente diferente de la interfaz serial UART, que está diseñada para comunicar dos dispositivos punto a punto y no utiliza direccionamiento . Esto significa que todos los datos transmitidos (comandos APDU) deben "empaquetarse" en el formato de datos del bus I2C.
  2. El trabajo con una tarjeta inteligente comienza con su reinicio, generalmente apagando la alimentación, por ejemplo, quitando físicamente la tarjeta del lector de tarjetas. Después del restablecimiento, la tarjeta inteligente primero envía el bloque de datos ATR (Respuesta para restablecer), que contiene la información de configuración necesaria para configurar la interacción con la tarjeta inteligente.
    Y el chip en el bus I2C no es una excepción, pero en el caso de que el microcontrolador deba soldarse a la placa de circuito impreso, es posible que no tenga un circuito de suministro de energía del microcircuito o control de software de la salida de reinicio. Por lo tanto, se implementa el reinicio del chip, incluso a nivel de los comandos del protocolo I2C.

Estos y otros problemas se resuelven en el marco del Protocolo Smart Card I2C, cuya descripción se puede encontrar en el sitio web de NXP www.nxp.com/docs/en/supporting-information/AN12207.pdf .

Parte de software


Una búsqueda en la biblioteca con la implementación del protocolo Smart Card I2C Protocol no arrojó ningún resultado. Por lo tanto, tuve que entender las especificaciones y hacer la implementación de las funciones básicas de lo que estaba a la mano.

Fuentes de croquis para 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); } 


Para trabajar con el puerto I2C, utilicé la biblioteca estándar de Wire. Debo decir de inmediato que esta biblioteca no es adecuada para la implementación completa del Protocolo Smart Card I2C, porque no permite controlar ACK y NACK al transmitir y leer bytes individuales, lo cual es necesario para implementar la recepción correcta de datos de longitud variable desde una tarjeta inteligente.

Sí, y los ejemplos habituales del código Wire no funcionaron la primera vez, pero después de bailar con un teclado de pandereta , varios litros de café, buscar en Google en Yandex y Yandex en Google, se encontró una solución.

 Wire.write ( ); Wire.endTransmission (false); Wire.requestFrom (ADDR_A7001, 2, true); 

A juzgar por la documentación de la biblioteca, este diseño no libera el bus I2C después de llamar a endTransmission . Pero resultó que para el módulo basado en ESP32 que utilicé, la transferencia de datos no ocurre físicamente durante la llamada a endTransmission (falso) , como está escrito en la documentación de la biblioteca Wire, sino durante la llamada a requestFrom (true) , mientras que los datos solo se ponen en cola antes de esto para transferir.

Dadas tales limitaciones, tuve que hacer algunas "muletas", pero realmente quería lanzar el chip A7001 sin reescribir las bibliotecas estándar. Debido a esto, no se implementó el manejo de errores de protocolo, y tampoco fue posible recibir datos de longitud variable (es decir, siempre debe especificar el número exacto de bytes para leer).

Dichas restricciones no están permitidas en un sistema real, pero no son esenciales para demostrar el uso de comandos APDU cuando se trabaja en el bus I2C. Por lo tanto, si se produce un error en el protocolo de intercambio al intercambiar datos a través del puerto I2C, entonces el interruptor de encendido / apagado es nuestro.
En otras palabras, si durante la repetición de estos experimentos todo funcionó y de repente se detuvo antes de buscar un error en el código, apague y vuelva a encender. Con un alto grado de probabilidad, esto puede solucionar el problema.

Ejemplos de código para trabajar con el chip A7001


En los ejemplos, uso varias funciones de ayuda:

vect_dump : envía datos de volcado en formato HEX al puerto de depuración;
sci2c_exchange : envía una matriz de datos a través de I2C y lee el número especificado de bytes de respuesta;
sci2c_status : lee el estado de respuesta del microcircuito y, si es necesario, muestra su estado en el puerto de depuración;
sci2c_apdu_send - envía un comando APDU;
sci2c_apdu_recv - lee la respuesta al comando APDU.

Inicialización de microchip


De acuerdo con la descripción del Protocolo I2C de la tarjeta inteligente , antes de comenzar a trabajar con el chip, se deben ejecutar tres comandos secuencialmente: reiniciar (restablecimiento en frío o suave), leer ATR (leer respuesta para restablecer) y configurar parámetros de intercambio (parámetro de intercambio del dispositivo maestro). Y solo después de eso, el chip está listo para aceptar comandos APDU.

Restablecimiento parcial


Aquí todo es simple, enviamos un comando de reinicio y esperamos el tiempo establecido:

 sci2c_exchange ({0b00011111}, 2); delay(5); //      (tRSTG, time, ReSeT Guard) 

Leer respuesta para restablecer


Leer ATR es un poco más complicado, ya que no solo debe enviar un comando, sino también leer los datos de respuesta. De acuerdo con la descripción del protocolo, el tamaño máximo de los CDBATS de datos devueltos, MAX (Bytes de datos de comando, Respuesta para restablecer, MAXimum) puede ser de 29 bytes.

 vect ATR = sci2c_exchange({0b101111}, 29+2); // 29  + 1  PCB + 1  —   vect_dump("ATR: ", ATR); 

Leer datos 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

Donde 1E es el tamaño de los datos devueltos (29 bytes + 1 byte de la PCB) y 00 es la PCB (Byte de control de protocolo), que debería ser igual a 0 y, aparentemente, en este ejemplo, los datos no se leyeron correctamente (debe haber un byte de la PCB, y hay tres de ellos).

Los siguientes son datos codificados en formato TLV:

B8h - Objeto de datos de bajo nivel , tamaño 3 bytes ( 11h 01h 05h );
B9h - Objeto de datos de enlace de protocolo , 2 bytes de tamaño ( 01h 01h );
BAh : objeto de datos de capa superior , 1 byte ( 01h ) de tamaño;
BBh : objeto de datos del sistema operativo , 13 bytes ( 41 37 30 30 31 43 47 20 32 34 32 52 31 ).

Descifrado de la configuración de lectura del chip
Objeto de datos de bajo nivel : 11h - versiones principales y secundarias del protocolo compatible.

Códigos de detección de errores : 01h : soporte para la detección de errores y el control de integridad de los datos transmitidos utilizando LRC (Código de redundancia longitudinal).

Entero en espera de trama (FWI) : 05h - retraso máximo entre dos comandos. El rango de valores puede ser de 10 ms a 5120ms, por defecto 5120ms. El valor se calcula mediante la fórmula T = 10ms x 2 ^ FWI. Lo que en este caso nos da un retraso de 320 ms (10ms x 2 ^ 5).

Objeto de datos de enlace de protocolo : consta de dos valores, 01h 01h , que codifican el protocolo compatible y el protocolo predeterminado. Estos valores significan soporte para el protocolo APDU [GOST R ISO / IEC 7816-3-2013], y, como puede suponer, el mismo protocolo está instalado de forma predeterminada.

Objeto de datos de capa superior : el número 01h significa soporte para el formato APDU corto y extendido.

El objeto de datos del sistema operativo es un identificador de hasta 15 bytes de tamaño, como se define en el estándar [GOST R ISO / IEC 7816-4-2013]. En nuestro caso, esta es la cadena " A7001CG 242R1 ".

Parámetro de intercambio de dispositivos maestros


El último comando para inicializar la configuración de intercambio:

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

Valor de retorno: CCh - (11001100b) de acuerdo con la hoja de datos, 4 y 5 bits deben ser negación bit a bit de los bits 2 y 3 (NNb codifica los CDBIMS negados a nivel de bit, MAX) y, de acuerdo con el valor codificado, el chip admite el tamaño de comando máximo posible de 252 bytes CDBIMS , MAX (entero de bytes de datos de comando, maestro a esclavo, valor máximo).

De acuerdo con la descripción del protocolo, después de ejecutar estos tres comandos y en ese orden, el microcircuito está listo para ejecutar los comandos APDU habituales (aunque parece funcionar sin establecer parámetros de intercambio, es decir, fue suficiente para realizar un reinicio suave y leer ATR).

Ejecutando comandos APDU


Cada ciclo de ejecución de comandos APDU consta de los siguientes pasos:

  1. Enviar APDU (comando de transmisión de datos maestro a esclavo).
  2. Espere el tiempo de protección para recibir y procesar el comando.
  3. Espere a que el procesamiento del comando lea el estado (comando de estado).
  4. Leer datos de respuesta (comando de transmisión de datos esclavo a maestro).

Esta lógica se implementa en las funciones sci2c_apdu_send y sci2c_apdu_recv , y aquí hay un punto importante: en el formato del protocolo del Protocolo Smart Card I2C, hay contadores de los comandos APDU transmitidos. Estos contadores deben controlar los dispositivos maestro y esclavo y están diseñados para controlar la secuencia de datos transmitidos, de modo que en caso de un error de recepción sea posible transmitir o solicitar datos APDU nuevamente.

Se pueden encontrar ejemplos de la implementación de estas funciones en el código, y a continuación se encuentran solo los comandos APDU y los datos de respuesta.

Ejemplo de la hoja de datos:


C-APDU => 00 A4 04 04 04 54 65 73 74 00 - lea el archivo con el nombre "Prueba".
R-APDU <= 6A 86 : según la hoja de datos, la respuesta debe ser 64 82 ( archivo o aplicación no encontrada ), pero en nuestro caso el firmware se carga en el microcircuito, y la respuesta difiere del ejemplo descrito en la documentación.

Ciclo de vida de producción de tarjetas de lectura


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

Leer Leer información de la tarjeta



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

Leer Leer información clave


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

En conclusión


Esta experiencia de implementar el intercambio de equipos APDU a través de la interfaz I2C fue muy interesante. Incluso me encontré varias veces pensando que disfruto resolviendo varios problemas del campo de los circuitos, y también de la soldadura ordinaria, desde la última vez que tuve que recoger un soldador hace más de 5 años.

Espero que este artículo sea útil y ayude a comprender a los interesados ​​en este tema. Escribe si el material te interesa. Intentaré responder a todas las preguntas de este artículo, y si el tema del uso del Protocolo Smart Card I2C es interesante, intentaré divulgarlo con más detalle en las siguientes publicaciones.

Referencias


Source: https://habr.com/ru/post/439574/


All Articles