JavaRush /Blog Java /Random-ES /De HTTP a HTTPS
Viacheslav
Nivel 3

De HTTP a HTTPS

Publicado en el grupo Random-ES
De HTTP a HTTPS - 1
Contenido:

Introducción

En el mundo moderno, no se puede vivir sin aplicaciones web. Y comenzaremos con un pequeño experimento. Cuando era niño, recuerdo cómo en todos los puestos vendían un periódico como “Argumentos y hechos”. Los recordé porque, según mi percepción personal desde la infancia, estos periódicos siempre me parecieron extraños. Y decidí si deberíamos ir a su sitio web:
De HTTP a HTTPS - 2
Si acudimos a la ayuda de Google Chrome, leeremos que este sitio no utiliza una conexión segura y la información que intercambia con el sitio puede ser accesible a terceros. Veamos otras noticias, por ejemplo las noticias de San Petersburgo del medio electrónico Fontanka:
De HTTP a HTTPS - 3
Como puedes comprobar, la web de Fontanka no tiene problemas de seguridad según estos datos. Resulta que los recursos web pueden ser seguros o no. También vemos que el acceso a recursos desprotegidos se produce a través del protocolo HTTP. Y si el recurso está protegido, el intercambio de datos se realiza mediante el protocolo HTTPS, donde la S al final significa "Seguro". El protocolo HTTPS se describe en la especificación rfc2818: " HTTP Over TLS ". Intentemos crear nuestra propia aplicación web y veamos por nosotros mismos cómo funciona. Y en el camino entenderemos los términos.
De HTTP a HTTPS - 4

Aplicación web en Java

Entonces, necesitamos crear una aplicación web muy simple en Java. Primero, necesitamos la propia aplicación Java. Para hacer esto, usaremos el sistema de compilación automática del proyecto Gradle. Esto nos permitirá no crear manualmente la estructura de directorios necesaria + Gradle administrará todas las bibliotecas necesarias para el proyecto por nosotros y se asegurará de que estén disponibles al ejecutar el código. Puede leer más sobre Gradle en una breve reseña: " Una breve introducción a Gradle ". Usemos el complemento Gradle Init y ejecutemos el comando:
gradle init --type java-application
Después de esto, abramos el script de compilación build.gradle, que describe en qué bibliotecas consta nuestro proyecto, que Gradle nos proporcionará. Agreguemos allí una dependencia del servidor web en el que experimentaremos:
dependencies {
    // Web server
    implementation 'io.undertow:undertow-core:2.0.20.Final'
     // Use JUnit test framework
     testImplementation 'junit:junit:4.12'
}
Para que una aplicación web funcione, definitivamente necesitamos un servidor web donde se alojará nuestra aplicación. Existe una gran variedad de servidores web, pero los principales son: Tomcat, Jetty, Undertow. Esta vez elegiremos Resaca. Para entender cómo podemos trabajar con este servidor web nuestro, vayamos al sitio web oficial de Undertow y vayamos a la sección de documentación . Tú y yo hemos conectado una dependencia de Undertow Core, por lo que nos interesa la sección sobre este mismo Core , es decir, el núcleo, la base del servidor web. La forma más sencilla es utilizar la API Builder para 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();
}
Si ejecutamos el código, podemos navegar al siguiente recurso web:
De HTTP a HTTPS - 5
Funciona de forma sencilla. Gracias a la API de Undertow Builder, agregamos un escucha HTTP al host local y al puerto 8080. Este escucha recibe solicitudes del navegador web y devuelve la cadena "Hello World" en respuesta. Gran aplicación web. Pero como vemos, utilizamos el protocolo HTTP, es decir. Este tipo de intercambio de datos no es seguro. Averigüemos cómo se realizan los intercambios utilizando el protocolo HTTPS.
De HTTP a HTTPS - 6

Requisitos para HTTPS

Para comprender cómo habilitar HTTPS, volvamos a la especificación HTTPS: " RFC-2818: HTTP Over TLS ". Según la especificación, los datos en el protocolo HTTPS se transmiten a través de los protocolos criptográficos SSL o TLS. Las personas a menudo se dejan engañar por el concepto de SSL y TLS. De hecho, SSL ha evolucionado y cambiado de versiones. Posteriormente, TLS se convirtió en el siguiente paso en el desarrollo del protocolo SSL. Es decir, TLS es simplemente una nueva versión de SSL. La especificación lo dice: “SSL y su sucesor TLS”. Entonces, aprendimos que existen protocolos criptográficos SSL/TLS. SSL es una abreviatura de Secure Sockets Layer y se traduce como "capa de sockets seguros". El enchufe traducido del inglés es un conector. Los participantes en la transmisión de datos a través de una red utilizan sockets como interfaz de programación (es decir, una API) para comunicarse entre sí a través de la red. El navegador actúa como un cliente y utiliza un socket de cliente, y el servidor que recibe una solicitud y emite una respuesta utiliza un socket de servidor. Y es entre estos sockets donde se produce el intercambio de datos. Por eso el protocolo se llamó originalmente SSL. Pero pasó el tiempo y el protocolo evolucionó. Y en un momento, el protocolo SSL se convirtió en el protocolo TLS. TLS es la abreviatura de Transport Layer Security. El protocolo TLS, a su vez, se basa en la especificación del protocolo SSL versión 3.0. El protocolo TLS es tema de artículos y reseñas separados, por lo que simplemente indicaré los materiales que encuentro interesantes: En resumen, la base de HTTPS es el protocolo de enlace TLS y la verificación de la "identidad del servidor" (es decir, la identificación del servidor) utilizando su certificado digital. Es importante. Recordemos esto, porque... Volveremos sobre este hecho más adelante. Entonces, anteriormente usamos HttpListener para decirle al servidor cómo operar a través del protocolo HTTP. Si en el ejemplo anterior agregamos un HttpListener para trabajar a través de HTTP, entonces para trabajar a través de HTTPS necesitamos agregar un HttpsListener:
De HTTP a HTTPS - 7
Pero para agregarlo necesitamos SSLContext. Curiosamente, SSLContext no es una clase de Undertow, sino javax.net.ssl.SSLContext. La clase SSLContext forma parte de la llamada " Java Secure Socket Extension " (JSSE), una extensión de Java para garantizar la seguridad de la conexión a Internet. Esta extensión se describe en la " Guía de referencia de Java Secure Socket Extension (JSSE) ". Como puede ver en la parte introductoria de la documentación, JSSE proporciona un marco y una implementación Java de los protocolos SSL y TLS. ¿Cómo obtenemos el SSLContext? Abra JavaDoc SSLContext y busque el método getInstance . Como puede ver, para obtener SSLContext necesitamos especificar el nombre "Protocolo de socket seguro". La descripción de los parámetros indica que estos nombres se pueden encontrar en la " Documentación de nombres de algoritmos estándar de arquitectura de criptografía Java ". Por tanto, sigamos las instrucciones y vayamos a la documentación. Y vemos que podemos elegir entre SSL y TLS:
De HTTP a HTTPS - 8
Ahora entendemos que necesitamos crear SSLContext de la siguiente manera:
public SSLContext getSSLContext() {
	// 1. Получаем контекст, в рамках которого будем работать по TLS протоколу
	SSLContext context = null;
	try {
		context = SSLContext.getInstance("TLS");
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	}
	return context;
}
Habiendo creado un nuevo contexto, recordamos que SSLContext se describió en la " Guía de referencia de Java Secure Socket Extension (JSSE) ". Leemos y vemos que "Un SSLContext recién creado debe inicializarse llamando al método init". Es decir, no basta con crear un contexto. Es necesario inicializarlo. Y esto es lógico, porque En cuanto a seguridad, solo te dijimos que queremos usar el protocolo TLS. Para inicializar SSLContext debemos proporcionar tres cosas: KeyManager, TrustManager, SecureRandom.
De HTTP a HTTPS - 9

Gestor de claves

KeyManager es un administrador de claves. Él es responsable de qué “credencial de autenticación” proporcionar a alguien que se comunique con nosotros. Credencial se puede traducir como identidad. La identidad es necesaria para que el cliente esté seguro de que el servidor es quien dice ser y de que se puede confiar en él. ¿Qué se utilizará como identificación? Como recordamos, la identidad del servidor se verifica mediante el certificado digital del servidor. Este proceso se puede representar de la siguiente manera:
De HTTP a HTTPS - 10
Además, la " Guía de referencia de JSSE: cómo funciona SSL " dice que SSL utiliza "criptografía asimétrica", lo que significa que necesitamos un par de claves: una clave pública y una clave privada. Ya que estamos hablando de criptografía, entra en juego la “Arquitectura de Criptografía Java” (JCA). Oracle proporciona un excelente documento sobre esta arquitectura: " Guía de referencia de la arquitectura de criptografía Java (JCA) ". Además, puede leer una breve descripción general de JCA en JavaRush: " Arquitectura de criptografía Java: primer contacto ". Entonces, para inicializar KeyManager, necesitamos un KeyStore, que almacenará el certificado de nuestro servidor. La forma más común de crear un almacén de claves y certificados es la utilidad keytool, que se incluye con el JDK. Se puede ver un ejemplo en la documentación de JSSE: " Creación de un almacén de claves para usar con JSSE ". Entonces, necesitamos usar la utilidad KeyTool para crear un almacén de claves y escribir el certificado allí. Curiosamente, la generación de claves se especificaba anteriormente mediante -genkey, pero ahora se recomienda utilizar -genkeypair. Necesitaremos definir las siguientes cosas:
  • alias : Alias ​​o simplemente el nombre bajo el cual se guardará la entrada en Keystore
  • keyalg : algoritmo de cifrado de claves. Elijamos el algoritmo RSA, que es esencialmente una solución estándar para nuestro propósito.
  • tamaño de clave : tamaño de clave (en bits). El tamaño mínimo recomendado es 2048, porque... Un tamaño más pequeño ya se ha roto. Puedes leer más aquí: " un certificado ssl en 2048 bits ".
  • dname : Nombre distinguido, nombre distinguido.
Es importante comprender que el recurso solicitado (por ejemplo, https://localhost) se comparará con él. Esto se llama "coincidencia de sujeto cn".
  • Validez : Duración en días durante los cuales el certificado generado es válido, es decir válido.
  • ext : Extensión de certificado especificada en " Extensiones con nombre ".
Para certificados autofirmados (es decir, para certificados creados de forma independiente), debe especificar las siguientes extensiones:
  • -ext san:critical=dns:localhost,ip:127.0.0.1 > para realizar la coincidencia de sujetos por SubjectAlternativeName
  • -ext bc=ca:false > para indicar que este certificado no se utiliza para firmar otros certificados
Ejecutemos el comando (ejemplo para sistema operativo 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
Porque Se creará el archivo, asegúrese de tener todos los derechos para crear el archivo. Es probable que también veas consejos como este:
De HTTP a HTTPS - 11
Aquí se nos dice que JKS es un formato propietario. Propietario significa que es propiedad privada de los autores y está destinado a ser utilizado únicamente en Java. Al trabajar con utilidades de terceros, puede surgir un conflicto, por lo que se nos advierte. Además, podemos recibir el error: The destination pkcs12 keystore has different storepass and keypass. Este error se produce porque las contraseñas para la entrada en el almacén de claves y para el almacén de claves en sí son diferentes. Como dice la documentación de keytool , "Por ejemplo, la mayoría de las herramientas de terceros requieren que storepass y keypass en un almacén de claves PKCS #12 sean iguales". Podemos especificar la clave nosotros mismos (por ejemplo, -destkeypass Entrypassw). Pero es mejor no violar los requisitos y establecer la misma contraseña. Entonces la importación podría verse así:
keytool -importkeystore -srckeystore C:/keystore.jks -destkeystore C:/keystore.jks -deststoretype pkcs12
Ejemplo de éxito:
De HTTP a HTTPS - 12
Para exportar el certificado a un archivo, puede ejecutar:
keytool -export -alias ssl -storepass passw0rd -file C:/server.cer -keystore C:/keystore.jks
Además, podemos obtener el contenido del almacén de claves de esta manera:
keytool -list -v -keystore C:/keystore.jks -storepass passw0rd
Genial, ahora tenemos un almacén de claves que contiene el certificado. Ahora puedes obtenerlo desde el código:
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);
	}
}
Si hay un KeyStore, entonces podemos inicializar el 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);
	}
}
Nuestro primer objetivo se ha logrado. Queda por descubrir qué es TrustManager. TrustManager se describe en la documentación JSSE en la sección " La interfaz de TrustManager ". Es muy similar a KeyManager, pero su propósito es comprobar si se puede confiar en la persona que solicita la conexión. Para decirlo sin rodeos, este es el KeyManager al revés =) No necesitamos TrustManager, así que pasaremos nulo. Luego se creará un TrustManager predeterminado que no verifica al usuario final que realiza solicitudes a nuestro servidor. La documentación lo dice: "se utilizará la implementación predeterminada". Lo mismo con SecureRandom. Si especificamos nulo, se utilizará la implementación predeterminada. Recordemos que SecureRandom es una clase JCA y se describe en la documentación de JCA en la sección " La clase SecureRandom ". En total, la preparación teniendo en cuenta todos los métodos descritos anteriormente puede verse así:
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);
	}
Todo lo que queda es iniciar el servidor:
// 2. Поднимаем servidor
 	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();
}
Esta vez nuestro servidor estará disponible en la siguiente dirección, https://localhost:443 sin embargo, aún recibiremos un error que indica que no se puede confiar en este recurso:
De HTTP a HTTPS - 13
Averigüemos qué está mal con el certificado y qué hacer al respecto.
De HTTP a HTTPS - 14

Gestión de certificados

Entonces, nuestro servidor ya está listo para funcionar vía HTTPS, pero el cliente no confía en él. ¿Por qué? Echemos un vistazo:
De HTTP a HTTPS - 15
El motivo es que este certificado es un Certificado autofirmado. Un certificado SSL autofirmado se refiere a un certificado de clave pública emitido y firmado por la misma persona a la que identifica. Es decir, no fue emitido por ninguna autoridad certificadora respetada (CA, también conocida como Autoridad Certificadora). La Autoridad de Certificación actúa como fiduciario y es similar a un notario en la vida cotidiana. Asegura que los certificados que emite son confiables. El servicio de emisión de certificados por parte de dichas CA es pago, por lo que nadie necesita perder la confianza ni correr riesgos para su reputación. De forma predeterminada, existen varias autoridades certificadoras en las que se confía. Esta lista es editable. Y cada sistema operativo tiene su propia gestión del listado de autoridades certificadoras. Por ejemplo, cómo administrar esta lista en Windows se puede leer aquí: " Administrar certificados raíz de confianza en Windows ". Agreguemos el certificado a los de confianza como se indica en el mensaje de error. Para ello, primero descargue el certificado:
De HTTP a HTTPS - 16
En el sistema operativo Windows, presione Win+R y ejecute mmcpara llamar a la consola de control. Luego, presione Ctrl+M para agregar la sección "Certificados" a la consola actual. A continuación, en el subapartado “Autoridades de certificación raíz de confianza” ejecutaremos Действия / Все задачи / Импорт. Importemos el archivo que se descargó anteriormente al archivo. Es posible que el navegador haya recordado el estado de confianza anterior del certificado. Por lo tanto, antes de abrir la página es necesario reiniciar el navegador. Por ejemplo, en Google Chrome, en la barra de direcciones debes ejecutar chrome://restart. En el sistema operativo Windows, también puede utilizar la utilidad para ver certificados certmgr.msc:
De HTTP a HTTPS - 17
Si hicimos todo correctamente, veremos una llamada exitosa a nuestro servidor vía HTTPS:
De HTTP a HTTPS - 18
Como puede ver, el certificado ahora se considera válido, el recurso está disponible y no hay errores.
De HTTP a HTTPS - 19

Línea de fondo

Entonces, descubrimos cómo se ve el esquema para habilitar el protocolo HTTPS en un servidor web y qué se necesita para esto. Espero que en este punto quede claro que el soporte lo brinda la interacción de Java Cryptography Architecture (JCA), que es responsable de la criptografía, y Java Secure Socket Extension (JSSE), que proporciona la implementación de TLS en el lado de Java. Vimos cómo se utiliza la utilidad keytool incluida en el JDK para trabajar con el almacén de claves y certificados KeyStore. Además, nos dimos cuenta de que HTTPS utiliza protocolos SSL/TLS por motivos de seguridad. Para reforzar esto, te aconsejo leer excelentes artículos sobre este tema: Con suerte, después de esta pequeña revisión, HTTPS se volverá un poco más transparente. Y si necesita habilitar HTTPS, puede comprender fácilmente los términos en la documentación de sus servidores y marcos de aplicaciones. #viacheslav
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION