id: Гость   вход   регистрация
текущее время 11:49 28/03/2024
Владелец: SATtva (создано 08/03/2007 19:01), редакция от 03/07/2007 21:34 (автор: SATtva) Печать
Категории: криптография, софт, gnupg, аутентификация, эцп, разное, расширения, сообщество
создать
просмотр
редакции
ссылки

Общая спецификация интеграции GnuPG


Оглавление документа:

Задачи


Задачами, решаемыми интеграцией GnuPG, являются:


  1. Валидация загружаемого пользователем (в свой профиль) открытого ключа на корректность с последующим внесением отпечатка в базу данных.
  2. Авторизация (по протоколу запрос-ответ) ряда чувствительных операций: смены пользовательского пароля доступа и почтового адреса.
  3. Защита приватной связи между пользователями сайта.
  4. Защита отправляемого пользователю восстановленного пароля.
  5. Функциональное обеспечение публичного доступа к серверам ключей.
  6. Функциональное обеспечение публичного декодировщика OpenPGP-пакетов.
  7. Проверка цифровых подписей для пользовательских комментариев.

Возможность аутентификации на сайте с помощью протокола запрос-ответ (подписание пользователем строки запроса, сгенерированной сервером) не определяется в числе задач, поскольку, хотя и скрывает пароль от наблюдателя на канале связи, сама по себе не в силах предотвратить несанкционированный доступ к учетной записи пользователя с помощью перехваченных cookie и иными путями. Решение же данной проблемы — SSL — вообще делает подобную схему избыточной, поскольку защищает как реквизиты авторизованной сессии (cookie/SID) при работе с сайтом, так и реквизиты пользователя (логин/пароль) в момент аутентификации.


Тем не менее, список задач остается открыт для дополнений.

Функции


Для решения означенных задач требуется реализовать следующий набор специальных функций. (Общие функции, обеспечивающие ввод/вывод данных, в этом списке не обозначены, и могут быть найдены ниже в исходном тексте класса.)

Загрузка ключа


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


Все этапы загрузки/валидации ключа выполняются в контексте специальной временной связки. Валидация ключа состоит из двух этапов: 1) имеет ли пользователь доступ к закрытой части ключа (это лишь защита от случайной загрузки пользователем чужого ключа, который он в дальнейшем не сможет заменить) и 2) пригоден ли ключ для операций шифрования. Первый этап производится в простом протоколе запрос/ответ и для верификации использует функцию VerifyMsg(). На втором этапе производится вызов CheckPK(). Если ключ удовлетворяет всем условиям, то он перемещается в основную связку сервера.

UploadPK(keyASCII)

Импортирует загруженный ключ на временную связку. Единственный аргумент, keyASCII, представляет собой загруженный пользователем блок открытого ключа, передаваемый в GnuPG через STDIN с параметром --import. Сразу после исполнения вызывается CheckPK(keyID), которая анализирует ключ с целью определения его пригодности для зашифрования.

RecievePK(keyID[, ksURL])

Импортирует ключ с сервера ключей на временную связку. Передает GnuPG указанный пользователем (в профиле) ID ключа в параметре командной строки --recv-keys. Опциональный аргумент ksURL позволяет пользователю задать URL сервера ключей, вместо дефолтного.


После закачки материала с сервера ключей анализирует статус-коды GPG. В случае, если произошла коллизия keyID, и с сервера было загружено более одного ключа, возвращает двумерный массив, содержащий отпечатки и имена всех загруженных ключей, дабы в последствии предложить пользователю выбрать ключ, принадлежащий ему (в этом случае в качестве keyID на вход функции подается уже отпечаток). Если статус-коды показывают импорт только одного ключа, то, как и для предыдущей функции, вызывается CheckPK(keyID).


Хотя вся указанная функциональность реализована, тестирование показало, что большинство серверов ключей не способны корректно обходить коллизии в ID: одни обязывают передавать ID в восьмизначном формате (который и образует коллизию), а другие, хоть и не устанавливают такого требования, все равно возвращают пакеты открытых ключей и главные UserID по их восьмизначным ID, независимо от реальной длины ввода.

AcceptPK(keyID)

Вызывается при импорте ключа в случае положительного ответа CheckPK() и переносит импортированный ключ с временной связки на главную. Использование идентификатора ключа в аргументе функции делает невозможным перенос нескольких ключей на главную связку, даже если пользователь и попытается это сделать (фактически будет импортирован только первый ключ в блоке).

Шифрование данных


Универсальное исполнение обеспечивается одной функцией, что решает как задачу защиты приватных сообщений, так и кода восстановления пароля (при возникновении потребности в шифровании двоичных данных, понадобится дополнительная функция, передающая GnuPG аргумент --no-textmode).

EncryptMsg(plaintext, keyID)

Шифрует произвольное текстовое сообщение. Вызывает GnuPG с параметрами -r <keyID> -e, подает открытый текст через STDIN и возвращает шифртекст, полученный от программы. Следует обратить внимание, что перед вызовом данной функции нужно произвести вызов CheckPK() и проверить, пригоден ли открытый ключ получателя для зашифрования.

Верификация данных

VerifyMsg(data)

Принимает произвольные данные, с которых снимает цифровую подпись (вызов GnuPG: gpg -v; команда --verify не используется, поскольку не возвращает сам заверенный текст). Результаты операции определяются по выданным статус-кодам. Возвращает 1) состояние подписи, 2) отпечаток первичного ключа, 3) метку времени ЭЦП, 4) идентификатор ЭЦП, а также 5) тело подписанного сообщения.

Извлечение ключа


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

GetPK(keyID)

Извлекает блок указанного открытого ключа из рабочей связки сервера и подает его на выход (параметры командной строки: --armor --export <keyID>).

Удаление/замена ключа


Для предотвращения замены ключа лицом, получившим несанкционированный доступ к учетной записи пользователя, его удаление из профиля должно быть подтверждено соответствующим закрытым ключом с помощью протокола запрос/ответ (используя VerifyMsg()). Аналогичная процедура предусмотрена для изменения пользовательского пароля и смены почтового адреса, что не позволит захватить аккаунт, не обладая закрытым ключом пользователя.

Функции обслуживания


Набор функций, используемых для обеспечения ряда вышеназванных задач.

CreateToken(procedure)

Генерирует псевдослучайный идентификатор для протокола запрос-ответ (в настоящее время используется при загрузке ключа в профиль и удалении ключа, смене пароля и мэйл-адреса: пользователь своей цифровой подписью должен подтвердить, что обладает закрытой частью ключа или санкционирует производимые изменения). Генерация производится по следующей схеме:

C = T, P, H(S, U, T, P)

где T — метка времени, P — обозначение операции (передается через аргумент функции), S — секретное значение системы, U — идентификатор PHP-сессии пользователя. (В качестве H применяется хэш-функция SHA-1.) Получаемый идентификатор выглядит примерно так:

1179831196|deletepk
251fb1d6718f7042948d77c583e75d5b3b39e52a

Злоумышленник, записавший заверенный идентификатор из прежнего сеанса работы пользователя, будет крайне ограничен в возможностях его использования для выполнения несанкционированных действий. Во-первых, он также должен передать номер сессии, для которой идентификатор был сгенерирован. Во-вторых, поскольку перехват SID в принципе возможен, необходимо повторно передать идентификатор в короткий промежуток времени до его истечения (обычно пять минут). В-третьих, даже при выполнении двух первых условий злоумышленник сможет авторизовать лишь ту операцию, для которой идентификатор был выдан (иными словами, он не сможет с помощью идентификатора на загрузку ключа удалить ключ пользователя из профиля). Изменить параметры идентификатора без знания секрета системы он также не сможет.

ValidateToken(token, procedure, expiry)

После проверки цифровой подписи данная функция проверяет аутентичность заверенного идентификатора. Прежде всего это делается для предотвращения повторной передачи устаревших идентификаторов (аргумент expiry задает время жизни идентификатора) или идентификаторов, использовавшихся в других операциях (определяется аргументом procedure).

CheckPK(keyID)

Парсит сертификат ключа (используются параметры --with-colons --list-public-keys <keyID>) и проверяет следующие условия: 1) ключ содержит по крайней мере один шифровальный подключ и 2) базовый ключ и шифровальный подключ в данный момент действительны. Оба условия не имеют критического значения при загрузке ключа (пользователю выводится только предупреждение), но при восстановлении пароля или отправке зашифрованного приватного сообщения должно привести к неудачному завершению операции.

DeletePK(keyID)

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

Реализация


Ограничивающим фактором в реализации схемы выступает запрет стандартной PHP-функции exec() (что является нормой для любого виртуального хостинга). Таким образом, взаимодействие с GnuPG может осуществляться только через интерфейс CGI. Использовать с этой целью CGI-сборку PHP нерационально, поэтому итоговая конструкция представляет собой:


.                   openSpace Engine
    пользовательский уровень, графический интерфейс
                           /\
                           ||
                           \/
               openSpace-GPG engine class
   функциональная прокладка, реализующая необходимый
    набор функций ввода/вывода для взаимодействия с
         GnuPG и проверки результатов операций
                           /\
                           ||
                           \/
                openSpace-GPG CGI wrapper
    простейшая Perl-оболочка, "тупо" вызывающая GnuPG
    с переданными параметрами командной строки и воз-
       вращающая вывод программы верхнему уровню
                           /\
                           ||
                           \/
                   GnuPG executable
     исполняемый файл программы, лежащий за пределами
           публичной www-директории сервера

Временный контекст


Все операции, требующие сохранения временных данных на диске сервера (по существу, это любые операции, поскольку статус-коды и сообщения об ошибках перенаправляются на диск всегда), выполняются в контексте временного каталога, создаваемого конструктором объекта по номеру PHP-сессии пользователя, и удаляемого деструктором объекта по окончании исполнения скрипта. Таким образом, весь парсинг и анализ входных и выходных данных должен производиться в текущем цикле исполнения движка, либо необходимо идти на особые ухищрения, как то сохранение необходимых данных в сессии пользователя (что не рекомендуется).

Класс openSpace-GPG


Основной функциональный класс. Вызов методов напрямую из класса без создания объекта невозможен без отработки конструктора и определения свойств.


Файл конфигурации gpg.conf


Поскольку GnuPG должен работать в полностью автономном режиме, видится необходимым следующий конфигурационный файл. Корректирующие параметры могут быть переданы как аргументы командной строки (например, --no-default-keyring).


CGI-обертка openSpace-GPG


Идея использования пайпа для передачи данных GnuPG из стандартного ввода принадлежит ПэГусеву, за что ему особая благодарность. В остальном обертка не содержит ничего существенного: HTTP-ввод разбирается на пары переменная=значение, вывод GPG открывается как файловый дескриптор и подается на стандартный вывод сервера для последующего перехвата вызывающей программой (конкретно, методом Call() класса; см. выше).


Для защиты от выполнения произвольных команд GnuPG путем прямого вызова скрипта вызывающее приложение должно передать секретное значение (HTTP-переменная pv), равное значению переменной $protection, заданной в скрипте.



 
На страницу: 1, 2, 3, 4, 5, 6, 7, 8 След.
Комментарии [скрыть комментарии/форму]
— spinore (23/05/2007 01:36, исправлен 23/05/2007 01:38)   профиль/связь   <#>
комментариев: 1515   документов: 44   редакций: 5786

планировал), имеет ли смысл изменить страницу смены пароля, чтобы при наличии в профиле
пользователя загруженного открытого ключа изменение пароля также требовало доказательства
владения закрытым ключом?


Разумеется, ДА!
А что касается просто отмены пароля по желанию пользователя...
– сейчас подумал и решил что тоже смысл имеет и хорошо было бы сделать.
Итоговый алгоритм примерно такой (пытаюсь на ходу сочинить):
Некоторый пользователь понимает что он работает с личного компьютера и у него всегда под рукой ключи.
Положим, он загрузил свой ключ в профиль.
За подписью случайной строки, решив усилить безопасность, он может включить
опцию "требовать подпись случайной строки при любом изменении информации в профиле,
(и тем более пароля), а также для смены загруженного ключа".
После этого возврата обратно уже нет. Продолбавшие приватный ключ регаются под другим юзером и начинают всё снова.
Пусть пользователь, использующий вышеназванный метод, решил что ему прийдётся временно,
например, поработатьв другой стране за чужим компьютером которому он всё же будет
доверять в плане написания постов и авторизации на pgpru.com. Тогда он за подписью случайной строки
может отключить опцию требования указанной подписи случайной строки для изменения информации в профиле, пароля и т.п.
Как-то так.
Может, кто что удачнее предложит.
Я, наприммер, не знаю свои пароли на защищённых сайтах – всегда копирую пас и вставляю его
в браузер. Итого хоть так – хоть так лезть в терминал...
— serzh (23/05/2007 02:20)   профиль/связь   <#>
комментариев: 232   документов: 17   редакций: 99
Именно такую схему я и имел ввиду =)
— SATtva (23/05/2007 14:59)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Опциональную авторизацию на сайте с помощью ключей я делать не хочу — это усложнит всю систему авторизации (в которой и так уже трудно разобраться) и потенциально может привести к ненужным ошибкам. На мой взгляд, наличие SSL делает подобную функциональность ненужной (разве что кому-то не придётся запоминать лишний пароль, но это для меня не слишком серьёзный стимул ;-).

В итоге будет так: при наличии в профиле ключа, смена самого ключа, а также пароля и мэйла будет требовать авторизации; всё остальное остаётся по-прежнему. Да, возможность подписи комментариев также будет реализована.
— SATtva (25/05/2007 14:59)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Всё сделано. Смена пароля и мэйла при наличии в профиле ключа требует подтверждения. Функция "вспоминания" пароля оставлена без изменения, т.е. если в настройках выбрано шифрование восстанавливаемого пароля, то ссылка с кодом восстановления будет выслана в зашифрованном виде (это и есть подтверждение владения ключом), но само задание нового пароля повторно подтверждать подписью не нужно.
— SATtva (31/05/2007 21:28)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Дизайн подсистемы подписи комментариев вызвал определённую трудность. Как я уже отмечал, тело всех страниц и комментариев хранится в базе данных в двух полях: одно поле (назовём его поле1) содержит "исходный текст", другое (поле2) — откомпилированный html (это оптимизация для ускорения загрузки страниц, дабы не приходилось форматировать wiki-разметку при каждом открытии). Также я отмечал, что делать ещё одно поле специально для подписанных сообщений я не хочу — слишком нерационально и слишком много изменений это за собой потянет.

Изначально я планировал, что исходный вариант с цифровой подписью pgp можно будет хранить в поле1. Однако, пересмотрев процедуру сохранения документов, обнаружил, что исходный текст перед сохранением тоже может претерпевать изменения (перезапись макросов и bbcode; последний сам же и делал), которые инвалидируют цифровую подпись. Кроме того, нельзя списывать со счетов и модераторскую правку.

Из этого родился третий вариант, по сути компромиссный. Перед сохранением документа, его исходный текст (ещё до отработки любых форматтеров) будет сохраняться в файл (вне БД) и, при необходимости, извлекаться для проверки ЭЦП. Но здесь есть один нюанс. Допустим, пользователь разместит такой подписанный комментарий. Модератор, найдя в нём нарушение, отредактирует его (в исходном тексте из поля1 уже не будет ЭЦП pgp, которую можно испортить). Тем не менее, при просмотре этого комментария, система будет по-прежнему сообщать, что подпись верна, поскольку оригинальный (подписанный) исходный текст, хранящийся в файле, не был затронут модерацией. Конечно, любой человек сможет запросить этот оригинал и лично сверить подпись, но, в целом, это не слишком очевидное решение: кто-то может решить, что именно изменённый вариант сообщения и был подписан.

Что думаете по этому поводу? Как этот нюанс можно обойти? И должен ли сайт в принципе сообщать достоверность подписи? (Я считаю, что должен, а также должен сообщать отпечаток ключа, которым была поставлена подпись.)

У меня есть одна идея. В случае подписанных комментариев мы ведь имеем две копии исходного текст: одна хранится в БД в поле1 и может быть изменена; другая — в файле и изменена быть не может. Когда система должна отобразить комментарий, она сверяет ЭЦП с копии из файла, а затем сравнивает обе копии: из файла и БД. Если копии не тождественны, выводится предупреждение с предложением открыть заверенный оригинал.
— Гость (01/06/2007 01:57)   <#>
предупреждение с предложением открыть заверенный оригинал.

А какой тогда смысл в модерации, если всё равно можно прочитать исходный вариант? Или такой и должна быть правильная модерация?
— serzh (01/06/2007 04:33)   профиль/связь   <#>
комментариев: 232   документов: 17   редакций: 99
Из этого родился третий вариант, по сути компромиссный. Перед сохранением документа, его исходный текст (ещё до отработки любых форматтеров) будет сохраняться в файл (вне БД) и, при необходимости, извлекаться для проверки ЭЦП. Но здесь есть один нюанс. Допустим, пользователь разместит такой подписанный комментарий. Модератор, найдя в нём нарушение, отредактирует его (в исходном тексте из поля1 уже не будет ЭЦП pgp, которую можно испортить). Тем не менее, при просмотре этого комментария, система будет по-прежнему сообщать, что подпись верна, поскольку оригинальный (подписанный) исходный текст, хранящийся в файле, не был затронут модерацией. Конечно, любой человек сможет запросить этот оригинал и лично сверить подпись, но, в целом, это не слишком очевидное решение: кто-то может решить, что именно изменённый вариант сообщения и был подписан.

При модерации лучше делать следующим образом:
1. извещать пользователя (документ был подписан, но изменён модератором)
2. удалять подписаный вариант из базы (если сообщение не соответствовало правилам форума или общепринятым нормам, то и к подписаному варианту доступ должен быть закрыт)
3. извещение автора о внесённых изменениях (если была исправлена ошибка, то он может просто переподписать документ)
— spinore (01/06/2007 07:48)   профиль/связь   <#>
комментариев: 1515   документов: 44   редакций: 5786
Вроде бы алгоритм предложенный SATtva логичный.
Вообще, на некоторых форумах давно практикуется уже: при удалении коментария
он помечается как удалённый и зарегистрированные пользователи могут его прочитать.
А по сути – да, исходное собщение должно оставаться навсегда.
— SATtva (01/06/2007 15:54)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
serzh,
Идея хорошая, но есть ряд проблем. В настоящее время может быть удалён или отредактирован только последний комментарий в ветке. Предположим, что модератор отредактировал сообщение пользователя — система срезала подпись и отправила пользователю предложение его переподписать. Но до того, как пользователь воспользовался этой возможностью, кто-то другой прокомментировал тему. Значит, человек уже не сможет открыть свой комментарий на редактирование и переподписать его.

(если сообщение не соответствовало правилам форума или общепринятым нормам, то и к подписаному варианту доступ должен быть закрыт)

Ну, если сообщение было настолько неуместно (скажем, спам), оно, как правило, просто удаляется, так что никаких его копий не остаётся в любом случае. Я сам редактирую пользовательские комментарии только для исправления разметки в них (цензурировал содержание лишь раз, удалив ссылку на сайт сомнительного содержания).
— SATtva (05/06/2007 17:11)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Исправлена критическая ошибка в методе ValidateToken(): программа не производила проверку аутентифицированной процедуры, что позволяло использовать токен из одной процедуры для авторизации другой (например, использовать токен, авторизующий загрузку ключа, для удаления ключа из профиля).
— spinore (05/06/2007 17:41)   профиль/связь   <#>
комментариев: 1515   документов: 44   редакций: 5786
Скоро впору будет писать эксплоиты для pgpru.com ^-)
— SATtva (05/06/2007 21:29)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Я только "за": быстрее ошибки отловим. Только перед публикацией эксплойта дайте недельку на исправление.
— spinore (06/06/2007 16:54)   профиль/связь   <#>
комментариев: 1515   документов: 44   редакций: 5786
Обязательно, SATtva, но это не ко мне. У меня квалификация не та чтоб писать эксплоиты.
— SATtva (07/06/2007 13:10, исправлен 31/08/2007 22:18)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Работа над подсистемой цифровой подписи комментариев завершена. (Обратите внимание на опцию "Обработать в сообщении цифровую подпись OpenPGP" под формой ввода комментария и редактирования страницы.) Тестирование убедительно прошу ограничить этой страницей.
— SATtva (07/06/2007 13:24)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Кстати, подписанные "эталонные" экземпляры хранятся не на диске, а в БД во вспомогательной таблице.
На страницу: 1, 2, 3, 4, 5, 6, 7, 8 След.
Ваша оценка документа [показать результаты]
-3-2-1 0+1+2+3