CCID App: Refactor (#3808)

- Move iso7816 related code to its own folder
- Refactor Iso7816Callback into Iso7816Handler
- Created new file for CCID commands
- Renamed variables according to standard

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Filipe Paz Rodrigues 2024-07-31 16:44:04 -05:00 committed by GitHub
parent f73d60cba8
commit 53cf700521
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 248 additions and 255 deletions

View file

@ -6,10 +6,13 @@
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/gui.h>
#include "iso7816_callbacks.h"
#include "iso7816_t0_apdu.h"
#include "iso7816_atr.h"
#include "iso7816_response.h"
#include "iso7816/iso7816_handler.h"
#include "iso7816/iso7816_t0_apdu.h"
#include "iso7816/iso7816_atr.h"
#include "iso7816/iso7816_response.h"
#include "ccid_test_app_commands.h"
typedef enum {
EventTypeInput,
@ -20,6 +23,7 @@ typedef struct {
ViewPort* view_port;
FuriMessageQueue* event_queue;
FuriHalUsbCcidConfig ccid_cfg;
Iso7816Handler* iso7816_handler;
} CcidTestApp;
typedef struct {
@ -63,6 +67,15 @@ uint32_t ccid_test_exit(void* context) {
CcidTestApp* ccid_test_app_alloc(void) {
CcidTestApp* app = malloc(sizeof(CcidTestApp));
//setup CCID USB
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
app->ccid_cfg.vid = 0x076B;
app->ccid_cfg.pid = 0x3A21;
app->iso7816_handler = iso7816_handler_alloc();
app->iso7816_handler->iso7816_answer_to_reset = iso7816_answer_to_reset;
app->iso7816_handler->iso7816_process_command = iso7816_process_command;
// Gui
app->gui = furi_record_open(RECORD_GUI);
@ -92,174 +105,26 @@ void ccid_test_app_free(CcidTestApp* app) {
furi_record_close(RECORD_GUI);
app->gui = NULL;
free(app->iso7816_handler);
// Free rest
free(app);
}
void ccid_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) {
UNUSED(context);
iso7816_icc_power_on_callback(atrBuffer, atrlen);
}
void ccid_xfr_datablock_callback(
const uint8_t* pcToReaderDataBlock,
uint32_t pcToReaderDataBlockLen,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen,
void* context) {
UNUSED(context);
iso7816_xfr_datablock_callback(
pcToReaderDataBlock, pcToReaderDataBlockLen, readerToPcDataBlock, readerToPcDataBlockLen);
}
static const CcidCallbacks ccid_cb = {
ccid_icc_power_on_callback,
ccid_xfr_datablock_callback,
};
//Instruction 1: returns an OK response unconditionally
//APDU example: 0x01:0x01:0x00:0x00
//response: SW1=0x90, SW2=0x00
void handle_instruction_01(ISO7816_Response_APDU* responseAPDU) {
responseAPDU->DataLen = 0;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
}
//Instruction 2: expect command with no body, replies wit with a body with two bytes
//APDU example: 0x01:0x02:0x00:0x00:0x02
//response: 'bc' (0x62, 0x63) SW1=0x90, SW2=0x00
void handle_instruction_02(
uint8_t p1,
uint8_t p2,
uint16_t lc,
uint16_t le,
ISO7816_Response_APDU* responseAPDU) {
if(p1 == 0 && p2 == 0 && lc == 0 && le >= 2) {
responseAPDU->Data[0] = 0x62;
responseAPDU->Data[1] = 0x63;
responseAPDU->DataLen = 2;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
//Instruction 3: sends a command with a body with two bytes, receives a response with no bytes
//APDU example: 0x01:0x03:0x00:0x00:0x02:CA:FE
//response SW1=0x90, SW2=0x00
void handle_instruction_03(
uint8_t p1,
uint8_t p2,
uint16_t lc,
ISO7816_Response_APDU* responseAPDU) {
if(p1 == 0 && p2 == 0 && lc == 2) {
responseAPDU->DataLen = 0;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
//instruction 4: sends a command with a body with 'n' bytes, receives a response with 'n' bytes
//APDU example: 0x01:0x04:0x00:0x00:0x04:0x01:0x02:0x03:0x04:0x04
//receives (0x01, 0x02, 0x03, 0x04) SW1=0x90, SW2=0x00
void handle_instruction_04(
uint8_t p1,
uint8_t p2,
uint16_t lc,
uint16_t le,
const uint8_t* commandApduDataBuffer,
ISO7816_Response_APDU* responseAPDU) {
if(p1 == 0 && p2 == 0 && lc > 0 && le > 0 && le >= lc) {
for(uint16_t i = 0; i < lc; i++) {
responseAPDU->Data[i] = commandApduDataBuffer[i];
}
responseAPDU->DataLen = lc;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
void iso7816_answer_to_reset(Iso7816Atr* atr) {
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
atr->TS = 0x3B;
atr->T0 = 0x00;
}
void iso7816_process_command(
const ISO7816_Command_APDU* commandAPDU,
ISO7816_Response_APDU* responseAPDU) {
//example 1: sends a command with no body, receives a response with no body
//sends APDU 0x01:0x01:0x00:0x00
//receives SW1=0x90, SW2=0x00
if(commandAPDU->CLA == 0x01) {
switch(commandAPDU->INS) {
case 0x01:
handle_instruction_01(responseAPDU);
break;
case 0x02:
handle_instruction_02(
commandAPDU->P1, commandAPDU->P2, commandAPDU->Lc, commandAPDU->Le, responseAPDU);
break;
case 0x03:
handle_instruction_03(commandAPDU->P1, commandAPDU->P2, commandAPDU->Lc, responseAPDU);
break;
case 0x04:
handle_instruction_04(
commandAPDU->P1,
commandAPDU->P2,
commandAPDU->Lc,
commandAPDU->Le,
commandAPDU->Data,
responseAPDU);
break;
default:
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED);
}
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
}
}
static const Iso7816Callbacks iso87816_cb = {
iso7816_answer_to_reset,
iso7816_process_command,
};
int32_t ccid_test_app(void* p) {
UNUSED(p);
//setup view
CcidTestApp* app = ccid_test_app_alloc();
//setup CCID USB
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
app->ccid_cfg.vid = 0x076B;
app->ccid_cfg.pid = 0x3A21;
FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
furi_hal_usb_unlock();
furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true);
furi_hal_usb_ccid_set_callbacks((CcidCallbacks*)&ccid_cb, NULL);
furi_hal_usb_ccid_set_callbacks(
(CcidCallbacks*)&app->iso7816_handler->ccid_callbacks, app->iso7816_handler);
furi_hal_usb_ccid_insert_smartcard();
iso7816_set_callbacks((Iso7816Callbacks*)&iso87816_cb);
//handle button events
CcidTestAppEvent event;
while(1) {
@ -280,8 +145,6 @@ int32_t ccid_test_app(void* p) {
furi_hal_usb_ccid_set_callbacks(NULL, NULL);
furi_hal_usb_set_config(usb_mode_prev, NULL);
iso7816_set_callbacks(NULL);
//teardown view
ccid_test_app_free(app);
return 0;

View file

@ -0,0 +1,123 @@
#include "iso7816/iso7816_t0_apdu.h"
#include "iso7816/iso7816_response.h"
//Instruction 1: returns an OK response unconditionally
//APDU example: 0x01:0x01:0x00:0x00
//response: SW1=0x90, SW2=0x00
void handle_instruction_01(ISO7816_Response_APDU* response_apdu) {
response_apdu->DataLen = 0;
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
}
//Instruction 2: expect command with no body, replies wit with a body with two bytes
//APDU example: 0x01:0x02:0x00:0x00:0x02
//response: 'bc' (0x62, 0x63) SW1=0x90, SW2=0x00
void handle_instruction_02(
uint8_t p1,
uint8_t p2,
uint16_t lc,
uint16_t le,
ISO7816_Response_APDU* response_apdu) {
if(p1 == 0 && p2 == 0 && lc == 0 && le >= 2) {
response_apdu->Data[0] = 0x62;
response_apdu->Data[1] = 0x63;
response_apdu->DataLen = 2;
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
//Instruction 3: sends a command with a body with two bytes, receives a response with no bytes
//APDU example: 0x01:0x03:0x00:0x00:0x02:CA:FE
//response SW1=0x90, SW2=0x00
void handle_instruction_03(
uint8_t p1,
uint8_t p2,
uint16_t lc,
ISO7816_Response_APDU* response_apdu) {
if(p1 == 0 && p2 == 0 && lc == 2) {
response_apdu->DataLen = 0;
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
//instruction 4: sends a command with a body with 'n' bytes, receives a response with 'n' bytes
//APDU example: 0x01:0x04:0x00:0x00:0x04:0x01:0x02:0x03:0x04:0x04
//receives (0x01, 0x02, 0x03, 0x04) SW1=0x90, SW2=0x00
void handle_instruction_04(
uint8_t p1,
uint8_t p2,
uint16_t lc,
uint16_t le,
const uint8_t* command_apdu_data_buffer,
ISO7816_Response_APDU* response_apdu) {
if(p1 == 0 && p2 == 0 && lc > 0 && le > 0 && le >= lc) {
for(uint16_t i = 0; i < lc; i++) {
response_apdu->Data[i] = command_apdu_data_buffer[i];
}
response_apdu->DataLen = lc;
iso7816_set_response(response_apdu, ISO7816_RESPONSE_OK);
} else if(p1 != 0 || p2 != 0) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2);
} else {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
}
}
void iso7816_answer_to_reset(Iso7816Atr* atr) {
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
atr->TS = 0x3B;
atr->T0 = 0x00;
}
void iso7816_process_command(
const ISO7816_Command_APDU* command_apdu,
ISO7816_Response_APDU* response_apdu) {
//example 1: sends a command with no body, receives a response with no body
//sends APDU 0x01:0x01:0x00:0x00
//receives SW1=0x90, SW2=0x00
if(command_apdu->CLA == 0x01) {
switch(command_apdu->INS) {
case 0x01:
handle_instruction_01(response_apdu);
break;
case 0x02:
handle_instruction_02(
command_apdu->P1,
command_apdu->P2,
command_apdu->Lc,
command_apdu->Le,
response_apdu);
break;
case 0x03:
handle_instruction_03(
command_apdu->P1, command_apdu->P2, command_apdu->Lc, response_apdu);
break;
case 0x04:
handle_instruction_04(
command_apdu->P1,
command_apdu->P2,
command_apdu->Lc,
command_apdu->Le,
command_apdu->Data,
response_apdu);
break;
default:
iso7816_set_response(response_apdu, ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED);
}
} else {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
}
}

View file

@ -0,0 +1,7 @@
#include "iso7816/iso7816_t0_apdu.h"
void iso7816_answer_to_reset(Iso7816Atr* atr);
void iso7816_process_command(
const ISO7816_Command_APDU* command_apdu,
ISO7816_Response_APDU* response_apdu);

View file

@ -0,0 +1,68 @@
// transforms low level calls such as XFRCallback or ICC Power on to a structured one
// an application can register these calls and listen for the callbacks defined in Iso7816Callbacks
#include <stdint.h>
#include <stddef.h>
#include <furi.h>
#include <furi_hal.h>
#include "iso7816_t0_apdu.h"
#include "iso7816_atr.h"
#include "iso7816_handler.h"
#include "iso7816_response.h"
void iso7816_icc_power_on_callback(uint8_t* atr_data, uint32_t* atr_data_len, void* context) {
furi_check(context);
Iso7816Handler* handler = (Iso7816Handler*)context;
Iso7816Atr iso7816_atr;
handler->iso7816_answer_to_reset(&iso7816_atr);
furi_assert(iso7816_atr.T0 == 0x00);
uint8_t atr_buffer[2] = {iso7816_atr.TS, iso7816_atr.T0};
*atr_data_len = 2;
memcpy(atr_data, atr_buffer, sizeof(uint8_t) * (*atr_data_len));
}
//dataBlock points to the buffer
//dataBlockLen tells reader how nany bytes should be read
void iso7816_xfr_datablock_callback(
const uint8_t* pc_to_reader_datablock,
uint32_t pc_to_reader_datablock_len,
uint8_t* reader_to_pc_datablock,
uint32_t* reader_to_pc_datablock_len,
void* context) {
furi_check(context);
Iso7816Handler* handler = (Iso7816Handler*)context;
ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)&handler->response_apdu_buffer;
ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)&handler->command_apdu_buffer;
uint8_t result = iso7816_read_command_apdu(
command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len);
if(result == ISO7816_READ_COMMAND_APDU_OK) {
handler->iso7816_process_command(command_apdu, response_apdu);
furi_assert(response_apdu->DataLen < CCID_SHORT_APDU_SIZE);
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LE);
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH) {
iso7816_set_response(response_apdu, ISO7816_RESPONSE_WRONG_LENGTH);
}
iso7816_write_response_apdu(response_apdu, reader_to_pc_datablock, reader_to_pc_datablock_len);
}
Iso7816Handler* iso7816_handler_alloc() {
Iso7816Handler* handler = malloc(sizeof(Iso7816Handler));
handler->ccid_callbacks.icc_power_on_callback = iso7816_icc_power_on_callback;
handler->ccid_callbacks.xfr_datablock_callback = iso7816_xfr_datablock_callback;
return handler;
}

View file

@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
#include "iso7816_atr.h"
#include "iso7816_t0_apdu.h"
typedef struct {
CcidCallbacks ccid_callbacks;
void (*iso7816_answer_to_reset)(Iso7816Atr* atr);
void (*iso7816_process_command)(
const ISO7816_Command_APDU* command,
ISO7816_Response_APDU* response);
uint8_t command_apdu_buffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE];
uint8_t response_apdu_buffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE];
} Iso7816Handler;
Iso7816Handler* iso7816_handler_alloc();

View file

@ -61,24 +61,25 @@ uint8_t iso7816_read_command_apdu(
//data buffer contains the whole APU response (response + trailer (SW1+SW2))
void iso7816_write_response_apdu(
const ISO7816_Response_APDU* response,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen) {
uint8_t* reader_to_pc_datablock,
uint32_t* reader_to_pc_datablock_len) {
uint32_t responseDataBufferIndex = 0;
//response body
if(response->DataLen > 0) {
while(responseDataBufferIndex < response->DataLen) {
readerToPcDataBlock[responseDataBufferIndex] = response->Data[responseDataBufferIndex];
reader_to_pc_datablock[responseDataBufferIndex] =
response->Data[responseDataBufferIndex];
responseDataBufferIndex++;
}
}
//trailer
readerToPcDataBlock[responseDataBufferIndex] = response->SW1;
reader_to_pc_datablock[responseDataBufferIndex] = response->SW1;
responseDataBufferIndex++;
readerToPcDataBlock[responseDataBufferIndex] = response->SW2;
reader_to_pc_datablock[responseDataBufferIndex] = response->SW2;
responseDataBufferIndex++;
*readerToPcDataBlockLen = responseDataBufferIndex;
*reader_to_pc_datablock_len = responseDataBufferIndex;
}

View file

@ -31,12 +31,11 @@ typedef struct {
uint8_t Data[0];
} FURI_PACKED ISO7816_Response_APDU;
void iso7816_answer_to_reset(Iso7816Atr* atr);
uint8_t iso7816_read_command_apdu(
ISO7816_Command_APDU* command,
const uint8_t* pcToReaderDataBlock,
uint32_t pcToReaderDataBlockLen);
const uint8_t* pc_to_reader_datablock,
uint32_t pc_to_reader_datablock_len);
void iso7816_write_response_apdu(
const ISO7816_Response_APDU* response,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen);
uint8_t* reader_to_pc_datablock,
uint32_t* reader_to_pc_datablock_len);

View file

@ -1,65 +0,0 @@
// transforms low level calls such as XFRCallback or ICC Power on to a structured one
// an application can register these calls and listen for the callbacks defined in Iso7816Callbacks
#include <stdint.h>
#include <stddef.h>
#include <furi.h>
#include <furi_hal.h>
#include "iso7816_t0_apdu.h"
#include "iso7816_atr.h"
#include "iso7816_callbacks.h"
#include "iso7816_response.h"
static Iso7816Callbacks* callbacks = NULL;
static uint8_t commandApduBuffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE];
static uint8_t responseApduBuffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE];
void iso7816_set_callbacks(Iso7816Callbacks* cb) {
callbacks = cb;
}
void iso7816_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen) {
Iso7816Atr atr;
callbacks->iso7816_answer_to_reset(&atr);
furi_assert(atr.T0 == 0x00);
uint8_t AtrBuffer[2] = {atr.TS, atr.T0};
*atrlen = 2;
memcpy(atrBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen));
}
//dataBlock points to the buffer
//dataBlockLen tells reader how nany bytes should be read
void iso7816_xfr_datablock_callback(
const uint8_t* pcToReaderDataBlock,
uint32_t pcToReaderDataBlockLen,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen) {
ISO7816_Response_APDU* responseAPDU = (ISO7816_Response_APDU*)&responseApduBuffer;
if(callbacks != NULL) {
ISO7816_Command_APDU* commandAPDU = (ISO7816_Command_APDU*)&commandApduBuffer;
uint8_t result =
iso7816_read_command_apdu(commandAPDU, pcToReaderDataBlock, pcToReaderDataBlockLen);
if(result == ISO7816_READ_COMMAND_APDU_OK) {
callbacks->iso7816_process_command(commandAPDU, responseAPDU);
furi_assert(responseAPDU->DataLen < CCID_SHORT_APDU_SIZE);
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LE);
} else if(result == ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH) {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_WRONG_LENGTH);
}
} else {
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INTERNAL_EXCEPTION);
}
iso7816_write_response_apdu(responseAPDU, readerToPcDataBlock, readerToPcDataBlockLen);
}

View file

@ -1,21 +0,0 @@
#pragma once
#include <stdint.h>
#include "iso7816_atr.h"
#include "iso7816_t0_apdu.h"
typedef struct {
void (*iso7816_answer_to_reset)(Iso7816Atr* atr);
void (*iso7816_process_command)(
const ISO7816_Command_APDU* command,
ISO7816_Response_APDU* response);
} Iso7816Callbacks;
void iso7816_set_callbacks(Iso7816Callbacks* cb);
void iso7816_icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen);
void iso7816_xfr_datablock_callback(
const uint8_t* dataBlock,
uint32_t dataBlockLen,
uint8_t* responseDataBlock,
uint32_t* responseDataBlockLen);