Клієнт-серверна архітектура

Криптографічний захист каналів зв

Повний розгляд криптографічних основ захисту мережевих комунікацій мовою Go: доступне пояснення симетричного та асиметричного шифрування на прикладі листування, хеш-функції та цифрові підписи, протокол TLS 1.3 із детальним розбором фаз рукостискання та захисту від атак Man-in-the-Middle, інфраструктура відкритих ключів (PKI) та сертифікати X.509, центри сертифікації (CA), пакет crypto/tls для створення захищених серверів і клієнтів, взаємна автентифікація (mTLS) та модель Zero Trust на прикладі корпоративного мікросервісу.

Криптографічний захист каналів зв'язку та протокол TLS

🎯 Навчальні цілі

  • Зрозуміти фундаментальну проблему: чому просте TCP-з'єднання принципово небезпечне для передачі конфіденційних даних, і які саме загрози воно несе.
  • Побудувати чітку інтуїцію про різницю між симетричним (один спільний ключ) і асиметричним (пара ключів: відкритий і закритий) шифруванням через доступні життєві аналогії.
  • Усвідомити роль хеш-функцій та цифрових підписів як фундаментальних будівельних блоків довіри в розподілених системах.
  • Детально розібрати, як працює сучасний протокол TLS 1.3 (Transport Layer Security): які кроки виконуються під час рукостискання, як узгоджуються криптографічні алгоритми та як обидві сторони приходять до спільного сеансового ключа.
  • Зрозуміти концепцію інфраструктури відкритих ключів (PKI) і роль сертифікатів X.509 як «цифрових посвідчень особи» серверів та клієнтів у мережі.
  • Опанувати практичну роботу з пакетом crypto/tls у Go: налаштування захищеного TLS-сервера, валідація сертифікатів на клієнтській стороні та реалізація взаємної автентифікації (mTLS) у мікросервісній архітектурі.
  • Ознайомитися з моделлю безпеки Zero Trust і зрозуміти, чому сучасні корпоративні системи вимагають верифікації кожної сторони комунікації, навіть усередині приватної мережі.

🛠️ Технологічний стек

  • Мова: Go 1.22+
  • Пакети стандартної бібліотеки: crypto/tls, crypto/x509, crypto/rand, encoding/pem
  • Інструменти діагностики: openssl CLI, Wireshark з фільтром tls, curl --tlsv1.3 --verbose
  • Розгортання: Docker для ізольованих тестових середовищ із власними CA

Чому просте TCP-з'єднання — це відкритий канал без жодного захисту

Базовий транспортний протокол TCP проєктувався без вбудованих механізмів криптографічного захисту. Будь-яке з'єднання, встановлене через сокет net.Dial("tcp", "host:port"), передає дані відкритим байтовим потоком, доступним для перехоплення та спотворення на проміжних мережевих вузлах.

Розглянемо передачу облікових даних клієнта:

{
  "action": "login",
  "username": "john.doe@company.com",
  "password": "SecretPass123"
}

Якщо цей JSON передається відкритим TCP-каналом, будь-який проміжний маршрутизатор, комутатор чи публічна точка доступу Wi-Fi має прямий доступ до байтів пакета. Окрім пасивного прослуховування трафіку, зловмисник із доступом до мережевого обладнання може реалізувати активну атаку — модифікувати зміст пакетів або підмінити кінцевий сервер.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

actor "Клієнт" as Client #DBEAFE
participant "Відкрита мережа\n(Маршрутизатори / Wi-Fi)" as Network #FEE2E2
actor "Зловмисник\n(Sniffer / Wireshark)" as Attacker #FEE2E2
participant "Сервер" as Server #DCFCE7

Client -> Network : TCP потік: user="admin", pass="secret123"
Network -> Attacker : Пасивне перехоплення байтів
note over Attacker #FEE2E2
  Дані передаються відкритим текстом:
  паролі та токени скомпрометовано
end note
Network -> Server : Доставка відкритого тексту
@enduml
Атака «Людина посередині» (Man-in-the-Middle, MITM): зловмисник вклинюється в канал між клієнтом і сервером, перехоплюючи або змінюючи мережеві пакети. Обидві легітимні сторони вважають, що спілкуються безпосередньо одна з одною.

Для вирішення цих загроз застосовується протокол TLS (Transport Layer Security) — стандарт криптографічного захисту мережевих комунікацій прикладного рівня. Його архітектура спирається на три базові примітиви: симетричне шифрування, асиметричну криптографію та хеш-функції з цифровими підписами.


Симетричне шифрування: один спільний ключ для двох сторін

У моделі симетричного шифрування (symmetric encryption) для операцій шифрування та дешифрування використовується один спільний секретний ключ.

Розглянемо схему взаємодії двох учасників — Аліси та Боба:

  1. Сторони заздалегідь узгоджують секретний ключ (наприклад, рядок SECRET_KEY_2026).
  2. Аліса перетворює вихідне повідомлення на шифротекст (ciphertext) за допомогою симетричного алгоритму (наприклад, AES):
f8a3d9c2e1b7...4f6a8c9d2e1b
  1. Шифротекст передається відкритою мережею. Навіть у разі перехоплення повідомлення неможливо прочитати без знання ключа.
  2. Боб застосовує той самий ключ для дешифрування і відновлює вихідний текст.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

actor "Аліса" as Alice #DBEAFE
actor "Боб" as Bob #DBEAFE
participant "Мережа\n(потенційний зловмисник)" as Network #FEE2E2

note over Alice #FEF3C7
  Спільний ключ: **SECRET_KEY_2026**
  (узгоджено заздалегідь)
end note

Alice -> Alice : Шифрує повідомлення\nключем SECRET_KEY_2026
Alice -> Network : Відправляє шифротекст:\nf8a3d9c2e1b7...
Network -> Bob : Доставляє шифротекст
Bob -> Bob : Дешифрує ключем\nSECRET_KEY_2026
note over Bob #DCFCE7
  Отримує вихідний текст
end note
@enduml

Симетричні алгоритми (AES, ChaCha20) оптимізовані для виконання на апаратному рівні сучасних процесорів (інструкції AES-NI) і забезпечують пропускну здатність у кілька гігабайтів за секунду.

Основне обмеження симетричного підходу — проблема розподілу ключів (key distribution problem): щоб передати спільний ключ відкритою мережею, потрібен захищений канал, для створення якого вже необхідний узгоджений ключ.

Історичний контекст: до розробки асиметричної криптографії розподіл симетричних ключів вимагав фізичної доставки кодових книг або спеціальних кур'єрів. У глобальних розподілених мережах із мільйонами незалежних клієнтів такий підхід непридатний.

Цю проблему вирішує асиметричне шифрування.


Асиметричне шифрування: пара ключів

Асиметричне шифрування (public-key cryptography) використовує пару математично пов'язаних ключів: відкритий (public key) та закритий (private key). Дані, зашифровані відкритим ключем, розшифровуються виключно відповідним закритим ключем:

  1. Боб генерує ключову пару. Закритий ключ зберігається в таємниці, а відкритий публікується у відкритий доступ.
  2. Аліса отримує відкритий ключ Боба і шифрує ним повідомлення.
  3. Отриманий шифротекст передається мережею.
  4. Тільки володар закритого ключа (Боб) може дешифрувати повідомлення. Навіть відправник (Аліса) після шифрування вже не може прочитати шифротекст без закритого ключа.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

actor "Аліса" as Alice #DBEAFE
actor "Боб" as Bob #DBEAFE
participant "Мережа\n(публічний простір)" as Network #FEE2E2

Bob -> Bob : Генерує пару ключів
note over Bob #DCFCE7
  **Закритий ключ (Private Key)**
  Тримає в таємниці, ніколи не розголошує
end note

Bob -> Network : Публікує **відкритий ключ (Public Key)**
note over Network #FEF3C7
  Будь-хто може завантажити і використовувати
end note

Alice -> Network : Завантажує відкритий ключ Боба
Alice -> Alice : Шифрує повідомлення\nвідкритим ключем Боба
Alice -> Network : Надсилає зашифрований текст

Network -> Bob : Передає зашифрований текст
Bob -> Bob : Дешифрує\nсвоїм закритим ключем
note over Bob #DCFCE7
  Тільки власник закритого ключа
  може прочитати повідомлення
end note
@enduml

Математична основа асиметричного шифрування базується на односторонніх функціях із лазівкою (trapdoor one-way functions) — операціях, які легко обчислити в прямому напрямку, але практично неможливо інвертувати без додаткового секретного параметра. Наприклад, алгоритм RSA (Rivest, Shamir, Adleman) спирається на обчислювальну складність факторизації добутку двох великих простих чисел (довжиною 2048 або 4096 біт), а алгоритми на основі еліптичних кривих (ECC) — на складність розв'язання задачі дискретного логарифму.


Хеш-функції та цифрові підписи: гарантія цілісності та автентичності

Крім конфіденційності та безпечного розподілу ключів, надійний транспортний канал повинен гарантувати дві властивості: цілісність (integrity) та автентичність (authenticity). Якщо шифротекст передається відкритим каналом, зловмисник може навмисно модифікувати байти потоку (активна атака). Без механізму перевірки цілісності отримувач не здатний визначити, чи відповідає дешифроване повідомлення початковому станові, відправленому відправником.

Для контролю цілісності застосовуються криптографічні хеш-функції (cryptographic hash functions) — алгоритми, які детерміновано перетворюють вхідні дані довільної довжини у вихідний бітовий рядок фіксованого розміру (дайджест або хеш, зазвичай 256 або 512 біт). Криптографічні хеш-функції мають такі фундаментальні властивості:

  1. Стійкість до відновлення прообразу (pre-image resistance): маючи значення хешу Завантаження..., обчислювально неможливо відновити початкове повідомлення Завантаження..., для якого Завантаження....
  2. Лавинний ефект (avalanche effect): зміна навіть одного біта у вхідних даних призводить до зміни приблизно половини бітів вихідного дайджесту, роблячи його значення непередбачуваним.
  3. Стійкість до колізій (collision resistance): практично неможливо знайти два різні повідомлення Завантаження..., для яких Завантаження....

Найпоширенішими алгоритмами цього класу є SHA-256 та SHA-512 (Secure Hash Algorithm). Приклад обчислення хешу на Go:

package main

import (
    "crypto/sha256"
    "fmt"
)

func main() {
    message := "Переказати 1000 гривень на рахунок X"
    hash := sha256.Sum256([]byte(message))
    fmt.Printf("SHA-256: %x\n", hash)

    // Змінимо одну цифру: "1000" -> "2000"
    modifiedMessage := "Переказати 2000 гривень на рахунок X"
    modifiedHash := sha256.Sum256([]byte(modifiedMessage))
    fmt.Printf("SHA-256 зміненого: %x\n", modifiedHash)
}

Вихід програми демонструє два кардинально відмінні 64-символьні шістнадцяткові рядки, попри різницю у вхідних даних лише в один символ.

Однак сама лише передача хешу разом із повідомленням не захищає від атакуючого на каналі зв'язку, який може перехопити пакет, замінити його зміст, обчислити новий хеш і надіслати підмінені значення отримувачу. Для запобігання підміні використовується цифровий підпис (digital signature).

Цифровий підпис поєднує хешування та асиметричну криптографію:

  1. Відправник обчислює дайджест вихідного повідомлення: Завантаження....
  2. Дайджест підписується (шифрується) закритим ключем відправника: Завантаження.... Отримане значення Завантаження... є цифровим підписом.
  3. Отримувач приймає повідомлення Завантаження... та підпис Завантаження....
  4. Отримувач самостійно обчислює хеш отриманого повідомлення Завантаження... та перевіряє підпис за допомогою відкритого ключа відправника: Завантаження....

Якщо обчислений хеш відповідає значенню з розшифрованого підпису, гарантуються дві умови:

  1. Цілісність: повідомлення не було модифіковано під час транспортування.
  2. Автентичність та невідмовність (non-repudiation): повідомлення створене виключно власником відповідного закритого ключа, оскільки відкритий ключ валідує лише коректний криптографічний підпис.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

box "Відправник: Аліса" #F1F5F9
participant "Повідомлення" as Msg #DBEAFE
participant "SHA-256" as Hash1 #E2E8F0
participant "Закритий ключ\nАліси" as PrivKey #FEE2E2
end box

box "Отримувач: Боб" #F1F5F9
participant "Отримане\nповідомлення" as RMsg #DBEAFE
participant "SHA-256" as Hash2 #E2E8F0
participant "Відкритий ключ\nАліси" as PubKey #DCFCE7
participant "Перевірка" as Verify #FEF3C7
end box

Msg -> Hash1 : Обчислити хеш
Hash1 -> PrivKey : Зашифрувати хеш\nзакритим ключем
note over PrivKey #FEE2E2
  **Цифровий підпис**
  (тільки Аліса може створити)
end note

PrivKey -> RMsg : Надіслати повідомлення + підпис

RMsg -> Hash2 : Обчислити хеш\nотриманого повідомлення
PrivKey -> PubKey : Розшифрувати підпис\nвідкритим ключем Аліси
Hash2 -> Verify : Порівняти обидва хеші
note over Verify #DCFCE7
  Якщо співпадають:
  ✅ Цілісність гарантована
  ✅ Автентичність підтверджена
end note
@enduml

Розглянуті базові примітиви — симетричне шифрування (швидкість та конфіденційність даних), асиметричне шифрування (безпечний розподіл ключів), хеш-функції (контроль цілісності) та цифрові підписи (автентифікація відправника) — складають криптографічний фундамент протоколу TLS.


Протокол TLS 1.3: захищене рукостискання та узгодження сеансового ключа

Протокол TLS (Transport Layer Security) — це криптографічний протокол, який працює поверх надійного транспорту TCP і забезпечує три базові властивості безпечної комунікації:

  1. Конфіденційність (confidentiality): захист від несанкціонованого перегляду переданих даних.
  2. Цілісність (integrity): виявлення будь-якої модифікації даних у процесі передачі.
  3. Автентичність (authenticity): підтвердження відповідності сторін заявленій ідентичності.

Стандарт TLS 1.3 (RFC 8446) оптимізував узгодження параметрів безпеки, усунув застарілі та потенційно вразливі криптографічні механізми й скоротив мережеву затримку. Розглянемо послідовність кроків під час TLS-рукостискання (TLS handshake).

Фаза 1: ClientHello — клієнт ініціює з'єднання

Клієнт ініціює процедуру встановлення каналу, надсилаючи повідомлення ClientHello, що містить:

  • Версію TLS, яку підтримує клієнт (у сучасному стеку — TLS 1.3).
  • Список наборів шифрів (cipher suites), підтримуваних клієнтом (наприклад, TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256).
  • Випадкове число клієнта (random nonce) — 32 байти криптографічно стійких псевдовипадкових даних для захисту від атак повторного відтворення (replay attacks).
  • Публічний ключ клієнта для узгодження секрету за алгоритмом Діффі-Геллмана (key share). На відміну від TLS 1.2, клієнт надсилає свій відкритий ключ уже в першому повідомленні, що усуває зайвий мережевий обмін.
Порівняння затримки рукостискання TLS 1.3 та TLS 1.2: Рукостискання в TLS 1.2 вимагало двох повних циклів передачі пакетів (2-RTT) до початку відправки зашифрованих прикладних даних. У TLS 1.3 цей процес скорочено до одного циклу (1-RTT), а при повторному підключенні до відомого сервера — до нуля циклів (0-RTT).

Фаза 2: ServerHello та шифрування повідомлень рукостискання

Сервер обирає взаємно підтримуваний криптографічний набір і надсилає повідомлення ServerHello, яке містить:

  • Обраний набір шифрів (cipher suite), наприклад TLS_AES_128_GCM_SHA256.
  • Випадкове число сервера (32 байти унікальних псевдовипадкових даних).
  • Публічний ключ сервера (key share) для завершення розрахунку секрету за алгоритмом ECDHE.
Шифрування метаданих у TLS 1.3: Отримавши публічні ключі з ClientHello та ServerHello, клієнт і сервер відразу розраховують проміжний секрет рукостискання (Handshake Secret). Усі наступні службові блоки сервера — EncryptedExtensions, Certificate, CertificateVerify та Finished — передаються у зашифрованому вигляді. У TLS 1.2 сертифікати передавалися відкритим текстом, що дозволяло пасивному спостерігачеві перехоплювати назви хостів та метадані сервісів.

Безпосередньо після ServerHello сервер передає зашифровані службові структури:

  1. EncryptedExtensions: параметри конфігурації з'єднання, які не впливають на ключовий матеріал (наприклад, узгодження протоколу прикладного рівня ALPN для HTTP/2).
  2. Certificate: ланцюжок цифрових сертифікатів X.509 сервера, підписаний довіреним CA.
  3. CertificateVerify: цифровий підпис хешу всіх попередніх повідомлень рукостискання, створений довгостроковим закритим ключем сервера. Цей підпис підтверджує, що сервер володіє закритим ключем, який відповідає сертифікату.

Клієнт верифікує сертифікат і підпис за такими критеріями:

  1. Ланцюжок підписів CA зводиться до локального сховища довірених кореневих сертифікатів.
  2. Цільовий домен відповідає записам у розширенні SAN (Subject Alternative Name).
  3. Поточний час потрапляє у вікно валідності сертифіката, а сам сертифікат не відкликаний.

Фаза 3: Обмін ключами Діффі-Геллмана та обчислення спільного секрету

Для узгодження симетричного сеансового ключа без його прямої передачі мережею використовується алгоритм ефемерного обміну ключами Діффі-Геллмана на еліптичних кривих (ECDHE — Elliptic Curve Diffie-Hellman Ephemeral).

У класичній схемі Діффі-Геллмана сторони використовують спільні відкриті параметри: просте число Завантаження... та генератор Завантаження.... Клієнт генерує приватне число Завантаження..., а сервер — приватне число Завантаження...:

Завантаження...
\begin{aligned} A &= g^a \bmod p \quad (\text{відкритий ключ клієнта}) \ B &= g^b \bmod p \quad (\text{відкритий ключ сервера}) \ K &= B^a \bmod p = (g^b)^a \bmod p = (g^a)^b \bmod p = A^b \bmod p \quad (\text{спільний секрет}) \end{aligned}

Сторонній спостерігач у мережі фіксує лише публічні значення Завантаження... та Завантаження.... Обчислення з них спільного секрету Завантаження... без знання секретів Завантаження... чи Завантаження... зводиться до розв'язання задачі дискретного логарифмування, що обчислювально недосяжно для сучасних комп'ютерів за умови достатньої довжини ключа.

У протоколі TLS 1.3 замість модульної арифметики застосовуються еліптичні криві (наприклад, Curve25519 або NIST P-256):

  • Приватні ключі являють собою великі скалярні значення Завантаження....
  • Публічні ключі — точки на еліптичній кривій Завантаження... та Завантаження....
  • Спільний секрет обчислюється як скалярний добуток: Завантаження....

Отриманий спільний секрет передається у функцію формування ключів HKDF (HMAC-based Extract-and-Expand Key Derivation Function), яка генерує робочі симетричні сеансові ключі для AES-GCM або ChaCha20-Poly1305: окремі ключі шифрування та вектори ініціалізації (IV) для клієнтського та серверного трафіку.

Параметр Ephemeral (ефемерний) вказує на те, що пари ключів Завантаження... та Завантаження... генеруються окремо для кожної сесії та видаляються з оперативної пам'яті після виведення ключів шифрування. Це гарантує виконання вимоги Perfect Forward Secrecy (PFS).

Фаза 4: Finished — підтвердження встановлення захищеного каналу

Після обчислення сеансових ключів сервер відправляє повідомлення Finished, що містить HMAC від транскрипту всіх надісланих та отриманих повідомлень рукостискання. Клієнт перевіряє значення HMAC, підтверджуючи відсутність спотворень чи атак підміни на попередніх етапах.

У відповідь клієнт відправляє власне повідомлення Finished. Після цього етап рукостискання вважається завершеним, і з'єднання переходить до фази Application Data: передача прикладних пакетів відбувається з використанням симетричного шифрування AEAD.

Повна послідовність повідомлень TLS 1.3 Handshake:

Loading diagram...
@startuml
skinparam style plain
autonumber
skinparam backgroundColor #FFFFFF
skinparam sequenceMessageAlign center

actor "Клієнт\n(Go / React Native)" as Client #DBEAFE
participant "Мережа TCP" as Network #E2E8F0
participant "Go TLS-сервер" as Server #DCFCE7

== Встановлення TCP-з'єднання ==
Client -> Network : SYN
Network -> Server : SYN
Server -> Network : SYN-ACK
Network -> Client : SYN-ACK
Client -> Network : ACK
Network -> Server : ACK
note over Client, Server #FEF3C7
  TCP-з'єднання ESTABLISHED
end note

== TLS 1.3 Handshake (1-RTT) ==
Client -> Server : **ClientHello**\n(TLS 1.3, ciphers, client_random, ECDHE public key share)
Server -> Client : **ServerHello**\n(обраний cipher, server_random, ECDHE public key share)

note over Client, Server #FEF3C7
  Обидві сторони обчислюють спільний секрет ECDHE
  та генерують Handshake Traffic Keys.
  Усі наступні повідомлення передаються ЗАШИФРОВАНИМИ!
end note

Server -> Client : **EncryptedExtensions** (параметри розширень)
Server -> Client : **Certificate** (сертифікат X.509 сервера)
Server -> Client : **CertificateVerify** (підпис транскрипту закритим ключем сервера)
Server -> Client : **Finished** (HMAC транскрипту рукостискання)

note over Client #DBEAFE
  Клієнт перевіряє сертифікат сервера:
  ✅ Підпис CA валідний
  ✅ Домен у SAN співпадає
  ✅ Термін дії актуальний
  ✅ Підпис CertificateVerify зійшовся
end note

Client -> Server : **Finished** (HMAC транскрипту від клієнта)

note over Client, Server #DCFCE7
  🔒 Рукостискання завершено (Application Traffic Keys активовано).
  Повний цикл зайняв лише 1-RTT.
end note

== Захищений обмін прикладними даними ==
Client -> Server : HTTP GET /api/v1/user (AEAD шифрування AES-GCM)
Server -> Client : HTTP 200 OK + JSON (AEAD шифрування AES-GCM)
@enduml
Захист від атак «людина посередині» (MITM): У разі підміни DNS або перехоплення мережевого трафіку фальшивий сервер може виконати TLS-рукостискання, але не здатний надати валідний сертифікат для цільового домену (наприклад, api.company.com), підписаний довіреним центром сертифікації. Клієнт зафіксує невідповідність сертифіката й розірве з'єднання до передачі будь-яких прикладних даних.

Інфраструктура відкритих ключів (PKI) та сертифікати X.509

Для перевірки автентичності публічних ключів і доменних імен використовується інфраструктура відкритих ключів (Public Key Infrastructure, PKI) — система розподілу та підтвердження довіри, заснована на ієрархії центрів сертифікації (Certificate Authorities, CA).

Ієрархія довіри: кореневі та проміжні центри сертифікації

В основі PKI лежать кореневі центри сертифікації (Root CA). Їхні сертифікати попередньо встановлені у сховищах довіри операційних систем (Windows, macOS, Linux) та мобільних платформ (Android, iOS). Закриті ключі Root CA зберігаються в апаратних модулях безпеки (HSM) в ізольованому контурі та застосовуються виключно для підпису сертифікатів проміжних центрів сертифікації (Intermediate CA).

Проміжні CA здійснюють регулярні операції з валідації та випуску кінцевих сертифікатів серверів. Розділення на кореневі та проміжні центри зменшує ризики: у разі компрометації проміжного CA відкликається лише його сертифікат, тоді як Root CA залишається незмінним. Таким чином формується ланцюжок довіри (certificate chain):

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

rectangle "Кореневий CA\n(Root CA: DigiCert Global Root)" as Root #FEE2E2
rectangle "Проміжний CA\n(Intermediate CA: DigiCert TLS RSA SHA256)" as Intermediate #FEF3C7
rectangle "Серверний сертифікат\napi.company.com" as Server #DCFCE7

Root -down-> Intermediate : підписує своїм\nзакритим ключем
Intermediate -down-> Server : підписує своїм\nзакритим ключем

note right of Root #FEE2E2
  Вбудований у сховища довіри ОС.
  Закритий ключ зберігається в HSM.
end note

note right of Intermediate #FEF3C7
  Видає сертифікати серверам.
  Сам підписаний Root CA.
end note

note right of Server #DCFCE7
  Містить відкритий ключ сервера
  та доменне ім'я api.company.com.
  Клієнт перевіряє весь ланцюжок.
end note
@enduml

Коли клієнт отримує сертифікат сервера під час TLS-рукостискання, він виконує наступні кроки перевірки:

  1. Перевіряє підпис серверного сертифіката за допомогою відкритого ключа проміжного CA (який також надсилається разом із серверним сертифікатом).
  2. Перевіряє підпис проміжного CA за допомогою відкритого ключа кореневого CA (який уже є у вбудованому списку довірених сертифікатів операційної системи).
  3. Перевіряє доменне ім'я в серверному сертифікаті: воно має співпадати з тим, до якого клієнт підключається.
  4. Перевіряє термін дії: поточна дата має потрапляти в інтервал між notBefore та notAfter.
  5. Перевіряє статус відкликання (revocation): чи не був цей сертифікат відкликаний раніше через компрометацію (через OCSP або CRL).

Якщо всі перевірки пройшли успішно — клієнт довіряє серверу і продовжує з'єднання. Якщо хоча б одна перевірка провалилася — з'єднання відхиляється.


Практична робота з TLS у Go: пакет crypto/tls

Стандартна бібліотека Go містить пакет crypto/tls, що реалізує специфікацію TLS (зокрема TLS 1.3) та інтегрується зі стандартними мережевими інтерфейсами net.Conn, net.Listener, а також сервером net/http.

Створення базового TLS-сервера

Для налаштування захищеного HTTP-сервера необхідні два файли: сертифікат сервера (server.crt) та відповідний закритий ключ (server.key). Для локальної розробки та тестування пари ключів і сертифікат генеруються за допомогою утиліти OpenSSL:

openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt \
  -days 365 -nodes -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
Чому розширення SAN є обов'язковим: У сучасних стандартах IETF та в стандартній бібліотеці Go (починаючи з Go 1.15) перевірка хоста за полем Common Name (CN) офіційно застаріла й ігнорується за замовчуванням. Якщо згенерувати сертифікат без subjectAltName, Go-клієнт викине критичну помилку x509: certificate relies on legacy Common Name field, use SANs instead.

Після виконання цієї команди в поточній директорії з'являться два файли: server.crt та server.key. Тепер напишемо Go-сервер, який використовує ці файли для TLS:

package main

import (
    "crypto/tls"
    "fmt"
    "log"
    "net/http"
)

func main() {
    // Простий HTTP-обробник
    http.HandleFunc("/api/v1/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintf(w, `{"status": "OK", "secure": true}`)
    })

    // Налаштування TLS: вимагаємо сучасний протокол TLS 1.3
    tlsConfig := &tls.Config{
        MinVersion: tls.VersionTLS13,
    }

    server := &http.Server{
        Addr:      ":8443",
        TLSConfig: tlsConfig,
    }

    log.Println("🔒 TLS-сервер запущено на https://localhost:8443")
    log.Fatal(server.ListenAndServeTLS("server.crt", "server.key"))
}

Ключові моменти цього коду:

  1. tls.Config — центральна структура налаштування параметрів безпеки:
    • MinVersion: tls.VersionTLS13 — відсікає всі застарілі протоколи (TLS 1.0, 1.1 та 1.2), усуваючи загрози атак типу перегляду та зниження версії (downgrade attacks).
    • Зверніть увагу: у Go поле tlsConfig.CipherSuites контролює шифри тільки для TLS 1.0–1.2. У TLS 1.3 шифронабори жорстко зафіксовані стандартом і автоматично обираються криптографічним ядром мови (AES-GCM або ChaCha20-Poly1305), тому ручне задання шифрів у цьому полі для TLS 1.3 є зайвим.
  2. ListenAndServeTLS("server.crt", "server.key") — метод, який зчитує пару ключів із диска та переводить сокет у режим очікування захищених з'єднань на порт 8443.

Запустимо сервер і перевіримо його роботу через curl:

curl --cacert server.crt https://localhost:8443/api/v1/health
$ curl --cacert server.crt https://localhost:8443/api/v1/health
{"status": "OK", "secure": true}
Замість небезпечного прапорця --insecure (який вимикає будь-яку верифікацію сервера), ми передали наш тестовий сертифікат як довірений центр через параметр --cacert server.crt. Це демонструє безпечне тестування без компрометації перевірки автентичності.

Налаштування TLS-клієнта у Go

Тепер створимо клієнтську програму на Go. Щоб клієнт довіряв нашому локальному сертифікату, ми створимо власний кореневий пул довіри (x509.CertPool):

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)

func main() {
    // Завантажуємо серверний сертифікат у локальний пул довіри
    caCert, err := os.ReadFile("server.crt")
    if err != nil {
        log.Fatalf("Не вдалося прочитати сертифікат: %v", err)
    }

    caCertPool := x509.NewCertPool()
    if !caCertPool.AppendCertsFromPEM(caCert) {
        log.Fatal("Не вдалося додати сертифікат до пулу CA")
    }

    // Налаштовуємо TLS-конфігурацію клієнта
    tlsConfig := &tls.Config{
        RootCAs:    caCertPool,       // Довіряємо нашому тестовому сертифікату
        MinVersion: tls.VersionTLS13, // Вимагаємо сучасний TLS 1.3
    }

    // Створюємо HTTP-клієнт із власним транспортом
    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: tlsConfig,
        },
    }

    resp, err := client.Get("https://localhost:8443/api/v1/health")
    if err != nil {
        log.Fatalf("Помилка запиту: %v", err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("Відповідь від сервера: %s\n", body)
    fmt.Printf("Версія TLS: %s\n", tls.VersionName(resp.TLS.Version))
    fmt.Printf("Cipher Suite: %s\n", tls.CipherSuiteName(resp.TLS.CipherSuite))
}

Результат виконання клієнта:

go run client.go
$ go run client.go
Відповідь від сервера: {"status": "OK", "secure": true}
Версія TLS: TLS 1.3
Cipher Suite: TLS_AES_128_GCM_SHA256

Клієнт успішно встановив захищене з'єднання, перевірив сертифікат сервера (додавши його до пулу довірених) і отримав зашифровану відповідь. Поле resp.TLS містить детальну інформацію про параметри встановленого TLS-з'єднання, включно з версією протоколу та обраним криптографічним набором.

Для продакшн-середовища замість самопідписаних сертифікатів використовуйте сертифікати, видані публічними довіреними CA (наприклад, безкоштовний Let's Encrypt через бібліотеку golang.org/x/crypto/acme/autocert). У такому випадку клієнту не потрібно модифікувати RootCAs — стандартний пул довірених CA операційної системи автоматично перевірить весь сертифікаційний ланцюжок.

Безпечні сокети: перехід від net.Conn до tls.Conn

У попередніх лекціях ми вивчали програмування сокетів через функції net.Listen та net.Dial, проєктували власні бінарні протоколи та текстові сервери. Важливо розуміти: протокол TLS не прив'язаний виключно до HTTP. Пакет crypto/tls спроєктований так, щоб безшовно інтегруватися з інтерфейсами net.Listener та net.Conn.

Будь-який TCP-потік можна зашифрувати, замінивши стандартний мережевий сокет на його криптографічну обгортку. Порівняємо реалізацію звичайного та захищеного сокетного ехо-сервера:

package main

import (
    "log"
    "net"
)

func runTCPServer() {
    // Створення звичайного сокета слухача
    listener, err := net.Listen("tcp", ":9000")
    if err != nil {
        log.Fatal(err)
    }
    defer listener.Close()

    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        // conn реалізує інтерфейс net.Conn (відкритий текст)
        go handleRawConnection(conn)
    }
}

func handleRawConnection(conn net.Conn) {
    defer conn.Close()
    buffer := make([]byte, 1024)
    n, _ := conn.Read(buffer)
    conn.Write(buffer[:n])
}

Ручне підвищення з'єднання (Connection Upgrading)

У клієнт-серверних протоколах часто виникає потреба розпочати комунікацію у відкритому режимі, а потім «підвищити» з'єднання до зашифрованого (так працює команда STARTTLS у протоколах SMTP або LDAP).

Пакет crypto/tls дозволяє обгорнути вже встановлений сокет net.Conn функцією tls.Server або tls.Client:

// 1. Встановлюємо звичайний TCP-сокет
rawConn, err := net.Dial("tcp", "localhost:9443")
if err != nil {
    log.Fatal(err)
}

// 2. Обгортаємо сокет криптографічним рушієм TLS
tlsConn := tls.Client(rawConn, tlsConfig)

// 3. Явно ініціюємо рукостискання з таймаутом через Context
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := tlsConn.HandshakeContext(ctx); err != nil {
    log.Fatalf("TLS Handshake завершився з помилкою: %v", err)
}

// Тепер через tlsConn можна безпечно передавати дані

Метод HandshakeContext(ctx) гарантує, що рукостискання буде примусово зупинено, якщо сервер зависне або атакуючий заблокує пакети відповіді.


Взаємна автентифікація (mTLS): коли сервер теж перевіряє клієнта

До цього моменту ми розглядали класичний сценарій TLS: клієнт перевіряє сервер, переконуючись, що розмовляє саме з тим доменом, до якого підключається. Але в розподілених системах — особливо в корпоративних мікросервісних архітектурах, платіжних шлюзах або мережах IoT — необхідна двостороння автентифікація: не тільки клієнт перевіряє сервер, але й сервер обов'язково верифікує ідентичність клієнта.

Це називається взаємною автентифікацією TLS (mutual TLS, скорочено mTLS). У такій схемі обидва учасники мають власні сертифікати X.509, підписані корпоративним центром сертифікації.

Сценарій застосування: корпоративний мікросервіс та модель Zero Trust

Уявімо сервіс платежів (Payment Service), що викликає внутрішній API обліку користувачів (User Service). Згідно з парадигмою безпеки Zero Trust («ніколи не довіряй, завжди перевіряй»), внутрішня мережа дата-центру розглядається як потенційно вороже середовище. Кожен внутрішній сервіс повинен криптографічно підтверджувати свої права доступу на транспортному рівні.

Режими перевірки клієнта (tls.ClientAuthType)

У структурі tls.Config поведінка сервера регулюється полем ClientAuth. Go надає п'ять режимів перевірки:

Режим перевіркиПоведінка сервераСфера застосування
tls.NoClientCertСервер не надсилає запит на сертифікат клієнта (за замовчуванням).Публічні вебсайти, класичний HTTPS.
tls.RequestClientCertЗапитує сертифікат, але не вимагає його та не валідує автоматично.Опціональний вхід або ручна валідація в коді.
tls.RequireAnyClientCertВимагає будь-який сертифікат, але не перевіряє його кореневим CA.Не рекомендується (відсутня перевірка довіри).
tls.VerifyClientCertIfGivenЯкщо сертифікат надано — суворо перевіряє його через ClientCAs.М'яка міграція системи на mTLS.
tls.RequireAndVerifyClientCertСуворий mTLS: вимагає валідний сертифікат, підписаний CA з пулу.Корпоративні мікросервіси, банкінг, Zero Trust.

Генерація сертифікатів для mTLS із підтримкою SAN

Створимо за допомогою openssl власний тестовий CA, а також сертифікати сервера і клієнта. Зверніть увагу: ми обов'язково вказуємо розширення subjectAltName для коректної роботи валідатора Go:

# Генеруємо закритий ключ та самопідписаний сертифікат Root CA
openssl genrsa -out ca.key 4096
openssl req -x509 -new -key ca.key -out ca.crt -days 3650 \
  -subj "/CN=CompanyInternalCA"

Реалізація mTLS-сервера у Go

Налаштуємо Go HTTP-сервер у режимі tls.RequireAndVerifyClientCert:

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "log"
    "net/http"
    "os"
)

func main() {
    // Завантажуємо кореневий сертифікат для перевірки клієнтів
    caCert, err := os.ReadFile("ca.crt")
    if err != nil {
        log.Fatalf("Не вдалося прочитати CA: %v", err)
    }

    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    tlsConfig := &tls.Config{
        ClientAuth: tls.RequireAndVerifyClientCert, // Обов'язкова валідація клієнта
        ClientCAs:  caCertPool,                     // Довірені CA для клієнтських ключів
        MinVersion: tls.VersionTLS13,
    }

    http.HandleFunc("/api/v1/user", func(w http.ResponseWriter, r *http.Request) {
        // Отримуємо верифікований сертифікат клієнта
        if len(r.TLS.PeerCertificates) > 0 {
            clientCert := r.TLS.PeerCertificates[0]
            log.Printf("✅ Авторизовано мікросервіс: %s", clientCert.Subject.CommonName)
            fmt.Fprintf(w, `{"authenticated_client": "%s", "access": "granted"}`, 
                clientCert.Subject.CommonName)
            return
        }
        http.Error(w, "Клієнтський сертифікат відсутній", http.StatusUnauthorized)
    })

    server := &http.Server{
        Addr:      ":8443",
        TLSConfig: tlsConfig,
    }

    log.Println("🔒 mTLS-сервер запущено на https://localhost:8443")
    log.Fatal(server.ListenAndServeTLS("server.crt", "server.key"))
}

Реалізація mTLS-клієнта у Go

Клієнт повинен завантажити власну пару ключів (client.crt, client.key) та надати її у структурі Certificates. Крім того, поле ServerName явно задає очікуване доменне ім'я сервера згідно з SAN:

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io"
    "log"
    "net/http"
    "os"
)

func main() {
    // Завантажуємо клієнтську пару ключів (сертифікат + закритий ключ)
    cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
    if err != nil {
        log.Fatalf("Не вдалося завантажити клієнтський сертифікат: %v", err)
    }

    // Завантажуємо Root CA для верифікації сервера
    caCert, err := os.ReadFile("ca.crt")
    if err != nil {
        log.Fatalf("Не вдалося прочитати CA: %v", err)
    }

    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},     // Надаємо власний сертифікат
        RootCAs:      caCertPool,                  // Перевіряємо сервер за нашим CA
        ServerName:   "user-service.internal",     // Очікуваний хост із SAN сервера
        MinVersion:   tls.VersionTLS13,
    }

    client := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: tlsConfig,
        },
    }

    resp, err := client.Get("https://localhost:8443/api/v1/user")
    if err != nil {
        log.Fatalf("Помилка запиту: %v", err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("✅ Відповідь від сервера: %s\n", body)
}

Результат виконання:

go run mtls-client.go
$ go run mtls-client.go
✅ Відповідь від сервера: {"authenticated_client": "payment-service.internal", "access": "granted"}

Якщо неавторизований клієнт спробує надіслати запит без сертифіката:

curl https://localhost:8443/api/v1/user --cacert ca.crt
$ curl https://localhost:8443/api/v1/user --cacert ca.crt
curl: (35) error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate

З'єднання скидається на рівні ядра TLS ще до того, як HTTP-обробник взагалі отримає байти запиту.

Loading diagram...
@startuml
skinparam style plain
skinparam backgroundColor #FFFFFF

actor "Payment Service\n(клієнт)" as Client #DBEAFE
participant "User Service\n(сервер)" as Server #DCFCE7

== Взаємна автентифікація ==
Client -> Server : ClientHello + публічний ключ ECDHE
Server -> Client : ServerHello + публічний ключ ECDHE
Server -> Client : **Certificate** (серверний сертифікат)
Server -> Client : **CertificateRequest** (вимога клієнтського сертифіката)

note over Client #FEE2E2
  Клієнт перевіряє серверний сертифікат
  і готує свій клієнтський сертифікат
end note

Client -> Server : **Certificate** (клієнтський сертифікат:\npayment-service.internal)
Client -> Server : **CertificateVerify** (підпис клієнта)

note over Server #FEF3C7
  Сервер перевіряє клієнтський сертифікат:
  ✅ Підписаний довіреним CA
  ✅ Термін дії актуальний
  ✅ CN=payment-service.internal
end note

Client -> Server : Finished
Server -> Client : Finished

note over Client, Server #DCFCE7
  🔒 Обидві сторони автентифіковані.
  Встановлено захищений канал.
end note
@enduml

Підсумок: ключові концепції захисту з'єднань

Основні принципи криптографічного захисту транспортного рівня:

🔐 Симетричне vs Асиметричне шифрування

Симетричне шифрування (AES, ChaCha20) використовує спільний сеансовий ключ для шифрування та дешифрування — забезпечує високу швидкість обробки даних за рахунок апаратних інструкцій процесора.

Асиметричне шифрування (RSA, ECDH) використовує пару ключів (відкритий та закритий) — вирішує завдання автентифікації та безпечного розподілу ключів без попереднього узгодження спільного секрету.

🤝 TLS Handshake

Протокол TLS 1.3 встановлює захищений канал за один мережевий цикл (1-RTT):

  1. Клієнт надсилає ClientHello з переліком підтримуваних шифрів та публічним ключем ECDHE.
  2. Сервер надсилає ServerHello, публічний ключ ECDHE та зашифрований сертифікат X.509.
  3. Обидві сторони обчислюють спільний секрет та генерують сеансові ключі через HKDF.
  4. Подальший трафік шифрується симетричним алгоритмом (AEAD).

📜 Сертифікати X.509 та PKI

Сертифікат X.509 — криптографічний документ, що зв'язує доменне ім'я сервера з його відкритим ключем і підтверджений цифровим підписом центру сертифікації (CA).

Ланцюжок довіри: Серверний сертифікат підписаний проміжним CA, який засвідчений кореневим CA (Root CA), вбудованим у сховище довіри операційної системи.

Клієнт перевіряє цілісність ланцюжка, співпадіння домену в SAN та термін дії сертифіката.

🔒 Взаємна автентифікація (mTLS)

У схемі mTLS виконується двостороння верифікація: клієнт перевіряє сервер, а сервер верифікує сертифікат клієнта.

Застосовується у міжсервісних комунікаціях, IoT та архітектурній моделі Zero Trust, де будь-який вузол мережі вимагає обов'язкової криптографічної автентифікації.

У Go реалізується через прапорець ClientAuth: tls.RequireAndVerifyClientCert на сервері та передачу клієнтського сертифіката в Certificates.


Практичні рекомендації та найкращі практики безпеки

Перш ніж завершити лекцію, зведемо разом найважливіші практичні поради щодо роботи з TLS у реальних проєктах:

Використання TLS 1.3 як базової версії протоколу

Старі версії TLS 1.0 та TLS 1.1 офіційно визнані застарілими та небезпечними (вразливості POODLE, BEAST, CRIME тощо). Навіть TLS 1.2, хоча й підтримується для зворотної сумісності, містить застарілі механізми обміну та важкі 2-RTT затримки. У Go завжди явно фіксуйте сучасний протокол:

tlsConfig := &tls.Config{
    MinVersion: tls.VersionTLS13,
}

Заборона вимкнення перевірки сертифікатів у продакшн

Опція InsecureSkipVerify: true повністю вимикає перевірку автентичності сервера і робить застосунок абсолютно беззахисним перед атаками людини посередині (MITM). Її використання допустиме виключно для локальних юніт-тестів.

Типова помилка початківців: розробник вмикає InsecureSkipVerify: true, щоб «заглушити помилку сертифіката» під час розробки, після чого цей код випадково потрапляє в репозиторій та на робочі сервери. Правильний інженерний підхід — підключити локальний тестовий сертифікат до пулу RootCAs клієнта.

Безпечне зберігання та ізоляція закритих ключів

Закритий ключ сервера (.key) — це найкритичніший секрет усієї інфраструктури:

  • Ніколи не додавайте файли ключів у систему контролю версій Git (додавайте *.key до .gitignore).
  • Обмежуйте файлові права доступу на рівні операційної системи: chmod 600 server.key.
  • У промислових середовищах використовуйте хмарні сховища секретів (HashiCorp Vault, AWS KMS) або апаратні модулі безпеки (HSM).

Автоматизація життєвого циклу сертифікатів

Сучасні сертифікати мають короткий життєвий цикл (90 днів у Let's Encrypt). Ручне оновлення неминуче призводить до людських помилок. Застосовуйте протокол ACME через стандартну бібліотеку autocert:

import "golang.org/x/crypto/acme/autocert"

func main() {
    certManager := &autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        Cache:      autocert.DirCache("certs"),
        HostPolicy: autocert.HostWhitelist("api.company.com"),
    }

    server := &http.Server{
        Addr: ":443",
        TLSConfig: &tls.Config{
            GetCertificate: certManager.GetCertificate,
        },
    }

    log.Fatal(server.ListenAndServeTLS("", ""))
}

Впровадження політики HSTS для вебклієнтів

Якщо сервіс взаємодіє з браузерами, додавайте HTTP-заголовок HTTP Strict Transport Security (HSTS), який наказує клієнту звертатися виключно через шифрований протокол HTTPS:

w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload")

Це надійно запобігає атакам класу SSL Stripping, де зловмисник намагається понизити захищене HTTPS-з'єднання до незахищеного HTTP.

Безперервний моніторинг термінів дії сертифікатів

Закінчення терміну дії сертифіката блокує доступ клієнтів до сервісу. Рекомендується налаштувати експорт метрик у системи моніторингу (Prometheus / Grafana) зі сповіщеннями за 30 днів до завершення строку:

import (
    "crypto/x509"
    "time"
)

func checkCertExpiry(cert *x509.Certificate) {
    daysUntilExpiry := time.Until(cert.NotAfter).Hours() / 24
    if daysUntilExpiry < 30 {
        log.Printf("⚠️ Сертифікат %s закінчується через %.0f днів!", cert.Subject.CommonName, daysUntilExpiry)
    }
}

Інтерактивні запитання для самоперевірки


Подальше вивчення та корисні ресурси

Для поглиблення знань про криптографічний захист мережевих комунікацій рекомендуємо наступні матеріали:

📚 Офіційна специфікація

RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3

Повна технічна специфікація протоколу TLS 1.3 від IETF. Детально описує кожен крок рукостискання, формати повідомлень та криптографічні алгоритми.

tools.ietf.org/html/rfc8446

🔐 Документація crypto/tls

Пакет crypto/tls стандартної бібліотеки Go

Повна документація всіх типів, функцій та опцій конфігурації TLS у Go, з численними прикладами коду.

pkg.go.dev/crypto/tls

🛡️ Інструменти тестування

SSL Labs Server Test

Безкоштовний онлайн-інструмент для перевірки конфігурації TLS вашого сервера: підтримувані версії протоколу, cipher suites, вразливості та рейтинг безпеки.

ssllabs.com/ssltest

🤖 Автоматизація сертифікатів

Let's Encrypt та ACME

Безкоштовний центр сертифікації з автоматичним видаванням і оновленням через протокол ACME. Документація Go-бібліотеки autocert.

letsencrypt.org | pkg.go.dev/golang.org/x/crypto/acme/autocert


Завдання для практичного закріплення

Налаштування HTTPS-сервера з автоматизацією Let's Encrypt

Розгорніть публічно доступний Go HTTP-сервер на хмарному VPS із валідним доменним ім'ям та автоматичним отриманням безкоштовного сертифіката від Let's Encrypt через бібліотеку autocert. Перевірте конфігурацію через SSL Labs Test — сервер має отримати рейтинг A або A+.

Реалізація взаємної автентифікації mTLS між мікросервісами

Створіть два Go-мікросервіси: Auth Service (видає JWT токени після перевірки облікових даних) та Data Service (повертає захищені дані тільки для автентифікованих сервісів). Налаштуйте взаємну автентифікацію mTLS між ними: Data Service має перевіряти, що запит надійшов саме від Auth Service через клієнтський сертифікат із коректним SAN.

Утиліта моніторингу терміну дії сертифікатів

Напишіть Go-утиліту, яка періодично (кожні 24 години) перевіряє термін дії сертифікатів усіх ваших продакшн-серверів (через підключення до них і аналіз resp.TLS.PeerCertificates) та надсилає попередження в Slack або Telegram-бот, якщо до закінчення терміну залишилося менше 30 днів.


Висновки

  1. Криптографічні примітиви: Симетричні алгоритми (AES, ChaCha20) забезпечують високопродуктивне шифрування трафіку, асиметричні алгоритми (RSA, ECDH) вирішують задачу автентифікації та обміну ключами, а хеш-функції у складі цифрових підписів гарантують цілісність даних і невідмовність.
  2. TLS 1.3: Оптимізований протокол захисту транспортного рівня, що зменшує затримку встановлення з'єднання до 1-RTT, забезпечує досконалу пряму секретність (PFS) за замовчуванням та шифрує службові сертифікати під час узгодження.
  3. Інфраструктура відкритих ключів (PKI): Ієрархічна модель довіри на базі сертифікатів X.509 дозволяє клієнтам перевіряти автентичність вузлів без попереднього спільного секрету.
  4. Мережевий стек Go: Пакет crypto/tls забезпечує прозору інтеграцію з net.Conn, net.Listener та http.Server, підтримуючи як стандартний TLS, так і взаємну автентифікацію mTLS.
  5. Архітектура Zero Trust: Використання mTLS усуває сліпу довіру до внутрішнього периметра мережі, вимагаючи обов'язкової двосторонньої автентифікації кожного сервісу на транспортному рівні.
Зв'язок із наступною темою: TLS забезпечує безпеку транспортного каналу між хостами, але встановлення з'єднання потребує резолюції символьних імен сервісів та маршрутизації повідомлень. У наступній лекції розглядаються протоколи прикладного рівня DNS та SMTP, а також інженерні механізми їх криптографічного захисту (DNSSEC, DoH, STARTTLS).

Протокол UDP та датаграмна передача даних

Розгляд протоколу UDP мовою Go: спостереження сирих датаграм без рукостискання через nc -u та tcpdump, заголовок UDP (RFC 768), обмеження MTU та загроза IP-фрагментації, примітиви net.ListenPacket, net.DialUDP та net.PacketConn, налаштування буферів сокета ОС, режими адресації Unicast, Broadcast і Multicast з протоколом IGMP, архітектура пулу воркерів (Worker Pool) для збору телеметрії та концепція RUDP (Reliable UDP).

Прикладні мережеві протоколи DNS та SMTP

Детальний розгляд фундаментальних протоколів прикладного рівня: DNS як основа інфраструктури імен в інтернеті (ієрархія доменів, формат DNS-повідомлень, типи записів A/AAAA/CNAME/MX/TXT/SRV, проблема CNAME на Zone Apex, механізми CNAME Flattening та RFC 9460 HTTPS/SVCB для роботи без www, резолвінг у Go через net.Resolver) та SMTP як стандарт електронної пошти (архітектура поштових систем, команди та коди відповідей, формат MIME, багатокомпонентні листи Multipart, вкладення файлів Base64, захист пошти STARTTLS/SPF/DKIM/DMARC, надсилання через пакет net/smtp).

Copyright © 2026