Решил я недавно разобраться в подробностях работы SSH. Использовал его для удалённого запуска команд давно, но, будучи не слишком опытным в системном администрировании, очень размыто представлял, зачем админы просят им отправить какой-то ключ, что с этим ключом происходит при подключении, зачем при запуске ssh периодически орёт на меня какими-то предупреждениями, и прочие прелести. К своему удивлению, не смог найти ресурсов с описанием протокола, после которых у меня не осталось бы только больше вопросов. Поэтому, после прочтения спецификаций и разборок с OpenSSH, хочу разложить всё по полочкам здесь.
Статья рассчитана на поверхностно знакомых с SSH, либо совсем не знакомых. Попытаюсь описать основные аспекты безопасности протокола: какие ключи и алгоритмы используются, в какой момент и зачем. Статья призвана дать базу в работе с самим протоколом SSH и OpenSSH (одной из программ, реализующих протокол), зная которую можно разбираться в его более продвинутых возможностях.
Что нужно знать
Единственное требование: знать о симметричном и об ассиметричном шифровании. Что такое криптографический ключ, что такое открытый и закрытый ключи, зачем они нужны, что умеют, как работает шифрование и цифровая подпись — на эти и связанные вопросы здесь ответа нет. Кому надо, тот загуглит про (а)симметричное шифрование, об этом есть множество доступных ресурсов.
Терминология
Далее везде
Пользователь — системный пользователь, то есть который создаётся на линуксе через
adduserили на винде в настройках.Клиент — программа, реализующая клиентскую часть протокола SSH: отправляющая запросы к SSH-серверу, пишущая вам в консоль гневные тирады из-за смены ключей, и прочее.
Сервер — соответственно программа, реализующая серверную часть SSH.
Стороны — клиент и сервер.
Вы — вы, то есть человек, использующий клиент.
Админ(истратор) — человек, ответственный за сервер. Возможно совпадает с "вами". Также это может быть некоторая автоматическая система, например, если вы используете облачные сервисы. В этом случае взаимодействие с сервером происходит через панель управления облаком.
Что такое SSH?
SSH — это сетевой протокол для защищённого управления удалёнными устройствами по незащищённому сетевому соединению. Самый примечательный сценарий использования: удалённый вход в систему и выполнение команд.
Это вольный перевод первого абзаца статьи про SSH на английской википедии. Думаю, каждый, кто открывал хоть одну ссылку про SSH в гугле, знает об этом.
Чуть менее тривиально (написано во втором абзаце) то, что на самом деле SSH состоит из трёх более или менее независимых протоколов: протокол транспортного уровня, протокол аутентификации, и протокол соединения. Возможно, называть их прямо отдельными протоколами — слишком громкие слова, и можно было бы считать, что это просто три стадии налаживания соединения, но так их обзывают в спецификации, а чем я хуже. Кстати, спецификация у каждого из них своя: RFC 4253, RFC 4252 и RFC 4254 соответственно, за подробностями можно и нужно обращаться к этим ссылкам.
Обычно на практике разделение на три протокола не часто упоминается, но чтобы понять, когда и зачем какой ключ используется, это довольно важно. Поэтому далее каждый из этих протоколов разберём отдельно.
Акт 1. Протокол транспортного уровня
Сцена 1. Установка защищённого соединения
Первый этап в работе SSH — наладить защищённый канал связи. Внимание, спойлер: позднее, например если используется аутентификация по паролю, мы будем отправлять по сети пароль голым текстом (а как иначе сервер сможет сравнить пароль с хешем, который хранит у себя?), либо не пароль так ещё кучу всякой конфиденциальной информации. Поэтому нужно осуществить некоторые танцы с бубном, чтобы кто попало эти данные не прочитал. Это, я считаю, самый сложный из трёх протоколов, так как содержит множество хитрых фикусов и рассуждений о безопасности.
Итак, первое, что делают стороны (клиент и сервер) после того, как TCP соединение установлено (а SSH обычно работает поверх TCP) — отправляют друг другу версию своего ПО и самого протокола SSH, которую хотят использовать (на момент написания статьи актуальна версия 2.0). Далее они обмениваются списками поддерживаемых алгоритмов шифрования (и не только).
Разработчики SSH предусмотрели, что, с одной стороны, алгоритмы шифрования, как и всё в жизни, со временем выходят из моды (точнее в них находят уязвимости), а с другой, не все разработчики клиентов/серверов SSH способны поддерживать все существующие алгоритмы. Поэтому и нужен шаг, на котором стороны договариваются об используемых алгоритмах. Сколько же алгоритмов и для каких конкретных целей нам понадобится? Не один и даже не два.
В SSH слова "соединение защищено" значат, что все отправляемые сообщения подвергаются симметричному шифрованию. То есть и у сервера, и у клиента есть (обычно) одинаковый ключ симметричного шифрования, который может и зашифровать, и расшифровать любое сообщение. Симметричные шифры бывают разные, поэтому первый алгоритм, о котором договариваются стороны: алгоритм симметричного шифрования (encryption algorithm).
Уточнение 1
На самом деле стороны договариваются одновременно обо всех алгоритмах, а "первым" я его назвал для удобства. Кроме того, алгоритмов симметричного шифрования может быть выбрано два: для сообщений от сервера к клиенту один, а от клиента к серверу другой. Но спецификация рекомендует использовать один в обе стороны, и для простоты далее я буду считать, что он один.
Вопрос: как так сделать, чтобы клиент и сервер, изначально общаясь по незащищённому соединению, смогли договориться о ключе симметричного шифрования, и при этом никто кроме клиента и сервера не узнал этот ключ? Ответ: использовать хитроумный алгоритм обмена ключами (англ. key exchange algorithm, он же kex algorithm). Это второй алгоритм, о котором договариваются стороны.
Конкретные алгоритмы для обмена ключами — это обычно вариации на тему алгоритма Диффи-Хеллмана. На английской википедии есть очень наглядная картинка (приведена ниже), где в роли ключей выступают цвета. Сначала стороны договариваются о некотором открытом ключе (обычно клиент его генерирует и отправляет серверу по незащищённому соединению, на картинке общим ключом является жёлтый цвет). Затем каждая сторона создаёт свой закрытый ключ (цвет заката и морской волны в моей интерпретации 💅). Он каким-то образом объединяется с открытым ключом (в примере с цветами — смешивается, а вообще то, как ключи объединяются, и как в принципе генерируются, зависит от конкретного выбранного алгоритма обмена ключами), и результат отправляется другой стороне. Затем каждая сторона объединяет уже полученный чужой результат со своим закрытым ключом, и в итоге, благодаря магии математики, оказывается, что у обеих сторон вышло одно и то же. Это одно и то же можно использовать в качестве ключа симметричного шифрования (либо чтобы его сгенерировать).
Этих двух алгоритмов достаточно, чтобы установить защищённое соединение: у обеих сторон есть ключ симметричного шифрования, поэтому они просто перед отправлением шифруют каждое сообщение, а при получении дешифруют.
Уточнение 2
На самом деле алгоритмов больше: есть ещё алгоритм сжатия данных (compression algorithm), и алгоритм имитовставки (mac algorithm), причём они, как и алгоритм симметричного шифрования, могут быть разные для сообщений от клиента к серверу и от сервера к клиенту. Но их подробности нам сейчас не слишком интересны. Со сжатием данных всё просто: содержимое сообщений сжимается с помощью выбранного алгоритма, чтобы экономить трафик. А с MAC всё наоборот чуть сложнее, но при этом знание о нём не имеет большой ценности при работе с SSH, поэтому позволю себе отправить любопытного читателя ознакомиться со спецификацией SSH.
Но это не конец. На этапе обмена ключами происходит ещё одно важное действие: клиент, так сказать, проверяет сервер на вшивость. Привычно думать, что в аутентификации нуждается клиент, то есть клиент должен подтвердить свою личность, чтобы злоумышленник не проник внутрь сервера, и не натворил делов. О предотвращении делов речь пойдёт позже. Но в SSH аутентификация сервера не менее важна: ведь клиент (опять спойлер) будет отправлять ему, например, свои пароли. Поэтому клиенту также нужно убедиться, что сервер не подставной. Для этого используется ещё один алгоритм, о котором стороны также договариваются перед обменом ключами — алгоритм цифровой подписи сервера. Это не официальное название, это я так обозвал в попытках отразить суть, в спецификации зовётся "server host key algorithm".
Сцена 2. Идентификация сервера
У сервера изначально есть пара ключей: закрытый и открытый, они называются ключами сервера (host keys). На самом деле такая пара не одна: у сервера по паре ключей на каждый алгоритм цифровой подписи, который он поддерживает (ведь разные алгоритмы генерируют ключи по-разному). У сервера OpenSSH на линуксе по умолчанию ключи сервера хранятся в папке /etc/ssh в файлах с названиями ssh_host_*_key (закрытый ключ) и ssh_host_*_key.pub (соответствующий открытый ключ), вместо звёздочки пишется название алгоритма, например rsa или ecdsa. Эти ключи перед запуском SSH-сервера кладёт туда администратор: либо генерирует с нуля (например, с помощью ssh-keygen -A), либо устанавливает из каких-то других соображений, мало ли в кармане завалялись.
Во время обмена ключами, сервер шифрует некоторую строку своим закрытым ключом (на этот момент они с клиентом уже договорились, какой алгоритм цифровой подписи использовать, поэтому ключ выбираются соответствующий), и отправляет результат клиенту впридачу с открытым ключом. Тот с помощью открытого ключа её расшифровывает. Изначальная строка составлена из некоторого набора данных, которые обеим сторонам на этот момент уже известны, вроде названия используемого протокола, версии ПО и т.п. (конкретно из каких описано в RFC), поэтому клиент может сравнить расшифрованную строку с оригинальной. Если они совпадают, значит сервер правда владеет закрытым ключом, соответствующим заявленному открытому.
Однако это ещё ничего не значит. С таким же успехом сервер злоумышленника мог бы сгенерировать свою пару открытый/закрытый ключ, отправить клиенту открытый, и подписать строку закрытым. Да, клиент убедится, что сервер действительно имеет закрытый ключ, который соответствует отправленному открытому, но это не значит, что сервер не подставной.
Как убедиться, что сервер настоящий — это вопрос менее тривиальный, чем применить цифровую подпись. Есть разные варианты, одни более безопасные, другие менее. Чаще и проще всего это делается так: клиент хранит у себя небольшую базу данных (обычно просто в виде файла в определённом формате), которые сопоставляют каждому серверу его открытый ключ. Например, клиент OpenSSH на линуксе смотрит в файл ~/.ssh/known_hosts.
Теоретическая ситуация: мне нужно ssh-нуться к машинке по адресу example.com. Для этого (будучи ответственным и волнующимся о безопасности человеком), я пойду к администратору, и узнаю у него публичный ключ сервера, запущенного на этой машинке (соответствующий алгоритму, который буду использовать для проверки сервера). Затем добавлю в known_hosts запись о том, что публичный ключ сервера example.com такой-то (формат файла подскажет гугл). Затем при каждом подключении к example.com клиент будет проверять, что отправленный сервером открытый ключ совпадает с тем, что лежит в known_hosts. Если совпадает, клиент проверит, что сервер правда владеет соответствующим закрытым ключо как описано выше. Если владеет — значит сервер точно не подставной. Небольшой нюанс: так как в known_hosts ключ сопоставляется домену, то если я хочу подключаться к этой машинке не только по домену, но и по IP адресу, этому IP адресу тоже нужно отдельной строчкой сопоставить тот же ключ.
Однако вряд ли вы при каждом подключении к новому серверу идете просить админа отправить ключ. Обычно при первом подключении к новому серверу клиенты SSH показывают сообщение такого характера:
The authenticity of host '...' can't be established.
ED25519 key fingerprint is SHA256:noP2S4gaQmKTIO8cHHg4ju3QptLSo6MEgCw2AiLwOJM.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Таким образом клиент говорит, что не знает, можно ли доверять этому серверу, и оставляет это на ваше усмотрение. Как можно действовать:
Набрать
noи отменить подключение.Набрать
yes: тогда клиент автоматически добавит полученный открытый ключ сервера вknown_hosts, сопоставив его домену, к которому пытались подключиться (обычно добавляется и сопоставление к IP адресу, соответствующему на данный момент этому домену). Перед тем, как бездумно набиратьyes, можно обратить внимание, что выше выведен хеш открытого ключа. Его можно сравнить с ожидаемого хешем ключа (например, опять же, спросить у админа, какой должен быть хеш, или, если подключаетесь к машине в облаке, в панели управления облака он может быть указан).Также можно ввести "fingerprint" — это, грубо говоря, тоже хеш публичного ключа, который можно спросить у админа или скопировать из авторитетного источника, тогда клиент сам проверит, что ключ подходит под этот fingerprint, и если так, добавит его в
known_hosts.
Стоит отметить, что так как ключ сервера используется клиентом для идентификации, если он в какой-то момент изменится (например, если на сервере переустановили SSH или операционку, либо может перегенерировали ключи, из-за того что закрытый оказался слит, или просто в целях профилактики), то сервер не получится идентифицировать. С точки зрения клиента такая смена ключа неотличима от попытки злоумышленника подсунуть липовый ключ. Поэтому в таких случаях клиент при подключении на вас наорёт, например, как орёт OpenSSH:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
6e:45:f9:a8:af:38:3d:a1:a5:c7:76:1d:02:f8:77:00.
Please contact your system administrator.
Add correct host key in /home/hostname /.ssh/known_hosts to get rid of this message.
Собственно русским по белому написано: возможно вас пытаются злоумыслить, а возможно просто ключ поменялся. Узнать это можно только обратившись к ответственным за сервер. Есть способы, как организовать регулярную смену ключей, но я не достаточно квалифицирован, чтобы их описывать. Любопытый читатель загуглит "ssh key rotation".
Хранить публичный ключ вкупе с доменом — не единственный способ идентификации сервера, ещё можно, например, использовать сертификаты: тогда клиент перед обменом ключами отправит свой сертификат, который клиент сможет проверить на достоверность. Но не буду на них останавливаться.
Итого
После всех проделанных действий (которые, кстати, обычно укладываются в 6-8 пакетов данных, переданных между клиентом и сервером) мы имеем: возможность защищённым образом передавать пакеты по незащищённой сети, и уверенность в том, что сервер не подставной. Для этого у нас есть
Ключ симметричного шифрования, который есть у обеих сторон, и который нигде не сохраняется, а генерируется заново при каждом подключении (и, вообще говоря, может меняться в течение соединения, для этого существует процедура повторного обмена ключами — key re-exchange — но это детали).
Ключ сервера: закрытый только у сервера, открытый у обеих сторон.
Обращу внимание, что пока что речь ни о каком ключе клиента не шла. Тот самый id_rsa.pub, который вечно нужно генерировать и кому-то отправлять, на текущий момент ещё не использовался.
Акт 2. Протокол аутентификации
До этого сервер распинался перед клиентом, как бы подтвердить свою личность, пора бы и клиенту что-нибудь подтвердить. Всю описанную выше процедуру вполне мог провернуть злоумышленник, пытающийся получить доступ к серверу, поэтому теперь сервер должен проверить клиента.
Простейший способ аутентифицировать клиента: не аутентифицировать клиента. Да, можно настроить сервер так, что как только соединение установлено, считается, что клиент молодец, и сразу может выполнять команды/делать что там SSH ещё умеет делать. Естественно, это дико небезопасно, но в некоторых случаях можно оправдать такую схему.
Но мы не будем её оправдывать. У нас же теперь есть защищённое соединение, по которому можно передавать что угодно без страха, что кто-то левый прочитает. Поэтому чуть менее простейший способ аутентификации клиента: по паролю. Клиент просто отправляет имя пользователя, под которым хочет зайти, и пароль. Сервер где-то у себя хранит хеш пароля (который заранее кто-то установил, например админ поставил и сообщил вам) для каждого пользователя, под которым можно зайти. При попытке входа пароль сравнивается с хешем, вот и сказочке конец. Возможность входа по паролю не обязательна, сервер SSH может её не поддерживать.
А вот обязательна возможность входа ещё менее простейшим, но более безопасным способом — по ключу. Здесь уже вы сами создаёте пару ключей клиента (например, с помощью утилиты ssh-keygen, по умолчанию закрытый ключ она записывает в файл ~/.ssh/id_rsa, а открытый — в ~/.ssh/id_rsa.pub), сообщаете администратору открытый ключ (при использовании ssh-keygen это id_rsa.pub), тот добавляет его на сервер. Если запущен сервер OpenSSH на линуксе, то у каждого пользователя на сервере в домашней папке есть свой файл ~/.ssh/auhorized_keys: администратор добавляет ключ клиента в authorized_keys того пользователя, под которым клиент сможет входить. Можно добавить один ключ в authorized_keys нескольких пользователей, тогда вы сможете входить под всеми ними. Под каким конкретно вы пытаетесь войти — указываете при подключении, например ssh myuser@example.com.
Дальше всё как было при идентификации сервера: клиент закрытым ключом шифрует некоторую строку (которую и клиент, и сервер могут составить из известных им данных, конкретная строка описана в спецификации), и отправляет шифр серверу вместе с открытым ключом, а так же отправляет имя пользователя, под которым хочет войти (например foo). Сервер проверяет, что открытый ключ ему известен (OpenSSH на линуксе смотрит, что он указан в файле /home/foo/.ssh/authorized_keys), расшифровывает строку открытым ключом, сравнивает её с оригиналом. Если всё совпало: клиент идентифицирован, и можно принимать от него команды/файлы/что угодно.
Важно! Ни в коем случае нельзя никому сообщать свой закрытый ключ (по умолчанию ~/.ssh/id_rsa без расширения .pub). Это всё равно, что рассказать свой пароль, только пароль можно сменить самому, а чтобы сменить ключ, надо дёргать администратора.
Акт 3. Протокол соединения
На этом этапе, защищённое соединение уже установлено, клиент подтвердил личность сервера, а сервер — клиента. Протокол соединения работает на прикладном уровне: если SSH используется для запуска команд в терминале он описывает, как передавать сами команды и настройки терминала, если для передачи файлов — процесс передачи файлов. Здесь не буду вдаваться в подробности, это для меня наименее интересная часть SSH, знать подробности которой не особо важно при работе с протоколом.
Резюме
При установке соединения в SSH используются следующие ключи:
Ключ сервера — концептуально это ключ цифровой подписи, нужен для проверки личности сервера клиентом. Не используется для шифрования.
Ключ клиента — концептуально это ключ цифровой подписи, нужен для проверки личности клиента сервером. Не используется для шифрования.
Ключ синхронного шифрования — меняется при каждом подключении, может меняться в течении сессии, используется для шифрования передаваемых сообщений.
Если вы используете OpenSSH на линуксе, то вот список основных файлов, которые используются. На клиенте:
~/.ssh/id_rsa— закрытый ключ клиента, который никому нельзя сообщать.~/.ssh/id_rsa.pub— открытый ключ клиента, который администратор добавляет на сервер, чтобы вы получили доступ.~/.ssh/known_hosts— список, сопоставляющий доменам/IP адресам серверов их открытые ключи. Используется чтобы удостовериться, что сервер не подставной.~/.ssh/config,/etc/ssh/ssh_config— в статье не упоминался, но это настройки клиента SSH, подробнее что настраивается расскажетman ssh_config.
На сервере:
/etc/ssh/ssh_host_*_key— закрытые ключи сервера, по ключу на каждый поддерживаемый алгоритм цифровой подписи./etc/ssh/ssh_host_*_key.pub— открытые ключи сервера./home/[user]/.ssh/authorized_keys— список открытых ключей клиентов, которым разрешено входить под пользователем[user]./etc/ssh/sshd_config— настройки сервера, подробнее вman sshd_config.
Естественно, здесь опущены разные мелочи, не расписаны принципы ротации ключей, использования сертификатов, и прочее прочее. Но, надеюсь, статья будет полезной для начинающих рыбок в море сетевых протоколов.