Pull to refresh
2674.93
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Нажималка для YubiKey

Reading time 12 min
Views 28K
Original author: Bertrand Fan
Если вы работаете в сфере информационных технологий, то у вас, возможно, есть аппаратный ключ безопасности YubiKey. У меня такой имеется. Это — YubiKey 5C Nano, показанный на следующем снимке.


YubiKey 5C Nano

Если вы работаете в области, не связанной с компьютерами, но при этом, в основном, пользуетесь ноутбуком, то вам, возможно, стоило бы обзавестись YubiKey. А если вы участвуете в политической кампании, или работаете журналистом, то вам, определённо, нужен YubiKey (или нечто подобное). Обсудите это с вашим отделом безопасности. Правда, этот материал будет кое о чём таком, о чём специалистам вашего отдела безопасности лучше не знать. Поэтому не говорите им об этой статье.

Ключ YubiKey используется в механизмах двухфакторной аутентификации. Это означает, что после того, как вы вошли в некую систему с помощью имени пользователя и пароля, система предложит вам авторизоваться с помощью ещё одного механизма. В результате, если ваши имя пользователя и пароль украдут, злоумышленнику придётся украсть ещё и второе средство аутентификации, что усложняет его задачу.

Существуют разные способы двухфакторной аутентификации. Например, часто встречается такая схема работы: веб-сайт предлагает вам отсканировать QR-код с помощью приложения вроде Google Authenticator, установленного на телефоне, генерирующего 6-значные коды. Работа этого механизма основана на том, что у сервера и у приложения имеется один и тот же секретный ключ. Телефон генерирует коды, основываясь на этом ключе и на текущей отметке времени. Такие же коды генерирует и сервер. Когда серверу предоставляют код, созданный в приложении, он проверяет, совпадает ли он с кодом, который сгенерировал он.


QR-код


Шестизначный код, сгенерированный приложением

Ещё один способ двухфакторной аутентификации основан на SMS-сообщениях. Он, правда, считается небезопасным. В данном случае сервер генерирует код и отправляет его на телефон в SMS-сообщении. Причина, по которой этот механизм считается небезопасным, заключается в том, что существует атака, известная как «SIM-джекинг», в ходе проведения которой некто обманом переносит интересующий его номер на новую SIM-карту, что приводит к тому, что он начинает получать SMS-сообщения, адресованные первоначальному владельцу номера.


SMS-сообщение с кодом

Ключи YubiKey — это маленькие устройства, подключаемые к USB-портам компьютеров и эмулирующие клавиатуру. Когда нажимают кнопку YubiKey (или, скорее, касаются её), устройство генерирует одноразовый пароль (One-Time password, OTP), который может быть проверен специальным сервером. На устройстве имеется секретный ключ, используемый для подписывания информации, но этот ключ никогда не выходит за пределы устройства, так как он сохранён в особом окружении, защищённом от несанкционированного доступа.

Мой YubiKey рассчитан на постоянное подключение к USB-порту. Поэтому я, когда, например, беру ноутбук с рабочего стола и переношу его в конференц-зал, забираю с собой и YubiKey. Но теперь мой ноутбук, как и ноутбуки многих «новообращённых» удалённых работников, никогда не покидает мой рабочий стол. Я подключил его к внешнему монитору, а чтобы сэкономить на столе немного места, я установил его вертикально, в сложенном состоянии, на специальной стойке.

Это усложнило задачу активации YubiKey. Особенно — если ноутбук находится далеко от внешней клавиатуры и мыши. Я решил эту проблему, купив USB-C-удлинитель, благодаря которому смог разместить YubiKey поближе к клавиатуре.

Да, я ещё не рассказал об одной особенности YubiKey 5C Nano. Дело в том, что кнопку устройства довольно тяжело «нажимать», и это — даже если не учитывать проблему большого расстояния до ключа. Область устройства, которой надо коснуться для активации ключа, очень мала.


YubiKey 5C Nano

Одна из особенностей YubiKey заключается в том, что устройство срабатывает только в том случае, если металлического контакта касается человек. Это предотвращает срабатывание ключа в том случае, если, например, YubiKey что-нибудь случайно заденет. Если вы когда-нибудь видели в Slack-канале или в документах Google одноразовый пароль, вроде tlerefhcvijlngibueiiuhkeibbcbecehvjiklltnbbl, то вы знаете о том, что система это не идеальная. Я бы сказал, что устройство не срабатывает в одной из пяти попыток его активировать.

На то, чтобы ключ нельзя было бы активировать программно, было потрачено много сил и времени.

Прежде чем мы пойдём дальше, мне хотелось бы остановиться на причинах подобного состояния дел. Если хакер сможет взломать компьютер и сумеет программно активировать YubiKey, это полностью лишит смысла использование данного ключа. Но я полагаю, что мы всегда идём на компромиссы между безопасностью и удобством. Например, часто бывает так, что YubiKey-пароль не нужно вводить каждый раз, подключаясь к некоей системе. Некоторые системы, после ввода такого пароля, позволяют, при следующих входах в них, некоторое время не вводить его снова. Или, например, когда система с двухфакторной аутентификацией выдаёт вам резервные коды доступа — часто ли вы их распечатываете на принтере и убираете в надёжное место? Каждый сам для себя находит устраивающий его баланс безопасности и удобства.

Учитывая вышесказанное, давайте поговорим о том, как можно программно активировать YubiKey.


Я называю этот механизм The Finger

Аппаратная часть системы


Для начала нам нужен какой-то способ для организации взаимодействия компьютера и The Finger. У меня есть куча макетных плат IZOKEE D1 Mini, которые представляют собой уменьшенную версию плат, использующих знаменитый чип ESP8266, применяемый во многих IoT-устройствах.


Плата с чипом ESP8266

Этот модуль можно подключить к ноутбуку по USB, но так как он поддерживает WiFi, на нём можно просто запустить веб-сервер и отправлять ему команды по беспроводной сети.

Далее, нам нужно нечто, способное перемещать The Finger в направлении YubiKey. После непродолжительных поисков в интернете я узнал о том, что с платой D1 Mini хорошо стыкуется шаговый двигатель 28BYJ-48.


Шаговый двигатель

Шаговый двигатель преобразует электрические импульсы во вращательные движения, у D1 Mini есть пины, способные выдавать подобные импульсы.


Схема работы двигателя

Но шаговый двигатель имеет вращающийся якорь, а нам нужно перемещать движущуюся часть The Finger по прямой. Я поискал по слову 28BYJ-48 на Thingiverse и обнаружил соответствующий механизм.


Механизм для преобразования вращательного движения в прямолинейное

Тут к двигателю крепится шестерня, которая перемещает вперёд-назад длинный стержень с зубцами. А если мы собираемся перемещать в направлении YubiKey длинную пластиковую штуковину, то она вполне может выглядеть как обычный палец. Я опять пошёл на Thingiverse, поискал там по слову «finger» и нашёл модель пальца, которую кто-то сделал для Хэллоуина.


Модель пальца

Я открыл файлы моделей в Fusion 360, и, пользуясь продвинутыми CAD-инструментами, сделал то, модель чего показана ниже.


Результат объединения моделей пальца и стержня

Потом я экспортировал модель в формат STL и напечатал её на 3D-принтере. В тот момент в принтер был заряжен пластик Prusament PLA Lipstick Red. Он и пошёл в дело. Когда пластиковый палец был готов, я взял его и коснулся им сенсора YubiKey. Ничего не произошло. Тогда я нашёл у себя на столе металлический винт и коснулся YubiKey на этот раз им. Ключ мгновенно выдал одноразовый пароль. Я прикрепил пластиковый палец к столу струбциной, просверлил в нём небольшое отверстие и вкрутил туда винт. Потом я коснулся сенсора винтом, вкрученным в пластиковый палец, но на этот раз устройство снова не отреагировало на прикосновение.


Обработка пластиковой модели

Именно тогда я понял, что я — идиот, и что когда я касался YubiKey металлическим винтом, металл просто передавал электрический заряд с моего тела на ёмкостный датчик касания YubiKey. Как же мне обмануть датчик и заставить его поверить в то, что пластиковая модель — это настоящий палец?

Я предположил, что принцип работы ёмкостных датчиков заключается в том, что они измеряют ёмкость человеческого тела по отношению к «земле». Поэтому, если подключить датчик прямо к «земле», то он решит, что тело коснувшегося его человека просто является очень хорошим проводником. Поэтому я взял изолированный провод, немного открутил винт, обмотал концом провода винт и опять его закрутил. Потом я подключил другой конец провода к выходу GND платы D1 Mini и снова коснулся YubiKey. На этот раз всё заработало!

К плате управления двигателем уже были подключены выходы 5V и GND платы D1 Mini. Поэтому я подумал, что мне может понадобиться подключить к GND и плату управления двигателем, и винт. Но неожиданно для себя я понял, что могу просто воткнуть другой конец провода, идущего от винта, между и металлическим корпусом двигателя (по-видимому — заземлённым) и пластиковой стенкой отсека для двигателя. И так всё тоже работало!


Провод, соединяющий корпус двигателя и винт, вкрученный в пластиковый палец

После того, как я убедился в том, что The Finger способен активировать YubiKey, я начал думать о том, как закрепить YubiKey рядом с пластиковым пальцем. Этот вопрос я решил, измерив цифровым штангенциркулем размеры USB-C-удлинителя и создав держатель для двигателя, пальца и удлинителя с YubiKey в Fusion 360.


Держатель

Удлинитель входит в отверстие держателя, расположенное слева, а двигатель монтируется справа.

На этом этапе работы мне понадобилось подключить плату управления двигателем к D1 Mini. Это можно сделать, припаяв несколько выводов к D1 Mini и подключив к ним соединительные провода Dupont, идущие к плате двигателя.


Подключение платы управления двигателем к плате D1 Mini

В следующей таблице показана схема подключения платы управления двигателем к плате D1 Mini.

D1 Mini Плата управления 28BYJ-48 
5V 5V
GND GND
D1 IN1
D2 IN2
D3 IN3
D4 IN4

После того, как все части системы оказались собранными воедино, у меня получилось то, что показано на следующем рисунке.


Система в сборе

Программная часть системы


Программная часть The Finger устроена гораздо проще, чем аппаратная. Писать программы для платы D1 Mini можно с помощью IDE Arduino. Перед началом работы нужно перейти в раздел Preferences и добавить туда, в поле Additional Board Manager URLs, следующую ссылку: https://arduino.esp8266.com/stable/package_esp8266com_index.json. После этого можно перейти в окно Board Managers и установить там пакет esp8266, позволяющий работать с платой LOLIN(WEMOS) D1 R2 & mini, которая должна быть выбрана в меню Tools.

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

#define LED 2 //Указываем пин, используемый для подключения светодиода

void setup() {
  pinMode(LED, OUTPUT); // Переводим пин LED в режим OUTPUT
}
// функция loop будет повторяться снова и снова
void loop() {
  digitalWrite(LED, LOW); // Включить светодиод (LOW - это уровень напряжения)
  delay(1000); // Подождать одну секунду
  digitalWrite(LED, HIGH); // Выключить светодиод LED установив напряжение в HIGH
  delay(1000); // Подождать две секунды
}

Потом я обнаружил этот скетч, демонстрирующий пример управления шаговым двигателем 28BYJ-48 по WiFi.

Вот та часть кода, которая отвечает за управление двигателем:

int Pin1 = D1; // подключён к IN1
int Pin2 = D2; // подключён к IN2
int Pin3 = D3; // подключён к IN3
int Pin4 = D4; // подключён к IN4
 
int pole1[] ={0,0,0,0, 0,1,1,1, 0}; //обмотка 1, 8 ступеней импульсов
int pole2[] ={0,0,0,1, 1,1,0,0, 0}; // обмотка 2, 8 ступеней импульсов
int pole3[] ={0,1,1,1, 0,0,0,0, 0}; // обмотка 3, 8 ступеней импульсов
int pole4[] ={1,1,0,0, 0,0,0,1, 0}; // обмотка 4, 8 ступеней импульсов

int poleStep = 0; 
int dirStatus = 3; // хранит направление движения. 3 = остановка (не менять)
String argId[] ={"ccw", "cw"};

...

void loop(void) {
    server.handleClient();
    MDNS.update();

    if (dirStatus == 1) {
        poleStep++;
        driveStepper(poleStep);
    } else if (dirStatus == 2) {
        poleStep--;
        driveStepper(poleStep);
    } else {
        driveStepper(8);
    }
    
    if (poleStep>7) { 
        poleStep=0; 
    }

    if (poleStep<0) {
        poleStep=7; 
    }

    delay(1);
}

/*
 * motorControl()
 * меняет значение переменной "dirStatus" на 1, 2 или 3
 * ничего не возвращает
 */
void motorControl() {
    if (server.arg(argId[0]) == "on") {
        dirStatus = 1;  // против часовой стрелки (CCW) 
    } else if (server.arg(argId[0]) == "off") {
        dirStatus = 3;  // двигатель выключен 
    } else if (server.arg(argId[1]) == "on") {
        dirStatus = 2;  // по часовой стрелке (CW)
    } else if (server.arg(argId[1]) == "off") {
        dirStatus = 3;  // двигатель выключен
    }
}

/*
 * @brief отправляет сигналы двигателю
 * @param "c" целое число, определяющее сигнал, подаваемый на обмотки двигателя
 * @return ничего не возвращает
 */
void driveStepper(int c)
{
    digitalWrite(Pin1, pole1[c]);  
    digitalWrite(Pin2, pole2[c]); 
    digitalWrite(Pin3, pole3[c]); 
    digitalWrite(Pin4, pole4[c]);
}

Вот как всё это устроено. Веб-сервер выдаёт страницу с двумя кнопками. Одна из них, CCW, включает вращение вала двигателя против часовой стрелки, другая, CW — включает вращение по часовой стрелке. Если нажимают кнопку CCW, значение переменной dirStatus меняется на 1, если нажимают кнопку CW — значение этой переменной меняется на 2. Повторные нажатия на уже нажатые кнопки переводит переменную dirStatus в значение 3 и двигатель выключается.

Тут можно обратить внимание на функцию loop(), в которой осуществляется проверка переменной dirStatus. В зависимости от её значения осуществляется либо увеличение значения переменной poleStep, либо его уменьшение (двигатель в это время работает), либо выключение двигателя.

Нам нужна единственная HTTP-конечная точка, которая позволит запустить вращение вала двигателя против часовой стрелки, выполняемое до тех пор, пока пластиковый палец не коснётся YubiKey. После этого двигатель должен вращаться в обратном направлении до тех пор, пока палец не примет исходную позицию. Потом двигатель выключается.

Это значит, что мы можем переписать функцию motorControl(), упростив её и приведя в такое состояние:

void motorControl() {
  dirStatus = 1;    // CCW 
}

Она включит вращение вала двигателя против часовой стрелки при обращении к конечной точке.

Затем мы модифицируем функцию loop(), сделав так, чтобы вал двигателя перемещался бы на 400 шагов против часовой стрелки, а затем — менял бы направление движения и, вращаясь по часовой стрелке, выполнял бы 400 шагов в обратном направлении. После этого двигатель останавливается.

int steps = 0;

void loop(void) {
    server.handleClient();
    MDNS.update();

    if (dirStatus == 1) {
        poleStep++;
        driveStepper(poleStep);
        steps++;
    } else if (dirStatus == 2) {
        poleStep--;
        driveStepper(poleStep);
        steps++;
    } else {
        driveStepper(8);
    }
    
    if (poleStep>7) { 
        poleStep=0; 
    }

    if (poleStep<0) {
        poleStep=7; 
    }

    if (steps > 400) {
        if (dirStatus == 1) {
            dirStatus = 2;  // CW
        } else {
            dirStatus = 3;  // двигатель выключен
        }
        steps = 0;
    }

    delay(1);
}

В том, как устроена аппаратная часть The Finger, есть одна приятная особенность. Она заключается в том, что из-за того, что двигатель является не особенно мощным, он, ничему не вредя, может продолжать вращаться против часовой стрелки даже после того, как пластиковый палец коснётся YubiKey. А когда двигатель вращается по часовой стрелке, возвращая палец в исходную позицию, кость, торчащая из пальца (делали-то его для Хэллоуина), упирается в край подставки и не даёт пальцу, прикреплённому к стержню, съехать с платформы. Я начал подбор значения переменной steps, от которой зависит расстояние, проходимое пальцем, с 1000 шагов. Потом, проверяя расстояние, на которое перемещается палец, постепенно уменьшал это значение, дойдя, в итоге, до 400 шагов.

Потом я выяснил MAC-адрес WiFi-платы, настроил статический IP-мэппинг на маршрутизаторе для этого адреса и сделал запись в локальном DNS о finger.localdomain.

Теперь, когда я обращаюсь к адресу http://finger.localdomain/press, двигатель включается, пластиковый палец касается YubiKey, а потом возвращается в исходную позицию.

Запуск The Finger


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

У того, кто пользуется клавиатурой без цифрового блока, в верхней правой части клавиатуры есть кнопки Print Screen, Scroll Lock и Pause. В 2020 году особого смысла в их обычном функционале нет, поэтому на них вполне можно «повесить» выполнение каких-то особых задач.

Настроить клавиатуру мне помогла программа Karabiner-Elements. Эта программа, как и аппаратная часть моего проекта, немного напоминает машину Голдберга.

Я воспользовался её вкладкой Simple modifications для того чтобы поменять From key на pause, а To key на f14.

Потом я перешёл на её вкладку Misc и щёлкнул по кнопке Open config folder (~/.config/karabiner). Тут можно найти файл karabiner.json, в котором, в раздел profiles.rules, я добавил следующий код:

{
            "description": "F14 to trigger Yubikey",
            "manipulators": [
              {
                "from": {
                  "key_code": "f14",
                  "modifiers": {
                    "optional": ["any"]
                  }
                },
                "to": [
                  {
                    "shell_command": "osascript /Users/YOUR_USERNAME/bin/yubikey.scpt"
                  }
                ],
                "type": "basic"
              }
            ]
          },

В файле ~/bin/yubikey.scpt можно написать Applescript-код, вызывающий shell-скрипт:

do shell script "/bin/sh ~/bin/yubikey.sh 2>&1 &"

Зачем вызывать один скрипт из другого? Я выяснил, что если запускаю shell-скрипт из Karabiner Elements, то открывается новый экземпляр Terminal.app. В результате окно, запрашивающее данные с YubiKey, теряет фокус. А при таком подходе всё работает в фоне.

И, наконец, в файле ~/bin/yubikey.sh имеется следующий код:

#!/bin/bash
curl "http://finger.localdomain/press" --silent >> /dev/null

А вот как выглядит работа готовой системы.


Нажатие на клавишу клавиатуры активирует YubiKey

Я, чтобы проверить работу системы, выполнил тестовый запрос на получение данных с YubiKey, но я могу гарантировать вам то, что The Finger делает своё дело и в ситуации, когда YubiKey нужен в реальных условиях. Вот код, позволяющий самостоятельно выполнить запрос ключа с YubiKey. Может, вам он зачем-то понадобится:

#!/bin/bash
printf "YubiKey for 'bert': "
read yubikey
green=`tput setaf 2`
reset=`tput sgr0`
echo "${green}SUCCESS${reset}"

Мы, имея этот shell-скрипт, можем запустить процесс активации YubiKey вообще не нажимая ни на какие кнопки. А именно, в терминале iTerm2 есть возможность, называемая Triggers (триггеры). Она позволяет выполнять какие-то действия, основываясь на анализе текстов в терминале. Можно создать регулярное выражение, которое ожидает появления текста «Yubikey for» и запускает скрипт активации YubiKey. В результате для запуска The Finger не нужно нажимать на особую кнопку.

Ещё одна возможность программной активации YubiKey заключается в том, чтобы создать собственное расширение для Google Chrome, которое ожидает появления определённых URL, запрашивающих пароль с YubiKey и в фоне обращается к http://finger.localdomain/press.

Итоги


Я показал The Finger одному человеку, а он говорит: «Итак… ты, по сути, сделал кнопку, которую ты нажимаешь для того чтобы нажать другую кнопку? Почему бы просто не нажать нужную кнопку?». Меня этот вопрос немного разозлил. Получается, что тот, кому я показывал проект, ничего не понял. «Неужели не ясно? Это — плохая кнопка. А это — кнопка хорошая. Я хочу нажимать на хорошую кнопку».

Работайте над тем, что для вас важно.

Были ли у вас проекты, которые казались кому-то из окружающих бессмысленными?



Tags:
Hubs:
+37
Comments 32
Comments Comments 32

Articles

Information

Website
ruvds.com
Registered
Founded
Employees
11–30 employees
Location
Россия
Representative
ruvds