Подарили мне как то YubiKey 5C Nano. Попользовался пару дней и захотелось автоматизировать работу с ним. Поискал похожие решения по автоматизации, нашел только статью, но в ней мне не понравилось, что используется скорее механическое, чем электрическое решение.
![YubiKey 5C Nano YubiKey 5C Nano](https://habrastorage.org/getpro/habr/upload_files/07a/1e2/911/07a1e2911d05f42a1d69943df5762cad.jpg)
Внешняя активация YubiKey
Площадка, которая отвечает за активацию YubiKey, металлическая. Первые попытки активировать не пальцем были с помощью поднесения ложки, которую я держал через тряпку для изоляции. Попытки оказались успешными. У меня был в наличии микроконтроллер ESP32. Я попробовал присоединить GPIO-вывод к YubiKey напрямую и менять вывод с высокого уровня на низкий уровень, но YubiKey от такого не активировался. После этого подумал, что нужно подключать не напрямую, а через реле.
Было куплено реле, управляемое через ESP32. Зажим крокодил для проводов из строительного магазина и была найдена тонкая и короткая, скорее всего медная, проволока.
![Реле Реле](https://habrastorage.org/getpro/habr/upload_files/54a/4d0/4ea/54a4d04ea509d24db1fe3ac4e1f16ba9.png)
![Зажим крокодил и проволока Зажим крокодил и проволока](https://habrastorage.org/getpro/habr/upload_files/5b9/bfe/2b4/5b9bfe2b49c17d949e99579a73d21dca.png)
Пришлось немного подбирать длину проволоки, потому что если она слишком длинная, при активации реле, YubiKey не активировался. В моём случае проволока была примерно 2 см.
Активация реле
Реле активируется за счет ESP32. А запрос на активацию на ESP32 отправляется по UART. Была написана простая программа для ESP32, которая ждет получения определенного символа по UART. И если он пришел, отправляет этот же символ обратно по UART, активирует реле и через пол секунды, деактивирует.
Код программы для ESP32
int incomingByte = 0; #define RELAY_IN 5 void setup() { Serial.begin(9600); pinMode(RELAY_IN, OUTPUT); } void loop() { if (Serial.available() > 0) { incomingByte = Serial.read(); if (incomingByte == 'k') { Serial.print((char)incomingByte); Serial.print('\n'); delay(500); digitalWrite(RELAY_IN, HIGH); delay(500); digitalWrite(RELAY_IN, LOW); } } }
Код компилировал в Arduino IDE.
Получение данных с YubiKey
YubiKey работает как HID устройство. Напрямую данные с HID устройства можно считывать с /dev/input/
. Необходимо найти нужный event
, мне не пришлось это делать, так как у меня было подключено только одно HID устройство. С /dev/input/event0
можно работать как с обычным файлом через open
, read
. Но лежат там не считанные символы, а event структуры, в которых лежат коды событий, которые можно не сложно перевести в символы. Массив для перевода лежит в keys
.
Код функции для считывания данных с YubiKey
void usb_read_token(char* token) { char* usb_dev = getenv("USB_DEV"); struct pollfd fds[1]; fds[0].fd = open(usb_dev, O_RDONLY | O_NONBLOCK); fds[0].events = POLLIN; if (fds[0].fd < 0) { printf("Error unable open for reading '%s'\n", usb_dev); exit(1); } char keys[100] = { 0, 1, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 12, 13, 14, 15, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 26, 27, 28, 29, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 39, 40, 41, 42, 43, 'z', 'x', 'c', 'v', 'b', 'n', 'm' }; struct input_event ev; int char_count = 0; char* pass = getenv("PASS"); sprintf(token, "%s", pass); while (true) { int timeout_ms = 1000; int ret = poll(fds, 1, timeout_ms); if (ret > 0) { if (fds[0].revents) { ssize_t r = read(fds[0].fd, &ev, sizeof(ev)); if (r < 0) { printf("Error %d\n", (int)r); break; } else { if (ev.type == 1 && ev.value == 1) { sprintf(token + strlen(token), "%c", keys[ev.code]); char_count++; if (char_count >= 44) { sprintf(token + strlen(token), "\n"); break; } } } } } } close(fds[0].fd); }
UART общение с ESP32
Общаться с ESP32 можно через /dev/ttyUSB0
. Необходимо выставить параметры через tcgetattr
. Программа отправляет определенный символ, считывает символ и проверяет на совпадение его с отправленным.
Код функции для общения с ESP32
void serial_send_command() { char* serial_dev = getenv("SERIAL_DEV"); const char check_sym = 'k'; int serial_port = open(serial_dev, O_RDWR); struct termios tty; if (tcgetattr(serial_port, &tty) != 0) { printf("Error %i from tcgetattr: %s\n", errno, strerror(errno)); exit(1); } tty.c_cflag &= ~PARENB; tty.c_cflag &= ~CSTOPB; tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; tty.c_cflag &= ~CRTSCTS; tty.c_cflag |= CREAD | CLOCAL; tty.c_lflag &= ~ICANON; tty.c_lflag &= ~ECHO; tty.c_lflag &= ~ECHOE; tty.c_lflag &= ~ECHONL; tty.c_lflag &= ~ISIG; tty.c_iflag &= ~(IXON | IXOFF | IXANY); tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); tty.c_oflag &= ~OPOST; tty.c_oflag &= ~ONLCR; tty.c_cc[VTIME] = 10; tty.c_cc[VMIN] = 0; cfsetispeed(&tty, B9600); cfsetospeed(&tty, B9600); if (tcsetattr(serial_port, TCSANOW, &tty) != 0) { printf("Error %i from tcsetattr: %s\n", errno, strerror(errno)); exit(1); } unsigned char msg[] = { check_sym, '\r' }; write(serial_port, msg, sizeof(msg)); char read_buf[256]; memset(&read_buf, '\0', sizeof(read_buf)); int num_bytes = read(serial_port, &read_buf, sizeof(read_buf)); if (num_bytes < 0) { printf("Error reading: %s", strerror(errno)); exit(1); } if (read_buf[0] != check_sym) { printf("Wrong read data\n"); printf("Read %i bytes. Received message: %s", num_bytes, read_buf); exit(1); } close(serial_port); }
Сервер для отдачи токена с YubiKey
Был написан простой TCP сервер, который на любой коннект отправляет пароль и токен. Проще всего делать запрос на него с помощью nc $ip $port
.
Код TCP сервера
int main(int argc, char* argv[]) { int sockfd, connfd; struct sockaddr_in servaddr, cli; char token[100]; uint32_t cli_len = sizeof(cli); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { printf("socket creation failed...\n"); exit(0); } else { printf("Socket successfully created..\n"); } bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(atoi(getenv("PORT"))); if ((bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) != 0) { printf("socket bind failed...\n"); exit(0); } else { printf("Socket successfully binded..\n"); } if ((listen(sockfd, 5)) != 0) { printf("Listen failed...\n"); exit(0); } else { printf("Server listening..\n"); } while (1) { connfd = accept(sockfd, (struct sockaddr*)&cli, &cli_len); if (connfd < 0) { printf("server accept failed...\n"); exit(0); } else { printf("server accept the client...\n"); } serial_send_command(); usb_read_token(token); write(connfd, token, strlen(token)); close(connfd); fflush(stdout); } close(sockfd); return 0; }
Служба для OpenWRT
Была написана простая служба для OpenWRT, которая считывает строки из файла, и создает переменные окружения.
Код службы
#!/bin/sh /etc/rc.common # Copyright (C) 2010-2015 OpenWrt.org START=99 USE_PROCD=1 PROG=/usr/bin/yubikey-hack start_service() { procd_open_instance procd_set_param command "$PROG" procd_set_param respawn procd_set_param stdout 1 file=$(cat /etc/yubikey-hack/env) for line in $file; do procd_append_param env "$line" done procd_close_instance }
Программа считывает из /etc/yubikey-hack/env
четыре переменных и кладет их в окружение. Путь до устройства ESP32, путь до устройства YubiKey, пароль, который вписывать перед токеном и порт сервера.
SERIAL_DEV=/dev/ttyUSB0 USB_DEV=/dev/input/event0 PASS= PORT=
Итоговая конфигурация
![Комплекс целиком Комплекс целиком](https://habrastorage.org/getpro/habr/upload_files/42b/053/03e/42b05303e9c9458d88de667a8933c23c.jpg)
Зажим крокодил держится прочно и перестановки комплекса не влияло на его работу. Для удобства комплекс подключается к USB HUB, а он уже подключается к роутеру. Решение без сбоев и проблем работает у меня уже месяц.
Код лежит на репозиториях yubikey-hack и yubikey-hack-openwrt-package.
ссылка на оригинал статьи https://habr.com/ru/articles/825858/
Добавить комментарий