Permalink
Browse files

Refactoring SSL API and more test cases.

  • Loading branch information...
atooni committed Jan 25, 2019
1 parent 90c826f commit ecb439204642b29a7380d8edcd8840f019fc0606
@@ -10,6 +10,7 @@ package-lock.json
.bloop
node_modules
target.tgz
.sbtopts

blended.sslcontext/notes.txt

@@ -54,5 +54,4 @@ class CertificateActivator extends DominoActivator with TypesafeConfigWatching {
setupCertificateManager(cfg, idSvc)
}
}

}
@@ -1,6 +1,7 @@
package blended.security.ssl.internal

import blended.container.context.api.ContainerIdentifierService
import blended.security.ssl.CommonNameProvider
import com.typesafe.config.Config
import blended.util.config.Implicits._

@@ -60,7 +61,7 @@ case class CertificateConfig(
provider: String,
alias: String,
minValidDays: Int,
cnProvider: ConfigCommonNameProvider
cnProvider: CommonNameProvider
)

object CertificateConfig {
@@ -40,13 +40,13 @@ case class MemoryKeystore(certificates: Map[String, CertificateHolder]) {
certCfg: CertificateConfig,
providerMap: Map[String, CertificateProvider],
oldCert : Option[CertificateHolder]
) : Try[MemoryKeystore] = Try {
) : Try[(MemoryKeystore, List[String])] = Try {
providerMap.get(certCfg.provider) match {
case None =>
log.warn(s"Certificate provider [${certCfg.provider}] not found, not updating certificate [${certCfg.alias}]")
this
(this, List.empty)
case Some(p) =>
update(certCfg.alias, p.refreshCertificate(oldCert, certCfg.cnProvider).get).get
(update(certCfg.alias, p.refreshCertificate(oldCert, certCfg.cnProvider).get).get, List(certCfg.alias))
}
}

@@ -75,8 +75,8 @@ case class MemoryKeystore(certificates: Map[String, CertificateHolder]) {
log.info(s"Certificate [${head.alias}] is about to expire in ${remaining.toDouble / millisPerDay} days...refreshing certificate")

refreshCertificate(head, providerMap, Some(serverCertificate)) match {
case Success(newMs) =>
changed(newMs, tail, providerMap, head.alias :: changedAliases).get
case Success((newMs, c)) =>
changed(newMs, tail, providerMap, c ::: changedAliases).get

case Failure(t) =>
log.info(s"Could not refresh certificate [${head.alias}], reusing the existing one.")
@@ -90,7 +90,14 @@ case class MemoryKeystore(certificates: Map[String, CertificateHolder]) {
// The keystore does not yet have a certificate for that alias
case None =>
log.info(s"Certificate with alias [${head.alias}] does not yet exist.")
changed(refreshCertificate(head, providerMap, None).get, tail, providerMap, head.alias :: changedAliases).get
refreshCertificate(head, providerMap, None) match {
case Success((newMs, c)) =>
changed(newMs, tail, providerMap, c ::: changedAliases).get

case Failure(t) =>
log.info(s"Could not refresh certificate [${head.alias}], reusing the existing one.")
changed(current, tail, providerMap, changedAliases).get
}
}
}
}
@@ -7,6 +7,7 @@ import java.security.{KeyPair, KeyPairGenerator, SecureRandom}
import blended.security.ssl.internal.JavaKeystore
import blended.testsupport.BlendedTestSupport
import org.bouncycastle.cert.X509v3CertificateBuilder
import scala.concurrent.duration._

import scala.util.{Success, Try}

@@ -18,7 +19,8 @@ trait SecurityTestSupport { this : CertificateRequestBuilder with CertificateSig

val keyStrength : Int = 2048
val sigAlg : String = "SHA256withRSA"
val validDays : Int = 365
val validDays : Int = 20
val millisPerDay : Long = 1.day.toMillis

val kpg : KeyPairGenerator = {
val kpg = KeyPairGenerator.getInstance("RSA")
@@ -31,7 +33,7 @@ trait SecurityTestSupport { this : CertificateRequestBuilder with CertificateSig
commonNameProvider = cnProvider,
sigAlg = "SHA256withRSA",
keyStrength = 2048,
validDays = 20
validDays = validDays
)

val jks: String => JavaKeystore = fileName => new JavaKeystore(
@@ -48,13 +50,13 @@ trait SecurityTestSupport { this : CertificateRequestBuilder with CertificateSig
f
}

def createRootCertificate(cn : String = "root") : Try[CertificateHolder] = Try {
def createRootCertificate(cn : String = "root", validDays : Int = validDays) : Try[CertificateHolder] = Try {

val cnProvider : CommonNameProvider = new HostnameCNProvider(cn)
new SelfSignedCertificateProvider(selfSignedCfg(cnProvider)).refreshCertificate(None, cnProvider).get
new SelfSignedCertificateProvider(selfSignedCfg(cnProvider).copy(validDays = validDays)).refreshCertificate(None, cnProvider).get
}

def createHostCertificate(hostName: String, issuedBy : CertificateHolder, validDays : Int = 10) : Try[CertificateHolder] = Try {
def createHostCertificate(hostName: String, issuedBy : CertificateHolder, validDays : Int = validDays) : Try[CertificateHolder] = Try {

issuedBy.privateKey match {

@@ -20,7 +20,7 @@ class CertificateCheckerSpec extends LoggingFreeSpec
val root : CertificateHolder = createRootCertificate().get

val newMs : MemoryKeystore = ms
.update("root", createRootCertificate().get).get
.update("root", root).get
.update("host1", createHostCertificate("host1", root, 20).get).get
.update("host2", createHostCertificate("host2", root, 8).get).get

@@ -0,0 +1,150 @@
package blended.security.ssl.internal

import java.math.BigInteger

import blended.security.ssl._
import blended.testsupport.scalatest.LoggingFreeSpec
import org.scalatest.Matchers

import scala.util.{Failure, Try}

class MemoryKeystoreSpec extends LoggingFreeSpec
with Matchers
with SecurityTestSupport
with CertificateRequestBuilder
with CertificateSigner {

private val cnProvider : CommonNameProvider = new HostnameCNProvider("host")

private val certCfg : CertificateConfig = CertificateConfig(
provider = "selfsigned",
alias = "cert",
minValidDays = validDays,
cnProvider = cnProvider
)

"The Memory key store" - {

"calculate the next certificate timeout correctly from an empty store" in {

val start : Long = System.currentTimeMillis()

val ms : MemoryKeystore = new MemoryKeystore(Map.empty)
val timeout : Long = ms.nextCertificateTimeout().get.getTime()

assert(start <= timeout && timeout <= System.currentTimeMillis())
}

"calculate the next certificate timeout correctly from an non-empty store" in {

val start : Long = System.currentTimeMillis() + (validDays - 1) * millisPerDay
val end : Long = start + 2 * millisPerDay

val root : CertificateHolder = createRootCertificate("root").get
val ms : MemoryKeystore = new MemoryKeystore(Map("root" -> root))
val timeout : Long = ms.nextCertificateTimeout().get.getTime()

assert(start <= timeout && timeout <= end)
}

"not allow inconsistent updates" in {

val root : CertificateHolder = createRootCertificate("root").get
val ms : MemoryKeystore = new MemoryKeystore(Map("root" -> root))

val host : CertificateHolder = createHostCertificate("host", root, validDays).get
.copy(privateKey = None)

intercept[InconsistentKeystoreException] {
ms.update("host", host).get
}
}

"should retrieve certificates that are not yet present in the keystore" in {

val provider : CertificateProvider = new SelfSignedCertificateProvider(selfSignedCfg(cnProvider))

val ms : MemoryKeystore = new MemoryKeystore(Map.empty)

val (newMs, changed) = ms.refreshCertificates(List(certCfg), Map("selfsigned" -> provider)).get

newMs.certificates should have size 1
newMs.certificates.keys should contain("cert")

changed should be (List("cert"))
}

"should not update the certificates if the required provider can't be found (empty store)" in {
val ms : MemoryKeystore = new MemoryKeystore(Map.empty)
val (newMs, changed) = ms.refreshCertificates(List(certCfg), Map.empty).get

newMs.certificates should be (empty)
changed should be (empty)
}

"should not update the certificates if the required provider can't be found (non-empty store)" in {

// Create a root certificate with a validity less than validDays to trigger a refresh
val root : CertificateHolder = createRootCertificate(validDays = validDays / 2).get

val ms : MemoryKeystore = new MemoryKeystore(Map(certCfg.alias -> root))
val (newMs, changed) = ms.refreshCertificates(List(certCfg), Map.empty).get

newMs.certificates should have size 1
changed should be (empty)

newMs.certificates.values.head.chain.head.getSerialNumber should be (BigInteger.ONE)
}

"should update the certificates that are about to expire" in {

val provider : CertificateProvider = new SelfSignedCertificateProvider(selfSignedCfg(cnProvider))

val timingOut : CertificateHolder = createRootCertificate("timingOut", validDays = validDays / 2).get
val stillValid : CertificateHolder = createRootCertificate("stillValid", validDays = validDays * 2).get

val ms : MemoryKeystore = new MemoryKeystore(Map("timingOut" -> timingOut, "stillValid" -> stillValid))

val certCfgs : List[CertificateConfig] = List(
certCfg.copy(alias = "timingOut"),
certCfg.copy(alias = "stillValid")
)

val (newMs, changed) = ms.refreshCertificates(certCfgs, Map("selfsigned" -> provider)).get

newMs.certificates should have size(2)
changed should be (List("timingOut"))

newMs.certificates.get("stillValid").get.chain.head.getSerialNumber should be (BigInteger.ONE)
newMs.certificates.get("timingOut").get.chain.head.getSerialNumber should be (new BigInteger("2"))
}

"should not update the keystore if the refresh has failed" in {

val provider : CertificateProvider = new SelfSignedCertificateProvider(selfSignedCfg(cnProvider)) {

override def refreshCertificate(existing: Option[CertificateHolder], cnProvider: CommonNameProvider): Try[CertificateHolder] =
Failure(new Exception("Boom"))
}

val timingOut : CertificateHolder = createRootCertificate("timingOut", validDays = validDays / 2).get
val stillValid : CertificateHolder = createRootCertificate("stillValid", validDays = validDays * 2).get

val ms : MemoryKeystore = new MemoryKeystore(Map("timingOut" -> timingOut, "stillValid" -> stillValid))

val certCfgs : List[CertificateConfig] = List(
certCfg.copy(alias = "timingOut"),
certCfg.copy(alias = "stillValid")
)

val (newMs, changed) = ms.refreshCertificates(certCfgs, Map("selfsigned" -> provider)).get

newMs.certificates should have size(2)
changed should be (List.empty)

newMs.certificates.get("stillValid").get.chain.head.getSerialNumber should be (BigInteger.ONE)
newMs.certificates.get("timingOut").get.chain.head.getSerialNumber should be (BigInteger.ONE)

}
}
}

0 comments on commit ecb4392

Please sign in to comment.