Подарили мне как то 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.