среда, 10 декабря 2014 г.

Авторизация узла Пандоры

В статье разобрано 3 реализованных в коде механизма авторизации узла Пандоры: хэш-загадке, которая ограждает от DoS-атак, электронной подписи, которая идентифицирует узел-собеседник, и картинке-загадке, которая отсеивает спамеров и ботов.

Речь пойдёт о бан-листе, системе штрафов, а также ускоренной авторизации по сеансовому ключу после обрыва связи. Для начала взглянем на общую диаграмму сеанса связи:







При авторизации охотник, как инициатор соединения, подвергается большему числу проверок, нежели слушатель. Стадии авторизации проходят под диктовку слушателя, а охотник должен строго отвечать на запросы слушателя. На этапе авторизации идёт синхронный обмен.

Сеанс начинается с фразы приветствия охотника:


hello = {:version=>0, :mode=>0, :mykey=>my_key, :tokey=>out_key, :addr=>my_callback}

В которой присутствует версия протокола, опции обмена, панхэш ключа на этом узле, панхэш ключа на том узле, ip-адрес для обратного подключения.

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


1. Хэш-загадка (puzzle)

Первым делом нужно заставить охотника совершить некоторый объём работы (концепция Proof-of-work). Это снижает способности охотника к DoS-атаке слушателя, которая, как вы знаете, заключается в ассиметричном расходовании ресурсов. С помощью хэш-загадки слушатель смещает баланс в свою сторону.

Для этого слушатель генерирует случайную 256-символьную фразу, в которой последний байт заменяется на длину в битах требуемой «отгадки», например, 14 бит.
Задача охотника – добавить такое число к фразе, первые 14 бит суммарного хэша sha1 которого совпадут с фразой-загадкой. В виде формулы можно выразить так:

F & M = sha1(F+A) & M,

где F – фраза,
M – битовая маска требуемой длины, например 011111111111111b,
A – блок (число), который необходимо найти, чтобы выполнилось тождество.

Так как аналитического решения у уравнения с хэшем нет, то охотнику приходится перебором подставлять числа и высчитывать хэш для каждого блока (фраза+число) до тех пор, пока решение не будет найдено. Время поиска зависит от требуемой длины маски и удачи. При 14 битах на среднем ноутбуке среднее время поиска равняется около 2 секунд (иногда больше, иногда меньше). Вы можете увеличить или уменьшить размер маски, регулируя этим самым требовательность к охотнику.

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

По умолчанию в Пандоре заданы такие параметры:


puzzle_bit_length = 14
puzzle_sec_delay = 2

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


# Generate random phrase
# RU: Сгенерировать случайную фразу
def get_sphrase(init=false)
phrase = params['sphrase'] if not init
if init or (not phrase)
phrase = OpenSSL::Random.random_bytes(256)
params['sphrase'] = phrase
init = true
end
[phrase, init]
end

phrase, init = get_sphrase(true)
phrase[-1] = puzzle_bit_length.chr
phrase[-2] = puzzle_sec_delay.chr

# Find sha1-solution
# RU: Находит sha1-загадку
def self.find_sha1_solution(phrase)
res = nil
lenbit = phrase[phrase.size-1].ord
len = lenbit/8
puzzle = phrase[0, len]
tailbyte = nil
drift = lenbit - len*8
if drift>0
tailmask = 0xFF >> (8-drift)
tailbyte = (phrase[len].ord & tailmask) if tailmask>0
end
i = 0
while (not res) and (i<0xffffffff -="" 1="" add="" and="" check="" def="" drift="" end="" hash="" i="" if="" len="" lenbit="phrase[phrase.size-1].ord" not="" offer="=puzzle)" or="" ord="" phrase="" puzzle="phrase[0," res="false" ru:="" self.check_sha1_solution="" sha1-="" sha1-solution="" tailbyte="nil" tailmask="">0
tailmask = 0xFF >> (8-drift)
tailbyte = (phrase[len].ord & tailmask) if tailmask>0
end
hash = Digest::SHA1.digest(phrase+add)
offer = hash[0, len]
if (offer==puzzle) and ((not tailbyte) or ((hash[len].ord & tailmask)==tailbyte))
res = true
end
res
end

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

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

Прохождение хэш-загадки может быть пропущено, если соединение поступает с известного ip-адреса или с узла, приславшего приветствие с заданными критериями (например, с панхэшем известного ключа). Все перепетии момента могут быть заданы на узле.

Хэш-загадка, например, вообще может быть отключена, но ЭЦП обоюдо обязательна: как для охотника, так и для слушателя.


2. Электронная подпись (sign)

В этом месте нужно заметить, что хэш-загадка была придумана, чтобы безопасно подойти к шагу обмена ключами, анкетами и началу «взрослой» криптографии. Ведь принятие Ключа, регистрация Узла и непосредственно сама проверка подписи требуют ощутимые ресурсы слушателя. Дополнительной мерой защиты может служить ограничение на число принимаемых неизвестных (не просчитываемых в системе доверия слушателя) ключей с одного ip-адреса за заданную единицу времени. Например, в сутки не более 3-х неизвестных ключей с одного ip.

Проверка ключа происходит так. Слушатель посылает охотнику фразу (по умолчанию, если была стадия хэш-загадки, то фраза не высылается, а используется та же самая), охотник берёт хэш sha2-384bit от фразы, подписывает своим ключом и высылает подпись слушателю:


rphrase = OpenSSL::Digest::SHA384.digest(rphrase)
sign = PandoraCrypto.make_sign(@rkey, rphrase)

Слушатель тоже берёт хэш от фразы и сверяет подпись:


res = PandoraCrypto.verify_sign(@skey, OpenSSL::Digest::SHA384.digest(params['sphrase']), rsign)

Если совпала, то происходит обратная проверка. Теперь уже охотник шлёт фразу слушателю, а слушатель подписывает её хэш и высылает охотнику.

Обратите внимание: подписывается не сама фраза, а хэш от фразы!
Дело в том, что ключи могут использоваться не только для авторизации узлов, но и для подписания записей. В этом случае, охотник (или слушатель) под видом «случайной» фразы может подсунуть какую-нибудь рабочую запись (Ключ, Человек, Договор, Проект и т. д.) и таким образом подставить владельца ключа (слушателя или охотника), который даже не будет подозревать, что его правом подписи воспользовались втёмную! В случае же когда подписывается хэш, а не сама фраза, такая манипуляция невозможна. В то же время обеспечивается надёжная проверка на владение закрытым ключом.

Если охотник прислал некорректную подпись, то он не подтверждает владение закрытым ключом. Сеанс связи обрывается, а IP-адрес наказывается штрафом.

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

Если охотник прислал корректную подпись, но доверие к его ключу не было указано ранее, то он подвергается следующей проверке - капче.

3. Картинка-загадка (captcha)

Если ключ охотника из предыдущей стадии не имеет доверия слушателя, то дальнейший ход сеанса связи зависит от настроек для капчи:

captcha_length = 4
captcha_attempts = 2
trust_for_captchaed = true

Если длина капчи captcha_length больше нуля (по умолчанию равна 4), то слушатель, используя библиотеки Cairo и Gdk, генерирует картинку с заданным числом символов и предлагает охотнику её отгадать. Если человек на узле-охотнике отгадал картинку, то ему присваивается временное доверие trust = 0.01 (на период сеанса связи). Такой механизм даёт право писать новичкам с неподтверждёнными ключами, но при этом ограждает от ботов и спам-рассылок.



Если же длина капчи задана 0, то охотнику будет выдано сообщение:
«Ключ на подтверждении»
До тех пор, пока на слушателе не проставят доверие ключу охотника, сеанс связи будет обрываться на данном этапе.


4. Активный бан-лист

В настоящий момент бан-лист не реализован в коде, но видится он примерно так:
узел ведёт список недавно подключавшихся ip-адресов и отмечает, кто как себя вёл.

Если недавно авторизация прошла нормально, то при повторном подключении этапы хэш-загадки и капчи могут быть пропущены, потребуется только подпись.

Если ip-адрес вел себя «неприлично», то накапливается длительность бана:
1) подключился и отключился без трафика - 1 мин
2) подключился, отправил приветствие, но не ответил на хэш-загадку - 2 мин
3) прислал отгадку раньше срока - 30 сек
4) прислал неверную отгадку - 10 мин
5) прислал отгадку в срок и верную, но отключился, не прислав подписи - 2 мин
6) прислал неверную подпись - 10 мин
7) прислал неверную капчу, исчерпав все (2 по умочанию) попытки - 2 мин
8) подключился повторно раньше чем через 5 мин, хотя был ответ "ключ на рассмотрении" - 5 мин
9) замолчал во время связи на этапе авторизации и не отвечает более 1 мин - 20 сек
10) прислал неадекватный сегмент для текущей стадии - 5 мин
11) авторизация с одного ip со вторым неизвестным ключом с интервалом менее 2 ч - 15 мин
12) с одного ip может быть не более 2х неавторизованных сессий

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



Если приходит подключение с адреса, который находится в бан-листе и текущее время не достигло времени разбанивания, то соединение разрывается. Также в unix-системах возможна связка Пандоры и фаервола (iptables, например), при которой отброс (reject) ip будет происходить на системном уровне, избавляя приложение от инициализации tcp (или udp) сокета.

5. Ускоренное восстановление сеанса

В настоящий момент авторизация производится на открытых ключах ассиметричного алгоритма RSA. Но в будущем запланирована генерация закрытых сеансовых ключей для симметричного алгоритма, например Blowfish.

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

6. После авторизации

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

Комментариев нет :

Отправить комментарий