forked from mirrors/lpac
* driver: pass EUICC APDU or HTTP struct to driver fini OP Currently, the .fini driver OP can only be used with global variables, but that will be an issue when we cant use global variables. This will be used with direct QMI in order to free the memory used by the driver private structure instead of global variables. Signed-off-by: Robert Marko <robert.marko@sartura.hr> * driver: apdu: rename QRTR QMI helpers to QMI helpers Almost all of the QRTR QMI helpers will be reused for direct QMI so, lets rename the sources to indicate that. Signed-off-by: Robert Marko <robert.marko@sartura.hr> * driver: apdu: extract common QMI code Support for using QMI directly and not over QRTR would use a lot of the same code as QMI over QRTR, so in order to prevent code duplication lets extract that common code so it can be reused. Signed-off-by: Robert Marko <robert.marko@sartura.hr> * driver: apdu: qmi-helpers: allow compiling without libqrtr Direct QMI backend wont use any of the libqmi QRTR functionality so it wont depend on libqrtr and thus we need to make sure QMI helpers still compile without libqrtr in the system. Signed-off-by: Robert Marko <robert.marko@sartura.hr> * driver: apdu: dont use global variables for QMI Since we split out the common QMI code then we cannot be using global variables since existing QRTR and the coming QMI drivers will clash by trying to use the same global variable names with the common code. So, lets move to passing a structure which is driver specific instead. Since we are not using global variables anymore, we cannot be using atexit anymore, so lets move that cleanup step to driver finish OP instead. Signed-off-by: Robert Marko <robert.marko@sartura.hr> * driver: apdu: add QMI backend Previously QMI over QRTR support was added, however that is only present in modern Qualcomm modems and only when running in PCIe mode and its quite common to still only use even the latest modems via USB. In that case, they are all still controllable via QMI but it is directly exposed as a character device in Linux so we can reuse most of the code from QMI over QRTR support but drop the support for libqrtr to talk to the modems. We require the QMI device path to be passed via QMI_DEVICE env variable for the backend to operate. Signed-off-by: Robert Marko <robert.marko@sartura.hr> * docs: ENVVARS: document QMI backend Document the new direct QMI backend ENV variables as well as make it clear that UIM_SLOT is not a QMI QRTR only variable. Signed-off-by: Robert Marko <robert.marko@sartura.hr> * driver: apdu: qmi-helpers: support opening QMI device in proxy mode Currently, we are using QMI_DEVICE_OPEN_FLAGS_NONE to open the QMI device and thus we are requesting exclusive access to the device while we need to talk to it. This will work fine as long as we are the only thing trying to use that QMI device at the same time or the device was not already opened in proxy mode. This is an issue since ModemManager will open the device in proxy mode so that qmicli or other applications can still talk to the same QMI device but it will break lpac from trying to use QMI. So, lets try and open the device in proxy mode, libqmi will the start the qmi-proxy service automatically as its built and installed as part of it. Signed-off-by: Robert Marko <robert.marko@sartura.hr> --------- Signed-off-by: Robert Marko <robert.marko@sartura.hr>
466 lines
9.8 KiB
C
466 lines
9.8 KiB
C
#include "pcsc.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <winscard.h>
|
|
#else
|
|
#include <PCSC/wintypes.h>
|
|
#include <PCSC/winscard.h>
|
|
#endif
|
|
|
|
#include <cjson/cJSON_ex.h>
|
|
#include <euicc/interface.h>
|
|
|
|
#define INTERFACE_SELECT_ENV "DRIVER_IFID"
|
|
|
|
#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 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)
|
|
{
|
|
fprintf(stderr, "SCardEstablishContext() failed: %08X\n", 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)
|
|
{
|
|
fprintf(stderr, "SCardListReaders() failed: %08X\n", 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)
|
|
{
|
|
fprintf(stderr, "SCardListReaders() failed: %08X\n", 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)
|
|
{
|
|
int ret;
|
|
int id;
|
|
DWORD dwActiveProtocol;
|
|
|
|
id = 0;
|
|
if (getenv(INTERFACE_SELECT_ENV))
|
|
{
|
|
id = atoi(getenv(INTERFACE_SELECT_ENV));
|
|
}
|
|
|
|
if (id != index)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
ret = SCardConnect(pcsc_ctx, reader, SCARD_SHARE_EXCLUSIVE, SCARD_PROTOCOL_T0, &pcsc_hCard, &dwActiveProtocol);
|
|
if (ret != SCARD_S_SUCCESS)
|
|
{
|
|
fprintf(stderr, "SCardConnect() failed: %08X\n", ret);
|
|
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)
|
|
{
|
|
fprintf(stderr, "SCardTransmit() failed: %08X\n", 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 json_print(cJSON *jpayload)
|
|
{
|
|
cJSON *jroot = NULL;
|
|
char *jstr = NULL;
|
|
|
|
if (jpayload == NULL)
|
|
{
|
|
goto err;
|
|
}
|
|
|
|
jroot = cJSON_CreateObject();
|
|
if (jroot == NULL)
|
|
{
|
|
goto err;
|
|
}
|
|
|
|
if (cJSON_AddStringOrNullToObject(jroot, "type", "driver") == NULL)
|
|
{
|
|
goto err;
|
|
}
|
|
|
|
if (cJSON_AddItemReferenceToObject(jroot, "payload", jpayload) == 0)
|
|
{
|
|
goto err;
|
|
}
|
|
|
|
jstr = cJSON_PrintUnformatted(jroot);
|
|
|
|
if (jstr == NULL)
|
|
{
|
|
goto err;
|
|
}
|
|
cJSON_Delete(jroot);
|
|
|
|
fprintf(stdout, "%s\n", jstr);
|
|
fflush(stdout);
|
|
|
|
free(jstr);
|
|
jstr = NULL;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
cJSON_Delete(jroot);
|
|
free(jstr);
|
|
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)
|
|
{
|
|
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", INTERFACE_SELECT_ENV))
|
|
{
|
|
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(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,
|
|
};
|