Skip to content

Instantly share code, notes, and snippets.

@arturaz
Last active March 30, 2020 09:24
Show Gist options
  • Save arturaz/08df58ea4a0004a0498218ff5bc9eac5 to your computer and use it in GitHub Desktop.
Save arturaz/08df58ea4a0004a0498218ff5bc9eac5 to your computer and use it in GitHub Desktop.
Support for creating a java SSLContext from letsencrypt certificates using BouncyCastle and Scala.
package app.utils
import java.io.{FileReader, Reader}
import java.nio.file.Path
import java.security.cert.Certificate
import java.security.{KeyStore, Security}
import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.jcajce.{JcaPEMKeyConverter, JcePEMDecryptorProviderBuilder}
import org.bouncycastle.openssl.{PEMEncryptedKeyPair, PEMKeyPair, PEMParser}
// From https://gist.github.com/subfuzion/08c5d85437d5d4f00e58
//
// SBT settings:
// val bouncyCastleVersion = "1.64"
// libraryDependencies ++= Seq(
// "org.bouncycastle" % "bcprov-jdk15on" % bouncyCastleVersion,
// "org.bouncycastle" % "bcpkix-jdk15on" % bouncyCastleVersion,
// )
object SSLUtil {
def createContext(maybeCaCrtFile: Option[Path], crtFile: Path, keyFile: Path, password: String): SSLContext = {
/**
* Add BouncyCastle as a Security Provider
*/
Security.addProvider(new BouncyCastleProvider)
val certificateConverter = new JcaX509CertificateConverter().setProvider("BC")
/**
* Load Certificate Authority (CA) certificate
*/
val maybeCaCrt = maybeCaCrtFile.map { caCrtFile =>
val caCertHolder = withReader(new PEMParser(new FileReader(caCrtFile.toFile))) { reader =>
reader.readObject.asInstanceOf[X509CertificateHolder]
}
certificateConverter.getCertificate(caCertHolder)
}
/**
* Load client certificate
*/
val certHolder = withReader(new PEMParser(new FileReader(crtFile.toFile))) {
_.readObject.asInstanceOf[X509CertificateHolder]
}
val cert = certificateConverter.getCertificate(certHolder)
/**
* Load client private key
*/
val keyObject = withReader(new PEMParser(new FileReader(keyFile.toFile)))(_.readObject)
val provider = new JcePEMDecryptorProviderBuilder().build(password.toCharArray)
val keyConverter = new JcaPEMKeyConverter().setProvider("BC")
val privateKey = keyObject match {
case pair: PEMEncryptedKeyPair => keyConverter.getKeyPair(pair.decryptKeyPair(provider)).getPrivate
case info: PrivateKeyInfo => keyConverter.getPrivateKey(info)
case _ => keyConverter.getKeyPair(keyObject.asInstanceOf[PEMKeyPair]).getPrivate
}
/**
* CA certificate is used to authenticate server
*/
val caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType)
caKeyStore.load(null, null)
maybeCaCrt.foreach(caCert => caKeyStore.setCertificateEntry("ca-certificate", caCert))
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
trustManagerFactory.init(caKeyStore)
/**
* Client key and certificates are sent to server so it can authenticate the client
*/
val clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType)
clientKeyStore.load(null, null)
clientKeyStore.setCertificateEntry("certificate", cert)
clientKeyStore.setKeyEntry("private-key", privateKey, password.toCharArray, Array[Certificate](cert))
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
keyManagerFactory.init(clientKeyStore, password.toCharArray)
val context = SSLContext.getInstance("TLSv1.2")
context.init(keyManagerFactory.getKeyManagers, trustManagerFactory.getTrustManagers, null)
context
}
private[this] def withReader[A, R <: Reader](r: R)(f: R => A): A = {
try { f(r) }
finally { r.close()}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment