Initial re-upload of spice2x-24-08-24
This commit is contained in:
367
external/scard/scard.cpp
vendored
Normal file
367
external/scard/scard.cpp
vendored
Normal file
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* MIT-License
|
||||
* Copyright (c) 2018 by nolm <nolan@nolm.name>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*
|
||||
* Modified version.
|
||||
*/
|
||||
|
||||
|
||||
#include "scard.h"
|
||||
#include <windows.h>
|
||||
#include <winscard.h>
|
||||
#include <thread>
|
||||
#include "util/utils.h"
|
||||
#include "util/logging.h"
|
||||
#include "hooks/sleephook.h"
|
||||
|
||||
#define MAX_APDU_SIZE 255
|
||||
#define SCARD_POLL_INTERVAL_MS 100
|
||||
|
||||
// set to detect all cards, reduce polling rate to 500ms.
|
||||
// based off acr122u reader, see page 26 in api document.
|
||||
// https://www.acs.com.hk/en/download-manual/419/API-ACR122U-2.04.pdf
|
||||
#define PICC_OPERATING_PARAMS 0xDFu
|
||||
BYTE PICC_OPERATING_PARAM_CMD[5] = {0xFFu, 0x00u, 0x51u, PICC_OPERATING_PARAMS, 0x00u};
|
||||
|
||||
// return bytes from device
|
||||
#define PICC_SUCCESS 0x90u
|
||||
#define PICC_ERROR 0x63u
|
||||
|
||||
static const BYTE UID_CMD[5] = { 0xFFu, 0xCAu, 0x00u, 0x00u, 0x00u };
|
||||
|
||||
enum scard_atr_protocol {
|
||||
SCARD_ATR_PROTOCOL_ISO14443_PART3 = 0x03,
|
||||
SCARD_ATR_PROTOCOL_ISO15693_PART3 = 0x0B,
|
||||
SCARD_ATR_PROTOCOL_FELICA_212K = 0x11,
|
||||
SCARD_ATR_PROTOCOL_FELICA_424K = 0x12,
|
||||
};
|
||||
|
||||
volatile bool should_exit = false;
|
||||
|
||||
winscard_config_t WINSCARD_CONFIG = {};
|
||||
|
||||
void scard_update(SCARDCONTEXT hContext, LPCTSTR readerName, uint8_t unit_no) {
|
||||
|
||||
// Connect to the smart card.
|
||||
LONG lRet = 0;
|
||||
SCARDHANDLE hCard{};
|
||||
DWORD dwActiveProtocol{};
|
||||
for (int retry = 0; retry < 10; retry++) {
|
||||
if ((lRet = SCardConnect(hContext, readerName, SCARD_SHARE_EXCLUSIVE, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
|
||||
&hCard, &dwActiveProtocol)) == SCARD_S_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
Sleep(20);
|
||||
}
|
||||
|
||||
if (lRet != SCARD_S_SUCCESS) {
|
||||
log_warning("scard", "error connecting to the card: 0x{:08x}", static_cast<unsigned>(lRet));
|
||||
return;
|
||||
}
|
||||
|
||||
// set the reader params
|
||||
lRet = 0;
|
||||
auto pci = dwActiveProtocol == SCARD_PROTOCOL_T1 ? SCARD_PCI_T1 : SCARD_PCI_T0;
|
||||
DWORD cbRecv = MAX_APDU_SIZE;
|
||||
BYTE pbRecv[MAX_APDU_SIZE];
|
||||
if ((lRet = SCardTransmit(hCard, pci, PICC_OPERATING_PARAM_CMD, sizeof(PICC_OPERATING_PARAM_CMD),
|
||||
nullptr, pbRecv, &cbRecv)) != SCARD_S_SUCCESS) {
|
||||
log_warning("scard", "error setting PICC params: 0x{:08x}", static_cast<unsigned>(lRet));
|
||||
return;
|
||||
}
|
||||
|
||||
if(cbRecv > 2 && pbRecv[0] != PICC_SUCCESS && pbRecv[1] != PICC_OPERATING_PARAMS)
|
||||
{
|
||||
log_warning("scard", "PICC params not valid 0x{:02x} != 0x{:02x}",
|
||||
static_cast<unsigned>(pbRecv[1]), static_cast<unsigned>(PICC_OPERATING_PARAMS));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read ATR to determine card type.
|
||||
TCHAR szReader[200];
|
||||
DWORD cchReader = 200;
|
||||
BYTE atr[32];
|
||||
DWORD cByteAtr = 32;
|
||||
lRet = SCardStatus(hCard, szReader, &cchReader, nullptr, nullptr, atr, &cByteAtr);
|
||||
if (lRet != SCARD_S_SUCCESS) {
|
||||
log_warning("scard", "error getting card status: 0x{:08x}", static_cast<unsigned>(lRet));
|
||||
return;
|
||||
}
|
||||
|
||||
// Only care about 20-byte ATRs returned by arcade-type smart cards
|
||||
if (cByteAtr != 20) {
|
||||
log_warning("scard", "ignoring card with len(atr) = {} ({})", cByteAtr, bin2hex(atr, cByteAtr));
|
||||
return;
|
||||
}
|
||||
|
||||
//log_info("scard", "atr Return: len({}), {}", cByteAtr, bin2hex(atr, cByteAtr));
|
||||
|
||||
// Figure out if we should reverse the UID returned by the card based on the ATR protocol
|
||||
BYTE cardProtocol = atr[12];
|
||||
BOOL shouldReverseUid = false;
|
||||
if (cardProtocol == SCARD_ATR_PROTOCOL_ISO15693_PART3) {
|
||||
log_info("scard", "card protocol: ISO15693_PART3");
|
||||
shouldReverseUid = true;
|
||||
} else if (cardProtocol == SCARD_ATR_PROTOCOL_ISO14443_PART3) {
|
||||
log_info("scard", "card protocol: ISO14443_PART3");
|
||||
} else if (cardProtocol == SCARD_ATR_PROTOCOL_FELICA_212K) {
|
||||
log_info("scard", "card protocol: FELICA_212K");
|
||||
} else if (cardProtocol == SCARD_ATR_PROTOCOL_FELICA_424K) {
|
||||
log_info("scard", "card protocol: FELICA_424K");
|
||||
} else {
|
||||
log_warning("scard", "Unknown NFC Protocol: 0x{:02x}", static_cast<unsigned>(cardProtocol));
|
||||
//return;
|
||||
}
|
||||
|
||||
// Read UID
|
||||
cbRecv = MAX_APDU_SIZE;
|
||||
if ((lRet = SCardTransmit(hCard, pci, UID_CMD, sizeof(UID_CMD),nullptr, pbRecv, &cbRecv)) != SCARD_S_SUCCESS) {
|
||||
log_warning("scard", "error querying card UID: 0x{:08x}", static_cast<unsigned>(lRet));
|
||||
return;
|
||||
}
|
||||
|
||||
if (cbRecv > 1 && pbRecv[0] == PICC_ERROR) {
|
||||
log_warning("scard", "UID query failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((lRet = SCardDisconnect(hCard, SCARD_LEAVE_CARD)) != SCARD_S_SUCCESS) {
|
||||
log_info("scard", "failed SCardDisconnect: 0x{:08x}", static_cast<unsigned>(lRet));
|
||||
}
|
||||
|
||||
if (cbRecv < 8) {
|
||||
log_info("scard", "padding card uid to 8 bytes (read uid: {})", bin2hex(pbRecv, cbRecv));
|
||||
memset(&pbRecv[cbRecv], 0, 8 - cbRecv);
|
||||
} else if (cbRecv > 8) {
|
||||
log_info("scard", "taking first 8 bytes of len(uid) = {}", cbRecv);
|
||||
}
|
||||
|
||||
// Copy UID to struct, reversing if necessary
|
||||
card_info_t card_info;
|
||||
if (shouldReverseUid) {
|
||||
for (DWORD i = 0; i < 8; i++) {
|
||||
card_info.uid[i] = pbRecv[7 - i];
|
||||
}
|
||||
} else {
|
||||
memcpy(card_info.uid, pbRecv, 8);
|
||||
}
|
||||
log_info("scard", "reader unit no: {}, read card: {}", unit_no, bin2hex(card_info.uid, 8));
|
||||
|
||||
// set card type to 1
|
||||
card_info.card_type = 1;
|
||||
|
||||
// update toggle
|
||||
bool flip_order = WINSCARD_CONFIG.flip_order;
|
||||
if (WINSCARD_CONFIG.flip_order) {
|
||||
log_info("scard", "Flip order of readers since -scardflip option is set");
|
||||
}
|
||||
if (WINSCARD_CONFIG.toggle_order && (GetKeyState(VK_NUMLOCK) & 1) > 0) {
|
||||
log_info("scard", "Flip order of readers since Num Lock is on");
|
||||
flip_order = !flip_order;
|
||||
}
|
||||
|
||||
// callback
|
||||
if (WINSCARD_CONFIG.cardinfo_callback) {
|
||||
WINSCARD_CONFIG.cardinfo_callback(flip_order ? ~unit_no : unit_no, &card_info);
|
||||
}
|
||||
}
|
||||
|
||||
void scard_clear(uint8_t unitNo) {
|
||||
|
||||
// reset
|
||||
card_info_t empty_cardinfo{};
|
||||
|
||||
// callback
|
||||
if (WINSCARD_CONFIG.cardinfo_callback)
|
||||
WINSCARD_CONFIG.cardinfo_callback(WINSCARD_CONFIG.flip_order ? ~unitNo : unitNo, &empty_cardinfo);
|
||||
}
|
||||
|
||||
void scard_loop(SCARDCONTEXT hContext, LPCTSTR slot0_reader_name, LPCTSTR slot1_reader_name, uint8_t reader_count) {
|
||||
LONG lRet = 0;
|
||||
SCARD_READERSTATE reader_states[2] = {
|
||||
{
|
||||
.szReader = slot0_reader_name,
|
||||
.pvUserData = nullptr,
|
||||
.dwCurrentState = SCARD_STATE_UNAWARE,
|
||||
.dwEventState = 0,
|
||||
.cbAtr = 0,
|
||||
.rgbAtr = { 0 },
|
||||
},
|
||||
{
|
||||
.szReader = slot1_reader_name,
|
||||
.pvUserData = nullptr,
|
||||
.dwCurrentState = SCARD_STATE_UNAWARE,
|
||||
.dwEventState = 0,
|
||||
.cbAtr = 0,
|
||||
.rgbAtr = { 0 },
|
||||
},
|
||||
};
|
||||
|
||||
if (reader_count < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (!should_exit) {
|
||||
lRet = SCardGetStatusChange(hContext, SCARD_POLL_INTERVAL_MS, reader_states, reader_count);
|
||||
if (lRet == SCARD_E_TIMEOUT) {
|
||||
continue;
|
||||
} else if (lRet != SCARD_S_SUCCESS) {
|
||||
log_warning("scard", "failed SCardGetStatusChange: 0x{:08x}", static_cast<unsigned>(lRet));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (uint8_t unit_no = 0; unit_no < reader_count; unit_no++) {
|
||||
if (!(reader_states[unit_no].dwEventState & SCARD_STATE_CHANGED)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DWORD newState = reader_states[unit_no].dwEventState ^ SCARD_STATE_CHANGED;
|
||||
bool wasCardPresent = (reader_states[unit_no].dwCurrentState & SCARD_STATE_PRESENT) > 0;
|
||||
if (newState & SCARD_STATE_UNAVAILABLE) {
|
||||
log_info("scard", "new card state: unavailable");
|
||||
Sleep(SCARD_POLL_INTERVAL_MS);
|
||||
} else if (newState & SCARD_STATE_EMPTY) {
|
||||
log_info("scard", "new card state: empty");
|
||||
scard_clear(unit_no);
|
||||
} else if (newState & SCARD_STATE_PRESENT && !wasCardPresent) {
|
||||
log_info("scard", "new card state: present");
|
||||
scard_update(hContext, reader_states[unit_no].szReader, unit_no);
|
||||
}
|
||||
|
||||
reader_states[unit_no].dwCurrentState = reader_states[unit_no].dwEventState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int scard_threadmain() {
|
||||
LONG lRet = 0;
|
||||
SCARDCONTEXT hContext = 0;
|
||||
|
||||
if ((lRet = SCardEstablishContext(SCARD_SCOPE_USER, nullptr, nullptr, &hContext)) != SCARD_S_SUCCESS) {
|
||||
log_warning("scard", "failed to establish SCard context: {}", bin2hex(&lRet, sizeof(LONG)));
|
||||
return lRet;
|
||||
}
|
||||
|
||||
LPCTSTR reader = nullptr;
|
||||
LPTSTR reader_name_slots[2] = {nullptr, nullptr };
|
||||
int readerNameLen = 0;
|
||||
while (!(reader_name_slots[0] || reader_name_slots[1])) {
|
||||
|
||||
// get list of readers
|
||||
LPTSTR reader_list = nullptr;
|
||||
auto pcchReaders = SCARD_AUTOALLOCATE;
|
||||
lRet = SCardListReaders(hContext, nullptr, (LPTSTR) &reader_list, &pcchReaders);
|
||||
|
||||
int slot0_idx = -1;
|
||||
int slot1_idx = -1;
|
||||
int readerCount = 0;
|
||||
switch (lRet) {
|
||||
case SCARD_E_NO_READERS_AVAILABLE:
|
||||
log_info("scard", "no readers available, waiting 1000ms...");
|
||||
Sleep(1000);
|
||||
break;
|
||||
|
||||
case SCARD_S_SUCCESS:
|
||||
|
||||
// So WinAPI has this terrible "multi-string" concept wherein you have a list
|
||||
// of null-terminated strings, terminated by a double-null.
|
||||
for (reader = reader_list; *reader; reader = reader + lstrlen(reader) + 1) {
|
||||
log_info("scard", "found reader: {}", reader);
|
||||
if (WINSCARD_CONFIG.slot0_reader_name && !lstrcmp(WINSCARD_CONFIG.slot0_reader_name, reader))
|
||||
slot0_idx = readerCount;
|
||||
if (WINSCARD_CONFIG.slot1_reader_name && !lstrcmp(WINSCARD_CONFIG.slot1_reader_name, reader))
|
||||
slot1_idx = readerCount;
|
||||
readerCount++;
|
||||
}
|
||||
|
||||
// If we have at least two readers, assign readers to slots as necessary.
|
||||
if (readerCount >= 2) {
|
||||
if (slot1_idx != 0)
|
||||
slot0_idx = 0;
|
||||
if (slot0_idx != 1)
|
||||
slot1_idx = 1;
|
||||
}
|
||||
|
||||
// if the reader count is 1 and no reader was set, set first reader
|
||||
if (readerCount == 1 && slot0_idx < 0 && slot1_idx < 0)
|
||||
slot0_idx = 0;
|
||||
|
||||
// If we somehow only found slot 1, promote slot 1 to slot 0.
|
||||
if (slot0_idx < 0 && slot1_idx >= 0) {
|
||||
slot0_idx = slot1_idx;
|
||||
slot1_idx = -1;
|
||||
}
|
||||
|
||||
// Extract the relevant names from the multi-string.
|
||||
int i;
|
||||
for (i = 0, reader = reader_list; *reader; reader = reader + lstrlen(reader) + 1, i++) {
|
||||
if (slot0_idx == i) {
|
||||
readerNameLen = lstrlen(reader);
|
||||
reader_name_slots[0] = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS,
|
||||
sizeof(TCHAR) * (readerNameLen + 1));
|
||||
memcpy(reader_name_slots[0], &reader[0], (size_t) (readerNameLen + 1));
|
||||
}
|
||||
if (slot1_idx == i) {
|
||||
readerNameLen = lstrlen(reader);
|
||||
reader_name_slots[1] = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS,
|
||||
sizeof(TCHAR) * (readerNameLen + 1));
|
||||
memcpy(reader_name_slots[1], &reader[0], (size_t) (readerNameLen + 1));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
log_warning("scard", "failed SCardListReaders: 0x{:08x}", lRet);
|
||||
Sleep(5000);
|
||||
break;
|
||||
}
|
||||
|
||||
if (reader_list) {
|
||||
SCardFreeMemory(hContext, reader_list);
|
||||
}
|
||||
}
|
||||
|
||||
if (reader_name_slots[0]) {
|
||||
log_info("scard", "using reader slot 0: {}", reader_name_slots[0]);
|
||||
}
|
||||
if (reader_name_slots[1]) {
|
||||
log_info("scard", "using reader slot 1: {}", reader_name_slots[0]);
|
||||
}
|
||||
|
||||
scard_loop(hContext, reader_name_slots[0], reader_name_slots[1], (uint8_t) (reader_name_slots[1] ? 2 : 1));
|
||||
|
||||
if (reader_name_slots[0]) {
|
||||
HeapFree(GetProcessHeap(), 0, reader_name_slots[0]);
|
||||
}
|
||||
if (reader_name_slots[1]) {
|
||||
HeapFree(GetProcessHeap(), 0, reader_name_slots[1]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void scard_threadstart() {
|
||||
std::thread t(scard_threadmain);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
void scard_fini() {
|
||||
should_exit = true;
|
||||
}
|
||||
82
external/scard/scard.h
vendored
Normal file
82
external/scard/scard.h
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* MIT-License
|
||||
* Copyright (c) 2018 by nolm <nolan@nolm.name>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*
|
||||
* Modified version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// cardinfo_t is a description of a card that was presented to a reader
|
||||
typedef struct card_info {
|
||||
int card_type = 0;
|
||||
uint8_t uid[8];
|
||||
} card_info_t;
|
||||
|
||||
/*
|
||||
* cardinfo_callback_t is a pointer to a function that will be called on card present.
|
||||
*
|
||||
* The provided cardinfo_t pointer will become invalid after the callback returns, so
|
||||
* any data contained within should be copied into more permanent storage if necessary.
|
||||
*/
|
||||
typedef void (*cardinfo_callback_t)(uint8_t slot_no, card_info_t *cardinfo);
|
||||
|
||||
// winscard_config_t describes the parameters used to configure the smart card listener.
|
||||
typedef struct {
|
||||
|
||||
// A pointer to a null-terminated string that specifies the name of the reader used for
|
||||
// the first slot, according to SCardListReaders.
|
||||
//
|
||||
// If this is NULL or a zero-length string, the first reader found will be used.
|
||||
const char *slot0_reader_name;
|
||||
|
||||
// A pointer to a null-terminated string that specifies the name of the reader used for
|
||||
// the second slot, according to SCardListReaders. If only this slot is set, or the reader
|
||||
// specified for the first slot is not found, this reader will become the second slot.
|
||||
//
|
||||
// If this is NULL or a zero-length string, the second reader found will be used, if any.
|
||||
const char *slot1_reader_name;
|
||||
|
||||
// A pointer to a function that will be called when a card state change event has occurred.
|
||||
//
|
||||
// If a card has been presented, cardinfo_t.card_type will be set accordingly. If a card has
|
||||
// been removed, then card_type will be 0, and the value of uid is undefined.
|
||||
cardinfo_callback_t cardinfo_callback;
|
||||
|
||||
// Flips the order of the card insertions for P1/P2
|
||||
bool flip_order;
|
||||
|
||||
// For toggling the order using NUMLOCK
|
||||
bool toggle_order;
|
||||
|
||||
} winscard_config_t;
|
||||
extern winscard_config_t WINSCARD_CONFIG;
|
||||
|
||||
/* scard_threadmain is the entry point for smart card listener thread. */
|
||||
int scard_threadmain();
|
||||
|
||||
/* start threadmain in a separate thread */
|
||||
void scard_threadstart();
|
||||
|
||||
/* scard_fini signals to terminate and clean up smart card listener routine. */
|
||||
void scard_fini();
|
||||
Reference in New Issue
Block a user