¿Cómo configurar correctamente HTTPS de cadena de certificados, certificado y clave privada? Akka HTTP

Intro

  • Tengo que programar un microservicio usando Akka HTTP. [Done]
  • El servicio tiene que funcionar dentro de un contenedor de muelles. [Done]
  • La comunicación (a través de REST API [Done]) con este servicio ha superado HTTPS. [TODO]

Problema

Mientras intenta hacer la solicitud HTTPS GET del navegador web:

Conexión de advertencia del navegador no segura

Mientras intenta hacer una solicitud de cURL al servicio en el servidor:

docker ps

PORTS

0,00,0,443-jó443/tcp

curl -v https://localhost

  • TCP_NODELAY set
  • Expire en 200 ms para 4 (transfer 0x5648dd24df90)
  • Conectado al host local (127.0.0.1) puerto 443 (#0)
  • ALPN, que ofrece h2
  • ALPN, que ofrece http/1.1
  • establecer con éxito los lugares de verificación de certificados:
  • CAfile: ninguno CApath: /etc/ssl/certs
  • TLSv1.3 (OUT), TLS Handhake, Cliente hola (1):
  • OpenSSL SSL_connect: SSL_ERROR_SYSCALL en relación con localhost:443
  • Conexión de cierre 0 curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL en relación con localhost:443

Pregunta

Cómo configurar correctamente el servidor de Akka HTTP para utilizar HTTPS, para un dado:

  • .pem-File (contiene la raíz y certificados intermedios),
  • .cert file (contiene el certificado para mi dominio),
  • .key-File (contiene la llave privada) ??

¿Qué he hecho hasta ahora?

  1. Concateno las certidumbres y mi certidumbre a una sola cadena de cert. pem file y junto con la llave privada crearon una p12 key store con openssl.
  2. Escribí el siguiente código:
package de.htw_berlin.http

import java.security.SecureRandom
import akka.http.scaladsl.{ConnectionContext, HttpsConnectionContext}
import de.htw_berlin.security.{PKCS12KeyStoreFactory, X509KeyManagersFactory}

import java.io.FileInputStream
import javax.net.ssl.SSLContext

object HttpsConnectionContextFactory {

  // TODO refactor
  def apply(keyStoreFilename: String, keyStorePassword: String): HttpsConnectionContext = {
    val p12KeyStore = PKCS12KeyStoreFactory(new FileInputStream(keyStoreFilename), keyStorePassword)
    val sslContext: SSLContext = SSLContext.getInstance("TLS")
    sslContext.init(X509KeyManagersFactory(p12KeyStore, keyStorePassword), null, new SecureRandom)
    ConnectionContext.httpsServer(sslContext)
  }

}
package de.htw_berlin.security

import java.security.KeyStore

import javax.net.ssl.{KeyManager, KeyManagerFactory}

object X509KeyManagersFactory {

  def apply(keyStore: KeyStore, keyStorePassword: String): Array[KeyManager] = {
    val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
    keyManagerFactory.init(keyStore, keyStorePassword.toCharArray)
    keyManagerFactory.getKeyManagers
  }

}
package de.htw_berlin.security

import java.io.InputStream
import java.security.KeyStore

object PKCS12KeyStoreFactory {

  def apply(keyStoreAsInputStream: InputStream, keyStorePassword: String): KeyStore = {
    val p12KeyStore = KeyStore.getInstance("PKCS12")
    p12KeyStore.load(keyStoreAsInputStream, keyStorePassword.toCharArray)
    p12KeyStore
  }

}
package de.htw_berlin.http

import akka.actor.typed.ActorSystem
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.{Http, HttpsConnectionContext}
import de.htw_berlin.http.dip.RequestHandler

import scala.concurrent.Future

// TODO test.
/** Represents a HTTPS server which can be bind to a host and port.
 * The server needs a request handler for processing incoming requests.
 *
 * @param host         the optional host of the server. The default value is Constants.DefaultServerIpv4Address.
 * @param port         the optional port number of the server. The default value is Constants.DefaultHttpsPort.
 * @param httpsContext the https connection context for the https connection.
 * @param actorSystem  an implicit actor system.
 * @see de.htw_berlin.http.dip.RequestHandler
 */
class Server(val host: String = Constants.DefaultServerIpv4Address,
             val port: Int = Constants.DefaultHttpsPort,
             val httpsContext: HttpsConnectionContext)(implicit val actorSystem: ActorSystem[Nothing]) {

  /** Binds the current server to the endpoint parameters (host and port) and uses the passed
   * handler for processing incoming connections.
   *
   * @param handler the handler to be used for processing incoming requests.
   * @return a server binding future.
   */
  def bindAndHandleWith(handler: RequestHandler): Future[ServerBinding] =
    Http().newServerAt(host, port).enableHttps(httpsContext).bind(handler.getRoute)

}
package de.htw_berlin.http

/** This object contains constants related with the current module http. */
object Constants {

  /** The server's default IPv4 address. */
  val DefaultServerIpv4Address = "0.0.0.0"
  /** The default port for HTTPS connections. */
  val DefaultHttpsPort = 443

}

Si no es suficiente, el código completo está en mi público. repositorio

¿Qué más he hecho?

Además del hecho de que leí mucho sobre x509 Standard y TLS/HTTP y busqué respuestas en otros hilos, también comprobé si el puerto 443 está abierto en el servidor (BTW: Hago todo dentro de una conexión VPN, así que el cortafuegos no debe importar?) y le pregunté al administrador para obtener ayuda, él no está familiarizado con Akka HTTP y docker, pero probablemente dijo que la cadena de salida

Pregunta hecha hace 3 años, 4 meses, 29 días - Por quantumquill


5 Respuestas:

  • Sorprendentemente, el problema estaba relacionado con Docker y la tienda clave creada con openssl: El usuario raíz dentro del contenedor no tenía el privilegio de leer la tienda clave (Excepción denegada por permiso).

    Sin embargo, el código funciona y mantendré este hilo como referencia para la comunidad.

    Respondida el Dec 16, 2020 a las 16:20 - por bytebard30f9

    Votos positivos: 0 | Votos negativos: 0

  • Estás pasando null para TrustManager[] argumento aquí:

    sslContext.init(X509KeyManagersFactory(p12KeyStore, keyStorePassword), null, new SecureRandom)
    
    

    Checkout la documentación sobre cómo inicializar y pasar TrustManager: https://doc.akka.io/docs/akka-http/current/server-side/server-https-support.html#using-https

    Respondida el Dec 16, 2020 a las 16:28 - por codealchemy

    Votos positivos: 0 | Votos negativos: 0

  • El puerto 443 está abierto y funcionando

    telnet localhost 443 y ver si hay algo como scape character is '^]'.

    Asegúrese de que su servidor está hablando en protocolo https en lugar de https

    curl --insecure -v https://localhost para saltar la verificación del certificado SSL (pero aún establecer una conexión SSL) para ver si realmente recibe respuesta de su servidor

    Asegúrese de que su certificado de servidor está firmado a nivel mundial en lugar de auto-firmado

    Su navegador no confía en certificado desconocido, por favor asegúrese de obtener un certificado SSL de CA global, por ejemplo. Encriptemos. Su navegador tiene una lista de certificados CA codificados para verificar el certificado de cada sitio web.

    Por otro lado, tu rizo está mirando hacia arriba /etc/ssl/certs para obtener una lista de certificados de CA para verificar el certificado de su sitio web, así que la bandera --insecure es evitar verificar el certificado SSL.

    Respondida el Dec 16, 2020 a las 16:35 - por bitcraftsman

    Votos positivos: 0 | Votos negativos: 0

  • Para configurar correctamente un servidor Akka HTTP para utilizar HTTPS con un certificado y clave privada, necesitas seguir algunos pasos adicionales:

    • Verificar tu certificado y clave privada: Asegúrate de que tu certificado y clave privada sean válidos y estén correctamente configurados.
    • Crear un archivo de almacén de claves PKCS12: Utilizando openssl, puedes combinar tu certificado y clave privada en un archivo PKCS12. Por ejemplo:

    bash
       openssl pkcs12 -export -out keystore.p12 -inkey privatekey.key -in certificate.crt
       

    • Modificar tu código Akka HTTP: En tu código, puedes cargar el archivo de almacén de claves PKCS12 y configurar el contexto de conexión HTTPS. Puedes utilizar algo así:

    scala
       import java.io.FileInputStream
       import javax.net.ssl.SSLContext
       import akka.http.scaladsl.{ConnectionContext, HttpsConnectionContext}

    object HttpsConnectionContextFactory { def apply(keyStoreFilename: String, keyStorePassword: String): HttpsConnectionContext = { val keyStore = KeyStore.getInstance("PKCS12") keyStore.load(new FileInputStream(keyStoreFilename), keyStorePassword.toCharArray) val keyManagerFactory = KeyManagerFactory.getInstance("SunX509") keyManagerFactory.init(keyStore, keyStorePassword.toCharArray) val sslContext = SSLContext.getInstance("TLS") sslContext.init(keyManagerFactory.getKeyManagers, null, new SecureRandom) ConnectionContext.httpsServer(sslContext) } }

    • Crear y usar el contexto de conexión HTTPS: En tu clase `Server`, puedes usar el contexto de conexión HTTPS que has creado en el paso anterior:

    scala
       class Server(val host: String = Constants.DefaultServerIpv4Address,
                    val port: Int = Constants.DefaultHttpsPort,
                    val httpsContext: HttpsConnectionContext)(implicit val actorSystem: ActorSystem[Nothing]) {
         def bindAndHandleWith(handler: RequestHandler): Future[ServerBinding] =
           Http().newServerAt(host, port).enableHttps(httpsContext).bind(handler.getRoute)
       }
       

    Con estos pasos, deberías poder configurar correctamente Akka HTTP para utilizar HTTPS con tu certificado y clave privada. Asegúrate de que el archivo de almacén de claves PKCS12 esté correctamente configurado y que tu código Akka HTTP esté cargando correctamente el contexto de conexión HTTPS.

    Respondida el Dec 17, 2020 a las 16:23 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • 
    OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:443
    

    significa que hay un problema con la configuración del certificado o con la clave privada.

    Configuración correcta de HTTPS con una cadena de certificados, certificado y clave privada

    Para configurar correctamente HTTPS con una cadena de certificados, un certificado y una clave privada en Akka HTTP, sigue estos pasos:

    • Crea una cadena de certificados: Combina los certificados raíz e intermedios en un solo archivo `.pem`.
    • Crea un archivo P12 almacén de claves: Convierte la cadena de certificados, el certificado y la clave privada en un archivo P12 almacén de claves utilizando la siguiente herramienta OpenSSL:
    
    openssl pkcs12 -export -out keystore.p12 -inkey privateKey.key -in certificate.crt -certfile chain.pem
    
    • Carga el almacén de claves P12: Utiliza el almacén de claves P12 para crear un contexto de conexión HTTPS:
    scala
    val keyStore = KeyStore.getInstance("PKCS12")
    keyStore.load(new FileInputStream("keystore.p12"), "password".toCharArray)
    
    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(X509KeyManagersFactory(keyStore, "password"), null, new SecureRandom)
    
    val httpsConnectionContext = ConnectionContext.httpsServer(sslContext)
    
    • Configura el servidor Akka HTTP: Utiliza el contexto de conexión HTTPS para configurar el servidor Akka HTTP:
    scala
    Http().newServerAt("localhost", 443).enableHttps(httpsConnectionContext).bind(route)
    

    Solución de problemas de conexión

    Si sigues teniendo problemas para establecer una conexión segura, comprueba lo siguiente:

    Asegúrate de que el almacén de claves P12 contiene la cadena de certificados, el certificado y la clave privada correctos. Verifica que la contraseña proporcionada para el almacén de claves es correcta. Comprueba si el puerto 443 está abierto en el servidor y no está bloqueado por un firewall. Intenta utilizar una herramienta como openssl s_client para conectarse al servidor HTTPS y verificar si el certificado es válido y confiable.

    Ejemplo de código completo

    scala
    import akka.actor.{ActorSystem, Props}
    import akka.http.scaladsl.Http
    import de.htw_berlin.http.dip.RequestHandler
    
    import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
    import java.io.FileInputStream
    import java.security.{KeyStore, SecureRandom}
    
    object Main extends App {
    
      implicit val actorSystem: ActorSystem = ActorSystem()
    
      val keyStore = KeyStore.getInstance("PKCS12")
      keyStore.load(new FileInputStream("keystore.p12"), "password".toCharArray)
    
      val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
      keyManagerFactory.init(keyStore, "password".toCharArray)
    
      val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
      trustManagerFactory.init(keyStore)
    
      val sslContext = SSLContext.getInstance("TLS")
      sslContext.init(keyManagerFactory.getKeyManagers, trustManagerFactory.getTrustManagers, new SecureRandom)
    
      val httpsConnectionContext = ConnectionContext.httpsServer(sslContext)
    
      val requestHandler = actorSystem.actorOf(Props(new RequestHandler()))
    
      Http().newServerAt("localhost", 443).enableHttps(httpsConnectionContext).bind(requestHandler.getRoute)
    }
    

    Espero que esto te ayude a resolver el problema y configurar correctamente HTTPS en tu microservicio Akka HTTP.

    Respondida el Dec 17, 2020 a las 16:28 - por Gemini

    Votos positivos: 0 | Votos negativos: 0