Подарили мне как то 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)
Dominux
01.07.2024 12:48Разрабы: создают специальное решение, чтобы заставить людей прожимать пароль ручками
Тупые люди: надо автоматизировать!
dabar347
Но ведь вся суть этой кнопки, что на нее человек сам нажимает. Зачем это всё
kt97679
Полезно для автоматизации некоторых сценариев.
0mogol0
ну примерно так же, как бумажка с паролем на мониторе...
Если нужна автоматизация - нужно подбирать другое решение.
kt97679
Работодатель дает вам yubikey и в начале рабочего дня вам нужно несколько раз коснуться его для настройки рабочей среды. Другого решения нет и не будет. Можно каждый день выполнять рутинную процедуру, а можно ее автоматизировать.
0mogol0
Ну я и говорю, бумажка с паролем...
Работодатель вводит сложные многобуквенные и цифровые пароли, которые к тому же надо регулярно менять, чтобы повысить защищенность системы. Недовольные этим пользователи записывают пароль на бумажке и клеют её к монитору.
Результат: в сети используются сложные пароли, которые знает любой, кто имеет физ. доступ к рабочему месту.
kt97679
Объясните, пожалуйста, каким образом автоматическое касание yubikey создает дыру в безопасности, сравнимую с паролем на мониторе?
vlad49
Как бумажка с паролем позволяет узнать этот пароль всем, кто проходит мимо, так и возможность автоматически активировать yubikey позволяет всем , кто незаметно залез в ваш компьютер воспользоваться ключом.
Ведь смысл yubikey только в этом - можно взломать компьютер сотрудника, можно спереть с него все приватные ключи и кейлогером снять все пароли - но если ключ хранится на yubikey, то без физического нажатия на кнопку хакер никуда дальше не попадет.
kt97679
для того, чтобы программно нажать на yubikey надо знать, как это сделать. Это не стандартное действие, поскольку код написан мной. Если дать волю паранойе, то, конечно, можно придумать сценарий, когда незаметно для меня хакер внедрился на мою машину, пронанализировал мои скрипты и понял, как пользоваться автонажатием. Но думаю, что при таком раскладе использую я автонажатие или нет уже не имеет значения, потому что моя машина и так под контролем хакера. Если я нажимаю yubikey руками он просто подождет, пока я это сделаю, и дальше получит доступ ко всему, к чему имею доступ я.
shrimproller
Ну да, один бухгалтер знает пароль другого бухгалтера от рдп. Ужс. Оба так-то сидят в одной и той же базе с одними и теми же данными и периодически, когда кто-то в отпуске, работают под учетками друг друга почти официально. А ещё снабженцы смогут зайти на почту друг друга, а когда уходят в отпуск, тоже даже сами пароли пишут, ещё и пересылку ставят. При этом, в помещение посторонние особо то и не заходят, а если заходят то что? Сеть за vpn, пароль есть логина нет, ip адреса неизвестны.
Сложные пароли ставят как защиту от перебора из вне. Адекватный главбух, который понимает что не должен вот этот вот новенький заходить под главбухом в базу не будет писать пароль от базы на бумажке. От рдп, от почты может ещё, но не от базы. А без последнего два первых почти бесполезны.
Вы о чем? Или я что-то не так понимаю?
Sing303
Например, можно так CI CD автоматизировать для подписи десктопных приложений, т.к. сертификаты десктопов только на yubikey сейчас дают хранить