Skip to content

Instantly share code, notes, and snippets.

@ShahOdin
Last active October 19, 2023 12:38
Show Gist options
  • Save ShahOdin/c1bd8ea3042bb2ae813c2102082d5325 to your computer and use it in GitHub Desktop.
Save ShahOdin/c1bd8ea3042bb2ae813c2102082d5325 to your computer and use it in GitHub Desktop.
package activation.service.instances.amazon
import cats.effect.{IO, IOApp, Resource, Sync}
import com.nimbusds.jose.util.X509CertUtils
import cats.syntax.all.*
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.openssl.{PEMParser, PEMKeyPair}
import java.security.Signature
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
import java.io.StringReader
import java.security.cert.X509Certificate
import java.security.interfaces.RSAPrivateCrtKey
import java.security.{PrivateKey, Security}
import scala.util.Try
import scala.util.chaining.*
import java.security.spec.RSAPublicKeySpec
import java.security.KeyFactory
import scala.io.Source
object TestApp extends IOApp.Simple:
def read[F[_] : Sync](resourcePath: String): Resource[F, String] = Resource
.fromAutoCloseable[F, Source](
Sync[F].blocking(Source.fromResource(resourcePath))
)
.map(_.getLines.mkString(System.lineSeparator()))
private def parseRSAPrivateKey(privateKeyPEM: String): IO[PrivateKey] = {
new StringReader(privateKeyPEM)
.pipe(new PEMParser(_))
.pipe(parser =>
Try(parser.readObject()).liftTo[IO]
.flatMap {
case obj if obj.isInstanceOf[PrivateKeyInfo] => obj.asInstanceOf[PrivateKeyInfo].pure
case obj if obj.isInstanceOf[PEMKeyPair] => obj.asInstanceOf[PEMKeyPair].getPrivateKeyInfo.pure
case _ => IO.raiseError(new RuntimeException("Boom!"))
}
.flatMap(privateKeyInfo =>
Try(new JcaPEMKeyConverter().getPrivateKey(privateKeyInfo)).liftTo[IO]
)
)
}
private def checkCompatibility(certificate: X509Certificate, privateKey: PrivateKey): IO[Unit] = for
_ <- Try(Security.addProvider(new BouncyCastleProvider())).liftTo[IO]
publicKey <- privateKey.asInstanceOf[RSAPrivateCrtKey]
.pipe(crt => new RSAPublicKeySpec(crt.getModulus, crt.getPublicExponent))
.pipe(rsaPublicKeySpec =>
Try(KeyFactory.getInstance("RSA"))
.flatMap(keyFactory => Try(keyFactory.generatePublic(rsaPublicKeySpec)))
.liftTo[IO]
)
matches = publicKey.equals(certificate.getPublicKey)
_ <- IO.println(s"public keys match: $matches")
yield ()
private def roundTrip(certificate: X509Certificate, privateKey: PrivateKey): IO[Unit] = for
_ <- Try(Security.addProvider(new BouncyCastleProvider())).liftTo[IO]
signatureData <- Try("TEST".getBytes("UTF-8")).liftTo[IO]
algorithm = "SHA1withRSA"
signer = Signature.getInstance(algorithm,"BC")
_ <- Try(signer.initSign(privateKey)).liftTo[IO]
_ <- Try(signer.update(signatureData)).liftTo[IO]
signature <- Try(signer.sign()).liftTo[IO]
_ <- Try(signer.initVerify(certificate)).liftTo[IO]
_ <- Try(signer.update(signatureData)).liftTo[IO]
verified <- Try(signer.verify(signature)).liftTo[IO]
_ <- IO.println(s"roundtrip sign verification: $verified")
yield ()
//how the key and certificate were generated
//openssl genpkey -algorithm RSA -out my-rsa.key
//openssl req -new -key my-rsa.key -out my-rsa.csr -sha1
//openssl x509 -req -in my-rsa.csr -signkey my-rsa.key -out my-rsa.crt -sha1
//openssl x509 -in my-rsa.crt -out my-rsa.pem -outform PEM
//the following all return the same value. ie the key and the certificate do indeed match.
//openssl rsa -noout -modulus -in my-rsa.key | openssl md5
//openssl req -noout -modulus -in my-rsa.csr | openssl md5
//openssl x509 -noout -modulus -in my-rsa.crt | openssl md5
override def run: IO[Unit] = (
read[IO]("my-rsa.pem").evalMap(s => IO.delay(X509CertUtils.parse(s))),
read[IO]("my-rsa.key").evalMap(parseRSAPrivateKey)
).tupled
.evalTap(checkCompatibility.tupled)
.evalTap(roundTrip.tupled)
.use_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment