diff --git a/src/main/scala/code/util/CertificateUtil.scala b/src/main/scala/code/util/CertificateUtil.scala new file mode 100644 index 00000000..d0ab7624 --- /dev/null +++ b/src/main/scala/code/util/CertificateUtil.scala @@ -0,0 +1,261 @@ +package code.util + +import java.io.{FileInputStream, IOException} +import java.security.cert.{Certificate, CertificateException, X509Certificate} +import java.security.interfaces.{RSAPrivateKey, RSAPublicKey} +import java.security.{PublicKey, _} + +import code.util.CryptoSystem.CryptoSystem +import code.util.Helper.MdcLoggable +import com.nimbusds.jose._ +import com.nimbusds.jose.crypto.{MACSigner, RSAEncrypter, RSASSASigner} +import com.nimbusds.jose.util.X509CertUtils +import com.nimbusds.jwt.{EncryptedJWT, JWTClaimsSet} +import net.liftweb.util.Props +import org.bouncycastle.operator.OperatorCreationException + + +object CryptoSystem extends Enumeration { + type CryptoSystem = Value + val RSA = Value + val AES = Value +} + +object CertificateUtil extends MdcLoggable { + + lazy val initPassword = try {System.getenv("UNLOCK")} catch {case _:Throwable => ""} + + // your-at-least-256-bit-secret + val sharedSecret: String = "your-at-least-256-bit-secret...." + + lazy val jksPath = Helper.getPropsValue("ssl_keystore_location").getOrElse("") + lazy val jksPasswd = Helper.getPropsValue("ssl_keystore_password").getOrElse(initPassword) + lazy val keyPasswd = Helper.getPropsValue("ssl_key_password").getOrElse(initPassword) + lazy val alias = Helper.getPropsValue("ssl_keystore_alias").getOrElse("") + + lazy val (publicKey: RSAPublicKey, privateKey: RSAPrivateKey) = Helper.getPropsAsBoolValue("ssl_client_auth", false) match { + case true => + getKeyPair( + jkspath = jksPath, + jkspasswd = jksPasswd, + keypasswd = keyPasswd, + alias = alias + ) + case false => + val keyPair = buildKeyPair(CryptoSystem.RSA) + val pubKey = keyPair.getPublic + val privateKey = keyPair.getPrivate + (pubKey, privateKey) + } + + def getKeyPair(jkspath: String, jkspasswd: String, keypasswd: String, alias: String): (PublicKey, Key) = { + val keyStore = KeyStore.getInstance(KeyStore.getDefaultType) + val inputStream = new FileInputStream(jkspath) + keyStore.load(inputStream, jkspasswd.toArray) + inputStream.close() + val privateKey: Key = keyStore.getKey(alias, keypasswd.toCharArray()) + if (privateKey.isInstanceOf[PrivateKey]) { + // Get certificate of public key + val cert: java.security.cert.Certificate = keyStore.getCertificate(alias) + + // Get public key + val publicKey: PublicKey = cert.getPublicKey + + // Return a key pair + (publicKey, privateKey) + } + else throw new RuntimeException("No private key") + } + + @throws[IOException] + @throws[NoSuchAlgorithmException] + @throws[CertificateException] + @throws[RuntimeException] + def getKeyStoreCertificate() = { + // This is used for QWAC certificate. Alias needs to be of that certificate. + val keyStore = KeyStore.getInstance(KeyStore.getDefaultType) + val inputStream = new FileInputStream(jksPath) + keyStore.load(inputStream, jksPasswd.toArray) + inputStream.close() + val privateKey: Key = keyStore.getKey(alias, keyPasswd.toCharArray()) + if (privateKey.isInstanceOf[PrivateKey]) { + // Get certificate of public key + val cert: java.security.cert.Certificate = keyStore.getCertificate(alias) + + // Return a private key and certificate + (privateKey, cert) + } + else throw new RuntimeException("No private key") + + } + + + @throws[NoSuchAlgorithmException] + def buildKeyPair(cryptoSystem: CryptoSystem): KeyPair = { + val keySize = 2048 + val keyPairGenerator = KeyPairGenerator.getInstance(cryptoSystem.toString) + keyPairGenerator.initialize(keySize) + keyPairGenerator.genKeyPair + } + + def convertRSAPublicKeyToAnRSAJWK(): String = { + import com.nimbusds.jose.jwk._ + // Convert to JWK format + val jwk: RSAKey = new RSAKey.Builder(publicKey) + .keyUse(KeyUse.SIGNATURE) + .keyIDFromThumbprint() + .build() + jwk.toJSONString() + } + + /** + * This is used for QWAC certificate. + * x5s is the part of te JOSE Protected header we use it in case of Java Web Signature. + * We sign response with rsaSigner and send it via "x-jws-signature" response header. + * it's verified via x5c value at third party app. + */ + lazy val (rsaSigner, x5c, rsaPublicKey) = { + val (privateKey: PrivateKey, certificate: Certificate) = + Props.mode match { + case Props.RunModes.Development | Props.RunModes.Test => SelfSignedCertificateUtil.generateSelfSignedCert("test.tesobe.com") + case _ => getKeyStoreCertificate + } + val publicKey: RSAPublicKey = certificate.getPublicKey.asInstanceOf[RSAPublicKey] + import com.nimbusds.jose.jwk._ + // Convert to JWK format + val jwk: RSAKey = new RSAKey.Builder(publicKey) + .privateKey(privateKey.asInstanceOf[RSAPrivateKey]) + .keyUse(KeyUse.SIGNATURE) + .keyIDFromThumbprint() + .build() + val x5c = X509CertUtils.toPEMString(certificate.asInstanceOf[X509Certificate], false) + .replace(X509CertUtils.PEM_BEGIN_MARKER, "") + .replace(X509CertUtils.PEM_END_MARKER, "") + (new RSASSASigner(jwk), x5c, publicKey) + } + + + def jwtWithHmacProtection(claimsSet: JWTClaimsSet): String = { + // Create HMAC signer + val signer: JWSSigner = new MACSigner(sharedSecret) + import com.nimbusds.jose.{JWSAlgorithm, JWSHeader} + import com.nimbusds.jwt.SignedJWT + val signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet) + // Apply the HMAC protection + signedJWT.sign(signer) + // Serialize to compact form, produces something like + // eyJhbGciOiJIUzI1NiJ9.SGVsbG8sIHdvcmxkIQ.onO9Ihudz3WkiauDO2Uhyuz0Y18UASXlSc1eS0NkWyA + val s: String = signedJWT.serialize() + // logger.info("jwtWithHmacProtection: " + s) + s + } + + def jwtWithHmacProtection(claimsSet: JWTClaimsSet, sharedSecret: String): String = { + // Create HMAC signer + val signer: JWSSigner = new MACSigner(sharedSecret) + import com.nimbusds.jose.{JWSAlgorithm, JWSHeader} + import com.nimbusds.jwt.SignedJWT + val signedJWT = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet) + // Apply the HMAC protection + signedJWT.sign(signer) + // Serialize to compact form, produces something like + // eyJhbGciOiJIUzI1NiJ9.SGVsbG8sIHdvcmxkIQ.onO9Ihudz3WkiauDO2Uhyuz0Y18UASXlSc1eS0NkWyA + val s: String = signedJWT.serialize() + // logger.info("jwtWithHmacProtection: " + s) + s + } + + def verifywtWithHmacProtection(jwt: String): Boolean = { + import com.nimbusds.jose.crypto.MACVerifier + import com.nimbusds.jwt.SignedJWT + val signedJWT: SignedJWT = SignedJWT.parse(jwt) + // your-at-least-256-bit-secret + val verifier = new MACVerifier(sharedSecret) + signedJWT.verify(verifier) + } + + def verifywtWithHmacProtection(jwt: String, sharedSecret: String): Boolean = { + import com.nimbusds.jose.crypto.MACVerifier + import com.nimbusds.jwt.SignedJWT + val signedJWT: SignedJWT = SignedJWT.parse(jwt) + // your-at-least-256-bit-secret + val verifier = new MACVerifier(sharedSecret) + signedJWT.verify(verifier) + } + + def parseJwtWithHmacProtection(jwt: String) = { + import com.nimbusds.jwt.SignedJWT + val signedJWT: SignedJWT = SignedJWT.parse(jwt) + val claimsSet = signedJWT.getJWTClaimsSet() + // logger.debug("signedJWT.getJWTClaimsSet(): " + claimsSet) + claimsSet + } + + def encryptJwtWithRsa(jwtClaims: JWTClaimsSet) = { + // Request JWT encrypted with RSA-OAEP-256 and 128-bit AES/GCM + val header = new JWEHeader(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM) + // Create an encrypter with the specified public RSA key + val encrypter = new RSAEncrypter(publicKey) + // Create the encrypted JWT object + val encryptedJWT = new EncryptedJWT(header, jwtClaims) + // Do the actual encryption + encryptedJWT.encrypt(encrypter) + // logger.debug("encryptedJwtWithRsa: " + encryptedJWT.serialize()) + // logger.debug("jwtClaims: " + jwtClaims) + // Serialise to JWT compact form + encryptedJWT.serialize() + } + + def decryptJwtWithRsa(encryptedJwtWithRsa: String) = { + import com.nimbusds.jose.crypto.RSADecrypter + import com.nimbusds.jwt.EncryptedJWT + // Parse back + val jwtParsed = EncryptedJWT.parse(encryptedJwtWithRsa) + // Create a decrypter with the specified private RSA key + val decrypter = new RSADecrypter(privateKey) + jwtParsed.decrypt(decrypter) + // logger.debug("encryptedJwtWithRsa: " + encryptedJwtWithRsa) + // logger.debug("getState: " + jwtParsed.getState) + // logger.debug("getJWTClaimsSet: " + jwtParsed.getJWTClaimsSet) + jwtParsed.getJWTClaimsSet + } + + + + def main(args: Array[String]): Unit = { + System.out.println("Public key:" + publicKey.getEncoded) + System.out.println("Private key:" + privateKey.getEncoded) + + val jwwtPayloadAsJson = + """{ + "login_user_name":"simonr", + "is_first":false, + "app_id":"593450734587345", + "app_name":"myapp4", + "time_stamp":"19-06-2017:22:27:11:100", + "cbs_token":"", + "cbs_id":"", + "session_id":"123" + }""" + + val jwtClaims: JWTClaimsSet = JWTClaimsSet.parse(jwwtPayloadAsJson) + + // 1.1 Encryption - JWT with RSA encryption + val encryptTokenWithRsa = encryptJwtWithRsa(jwtClaims) + logger.info(s"encryptTokenWithRsa =$encryptTokenWithRsa") + + // 1.2 Decryption - JWT with RSA encryption + val decryptToken = decryptJwtWithRsa(encryptTokenWithRsa) + logger.info(s"decryptToken = $decryptToken") + + // 2.1 JWT with HMAC protection + val hmacJwt = jwtWithHmacProtection(jwtClaims) + logger.info(s"hmacJwt = $hmacJwt") + + parseJwtWithHmacProtection(hmacJwt) + + println(convertRSAPublicKeyToAnRSAJWK) + + } + +} \ No newline at end of file