Библиотека сайта rus-linux.net
Yubikey - аутентификация по одноразовым паролям
Оригинал: Yubikey One-Time Password Authentication
Автор: Dirk Merkel
Дата: 1 января 2009
Перевод: Александр Тарасов aka oioki
Дата перевода: 16 сентября 2009
Продолжение статьи. Начало смотри здесь
Добавление Yubikey-аутентификации к системе Typo
Теперь у нас есть ясное понимание технологии, давайте же сделаем что-нибудь практическое. Например, добавим аутентификацию по Yubikey к существующему приложению. Для ведения блога я пользуюсь Typo. Приложение Typo разработано на платформе Ruby on Rails, последний снимок его исходного кода можно найти в открытом SVN-репозитории проекта. Нравится вам или нет структура приложения RoR, но в данный момент она работает на нас, потому что облегчает поиск файлов, которые нужно изменить. Взгляните на рисунок 5, там изображена основная схема процедуры проверки пароля, которую мы будем реализовывать.
Рисунок 5. Блок-схема проверки одноразового пароля Yubikey
Начнем. Поместим клиентскую библиотеку для языка Ruby - yubico.rb в каталог lib проекта. После добавления соответствующей команды require в файл config/environments.rb, мы можем быть уверены, что библиотека будет доступна из любой точки приложения.
Для аутентификации с помощью Yubikey необходимы две группы настроек. Во-первых, это настройки уровня сайта, а именно API-ключи и соответствующий ID - они необходимы для отправки запросов аутентификации на веб-сервис. Также есть опция, включающая/выключающая аутентификацию по Yubikey на уровне блога. Typo хранит эти настройки, присваивая им серийный номер и сохраняя в поле blogs.settings. Для нас это означает, что нам не придется самостоятельно вносить изменения в базу данных. Однако, необходимо будет адаптировать пользовательский интерфейс и модель данных, используемую для хранения этих настроек внутри приложения. В листинге 1 показан код, который добавляет эти три настройки в соответствующий HTML-шаблон в админке. Аналогично, в листинге 2 приведен код для добавления этих же настроек в модель данных. Это все, что требуется, чтобы Rails нарисовал нам форму для ввода этих настроек и сохранения их в базе данных каждого блога. На рисунке 6 изображен итоговый результат.
Листинг 1. Typo: HTML-код настроек Yubikey уровня блога
файл: app/views/admin/settings/index.html.erb ... <!-- Yubikey authentication - start --> <fieldset id="authentication" class="set" style="margin-top:10px;"> <legend><%= _("Authentication")%></legend> <ul> <li> <label class="float"><%= _("Require Yubikey OTP")%>:</label> <input name="setting[yubikey_required]" id="yubikey_required" type="checkbox" value="1" <%= 'checked="checked"' if this_blog.yubikey_required%> /> <input name="setting[yubikey_required]" type="hidden" value="0" /> </li> <li> <label for="yubikey_api_id" class="float"><%= _("Yubico API ID")%>:</label> <input name="setting[yubikey_api_id]" id="yubikey_api_id" type="text" value="<%=h this_blog.yubikey_api_id %>" size="6" /> </li> <li> <label for="yubikey_api_key" class="float"><%= _("Yubico API Key")%>:</label> <input name="setting[yubikey_api_key]" id="yubikey_api_key" type="text" value="<%=h this_blog.yubikey_api_key %>" size="50" /> </li> </ul> </fieldset> <!-- Yubikey authentication - end --> ...
Листинг 2. Typo: Добавление настроек Yubikey уровня блога в модель
файл: app/model/blog.rb ... # Authentication setting :yubikey_required, :boolean, false setting :yubikey_api_id, :string, '' setting :yubikey_api_key, :string, '' ...
Рисунок 6. Typo: Интерфейс для изменения настроек Yubikey уровня блога
Далее, есть две настройки, которые относятся к пользователю: это Yubikey ID и Yubikey Required. Первая необходима для связывания аккаунта Typo с уникальным открытым идентификатором Yubikey ID пользователя, а последняя позволяет пользователям включать или отключать аутентификацию по Yubikey лишь для себя. Теперь давайте сделаем так, чтобы пользователь мог изменять обе настройки из админки приложения. Чтобы новые настройки появились в пользовательском интерфейсе, я добавил новую секцию в часть HTML-шаблона, содержащего вывод формы редактирования настроек пользователя (см. листинг 3). Благодаря тому, что RoR поддерживает ActiveRecord, нам больше не нужно будет писать никакого кода, сохраняющего настройки в базе данных; однако, нужно добавить соответствующие поля в пользовательскую таблицу, в которой сохраняются настройки с этого экрана. В платформе Rails это осуществляется добавлением миграции базы данных, что по сути является абстрактным способом описания инкрементального изменения базы данных. В нашем случае мы добавляем в пользовательскую таблицу поля yubikey_id и yubikey_required, для этого создаем миграцию, показанную в листинге 4. Теперь нам нужно запустить утилиту rake из командной строки и указать ей обновить базу данных: rake db:migrate
. Приятной особенностью миграций Rails является то, что они независимы от провайдера базы данных. Миграцию, которую мы создали в листинге 4, можно использовать в любых СУБД, поддерживаемых приложением Typo. На момент написания данной статьи это были СУБД MySQL, PostgreSQL и SQLite. Итак, мы можем полюбоваться на новые настройки в админке пользователя (см. рисунок 7).
Листинг 3. Typo: HTML-код настроек Yubikey уровня пользователя
файл: app/views/admin/users/_form.html.erb: ... <li> <label class="float" for="user_notify_on_new_articles"><%= _("Send notification messages when new articles are posted")%>? </label> <%= check_box 'user', 'notify_on_new_articles' %> </li> <!-- new options for Yubikey authentication - start --> <li> <label class="float" for="user_yubikey_required"><%= _("Yubikey Required")%>? </label> <%= check_box 'user', 'yubikey_required' %> </li> <li> <label class="float" for="user_yubikey_id"><%= _("Yubikey ID")%>: </label> <%= text_field 'user', 'yubikey_id' %> </li> <!-- new options for Yubikey authentication - end --> </ul> </fieldset> <!--[eoform:user]-->
Листинг 4. Typo: Миграция базы данных с настройками Yubikey
файл: db/migrate/071_add_yubikey_columns_to_user.rb: class AddYubikeyColumnsToUser < ActiveRecord::Migration def self.up add_column :users, :yubikey_id, :string, :null => false, :default => '' add_column :users, :yubikey_required, :boolean, :null => false, :default => false end def self.down remove_column :users, :yubikey_id remove_column :users, :yubikey_required end end
Рисунок 7. Typo: Интерфейс для изменения настроек Yubikey уровня пользователя
Все, подготовили предварительные шаги, теперь можем сконцентрироваться на собственно аутентификации. Во-первых, давайте создадим поле ввода Yubikey OTP в окне входа в систему, которое появляется, когда аутентификация по Yubikey включена глобально для всего блога. Я сделал это, изменив часть шаблона, который выводит форму входа в системе, как на листинге 5. Обратите внимание, что нам придется всегда выводить поле Yubikey OTP, ведь если пользователь не укажет свое имя, мы не можем знать, требуется ли Yubikey-аутентификация или нет. На рисунке 8 изображено модифицированное окно входа в систему.
Когда пользователь отправит форму, Rails направит введенные данные методу login классу AccountsController (см. листинг 6). Именно здесь нам надо добавить логику аутентификации по Yubikey. После того, как существующий код проверит обычные логин и пароль, у нас формируется объект User, для которого мы знаем, нужна ли ему Yubikey-аутентификация или нет. Если да, то вызываем статический метод authenticate_yubikey на этом объекте. Смотрите листинг 7, мы проверяем, чтобы ни Yubikey OTP с формы входа в систему, ни открытый Yubikey ID не были пустыми. Более того, по определению первые 12 символов одноразового пароля должны совпадать с открытым идентификатором, соответствующим аккаунту. Если все в порядке, мы создаем объект Yubico, который сам отошлет запрос к веб-сервису и обработает результат. Метод попросту возвращает булево значение. "Истина" означает, что пользователь был аутентифицирован. "Ложь" означает неверный одноразовый пароль, либо попытка войти под неавторизованным пользователем - возможно, кто-то пытается взломать аккаунт.
Листинг 5. Typo: Модфицированная HTML-форма входа в систему
файл: app/views/shared/_loginform.html.erb: <% form_tag :action=> "login" do %> <ul> <li> <label for="user_login"><%= _('Username')%>:</label> <input type="text" name="user_login" id="user_login" value=""/> </li> <li> <label for="user_password"><%= _('Password') %>:</label> <input type="password" name="user_password" id="user_password" /> </li> <!-- Yubikey authentication - start --> <% if this_blog.yubikey_required %> <li> <label for="yubikey_otp"><%= _('Yubikey OTP') %>:</label> <input type="text" name="yubikey_otp" id="yubikey_otp" /> </li> <% end %> <!-- Yubikey authentication - end --> <li class="r"><input type="submit" name="login" value= "<%= _('Login') %> »" class="primary" id="submit" /> </li> </ul> <p><%= link_to "« " + _('Back to ') + this_blog.blog_name, this_blog.base_url %></p> <% end %>
Листинг 6. Typo: Yubikey-аутентификация, часть 1
файл: app/controllers/accounts_controller.rb: ... def login case request.method when :post self.current_user = User.authenticate(params[:user_login], params[:user_password]) # check whether Yubikey authentication is required and perform # authentication if logged_in? && (!this_blog.yubikey_required || !self.current_user.yubikey_required || self.current_user.authenticate_yubikey( this_blog, self.current_user.yubikey_id, params[:yubikey_otp])) session[:user_id] = self.current_user.id flash[:notice] = _("Login successful") redirect_back_or_default :controller => "admin/dashboard", :action => "index" else flash.now[:notice] = _("Login unsuccessful") @login = params[:user_login] end end end ...
Листинг 7. Typo: Yubikey-аутентификация, часть 2
файл: app/model/user.rb ... # Authenticate a user's Yubikey ID. # # Example: # @user.authenticate_yubikey(this_blog, 'thcrefhcvijl', # 'thcrefhcvijldvlfugbhrghkibjigdbunhjlfnbtvfbc') # def authenticate_yubikey(this_blog, yubikey_id = '', yubikey_otp = '') if (yubikey_id.empty? || yubikey_otp.empty? || !yubikey_otp[0, 12].eql?(yubikey_id)) return false else begin yk = Yubico.new(this_blog.yubikey_api_id, this_blog.yubikey_api_key) return yk.verify(yubikey_otp).eql?('OK') rescue return false end end end ...
Рисунок 8. Typo: Модифицированная форма входа в систему
Вот и все! Мой Typo-блог теперь адаптирован к Yubikey. Отправлю свои наработки в репозиторий Typo, в виде патча.
Возможные реализации
Реализацию Yubikey-аутентификации можно было сделать по-другому. Во-первых, можно было бы опустить имя пользователя, ведь пароль Yubikey уже включает открытый идентификатор, который можно привязать к пользовательскому аккаунту. Однако эта схема будет работать лишь в случае единственного аккаунта на один ключ Yubikey.
Во-вторых, можно минимизировать число изменений пользовательского интерфейса, если отвести под одноразовый пароль Yubikey место в поле обычного пароля. Одноразовый пароль имеет фиксированную длину, поэтому все оставшиеся символы в этом поле будут составлять обычный пароль. Также нужно не забывать, что Yubikey завершает посыл одноразового пароля символом новой строки, поэтому необходимо будет вводить сначала обычный пароль, а потом уже одноразовый - по-другому и не выйдет.
В-третьих, можно сделать вход в систему двухэтапным. Сначала у пользователя спрашивают одноразовый пароль и проверяют его. Если проверка прошла удачно, нужно спросить у пользователя обычные логин и пароль. Чтобы понять преимущества такого подхода, рассмотрим ситуацию, когда имя пользователя, обычный пароль и одноразовый пароль отправляются одновременно. Если злоумышленники перехватят эти отправляемые данные, причем не дадут одноразовому паролю дойти до сервера аутентификации, у них в руках будет все три части, по которым они могут проникнуть в вашу систему. Однако, если вы отправите одноразовый пароль на первом этапе, тогда даже если злоумышленники перехватят его, они не смогут им воспользоваться, ведь у них нет соответствующих обычных логина и пароля. Чтобы вы добрались до окошка ввода логина и пароля (чтобы напечатать их для злоумышленников), одноразовый пароль должен пройти проверку на веб-сервере аутентификации, причем после проверки он становится уже бесполезным. Таким образом, задача атакующего будет значительно усложнена.
Заключение
На своем веб-сайте компания Yubico поддерживает и пополняет список приложений и служб, которые основаны на Yubikey. Существуют модули для WordPress, интеграция с SSH, форума phpBB и вход в систему Windows (коммерческая бета-версия). Как показано выше в примере с Typo, процесс адаптации программы к Yubikey достаточно прост. Надеюсь, что эта статья вдохновит вас, и вы добавите функции аутентификации по Yubikey к своей любимой программе.
Источники информации
- Страница компании Yubico, посвященная Yubikey: www.yubico.com/products/yubikey
- Приложения, поддерживающие Yubikey: yubico.com/products/apps
- Почтовый веб-клиент RoundCube: www.roundcube.net
- Скрипт блога Typo: www.typosphere.org
Дирк Меркель (Dirk Merkel) - технический директор компании Vivantech Inc. На досуге ему нравится посылать свои патчи хорошим проектам с открытым исходным кодом. Он также пишет о веб-разработке. Живет Дирк в Сан-Диего со своей любимой женой и двумя замечательными дочерьми. Дирку можно написать на адрес dmerkel@vivantech.com.