Как стать автором
Обновить

Послушный YubiKey

Уровень сложностиСредний
Время на прочтение5 мин
Количество просмотров4.7K

Подарили мне как то YubiKey 5C Nano. Попользовался пару дней и захотелось автоматизировать работу с ним. Поискал похожие решения по автоматизации, нашел только статью, но в ней мне не понравилось, что используется скорее механическое, чем электрическое решение.

YubiKey 5C Nano
YubiKey 5C Nano

Внешняя активация YubiKey

Площадка, которая отвечает за активацию YubiKey, металлическая. Первые попытки активировать не пальцем были с помощью поднесения ложки, которую я держал через тряпку для изоляции. Попытки оказались успешными. У меня был в наличии микроконтроллер ESP32. Я попробовал присоединить GPIO-вывод к YubiKey напрямую и менять вывод с высокого уровня на низкий уровень, но YubiKey от такого не активировался. После этого подумал, что нужно подключать не напрямую, а через реле.

Было куплено реле, управляемое через ESP32. Зажим крокодил для проводов из строительного магазина и была найдена тонкая и короткая, скорее всего медная, проволока.

Реле
Реле
Зажим крокодил и проволока
Зажим крокодил и проволока

Пришлось немного подбирать длину проволоки, потому что если она слишком длинная, при активации реле, 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=

Итоговая конфигурация

Комплекс целиком
Комплекс целиком

Зажим крокодил держится прочно и перестановки комплекса не влияло на его работу. Для удобства комплекс подключается к USB HUB, а он уже подключается к роутеру. Решение без сбоев и проблем работает у меня уже месяц.

Код лежит на репозиториях yubikey-hack и yubikey-hack-openwrt-package.

Теги:
Хабы:
Всего голосов 4: ↑3 и ↓1+4
Комментарии14

Публикации

Истории

Работа

Программист С
42 вакансии

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань