Skip to content

Commit

Permalink
remove secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
dineshc4 committed Mar 10, 2024
1 parent 921a1bf commit 3dd4b7b
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 22 deletions.
1 change: 0 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ lazy val client = Project("zio-k8s-client", file("zio-k8s-client"))
"io.circe" %% "circe-generic" % "0.14.6",
"io.circe" %% "circe-parser" % "0.14.6",
"io.circe" %% "circe-yaml" % "0.14.2",
"org.bouncycastle" % "bcpkix-jdk18on" % "1.77",
"dev.zio" %% "zio-config-typesafe" % zioConfigVersion % Test,
"dev.zio" %% "zio-test" % zioVersion % Test,
"dev.zio" %% "zio-test-sbt" % zioVersion % Test,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package com.coralogix.zio.k8s.client.config

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import org.bouncycastle.openssl.{ PEMKeyPair, PEMParser }
import zio.{ System, ZIO }

import java.io.{ File, FileInputStream, InputStreamReader }
import java.security.KeyStore
import java.io.{ File, FileInputStream }
import java.security.cert.{ CertificateFactory, X509Certificate }
import java.security.spec.PKCS8EncodedKeySpec
import java.security.{ KeyFactory, KeyStore, PrivateKey }
import java.util.Base64
import javax.net.ssl.{ KeyManager, KeyManagerFactory }

private object KeyManagers {
Expand Down Expand Up @@ -40,22 +38,11 @@ private object KeyManagers {
): ZIO[Any, Throwable, Array[KeyManager]] =
for {
keyStore <- getDefaultKeyStore
provider <- ZIO.attempt(new BouncyCastleProvider())

privateKey <- ZIO.scoped(loadKeyStream(key) flatMap { stream =>
ZIO.attempt {
val pemKeyPair = new PEMParser(new InputStreamReader(stream))
val converter = new JcaPEMKeyConverter().setProvider(provider)
pemKeyPair.readObject() match {
case pair: PEMKeyPair => converter.getPrivateKey(pair.getPrivateKeyInfo)
case pk: PrivateKeyInfo => converter.getPrivateKey(pk)
case other: Any =>
throw new IllegalStateException(
s"Unexpected key pair type ${other.getClass.getSimpleName}"
)
}
}

pemData <- ZIO.scoped(loadKeyStream(key) flatMap { stream =>
ZIO.attempt(scala.io.Source.fromInputStream(stream).mkString)
})
privateKey <- extractKeyFromPEMData(pemData)

certificateFactory <- ZIO.attempt(CertificateFactory.getInstance("X509"))
x509Cert <- ZIO.scoped(loadKeyStream(certificate) flatMap { stream =>
Expand All @@ -76,4 +63,113 @@ private object KeyManagers {
kmf <- ZIO.attempt(KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm))
_ <- ZIO.attempt(kmf.init(keyStore, "changeit".toCharArray))
} yield kmf.getKeyManagers

/** This class encapsulates the PEM header/footer details for different key formats, used to
* extract and parse the key using the correct algorithm,
* @param header
* The expected PEM header for the key type
* @param footer
* The expected PEM footer for the key type
*/
case class PEMHeaderFooter(val header: String, val footer: String)

val pkcs1PrivateKeyHdrFooter =
PEMHeaderFooter("-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----")
val pkcs8PrivateKeyHdrFooter =
PEMHeaderFooter("-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----")
// openssl may generate following header/footer specifically for EC case
val pkcs8ECPrivateKeyHdrFooter =
PEMHeaderFooter("-----BEGIN EC PRIVATE KEY-----", "-----END EC PRIVATE KEY-----")

trait PrivateKeyParser {
val headerFooter: PEMHeaderFooter

def getPrivateKey(keyBytes: Array[Byte]): ZIO[Any, Throwable, PrivateKey]

protected def getPKCS8PrivateKey(
keyBytes: Array[Byte],
algo: String
): ZIO[Any, Throwable, PrivateKey] = for {
spec <- ZIO.attempt(new PKCS8EncodedKeySpec(keyBytes))
factory <- ZIO.attempt(KeyFactory.getInstance(algo))
privateKey <- ZIO.attempt(factory.generatePrivate(spec))
} yield privateKey
}

object PKCS1PrivateKeyParser extends PrivateKeyParser {
override val headerFooter: PEMHeaderFooter = pkcs1PrivateKeyHdrFooter
override def getPrivateKey(keyBytes: Array[Byte]) = {
// java security API does not support pkcs#1 so convert to pkcs#8 RSA first
val pkcs1Length = keyBytes.length;
val totalLength = pkcs1Length + 22;
val pkcs8Header: Array[Byte] = Array[Byte](
0x30.toByte,
0x82.toByte,
((totalLength >> 8) & 0xff).toByte,
(totalLength & 0xff).toByte, // Sequence + total length
0x2.toByte,
0x1.toByte,
0x0.toByte, // Integer (0)
0x30.toByte,
0xd.toByte,
0x6.toByte,
0x9.toByte,
0x2a.toByte,
0x86.toByte,
0x48.toByte,
0x86.toByte,
0xf7.toByte,
0xd.toByte,
0x1.toByte,
0x1.toByte,
0x1.toByte,
0x5.toByte,
0x0.toByte, // Sequence: 1.2.840.113549.1.1.1, NULL
0x4.toByte,
0x82.toByte,
((pkcs1Length >> 8) & 0xff).toByte,
(pkcs1Length & 0xff).toByte // Octet string + length
)
val pkcs8Bytes = pkcs8Header ++ keyBytes
PKCS8PrivateKeyParser.getPrivateKey(pkcs8Bytes)
}
}
object PKCS8PrivateKeyParser extends PrivateKeyParser {
override val headerFooter: PEMHeaderFooter = pkcs8PrivateKeyHdrFooter
override def getPrivateKey(keyBytes: Array[Byte]) = {
// The PKCS#8 header doesn't specify the keys algo, so try RSA and EC in turn
val algos = List("RSA", "EC")
def tryWithAlgo(algo: String) = getPKCS8PrivateKey(keyBytes, algo)

ZIO
.collectFirst(algos)(algo => tryWithAlgo(algo).fold(_ => None, pk => Some(pk)))
.someOrFail(new Exception(s"Failed trying to parse PKCS8 private key using each of $algos"))
}
}

object PKCS8ECPrivateKeyParser extends PrivateKeyParser {
override val headerFooter: PEMHeaderFooter = pkcs8ECPrivateKeyHdrFooter
override def getPrivateKey(keyBytes: Array[Byte]) = getPKCS8PrivateKey(keyBytes, "EC")
}

val privateKeyParsers =
List(PKCS1PrivateKeyParser, PKCS8PrivateKeyParser, PKCS8ECPrivateKeyParser)

private def findPrivateKeyParser(pemData: String): Option[PrivateKeyParser] =
privateKeyParsers.find { parser =>
pemData.startsWith(parser.headerFooter.header)
}

private def extractKeyFromPEMData(pemData: String): ZIO[Any, Throwable, PrivateKey] = for {
parser <- ZIO
.succeed(findPrivateKeyParser(pemData))
.someOrFail(new Exception("Private key not in a supported PEM format"))
base64EncodedKey =
pemData
.replace(parser.headerFooter.header, "")
.replace(parser.headerFooter.footer, "")
.replace(util.Properties.lineSeparator, "")
encoded <- ZIO.attempt(Base64.getDecoder.decode(base64EncodedKey))
key <- parser.getPrivateKey(encoded)
} yield key
}

0 comments on commit 3dd4b7b

Please sign in to comment.