Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to configure a Play application to use Let's Encrypt certificate?

Once I obtain the certificate, how do I generate a JKS key store from it?

How do I configure the Play application to use this key store?

Anything else I need to do?

like image 257
Jan Avatar asked Jul 12 '16 22:07

Jan


People also ask

Can we use lets encrypt in production?

Let's Encrypt SSL certificates is absolutely free and also used for production use as well. In order to install Let's Encrypt free SSL, you will need a valid domain name pointed with your server IP and it will issue a certificate for you.


Video Answer


2 Answers

Here is a script to obtain (update) the letsencrypt certificate:

#!/bin/bash

/path/to/your/app/stop # stop the play application; especially if it is running on port 80 otherwise the certificate generation will fail

rm -rf /etc/letsencrypt.bak

mv /etc/letsencrypt /etc/letsencrypt.bak

./letsencrypt-auto certonly --standalone -n -m [email protected] --agree-tos -d example.com -d www.example.com

cd /etc/letsencrypt/live/example.com

openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out cert_and_key.p12 -CAfile chain.pem -caname root -passout pass:your_password

keytool -importkeystore -srcstorepass your_password -destkeystore keyStore.jks -srckeystore cert_and_key.p12 -srcstoretype PKCS12 -storepass your_password

/path/to/your/app/start # start the application

You can schedule a cron job to run this script periodically as letsencrypt certificates currently expire after 90 days.

Once you obtain the certificate you need to modify the application start script as follows:

/path/to/your/app/app_name_script -Dhttps.port=443 -Dplay.server.https.keyStore.path=/etc/letsencrypt/live/example.com/keyStore.jks -Dplay.server.https.keyStore.password=your_password -Djdk.tls.ephemeralDHKeySize=2048 -Djdk.tls.rejectClientInitiatedRenegotiation=true # ... more parameters if required

Nearly there. When you run the application you get A- rating from SSL Labs. The rating downgrade is related to the Forward Secrecy. In order to sort out the Forward Secrecy issue (and get a full A rating) you need to specify the order of the cipher suites by implementing a custom SSLEngineProvider:

package controllers

import java.nio.file._
import java.security.KeyStore
import javax.net.ssl._

import play.core.ApplicationProvider
import play.server.api._

class CustomSslEngineProvider(appProvider: ApplicationProvider) extends SSLEngineProvider {

  val priorityCipherSuites = List(
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA")


  def readPassword(): Array[Char] = System.getProperty("play.server.https.keyStore.password").toCharArray

  def readKeyInputStream(): java.io.InputStream = {
    val keyPath = FileSystems.getDefault.getPath(System.getProperty("play.server.https.keyStore.path"))
    Files.newInputStream(keyPath)
  }

  def readKeyManagers(): Array[KeyManager] = {
    val password = readPassword()
    val keyInputStream = readKeyInputStream()
    try {
      val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
      keyStore.load(keyInputStream, password)
      val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
      kmf.init(keyStore, password)

      kmf.getKeyManagers
    } finally {
      keyInputStream.close()
    }
  }

  def createSSLContext(): SSLContext = {
    val keyManagers = readKeyManagers()
    val sslContext = SSLContext.getInstance("TLS")
    sslContext.init(keyManagers, Array.empty, null)
    sslContext
  }

  override def createSSLEngine(): SSLEngine = {
    val ctx = createSSLContext()
    val sslEngine = ctx.createSSLEngine
    val cipherSuites = sslEngine.getEnabledCipherSuites.toList
    val orderedCipherSuites =
      priorityCipherSuites.filter(cipherSuites.contains) ::: cipherSuites.filterNot(priorityCipherSuites.contains)
    sslEngine.setEnabledCipherSuites(orderedCipherSuites.toArray)
    val params = sslEngine.getSSLParameters
    params.setUseCipherSuitesOrder(true)
    sslEngine.setSSLParameters(params)
    sslEngine
  }
}

Do not forget to set

play.server.https.engineProvider=controllers.CustomSslEngineProvider

in your application.conf.

Tested with Play 2.5.x

like image 184
Jan Avatar answered Oct 09 '22 02:10

Jan


I searched in various forums, in the end I came up with a very fast (and almost automated) solution: First, as on the letsencrypt website they suggest, run these:

sudo apt-get update
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot 

then run

sudo certbot certonly --standalone -d domain.name.com

(for wildcards it's a bit more complicated, but the remainder of this procedure should be the same)

at this point it should tell you where the keys are, on some directory like /etc/letsencrypt/live/domain.name.com/*.pem (three keys: fullchain, privkey, and a chain)

then run openssl (editing domain name and password)

sudo openssl pkcs12 
        -export -in /etc/letsencrypt/live/domain.name.com/fullchain.pem 
        -inkey /etc/letsencrypt/live/domain.name.com/privkey.pem 
        -out cert_and_key.p12 
        -CAfile /etc/letsencrypt/live/domain.name.com/chain.pem 
        -caname root 
        -passout pass:<insert some password here>

then keytool (editing keystore path and password)

sudo keytool 
  -importkeystore 
  -srcstorepass <the password you inserted above>
  -destkeystore <path/key>.jks 
  -srckeystore cert_and_key.p12 
  -srcstoretype PKCS12 
  -storepass <the password you inserted above>

and finally you should find the jks key on the path you wrote above.

In application.conf:

play.server.https.keyStore.path = "<path/key>.jks"
play.server.https.keyStore.type = "JKS"
play.server.https.keyStore.password = "<the password you inserted above>"

Tested with Play 2.6.15, on Ubuntu 16 and 18

like image 45
LowFieldTheory Avatar answered Oct 09 '22 01:10

LowFieldTheory