Skip to content

Instantly share code, notes, and snippets.

@benjumanji
Created January 25, 2018 18:37
Show Gist options
  • Save benjumanji/99addd0f97e2ff3b29a2d026b1c71499 to your computer and use it in GitHub Desktop.
Save benjumanji/99addd0f97e2ff3b29a2d026b1c71499 to your computer and use it in GitHub Desktop.
package xxx.security.authc.keymaker.infrastructure.vault
import java.io.{ ByteArrayOutputStream, OutputStream }
import monix.execution.schedulers.SchedulerService
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.operator.{ ContentSigner, DefaultSignatureAlgorithmIdentifierFinder }
import xxx.security.authc.keymaker.infrastructure.vault.clients.transit.{ HashAlgorithm, TransitClient }
import scala.concurrent.Await
import scala.concurrent.duration.Duration
case class VaultContentSigner(transitClient: TransitClient, key: String)(implicit ss: SchedulerService) extends ContentSigner {
val sigAlgId: AlgorithmIdentifier = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withECDSA")
val baos: ByteArrayOutputStream = new ByteArrayOutputStream()
override def getAlgorithmIdentifier: AlgorithmIdentifier = sigAlgId
override def getOutputStream: OutputStream = baos
override def getSignature: Array[Byte] = {
val signatureCall = transitClient.signData(key, HashAlgorithm.`SHA2-256`, baos.toByteArray).runAsync
// it is okay to use Duration.Inf here because each http call in transit client has a defined timeout
val signatureResponse = Await.result(signatureCall, Duration.Inf)
signatureResponse.body.data
}
}
package xxx.security.authc.keymaker
import java.security.KeyFactory
import java.security.interfaces.ECPublicKey
import java.security.spec.X509EncodedKeySpec
import javax.security.auth.x500.X500Principal
import com.google.protobuf.ByteString
import monix.eval.Task
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.bouncycastle.util.encoders.Base64
import xxx.security.authc.keymaker.infrastructure.Environment
import xxx.security.authc.keymaker.infrastructure.vault.VaultContentSigner
import xxx.security.authc.keymaker.infrastructure.vault.clients.transit.KeyType
import xxx.security.authc.keymaker.v1.{ CreatePartyRequest, CreatePartyResponse, KeyMakerGrpc }
import scala.concurrent.Future
case class KeyMaker(env: Environment) extends KeyMakerGrpc.KeyMaker {
import env.schedulerService
override def createParty(request: CreatePartyRequest): Future[CreatePartyResponse] = {
val response: Task[CreatePartyResponse] = for {
identity <- Task.fromTry(Identity.fromRequest(request).toTry)
identityKeyName = identity.keyName()
keyPem <- getKeyPem(identityKeyName)
publicKey = buildPublicKey(keyPem)
csr = buildCsr(identity.name, identityKeyName, publicKey)
encodedCsr = encodeCsrAsPem(csr)
signedCertificate <- env.pkiClient.signCertificate(encodedCsr)
} yield CreatePartyResponse(cert = ByteString.copyFromUtf8(signedCertificate.body.certificate))
response.runAsync
}
/**
* Creates and queries a key pem using vault's transit backend
* @param keyName The name of the key to create
* @return A task with the keyPem created
*/
private def getKeyPem(keyName: String): Task[String] = {
for {
_ <- env.transitClient.createKey(keyName, KeyType.`ECDSA-P256`)
getKeyResponse <- env.transitClient.getKey(keyName)
} yield getKeyResponse.body.keyTypeData.value.publicKey
}
/**
* Builds an ECPublicKey instance based on a keyPem
* @param keyPem The key pem
* @return The built ECPublicKey instance
*/
private def buildPublicKey(keyPem: String): ECPublicKey = {
val keyDer = Base64.decode(keyPem.getBytes)
val keyFactory = KeyFactory.getInstance("ECDSA", new BouncyCastleProvider())
keyFactory.generatePublic(new X509EncodedKeySpec(keyDer)).asInstanceOf[ECPublicKey]
}
/** Builds a certificate signing request */
private def buildCsr(identityName: X500Name, keyName: String, publicKey: ECPublicKey): PKCS10CertificationRequest = {
val principal = new X500Principal(identityName.toString)
val p10Builder = new JcaPKCS10CertificationRequestBuilder(principal, publicKey)
val signer = VaultContentSigner(env.transitClient, keyName)
p10Builder.build(signer)
}
/**
* Encodes a CSR as PEM
* @param csr Certificate signing request to encode
* @return Encoded CSR
*/
private def encodeCsrAsPem(csr: PKCS10CertificationRequest): String = {
val baos = new java.io.ByteArrayOutputStream()
val writer = new JcaPEMWriter(new java.io.OutputStreamWriter(baos))
writer.writeObject(csr)
writer.flush()
baos.toString()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment