Intro
- I have to program a microservice using Akka HTTP. [Done]
- The service has to run inside a docker container. [Done]
- The communication (via REST API [Done]) with this service has go over HTTPS. [TODO]
Problem
While trying to make the HTTPS GET request from the web browser:
Browser warning connection not secure
While trying to make a cURL request to the service on the server:
docker ps
PORTS
0.0.0.0:443->443/tcp
curl -v https://localhost
- TCP_NODELAY set
- Expire in 200 ms for 4 (transfer 0x5648dd24df90)
- Connected to localhost (127.0.0.1) port 443 (#0)
- ALPN, offering h2
- ALPN, offering http/1.1
- successfully set certificate verify locations:
- CAfile: none CApath: /etc/ssl/certs
- TLSv1.3 (OUT), TLS handshake, Client hello (1):
- OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:443
- Closing connection 0 curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to localhost:443
Question
How to configure properly the Akka HTTP's server to use HTTPS, for a given:
- .pem-File (containing the root and intermediate certificates),
- .cert file (containing the certificate for my domain),
- .key-File (containing the private key) ???
What I have done so far?
- I concatenate the ca-certs and my-cert to a single cert-chain.pem file and together with the private key created a p12 key store with openssl.
- I wrote the following code:
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
}
If it's not enough, the full code is in my public repository
What else I have done
Beside the fact that I read a lot about x509 Standard and TLS/HTTP and searched for answers in another threads, I also checked if the port 443 is open on the server (BTW: I do everything inside a VPN connection, so the firewall should not matter??) and I asked the admin for help, he's not familiar with Akka HTTP and docker, but he said that the curl output probably has something to do with the certificate chain.