id: Гость   вход   регистрация
текущее время 01:51 02/05/2024
Владелец: SATtva редакция от 20/03/2007 20:00 (автор: SATtva) Печать
создать
просмотр
редакции
ссылки

Это старая редакция страницы Разработки / Движок / Gnu P G за 20/03/2007 20:00.


Общая спецификация интеграции GnuPG [ЧЕРНОВИК]


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

Задачи


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


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

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


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

Функции


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

Загрузка/валидация ключа


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


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

UploadPK(keyASCII)

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

RecievePK(keyID[, ksURL])

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


Вопросы: следует ли перед исполнением команды с параметром --recv-keys исполнить ее с --search-keys и, если сервер вернет более одно ключа, остановить программу и выдать предупрждение (к примеру, чтобы пользователь скорректировал запрос и указал keyID в 16-значном формате)? Альтернативным способом снятия двусмысленности может быть такой: показать пользователю результаты поиска и предложить выбрать принадлежащий ему ключ. Требуется протестировать альфу и изучить статус-коды, возвращаемые в различных описанных режимах.

CheckPK(keyID)

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

AcceptPK(keyID)

Вызывается при импорте ключа в случае положительного ответа CheckPK() и переносит импортированный ключ с временной связки на главную.

RejectPK(keyID)

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

DeletePK(keyID)

Удаляет открытый ключ с главной связки.

FlushTemp()

Очищает временную связку ключей.


<...>

Реализация


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


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

Класс openSpace-GPG


<?php

// OPENSPACE-GPG INTEGRATION CLASS version 0.0.2
// (c) 2007 SATtva, http://www.vladmiller.info
// Originally for "openPGP in Russia" http://www.pgpru.com
// Licensed under GNU General Public License (GPL)
// http://opensource.org/licenses/gpl-license.php

// ToDo:
// - a whole pile of!

/*

########################################################
##          openSpace-GPG integration class           ##
########################################################

    NOTE: PHP 5.0.0 or later is required!

*/

class GPG
{
    
// VARIABLES
    
var $engine;
    var 
$UID        NULL;        // pubkey user ID
    
var $keyID        NULL;        // pubkey ID
    
var $finger        NULL;        // pubkey fingerprint
    
var $baseurl;                // site url with proper protocol
    
var $homedir;                // gpg homedir
    
var $tempdir;                // temporary data dir
    
var $sessdir;                // user session dir
    
var $cgiwdir        'cgi-bin/openspace-gpg/wrapper';    // cgi wrapper location
    
var $stfile;                // gpg status file
    
var $srfile;                // gpg stderr file

    // CONSTRUCTOR
    
function __construct(&$engine)
    {
        
// defining object main properties
        
$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->sessdir    $this->tempdir.'/'.session_id();
        
$this->stfile    $this->sessdir.'/status';
        
$this->srfile    $this->sessdir.'/error';
        
        
// creating user session directory
        
if (false === @mkdir($this->sessdir0777true))
        {
            die(
'openSpace-GPG: unable to create user session directory.');
        }
    }
    
    
// 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($script1024);
            }
            
fclose($script);
        }
        
        
// throwing away appended error code value
        
return substr($result0strrpos($result"\n"));
    }
    
    
// get gpg status codes of the last operation
    
function GetStatus()
    {
        
$statusfile = @fopen($this->stfile'r');
        
        
// read status file
        
if (!$statusfile)
        {
            return 
false;
        }
        else
        {
            while (
false === feof($statusfile))
            {
                
$statuscodes .= fread($statusfile1024);
            }
            
fclose($statusfile);
        }
        
        if (
$statuscodes)
        {
            
$rows explode("\n"str_replace("\r"''$statuscodes));
            foreach (
$rows as $row) if (substr($row09) == '[GNUPG:] '$matrix[] = explode(' 'substr($row9));
            return 
$matrix;
        }
        else return 
false;
    }
    
    
// get parseable key details listing
    
function GetList($keyID '')
    {
        if (!
$keyID$keyID $this->finger;
        if (!
$keyID$keyID $this->keyID;
        if (!
$keyID) return false;
        
        if (
$list $this->Call('--with-colons --fixed-list-mode --with-fingerprint --list-public-keys '.$keyID))
        {
            
$rows explode("\n"str_replace("\r"''$list));
            foreach (
$rows as $row) if (substr($row03) != 'tru'$matrix[] = explode(':' $row);
            return 
$matrix;
        }
        else return 
false;
    }
    
    
// define pubkey-related object properties from gpg status
    // codes ('status') or key listing ('list', needs $keyID)
    
function DefineKey($source$keyID '')
    {
        if (
$source == 'status')
        {
            if (
false === $status $this->GetStatus()) return false;
            
            foreach (
$status as $code)
            {
                if (
$code[0] == 'IMPORTED')
                {
                    
$this->keyID $code[1];
                    
// recompose primary user ID
                    
$temp $code;
                    unset(
$temp[0], $temp[1]);
                    
$this->UID implode(' '$temp);
                }
                else if (
$code[0] == 'IMPORT_OK')
                {
                    
$this->keyID    substr($code[2], -16);
                    
$this->finger    $code[2];
                }
            }
            return 
true;
        }
        else if (
$source == 'list')
        {
            if (
false === $list $this->GetList($keyID)) return false;
            
            foreach (
$list as $row)
            {
                if (
$row[0] == 'pub'$this->keyID    $row[4];
                if (
$row[0] == 'fpr'$this->finger $row[9];
                if (
$row[0] == 'uid' && !$definedUID)
                {
                    
$this->UID    $row[9];
                    
$definedUID    true;
                }
            }
            return 
true;
        }
    }
    
    
// check gpg operation
    
function SelfCheck()
    {
        
$gpg $this->Call('--version');

        if (
$gpg == true)
        {
            
$gpg substr($gpg0strpos($gpg"\n"));
            
            if (
stristr($gpg'gpg (gnupg)') == true) return true;
            else return 
false;
        }
        else
        {
            return 
false;
        }
    }
    
    
// import key onto the temp keyring through webform
    
function UploadPK($keyblock)
    {
        
// save pubkey into the temp file.
        #########################################################
        #  is there a way to pass it through the wrapper along  #
        #  with other command line parameters in one call?      #
        #########################################################
        
$tempfile = @fopen($this->sessdir.'/tempfile''w');
        
        if (!
$tempfile)
        {
            die(
'openSpace-GPG: unable to open temporary file with write access.');
        }
        else
        {
            
fwrite($tempfile$keyblock262144);
            
fclose($tempfile);
        }
        
        
// import key
        
$this->Call("--no-default-keyring --keyring {$this->tempdir}/tempring --import {$this->sessdir}/tempfile");
    }
    
    
// makes sure imported key is suitable for encryption
    
function CheckPK()
    {
    }
    
    
// DESTRUCTOR
    
function __destruct()
    {
        
// flush session dir
        
clearstatcache();
        if (
$dh opendir($this->sessdir))
        {
            while (
false !== ($filename readdir($dh)))
            {
                if (
is_dir($file $this->sessdir.'/'.$filename) !== trueunlink($file);
            }
            
closedir($dh);
            
rmdir($this->sessdir);
        }
    }
}

?>

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


Поскольку GnuPG должен работать в полностью автономном режиме, видится необходимым следующий конфигурационный файл:


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


Ничего существенного: