CCID: Improve request and response data handling (#3741)

* CCID: Improve request and response data handling
  - Add iso7816_set_response function: serves a helpers to set SW1 and SW2 values
  - improved iso7816_read_response_apdu by correctly parsing Lc and Le values
  - add client script to make testing easier
* lint and rename
* Format
* Review changes: pragma once, typedef
* Move command/response data and datalen into respective structures
* Remove conditional for Lc=0
* Fix comment: Le
* Make PVS happy and fix spelling

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
Filipe Paz Rodrigues 2024-07-06 06:08:44 -05:00 committed by GitHub
parent 248e926a82
commit 1510d8773b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 341 additions and 118 deletions

View File

@ -9,6 +9,7 @@
#include "iso7816_callbacks.h"
#include "iso7816_t0_apdu.h"
#include "iso7816_atr.h"
#include "iso7816_response.h"
typedef enum {
EventTypeInput,
@ -118,6 +119,76 @@ static const CcidCallbacks ccid_cb = {
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,
uint8_t lc,
uint8_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, uint8_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,
uint8_t lc,
uint8_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;
@ -125,48 +196,38 @@ void iso7816_answer_to_reset(Iso7816Atr* atr) {
}
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) {
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:0x02:0x00:0x00
//sends APDU 0x01:0x01: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;
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 {
responseAPDU->SW1 = 0x6A;
responseAPDU->SW2 = 0x00;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_CLASS_NOT_SUPPORTED);
}
}

View File

@ -0,0 +1,116 @@
# pylint: disable=missing-module-docstring, too-many-arguments, consider-using-f-string, missing-function-docstring
from smartcard.System import readers
def test_apdu(connection, test_name, apdu, expected_sw1, expected_sw2, expected_data):
print("Running test: [%s]" % test_name)
data, sw1, sw2 = connection.transmit(apdu)
failed = []
if sw1 != expected_sw1:
failed.append("SW1: Expected %x, actual %x" % (expected_sw1, sw1))
if sw2 != expected_sw2:
failed.append("SW2: Expected %x, actual %x" % (expected_sw2, sw2))
if len(data) != len(expected_data):
failed.append(
"Data: Sizes differ: Expected %x, actual %x"
% (len(expected_data), len(data))
)
print(data)
elif len(data) > 0:
data_matches = True
for i, _ in enumerate(data):
if data[i] != expected_data[i]:
data_matches = False
if not data_matches:
failed.append("Data: Expected %s, actual %s" % (expected_data, data))
if len(failed) > 0:
print("Test failed: ")
for failure in failed:
print("- %s" % failure)
else:
print("Test passed!")
def main():
r = readers()
print("Found following smartcard readers: ")
for i, sc in enumerate(r):
print("[%d] %s" % (i, sc))
print("Select the smartcard reader you want to run tests against:")
reader_index = int(input())
if reader_index < len(r):
connection = r[reader_index].createConnection()
connection.connect()
test_apdu(
connection,
"INS 0x01: No Lc, no Data, No Le. Expect no data in return",
[0x01, 0x01, 0x00, 0x00],
0x90,
0x00,
[],
)
test_apdu(
connection,
"INS 0x02: No Lc, no Data, Le=2. Expect 2 byte data in return",
[0x01, 0x02, 0x00, 0x00, 0x02],
0x90,
0x00,
[0x62, 0x63],
)
test_apdu(
connection,
"INS 0x03: Lc=2, data=[0xCA, 0xFE], No Le. Expect no data in return",
[0x01, 0x03, 0x00, 0x00, 0x02, 0xCA, 0xFE],
0x90,
0x00,
[],
)
test_apdu(
connection,
"INS 0x04: Lc=2, data=[0xCA, 0xFE], Le=2. Expect 1 byte data in return",
[0x01, 0x04, 0x00, 0x00, 0x02, 0xCA, 0xFE, 0x02],
0x90,
0x00,
[0xCA, 0xFE],
)
small_apdu = list(range(0, 0x0F))
test_apdu(
connection,
"INS 0x04: Lc=0x0F, data=small_apdu, Le=0x0F. Expect 14 bytes data in return",
[0x01, 0x04, 0x00, 0x00, 0x0F] + small_apdu + [0x0F],
0x90,
0x00,
small_apdu,
)
max_apdu = list(range(0, 0x30))
test_apdu(
connection,
"INS 0x04: Lc=0x30, data=max_apdu, Le=0x30. Expect 0x30 bytes data in return",
[0x01, 0x04, 0x00, 0x00, 0x30] + max_apdu + [0x30],
0x90,
0x00,
max_apdu,
)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,2 @@
pyscard
# or sudo apt install python3-pyscard

View File

@ -1,9 +1,6 @@
#ifndef _ISO7816_ATR_H_
#define _ISO7816_ATR_H_
#pragma once
typedef struct {
uint8_t TS;
uint8_t T0;
} Iso7816Atr;
#endif //_ISO7816_ATR_H_

View File

@ -1,17 +1,21 @@
// 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 <stdint.h>
#include <stddef.h>
#include <furi.h>
#define ISO7816_RESPONSE_BUFFER_SIZE 255
#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;
}
@ -36,41 +40,26 @@ void iso7816_xfr_datablock_callback(
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;
ISO7816_Response_APDU* responseAPDU = (ISO7816_Response_APDU*)&responseApduBuffer;
if(callbacks != NULL) {
struct ISO7816_Command_APDU commandAPDU;
ISO7816_Command_APDU* commandAPDU = (ISO7816_Command_APDU*)&commandApduBuffer;
const uint8_t* commandApduDataBuffer = NULL;
uint8_t commandApduDataBufferLen = 0;
uint8_t result =
iso7816_read_command_apdu(commandAPDU, pcToReaderDataBlock, pcToReaderDataBlockLen);
iso7816_read_command_apdu(&commandAPDU, pcToReaderDataBlock, pcToReaderDataBlockLen);
if(result == ISO7816_READ_COMMAND_APDU_OK) {
callbacks->iso7816_process_command(commandAPDU, responseAPDU);
if(commandAPDU.Lc > 0) {
commandApduDataBufferLen = commandAPDU.Lc;
commandApduDataBuffer = &pcToReaderDataBlock[5];
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);
}
callbacks->iso7816_process_command(
&commandAPDU,
&responseAPDU,
commandApduDataBuffer,
commandApduDataBufferLen,
responseApduDataBuffer,
&responseApduDataBufferLen);
} else {
//class not supported
responseAPDU.SW1 = 0x6E;
responseAPDU.SW2 = 0x00;
iso7816_set_response(responseAPDU, ISO7816_RESPONSE_INTERNAL_EXCEPTION);
}
iso7816_write_response_apdu(
&responseAPDU,
readerToPcDataBlock,
readerToPcDataBlockLen,
responseApduDataBuffer,
responseApduDataBufferLen);
iso7816_write_response_apdu(responseAPDU, readerToPcDataBlock, readerToPcDataBlockLen);
}

View File

@ -1,5 +1,4 @@
#ifndef _ISO7816_CALLBACKS_H_
#define _ISO7816_CALLBACKS_H_
#pragma once
#include <stdint.h>
#include "iso7816_atr.h"
@ -8,12 +7,8 @@
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);
const ISO7816_Command_APDU* command,
ISO7816_Response_APDU* response);
} Iso7816Callbacks;
void iso7816_set_callbacks(Iso7816Callbacks* cb);
@ -23,6 +18,4 @@ void iso7816_xfr_datablock_callback(
const uint8_t* dataBlock,
uint32_t dataBlockLen,
uint8_t* responseDataBlock,
uint32_t* responseDataBlockLen);
#endif //_ISO7816_CALLBACKS_H_
uint32_t* responseDataBlockLen);

View File

@ -0,0 +1,8 @@
#include <stdint.h>
#include "iso7816_t0_apdu.h"
#include "iso7816_response.h"
void iso7816_set_response(ISO7816_Response_APDU* responseAPDU, uint16_t responseCode) {
responseAPDU->SW1 = (responseCode >> (8 * 1)) & 0xff;
responseAPDU->SW2 = (responseCode >> (8 * 0)) & 0xff;
}

View File

@ -0,0 +1,12 @@
#pragma once
#define ISO7816_RESPONSE_OK 0x9000
#define ISO7816_RESPONSE_WRONG_LENGTH 0x6700
#define ISO7816_RESPONSE_WRONG_PARAMETERS_P1_P2 0x6A00
#define ISO7816_RESPONSE_WRONG_LE 0x6C00
#define ISO7816_RESPONSE_INSTRUCTION_NOT_SUPPORTED 0x6D00
#define ISO7816_RESPONSE_CLASS_NOT_SUPPORTED 0x6E00
#define ISO7816_RESPONSE_INTERNAL_EXCEPTION 0x6F00
void iso7816_set_response(ISO7816_Response_APDU* responseAPDU, uint16_t responseCode);

View File

@ -2,37 +2,73 @@
#include <stdint.h>
#include <string.h>
#include <furi.h>
#include <furi_hal.h>
#include "iso7816_t0_apdu.h"
//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,
uint8_t iso7816_read_command_apdu(
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];
if(dataLen == 4) {
command->Lc = 0;
command->Le = 0;
command->LePresent = false;
return ISO7816_READ_COMMAND_APDU_OK;
} else if(dataLen == 5) {
//short le
command->Lc = 0;
command->Le = dataBuffer[4];
command->LePresent = true;
return ISO7816_READ_COMMAND_APDU_OK;
} else if(dataLen > 5 && dataBuffer[4] != 0x00) {
//short lc
command->Lc = dataBuffer[4];
if(command->Lc > 0 && command->Lc < CCID_SHORT_APDU_SIZE) { //-V560
memcpy(command->Data, &dataBuffer[5], command->Lc);
//does it have a short le too?
if(dataLen == (uint32_t)(command->Lc + 5)) {
command->Le = 0;
command->LePresent = false;
return ISO7816_READ_COMMAND_APDU_OK;
} else if(dataLen == (uint32_t)(command->Lc + 6)) {
command->Le = dataBuffer[dataLen - 1];
command->LePresent = true;
return ISO7816_READ_COMMAND_APDU_OK;
} else {
return ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH;
}
} else {
return ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH;
}
} else {
return ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH;
}
}
//data buffer countains the whole APU response (response + trailer (SW1+SW2))
//data buffer contains the whole APU response (response + trailer (SW1+SW2))
void iso7816_write_response_apdu(
const struct ISO7816_Response_APDU* response,
const ISO7816_Response_APDU* response,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen,
uint8_t* responseDataBuffer,
uint32_t responseDataLen) {
uint32_t* readerToPcDataBlockLen) {
uint32_t responseDataBufferIndex = 0;
//response body
if(responseDataLen > 0) {
while(responseDataBufferIndex < responseDataLen) {
readerToPcDataBlock[responseDataBufferIndex] =
responseDataBuffer[responseDataBufferIndex];
if(response->DataLen > 0) {
while(responseDataBufferIndex < response->DataLen) {
readerToPcDataBlock[responseDataBufferIndex] = response->Data[responseDataBufferIndex];
responseDataBufferIndex++;
}
}

View File

@ -1,11 +1,14 @@
#ifndef _ISO7816_T0_APDU_H_
#define _ISO7816_T0_APDU_H_
#pragma once
#include <stdint.h>
#include "iso7816_atr.h"
#include "core/common_defines.h"
struct ISO7816_Command_APDU {
#define ISO7816_READ_COMMAND_APDU_OK 0
#define ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LE 1
#define ISO7816_READ_COMMAND_APDU_ERROR_WRONG_LENGTH 2
typedef struct {
//header
uint8_t CLA;
uint8_t INS;
@ -13,24 +16,27 @@ struct ISO7816_Command_APDU {
uint8_t P2;
//body
uint8_t Lc;
uint8_t Le;
} FURI_PACKED;
uint16_t Lc; //data length
uint16_t Le; //maximum response data length expected by client
struct ISO7816_Response_APDU {
//Le can have value of 0x00, which actually meand 0x100 = 256
bool LePresent;
uint8_t Data[0];
} FURI_PACKED ISO7816_Command_APDU;
typedef struct {
uint8_t SW1;
uint8_t SW2;
} FURI_PACKED;
uint16_t DataLen;
uint8_t Data[0];
} FURI_PACKED ISO7816_Response_APDU;
void iso7816_answer_to_reset(Iso7816Atr* atr);
void iso7816_read_command_apdu(
struct ISO7816_Command_APDU* command,
const uint8_t* dataBuffer,
uint32_t dataLen);
uint8_t iso7816_read_command_apdu(
ISO7816_Command_APDU* command,
const uint8_t* pcToReaderDataBlock,
uint32_t pcToReaderDataBlockLen);
void iso7816_write_response_apdu(
const struct ISO7816_Response_APDU* response,
const ISO7816_Response_APDU* response,
uint8_t* readerToPcDataBlock,
uint32_t* readerToPcDataBlockLen,
uint8_t* responseDataBuffer,
uint32_t responseDataLen);
#endif //_ISO7816_T0_APDU_H_
uint32_t* readerToPcDataBlockLen);

View File

@ -19,7 +19,8 @@ static const uint8_t USB_DEVICE_NO_PROTOCOL = 0x0;
#define CCID_TOTAL_SLOTS 1
#define CCID_SLOT_INDEX 0
#define CCID_DATABLOCK_SIZE 256
#define CCID_DATABLOCK_SIZE \
(4 + 1 + CCID_SHORT_APDU_SIZE + 1) //APDU Header + Lc + Short APDU size + Le
#define ENDPOINT_DIR_IN 0x80
#define ENDPOINT_DIR_OUT 0x00

View File

@ -5,6 +5,8 @@
#include "hid_usage_consumer.h"
#include "hid_usage_led.h"
#define CCID_SHORT_APDU_SIZE (0xFF)
#ifdef __cplusplus
extern "C" {
#endif