id: Гость   вход   регистрация
текущее время 23:31 28/03/2024
Владелец: SATtva (создано 27/01/2015 17:51), редакция от 18/03/2015 21:51 (автор: SATtva) Печать
Категории: софт, gnupg, openpgp, инфобезопасность, стандарты, свободный софт, разграничение доступа
создать
просмотр
редакции
ссылки

GPG Remote


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

Постановка проблемы


Использование GnuPG в сетевой среде всегда несёт риск того, что противник, способный скомпрометировать какое-либо из клиентских приложений (email-клиент, IM-клиент и т.п.), сможет с лёгкостью извлечь закрытые ключи, выполнив gpg --export-secret-keys. Смарткарты являются общепринятым решением данной проблемы, но сами они, в свою очередь, являются специализированными устройствами, которые 1) не всегда могут быть доступны пользователю либо 2) могут быть недоверяемы по тем или иным причинам.

Предлагаемое решение


GPG Remote — это клиент-серверное приложение, переносящее операции с закрытыми ключами GnuPG на удалённый сервер, исполняемый в безопасной доверенной системе. Сервер отфильтровывает клиентский ввод в соответствии с заданными правилами и запускает GnuPG, выступая посредником между программой и клиентом.


GPG Remote позволяет разделить процесс исполнения GnuPG между клиентом-фронтэндом и сервером-бекэндом. Клиентская часть приложения эмулирует консольный интерфейс GnuPG: принимает аргументы командной строки и данные, передаваемые пользователем через стандартный ввод (STDIN). Затем производится парсинг консольных аргументов и определение файлов, которые пользователь хотел передать GnuPG для обработки. Из этих данных формируется пакет запроса, который передаётся серверу.


Сервер работает в доверенной среде, его задача — выполнить gpg безопасным образом. Этой цели служит белый список консольных опций gpg, согласно которому сервер отфильтровывает полученные от клиента аргументы командной строки (прежде всего, команды типа --export-secret-keys). Клиентские файлы сохраняются во временную директорию, а их пути в аргументах командной строки соответствующим образом заменяются. Наконец, сервер вызывает gpg, а вывод программы (состоящий из потоков STDERR и STDOUT, кода завершения программы, а также сгенерированных файлов) посылается обратно клиенту.

Установка


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


Для запуска клиента GPG Remote в качестве замены системному gpg необходимо переименовать скрипт gpgremote_client.py в /usr/bin/gpg либо поставить символическую ссылку с указанного пути на файл скрипта. Если клиентский и серверный компоненты запускаются на одной системе, убедитесь, чтобы связки ключей GnuPG были доступны (на чтение и запись) только пользователю, из-под которого запускается сервер.


Если требуется поддержка удалённого ввода парольных фраз (при запуске клиента и сервера на отдельных системах), сделайте следующее:


  1. Убедитесь, что на клиенте установлено стандартное приложение pinentry.
  2. Установите на клиентскую и серверную системы библиотеку pyassuan.
  3. Корректно настройте и запустите gpg-agent на серверной системе. При запуске gpg-agent укажите в опции --pinentry-program путь к pinentry.py из дистрибутива GPG Remote (для дополнительной информации см. man gpg-agent).

Для поддержки "тревожных" команд (см. одноимённый раздел ниже) установите на серверной системе Python-модуль pbkdf2.

Настройка


Клиент считывает свои настройки (в частности, хост и порт сервера) из файла gpgremote_client.conf, находящегося в директории ~/.gnupg. Этот путь можно переопределить с помощью переменной окружения GNUPGHOME.


По умолчанию, сервер берёт свои настройки из файла gpgremote_server.conf, также находящегося в директории ~/.gnupg (если этот путь не был переопределён с помощью переменной окружения GNUPGHOME), однако с помощью опции -c/--config можно указать другой путь и имя файла конфигурации. Большинство других параметров сервера также можно изменить из командной строки (используйте -h/--help для вывода всех опций, доступных для запуска сервера).

Белый список


Вторая часть серверных настроек — это белый список опций gpg, находящийся в файле whitelist.conf в одной директории с конфигурационным файлом. Его формат достаточно прост, но правильная настройка имеет критически важное значение для безопасности сервера (см. раздел "Меры безопасности"):


  1. Строки, не начинающиеся со знака тире, игнорируются.
  2. Каждая строка содержит один набор опций.
  3. Набор может быть представлен одной опцией в короткой (с одним тире) или в длинной форме (с двумя тире), либо разделёнными пробелом несколькими опциями в короткой или длинной форме (в произвольном порядке).
  4. Если набор содержит слова, не начинающиеся с тире, то они имеют следующее значение:
    • Слово в квадратных скобках является произвольным параметром — опции в данном наборе считаются параметеризуемыми.
    • Слово без квадратных скобок является разрешённым значением параметра — опции в данном наборе считаются параметеризуемыми и могут принимать параметр только в указанном значении. Если требуется разрешить несколько значений параметра, их необходимо привести в этой же строке через пробел (поддерживаются кавычки и экранирование пробелов).
    • Слово #NO_FILES в квадратных скобках устанавливает для данного набора опций флаг "без файлов" — программа не будет рассматривать переданные аргументы командной строки в качестве имён файлов (см. раздел "Меры безопасности").

Одноразовые пароли


Одноразовые пароли (OTP) — это дополнительная мера безопасности для защиты закрытых ключей. После её активации при использовании закрытого ключа пользователь должен будет ввести короткий случайный пароль (из заранее сгенерированного списка) наряду с основной парольной фразой. Поскольку для каждой операции такой пароль уникален, это препятствует использованию закрытого ключа со стороны противника, даже если он перехватит парольную фразу и попытается воспользоваться ключом через GPG Remote. (Обратите внимание: для поддержки данной функции необходимо использовать pinentry.py из состава GPG Remote. См. выше раздел "Установка".)


Для использования данной функции, активируйте её в файле настроек сервера или опцией --otp при запуске сервера. После этого вызовите сервер с опцией --gen-otp и укажите, какое число одноразовых паролей требуется сгенерировать. Чем длиннее будет список, тем реже его надо будет обновлять, но и тем больше операций сможет совершить противник, если этот список окажется скомпрометирован. Обратите внимание, что в любой момент можно сгенерировать новый список паролей, и все пароли, которые оставались в предыдущем списке, будут аннулированы; после генерации нового списка перезапуск сервера не требуется.


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


Учтите, что кэширование парольных фраз в gpg-agent обходит механизм OTP: пока парольная фраза закэширована, ключ может применяться без всяких запросов к пользователю.

"Тревожные" команды


Существует возможность настройки произвольного количества так называемых "тревожных", или экстренных, команд. Эти команды (в сущности, shell-команды любого вида) исполняются на стороне сервера, если в диалог pinentry введена определённая парольная фраза. (Обратите внимание: для поддержки данной функции необходимо использовать pinentry.py из состава GPG Remote. См. выше раздел "Установка".)


Правила для "тревожных" команд задаются специальными пунктами в конфигурационном файле сервера. Имя правила должно быть уникальным и начинаться с префикса panic_. Значение правила должно состоять из защитного токена и следующей далее через пробел shell-команды или нескольких команд в обычном виде (иными словами, без кавычек, экранирования и т.п.) либо специальной команды (см. ниже). Защитный токен представляет собой PBKDF2-хэш от парольной фразы, которая должна вызывать срабатывание правила. Чтобы получить такой токен, запустите сервер с опцией --gen-token и введите парольную фразу и, если нужно, число итераций хэширования.


Одна парольная фраза может вызывать срабатывание любого числа правил, если все они используют эту фразу (но не обязательно один и тот же токен). Сработавшая команда вызывается серверным процессом pinentry без возврата какого-либо вывода. Вызов команды осуществляется до передачи введённого пароля в gpg-agent с правами пользователя, от которого запущен процесс gpg-agent. Правила исполняются в том порядке, как они указаны в файле настроек.


При исполнении shell-команд им, помимо прочих, передаются следующие переменные окружения:


  • GPG_REMOTE_PID: ID серверного процесса GPG Remote.
  • GPG_REMOTE_KEYRINGS: Разделённый пробелами список путей к не пустым связкам ключей gpg.

Вместо shell-команд "тревожные" правила могут содержать следующие специальные команды. Имейте в виду, что правило может содержать только одну специальную команду:


  • STOP: Штатно останавливает сервер GPG Remote. Сервер отправляет клиенту сообщение о неопределённой ошибке, завершает обработку оставшихся параллельных запросов, удаляет все временные данные, полученные от клиента, и останавливает программу.
  • KILL: Немедленно прерывает работу сервера GPG Remote. Сервер посылает себе системный сигнал SIGKILL без выполнения каких-либо завершающих процедур.

Обратите внимание, что gpg-agent считывает закрытый ключ в память до того, как вызывает pinentry, поэтому использование команд rm/wipe для экстренного стирания связок ключей не приведёт к немедленному уничтожению ключа — для этого также необходимо остановить серверный процесс GPG Remote (с помощью специальных команд STOP или KILL), чтобы не дать ему отправить результат работы gpg обратно клиенту. В тех случаях, когда в этом есть потребность, используйте возможность последовательного выполнения нескольких правил (путём назначения им одинаковых токенов безопасности или парольных фраз).

Меры безопасности


Аутентификация/шифрование канала связи лежит за пределами данного приложения. Для организации безопасной клиент-серверной коммуникации пользователь может использовать SSH- или VPN-туннелирование.


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


Учтите, что даже если модифицирующие связку ключей опции (такие как --delete-key или --import) не внесены в белый список, у клиента всё равно сохранится возможность добавлять открытые и закрытые ключи на связку, просто передавая их через стандартный ввод (обрабатываемый программой gpg по контексту). Если такие действия нежелательны, администратору сервера следует запускать сервер из-под пользователя, не имеющего прав записи в файлы связок gpg. Имейте в виду, что стандартные файлы связок всегда можно переопределить с помощью опций --no-default-keyring, --keyring и --secret-keyring.


Другая потенциальная угроза серверу заключается в риске утечки его локальных файлов. При наивной реализации пользователь мог бы попросить сервер выполнить gpg -o – --enarmor [путь к локальному файлу], и сервер, не задумываясь, передал бы содержимое этого файла через STDIN. Чтобы этого избежать, сервер проверяет, чтобы число аргументов командной строки, в которых могут содержаться имена файлов, равнялось количеству файлов, полученных от клиента в пакете запроса. (Все эти сложности необходимы, т.к. если бы сервер просто отказывался выполнять запросы, включающие пути к существующим локальным файлам, возникала бы утечка информации о содержимом файловой системы сервера.) В то же время, для такого механизма необходима корректная настройка белого списка сервера в плане обозначения опций с параметрами: если опция может принимать параметры, её набор должен содержать указатель параметра (произвольный или разрешённый), в противном случае сервер может оказаться уязвим к описанной атаке.


Обратите внимание, что ряд консольных опций gpg (в частности, --list-keys, --list-sigs и др.) могут принимать произвольное количество нефайловых аргументов. Для учёта такого случая набор опций в белом списке может содержать специальное ключевого слово [#NO_FILES]. Если клиентский запрос будет включать опцию из такого набора, сервер удалит из запроса опции -o/--output и не станет возвращать клиенту какие бы то ни было файлы.


Получаемые от клиента файлы (которые могут содержать открытый текст секретных данных) сервер сохраняет во временную директорию. По умолчанию это системная директория для временных файлов (как правило, /tmp), но если такая директория по какой-либо причине является небезопасной, её путь можно переопределить с помощью переменной окружения TEMP или опции --temp при запуске сервера. (Обратите внимание, что файлы сохраняются не непосредственно в temp-директорию, а во временные подкаталоги в ней, создаваемые с правами доступа 0700, т.е. доступные только для пользователя, под которым запущен сервер GPG Remote.)


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


Клиент имеет возможность вызвать у сервера отказ в обслуживании, посылая ему чрезмерно объёмные запросы. Для предотвращения такого сценария следует использовать параметры ограничения ресурсов сервера: size_limit, threads и queue. Первый ограничивает объём клиентского пакета запроса и, как следствие, объём потребляемой памяти. Второй устанавливает ограничение на число процессорных потоков, выделяемых на обработку запросов (каждый запрос обрабатывается одним потоком). Учтите, что максимальный объём ОЗУ, требуемый серверу, составляет примерно S * T * 2, где S — лимит на объём пакета и T — число потоков (т.е. при заданных по умолчанию S=1 ГБ и T=2 максимальный объём ОЗУ может составить 4 ГБ). Наконец, значение queue — это количество запросов, которые могут встать в очередь на обработку. Если это значение выше числа потоков, то оставшиеся запросы будут ожидать завершения активных. Ожидающие запросы не потребляют дополнительных ресурсов, за исключением открытого сокета для соединения.


При использовании удалённого ввода парольных фраз, они никогда не попадают в память долгоживущего серверного процесса GPG Remote. Тем не менее, введённая парольная фраза остаётся в памяти клиентского модуля вплоть до завершения вызова gpg, и на протяжении этого времени она остаётся уязвима к своппингу ОЗУ. Убедитесь, чтобы swap-раздел ОС был зашифрован, отключен или примите иные адекватные меры предосторожности.


Одноразовые пароли (OTP) являются механизмом контроля доступа, применяемым на уровне логики GPG Remote. Данный механизм не затрагивает какой бы то ни было криптографический материал, и от него не следует ожидать обеспечения особых криптографических свойств, таких как PFS.


Если на сервере настроены "тревожные" правила, и при этом в токенах безопасности используется большое число итераций, противник потенциально будет способен определить этот факт по задержке, с которой gpg возвращает вывод, поскольку введённая парольная фраза будет сравниваться с каждым из уникальных токенов. Также выполнение тех или иных "тревожных" правил может быть выявлено, если исполняемая команда требует значительного времени для завершения.

Технические сведения


Коммуникационный протокол представляет собой простейший двухшаговый запрос-ответ. Формат пакета для обоих направлений показан ниже.

<len_p> | <len_j> | JSON(<header>, <type>, <fields>, <files_meta>) | [binary]

  • len_p (8 bytes): Общая длина пакета.
  • len_j (8 bytes): Длина JSON-пакета.
  • header (list): Токен аутентификации auth (опционально) и версия приложения version.
  • type (str): Идентификатор пакета.
  • fields (list): Произвольный набор полей.
  • files_meta (dict): Отображение путь_файла->длина_файла.
  • binary (bytes): Конкатенированное содержимое файлов (опционально).

Если используется токен аутентификации, он должен быть представлен в виде свёртки HMAC-SHA256 (в шестнадцатеричной нотации) всех метаданных в JSON-пакете. Токен вычисляется следующим образом: элементы метаданных (за исключением элемента auth) помещаются в вышеуказанном порядке в плоский список, кодируются в JSON-строку, которая передаётся в контекст функции HMAC. В настоящее время аутентификация применяется только для IPC-сообщений между сервером и pinentry. По этой причине аутентификация binary-пакета не производится.


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


  1. server>gpg-agent: устанавливает в переменной окружения PINENTRY_USER_DATA (передаваемой через весь стек вызова gpg > gpg-agent > pinentry) сведения для установления IPC-канала, в том числе имя IPC-сокета и сеансовый ключ аутентификации.
  2. gpg-agent>pinentry: вызывает pinentry, передавая переменную окружения PINENTRY_USER_DATA и инициализирует Assuan-протокол.
  3. server>pinentry: передаёт по IPC-каналу (UNIX-сокету) открытый сетевой сокет клиентского соединения непосредственно процессу pinentry.
  4. pinentry>client: по предоставленному сетевому соединению посылает клиенту необходимые данные (текстовые строки и опции запуска, полученные от gpg-agent на этапе 2) для вызова стандартного приложения pinentry.
  5. client: вызывает стандартное приложение pinentry и получает пользовательский ответ (в виде ответа Assuan-протокола).
  6. client>pinentry: пересылает пользовательский ответ.
  7. pinentry: выполняет "тревожные" команды, если введённая парольная фраза вызвала срабатывание каких-либо из них.
  8. pinentry>gpg-agent: воспроизводит пользовательский ответ в начатом на этапе 2 Assuan-протоколе.

При вводе неверной парольной фразы цикл 2-8 повторяется по запросу gpg-agent.


Стоит отметить, что, хотя доступ к UNIX-сокету IPC-интерфейса (используемого для связи между сервером и процессом pinentry) не ограничивается средствами ОС (чтобы имелась возможность запуска сервера и gpg-agent под разными пользователями), сервер производит проверку аутентичности поступающих сообщений с помощью сеансового ключа аутентификации, который он передаёт pinentry на первом этапе протокола.


Список одноразовых паролей (OTP) хранится на сервере в открытом виде. Каждая строка файла списка представляет собой разделённый двоеточием идентификационный номер и непосредственно строку пароля


Токен безопасности для "тревожных" правил представляет собой вывод PBKDF2[SHA-1, HMAC] с эффективным предельным объёмом энтропии 256 бит. Длина значения соли составляет 64 бита. Формат токена — это строка разделённых двоеточиями и Base62-кодированных элементов: число итераций (в байтовом представлении), соль, хэш PBKDF2.


Значение порта сервера по умолчанию (29797) было получено в Python следующим образом:

int.from_bytes(b'gpgremote', 'big') % 2 ** 16

(В то же время, было замечено, что при такой процедуре значение имеют только последние байты b'te'.)

Проблемы и ограничения


  • Интерактивные консольные операции (такие как генерация и редактирование ключа и т.п.) не поддерживаются.

  • Клиент не поддерживает чтение ввода из TTY, данные могут быть переданы только через пайп в STDIN.

  • Передача файловых дескрипторов и иные продвинутые виды IPC для взаимодействия с вызываемым процессом gpg не поддерживаются.

  • Переменные окружения клиента не передаются вызываемому процессу gpg. Если требуется вызов gpg с определённым окружением (к примеру, LANG), необходимо запустить сервер GPG Remote с данными переменными.

  • При использовании GnuPG 2.x без специализированного приложения Pinentry из состава GPG Remote операции с защищёнными закрытыми ключами приведут к вызову на стороне сервера стандартного диалога Pinentry, который заблокирует процесс gpg, не давая ему корректно завершиться. Если клиент и сервер GPG Remote запускаются на одной системе, то такое поведение может быть даже желательным (однако убедитесь, что значения conn_timeout у клиента и gpg_timeout у сервера достаточно велики, чтобы пользователь успел ввести парольную фразу), в противном случае администратору сервера следует предпринять меры для отключения gpg-agent на стороне сервера (например, путём перехода на версию GnuPG 1.4.x или с помощью запуска gpg-agent с опцией --batch).

Планы


  • Снизить потребление памяти.

 
На страницу: 1, 2, 3, 4 След.
Комментарии [скрыть комментарии/форму]
— SATtva (31/01/2015 17:55)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118

Не особо, есть много других вещей, на которые я бы предпочёл потратить время.
— Гость (31/01/2015 21:05)   <#>

Если она будет закеширована навечно, то, как говорится, а имеет ли весь комбайн смысл... Впрочем, при прочих равных «раз модульнее, то безопасней» типа. ☺
— SATtva (01/02/2015 11:26, исправлен 01/02/2015 11:26)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118

Блин, ну Вам-то, я надеялся, не придётся объяснять про RTFM man gpg-agent.

— Гость (01/02/2015 13:03)   <#>

У меня было стойкое ощущение, что всё это должно работать автоматически, поэтому я сразу пошёл с другой стороны: можно ли отключить pinentry и весь этот комбайн просто настройками конфигов. Ответ нашёлся быстро, что нельзя. Типа раз софт был скомпилен с соответствующими зависимостями, то никак. К тому же, я уже знал о подобных проблемах от более опытных товарищей, и знал, что они тоже не отключают, т.к. тривиально без перекомпиляции это не сделать. Впрочем, раз у них хоть и с глюками, но работает, может быть, они как-то тоже настраивали руками запуск агента, не знаю, не подумал об этом. Я вообще думал, что эта беда (gpg-agent) обойдёт меня стороной.

Разумным было бы ожидать неких указаний на нужность ручного старта в авторских комментариях в mcabberrc, но там ничего этого нет. Правда, можно сказать, это не его проблемы, он непосредственно на gpg2 не завязывается, так что имеет право сказать «меня не касается, как у вас работает gpg». А откуда ещё это следует узнавать? Всё работало железно, а потом бац, и сломалось. Ну pinentry, ну запрашивает пароль, ну ладно, но почему сыпятся бесконечные запросы и не работает кэширование? Как всегда, слишком много пререквизитов на «должно было быть очевидным пользователям».

Всё ж делается для юзерофильности. Сейчас автоматика там, где раньше и близко не было. Старт иксов — всё на автомате, никакого ручного конфигурирования. Писать ~/.xsession уже нет необходимости — при его отсутствии запустится с дефолтами, ну, и во всём так. А тут наоборот, перестёт функционировать даже то, что всегда железно работало.
— SATtva (01/02/2015 13:12)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Сорри, смайлик забыл. :)
— Гость (01/02/2015 13:59)   <#>
Это не баг, а фича [1], [2]. ☺
— SATtva (01/02/2015 16:56)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Сделал PoC с кастомным pinentry, пока без передачи клиенту (надо было проверить передачу открытого сетевого сокета между процессами, чтобы не запускать на клиенте ещё один сервер и не скатываться в архитектурный звиздец). Работает. :)
— Гость (01/02/2015 17:58)   <#>
Поздравлямс. :) Сервер тоже можно будет запускать с правами (иного) локального юзера?
— SATtva (01/02/2015 18:12)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Уже сейчас можно. И не только локального — сервер и клиент могут работать на произвольных системах.
— Гость (01/02/2015 19:18)   <#>
Имелось в виду, что запуск сервера не требует прав рута.
— SATtva (01/02/2015 22:28)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Вообще никаким боком не требует, если только не хотите вешать его на низкий порт.
— Гость (01/02/2015 22:35)   <#>
Кстати, хорошо, что упомянули порт. Будут какие-то дефолты на этот счёт? Ну, чтоб не конфликтовать с другими сетевыми демонами в плане выбора портов. Потом можно пропихнуть умолчальные порты gpg-remote в /etc/services. ☺
— SATtva (01/02/2015 22:48)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Дефолтный порт — 29797 (в доках написано, откуда взялся такой номер). Гуглинг показывает, что ничем вменяемым он не занят, IANA'ой не делегирован.
— Гость (01/02/2015 23:56)   <#>
++
Кстати, из-за способа генерации числа из строки на номер порта влияли только её последние 2 байта:
++
— SATtva (02/02/2015 17:42)   профиль/связь   <#>
комментариев: 11558   документов: 1036   редакций: 4118
Забавно, не придал этому значение.
На страницу: 1, 2, 3, 4 След.
Ваша оценка документа [показать результаты]
-3-2-1 0+1+2+3