Created
January 25, 2018 18:37
-
-
Save benjumanji/99addd0f97e2ff3b29a2d026b1c71499 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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