lpac/driver/apdu/pcsc.c

392 lines
10 KiB
C

#include "pcsc.h"
#include <cjson/cJSON_ex.h>
#include <euicc/interface.h>
#include <lpac/utils.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef _WIN32
# include "pcsc_win32.h"
# include <winscard.h>
#else
# include <PCSC/winscard.h>
# include <PCSC/wintypes.h>
#endif
#define ENV_DRV_IFID APDU_ENV_NAME(PCSC, DRV_IFID)
#define ENV_DRV_NAME APDU_ENV_NAME(PCSC, DRV_NAME)
#define ENV_DRV_IGNORE_NAME APDU_ENV_NAME(PCSC, DRV_IGNORE_NAME)
#define EUICC_INTERFACE_BUFSZ 264
// #define APDU_ST33_MAGIC "\x90\xBD\x36\xBB\x00"
#define APDU_TERMINAL_CAPABILITIES "\x80\xAA\x00\x00\x0A\xA9\x08\x81\x00\x82\x01\x01\x83\x01\x07"
#define APDU_OPENLOGICCHANNEL "\x00\x70\x00\x00\x01"
#define APDU_CLOSELOGICCHANNEL "\x00\x70\x80\xFF\x00"
#define APDU_SELECT_HEADER "\x00\xA4\x04\x00\xFF"
static SCARDCONTEXT pcsc_ctx;
static SCARDHANDLE pcsc_hCard;
static LPSTR pcsc_mszReaders;
static void pcsc_error(const char *method, const int32_t code) {
fprintf(stderr, "%s failed: %08X (%s)\n", method, code, pcsc_stringify_error(code));
}
static bool is_ignored_reader_name(const char *reader) {
char *value = getenv(ENV_DRV_IGNORE_NAME);
if (value == NULL)
return false;
const char *token = NULL;
for (token = strtok(value, ";"); token != NULL; token = strtok(NULL, ";")) {
if (strstr(reader, token) == NULL)
continue;
return true; // reader name is in ignore list, skip
}
return false;
}
static int pcsc_ctx_open(void) {
int ret;
DWORD dwReaders;
pcsc_ctx = 0;
pcsc_hCard = 0;
pcsc_mszReaders = NULL;
ret = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &pcsc_ctx);
if (ret != SCARD_S_SUCCESS) {
pcsc_error("SCardEstablishContext()", ret);
return -1;
}
#ifdef SCARD_AUTOALLOCATE
dwReaders = SCARD_AUTOALLOCATE;
ret = SCardListReaders(pcsc_ctx, NULL, (LPSTR)&pcsc_mszReaders, &dwReaders);
#else
// macOS does not support SCARD_AUTOALLOCATE, so we need to call SCardListReaders twice.
// First call to get the size of the buffer, second call to get the actual data.
ret = SCardListReaders(pcsc_ctx, NULL, NULL, &dwReaders);
if (ret != SCARD_S_SUCCESS) {
pcsc_error("SCardListReaders()", ret);
return -1;
}
pcsc_mszReaders = malloc(sizeof(char) * dwReaders);
if (pcsc_mszReaders == NULL) {
fprintf(stderr, "malloc: not enough memory\n");
return -1;
}
ret = SCardListReaders(pcsc_ctx, NULL, pcsc_mszReaders, &dwReaders);
#endif
if (ret != SCARD_S_SUCCESS) {
pcsc_error("SCardListReaders()", ret);
return -1;
}
return 0;
}
static int pcsc_iter_reader(int (*callback)(int index, const char *reader, void *userdata), void *userdata) {
int ret;
LPSTR psReader;
psReader = pcsc_mszReaders;
for (int i = 0, n = 0;; i++) {
char *p = pcsc_mszReaders + i;
if (*p == '\0') {
ret = callback(n, psReader, userdata);
if (ret < 0)
return -1;
if (ret > 0)
return 0;
if (*(p + 1) == '\0') {
break;
}
psReader = p + 1;
n++;
}
}
return -1;
}
static int pcsc_open_hCard_iter(int index, const char *reader, void *userdata) {
DWORD dwActiveProtocol;
const int id = getenv_or_default(ENV_DRV_IFID, (int)-1);
if (id != -1 && id != index) {
const char *part_name = getenv(ENV_DRV_NAME);
if (strstr(reader, part_name) == NULL) {
return 0;
}
}
if (is_ignored_reader_name(reader)) {
return 0; // skip ignored reader names
}
const int ret =
SCardConnect(pcsc_ctx, reader, SCARD_SHARE_EXCLUSIVE, SCARD_PROTOCOL_T0, &pcsc_hCard, &dwActiveProtocol);
if (ret != SCARD_S_SUCCESS) {
pcsc_error("SCardConnect()", ret);
// see <https://blog.apdu.fr/posts/2024/12/gnupg-and-pcsc-conflicts-episode-3/>
if (ret == SCARD_E_SHARING_VIOLATION)
return 0; // skip
return -1;
}
return 1;
}
static int pcsc_open_hCard(void) { return pcsc_iter_reader(pcsc_open_hCard_iter, NULL); }
static void pcsc_close(void) {
if (pcsc_mszReaders) {
// macOS does not support SCARD_AUTOALLOCATE, so we need to free the buffer manually.
#ifdef SCARD_AUTOALLOCATE
SCardFreeMemory(pcsc_ctx, pcsc_mszReaders);
#else
// on macOS, pcsc_mszReaders is allocated by malloc()
free(pcsc_mszReaders);
#endif
}
if (pcsc_hCard) {
SCardDisconnect(pcsc_hCard, SCARD_UNPOWER_CARD);
}
if (pcsc_ctx) {
SCardReleaseContext(pcsc_ctx);
}
pcsc_ctx = 0;
pcsc_hCard = 0;
pcsc_mszReaders = NULL;
}
static int pcsc_transmit_lowlevel(uint8_t *rx, uint32_t *rx_len, const uint8_t *tx, const uint8_t tx_len) {
int ret;
DWORD rx_len_merged;
rx_len_merged = *rx_len;
ret = SCardTransmit(pcsc_hCard, SCARD_PCI_T0, tx, tx_len, NULL, rx, &rx_len_merged);
if (ret != SCARD_S_SUCCESS) {
pcsc_error("SCardTransmit()", ret);
return -1;
}
*rx_len = rx_len_merged;
return 0;
}
static void pcsc_logic_channel_close(uint8_t channel) {
uint8_t tx[sizeof(APDU_CLOSELOGICCHANNEL) - 1];
uint8_t rx[EUICC_INTERFACE_BUFSZ];
uint32_t rx_len;
memcpy(tx, APDU_CLOSELOGICCHANNEL, sizeof(tx));
tx[3] = channel;
rx_len = sizeof(rx);
pcsc_transmit_lowlevel(rx, &rx_len, tx, sizeof(tx));
}
static int pcsc_logic_channel_open(const uint8_t *aid, uint8_t aid_len) {
int channel = 0;
uint8_t tx[EUICC_INTERFACE_BUFSZ];
uint8_t *tx_wptr;
uint8_t rx[EUICC_INTERFACE_BUFSZ];
uint32_t rx_len;
if (aid_len > 32) {
goto err;
}
rx_len = sizeof(rx);
if (pcsc_transmit_lowlevel(rx, &rx_len, (const uint8_t *)APDU_OPENLOGICCHANNEL, sizeof(APDU_OPENLOGICCHANNEL) - 1)
< 0) {
goto err;
}
if (rx_len != 3) {
goto err;
}
if ((rx[1] & 0xF0) != 0x90) {
goto err;
}
channel = rx[0];
tx_wptr = tx;
memcpy(tx_wptr, APDU_SELECT_HEADER, sizeof(APDU_SELECT_HEADER) - 1);
tx_wptr += sizeof(APDU_SELECT_HEADER) - 1;
memcpy(tx_wptr, aid, aid_len);
tx_wptr += aid_len;
tx[0] = (tx[0] & 0xF0) | channel;
tx[4] = aid_len;
rx_len = sizeof(rx);
if (pcsc_transmit_lowlevel(rx, &rx_len, tx, tx_wptr - tx) < 0) {
goto err;
}
if (rx_len < 2) {
goto err;
}
switch (rx[rx_len - 2]) {
case 0x90:
case 0x61:
return channel;
default:
goto err;
}
err:
if (channel) {
pcsc_logic_channel_close(channel);
}
return -1;
}
static int apdu_interface_connect(struct euicc_ctx *ctx) {
uint8_t rx[EUICC_INTERFACE_BUFSZ];
uint32_t rx_len;
if (pcsc_open_hCard() < 0) {
return -1;
}
rx_len = sizeof(rx);
pcsc_transmit_lowlevel(rx, &rx_len, (const uint8_t *)APDU_TERMINAL_CAPABILITIES,
sizeof(APDU_TERMINAL_CAPABILITIES) - 1);
return 0;
}
static void apdu_interface_disconnect(struct euicc_ctx *ctx) { pcsc_close(); }
static int apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx,
uint32_t tx_len) {
*rx = malloc(EUICC_INTERFACE_BUFSZ);
if (!*rx) {
fprintf(stderr, "SCardTransmit() RX buffer alloc failed\n");
return -1;
}
*rx_len = EUICC_INTERFACE_BUFSZ;
if (pcsc_transmit_lowlevel(*rx, rx_len, tx, tx_len) < 0) {
free(*rx);
*rx_len = 0;
return -1;
}
return 0;
}
static int apdu_interface_logic_channel_open(struct euicc_ctx *ctx, const uint8_t *aid, uint8_t aid_len) {
return pcsc_logic_channel_open(aid, aid_len);
}
static void apdu_interface_logic_channel_close(struct euicc_ctx *ctx, uint8_t channel) {
pcsc_logic_channel_close(channel);
}
static int pcsc_list_iter(int index, const char *reader, void *userdata) {
cJSON *json = userdata;
cJSON *jreader;
char index_str[16];
snprintf(index_str, sizeof(index_str), "%d", index);
jreader = cJSON_CreateObject();
if (!jreader) {
return -1;
}
if (!cJSON_AddStringOrNullToObject(jreader, "env", index_str)) {
return -1;
}
if (!cJSON_AddStringOrNullToObject(jreader, "name", reader)) {
return -1;
}
if (!cJSON_AddItemToArray(json, jreader)) {
return -1;
}
return 0;
}
static int libapduinterface_init(struct euicc_apdu_interface *ifstruct) {
set_deprecated_env_name(ENV_DRV_IFID, "DRIVER_IFID");
set_deprecated_env_name(ENV_DRV_NAME, "DRIVER_NAME");
memset(ifstruct, 0, sizeof(struct euicc_apdu_interface));
if (pcsc_ctx_open() < 0) {
return -1;
}
ifstruct->connect = apdu_interface_connect;
ifstruct->disconnect = apdu_interface_disconnect;
ifstruct->logic_channel_open = apdu_interface_logic_channel_open;
ifstruct->logic_channel_close = apdu_interface_logic_channel_close;
ifstruct->transmit = apdu_interface_transmit;
return 0;
}
static int libapduinterface_main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <list>\n", argv[0]);
return -1;
}
if (strcmp(argv[1], "list") == 0) {
cJSON *payload;
cJSON *data;
payload = cJSON_CreateObject();
if (!payload) {
return -1;
}
if (!cJSON_AddStringOrNullToObject(payload, "env", ENV_DRV_IFID)) {
return -1;
}
data = cJSON_CreateArray();
if (!data) {
return -1;
}
pcsc_iter_reader(pcsc_list_iter, data);
if (!cJSON_AddItemToObject(payload, "data", data)) {
return -1;
}
json_print("driver", payload);
return 0;
}
return 0;
}
static void libapduinterface_fini(struct euicc_apdu_interface *ifstruct) {}
const struct euicc_driver driver_apdu_pcsc = {
.type = DRIVER_APDU,
.name = "pcsc",
.init = (int (*)(void *))libapduinterface_init,
.main = libapduinterface_main,
.fini = (void (*)(void *))libapduinterface_fini,
};