diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | libyhy.c | 288 | ||||
-rw-r--r-- | libyhy.h | 24 | ||||
-rw-r--r-- | yhy_test.c | 96 |
5 files changed, 432 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8737657 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.so +*.swp +yhy_test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5c5268a --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +.PHONY: all clean + +CFLAGS = -g -Wall -Wpedantic + +%.so: CFLAGS += -shared -fPIC +%.debug.so: CFLAGS += -shared -fPIC -DDEBUG + +%.so :: %.c %.h + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS) + +%.debug.so :: %.c %.h + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS) + +yhy_test: LDLIBS = -lyhy.debug +yhy_test: LDFLAGS = -L. -Wl,-rpath . + +all: libyhy.so libyhy.debug.so yhy_test + +clean: + $(RM) libyhy.so + $(RM) yhy_test diff --git a/libyhy.c b/libyhy.c new file mode 100644 index 0000000..041b0b7 --- /dev/null +++ b/libyhy.c @@ -0,0 +1,288 @@ +#include "libyhy.h" +#include <stdint.h> +#include <fcntl.h> +#include <termios.h> +#include <unistd.h> +#include <string.h> + +#include <stdio.h> +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#ifdef DEBUG + #define DBG(x) (x) +#else + #define DBG(x) +#endif + +#define DEFAULT_NODE_ID 0 + +//TODO +//0x AA substitution where applicable +//Confirm checksum on receive +//Handle status flags on return + +#pragma pack(push, 1) +typedef struct { + uint16_t magic; //0xAABB + uint16_t csum_span; + uint16_t node; + uint16_t command; +} yhy_transmit_t; + +typedef struct { + uint16_t magic; //0xAABB + uint16_t csum_span; + uint16_t node; + uint16_t command; + uint8_t status; //0 on success +} yhy_response_t; +#pragma pack(pop) + +enum /*_yhy_command*/ { + YHY_SET_BAUDRATE = 0x0101, + /* uint8_t baudrate_id + * 0 -> 4800 + * 1 -> 9600 + * 2 -> 14400 + * 3 -> 19200 + * 4 -> 38800 + * 5 -> 38400 + * 6 -> 57600 + * 7 -> 115200 + */ + YHY_SET_NODEID = 0x0102, + /* uint16_t nodeid */ + YHY_GET_NODEID = 0x0103, + /* No payload */ + YHY_READ_VERSION = 0x0104, + /* No payload */ + YHY_BEEP = 0x0106, + /* uint8_t beeptime in 10ms increments */ + YHY_LED = 0x0107, + /* uint8_t LED status bitfield + * Bit 0 -> Green LED + * Bit 1 -> Red LED + */ + YHY_ANTENNA = 0x010C, + /* uint8_t anntenna state + * * 0 -> Antenna off + * * 1 -> Antenna on + */ + YHY_MIFARE_REQUEST = 0x0201, + /* uint8_t command + * * 0x52 -> Wake up all cards (WUPA) + * * 0x26 -> Request idle cards (REQA) + */ + YHY_MIFARE_ANTICOLL = 0x0202, + /* No payload */ + YHY_MIFARE_SELECT = 0x0203, + /* uint32_t Card UID */ + YHY_MIFARE_HLTA = 0x0204, + /* No payload */ + YHY_MIFARE_AUTH = 0x0207, + /* uint8_t auth_mode + * * 0x60 -> Key A + * * 0x61 -> Key B + * uint8_t block + * uint8_t[6] key + */ + YHY_MIFARE_READ = 0x0208, + /* Payload uint8_t block */ + YHY_MIFARE_WRITE = 0x0209, + /* uint8_t block + * uint8_t[16] data */ + YHY_ULTRALIGHT_SELECT = 0x0212, + /* No payload */ + YHY_ULTRALIGHT_WRITE = 0x0213 + /* uint8_t page + * uint8_t[4] data */ +}; + +static void xxd(uint8_t* buf, size_t len){ + size_t n; + for(n = 0; n < len; n++){ + printf("%02X ", buf[n]); + } +} + +static ssize_t yhy_send_command(int fd, uint16_t node, uint16_t cmd, uint8_t* payload, size_t len){ + size_t n; + uint8_t checksum = 0x00; + yhy_transmit_t header = { + .magic = htobe16(0xAABB), + .csum_span = htole16(len + 5), + .node = node, + .command = htole16(cmd) + }; + + for(n = 4; n < sizeof(header); n++){ + checksum ^= ((uint8_t*)(&header))[n]; + } + + for(n = 0; n < len; n++){ + checksum ^= payload[n]; + } + + DBG(printf("Out: ")); + DBG(xxd((uint8_t*) &header, sizeof(header))); + DBG(xxd(payload, len)); + DBG(xxd(&checksum, 1)); + DBG(printf("\n")); + + write(fd, &header, sizeof(header)); + if(len){ + write(fd, payload, len); + } + write(fd, &checksum, 1); + return 0; +} + +static ssize_t yhy_receive_response(int fd, uint8_t* response, size_t max_len){ + //maximum received data is probably firmware version, assume 1024 bytes + uint8_t recv_buffer[1024] = ""; + yhy_response_t* resp = (yhy_response_t*) recv_buffer; + read(fd, recv_buffer, sizeof(yhy_response_t)); + + //UL_SELECT returns wrong length from device + if(resp->command == YHY_ULTRALIGHT_SELECT && resp->status == 0){ + DBG(printf("Fixing UL_SELECT response length (was %d)\n", le16toh(resp->csum_span))); + resp->csum_span = htole16(0x0D); + } + + uint16_t bytes_left = le16toh(resp->csum_span) - 5; + read(fd, recv_buffer + sizeof(yhy_response_t), bytes_left); + + DBG(printf("In: ")); + DBG(xxd(recv_buffer, sizeof(yhy_response_t))); + DBG(printf(" | ")); + DBG(xxd(recv_buffer + sizeof(yhy_response_t), bytes_left)); + DBG(printf("\n")); + + if(bytes_left > 1 && response && max_len){ + memcpy(response, recv_buffer + sizeof(yhy_response_t), MIN(bytes_left - 1, max_len)); + } + return bytes_left - 1; +} + +static ssize_t yhy_sync(int fd, uint16_t node, uint16_t cmd, uint8_t* payload, size_t payload_len, uint8_t* response, size_t response_len){ + yhy_send_command(fd, node, cmd, payload, payload_len); + return yhy_receive_response(fd, response, response_len); +} + +int yhy_open(char* device){ + struct termios tty; + + int fd = open(device, O_RDWR); + if(fd < 0) { + return -1; + } + + if(tcgetattr(fd, &tty)){ + return -1; + } + + cfsetospeed(&tty, B115200); + cfsetispeed(&tty, B115200); + + tty.c_iflag &= ~(IXON | IXOFF | IXANY); + tty.c_cflag |= (CLOCAL | CREAD); + tty.c_cflag &= ~PARENB; + tty.c_cflag &= ~CSTOPB; + tty.c_cflag &= ~CSIZE; + tty.c_cflag &= ~CRTSCTS; + tty.c_cflag |= CS8; + tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + + if(tcsetattr(fd, TCSAFLUSH, &tty)){ + return -1; + } + + return fd; +} + +int yhy_sync_read_version(int fd, char* version, size_t max_len){ + ssize_t bytes = yhy_sync(fd, DEFAULT_NODE_ID, YHY_READ_VERSION, NULL, 0, (uint8_t*) version, max_len); + if(bytes >= max_len){ + version[max_len] = 0; + } + else{ + version[bytes] = 0; + } + return bytes; +} + +int yhy_sync_beep(int fd, uint8_t time){ + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_BEEP, &time, 1, NULL, 0); +} + +int yhy_sync_led(int fd, uint8_t led_bits){ + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_LED, &led_bits, 1, NULL, 0); +} + +int yhy_sync_antenna(int fd, uint8_t enable){ + uint8_t status = enable ? 1 : 0; + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_ANTENNA, &status, 1, NULL, 0); +} + +int yhy_sync_request(int fd, uint8_t wakeup, uint16_t* atqa){ + *atqa = 0; + + //Select WUPA or REQA + uint8_t wup = wakeup ? 0x52 : 0x26; + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_MIFARE_REQUEST, &wup, 1, (uint8_t*) atqa, 2); +} + +int yhy_sync_anticoll(int fd, uint8_t* serial, size_t length){ + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_MIFARE_ANTICOLL, NULL, 0, serial, length); +} + +int yhy_sync_select(int fd, uint8_t* serial, size_t length, uint8_t* sak){ + *sak = 0; + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_MIFARE_SELECT, serial, length, sak, 1); +} + +int yhy_sync_hlta(int fd){ + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_MIFARE_HLTA, NULL, 0, NULL, 0); +} + +int yhy_sync_auth(int fd, uint8_t key_a, uint8_t block, uint8_t key[6]){ + struct { + uint8_t mode; + uint8_t block; + uint8_t key[6]; + } auth_payload = { + .mode = (key_a ? 0x60 : 0x61), + .block = block + }; + memcpy(&(auth_payload.key), key, 6); + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_MIFARE_AUTH, (uint8_t*) (&auth_payload), sizeof(auth_payload), NULL, 0); +} + +int yhy_sync_read(int fd, uint8_t block, uint8_t* data, size_t max_len){ + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_MIFARE_READ, &block, 1, data, max_len); +} + +int yhy_sync_write(int fd, uint8_t block, uint8_t data[16]){ + struct { + uint8_t block; + uint8_t data[16]; + } write_payload = { + .block = block + }; + memcpy(&(write_payload.data), data, 16); + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_MIFARE_WRITE, (uint8_t*) (&write_payload), sizeof(write_payload), NULL, 0); +} + +int yhy_sync_ulselect(int fd, uint8_t* serial, size_t length){ + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_ULTRALIGHT_SELECT, NULL, 0, serial, length); +} + +int yhy_sync_ulwrite(int fd, uint8_t block, uint8_t data[4]){ + struct { + uint8_t block; + uint8_t data[4]; + } write_payload = { + .block = block + }; + memcpy(&(write_payload.data), data, 4); + return yhy_sync(fd, DEFAULT_NODE_ID, YHY_ULTRALIGHT_WRITE, (uint8_t*) (&write_payload), sizeof(write_payload), NULL, 0); +} diff --git a/libyhy.h b/libyhy.h new file mode 100644 index 0000000..2926e81 --- /dev/null +++ b/libyhy.h @@ -0,0 +1,24 @@ +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> + +/* Open a device port */ +int yhy_open(char* device); + +/* Synchronous API */ +/* These calls will return data directly, at the cost of blocking execution */ +int yhy_sync_read_version(int fd, char* version, size_t allocated); +int yhy_sync_beep(int fd, uint8_t time); +int yhy_sync_led(int fd, uint8_t led_bits); +int yhy_sync_antenna(int fd, uint8_t enable); + +/* Mifare commands */ +int yhy_sync_request(int fd, uint8_t wakeup, uint16_t* atqa); +int yhy_sync_anticoll(int fd, uint8_t* serial, size_t length); +int yhy_sync_select(int fd, uint8_t* serial, size_t length, uint8_t* sak); +int yhy_sync_hlta(int fd); +int yhy_sync_auth(int fd, uint8_t key_a, uint8_t block, uint8_t key[6]); +int yhy_sync_read(int fd, uint8_t block, uint8_t* data, size_t max_len); +int yhy_sync_write(int fd, uint8_t block, uint8_t data[16]); +int yhy_sync_ulselect(int fd, uint8_t* serial, size_t length); +int yhy_sync_ulwrite(int fd, uint8_t block, uint8_t data[4]); diff --git a/yhy_test.c b/yhy_test.c new file mode 100644 index 0000000..65cfa4a --- /dev/null +++ b/yhy_test.c @@ -0,0 +1,96 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include "libyhy.h" + +#define ATQA_UIDSIZE_BITS(x) ((x & 0x00C0) >> 6) +#define UID_CASCADE_TAG 0x88 +#define SAK_UID_INCOMPLETE(x) (x & 0x04) + +int main(int argc, char** argv){ + size_t n; + uint8_t test_block = 4; + int fd = yhy_open("/dev/ttyUSB0"); + + printf("YHY Test: %d\n", fd); + yhy_sync_led(fd, 3); + //yhy_sync_beep(fd, 0x64); + + printf("Reading version\n"); + char version[512]; + yhy_sync_read_version(fd, version, 512); + printf("Version %s\n\n", version); + + size_t bytes; + uint16_t atqa = 0; + uint8_t sak = 0; + uint8_t serial[10] = ""; + + //iterate cards in field + printf("Scanning field\n\n"); + + for(yhy_sync_request(fd, 1, &atqa); atqa; yhy_sync_request(fd, 0, &atqa)){ + printf("ATQA %04X\n", atqa); + //ATQA and SAK should not be used for identifying PICCs. + //However, we don't have lower-level access to the APDUs + printf("ATQA UID size: %d\n\n", ATQA_UIDSIZE_BITS(atqa)); + + if(ATQA_UIDSIZE_BITS(atqa) == 1){ + printf("Based on ATQA, running UL select\n"); + bytes = yhy_sync_ulselect(fd, serial, sizeof(serial)); + printf("Received %lu bytes\n", bytes); + } + else{ + printf("Performing Anticollision (Cascade Level 1)\n"); + bytes = yhy_sync_anticoll(fd, serial, sizeof(serial)); + printf("UID %lu bytes, %02X %02X %02X %02X\n", bytes, + serial[0], + serial[1], + serial[2], + serial[3]); + + if(serial[0] == UID_CASCADE_TAG){ + printf("UID has CT prefix, UID is yet incomplete\n"); + } + + printf("\nSelecting (Cascade Level 1)\n"); + yhy_sync_select(fd, serial, 4, &sak); + printf("SAK CL1 %02X\n", sak); + if(SAK_UID_INCOMPLETE(sak)){ + printf("SAK CL1 indicates incomplete UID\n"); + } + } + + printf("\nHalting card\n"); + yhy_sync_hlta(fd); + } + + printf("\nAll PICCs enumerated\n"); + + /* + printf("Authenticating for block %d\n", test_block); + uint8_t key[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + yhy_sync_auth(fd, 1, test_block, key); + printf("\n"); + + printf("Reading block %d\n", test_block); + uint8_t data[16] = {0}; + yhy_sync_read(fd, test_block, data, 16); + printf("Block %d data: ", test_block); + for(n = 0; n < 16; n++){ + printf("%02x ", data[n]); + } + printf("\n\n"); + + printf("Incrementing all bytes in block %d\n", test_block); + for(n = 0; n < 16; n++){ + data[n]++; + } + + printf("Writing block %d\n", test_block); + yhy_sync_write(fd, test_block, data); + printf("\n"); + */ + + close(fd); +} |