Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial ISO7816 support #3681

Merged
merged 4 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 90 additions & 37 deletions applications/debug/ccid_test/ccid_test_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
#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"

typedef enum {
EventTypeInput,
Expand All @@ -33,38 +34,6 @@ typedef enum {
CcidTestSubmenuIndexInsertSmartcardReader
} SubmenuIndex;

void icc_power_on_callback(uint8_t* atrBuffer, uint32_t* atrlen, void* context) {
UNUSED(context);

iso7816_answer_to_reset(atrBuffer, atrlen);
}

//dataBlock points to the buffer
//dataBlockLen tells reader how nany bytes should be read
void xfr_datablock_callback(
const uint8_t* dataBlock,
uint32_t dataBlockLen,
uint8_t* responseDataBlock,
uint32_t* responseDataBlockLen,
void* context) {
UNUSED(context);

struct ISO7816_Command_APDU commandAPDU;
iso7816_read_command_apdu(&commandAPDU, dataBlock, dataBlockLen);

struct ISO7816_Response_APDU responseAPDU;
//class not supported
responseAPDU.SW1 = 0x6E;
responseAPDU.SW2 = 0x00;

iso7816_write_response_apdu(&responseAPDU, responseDataBlock, responseDataBlockLen);
}

static const CcidCallbacks ccid_cb = {
icc_power_on_callback,
xfr_datablock_callback,
};

static void ccid_test_app_render_callback(Canvas* canvas, void* ctx) {
UNUSED(ctx);
canvas_clear(canvas);
Expand Down Expand Up @@ -127,6 +96,86 @@ void ccid_test_app_free(CcidTestApp* app) {
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,
};

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 struct ISO7816_Command_APDU* commandAPDU,
struct ISO7816_Response_APDU* responseAPDU,
const uint8_t* commandApduDataBuffer,
uint8_t commandApduDataBufferLen,
uint8_t* responseApduDataBuffer,
uint8_t* responseApduDataBufferLen) {
//example 1: sends a command with no body, receives a response with no body
//sends APDU 0x01:0x02:0x00:0x00
//receives SW1=0x90, SW2=0x00
if(commandAPDU->CLA == 0x01 && commandAPDU->INS == 0x01) {
responseAPDU->SW1 = 0x90;
responseAPDU->SW2 = 0x00;
}
//example 2: sends a command with no body, receives a response with a body with two bytes
//sends APDU 0x01:0x02:0x00:0x00
//receives 'bc' (0x62, 0x63) SW1=0x80, SW2=0x10
else if(commandAPDU->CLA == 0x01 && commandAPDU->INS == 0x02) {
responseApduDataBuffer[0] = 0x62;
responseApduDataBuffer[1] = 0x63;

*responseApduDataBufferLen = 2;

responseAPDU->SW1 = 0x90;
responseAPDU->SW2 = 0x00;
}
//example 3: ends a command with a body with two bytes, receives a response with a body with two bytes
//sends APDU 0x01:0x03:0x00:0x00:0x02:CA:FE
//receives (0xCA, 0xFE) SW1=0x90, SW2=0x02
else if(
commandAPDU->CLA == 0x01 && commandAPDU->INS == 0x03 && commandApduDataBufferLen == 2 &&
commandAPDU->Lc == 2) {
//echo command body to response body
responseApduDataBuffer[0] = commandApduDataBuffer[0];
responseApduDataBuffer[1] = commandApduDataBuffer[1];

*responseApduDataBufferLen = 2;

responseAPDU->SW1 = 0x90;
responseAPDU->SW2 = 0x00;
} else {
responseAPDU->SW1 = 0x6A;
responseAPDU->SW2 = 0x00;
}
}

static const Iso7816Callbacks iso87816_cb = {
iso7816_answer_to_reset,
iso7816_process_command,
};

int32_t ccid_test_app(void* p) {
UNUSED(p);

Expand All @@ -135,14 +184,16 @@ int32_t ccid_test_app(void* p) {

//setup CCID USB
// On linux: set VID PID using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist
app->ccid_cfg.vid = 0x1234;
app->ccid_cfg.pid = 0x5678;
app->ccid_cfg.vid = 0x076B;
app->ccid_cfg.pid = 0x3A21;

FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config();
furi_hal_usb_unlock();
furi_hal_ccid_set_callbacks((CcidCallbacks*)&ccid_cb);
furi_hal_ccid_set_callbacks((CcidCallbacks*)&ccid_cb, NULL);
furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true);

iso7816_set_callbacks((Iso7816Callbacks*)&iso87816_cb);

//handle button events
CcidTestAppEvent event;
while(1) {
Expand All @@ -161,7 +212,9 @@ int32_t ccid_test_app(void* p) {

//tear down USB
furi_hal_usb_set_config(usb_mode_prev, NULL);
furi_hal_ccid_set_callbacks(NULL);
furi_hal_ccid_set_callbacks(NULL, NULL);

iso7816_set_callbacks(NULL);

//teardown view
ccid_test_app_free(app);
Expand Down
9 changes: 9 additions & 0 deletions applications/debug/ccid_test/iso7816_atr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef _ISO7816_ATR_H_
#define _ISO7816_ATR_H_

typedef struct {
uint8_t TS;
uint8_t T0;
} Iso7816Atr;

#endif //_ISO7816_ATR_H_
76 changes: 76 additions & 0 deletions applications/debug/ccid_test/iso7816_callbacks.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// 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 "iso7816_t0_apdu.h"
#include "iso7816_atr.h"
#include "iso7816_callbacks.h"
#include <stdint.h>
#include <stddef.h>
#include <furi.h>

#define ISO7816_RESPONSE_BUFFER_SIZE 255

static Iso7816Callbacks* callbacks = NULL;

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) {
struct ISO7816_Response_APDU responseAPDU;
uint8_t responseApduDataBuffer[ISO7816_RESPONSE_BUFFER_SIZE];
uint8_t responseApduDataBufferLen = 0;

if(callbacks != NULL) {
struct ISO7816_Command_APDU commandAPDU;

const uint8_t* commandApduDataBuffer = NULL;
uint8_t commandApduDataBufferLen = 0;

iso7816_read_command_apdu(&commandAPDU, pcToReaderDataBlock, pcToReaderDataBlockLen);

if(commandAPDU.Lc > 0) {
commandApduDataBufferLen = commandAPDU.Lc;
commandApduDataBuffer = &pcToReaderDataBlock[5];
}

callbacks->iso7816_process_command(
&commandAPDU,
&responseAPDU,
commandApduDataBuffer,
commandApduDataBufferLen,
responseApduDataBuffer,
&responseApduDataBufferLen);

} else {
//class not supported
responseAPDU.SW1 = 0x6E;
responseAPDU.SW2 = 0x00;
}

iso7816_write_response_apdu(
&responseAPDU,
readerToPcDataBlock,
readerToPcDataBlockLen,
responseApduDataBuffer,
responseApduDataBufferLen);
}
28 changes: 28 additions & 0 deletions applications/debug/ccid_test/iso7816_callbacks.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#ifndef _ISO7816_CALLBACKS_H_
#define _ISO7816_CALLBACKS_H_

#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 struct ISO7816_Command_APDU* command,
struct ISO7816_Response_APDU* response,
const uint8_t* commandApduDataBuffer,
uint8_t commandApduDataBufferLen,
uint8_t* responseApduDataBuffer,
uint8_t* responseApduDataBufferLen);
} 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);

#endif //_ISO7816_CALLBACKS_H_
43 changes: 27 additions & 16 deletions applications/debug/ccid_test/iso7816_t0_apdu.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,45 @@
#include <furi.h>
#include "iso7816_t0_apdu.h"

void iso7816_answer_to_reset(uint8_t* dataBuffer, uint32_t* atrlen) {
//minimum valid ATR: https://smartcard-atr.apdu.fr/parse?ATR=3B+00
uint8_t AtrBuffer[2] = {
0x3B, //TS (direct convention)
0x00 // T0 (Y(1): b0000, K: 0 (historical bytes))
};
*atrlen = 2;

memcpy(dataBuffer, AtrBuffer, sizeof(uint8_t) * (*atrlen));
}

//reads dataBuffer with dataLen size, translate it into a ISO7816_Command_APDU type
//extra data will be pointed to commandDataBuffer
void iso7816_read_command_apdu(
struct ISO7816_Command_APDU* command,
const uint8_t* dataBuffer,
uint32_t dataLen) {
UNUSED(dataLen);

command->CLA = dataBuffer[0];
command->INS = dataBuffer[1];
command->P1 = dataBuffer[2];
command->P2 = dataBuffer[3];
command->Lc = dataBuffer[4];
}

//data buffer countains the whole APU response (response + trailer (SW1+SW2))
void iso7816_write_response_apdu(
const struct ISO7816_Response_APDU* response,
uint8_t* dataBuffer,
uint32_t* dataLen) {
dataBuffer[0] = response->SW1;
dataBuffer[1] = response->SW2;
*dataLen = 2;
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen,
uint8_t* responseDataBuffer,
uint32_t responseDataLen) {
uint32_t responseDataBufferIndex = 0;

//response body
if(responseDataLen > 0) {
while(responseDataBufferIndex < responseDataLen) {
readerToPcDataBlock[responseDataBufferIndex] =
responseDataBuffer[responseDataBufferIndex];
responseDataBufferIndex++;
}
}

//trailer
readerToPcDataBlock[responseDataBufferIndex] = response->SW1;
responseDataBufferIndex++;

readerToPcDataBlock[responseDataBufferIndex] = response->SW2;
responseDataBufferIndex++;

*readerToPcDataBlockLen = responseDataBufferIndex;
}
10 changes: 7 additions & 3 deletions applications/debug/ccid_test/iso7816_t0_apdu.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#define _ISO7816_T0_APDU_H_

#include <stdint.h>
#include "iso7816_atr.h"
#include "core/common_defines.h"

struct ISO7816_Command_APDU {
//header
Expand All @@ -20,13 +22,15 @@ struct ISO7816_Response_APDU {
uint8_t SW2;
} FURI_PACKED;

void iso7816_answer_to_reset(uint8_t* atrBuffer, uint32_t* atrlen);
void iso7816_answer_to_reset(Iso7816Atr* atr);
void iso7816_read_command_apdu(
struct ISO7816_Command_APDU* command,
const uint8_t* dataBuffer,
uint32_t dataLen);
void iso7816_write_response_apdu(
const struct ISO7816_Response_APDU* response,
uint8_t* dataBuffer,
uint32_t* dataLen);
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen,
uint8_t* responseDataBuffer,
uint32_t responseDataLen);
#endif //_ISO7816_T0_APDU_H_
4 changes: 2 additions & 2 deletions targets/f18/api_symbols.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,62.3,,
Version,+,63.0,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/bt/bt_service/bt_keys_storage.h,,
Header,+,applications/services/cli/cli.h,,
Expand Down Expand Up @@ -1157,7 +1157,7 @@ Function,+,furi_hal_bus_is_enabled,_Bool,FuriHalBus
Function,+,furi_hal_bus_reset,void,FuriHalBus
Function,+,furi_hal_ccid_ccid_insert_smartcard,void,
Function,+,furi_hal_ccid_ccid_remove_smartcard,void,
Function,+,furi_hal_ccid_set_callbacks,void,CcidCallbacks*
Function,+,furi_hal_ccid_set_callbacks,void,"CcidCallbacks*, void*"
Function,+,furi_hal_cdc_get_ctrl_line_state,uint8_t,uint8_t
Function,+,furi_hal_cdc_get_port_settings,usb_cdc_line_coding*,uint8_t
Function,+,furi_hal_cdc_receive,int32_t,"uint8_t, uint8_t*, uint16_t"
Expand Down
Loading