/* * Copyright (c) 2025 Onomondo ApS & sysmocom - s.f.m.c. GmbH. All rights reserved. * * SPDX-License-Identifier: AGPL-3.0-only * * Author: Philipp Maier / sysmocom - s.f.m.c. GmbH */ #include #include #include #include #include #include #include #include #include #include #include #include #define PCSC_ERROR(reader_num, rv, text) \ if (rv != SCARD_S_SUCCESS) { \ IPA_LOGP(SSCARD, LERROR, "PCSC reader #%d error: %s (%s,0x%lX)\n", \ reader_num, pcsc_stringify_error(rv), text, rv); \ goto error; \ } struct scard_ctx { bool initialized; unsigned int reader_num; SCARDCONTEXT hContext; DWORD dwActiveProtocol; SCARDHANDLE hCard; SCARD_IO_REQUEST pioRecvPci; const SCARD_IO_REQUEST *pioSendPci; }; /*! Initialize smartcard reader (and card). * \param[in] reader_num device number of the smartcard reader. * \returns pointer to newly allocated smartcard reader context. */ void *ipa_scard_init(unsigned int reader_num) { struct scard_ctx *ctx; long rc; LPSTR mszReaders = NULL; DWORD dwReaders; unsigned int num_readers; char *reader_name; ctx = IPA_ALLOC_ZERO(struct scard_ctx); ctx->reader_num = reader_num; /* Initialize reader */ rc = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &ctx->hContext); PCSC_ERROR(reader_num, rc, "SCardEstablishContext"); dwReaders = SCARD_AUTOALLOCATE; rc = SCardListReaders(ctx->hContext, NULL, (LPSTR) & mszReaders, &dwReaders); PCSC_ERROR(reader_num, rc, "SCardListReaders"); num_readers = 0; reader_name = mszReaders; while (*reader_name != '\0' && num_readers != reader_num) { reader_name += strlen(reader_name) + 1; num_readers++; } if (reader_num != num_readers) { IPA_LOGP(SSCARD, LINFO, "no smartcard reader.\n"); goto error; } /* Initialize card */ rc = SCardConnect(ctx->hContext, reader_name, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, &ctx->hCard, &ctx->dwActiveProtocol); PCSC_ERROR(reader_num, rc, "SCardConnect"); ctx->pioSendPci = SCARD_PCI_T0; IPA_LOGP(SSCARD, LINFO, "PCSC reader #%d (%s) initialized.\n", reader_num, reader_name); ctx->initialized = true; SCardFreeMemory(ctx->hContext, mszReaders); return ctx; error: IPA_LOGP(SSCARD, LERROR, "PCSC reader #%d initialization failed!\n", reader_num); if (mszReaders) SCardFreeMemory(ctx->hContext, mszReaders); IPA_FREE(ctx); return NULL; } /*! Transceive smartcard APDU. * \param[inout] scard_ctx smartcard reader context. * \param[out] res buffer to store smartcard response. * \param[out] req buffer with smartcard request. * \returns 0 on success, -EIO on failure. */ int ipa_scard_transceive(void *scard_ctx, struct ipa_buf *res, const struct ipa_buf *req) { struct scard_ctx *ctx = scard_ctx; LONG rc; assert(ctx); assert(res); assert(req); res->len = res->data_len; IPA_LOGP(SSCARD, LDEBUG, "PCSC reader #%d TX: \n", ctx->reader_num); ipa_buf_hexdump_multiline(req, 64, 1, SSCARD, LINFO); rc = SCardTransmit(ctx->hCard, ctx->pioSendPci, req->data, req->len, &ctx->pioRecvPci, res->data, (LPDWORD) &res->len); PCSC_ERROR(ctx->reader_num, rc, "SCardEndTransaction"); if (res->len) { IPA_LOGP(SSCARD, LDEBUG, "PCSC reader #%d RX: \n", ctx->reader_num); ipa_buf_hexdump_multiline(res, 64, 1, SSCARD, LINFO); } else { IPA_LOGP(SSCARD, LDEBUG, "PCSC reader #%d RX: (no data)\n", ctx->reader_num); } return 0; error: IPA_LOGP(SSCARD, LERROR, "PCSC reader #%d transceive failed!\n", ctx->reader_num); return -EIO; } /*! Reset smartcard. * \param[inout] scard_ctx smartcard reader context. * \returns 0 on success, -EIO on failure. */ int ipa_scard_reset(void *scard_ctx) { struct scard_ctx *ctx = scard_ctx; LONG rc; assert(ctx); rc = SCardReconnect(ctx->hCard, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, SCARD_RESET_CARD, &ctx->dwActiveProtocol); PCSC_ERROR(ctx->reader_num, rc, "SCardReconnect"); IPA_LOGP(SSCARD, LINFO, "PCSC reader #%d card reset\n", ctx->reader_num); return 0; error: IPA_LOGP(SSCARD, LERROR, "PCSC reader #%d reset failed!\n", ctx->reader_num); return -EIO; } /*! Read smartcard ATR. * \param[inout] scard_ctx smartcard reader context. * \param[out] res buffer to store the ATR. * \returns 0 on success, -EIO on failure. */ int ipa_scard_atr(void *scard_ctx, struct ipa_buf *atr) { /* Needed by SCardStatus, but we are not interested in those values here */ char pbReader[MAX_READERNAME]; DWORD dwReaderLen = sizeof(pbReader); DWORD dwState; DWORD dwProt; struct scard_ctx *ctx = scard_ctx; LONG rc; assert(ctx); atr->len = atr->data_len; rc = SCardStatus(ctx->hCard, pbReader, &dwReaderLen, &dwState, &dwProt, atr->data, (LPDWORD) &atr->len); PCSC_ERROR(ctx->reader_num, rc, "SCardStatus"); IPA_LOGP(SSCARD, LINFO, "PCSC reader #%d ATR:%s\n", ctx->reader_num, ipa_buf_hexdump(atr)); return 0; error: IPA_LOGP(SSCARD, LERROR, "PCSC reader #%d atr query failed!\n", ctx->reader_num); return -EIO; } /*! Free smartcard reader (and card). * \param[inout] scard_ctx smartcard reader context. * \returns 0 on success, -EIO on failure. */ int ipa_scard_free(void *scard_ctx) { struct scard_ctx *ctx = scard_ctx; LONG rc; if (!scard_ctx) return 0; rc = SCardDisconnect(ctx->hCard, SCARD_UNPOWER_CARD); PCSC_ERROR(ctx->reader_num, rc, "SCardDisconnect"); /* As it seems the libpcsc-lite API calls SCardReleaseContext / SCardEstablishContext leak memoroy. There is * always a remaining, still reachable amount of 1,614 bytes present when the program exits. Since we establish * the hContext only once in this implementation this problem is no matter of concern. */ rc = SCardReleaseContext(ctx->hContext); PCSC_ERROR(ctx->reader_num, rc, "SCardReleaseContext"); IPA_LOGP(SSCARD, LINFO, "PCSC reader #%d freed\n", ctx->reader_num); IPA_FREE(ctx); return 0; error: IPA_LOGP(SSCARD, LERROR, "PCSC reader #%d free failed!\n", ctx->reader_num); return -EIO; }