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

Комментарии (14)


  1. dabar347
    01.07.2024 12:48
    +2

    Но ведь вся суть этой кнопки, что на нее человек сам нажимает. Зачем это всё


    1. kt97679
      01.07.2024 12:48

      Полезно для автоматизации некоторых сценариев.


      1. 0mogol0
        01.07.2024 12:48
        +1

        ну примерно так же, как бумажка с паролем на мониторе...

        Если нужна автоматизация - нужно подбирать другое решение.


        1. kt97679
          01.07.2024 12:48

          Работодатель дает вам yubikey и в начале рабочего дня вам нужно несколько раз коснуться его для настройки рабочей среды. Другого решения нет и не будет. Можно каждый день выполнять рутинную процедуру, а можно ее автоматизировать.


          1. 0mogol0
            01.07.2024 12:48
            +2

            Другого решения нет и не будет. Можно каждый день выполнять рутинную процедуру, а можно ее автоматизировать.

            Ну я и говорю, бумажка с паролем...
            Работодатель вводит сложные многобуквенные и цифровые пароли, которые к тому же надо регулярно менять, чтобы повысить защищенность системы. Недовольные этим пользователи записывают пароль на бумажке и клеют её к монитору.

            Результат: в сети используются сложные пароли, которые знает любой, кто имеет физ. доступ к рабочему месту.


            1. kt97679
              01.07.2024 12:48
              +1

              Объясните, пожалуйста, каким образом автоматическое касание yubikey создает дыру в безопасности, сравнимую с паролем на мониторе?


              1. vlad49
                01.07.2024 12:48

                Как бумажка с паролем позволяет узнать этот пароль всем, кто проходит мимо, так и возможность автоматически активировать yubikey позволяет всем , кто незаметно залез в ваш компьютер воспользоваться ключом.

                Ведь смысл yubikey только в этом - можно взломать компьютер сотрудника, можно спереть с него все приватные ключи и кейлогером снять все пароли - но если ключ хранится на yubikey, то без физического нажатия на кнопку хакер никуда дальше не попадет.


                1. kt97679
                  01.07.2024 12:48

                  для того, чтобы программно нажать на yubikey надо знать, как это сделать. Это не стандартное действие, поскольку код написан мной. Если дать волю паранойе, то, конечно, можно придумать сценарий, когда незаметно для меня хакер внедрился на мою машину, пронанализировал мои скрипты и понял, как пользоваться автонажатием. Но думаю, что при таком раскладе использую я автонажатие или нет уже не имеет значения, потому что моя машина и так под контролем хакера. Если я нажимаю yubikey руками он просто подождет, пока я это сделаю, и дальше получит доступ ко всему, к чему имею доступ я.


            1. shrimproller
              01.07.2024 12:48

              Ну да, один бухгалтер знает пароль другого бухгалтера от рдп. Ужс. Оба так-то сидят в одной и той же базе с одними и теми же данными и периодически, когда кто-то в отпуске, работают под учетками друг друга почти официально. А ещё снабженцы смогут зайти на почту друг друга, а когда уходят в отпуск, тоже даже сами пароли пишут, ещё и пересылку ставят. При этом, в помещение посторонние особо то и не заходят, а если заходят то что? Сеть за vpn, пароль есть логина нет, ip адреса неизвестны.

              Сложные пароли ставят как защиту от перебора из вне. Адекватный главбух, который понимает что не должен вот этот вот новенький заходить под главбухом в базу не будет писать пароль от базы на бумажке. От рдп, от почты может ещё, но не от базы. А без последнего два первых почти бесполезны.

              Вы о чем? Или я что-то не так понимаю?


    1. Sing303
      01.07.2024 12:48
      +1

      Например, можно так CI CD автоматизировать для подписи десктопных приложений, т.к. сертификаты десктопов только на yubikey сейчас дают хранить


  1. kt97679
    01.07.2024 12:48
    +2

    Несколько лет назад решал ровно такую задачу. Вот мое решение.


  1. nextbystander
    01.07.2024 12:48
    +2

    Почему "Зажим краб"? Всегда это был "зажим крокодил".


    1. karen07 Автор
      01.07.2024 12:48
      +1

      Поправил)


  1. Dominux
    01.07.2024 12:48

    Разрабы: создают специальное решение, чтобы заставить людей прожимать пароль ручками

    Тупые люди: надо автоматизировать!