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

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

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

Эта часть — первая, и здесь мы рассмотрим базу (протоколы, работу сетей, напишем скрипт на python для TCP-соединения), а во второй уже затронем тему виртуальных частных сетей и прокси, а также несколько других тем.


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

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

  • HTTP — согласно ему, браузер формирует сообщение для сервера;

  • DNS — согласно ему, браузер узнает IP-адрес сайта по его доменному имени;

  • TCP — согласно ему, устанавливается соединение и гарантируется целостность передачи данных;

  • IP — согласно ему, осуществляется адресация в сети;

  • Ethernet — согласно ему, осуществляется физическая передача данных между устройствами в сети.

  • FTP — согласно ему, компьютеры могут передавать друг другу файлы по сети.

  • SMTP — согласно ему, сервера маршрутизирует электронную почту в сети.

  • UDP — протокол, ориентированный на отправку дейтаграмм (небольших блоков данных) без установления соединения. Он не гарантирует доставку пакетов и не восстанавливает потерянные данные. Зато UDP проще и быстрее чем TCP. А TCP уже предоставляет гарантии доставки пакетов, их упорядочивания и восстановления при ошибках. Этим они и тяжелее UDP.

Transmission Control Protocol/Internet Protocol (Стек протоколов TCP/IP) — если сказать простым языком, это набор взаимодействующих протоколов разных уровней, согласно которым происходит обмен данными в сети. Каждый протокол — это набор правил, согласно которым происходит обмен данными. Итого, стек протоколов TCP/IP — это набор наборов правил. Множество протоколов можно классифицировать по сетевым уровням, на котором они работают. Наиболее распространенные сетевые модели — это OSI и TCP/IP.

В критичных системах (например DNS) чаще используется UDP. Здесь важна скорость, а не гарантия доставки. Пока отправится TCP пакет, UDP пакет уже успеет три раза вернуться. А в других приложениях, где более важна целостность данных, а не скорость, применяется TCP.

Узел сети — это устройство, являющееся частью компьютерной сети. Узлы можно разделить на оконечные и промежуточные:

Оконечные узлы — это узлы, которые отправляют и/или принимают какие-либо данные. Простыми словами, это устройства, которые являются получателем или источником информации.

Промежуточные узлы — это узлы, которые соединяют оконечные узлы между собой.

Например, смартфон через Wi-Fi отправляет запрос к серверу. Смартфон и сервер — оконечные узлы, а Wi-Fi-роутер — это промежуточный узел.

❯ Проблема века — конфиденциальность.

В последнее время началась охота за данными людей. Она всегда была, человек старался контролировать всех, но сейчас она набирает обороты. Ведь взять только многочисленные сервисы — все они имеют рекомендации, а ради них надо собирать терабайты данных. Когда, кто зашел? Что искал? С какой страны? И многое, многое другое. Практически полный отчет на каждого. И даже если не брать интернет-сервисы, то даже простой магазин у вашего дома тоже может составлять досье на вас. Что вы берете, когда. Например, мужчина средних лет заходит в магазин утром, берет пару бутылок воды и фруктов, через два часа возвращается за еще одной бутылкой воды. Из этих данных можно понять, что это спортсмен, который бегает два часа утром и следит за здоровьем. Можно даже узнать его примерное место жительства.

Компании и правительственные организации используют целый ряд современных технологий — от куки-файлов веб-сайтов до датчиков, встроенных в машины, а также устройств для сбора беспрецедентных объемов данных о своих клиентах, гражданах, сотрудниках. В их задачи входит оценка настроений и предпочтений, прогнозирование потребностей, повышение производительности труда, выявление мошенничества, отслеживание местонахождения, мониторинг здоровья, контроль безопасности.

Большая часть этих данных собирается с благими целями. Однако никто не может ответить на один из самых актуальных вопросов: где проходит граница? Люди хотят быть осведомлены о том, как обрабатываются и хранятся персональные сведения и насколько обеспечена их конфиденциальность. Соблюдаются ли «право на забвение» и множество других международных, государственных и локальных правил безопасности?

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

Например, недавно на сайте госзакупок появилась заявка на разработку системы обмена данных между министерством обороны РФ и ФСБ о россиянах, получивших повестки. Система будет использовать XML-файлы и стандартные технологии (REST API и HTTP). Если злоумышленник получит доступ к этой системе, значит он получит доступ к миллионам данных совершеннолетних граждан.

Обеспечение безопасности является сложной задачей для любой компании, особенно с развитием облачных сред.

С 2020 года все начали переходить на удаленную работу — и ныне это стало своеобразным трендом. Работа из дома означает, что многие используют корпоративные компьютеры и телефоны в личных целях. Однако рабочие устройства могут быть не такими надежными, как кажется. У людей, использующих корпоративные компьютеры и телефоны в личных целях, нередко возникает вопрос: может ли работодатель видеть, какие веб-сайты я посещаю через домашнюю сеть Wi-Fi или находясь дома?

В этой статье, как я уже говорил, мы сами попытаемся защитить свой онлайн трафик.

❯ Какие способы мониторинга трафика?

Linux предоставляет огромное количество инструментов для работы и мониторинга сети. Иногда нам приходится отслеживать нагрузку на сеть, дабы определить, что творится "по ту сторону экрана".

Особенно часто такие задачи возникают перед системными администраторами, перед которыми стоит задача отслеживать работу сети целой компании. Но также это может быть полезно и обычным пользователям.

Если вы смотрите эту статью в интернете, вы уже знаете, зачем нужна сеть. Разве что стоит отметить, что почти все компании имеют какие-то внутренние сервисы и для них организуется так называемая локальная сеть. Но мы ещё к этому придём. Итак, для того, чтобы различные компьютеры с разными операционными системами и программами могли взаимодействовать между собой, существует универсальная сетевая модель, называемая OSI, которая определяет стандарты. Эта модель делит взаимодействие на шаги, так называемые уровни, и каждый уровень имеет свои правила. Набор этих правил определяет, как именно должно происходить взаимодействие и называется протоколом. Названия каких-то протоколов вы наверняка где-то видели — IP, DNS, HTTP. Хотя стандартная модель OSI предполагает 7 уровней, администраторы чаще всего работают со вторым, третьим, четвёртым уровнями и объединяют 5, 6 и 7 уровни в один, чаще всего называя седьмым.

Ядро ОС отвечает за реализацию физического, канального, сетевого и транспортного уровней, предоставляя прикладным программам унифицированный интерфейс для работы с сетью.

Вернемся к мониторингу. Линукс богат сетевыми утилитами и инструментов для администрирования сетей. Начиная от net-tools и iptables заканчивая tcpdump и WireShark.

Давайте рассмотрим утилиту для мониторинга сети iptraf. Это небольшая программа, написанная на C и ncurses для мониторинга сетевой активности компьютера.

# Ubuntu/debian
apt install iptraf

# Arch
pacman -S iptraf-ng

# Centos/redhat
yum install iptraf

После установки выполните команду sudo iptraf-ng.

Перед вами откроется ncurses-интерфейс. Здесь есть монитор трафика, настройки интерфейса, статистика, мониторы и фильтры, а также конфигурация.

Например, для просмотра сетевых соединений и статистики трафика для каждого из них выберите IP traffic monitor. Затем выберите сетевой интерфейс.

Далее вы увидите все IP адреса, с которыми работает система. Вы увидите количество пакетов, полученный объем данных и прочую информацию.

Небольшую справку по этой утилите вы можете увидеть снизу:

usage: iptraf-ng [options]
   or: iptraf-ng [options] -B [-i <iface> | -d <iface> | -s <iface> | -z <iface> | -l <iface> | -g]

    -h, --help            show this help message

    -i <iface>            start the IP traffic monitor (use '-i all' for all interfaces)
    -d <iface>            start the detailed statistics facility on an interface
    -s <iface>            start the TCP and UDP monitor on an interface
    -z <iface>            shows the packet size counts on an interface
    -l <iface>            start the LAN station monitor (use '-l all' for all LAN interfaces)
    -g                    start the general interface statistics

    -B                    run in background (use only with one of the above parameters
    -f                    clear all locks and counters
    -t <n>                run only for the specified <n> number of minutes
    -L <logfile>          specifies an alternate log file

И давайте рассмотрим еще один инструмент — iftop. Эта утилита позволяет отслеживать использование сети в реальном времени, выявлять активные подключения и анализировать сетевую активность. Установить можно предельно просто:

# Ubuntu/debian
apt install iftop

# Arch
pacman -S iftop

# Centos/redhat
yum install iftop

Для запуска используйте команду:

sudo iftop -i <сетевой интерфейс>

Также можно записывать сетевой трафик в лог:

#!/bin/bash

LOGFILE="$HOME/iftop_traffic.log"
INTERFACE="eth0"

sudo iftop -i $INTERFACE > "$LOGFILE"

echo "Log saved in $LOGFILE"

Вот справка по утилите iftop:

Synopsis: iftop -h | [-npblNBP] [-i interface] [-f filter code]
                               [-F net/mask] [-G net6/mask6]

   -h                  display this message
   -n                  don't do hostname lookups
   -N                  don't convert port numbers to services
   -p                  run in promiscuous mode (show traffic between other
                       hosts on the same network segment)
   -b                  don't display a bar graph of traffic
   -B                  Display bandwidth in bytes
   -i interface        listen on named interface
   -f filter code      use filter code to select packets to count
                       (default: none, but only IP packets are counted)
   -F net/mask         show traffic flows in/out of IPv4 network
   -G net6/mask6       show traffic flows in/out of IPv6 network
   -l                  display and count link-local IPv6 traffic (default: off)
   -P                  show ports as well as hosts
   -m limit            sets the upper limit for the bandwidth scale
   -c config file      specifies an alternative configuration file
   -t                  use text interface without ncurses

   Sorting orders:
   -o 2s                Sort by first column (2s traffic average)
   -o 10s               Sort by second column (10s traffic average) [default]
   -o 40s               Sort by third column (40s traffic average)
   -o source            Sort by source address
   -o destination       Sort by destination address

   The following options are only available in combination with -t
   -s num              print one single text output afer num seconds, then quit
   -L num              number of lines to print

iftop, version 1.0pre4
copyright (c) 2002 Paul Warren <pdw@ex-parrot.com> and contributors

❯ Безопасность на уровне протоколов: SSL/TLS

Для безопасной передачи данных между узлами в сети есть специальный защищенный протокол TLS (Transport Layer Security) / SSL (Secure Socket Layers). Цель протокола — обеспечить шифрование данных при передаче.

Стоит перед тем, как мы перейдем к самому протоколу, рассказать о сетевой модели OSI (The Open System Interconnection model).

Сетевая модель OSI

OSI — это базовая иерархическая модель взаимодействия открытых систем. Т.е основа, база, которая описывает механизм передачи данных. Она представляет стандартный способ организации различных аспектов коммуникаций в рамках обмена данными в сети.

OSI
OSI

Поверх 3 уровня, в частности IP адресов были построены такие протоколы передачи данных как TCP, HTTPS, UDP, FTP, ICMP и т.д. Они заботятся о многих вещах: гарантии передачи данных, безопасности и передаче конкретных высокоуровневых структур данных.

Модель OSI создали разработчики из International Organization for Standardization (ISO) в далеком 1984 году. Эта модель — база базовая, ее должен знать каждый специалист, который решил изучить работу сетей.

А вообще, история разработки этой модели началась с небольшой группы ученых. Основными личностями являются Майк Канепа и Чарльз Бакман. В 1970-х годах эта группа сосредоточилась на создании прототипов систем для компании Honeywell Information System. А в середине 1970-х ученые поняли, что для поддержки машин нужна улучшенная коммуникационная архитектура.

В 1980-х годах она стала рабочим продуктом группы взаимодействия открытых систем Международной организации по стандартизации (ISO). Модель не смогла дать полное описание сети и не получила поддержку архитекторов на заре Интернета, который впоследствии нашел отражение в менее предписывающем TCP/IP, в основном под руководством Инженерного совета Интернета (IETF).

Стоит поговорить об уровнях OSI

Уровень                       Тип данных    Функции                             Примеры
7. Прикладной (приложения)    Данные        Доступ к сетевым службам            HTTP, FTP, POP3, SMTP, WebSocket
6. Представления              Данные        Представление и шифрование данных   ASCII, EBCDIC, SSL, gzip
5. Сеансовый (session)        Данные        Управление сеансом связи            RPC, PAP, L2TP, gRPC
4. Транспортный (transport)   Сегменты и    Связь между пунктами и надёжность   TCP, UDP, SCTP, Порты  
                              датаграммы
3. Сетевой (network)          Пакеты        Определение маршрута и адресация    IPv4, IPv6, IPsec, AppleTalk, ICMP
2. Канальный (data link)      Биты и кадры  Физическая адресация                PPP, IEEE 802.22, Ethernet, DSL, ARP
1. Физический (physical)      Биты          Работа с двоичными данными          USB, RJ, радиоканал

Physical Layer (Физический). То есть работа с двоичными данными, радиоканалами, и другими низкоуровневыми вещами, где часто играет роль физика.
Data Link Layer (Канальный). Поддерживает надежность передачи/приема данных между сетевым оборудование в локальной сети (LAN). Также этот уровень контролирует доступ к сетевым ресурсам, разграничивает его по заданным правилам.
Network Layer (Сетевой). Маршрутизирует данные между разными сетями и находит оптимальные пути для их доставки.
Transport Layer (Транспортный). Отвечает за точность данных, которые передаются в рамках сети. Также обеспечивает управление потоком данных и выполняет мониторинг ошибок.
Session Layer (Сеансовый). Контролирует сеансы связи в рамках сети, обеспечивая начало, поддержание и завершение подключений.
Presentation Layer (Представительский). Преобразовывает, шифрует, сжимает данные, обеспечивая их точность и читаемость для устройств на другой стороне сети.
Application Layer (Прикладной). Предоставляет пользовательский интерфейс для взаимодействия с сетевыми компонентами. Включает в себя специальные сервисы и протоколы: HTTP (для веб-браузеров), FTP (для передачи файлов), SMTP (для электронной почты) и т .д.

Почитать описание каждого уровня вы можете на википедии.

История SSL/TLS

Протокол Secure Sockets Layer (SSL) появился на свет благодаря компании Netscape, в 1995 году. Его целью была обеспечение безопасной передачи данных между клиентом и сервером в интернете. Насчитывается несколько версий SSL, каждая из которых более безопасная, чем предыдущая.

В 1999 году его заменили на TLS (Transport Layer Security). Но до сих пор упоминают термин SSL для обозначения обоих протоколов.

Сайт, где используют SSL/TLS-протокол, отличается тем, что его URL-адрес начинается с «HTTPS», а не «HTTP», что свидетельствует о защищенном соединении.

Механика работы TLS/SSL

TLS и SSL используют:

  • Ассиметричная криптография для аутентификации. Кстати, этот тип шифрования более ресурсоемкий, поэтому его комбинируют с симметрическим шифрованием.

  • Симметричное шифрование для конфиденциальности.

  • Коды аутентичности сообщений для сохранения целостности сообщений.

Данный протокол широко используется в приложениях, работающих с сетью. Вообще, можно сказать, что это одно и тоже, ведь с версии 3.0 SSL появился его приемник — TLS, но название так прижилось, что до сих пор под SSL чаще всего подразумевают TLS.

Когда пользователь заходит на какой-то сайт по HTTPS, браузер запрашивает сертификат у сервера, который высылает копию SSL-сертификата с открытым ключом шифрования. После браузер проверяет и подтверждает этот сертификат. Кроме того, проверяется дата действия сертификата и наличие корневого сертификата, выданного надежным центром сертификации. Если браузер доверяет сертификату, то он генерирует предварительный секрет (pre-master secret) сессии на основе открытого ключа, используя максимально высокий уровень шифрования, который поддерживают обе стороны. Сервер расшифровывает предварительный секрет с помощью своего закрытого ключа, соглашается продолжить коммуникацию и создать общий секрет (master secret), используя определенный вид шифрования. Теперь обе стороны используют симметричный ключ, который действителен только для данной сессии. После ее завершения ключ уничтожается, а при следующем посещении сайта процесс рукопожатия запускается сначала.

Принцип работы SSL и TLS — практически один и тот же. Поверх протокола TCP/IP устанавливается защищенный канал, внутри которого передаются данные по прикладному протоколу (HTTP, DNS, FTP, SMTP и другие).

Прикладной протокол находится в канале TLS/SSL, а тот в свою очередь уже в TCP/IP. По сути данные передаются именно по TCP/IP, но суть в том, что данные зашифрованы, и расшифровать их может только машина, которая установила соединения.

|--------------------------------|                               \
|             TCP/IP             |    Основной протокол           \
|--------------------------------|                                 \
|            SSL/TLS             |    Протокол шифрования           \
|--------------------------------|                                   \
|        Прикладной протокол     |    Прикладной протокол             |  Сетевой канал с зашифрованными данными
|--------------------------------|                                   /
|            SSL/TLS             |                                  /
|--------------------------------|                                 /
|             TCP/IP             |                                /
|--------------------------------|                               /

Установка соединения обеспечивается в несколько этапов:

  1. Клиент устанавливает соединение с сервером и запрашивает защищенное подключение. Это может обеспечиваться установлением соединения на порт для SSL/TLS (по умолчанию 443), либо дополнительны запросом клиентом установки защищенного соединения после установки обычного.

  2. При установке соединения клиент предоставляет список алгоритмов шифрования, которые доступны. Сервер сверяет их со своим списком, и после выбирает самый надежный алгоритм, после чего сообщает его клиенту.

  3. Сервер отправляет клиенту свой цифровой сертификат (который подписан удостоверяющим центром) и открытый ключ сервера.

  4. Клиент, при желании может проверить сертификат сервера. Он связывается с сервером доверенного центра и проверяет, валиден ли он. Желательно клиенту обязательно проверять, чтобы избежать ARP-спуфинга. Злоумышленник в этом типе кибератак может выступать как и клиентом, так и сервером.

  5. Генерируется ключ сеанса для защищенного соединения. Генерация происходит так:

  • клиент генерирует случайную последовательность из чисел;

  • клиент шифрует ее открытым ключом сервера и посылает результат на сервер;

  • сервер расшифровывает полученную последовательность при помощи закрытого ключа.

Check

Учитывая, что алгоритм шифрования ассиметричный, расшифровать последовательность может только сервер. В основе ассиметричного шифрования — два ключа (приватный и публичный). Публичным ключом сообщение шифруется, а приватным расшифровывается. Расшифровать ключ при помощи публичного ключа нельзя.

  1. Таким образом устанавливается зашифрованное соединение. Данные, передаваемые по нему, шифруются и расшифровываются до тех пор, пока соединение не будет разорвано.

Корневой сертификат

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

Запрос на подпись (CSR, Certificate Sign Request)

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

Сертификат клиента

Такие сертификаты используются при двусторонней верификации, когда клиент проверяет что сервер валидный. И также с обратной стороны — сервер проверяет что клиента не подменили. Такое взаимодействие называется Mutual Authentication. Такой тип аутентификации позволяет повысить уровень безопасности или заменить стандартную аутентификацию через логин и пароль.

Хеширование

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

Поговорим подробнее о хешировании. Что объединяет пароль пользователя, подпись сертификата, контрольную сумму, криптовалюту? Хеширование.

В основе хеширования лежит хеш-функция. Это функция преобразования данных любой длины в данные заданной длины, и почти гарантирует безопасность данных.

 Входные                             Итоговые
 Данные        +-------------+        Данные
  /  \    ->   | Хеш функция |   ->    {DT}
 {DATA}        +-------------+
                (хеш алгоритм)

Основные принципы хеширования:

  1. Хеш всегда фиксированного размера, например 32 символа.

  2. Одинаковые данные выдают одинаковый хеш. Но стоит изменить хотя бы 1 символ, хеш будет другой.

  3. Разные значения выдают разный хеш.

  4. Необратимое образование.

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

Алгоритмов хеширования много (md5, sha1, sha256, sha5211, crc-32). Каждый из них использует в своей основе разные алгоритмы, соответственно и разный шанс возникновения коллизии. Есть три семейства алгоритмов хеширования:

|    MD   |          CRC          |         SHA         |
|---------|-----------------------|---------------------|
| MD-5    | CRC-1, 4, 5, 6, 7, 8  | SHA-3-254 - 512 бит |
| MD-4    | CRC-10, 11, 12, 15-16 | SHA-2-224 - 512 бит |
| MD-2    | CRC-24, 30, 32, 64    | SHA-1 - 128 бит     |
| 128 бит | 64 бита               | Довольно популярный |

Таймлайн создания криптографических хеш-функций и прочая информация:

-------------+----------------+-----------+------------+----------+
           1989              1990        1993         2006       2011
                                                                  |> MD5 небезопасен
|> MD1     |> MD2    |> MD3    |> MD4    |> MD4       |> MD3      | Не смотря на это,
|          |         |         |         | взломан    | взломан   | его иногда используют

----------------------------------------------------------------------------------

SHA 1 (=md5)    |  SHA 2
160 бит         | SHA 224
                | SHA 256
                | SHA 384
                | SHA 512
                | SHA 512/256
                | SHA 224/512

В 2014 Google отказался от sha1, а в 2016 уже яндекс.

2008 год — первые коллизии в sha2.

Целью хеш-алгоритма, в контексте SSL/TLS, является преобразовать все содержимое сертификата в строку из байт фиксированной длины. Для шифрования значения хеша используется закрытый ключ центра сертификации (подпись).

Хеш-алгоритм также использует MAC (message authentication code). Она необходима для проверки целостности передаваемых данных. MAC использует функцию отображения, чтобы представлять данные сообщения как фиксированное значение длины, а потом хеширует сообщение.

В протоколе TLS используется HMAC (hashed message authentication code). Его отличие заключается в том, что он использует хеш-функцию сразу с общим секретным ключом. То есть ключ становится частью данных, и для подтверждения подлинности обе стороны должны использовать одинаковые ключи.

На сегодняшний день поддерживаются алгоритм хеширования SHA2, и чаще всего используется SHA-256. Он довольно безопасен и не такой большой как SHA-512. Длина слова SHA-512 равна 64 бита, когда как SHA-256 уже 32 бита. И другие значения также больше — количество раундов в цикле равно 80 (вместо 64), а сообщение разбивается на блоки по 1024 бита (вместо 512 бит). Когда-то использовали SHA1, MD5 и т.д., но сегодня они считаются уязвимыми. Хотя иногда даже сегодня можно увидеть MD5 хеши.


Давайте попробуем для примера реализовать клиент-серверное приложение на основе TLS (и узнаем что такое протокол Диффи-Хеллмана).

Алгоритм Диффи-Хеллмана

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

diffie-hallman
diffie-hallman

Алгоритм Диффи-Хеллмана используется для того, чтобы две стороны могли создать общий секретный ключ, его еще называют "транспортный ключ", который затем используется для шифрования и дешифрования сообщений.

diffie
diffie

Реализация простейшего приложения на SSL Socket на Python

Давайте попробуем реализовать TLS соединение на Python. Для примера я создам простейший чат, где один клиент будет общаться с сервером. Для этого нам не нужно никаких библиотек, но я использовал rich для вывода красивых сообщений.

Итак, давайте сначала импортируем все библиотеки что нам будут нужны:

from pathlib import Path
from datetime import datetime
import socket
import ssl
import sys
import time
from rich import print

Если что, библиотеку rich можно установить командой pip3 install rich.

После этого напишем несколько базовых функций:

def print_msg(text: str, msg_type: str='info') -> None:
    """
    Выводит детальное и красивое сообщение.

    :param text: Текст сообщения
    :param msg_type: Тип сообщения (по умолчанию info)
    """
    msg_type = msg_type.lower()

    if msg_type == 'info':
        message = f'[green]INFO::{datetime.now()}[/green] -- {text}'
    elif msg_type == 'warning':
        message = f'[yellow]WARNING::{datetime.now()}[/yellow] -- {text}'
    elif msg_type == 'error':
        message = f'[red]ERROR::{datetime.now()}[/red] -- {text}'
    elif msg_type == 'debug':
        message = f'[blue]DEBUG::{datetime.now()}[/blue] -- {text}'
    else:
        message = f'[cyan]{msg_type.upper()}::{datetime.now()}[/cyan] -- {text}'

    print(message)


def ping() -> None:
    """
    Функция для оперативной проверки, есть ли в системе подключение к интернету.
    """
    ip_lists = ["8.8.8.8", '1.1.1.1']
    port = 53

    for ip in ip_lists:
        try:
            socket.setdefaulttimeout(3)
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

            print_msg(f'Try ping {ip}...', 'debug')

            server_address = (ip, port)

            sock.connect(server_address)
        except OSError as error:
            print_msg(f'OSError occured when ping {ip}: {error}', 'warning')
        else:
            print_msg(f'Internet connection is enabled!', 'info')
            sock.close()
            return True

    print_msg(f'Connection not acquired', 'error')
    return False


def first_check():
    """
    Первая проверка наличия подключения к сети.
    """
    if ping():
        live = " CONNECTION ACQUIRED "
        print('-' * len(live))
        print(live)
        print('-' * len(live))
        connection_acquired_time = datetime.now()
        acquiring_message = "connection acquired at: " + connection_acquired_time.strftime("%Y-%m-%d %H:%M:%S")
        print(acquiring_message)

        return True
    else:
        not_live = ' CONNECTON NOT ACQUIRED '
        print('-' * len(not_live))
        print(not_live)
        print('-' * len(not_live))

        return False

Здесь я реализовал функцию для пинга адресов 8.8.8.8 и 1.1.1.1 (для проверки подключения к сети), функцию вывода "красивых сообщений" и проверку подключения к сети.

Теперь наша задача реализовать сервер:

class Server:
    """
    Класс, представляющий собой TLS-сервер.

    Каждый объект этого класса содержит:
     + имя хоста
     + порт
     + путь до клиентского сертификата
     + путь до серверного ключа
     + путь до серверного сертификата
     + SSL контекст
    """
    def __init__(self, hostname: str, port: int, client_cert: str, server_key: str, server_cert: str):
        """
        Инициализация TLS-сервера

        :param hostname: имя хоста сервера
        :param port: порт сервера
        :param client_cert: путь до сертификата клиента
        :param server_key: путь до ключа сервера
        :param server_cert: путь до сертификата сервера
        """
        self.hostname = hostname
        self.port = port

        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            self.sock.bind((self.hostname, self.port))
            print_msg(f'Binding TLS server address: {self.hostname}:{self.port}', 'info')
        except Exception as ex:
            print_msg(f'An error occured when binding address: {ex}', 'error')
            sys.exit(1)
        else:
            print_msg(f'Server address has been binded successfully.', 'debug')

        try:
            self.client_cert = Path(client_cert)
            self.server_key = Path(server_key)
            self.server_cert = Path(server_cert)
        except TypeError as t_ex:
            print_msg(f'Type error when loading file paths: {t_ex}', 'error')
            sys.exit(1)

        self._check_paths()

        self.context = self._create_ssl_context()

    def _check_paths(self) -> bool:
        """
        Скрытый метод для проверки существования путей сертификатов и ключей.
        """
        
        if not self.server_key.exists() or not self.server_cert.exists():
            print_msg('Server keyfile or certfile has not exists. Exiting...', 'error')
        elif not self.client_cert.exists():
            print_msg('Client certfile is not exists', 'warning')
        else:
            print_msg('All files have been loaded successfully.')
            return True

        sys.exit(1)

    def _create_ssl_context(self) -> ssl.SSLContext:
        """
        Скрытый метод для создания SSL-контекста сервера.

        :return: новый объект SSLContext
        
        NOTE: данный метод SSL-контекст при помощи удобной функции:
            ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)

        NOTE: Протокол и настройки функции create_default_context могут быть изменены в любое
            время без предварительного уведомления. Значения представляют собой справедливый 
            баланс между максимальной совместимостью и безопасностью.
        """
        context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
        
        print_msg(f'Create SSL context (purpose: CLIENT_AUTH)', 'debug')

        try:
            context.load_cert_chain(certfile=self.server_cert, keyfile=self.server_key)
            context.options |= ssl.OP_SINGLE_ECDH_USE
            context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
            print_msg(f'Setup SSL context: cert required; no TLSv1, TLSv1_1, no TLSv1_2.', 'debug')
        except ssl.SSLError as ssl_ex:
            print_msg(f'SSL Context setup exception (SSlError): {ssl_ex}', 'error')
            sys.exit(1)
        else:
            print_msg(f'SSL Context created successfully!', 'debug')

        return context

    def get_message(self, conn, addr):
        """
        Получаем и выводим сообщение от клиента
        """
        print_msg(f"Wait message from {addr}...", 'info')
        message = conn.recv(1024).decode()
        print_msg(f'Received message from {addr}: {message}', 'info')
        print(f'{addr[0]}:{addr[1]} say: {message}')

    def broadcast(self, conn, addr):
        """
        Метод для отправки и получения сообщений

        :param conn: клиентское подключение
        :param addr: адрес клиента
        """
        print_msg(f'Start broadcast for {addr}', 'debug')
        print('Enter `exit` for stop broadcasting')

        while True:
            # Получаем сообщение от клиента
            self.get_message(conn, addr)

            # Отправляем сообщение клиенту
            cmd = input(f'{self.hostname}:{self.port} (server) $ ').lower().strip()

            if len(cmd) == "0":
                continue
            elif cmd == 'exit':
                break
            else:
                conn.send(cmd.encode())

        print_msg(f'Stop broadcast for {addr}', 'debug')

    def wrapping(self):
        """
        Метод для включения режима ожидания клиентов и "оборачивания" сокета.
        В обертке мы получаем данные клиента, сообщение и отправляем его обратно клиенту
        """
        print_msg('Listen socket...', 'debug')
        self.sock.listen(1)
        
        print_msg('Wrap server-side socket...', 'debug')

        try:
            with self.context.wrap_socket(self.sock, server_side=True) as wrapped_sock:
                wrapped_sock.settimeout(3600)       # Задаем таймаут 3600 секунд
                conn, addr = wrapped_sock.accept()  # Подключаем клиента
                print_msg(f'New address connected: {addr}', 'info')
                self.broadcast(conn, addr)          # Общаемся с клиентом
        except KeyboardInterrupt:
            self.sock.close()
            print_msg('KeyboardInterrupt was received. Shutdown server...', 'warning')

И клиента:

#!/usr/bin/python3
from pathlib import Path
import socket
import ssl
import sys
from src.utils import print_msg


class Client:
    def __init__(self, hostname: str, port: int, client_key: str, client_cert: str, server_cert: str):
        self.hostname = hostname
        self.port = port

        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
            self.sock.connect((self.hostname, self.port))
            print_msg(f'Connecting to TLS server address: {self.hostname}:{self.port}', 'info')
        except Exception as ex:
            print_msg(f'An error occured when connecting to address: {ex}', 'error')
            sys.exit(1)
        else:
            print_msg(f'Client has been connected successfully.', 'debug')

        try:
            self.client_cert = Path(client_cert)
            self.client_key = Path(client_key)
            self.server_cert = Path(server_cert)
        except TypeError as t_ex:
            print_msg(f'Type error when loading file paths: {t_ex}', 'error')
            sys.exit(1)

        self._check_paths()

        self.context = self._create_ssl_context()

    def _check_paths(self) -> bool:
        """
        Скрытый метод для проверки существования путей сертификатов и ключей.
        """
        
        if not self.client_key.exists() or not self.client_cert.exists():
            print_msg('Client keyfile or certfile has not exists. Exiting...', 'error')
        elif not self.server_cert.exists():
            print_msg('Server certfile is not exists', 'warning')
        else:
            print_msg('All files have been loaded successfully.')
            return True

        sys.exit(1)

    def _create_ssl_context(self) -> ssl.SSLContext:
        """
        Скрытый метод для создания SSL-контекста сервера.

        :return: новый объект SSLContext
        
        NOTE: данный метод SSL-контекст при помощи удобной функции:
            ssl.create_default_context(purpose=Purpose.SERVER_AUTH, cafile=None, capath=None, cadata=None)

        NOTE: Протокол и настройки функции create_default_context могут быть изменены в любое
            время без предварительного уведомления. Значения представляют собой справедливый 
            баланс между максимальной совместимостью и безопасностью.
        """
        context = ssl.SSLContext(ssl.PROTOCOL_TLS, cafile=self.server_cert)

        print_msg(f'Create SSL context (PROTOCOL_TLS)', 'debug')

        try:
            context.load_cert_chain(certfile=self.client_cert, keyfile=self.client_key)
            context.load_verify_locations(cafile=self.server_cert)
            context.options |= ssl.OP_SINGLE_ECDH_USE
            context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_TLSv1_2
        except ssl.SSLError as ssl_ex:
            print_msg(f'SSL Context setup exception (SSlError): {ssl_ex}', 'error')
            sys.exit(1)
        else:
            print_msg(f'SSL Context created successfully!', 'debug')

        return context

    def send_messages(self):
        with self.context.wrap_socket(self.sock, server_side=False, server_hostname=self.hostname) as wrapped_sock:
            self.wrapped_sock.settimeout(3600)
            print(f'{wrapped_sock.version()}; Type `exit` for disconnect')
            
            while True:
                message = input("Message > ").lower().strip()

                if len(message) == 0:
                    continue
                elif message == 'exit':
                    sys.exit(1)
                else:
                    wrapped_sock.send(message.encode())

                receives = wrapped_sock.recv(1024).decode()

                print(f'{self.hostname}@{self.port} say: {receives}')

Теперь можно проверить работу... Но перед этим надо сгенерировать ключ и сертификат для подключения клиента к серверу. Сделать это можно через утилиту openssl следующей командой:

openssl req -new -newkey rsa:3072 -days 365 -nodes -x509 -keyout client.key -out client.crt
openssl req -new -newkey rsa:3072 -days 365 -nodes -x509 -keyout server.key -out server.crt

И наконец можно написать главную функцию:

def main():
    if not first_check():
        print_msg('Please, connect to network', 'note')
        sys.exit(1)

    command = input("Server or client? ").lower()

    if command == 'server':
        server = Server("127.0.0.1", 8080, "client.crt", "server.key", "server.crt")
        server.wrapping()
    else:
        client = Client("127.0.0.1", 8080, "client.key", 'client.crt', 'server.crt')
        client.send_messages()


if __name__ == '__main__':
    main()

Вот такое небольшое клиент-серверное приложение у нас получилось. Вы можете додумать его, сделать многопоточный защищенный мессенджер. А мы переходим к заключению первой части статьи.

❯ Заключение

Эта часть — только первая, в следующей части мы рассмотрим что такое прокси, VPN. Мы узнаем как можно сделать свою частную сеть на сервере буквально в пару команд. Я бы хотел все уместить в одну статью, но тогда она получится слишком большая. Поэтому в ближайшие дни, а может и сразу, будет опубликована вторая часть. Если вам понравится тематика, то пожалуйста напишите в комментарии, хотите ли видеть еще статьи по этой теме.

Я надеюсь, вам понравилась эта статья. Одна из самых больших и трудоемких работ, я сам узнал много нового в процессе написания. Было сложно, я потратил несколько недель на изучение этой темы.

Любая критика приветствуется, я понимаю что я мог легко что-то упустить или непонятно (а может даже неправильно) описать. Но я постарался проверить, но местами вы вполне себе можете увидеть слабые моменты. Но жизнь — это постоянное самосовершенство.

Буду рад если вы присоединитесь к моему небольшому телеграм-блогу.

? Читайте также:

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud - в нашем Telegram-канале

Перейти

Источники

Комментарии (3)


  1. Dmitri-D
    17.08.2024 02:14
    +1

    давай уж дальше, в сторону линукса - про утилиту ip, про сетевые namespaces, про поддержку инкапсуляции и декапсуляции в ядре, про мосты, про eBPF, про то как докер соединен c сетью, про сетевой экран, про ARP, про его отсутствие для IPv6 и т.п. Там в линуксе добра про сети хватит на 5 таких статей и это только по верхам


    1. DrArgentum Автор
      17.08.2024 02:14

      Спасибо, учту, узнаю и напишу.


      1. Dmitri-D
        17.08.2024 02:14

        просто для примера -- хотел пояснить что я имею ввиду под ip утилитой -- вот тут она немножко упоминается ;)

        https://medium.com/@bjnandi/linux-network-namespace-with-bridge-d68831d5e8a1
        https://unix.stackexchange.com/questions/270883/trying-to-run-openvpn-in-network-namespace
        https://unix.stackexchange.com/questions/742684/how-to-use-network-namespaces-for-vpn-split-tunneling

        успехов