Skip to content
Permalink
Browse files

Refactored change handling in MemoryKeyStore

  • Loading branch information
lefou committed Mar 16, 2020
1 parent 6ce6c2e commit a3918c23005ca922411d412c9c8d705ae15d1cc2
@@ -96,11 +96,10 @@ class ScepCertificateProvider(cfg : ScepConfig)
cnProvider : CommonNameProvider
) : Try[CertificateHolder] = Try {

val selfSigned = selfSignedCertificate(cnProvider).get

val reqCert : CertificateHolder = inCert match {
case None =>
log.info(s"Requesting initial certificate from SCEP server at [${cfg.url}].")
val selfSigned = selfSignedCertificate(cnProvider).get
selfSigned
case Some(c) =>
log.info(s"Refreshing certificate from SCEP server at [${cfg.url}].")
@@ -42,15 +42,15 @@ object ScepTestClient {

val provider = new ScepCertificateProvider(scepConfig)

val cert1 = provider.refreshCertificate(None, cnProvider).get.copy(changed = true)
val cert1 = provider.refreshCertificate(None, cnProvider).get

cert1.chain.foreach { c =>
log.info(X509CertificateInfo(c).toString)
}

log.info("=" * 100)

val cert2 = provider.refreshCertificate(Some(cert1), cnProvider).get.copy(changed = true)
val cert2 = provider.refreshCertificate(Some(cert1), cnProvider).get

cert2.chain.foreach { c =>
log.info(X509CertificateInfo(c).toString)
@@ -27,7 +27,7 @@ object SelfSignedTest {
val keystore = new JavaKeystore(new File("/tmp/keystore"), "test".toCharArray, Some("test".toCharArray))
val memStore = new MemoryKeystore(Map.empty)

val cert : CertificateHolder = provider.refreshCertificate(None, selfSignedCfg.commonNameProvider).get.copy(changed = true)
val cert : CertificateHolder = provider.refreshCertificate(None, selfSignedCfg.commonNameProvider).get
println(cert.dump)

keystore.saveKeyStore(memStore.update("cert", cert).get)
@@ -0,0 +1,13 @@
package blended.security.ssl

sealed trait CertificateChange {
def changed: Boolean
}

object CertificateChange {
sealed abstract class CertificateChangeBase protected (val changed: Boolean) extends CertificateChange
case object Added extends CertificateChangeBase(true)
case object Updated extends CertificateChangeBase(true)
case object Unchanged extends CertificateChangeBase(false)
}

@@ -22,11 +22,11 @@ import scala.util.Try
* with the factory method(s) in the companion object. These methods will create the
* sorted chain verify the signatures of each certificate within the chain.
*/
case class CertificateHolder(
case class CertificateHolder private (
publicKey : PublicKey,
privateKey : Option[PrivateKey],
chain : List[X509Certificate],
changed : Boolean = false
change : CertificateChange = CertificateChange.Unchanged
) {

val subjectPrincipal : Option[X500Principal] = chain.headOption.map(_.getIssuerX500Principal())
@@ -14,19 +14,39 @@ case class MemoryKeystore(certificates : Map[String, CertificateHolder]) {
private[this] val log : Logger = Logger[MemoryKeystore]
private[this] val millisPerDay : Long = 1.day.toMillis

val changedAliases : List[String] = certificates.filter { case (_, v) => v.changed }.keys.toList

// The in memory keystore is consistent if and only if all certificates have a private key defined
// or none of it does have a private key defined.
/**
* Return those aliases of certificates which were updated or newly added.
*/
def changedAliases : List[String] = certificates.collect {
case (key, CertificateHolder(_, _, _, change)) if change.changed => key
}.toList

/**
* The in memory keystore is consistent if and only if all certificates have a private key defined
* or none of it does have a private key defined.
*/
def consistent : Boolean = {
certificates.values.forall(_.privateKey.isDefined) || certificates.values.forall(_.privateKey.isEmpty)
}

/**
* Updates/replaces the certificate (chain) for the given alias and updates
* the `change` flag of the associated [[CertificateHolder]].
*/
def update(alias : String, cert : CertificateHolder) : Try[MemoryKeystore] = Try {

log.info(s"Updating memory keystore [alias=$alias]")
val result : MemoryKeystore =
MemoryKeystore(certificates.filterKeys(_ != alias) + (alias -> cert.copy(changed = true)))
val newState = certificates.get(alias) match {
case None => CertificateChange.Added
case Some(oldCert) =>
oldCert match {
case CertificateHolder(_, _, _, CertificateChange.Unchanged) =>
if (oldCert.dump == cert.dump) CertificateChange.Unchanged
else CertificateChange.Updated
case CertificateHolder(_, _, _, change) => change
}
}
log.info(s"Updating memory keystore [alias=$alias] and change state [${newState}]")
val result : MemoryKeystore = MemoryKeystore(certificates + (alias -> cert.copy(change = newState)))

if (result.consistent) {
log.info(s"Updated keystore aliases : [${result.certificates.keys}]")
@@ -128,20 +128,23 @@ class CertificateManagerImpl(

// first refresh the server certificates if required
log.debug("Loading keystore...")
val ks = loadKeyStore().get
val ks: Option[MemoryKeystore] = loadKeyStore().get
log.debug(s"Loaded keystore [$ks]")

ks.map { ms =>
log.debug(s"Refreshing certificates for keystore [$ms]")
val changedKs = ms.refreshCertificates(cfg.certConfigs, providerMap).get
log.debug(s"Changed certificate aliases [${changedKs.certificates.collect{
case c if c._2.change.changed => c._1 -> c._2.change
}}]")

log.debug(s"Saving keystore...")
val jks = javaKeystore.get
jks.saveKeyStore(changedKs) match {
case Failure(t) =>
log.warn(t)(s"Failed to save keystore to file [${jks.keystore.getAbsolutePath()}] : [${t.getMessage()}]")
throw t
case Success(keyStore) => keyStore
case Success(_) => changedKs
}
}
}
@@ -4,9 +4,8 @@ import java.io.{File, FileInputStream, FileOutputStream}
import java.security.cert.X509Certificate
import java.security.{KeyStore, PrivateKey, PublicKey}

import blended.security.ssl.{CertificateHolder, InconsistentKeystoreException, MemoryKeystore}
import blended.security.ssl.{CertificateChange, CertificateHolder, InconsistentKeystoreException, MemoryKeystore}
import blended.util.logging.Logger

import scala.collection.JavaConverters._
import scala.util.Try

@@ -32,7 +31,7 @@ class JavaKeystore(

val ks : KeyStore = loadKeyStoreFromFile().get

ms.certificates.filter(_._2.changed).foreach {
ms.certificates.filter(_._2.change.changed).foreach {
case (alias, cert) =>
keypass match {
case None =>
@@ -46,7 +45,7 @@ class JavaKeystore(
}

saveKeyStoreToFile(ks).get
MemoryKeystore(ms.certificates.mapValues(_.copy(changed = false)))
MemoryKeystore(ms.certificates.mapValues(_.copy(change = CertificateChange.Unchanged)))
}

private[ssl] def loadKeyStoreFromFile() : Try[KeyStore] = Try {
@@ -49,7 +49,7 @@ class JavaKeystoreSpec extends LoggingFreeSpec
ms1.consistent should be(true)
ms1.certificates should be(empty)

val cert : CertificateHolder = createRootCertificate(cn = "root").get.copy(changed = true)
val cert : CertificateHolder = createRootCertificate(cn = "root").get
val ms2 : MemoryKeystore = jks.saveKeyStore(ms1.update("test", cert).get).get

ms2.certificates should have size 1
@@ -69,7 +69,7 @@ class JavaKeystoreSpec extends LoggingFreeSpec
ms1.consistent should be(true)
ms1.certificates should be(empty)

val cert : CertificateHolder = createRootCertificate(cn = "root").get.copy(changed = true)
val cert : CertificateHolder = createRootCertificate(cn = "root").get
jks.saveKeyStore(ms1.update("test", cert).get).get

val ms2 : MemoryKeystore = jks.loadKeyStore().get
@@ -2,7 +2,7 @@ package blended.security.ssl.internal

import java.io.File

import blended.security.ssl.{CertificateRequestBuilder, CertificateSigner, MemoryKeystore, SecurityTestSupport}
import blended.security.ssl.{CertificateChange, CertificateRequestBuilder, CertificateSigner, MemoryKeystore, SecurityTestSupport}
import blended.testsupport.BlendedTestSupport
import blended.testsupport.scalatest.LoggingFreeSpec
import org.scalatest.Matchers
@@ -16,7 +16,7 @@ class TrustStoreRefresherSpec extends LoggingFreeSpec
"The truststore refresher" - {

val pwd : String = "trust"
val ms : MemoryKeystore = MemoryKeystore(Map("root" -> createRootCertificate(cn = "root").get.copy(changed = true)))
val ms : MemoryKeystore = MemoryKeystore(Map("root" -> createRootCertificate(cn = "root").get.copy(change = CertificateChange.Added)))

"not update anything if the truststore properties are not set" in {
System.clearProperty(SslContextProvider.propTrustStorePwd)

0 comments on commit a3918c2

Please sign in to comment.
You can’t perform that action at this time.