Это старая редакция страницы Разработки / Движок / Gnu P G за 19/03/2007 17:31.
Общая спецификация интеграции GnuPG [ЧЕРНОВИК]
Задачи
Задачами, решаемыми интеграцией GnuPG, являются:
- Валидация загружаемого пользователем (в свой профиль) открытого ключа на корректность с последующим внесением отпечатка в базу данных.
- Защита приватной связи между пользователями сайта.
- Защита отправляемого пользователю восстановленного пароля.
Возможность аутентификации на сайте с помощью протокола запрос-ответ (подписание пользователем строки запроса, сгенерированной сервером) не определяется в числе задач, поскольку, хотя и скрывает пароль от наблюдателя на канале связи, сама по себе не в силах предотвратить несанкционированный доступ к учетной записи пользователя с помощью перехваченных cookie и иными путями. Решение же данной проблемы — SSL — вообще делает подобную схему избыточной, поскольку защищает как реквизиты авторизованной сессии (cookie/SID) при работе с сайтом, так и реквизиты пользователя (логин/пароль) в момент аутентификации.
Тем не менее, список задач остается открыт для дополнений.
Функции
Для решения означенных задач требуется реализовать следующий набор функций (достаточность и необходимость всех функций из списка подлежит обсуждению).
Загрузка/валидация ключа
Загрузка ключа выполняется на странице персональных настроек уже после регистрации и успешного входа в систему. Загрузка на этапе регистрации не имеет смысла по той причине, что HTTP-POST-запрос как целое не подвергается цифровому подписанию, и потому может быть модифицирован злоумышленником, тогда как использование SSL снимает подобные риски и на этапе регистрации, и последующего логина и изменения настроек.
Все этапы загрузки/валидации ключа выполняются в контексте специальной временной связки. Если импортированный ключ удовлетворяет необходимым условиям (см. ниже), производится его автоматический перенос в основную связку сервера.
UploadPK(keyASCII)
Импортирует загруженный ключ на временную связку. Единственный аргумент, keyASCII, представляет собой загруженный пользователем блок открытого ключа, передаваемый в GnuPG через STDIN с параметром --import. Сразу после исполнения вызывается CheckPK(keyID), которая анализирует статус-коды GPG с целью определения пригодности ключа для зашифрования.
RecievePK(keyID[, ksURL])
Импортирует ключ с сервера ключей на временную связку. Передает GnuPG указанный пользователем (в профиле) ID ключа в параметре командной строки --recv-keys. Опциональный аргумент ksURL позволяет пользователю задать URL сервера ключей, вместо дефолтного subkeys.pgp.net. Как и в предыдущем случае сразу после исполнения вызывается CheckPK(keyID).
Вопросы: следует ли перед исполнением команды с параметром --recv-keys исполнить ее с --search-keys и, если сервер вернет более одно ключа, остановить программу и выдать предупрждение (к примеру, чтобы пользователь скорректировал запрос и указал keyID в 16-значном формате)? Альтернативным способом снятия двусмысленности может быть такой: показать пользователю результаты поиска и предложить выбрать принадлежащий ему ключ. Требуется протестировать альфу и изучить статус-коды, возвращаемые в различных описанных режимах.
CheckPK(keyID)
Анализирует статус-коды, возвращенные GnuPG сразу после импорта пользовательского ключа на временную связку. Проверяет следующие условия: 1) ключ содержит по крайней мере один шифровальный подключ и 2) базовый ключ и шифровальный подключ в данный момент действительны. Оба условия не имеют критического значения при загрузке ключа (пользователю выводится только предупреждение), но при восстановлении пароля или отправке приватного сообщения операция завершится неудачей.
Определить необходимые статус-коды, подлежащие проверке, и возвращаемые функцией коды ошибки в каждом конкретном случае.
AcceptPK(keyID)
Вызывается при импорте ключа в случае положительного ответа CheckPK() и переносит импортированный ключ с временной связки на главную.
RejectPK(keyID)
Вызывается при импорте ключа в случае отрицательного ответа CheckPK() и удаляет ключ с временной связки.
DeletePK(keyID)
Удаляет открытый ключ с главной связки.
FlushTemp()
Очищает временную связку ключей.
<...>
Реализация
Ограничивающим фактором в реализации схемы выступает запрет стандартной PHP-функции exec() (что является нормой для любого виртуального хостинга). Таким образом, взаимодействие с GnuPG может осуществляться только через интерфейс CGI. Использовать с этой целью CGI-сборку PHP нерационально, поэтому итоговая конструкция представляет собой:
Файл конфигурации gpg.conf
Поскольку GnuPG должен работать в полностью автономном режиме, видится необходимым следующий конфигурационный файл:
CGI-обертка openSpace-GPG
Ничего существенного:
Класс openSpace-GPG
<?php
/*
########################################################
## openSpace-GPG integration class ##
########################################################
NOTE: PHP 5.0.0 or later is required!
*/
class GPG
{
// VARIABLES
var $engine;
var $keyID;
var $fingerprint;
var $baseurl;
var $homedir;
var $tempdir;
var $cgiwdir;
var $stsfile;
// CONSTRUCTOR
function GPG(&$engine)
{
$this->engine = & $engine;
$this->baseurl = ( $this->engine->config['ssl'] == true ? str_replace('http://', 'https://', $this->engine->config['base_url']) : $this->engine->config['base_url'] );
$this->homedir = rtrim($this->engine->config['gpg_home'], '/');
$this->tempdir = rtrim($this->engine->config['gpg_temp'], '/');
$this->wrapper = trim($this->engine->config['gpg_wrapper'], '/');
$this->stfile = $this->homedir.'/status';
$this->srfile = $this->homedir.'/error';
}
// call openspace-gpg wrapper
// $request - additional gpg commant line parameters
// (except homedir and status-file)
// $method - passing method: post or get (default)
function Call($request, $method = 'get')
{
if ($method != 'get' && $method != 'post') $method = 'get';
$request = array(
'http' => array(
'method' => $method,
'header' => ( $method == 'post' ? 'Content-type: application/x-www-form-urlencoded' : '' ),
'content' => http_build_query(array(
'hd' => $this->homedir, // homedir
'sf' => $this->stfile, // status-file
'sr' => $this->srfile, // stderr
'cl' => $request) // command line params
) // end of content array
) // end of http array
); // end of request array
$context = stream_context_create($request);
$script = @fopen($this->baseurl.$this->wrapper, 'r', false, $context);
if (!$script)
{
die('openSpace-GPG: unable to open CGI wrapper.');
}
else
{
// reading output till the end
while (false === feof($script))
{
$result .= fgets($script, 1024);
}
}
fclose($script);
// throwing away appended error code value
return substr($result, 0, strrpos($result, "\n"));
}
// check gpg operation
function SelfCheck()
{
$gpg = $this->Call('--version');
if ($gpg == true)
{
$gpg = substr($gpg, 0, strpos($gpg, "\n"));
if (stristr($gpg, 'gpg (gnupg)') == true) return true;
else return false;
}
else
{
return false;
}
}
// Key import through webform
// function UploadPK($
// {
// }
}
?>