JavaRush /Java блог /Random UA /Від HTTP до HTTPS
Viacheslav
3 рівень

Від HTTP до HTTPS

Стаття з групи Random UA
Від HTTP до HTTPS - 1
Зміст:

Вступ

У світі без веб-додатків ніяк. І почнемо ми з невеликого експерименту. У дитинстві я пам'ятаю, як у всіх кіосках продавалася така газета, як "Аргументи та факти". Згадав я про них тому, що на моє особисте сприйняття з дитинства, ці газети виглядали завжди дивно. І вирішив, а чи не зайти нам на їхній сайт:
Від HTTP до HTTPS - 2
Якщо перейти до довідки Google Chrome, ми прочитаємо, що цей сайт не використовує захищене з'єднання та інформація, якою ви обмінюєтеся з сайтом, може бути доступна стороннім. Давайте перевіримо якісь інші новини, наприклад новини Санкт-Петербурга від "Фонтанки", електронного ЗМІ:
Від HTTP до HTTPS - 3
Як видно, сайт Фонтанки з безпекою за цими даними проблем немає. Виходить, що веб-ресурси можуть бути безпечними, а можуть не бути. Також бачимо, що звернення до не захищених ресурсів відбувається за протоколом HTTP. А якщо ресурс захищений, обмін даними здійснюється за протоколом HTTPS, де S на кінці позначає "Secure". Протокол HTTPS описаний у специфікації rfc2818: " HTTP Over TLS ". Спробуємо створити свій веб-додаток і самі побачити, як це працює. І попутно будемо розбираючись у термінах.
Від HTTP до HTTPS - 4

Веб-додаток на Java

Отже, нам потрібно створити найпростіший веб-додаток на Java. Для початку, нам потрібна сама програма на Java. Для цього скористаємося системою автоматичного збирання проекту Gradle. Це нам дозволить не створювати вручну потрібну структуру каталогів + Gradle за нас керуватиме всіма необхідними для проекту бібліотеками та забезпечуватиме, щоб вони були доступні під час виконання коду. Детальніше про Gradle можна прочитати в невеликому огляді: " Коротке знайомство з Gradle ". Скористайтеся Gradle Init Plugin'ом і виконаємо команду:
gradle init --type java-application
Після цього відкриємо білд-скрипт build.gradle, в якому описано, з яких бібліотек складається наш проект, які Gradle нам надасть. Додамо туди залежність від веб-сервера, на якому ми експериментуватимемо:
dependencies {
    // Web server
    implementation 'io.undertow:undertow-core:2.0.20.Final'
     // Use JUnit test framework
     testImplementation 'junit:junit:4.12'
}
Щоб веб-програма працювала, нам обов'язково потрібен веб-сервер, де буде розміщено нашу програму. Веб серверів існує безліч, але основні це: Tomcat, Jetty, Undertow. Ми з Вами виберемо зараз Undertow. Щоб зрозуміти, як нам працювати з цим нашим веб-сервером, перейдемо на офіційний сайт Undertow і перейдемо в розділ документації . Ми з Вами підключабо залежність від Undertow Core, тому нам цікавить розділ про цей Core , тобто ядро, основу веб-сервера. Найпростішим способом є використання Builder API для Undertow:
public static void main(String[] args) {
	Undertow server = Undertow.builder()
            .addHttpListener(8080, "localhost")
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
    server.start();
}
Якщо ми виконаємо код, ми зможемо перейти на наступний веб-ресурс:
Від HTTP до HTTPS - 5
Працює просто. Завдяки Undertow Builder API ми додаємо HTTP слухача на адресау localhost та порт 8080. Цей слухач отримує запити від веб-браузера та повертає у відповідь рядок "Hello World". Відмінний веб-додаток. Але як бачимо, ми використовуємо протокол HTTP, тобто. такий обмін даними небезпечний. Давайте розбиратися, як здійснюють обмін по протоколу HTTPS.
Від HTTP до HTTPS - 6

Вимоги до HTTPS

Щоб зрозуміти, як підключити HTTPS, повернемося до специфікації HTTPS: " RFC-2818: HTTP Over TLS ". Згідно зі специфікацією, дані у протоколі HTTPS передаються поверх криптографічних протоколів SSL або TLS. Часто людей вводить в оману, що є SSL та TLS. Насправді SSL розвивався і змінював свої версії. Пізніше черговим витком у розвитку протоколу SSL став TLS. Тобто TLS це просто нова версія SSL. У специфікації і сказано: " SSL, and its successor TLS " . Отже, ми довідалися, що є криптографічні протоколи SSL/TLS. SSL- Це скорочення від Secure Sockets Layer і перекладається як "рівень захищених cокетів". Сокет (Socket) у перекладі з англійської - роз'єм. Учасники передачі даних по мережі використовують сокети як програмний інтерфейс (тобто API) для спілкування один з одним по мережі. Браузер постає як клієнт і використовує клієнтський сокет, а сервер приймає запит і видає відповідь використовує серверний сокет. І саме між цими сокетами відбувається обмін даними. Тому спочатку протокол і названо SSL. Але час минав і протокол розвивався. І одного разу протокол SSL став протоколом TLS. TLS – це скорочення від Transport Layer Security. TLS-протокол у свою чергу базується на специфікації протоколу SSL версії 3.0. Протокол TLS – це тема окремих статей та оглядів, тому я просто вкажу матеріали, які вважаю цікавими: Якщо дуже коротко, то основа HTTPS - це TLS handshake та перевірка "Server Identity" (тобто ідентифікація сервера) за його цифровим сертифікатом. Це важливо. Запам'ятаймо це, т.к. Пізніше ми повернемося до цього факту. Отже, раніше ми використовували HttpListener, щоб розповісти серверу, як працювати за протоколом HTTP. Якщо в прикладі вище ми додали HttpListener для роботи з HTTP, то для роботи з HTTPS нам потрібно додати HttpsListener:
Від HTTP до HTTPS - 7
Але щоб його додати нам потрібний SSLContext. Цікаво, що SSLContext - це клас не з Undertow, а javax.net.ssl.SSLContext. Клас SSLContext входить у так званий " Java Secure Socket Extension " (JSSE) - розширення Java для забезпечення безпеки інтернет з'єднання. Дане розширення описано в документі " Java Secure Socket Extension (JSSE) Reference Guide ". Як видно з вступної частини документації, JSSE надає фреймворк та Java реалізацію протоколів SSL та TLS. Як нам отримати SSLContext? Відкриваємо JavaDoc SSLContext і знаходимо метод getInstance . Як видно, для отримання SSLContext нам потрібно вказати назву Secure Socket Protocol. В описі параметрів зазначено, що ці назви можна подивитися в "Java Cryptography Architecture Standard Algorithm Name Documentation ". Тому, слідуємо вказівкам і йдемо в документацію. І бачимо, що ми можемо вибрати між SSL і TLS:
Від HTTP до HTTPS - 8
Тепер нам зрозуміло, що SSLContext нам треба створити так:
public SSLContext getSSLContext() {
	// 1. Получаем контекст, в рамках которого будем работать по TLS протоколу
	SSLContext context = null;
	try {
		context = SSLContext.getInstance("TLS");
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	}
	return context;
}
Створивши новий контекст згадуємо, що SSLContext описувався в " Java Secure Socket Extension (JSSE) Reference Guide ". Читаємо й бачимо, що "A newly created SSLContext should be initialized by calling the init method". Тобто створити контекст – мало. Його треба ініціалізувати. І це логічно, т.к. про безпеку ми розповіли лише те, що ми хочемо використовувати протокол TLS. Щоб ініціалізувати SSLContext, нам потрібно надати три речі: KeyManager, TrustManager, SecureRandom.
Від HTTP до HTTPS - 9

KeyManager

KeyManager – це менеджер ключів. Він відповідає за те, який "authentication credential" надати тому, хто до нас звернутися. Credential можна перекласти як посвідчення. Посвідчення потрібно, щоб клієнт був упевнений, що сервер той, за кого себе видає і йому можна довіряти. Що буде використано як посвідчення? Як ми пам'ятаємо, Server Identity перевіряється за цифровим сертифікатом сервера. Цей процес можна представити так:
Від HTTP до HTTPS - 10
Крім того, в " JSSE Reference Guide: How SSL Works " сказано, що SSL використовує "asymmetric cryptography", а це означає, що нам потрібна пара ключів: public key і private key. Оскільки мова зайшла про криптографію, то справа вступає "Java Cryptography Architecture" (JCA). Oracle з цієї архітектури надає чудовий документ: " Java Cryptography Architecture (JCA) Reference Guide ". Крім цього, можна прочитати короткий огляд JCA на JavaRush: Java Cryptography Architecture.Отже, для ініціалізації KeyManager нам потрібен KeyStore, в якому буде зберігатися сертифікат нашого сервера. Найпоширенішим способом створення сховища ключів і сертифікатів є утиліта keytool, яка входить до складу JDK. Приклад можна побачити в документації JSSE: Creating a Keystore to Use with JSSE ". Отже, нам потрібно за допомогою утиліти KeyTool створити сховище ключів і записати туди сертифікат. Цікаво, що раніше генерація ключа задавалася за допомогою -genkey, а тепер рекомендується використовувати -genkeypair. Нам потрібно буде визначити такі речі:
  • alias : Псевдонім або просто ім'я, під яким буде збережено запис у Keystore
  • keyalg : Алгоритм шифрування ключів. Виберемо алгоритм RSA, який є, по суті, стандартним рішенням для нашої мети.
  • keysize : Розмір ключа (у бітах). Мінімальний розмір 2048, т.к. розмір менший уже зламувався. Докладніше можна прочитати тут: " a ssl certificate in 2048 bit ".
  • dname : Distinguished Name, відмінне ім'я.
Важливо розуміти, що запитуваний ресурс (наприклад, https://localhost) порівнюватиметься за ним. Це називається "subject cn matching".
  • validity : Тривалість у днях, протягом яких генерований сертифікат валіден, тобто. дійсний.
  • ext : Certificate Extension, зазначені в " Named Extensions ".
Для Self-signed Certificates (тобто для сертифікатів, створених самостійно) необхідно вказати такі розширення:
  • -ext san:critical=dns:localhost,ip:127.0.0.1 > для виконання subject matching по SubjectAlternativeName
  • -ext bc=ca:false > щоб вказати, що цей сертифікат не використовується для підпису інших сертифікатів
Виконаємо команду (приклад для Windows):
keytool -genkeypair -alias ssl -keyalg RSA -keysize 2048 -dname "CN=localhost,OU=IT,O=Javarush,L=SaintPetersburg,C=RU,email=contact@email.com" -validity 90 -keystore C:/keystore.jks -storepass passw0rd -keypass passw0rd -ext san:critical=dns:localhost,ip:127.0.0.1 -ext bc=ca:false
Т.к. буде створено файл, переконайтеся, що у Вас є всі права на створення файлу. Крім того, швидше за все, Ви побачите пораду на кшталт цього:
Від HTTP до HTTPS - 11
Тут нам кажуть, що JKS – пропрієтарний формат. Пропрієтарний - значить є приватною власністю авторів і призначений для використання лише у Java. При роботі зі сторонніми утилітами може виникнути конфлікт, тому нас попереджають. Крім того, ми можемо отримати помилку: The destination pkcs12 keystore has different storepass and keypass. Ця помилка виникає через те, що використовуються різні паролі від запису в Keystore і від keystore. Як сказано в документації до keytool , "For example, most third-party tools require storepass and keypass in PKCS #12 keystore to be the same". Ми можемо вказати ключ (наприклад, -destkeypass entrypassw). Але краще не порушувати вимог та задати однаковий пароль. Отже, імпорт може виглядати так:
keytool -importkeystore -srckeystore C:/keystore.jks -destkeystore C:/keystore.jks -deststoretype pkcs12
Приклад успішного виконання:
Від HTTP до HTTPS - 12
Для експорту сертифіката у файл можна виконати:
keytool -export -alias ssl -storepass passw0rd -file C:/server.cer -keystore C:/keystore.jks
Крім того, ми можемо отримати вміст Keystore таким чином:
keytool -list -v -keystore C:/keystore.jks -storepass passw0rd
Відмінно, тепер ми маємо keystore, в якому є сертифікат. Тепер його можна отримувати з коду:
public KeyStore getKeyStore() {
	// Согласно https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore
	try(FileInputStream fis = new FileInputStream("C:/keystore.jks")){
		KeyStore keyStore = KeyStore.getInstance("pkcs12");
		keyStore.load(fis, "passw0rd".toCharArray());
		return keyStore;
	} catch (IOException ioe) {
		throw new IllegalStateException(ioe);
	} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
		throw new IllegalStateException(e);
	}
}
Якщо є KeyStore, то можемо ініціалізувати і KeyManager:
public KeyManager[] getKeyManagers(KeyStore keyStore) {
	String keyManagerAlgo = KeyManagerFactory.getDefaultAlgorithm();
	KeyManagerFactory keyManagerFactory = null;
	try {
		keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgo);
		keyManagerFactory.init(keyStore, "passw0rd".toCharArray());
		return keyManagerFactory.getKeyManagers();
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	} catch (UnrecoverableKeyException | KeyStoreException e) {
		throw new IllegalStateException(e);
	}
}
Наша перша мета досягнута. Залишилося розібратися, що таке TrustManager. TrustManager описаний у документації JSSE у розділі " The TrustManager Interface ". Він дуже схожий на KeyManager, але його ціль перевірити, чи можна довіряти тому, хто запитує з'єднання. Якщо зовсім грубо, то це KeyManager навпаки =) У нас немає необхідності в TrustManager'і, тому передамо null. Тоді буде створено TrustManager за умовчанням, який не перевіряє кінцевого користувача, який виконує запити на наш сервер. У документації так і сказано: "default implementation will be used". Аналогічно із SecureRandom. Якщо ми вкажемо null, буде використана реалізація за замовчуванням. Згадаймо тільки, що SecureRandom - це клас, що відноситься до JCA та описаний у документації JCA у розділі "". Отже, підготовка з урахуванням всіх вищеописаних методів може бути таким:
public static void main(String[] args) {
	// 1. Подготавливаем приложение к работе по HTTPS
	App app = new App();
	SSLContext sslContext = app.getSSLContext();
	KeyStore keyStore = app.getKeyStore();
	KeyManager[] keyManagers = app.getKeyManagers(keyStore);
	try {
		sslContext.init(keyManagers, null, null);
	} catch (KeyManagementException e) {
		throw new IllegalStateException(e);
	}
Далі залишається лише запустити сервер:
// 2. Поднимаем сервер
 	int httpsPort = 443;
	Undertow server = Undertow.builder()
            .addHttpsListener(httpsPort, "localhost", sslContext)
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
	server.start();
}
Цього разу наш сервер буде доступний за адресаою https://localhost:443 Проте, ми як і раніше отримаємо помилку, що не можна довіряти цьому ресурсу:
Від HTTP до HTTPS - 13
Давайте розбиратися, що не так із сертифікатом і що з цим робити.
Від HTTP до HTTPS - 14

Управління сертифікатами

Отже, наш сервер вже готовий працювати за HTTPS, але клієнт йому не довіряє. Чому? Давайте подивимося:
Від HTTP до HTTPS - 15
Причина в тому, що сертифікат є самопідписаним (Self-signed Certificate). Під самопідписаним SSL сертифікатом розуміють сертифікат відкритого ключа, виданий та підписаний тією самою особою, яку він ідентифікує. Тобто його не видавав шановний центр сертифікації (CA, він же Certificate Authority). Центр сертифікації (Certificate Authority) виступає як довірена особа і схожий на нотаріуса у звичайному житті. Він запевняє, що видані сертифікати надійні. Послуга видачі сертифікатів такими CA є платною, тому втрата довіри та репутаційні ризики нікому не потрібні. За промовчанням є кілька центрів сертифікації, яким довіряють. Цей список доступний для редагування. І управління списком центрів сертифікації у кожній операційній системі своє. Наприклад, керування цим списком у Windows можна прочитати тут:Manage Trusted Root Certificates in Windows . Давайте додамо сертифікат у довірені, як зазначено в повідомленні про помилку. Для цього, спочатку, скачаємо сертифікат:
Від HTTP до HTTPS - 16
У OS Windows натиснемо Win+R і здійснимо mmcдля виклику консолі управління. Далі натиснемо Ctrl+M для додавання розділу "Сертифікати" до поточної консолі. Далі в підрозділі "Довірені кореневі центри сертифікації" виконаємо Действия / Все задачи / Импорт. Виконаємо імпорт файлу, завантаженого раніше у файл. Браузер міг запам'ятати минуле стан довіри сертифікату. Тому перед відкриттям сторінки потрібно виконати рестарт браузера. Наприклад, у Google Chrome в адресаному рядку потрібно виконати chrome://restart. У OS Windows для перегляду сертифікатів також можна скористатися утилітою certmgr.msc:
Від HTTP до HTTPS - 17
Якщо ми все зробабо правильно, то побачимо успішне звернення до нашого HTTPS сервера:
Від HTTP до HTTPS - 18
Як бачимо, сертифікат тепер вважається дійсним, ресурс доступний, помилок немає.
Від HTTP до HTTPS - 19

Підсумок

Ось ми з Вами і розібрали, як виглядає схема включення протоколу HTTPS на веб-сервері і що для цього потрібно. Сподіваюся, на цьому етапі стало зрозуміло, що підтримка забезпечена взаємодією Java Cryptography Architecture (JCA), що відповідає за криптографію, та Java Secure Socket Extension (JSSE), що забезпечує реалізацію TLS на стороні Java. Ми побачабо, як використовується утиліта keytool, що входить до складу JDK, для роботи зі сховищем ключів і сертифікатів KeyStore. Крім того, ми зрозуміли, що HTTPS використовує протоколи SSL/TLS для безпеки. Для закріплення раджу прочитати чудові статті на цю тему: Сподіваюся, після цього невеликого огляду HTTPS стане трохи прозорішим. А у разі потреби включення HTTPS Ви зможете легко розуміти терміни з документації ваших серверів додатків та фреймворків. #Viacheslav
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ