Invisible Internet Project (I2P, проект невидимого интернета) – лидер среди технологий приватной передачи информации. Полная децентрализация и независимость делают сеть I2P архитектурно сложной, но уникальной в своем роде. Статья посвящена вопросу: может ли программист, несведущий в криптографии и сетях написать приложение, работающее через I2P.
Современные реализации I2P-роутеров имеют поддержку API-протокола SAM (Simple Anonymous Messaging), который позволяет внешним приложениям общаться через I2P, используя всего несколько простых команд. В рамках этой статьи рассмотрим необходимый минимум для начала собственных экспериментов.
Небольшая ретроспектива
История I2P начинается в первой половине 2000-ых. Хорошие практики разработки ПО в эпоху видеокассет распространялись неактивно. Представить в то время современные концепции взаимодействия с внешними приложениями через удобный интерфейс (API) было сложно.
Изначально программный клиент сети предоставлял во внешний мир только прокси. Прокси, по-простому говоря, является посредником, который принимает запрос, например, «example.i2p», и обрабатывает его. Для внешнего приложения вся внутренняя логика абсолютно скрыта, а сам процесс напоминает использование обычной сети.
Неискушенный читатель наверняка не видит в этом проблему, ведь «есть прокси – делай что хочешь»! Отчасти это так – клиент-серверные приложения будут работать в штатном режиме: сайт откроется, IRC-чат тоже. Но что делать с peer-to-peer технологиями и как динамически создавать новые идентификаторы пользователя, чтобы все приложения не серфили сеть от одного лица?
В I2P-роутере существует протокол I2CP (I2P Control Protocol), который призван логически разделить сущность роутера анонимной сети и внешнее приложение. Однако не все так радужно, как показалось команде разработчиков в начале нулевых: I2CP является очень громоздким и сложным протоколом. Чтобы внешнее приложение могло делать хоть что-то осмысленное, оно должно содержать больше половины кода роутера. Представьте, что для покупки в интернет-магазине вам нужно владеть хотя бы разговорным китайским!
Сложность интеграции I2P во внешнюю программу весьма неплохо объясняет перегруженность исконного Java-роутера всевозможными дополнительными приложениями вроде BitTorrent и Email, которые превратили I2P-роутер из небольшой утилиты в монолитного мамонта.
Актуальному API-протоколу SAM предшествовал BOB (Basic Open Bridge). Когда сеть I2P глобально перешла на более совершенную криптографию (отказ от алгоритма SHA1), разработчики флагманского Java-роутера решили оставить Боба в прошлом и не учить его новым стандартам. Как бы там ни было, тратить и без того скудные ресурсы на одновременное сопровождение двух похожих протоколов показалось нецелесообразно.
Перед тем, как перейти к основной части статьи отмечу, что в реализации роутера на C++ (i2pd) BOB является актуальным протоколом наравне с SAM. Команда PurpleI2P не дает ему погибнуть, при необходимости актуализируя кодовую (и функциональную) базу. Если загоритесь энтузиазмом, можете с ним ознакомиться.
Начало урока
Существует несколько библиотек для разных языков программирования, предоставляющие готовые функции и методы для взаимодействия с I2P-роутером через SAM. Их официальный перечень легко найти на странице документации. Если удачно напишете свою библиотечку, подумайте о ее добавлении в этот список.
Разберем принципы работы с SAM на самом общем примере – через терминал. Для примера используются утилита netcat и роутер i2pd (Java-роутер будет работать аналогичным образом). Старый добрый telnet для подключения к Сэму не подойдет, потому что использует два символа завершения строки (\r\n
), а SAM обрабатывает конец сообщения по символу \n
. Символ \r
не предусмотрен протоколом, поэтому считается ошибкой, из-за чего происходит остановка работы.
В i2pd SAM по умолчанию, как правило, включен. Чтобы убедиться в этом, откройте веб-интерфейс (http://127.0.0.1:7070
), в котором на главной странице отображается статус сервисов. Если SAM все-таки неактивен, включите его через конфигурационный файл параметром sam.enabled = true
. По умолчанию он доступен для обращений по адресу 127.0.0.1:7656
.
Hello hidden world
При подключении к сокету, который слушает служба SAM, нужно инициировать рукопожатие (handshake). Для этого необходимо написать HELLO VERSION
. В ответ роутер возвращает свою версию протокола SAM. Если RESULT=OK
, рукопожатие прошло успешно. Также в команду HELLO VERSION
можно добавить требование к версии: HELLO VERSION MIN=3.0 MAX=3.3
. Это пригодится в случае использования новых возможностей, о которых старые роутеры не знают. Практика многих людей показывает, что версии 3.0 достаточно для реализации самых различных задач.
При создании сессии необходимо указать ее никнейм (id), который мы будем использовать в дальнейшем для подключения к сессии, а также ключи, которые будут являться криптографическими идентификатором новой скрытой точки (destination). Главное требование к никнейму – отсутствие пробелов, а вот с ключами вопрос несколько интереснее. Если указать значение TRANSIENT
, создадутся случайные ключи, которые после создания сессии будут выданы монолитным массивом информации (в кодировке base64), в который одновременно входят публичные и приватные ключи. Так как размер части с публичными и приватными ключами заранее известен, при программной реализации не составляет труда раскодировать информацию и отделить публичные ключи от приватных (например, чтобы их можно было сообщить собеседнику). Для полноты алгоритма действий, а также для простоты использования SAM через терминал с ручным копированием ключей, создам ключи отдельной командой перед созданием сессии.
Команда DEST GENERATE
может быть использована в таком виде, либо с явным указанием типа подписи. По умолчаниюSIGNATURE_TYPE=0
- устаревший тип, сейчас рекомендуется использовать 7
, но чтобы не усложнять восприятие, воспользуемся типом подписи по умолчанию, то есть не будем его указывать. О типах подписи не обязательно знать для базового использования I2P.
После получения команды, роутер возвращает ключи с явным разделением публичных (PUB) и приватных (PRIV). Это весьма удобно при использовании простого парсинга по ключевым словам.
Теперь переходим непосредственно к созданию сессии. SAM поддерживает три типа сессий: STREAM (для TCP), DATAGRAM (отправка и получение пакетов UDP) и RAW (вариация на тему UDP). Наиболее наглядным и базовым протоколом является TCP. Его используют для основных операций вроде передачи файлов и сообщений, поэтому для примера создадим STREAM-сессию.
SESSION CREATE STYLE=STREAM ID=HabraHabr DESTINATION=БЛОК_ПРИВАТНЫХ_КЛЮЧЕЙ
Параметр STYLE
отвечает за тип сессии (в нашем случае STREAM
), DESTINATION
– принимает приватные ключи (блок информации из секции PRIV
недавно сгенерированных ключей), ID
– никнейм сессии, по которому можно к ней обратиться (в примере: HabraHabr
).
Через несколько секунд роутер сообщает об успешном создании сессии. Сессия будет жить пока сокет, который ее создал, существует. При этом работать с сессией необходимо через новые подключения к Сэму. Это значит, что контрольный сокет хоть и не используется напрямую, но должен существовать до тех пор, пока нам нужна конкретная сессия.
Через новое окно снова подключаюсь к Сэму и произвожу стандартное рукопожатие. Есть два пути использования существующей сессии «HabraHabr»: инициировать соединение с другим скрытым узлом от лица этой сессии, зная его адрес, либо подключиться к локальному стриму для ожидания входящего соединения извне. Чтобы принять сообщения, которые будут отправлены из второй сессии, подключимся к стриму командой STREAM ACCEPT ID=HabraHabr
.
С первой сессией закончено. Открываю новое окно терминала, подключаюсь к Сэму и создаю новую сессию «Novosibirsk» лишь с той разницей, что не генерирую ключи отдельно, а указываю TRANSIENT
в параметре DESTINATION
. Суть происходящего от этого не меняется.
Чтобы подключиться к предыдущей сессии «HabraHabr», открываю еще одно окно терминала, подключаюсь к Сэму и ввожу команду:
STREAM CONNECT ID=Novosibirsk DESTINATION=ПУБЛИЧНЫЕ_КЛЮЧИ_СЕССИИ_HabraHabr
При входящем сообщении, на сокет приходит блок публичных ключей подключенного абонента, который является его внутрисетевым идентификатором (на скриншоте слева). В текущей сессии эта информация не нужна, но она может пригодиться, чтобы позже установить соединение с абонентом по нашей инициативе.
Таким образом происходит соединение двух конечных точек. Несмотря на то, что фактически они расположены на одном I2P-роутере, соединение проходит через несколько компьютеров по всему миру, причем соединение в каждую сторону осуществляется через отдельную цепочку транзитных узлов. Иногда это наглядно прослеживается при быстрой отправке сообщений из одного окна терминала в другое и при долгой доставке в обратном направлении.
То, что показано в примере, как примитивный текстовый чат, по своей сути является прямым подключением двух абонентов через скрытую сеть. Подобное P2P (пир-ту-пир) соединение можно использовать для общения по самым различным протоколам без использования сервера-посредника и сохраняя конфиденциальность обеих сторон.
Когда один из собеседников прервет соединение, пользователь на другом конце также отключится от своей сессии. Манипуляции собеседника на другом конце провода не влияют на локальную сессию, но каждый раз к ней необходимо переподключаться с нового сокета, чтобы поймать новое входящее соединение. Можно сказать, каждое подключение является одноразовым. В случае примера с терминалами это означает, что для каждого соединения необходимо новое окно терминала с подключением к Сэму в режиме ожидания.
Это особенно важно иметь ввиду при практической реализации: необходимо создавать новое подключение к локальной сессии каждый раз, когда существующее приняло входящее соединение. Также возможно одновременное наличие нескольких ожидающих сокетов, что исключит отказ в соединении для пользователя, для которого мы не успели создать новое ожидающее соединение.
Резюме
Данная статья демонстрирует основные принципы работы, которую ваше приложение может выполнять непосредственно, либо используя обертки этих же команд в виде библиотеки. Если тема окажется востребованной, разберем SAM более подробно.