id:
Гость
вход
регистрация
текущее время 01:54 12/10/2008
главная
проект
новости
форум
faq
библиотека
черновики
разработки
сервисы
софт
поиск
Владелец:
SATtva
(создано 08/03/2007 19:01), редакция от 03/07/2007 21:34 (автор:
SATtva
)
Печать
Категории:
криптография
,
софт
,
gnupg
,
аутентификация
,
эцп
,
разное
,
расширения
,
сообщество
http://www.pgpru.com
/
Разработки
/
Движок
/GnuPG
>>>
Последние изменения
Последние комментарии
Удаленные документы
Требуется доработка
Досье пользователей
Опросы
Keyserver
Документация Wiki
Правила сайта
Регистрация
==Общая спецификация интеграции GnuPG== {{toc}} ===Задачи=== Задачами, решаемыми интеграцией GnuPG, являются: 1. Валидация загружаемого пользователем (в свой профиль) открытого ключа на корректность с последующим внесением отпечатка в базу данных. 2. Авторизация (по протоколу запрос-ответ) ряда чувствительных операций: смены пользовательского пароля доступа и почтового адреса. 2. Защита приватной связи между пользователями сайта. 3. Защита отправляемого пользователю восстановленного пароля. 4. Функциональное обеспечение публичного доступа к серверам ключей. 5. Функциональное обеспечение публичного декодировщика OpenPGP-пакетов. 6. Проверка цифровых подписей для пользовательских комментариев. Возможность аутентификации на сайте с помощью протокола запрос-ответ (подписание пользователем строки запроса, сгенерированной сервером) не определяется в числе задач, поскольку, хотя и скрывает пароль от наблюдателя на канале связи, сама по себе не в силах предотвратить несанкционированный доступ к учетной записи пользователя с помощью перехваченных 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 нерационально, поэтому итоговая конструкция представляет собой: %%(php) . openSpace Engine пользовательский уровень, графический интерфейс /\ || \/ openSpace-GPG engine class функциональная прокладка, реализующая необходимый набор функций ввода/вывода для взаимодействия с GnuPG и проверки результатов операций /\ || \/ openSpace-GPG CGI wrapper простейшая Perl-оболочка, "тупо" вызывающая GnuPG с переданными параметрами командной строки и воз- вращающая вывод программы верхнему уровню /\ || \/ GnuPG executable исполняемый файл программы, лежащий за пределами публичной www-директории сервера %% ====Временный контекст==== Все операции, требующие сохранения временных данных на диске сервера (по существу, это любые операции, поскольку статус-коды и сообщения об ошибках перенаправляются на диск всегда), выполняются в контексте временного каталога, создаваемого конструктором объекта по номеру PHP-сессии пользователя, и удаляемого деструктором объекта по окончании исполнения скрипта. Таким образом, весь парсинг и анализ входных и выходных данных должен производиться в текущем цикле исполнения движка, либо необходимо идти на особые ухищрения, как то сохранение необходимых данных в сессии пользователя (что не рекомендуется). ====Класс openSpace-GPG==== Основной функциональный класс. Вызов методов напрямую из класса без создания объекта невозможен без отработки конструктора и определения свойств. %% <?php // OPENSPACE-GPG INTEGRATION CLASS version 0.6 // (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: // nothing currently /* ######################################################## ## openSpace-GPG integration class ## ######################################################## NOTE: PHP 5.0 and GnuPG 1.4 or later required! */ class GPG { // VARIABLES var $engine; var $UID = NULL; // pubkey user ID var $keyID = NULL; // pubkey ID var $finger = NULL; // pubkey fingerprint var $secret = ''; // secret protection value var $context = ''; // keyring context var $override = ''; // override global command line and config parameters var $baseurl; // site url with a proper protocol var $homedir; // gpg homedir var $tempdir; // temporary data dir var $sessdir; // user session dir var $stfile; // gpg status file var $srfile; // gpg stderr file // CONSTRUCTOR function __construct(&$engine) { // defining main object properties $this->engine = & $engine; $this->secret = sha1($this->engine->config['system_seed'].'some_secret_value'); $this->baseurl = str_replace('https://', 'http://', $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.'/'.GPG_STATUS_NAME; $this->srfile = $this->sessdir.'/'.GPG_STDERR_NAME; // creating user session directory if (false === mkdir($this->sessdir)) { die('openSpace-GPG: unable to create user session directory.'); } else { // creating session files and setting appropriate privileges $file = @fopen($this->stfile, 'w'); @fclose($file); $file = @fopen($this->srfile, 'w'); @fclose($file); chmod($this->sessdir, 0777); chmod($this->stfile, 0777); chmod($this->srfile, 0777); } } ######################################################## ## Common functions ## ######################################################## // call openspace-gpg wrapper: executes gpg with passed // command line parameters and returns STDOUT. status codes // and STDERR are written into the out files and can be // processed later. // $request - additional gpg command line parameters // (except homedir and status-file) // $method - passing method: post or get (default) // $input - any data that is needed to be passed to // gpg in STDIN for processing (with // $method = 'post' only) function Call($request, $method = 'get', $input = '') { // defining http method if ($method != 'get' && $method != 'post') $method = 'get'; // preparing stdin data if ($method != 'post') $input = ''; else $input = base64_encode($input); // preparing http request $request = array( 'http' => array( 'method' => $method, 'header' => ( $method == 'post' ? 'Content-type: application/x-www-form-urlencoded' : '' ), 'content' => http_build_query(array( 'pv' => $this->secret, // protection value 'hd' => $this->homedir, // homedir 'sf' => $this->stfile, // status-file 'sr' => $this->srfile, // stderr 'cl' => $this->override.' '.$request, // command line params 'st' => $input) // stdin data ) // 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); // here is what gpg has returned return str_replace("\r", '', trim($result)); } } // sanitize user input string and prepare it for // use in gpg command line variables. we don't want // to allow arbitrary commands execution, right? function PrepareInput($str, $len = 255) { if (!$str = trim(html_entity_decode($str), ' -')) return ''; else if (strpos($str, "\n")) return ''; else if ($sp = strpos($str, ' ')) $str = substr($str, 0, $sp); return substr($str, 0, $len); } // define keyring context. empty value means default keyring. // special 'temp' value means temporary ring being cleared // out by the object destructor. anything else is a keyring // filename under gpg_home dir (be careful not to point out // to any existing file) function SetContext($new = '') { if ($new == '') return $this->context = ''; else if ($new == 'temp') return $this->context = "--no-default-keyring --keyring {$this->sessdir}/".GPG_TEMP_RING_NAME; else return $this->context = "--no-default-keyring --keyring {$this->homedir}/".$new; } // generate an unique challenge token C as follows: // C = T,P,H(S,U,T,P) // where T - unix timestamp, P - procedure ID (see below), // S - system secret value, U - user session ID. because // we use SID value in MAC calculation, an attacker wishing // to replay signed user token needs to somehow intercept // a whole user session (not only session vars) and present // it along with the other HTTP-POST data. this always poses // some risk, so we are putting timestamp in, too (this // measure narrows potential vulnerability to replay attack // to a more restricted window: see $expiry argument of the // next method). // // $procedure specifies exact operation where challenge- // response protocol is utilised. this needs to be some // simple identification string. currently defined are: // 'uploadpk' - uploading a key into the user profile // 'deletepk' - removing a key from the profile // 'changepwd' - changing password for logged in user // 'changemail' - changing email address function CreateToken($procedure) { $time = time(); // in the clear part we use value separator for better // handling in the token validation method (see below). // hash context goes as single concatenated string return $token = "$time|$procedure\n".sha1($this->engine->config['system_seed'].session_id().$time.$procedure); } // check whether challenge token is correct and did not // expired ($expiry argument in minutes). expiration // parameter is used to prevent replay attacks in case // user session still didn't expired or an attacker has // managed to intercept user session. // NB: $token must be passed in the same form as it was // produced by CreateToken() method (e.g. without any pgp // boilerplates). signature verification is beyond the // scope of this function function ValidateToken($token, $procedure, $expiry = 5) { // parsing passed token, checking proper syntax if (is_array($strings = explode("\n", $token))) { list($tokenTime, $tokenProc) = explode('|', $strings[0]); $tokenMAC = $strings[1]; // something's wrong with the input if (!$tokenTime || !$tokenProc || !$tokenMAC) return false; } else return false; // recalculating MAC $newMAC = sha1($this->engine->config['system_seed'].session_id().$tokenTime.$tokenProc); // validating conditions. exact order is crucial! if ($tokenMAC !== $newMAC) { // MAC mismatch return false; } else if ($tokenProc !== $procedure) { // procedure mismatch return false; } else if (time() > ($expiry * 60 + $tokenTime)) { // token expired return false; } else { return true; } } // get gpg status codes of the last operation. returns // multidimensional array with [GNUPG:] string thrown out function GetStatus() { $statusfile = @fopen($this->stfile, 'r'); // read status file if (!$statusfile) { return false; } else { while (false === feof($statusfile)) { $statuscodes .= fread($statusfile, 1024); } fclose($statusfile); } if ($statuscodes) { $len = strlen(GPG_STATUS_LINE); $rows = explode("\n", str_replace("\r", '', $statuscodes)); foreach ($rows as $row) if (substr($row, 0, $len) == GPG_STATUS_LINE) $matrix[] = explode(' ', substr($row, $len)); return $matrix; } else return false; } // in debug mode returns gpg STDERR contents (stub string // otherwise) of false if no gpg error function GetError() { $errorfile = @fopen($this->srfile, 'r'); // read status file if (!$errorfile) { return false; } else { while (false === feof($errorfile)) { $errorcodes .= fread($errorfile, 1024); } fclose($errorfile); } if ($errorcodes) { if ($this->engine->config['gpg_debug'] == true) return nl2br("OpenSpace-GPG terminated, error output follows:\n------\n". str_replace("\r", '', $errorcodes)); else return GPG_GENERAL_ERROR; } else return false; } // get parseable key details listing. returns multidimensional // array with 'tru' (trustdb) values excepted (empty array if // selected key is absent). function GetList($keyID = '') { $keyID = $this->PrepareInput($keyID, 42); if (!$keyID) $keyID = '0x'.$this->finger; if (!$keyID) $keyID = $this->keyID; if (!$keyID) return false; if ($list = $this->Call("{$this->context} --list-public-keys $keyID")) { $rows = explode("\n", $list); foreach ($rows as $row) if ($row && substr($row, 0, 3) != 'tru') $matrix[] = explode(':', $row); return $matrix; } else return false; } // define pubkey-related object properties from gpg status // codes ('status') or key listing ('list'). // $keyID is required only for $source = 'list' function DefineKey($source, $keyID = '') { if ($source == 'status') { if (false === $status = $this->GetStatus()) { return false; } else foreach ($status as $code) { if ($code[0] == 'IMPORTED') { $this->keyID = $code[1]; // recompose primary user ID into a single string $temp = $code; unset($temp[0], $temp[1]); $this->UID = implode(' ', $temp); } else if ($code[0] == 'IMPORT_OK') { $this->keyID = '0x'.substr($code[2], -16); $this->finger = $code[2]; } } return true; } else if ($source == 'list') { if (false === $list = $this->GetList($keyID)) { return false; } else foreach ($list as $row) { if ($row[0] == 'pub') { $this->keyID = '0x'.$row[4]; } else if ($row[0] == 'fpr') { $this->finger = $row[9]; } else if ($row[0] == 'uid' && !$definedUID) { $this->UID = str_replace('\x3a', ':', $row[9]); $definedUID = true; } } return true; } } ######################################################## ## Special functions ## ######################################################## // check gpg operation and version number. returns one of // these error code values: // 0 - everything's okey // 1 - no output from the cgi backend // 2 - no gpg version string returned // 3 - gpg detected but of an older version than is required function SelfCheck() { if ($gpg = $this->Call('--list-config')) { $gpg = explode("\n", $gpg); // no gpg version string in the wrapper output if (is_array($gpg) === false || false === is_array($sub = explode(':', $gpg[0]))) { return 2; } else { if ($sub[0] == 'cfg' && $sub[1] == 'version') { // version number requirement is not met if ($sub[2] < GPG_VERSION_MIN) return 3; } else { return 2; } } } else { // no output from the backend wrapper return 1; } // everything's okey return 0; } // make sure an imported key is suitable for encryption. // checks the following conditions are met: // - at least one encryption subkey is present, not expired, // and not revoked // - base key is not expired, and not revoked // returns these error code values: // 0 - everything's okey // 1 - not suitable for encryption (encryption subkey is // absent, expired or revoked) // 2 - pubkey is unusable (expired or revoked) // 3 - no key found in GetList() output function CheckPK($keyID) { $pubkey = false; $subkey = false; if (false == $list = $this->GetList($keyID)) { // no key found return 3; } else foreach ($list as $row) { // check for base key usability if ($row[0] == 'pub') { if ( ($row[1] != 'r' && $row[1] != 'e') && // check for revocation/expiry status ($row[5] < time() && ($row[6] == '' || $row[6] > time())) ) // check creation and expiration dates { $pubkey = true; } } // check for subkey presence and usability else if ($row[0] == 'sub') { if ( ($row[1] != 'r' && $row[1] != 'e') && // check for revocation/expiry status (strpos($row[11], 'e') !== false) && // check that subkey is intended for encryption ($row[5] < time() && ($row[6] == '' || $row[6] > time())) ) // check creation and expiration dates { $subkey = true; } } } // return codes if ($pubkey === false) return 2; // public key is unusable if ($subkey === false) return 1; // key is not suitable for encryption // key is okey return 0; } // import key onto the temp keyring through webform for // later processing. be warned that imported key must // be processed in the current cycle before object destructor // cleans things up function UploadPK($keyblock) { // import key $_context = $this->context; $this->Call($this->SetContext('temp')." --import", 'post', $keyblock); $this->context = $_context; if (false === $error = $this->GetError()) return true; else die($error); } // download selected key from the public keyserver. keyserver // name may be passed along the key ID. returns one of the // following: // true - defined key (and defined key *only*) was // downloaded successfully. // false - defined key wasn't found on server. // array() - indexed array (if more than one key was // found; of no use currently :-( // [0] - username // [1] - fingerprint function RecievePK($keyID, $keyserver = '') { // sanitizing user input if (!$keyserver = $this->PrepareInput($keyserver)) $keyserver = $this->engine->config['gpg_server']; $keyID = $this->PrepareInput($keyID, 42); // requesting key from the keyserver $_context = $this->context; $this->Call($this->SetContext('temp')." --keyserver $keyserver --recv-key $keyID"); $this->context = $_context; // loading gpg status codes if (false === $status = $this->GetStatus()) { return false; } else foreach ($status as $index => $code) { if ($status[$index][0] == 'IMPORTED') { $i++; // defining first output array element: UID $temp = $code; unset($temp[0], $temp[1]); $output[$i][0] = implode(' ', $temp); // defining second output array element: fingerprint. // however in case of import error we need to clear // output set in the current cycle. if ($status[$index + 1][0] == 'IMPORT_OK') { $output[$i][1] = $status[$index + 1][2]; } else { unset($output[$i--]); } } else if ($status[$index][0] == 'IMPORT_RES') { // check how many keys was imported to determine // function's resulting output if ((int)$status[$index][1] === 0) $result = false; else if ((int)$status[$index][1] === 1) $result = true; else if ((int)$status[$index][1] > 1) $result = $output; } } return $result; } // move selected key from a temp keyring to the main keyring. // $keyID variable is necessary to not allow passing of multiple // uploaded keys function AcceptPK($keyID) { $_context = $this->context; $keyID = $this->PrepareInput($keyID, 42); $pack = $this->Call($this->SetContext('temp')." --export $keyID"); $this->context = $_context; $this->Call("{$this->context} --import", 'post', $pack); if (false === $error = $this->GetError()) return true; else die($error); } // remove selected public key from the main keyring. // in order to avoid ambiguity passing key fingerprint // is needed. function DeletePK($fingerprint) { if (strlen($fingerprint = $this->PrepareInput($fingerprint, 42)) < 42) return false; $this->Call("{$this->context} --delete-key $fingerprint"); if (false === $error = $this->GetError()) return true; else die($error); } // extract selected public key from the keyring function GetPK($keyID) { $keyID = $this->PrepareInput($keyID, 42); $key = $this->Call("{$this->context} --export $keyID"); if (false === $error = $this->GetError()) return $key; else die($error); } // encrypt passed plaintext data with selected public key function EncryptMsg($plaintext, $keyID) { $keyID = $this->PrepareInput($keyID, 42); $ciphertext = $this->Call("{$this->context} --recipient $keyID --encrypt", 'post', $plaintext); if (false === $error = $this->GetError()) return $ciphertext; else die($error); } // verify passed data. returns indexed array with the following elements: // [0] - status: true, false or null (in case of error) // [1] - primary key FPR // [2] - signature creation unix timestamp // [3] - signature ID // [4] - signed message body // [5] - additional status: // 0 = not relevant // 1 = expired key // 2 = expired signature // 3 = revoked key // elements [1]-[3] may be null if input error encountered function VerifyMsg($data) { // we are using '-v' specifically to catch clear message // after signature verification is complete $body = $this->Call("{$this->context} -v", 'post', $data); $results = array( false, // [0] NULL, // [1] NULL, // [2] NULL, // [3] $body, // [4], always defined 0, // [5] ); // checking status codes of the verification operation if (false === $status = $this->GetStatus()) { // something's wrong, aborting $results[0] = NULL; return $results; } else foreach ($status as $code) { if ($code[0] == 'VALIDSIG') { // defining output elements for good sig $results[0] = true; $results[1] = $code[10]; $results[2] = $code[3]; } else if ($code[0] == 'BADSIG') { // in case of bad signature we return // long keyID, not a full FPR! $results[1] = $code[1]; } else if ($code[0] == 'ERRSIG') { // signature verification error (no pubkey?). // only long keyID is returned! $results[0] = NULL; $results[1] = $code[1]; $results[2] = $code[5]; } else if ($code[0] == 'EXPKEYSIG') { // signature with expired key $results[5] = 1; } else if ($code[0] == 'EXPSIG') { // signature itself is expired $results[5] = 2; } else if ($code[0] == 'REVKEYSIG') { // signing key is revoked $results[5] = 3; } else if ($code[0] == 'SIG_ID') { // defining remaining output element: sigID // (if applicable) $results[3] = $code[1]; } else if ($code[0] == 'ERROR' || $code[0] == 'NODATA') { // input error encountered, aborting $results[0] = NULL; return $results; } } return $results; } // print packets listing for the given data // which may be passed as a plain text or a // binary object function DecodePackets($data) { return $this->Call('--list-packets', 'post', $data); } // send the given key to the keyserver. despite // returning Call() results function returns nothing, // and gpg status codes is empty too function SendPK($keyID, $keyserver = '') { // defining keyserver if (!$keyserver) $keyserver = $this->engine->config['gpg_server']; else $keyserver = $this->PrepareInput($keyserver); $keyID = $this->PrepareInput($keyserver, 42); return $this->Call("{$this->context} --keyserver $keyserver --send-keys $keyID"); } // search $string on a public $keyserver. // returns associative array with keyIDs as keys // and indexed arrays as values. subarrays' values // are as follows: // [0] - (str) primary key type // [1] - (int) key length // [2] - (int) key creation timestamp // [3] - (str) status: // 'r' - revoked // [4] - (array): // (str) UID => (int) UID creation timestamp function SearchPK($string, $keyserver = '') { // matches limit $max = 50; // defining keyserver if (!$keyserver) $keyserver = $this->engine->config['gpg_server']; else $keyserver = $this->PrepareInput($keyserver); // requesting key search if ($list = $this->Call("--keyserver $keyserver --search-key $string")) { $rows = explode("\n", $list); array_shift($rows); // how many matches we've got (hoping // server returned matches count)? if (false === is_array($row = explode(':', $rows[0]))) { return false; } else if ($row[0] == 'info') { // break flooding searches if ($max < $i = (int)$row[2]) return false; $n = 0; array_shift($rows); } // filling results array $results = array(); if (!isset($i) || $i > 0) foreach ($rows as $row) { $cells = explode(':', $row); // new pubkey element if ($cells[0] == 'pub') { if (isset($i) && ++$n > $i) break; // this should not going to happen in the normal course of action, but what could be... if ($n > $max) return false; // break flooding searches switch ($cells[2]) { case '1': $type = 'RSA'; break; case '3': $type = 'RSA-S'; break; case '17': $type = 'DSA'; break; default: $type = 'Undefined'; } $results[$keyID = $cells[1]] = array( 0 => $type, 1 => (int)$cells[3], 2 => (int)$cells[4], 3 => trim($cells[6]), 4 => array() ); } // uid for the current pubkey element else if ($cells[0] == 'uid') { $results[$keyID][4][$cells[1]] = (int)$cells[2]; } } return $results; } else { return false; } } // DESTRUCTOR function __destruct() { // flush session dir in the end of script execution if ($dh = opendir($this->sessdir)) { while (false !== ($filename = readdir($dh))) { if (is_dir($file = $this->sessdir.'/'.$filename) !== true) unlink($file); } closedir($dh); rmdir($this->sessdir); } } } ?> %% ====Файл конфигурации ##gpg.conf##==== Поскольку GnuPG должен работать в полностью автономном режиме, видится необходимым следующий конфигурационный файл. Корректирующие параметры могут быть переданы как аргументы командной строки (например, ##--no-default-keyring##). %% # Stand-alone GPG configuration for openSpace-GPG force-mdc no-greeting no-emit-version no-auto-check-trustdb no-secmem-warning trust-model always quiet batch yes armor textmode with-colons fixed-list-mode with-fingerprint comment openSpace-GPG - http://openspace.vladmiller.info keyserver-options no-auto-key-retrieve honor-keyserver-url import-options import-minimal export-options export-minimal personal-cipher-preferences TWOFISH AES256 AES192 BLOWFISH CAST5 AES 3DES personal-digest-preferences SHA512 SHA384 SHA256 RIPEMD160 SHA1 MD5 personal-compress-preferences BZIP2 ZLIB ZIP Uncompressed %% ====CGI-обертка openSpace-GPG==== Идея использования пайпа для передачи данных GnuPG из стандартного ввода принадлежит ((username:ПэГусев ПэГусеву)), за что ему особая благодарность. В остальном обертка не содержит ничего существенного: HTTP-ввод разбирается на пары ##переменная=значение##, вывод GPG открывается как файловый дескриптор и подается на стандартный вывод сервера для последующего перехвата вызывающей программой (конкретно, методом ##Call()## класса; см. выше). Для защиты от выполнения произвольных команд GnuPG путем прямого вызова скрипта вызывающее приложение должно передать секретное значение (HTTP-переменная ##pv##), равное значению переменной ##$protection##, заданной в скрипте. %% #!/usr/bin/perl # OPENSPACE-GPG CGI WRAPPER version 1.0 # (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 # Input processing code based on Quick Survey 1.4 # Copyright 1999-2003 CGI-Factory.com TM # A subsidiary of SiliconSoup.com LLC # Web site: http://www.cgi-factory.com # E-Mail: cgifactory@cgi-factory.com # Released Date: June 24, 2003 # Processing input if ($ENV{'REQUEST_METHOD'} eq 'GET') { @pairs = split(/&/, $ENV{'QUERY_STRING'}); } else { read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); @pairs = split(/&/, $buffer); } foreach $pair (@pairs) { ($name, $value) = split(/=/, $pair); $name =~ tr/+/ /; $name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $value =~ s/ /\s/g; if ($name && $value) { $input{$name} = $value; } } print "Content-Type: text/plain "; # Arbitrary execution protection $protection = "secret"; if ($input{'pv'} eq "" || $input{'pv'} ne $protection) { die(); } # Decomposing Base64 input (aka "quazi-STDIN") if ($input{'st'}) { use MIME::Base64 (); $pack = MIME::Base64::decode($input{'st'}); } # Envoking GPG. # Homedir and status file paths must be passed by the calling # application. # We are using piping to trick GPG into using passed data as # STDIN, thus allowing to process anything in one call with no # temporary files involved (such "STDIN" is have to be base64- # encoded). Credit for the idea goes to PeGusev @ pgpru.com. # STDOUT can be captured by the calling application for later # processing. No error code values are returned (rely on status # codes and STDERR if necessary). open(GPGIN, "| gpg --homedir $input{'hd'} --status-file $input{'sf'} $input{'cl'} 2>$input{'sr'}"); print GPGIN "$pack "; close(GPGIN); %%
Ваше имя:
Запомнить псевдоним (сохранить в cookie)
OpenPGP-подписанный текст в кодировке
CP1251 (Windows)
UTF-8
KOI8-R
CP866 (DOS)
KOI8-U
Помощь
Сохранить параметры OpenPGP в cookie
Пожалуйста, напишите, кого/что вы видите
на изображенной слева картинке. Если
там несколько персонажей/предметов,
перечислите их в именительном падеже
через пробел (одинаковых приводите во
множественном числе).
(осталось попыток на решение теста: 3)
Поддержка
BBCode
включена
Нормы пользования
. Некоторые права на материалы сайта защищены по условиям лицензии CreativeCommons. Движок
openSpace 0.8.24a
и дизайн сайта © 2006-2007
Vlad "SATtva" Miller
.