Skip to content

Commit

Permalink
Refactoring SCEP Certificate provider to support multiple SAN's
Browse files Browse the repository at this point in the history
  • Loading branch information
atooni committed Feb 28, 2019
1 parent 0778307 commit 007eeb6
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 30 deletions.
@@ -1,12 +1,14 @@
package blended.security.scep.internal

import java.io.{ByteArrayOutputStream, PrintStream}
import java.net.URL
import java.security.cert.Certificate
import java.security._
import java.security.cert.{Certificate, X509Certificate}

import blended.security.ssl._
import javax.security.auth.callback.CallbackHandler
import javax.security.auth.x500.X500Principal
import org.bouncycastle.asn1.DERPrintableString
import org.bouncycastle.asn1.{ASN1Encodable, DERPrintableString}
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
Expand All @@ -17,6 +19,9 @@ import org.jscep.transport.response.Capabilities
import scala.collection.JavaConverters._
import scala.util.Try
import blended.util.logging.Logger
import org.bouncycastle.asn1.x509.{Extension, ExtensionsGenerator, GeneralName, GeneralNames}
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import sun.misc.BASE64Encoder

case class ScepConfig(
url : String,
Expand All @@ -26,7 +31,9 @@ case class ScepConfig(
scepChallenge : String
)

class ScepCertificateProvider(cfg: ScepConfig) extends CertificateProvider {
class ScepCertificateProvider(cfg: ScepConfig)
extends CertificateRequestBuilder
with CertificateProvider {

private[this] lazy val log = Logger[ScepCertificateProvider]

Expand All @@ -47,10 +54,10 @@ class ScepCertificateProvider(cfg: ScepConfig) extends CertificateProvider {
existing match {
case None =>
log.info("Obtaining initial server certificate from SCEP server.")
enroll(selfSignedCertificate(cnProvider).get, cnProvider)
enroll(None, cnProvider)
case Some(c) =>
log.info("Refreshing certificate previously obtained from SCEP server.")
enroll(c, cnProvider)
enroll(Some(c), cnProvider)
}
}

Expand All @@ -66,27 +73,64 @@ class ScepCertificateProvider(cfg: ScepConfig) extends CertificateProvider {
new SelfSignedCertificateProvider(selfSignedConfig).refreshCertificate(None, cnProvider)
}

private[this] def enroll(inCert : CertificateHolder, cnProvider: CommonNameProvider): Try[CertificateHolder] = Try {
private def dumpCsr(csr : PKCS10CertificationRequest) : Unit = {

inCert.privateKey match {
val s = new BASE64Encoder().encode(csr.getEncoded())
println("-----BEGIN CERTIFICATE REQUEST-----\n" + s + "\n-----END CERTIFICATE REQUEST-----\n")
}

private[this] def enroll(
inCert : Option[CertificateHolder],
cnProvider: CommonNameProvider
): Try[CertificateHolder] = Try {

val selfSigned = selfSignedCertificate(cnProvider).get

val csrSignKey : Option[PrivateKey] = inCert match {
case None => selfSigned.privateKey
case Some(c) => c.privateKey
}

csrSignKey match {
case None =>
throw new Exception("Certificate to refresh must have a private key defined.")
throw new Exception("No key found to sign SCEP certificate request.")

case Some(privKey) =>

val reqCert = inCert.chain.head

log.info(s"Trying to obtain server certificate from SCEP server at [${cfg.url}] with existing certificate [${X509CertificateInfo(reqCert)}]" )
val reqCert : X509Certificate = inCert match {
case None =>
log.info(s"Requesting initial certificate from SCEP server at [${cfg.url}].")
selfSigned.chain.head
case Some(c) =>
log.info(s"Refreshing certificate from SCEP server at [${cfg.url}].")
c.chain.head
}

val csrBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Principal(cnProvider.commonName().get), inCert.publicKey)
val csrBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Principal(cnProvider.commonName().get), reqCert.getPublicKey())
csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_challengePassword, new DERPrintableString(cfg.scepChallenge))

if (cnProvider.alternativeNames().get.nonEmpty) {
val altNames : Array[GeneralName] = cnProvider.alternativeNames().get.map { n=>
log.info(s"Adding alternative dns name [$n] to SCEP certificate request.")
new GeneralName(GeneralName.dNSName, n)
}.toArray

val names = new GeneralNames(altNames)
val extGen = new ExtensionsGenerator()
val sanExt = extGen.addExtension(Extension.subjectAlternativeName, false, names)

csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate())
}

// TODO add extensions ?

val csrSignerBuilder = new JcaContentSignerBuilder(cfg.csrSignAlgorithm)
val csrSigner = csrSignerBuilder.build(privKey)
val csr = csrBuilder.build(csrSigner)

dumpCsr(csr
)

val response = scepClient.enrol(reqCert, privKey, csr)

// TODO: Active wait is baaaad
Expand All @@ -106,7 +150,7 @@ class ScepCertificateProvider(cfg: ScepConfig) extends CertificateProvider {
log.info(s"Retrieved [${certs.length}] certificates from [${cfg.url}].")

CertificateHolder.create(
publicKey = inCert.publicKey,
publicKey = reqCert.getPublicKey(),
privateKey = Some(privKey),
chain = certs
).get
Expand Down
@@ -1,46 +1,58 @@
package blended.security.scep.internal

import scala.util.Try
import java.io.File

import blended.security.ssl.internal.{JavaKeystore, MemoryKeystore}

import blended.security.ssl.{ CommonNameProvider, X509CertificateInfo }
import scala.util.Try
import blended.security.ssl.{CommonNameProvider, X509CertificateInfo}
import blended.util.logging.Logger

object ScepTestClient {

private[this] val log = Logger[ScepTestClient]

private val keystore = new JavaKeystore(new File("/tmp/keystore"), "test".toCharArray, Some("test".toCharArray))

def main(args: Array[String]) : Unit = {

log.info("Starting Scep Test Client ...")

val cnProvider = new CommonNameProvider {
override def commonName(): Try[String] = Try { "CN=cc9999lnxprx01.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" }
override def alternativeNames(): Try[List[String]] = Try { List("cc9999lnxprx01.9999.cc.kaufland", "cachea.9999.cc.kaufland") }
override def commonName(): Try[String] = Try { "CN=cachea.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" }
//override def commonName(): Try[String] = Try { "CN=cc9999lnxprx01.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" }
override def alternativeNames(): Try[List[String]] = Try { List("cachea.9998.cc.kaufland", "cachea.9999.cc.kaufland") }
}

val scepConfig = new ScepConfig(
url = "http://iqscep01:8080/pgwy/scep/sib",
url = "http://scep-t.pki.schwarz:8080/pgwy/scep/sib",
profile = None,
keyLength = 2048,
csrSignAlgorithm = "SHA1withRSA",
scepChallenge ="password"
scepChallenge ="qXUDZTttAZDghBguVk2M"
)

val provider = new ScepCertificateProvider(scepConfig)

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

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

log.info("=" * 100)

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

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

val memStore : MemoryKeystore = new MemoryKeystore(Map.empty)
.update("initial", cert1).get
.update("refreshed", cert2).get

keystore.saveKeyStore(memStore)
}
}

Expand Down
@@ -0,0 +1,37 @@
package blended.security.scep.internal

import java.io.File

import blended.security.ssl._
import blended.security.ssl.internal.{JavaKeystore, MemoryKeystore}

import scala.util.Try

object SelfSignedTest {

private val selfSignedCfg : SelfSignedConfig = SelfSignedConfig(
commonNameProvider = new CommonNameProvider {
override def commonName(): Try[String] = Try { "CN=cachea.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" }
//override def commonName(): Try[String] = Try { "CN=cc9999lnxprx01.9999.cc.kaufland, O=Schwarz IT GmbH & Co. KG, C=CC" }
override def alternativeNames(): Try[List[String]] = Try { List("cc9999lnxprx01.9999.cc.kaufland", "cachea.9999.cc.kaufland") }
},
keyStrength = 2048,
sigAlg = "SHA256withRSA",
validDays = 1
)

private val provider : CertificateProvider = new SelfSignedCertificateProvider(selfSignedCfg)

def main(args: Array[String]) : Unit = {

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)
println(cert.dump)

keystore.saveKeyStore(memStore.update("cert", cert).get)

}

}
Expand Up @@ -39,7 +39,7 @@ case class CertificateHolder (

// A complete dump of the certificate chain as a String
def dump : String = {
chain.map { c => c.toString() }.mkString("*" * 30 , "\n\n--- Signed by ---\n\n", "*" * 30)
chain.map { c => c.toString() }.mkString("\n" + "*" * 30 , "\n\n--- Signed by ---\n\n", "*" * 30)
}
}

Expand Down
23 changes: 15 additions & 8 deletions doc/_notes/ContinousBuild
Expand Up @@ -21,13 +21,20 @@ https://jaxenter.de/der-steuermann-fuers-containerschiff-65844

Install Jenkins in Kubernetes
https://www.blazemeter.com/blog/how-to-setup-scalable-jenkins-on-top-of-a-kubernetes-cluster
https://www.infoq.com/articles/scaling-docker-with-kubernetes

1. Create jenkins namespace
kubectl create namespace jenkins
Make sure the default account can be used:
https://github.com/fabric8io/fabric8/issues/6840

Plan:
- create and configure Docker Image for jenkins master
- project blended.helm/docker/jenkins-master
- run image locally on docker only
- deploy image to minikube
- deploy image to kubectl
Sample Jenkins Pipeline
https://github.com/eldada/jenkins-pipeline-kubernetes/blob/master/Jenkinsfile

Automating jenkins setup
https://dzone.com/articles/dockerizing-jenkins-2-setup-and-using-it-along-wit
https://dzone.com/articles/dockerizing-jenkins-part-2-deployment-with-maven-a
https://dzone.com/articles/securing-password-with-docker-compose-docker-secre
https://dzone.com/articles/putting-jenkins-build-logs-into-elk-stack-filebeat
https://dzone.com/articles/creating-jenkins-configuration-as-code-and-applyin

Multinode local kubernetes cluster
https://github.com/kinvolk/kube-spawn

0 comments on commit 007eeb6

Please sign in to comment.