libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

CryptoUtil.kt (12132B)


      1 /*
      2  * This file is part of LibEuFin.
      3  * Copyright (C) 2024 Taler Systems S.A.
      4 
      5  * LibEuFin is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU Affero General Public License as
      7  * published by the Free Software Foundation; either version 3, or
      8  * (at your option) any later version.
      9 
     10  * LibEuFin is distributed in the hope that it will be useful, but
     11  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13  * Public License for more details.
     14 
     15  * You should have received a copy of the GNU Affero General Public
     16  * License along with LibEuFin; see the file COPYING.  If not, see
     17  * <http://www.gnu.org/licenses/>
     18  */
     19 
     20 package tech.libeufin.common.crypto
     21 
     22 import org.bouncycastle.asn1.x500.X500Name
     23 import org.bouncycastle.asn1.x509.BasicConstraints
     24 import org.bouncycastle.asn1.x509.Extension
     25 import org.bouncycastle.asn1.x509.KeyUsage
     26 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
     27 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
     28 import org.bouncycastle.crypto.generators.Argon2BytesGenerator
     29 import org.bouncycastle.crypto.generators.BCrypt
     30 import org.bouncycastle.crypto.params.Argon2Parameters
     31 import org.bouncycastle.jce.provider.BouncyCastleProvider
     32 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
     33 import tech.libeufin.common.*
     34 import java.io.ByteArrayOutputStream
     35 import java.io.InputStream
     36 import java.math.BigInteger
     37 import java.security.KeyFactory
     38 import java.security.KeyPairGenerator
     39 import java.security.MessageDigest
     40 import java.security.Signature
     41 import java.security.cert.CertificateFactory
     42 import java.security.cert.X509Certificate
     43 import java.security.interfaces.RSAPrivateCrtKey
     44 import java.security.interfaces.RSAPublicKey
     45 import java.security.spec.*
     46 import java.util.*
     47 import javax.crypto.*
     48 import javax.crypto.spec.IvParameterSpec
     49 import javax.crypto.spec.PBEKeySpec
     50 import javax.crypto.spec.SecretKeySpec
     51 
     52 /** Helpers for dealing with cryptographic operations in EBICS / LibEuFin */
     53 object CryptoUtil {
     54 
     55     private val provider = BouncyCastleProvider()
     56 
     57     /** Load an RSA private key from its binary PKCS#8 encoding  */
     58     fun loadRSAPrivate(encodedPrivateKey: ByteArray): RSAPrivateCrtKey {
     59         val spec = PKCS8EncodedKeySpec(encodedPrivateKey)
     60         val priv = KeyFactory.getInstance("RSA").generatePrivate(spec)
     61         return priv as RSAPrivateCrtKey
     62     }
     63 
     64     /** Load an RSA public key from its binary X509 encoding */
     65     fun loadRSAPublic(encodedPublicKey: ByteArray): RSAPublicKey {
     66         val spec = X509EncodedKeySpec(encodedPublicKey)
     67         val pub = KeyFactory.getInstance("RSA").generatePublic(spec)
     68         return pub as RSAPublicKey
     69     }
     70 
     71     /** Create an RSA public key from its components: [modulus] and [exponent] */
     72     fun RSAPublicFromComponents(modulus: ByteArray, exponent: ByteArray): RSAPublicKey {
     73         val modulusBigInt = BigInteger(1, modulus)
     74         val exponentBigInt = BigInteger(1, exponent)
     75         val spec = RSAPublicKeySpec(modulusBigInt, exponentBigInt)
     76         return KeyFactory.getInstance("RSA").generatePublic(spec) as RSAPublicKey
     77     }
     78 
     79     /** Extract an RSA public key from a [raw] X.509 certificate */
     80     fun RSAPublicFromCertificate(raw: ByteArray): RSAPublicKey {
     81         val certificate = CertificateFactory.getInstance("X.509").generateCertificate(raw.inputStream())
     82         return certificate.publicKey as RSAPublicKey
     83     }
     84 
     85     /** Generate an RSA public key from a [private] one */
     86     fun RSAPublicFromPrivate(private: RSAPrivateCrtKey): RSAPublicKey {
     87         val spec = RSAPublicKeySpec(private.modulus, private.publicExponent)
     88         return KeyFactory.getInstance("RSA").generatePublic(spec) as RSAPublicKey
     89     }
     90 
     91     /** Generate a self-signed X.509 certificate from an RSA [private] key */
     92     fun X509CertificateFromRSAPrivate(private: RSAPrivateCrtKey, name: String): X509Certificate {
     93         val start = Date()
     94         val calendar = Calendar.getInstance()
     95         calendar.time = start
     96         calendar.add(Calendar.YEAR, 1_000)
     97         val end = calendar.time
     98 
     99         val name = X500Name("CN=$name")
    100         val builder = JcaX509v3CertificateBuilder(
    101             name,
    102             BigInteger(20, Random()),
    103             start,
    104             end, 
    105             name,
    106             RSAPublicFromPrivate(private)
    107         )
    108 
    109         
    110         builder.addExtension(Extension.keyUsage, true, KeyUsage(
    111             KeyUsage.digitalSignature 
    112                 or KeyUsage.nonRepudiation
    113                 or KeyUsage.keyEncipherment
    114                 or KeyUsage.dataEncipherment
    115                 or KeyUsage.keyAgreement
    116                 or KeyUsage.keyCertSign
    117                 or KeyUsage.cRLSign
    118                 or KeyUsage.encipherOnly
    119                 or KeyUsage.decipherOnly
    120         ))
    121         builder.addExtension(Extension.basicConstraints, true, BasicConstraints(true))
    122 
    123         val certificate = JcaContentSignerBuilder("SHA256WithRSA").build(private)
    124         return JcaX509CertificateConverter()
    125             .setProvider(provider)
    126             .getCertificate(builder.build(certificate))
    127 
    128     }
    129 
    130     /** Generate an RSA key pair of [keysize] */
    131     fun genRSAPair(keysize: Int): Pair<RSAPrivateCrtKey, RSAPublicKey> {
    132         val gen = KeyPairGenerator.getInstance("RSA")
    133         gen.initialize(keysize)
    134         val pair = gen.genKeyPair()
    135         return Pair(pair.private as RSAPrivateCrtKey, pair.public as RSAPublicKey)
    136     }
    137     /** Generate an RSA private key of [keysize] */
    138     fun genRSAPrivate(keysize: Int): RSAPrivateCrtKey = genRSAPair(keysize).first
    139     /** Generate an RSA public key of [keysize] */
    140     fun genRSAPublic(keysize: Int): RSAPublicKey = genRSAPair(keysize).second
    141 
    142     /**
    143      * Hash an RSA public key according to the EBICS standard (EBICS 2.5: 4.4.1.2.3).
    144      */
    145     fun getEbicsPublicKeyHash(publicKey: RSAPublicKey): ByteArray {
    146         val keyBytes = ByteArrayOutputStream()
    147         keyBytes.writeBytes(publicKey.publicExponent.encodeHex().trimStart('0').toByteArray())
    148         keyBytes.write(' '.code)
    149         keyBytes.writeBytes(publicKey.modulus.encodeHex().trimStart('0').toByteArray())
    150         val digest = MessageDigest.getInstance("SHA-256")
    151         return digest.digest(keyBytes.toByteArray())
    152     }
    153 
    154     fun genEbicsE002Key(encryptionPublicKey: RSAPublicKey): Pair<SecretKey, ByteArray> {
    155         // Gen transaction key
    156         val keygen = KeyGenerator.getInstance("AES", provider)
    157         keygen.init(128)
    158         val transactionKey = keygen.generateKey()
    159         // Encrypt transaction keyA
    160         val cipher = Cipher.getInstance(
    161             "RSA/None/PKCS1Padding",
    162             provider
    163         )
    164         cipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey)
    165         val encryptedTransactionKey = cipher.doFinal(transactionKey.encoded)
    166         return Pair(transactionKey, encryptedTransactionKey)
    167     }
    168     
    169     /**
    170      * Encrypt data according to the EBICS E002 encryption process.
    171      */
    172     fun encryptEbicsE002(
    173         transactionKey: SecretKey,
    174         data: InputStream
    175     ): CipherInputStream {
    176         val cipher = Cipher.getInstance(
    177             "AES/CBC/X9.23Padding",
    178             provider
    179         )
    180         val ivParameterSpec = IvParameterSpec(ByteArray(16))
    181         cipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec)
    182         return CipherInputStream(data, cipher)
    183     }
    184 
    185     fun decryptEbicsE002Key(
    186         privateKey: RSAPrivateCrtKey,
    187         encryptedTransactionKey: ByteArray
    188     ): SecretKeySpec {
    189         val cipher = Cipher.getInstance(
    190             "RSA/None/PKCS1Padding",
    191             provider
    192         )
    193         cipher.init(Cipher.DECRYPT_MODE, privateKey)
    194         val transactionKeyBytes = cipher.doFinal(encryptedTransactionKey)
    195         return SecretKeySpec(transactionKeyBytes, "AES")
    196     }
    197 
    198     fun decryptEbicsE002(
    199         transactionKey: SecretKeySpec,
    200         encryptedData: InputStream
    201     ): CipherInputStream {
    202         val cipher = Cipher.getInstance(
    203             "AES/CBC/X9.23Padding",
    204             provider
    205         )
    206         val ivParameterSpec = IvParameterSpec(ByteArray(16))
    207         cipher.init(Cipher.DECRYPT_MODE, transactionKey, ivParameterSpec)
    208         return CipherInputStream(encryptedData, cipher)
    209     }
    210 
    211     /**
    212      * Signing algorithm corresponding to the EBICS A006 signing process.
    213      *
    214      * Note that while [data] can be arbitrary-length data, in EBICS, the order
    215      * data is *always* hashed *before* passing it to the signing algorithm, which again
    216      * uses a hash internally.
    217      */
    218     fun signEbicsA006(data: ByteArray, privateKey: RSAPrivateCrtKey): ByteArray {
    219         val signature = Signature.getInstance("SHA256withRSA/PSS", provider)
    220         signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
    221         signature.initSign(privateKey)
    222         signature.update(data)
    223         return signature.sign()
    224     }
    225 
    226     fun verifyEbicsA006(sig: ByteArray, data: ByteArray, publicKey: RSAPublicKey): Boolean {
    227         val signature = Signature.getInstance("SHA256withRSA/PSS", provider)
    228         signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1))
    229         signature.initVerify(publicKey)
    230         signature.update(data)
    231         return signature.verify(sig)
    232     }
    233 
    234     fun digestEbicsOrderA006(orderData: ByteArray): ByteArray {
    235         val digest = MessageDigest.getInstance("SHA-256")
    236         for (b in orderData) {
    237             when (b) {
    238                 '\r'.code.toByte(), '\n'.code.toByte(), (26).toByte() -> Unit
    239                 else -> digest.update(b)
    240             }
    241         }
    242         return digest.digest()
    243     }
    244 
    245     fun decryptKey(data: EncryptedPrivateKeyInfo, passphrase: String): RSAPrivateCrtKey {
    246         /* make key out of passphrase */
    247         val pbeKeySpec = PBEKeySpec(passphrase.toCharArray())
    248         val keyFactory = SecretKeyFactory.getInstance(data.algName)
    249         val secretKey = keyFactory.generateSecret(pbeKeySpec)
    250         /* Make a cipher */
    251         val cipher = Cipher.getInstance(data.algName)
    252         cipher.init(
    253             Cipher.DECRYPT_MODE,
    254             secretKey,
    255             data.algParameters // has hash count and salt
    256         )
    257         /* Ready to decrypt */
    258         val decryptedKeySpec: PKCS8EncodedKeySpec = data.getKeySpec(cipher)
    259         val priv = KeyFactory.getInstance("RSA").generatePrivate(decryptedKeySpec)
    260         return priv as RSAPrivateCrtKey
    261     }
    262 
    263     fun hashStringSHA256(input: String): ByteArray =
    264         MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8))
    265 
    266     fun bcrypt(password: String, salt: ByteArray, cost: Int): ByteArray {
    267         val pwBytes = BCrypt.passwordToByteArray(password.toCharArray())
    268         return BCrypt.generate(pwBytes, salt, cost)
    269     }
    270     
    271     fun hashArgon2id(password: String, salt: ByteArray): ByteArray {
    272         // OSWAP recommended config https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
    273         val builder = Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
    274             .withIterations(1)
    275             .withMemoryAsKB(47104)
    276             .withParallelism(1)
    277             .withSalt(salt)
    278 
    279         val gen = Argon2BytesGenerator()
    280         gen.init(builder.build())
    281 
    282         val result = ByteArray(32)
    283         gen.generateBytes(password.toCharArray(), result, 0, result.size)
    284         return result
    285     }
    286 
    287     private fun mfaBodyHash(body: ByteArray, salt: Base32Crockford16B): Base32Crockford64B {
    288         val digest = MessageDigest.getInstance("SHA-512")
    289         digest.update(salt.raw)
    290         val hash = digest.digest(body)
    291         return Base32Crockford64B(hash)
    292     }
    293 
    294     fun mfaBodyHashCheck(body: ByteArray, hash: Base32Crockford64B, salt: Base32Crockford16B): Boolean {
    295         val check = mfaBodyHash(body, salt)
    296         return check == hash
    297     }
    298 
    299     fun mfaBodyHashCreate(body: ByteArray): Pair<Base32Crockford64B, Base32Crockford16B> {
    300         val salt = Base32Crockford16B.secureRand()
    301         val hash = mfaBodyHash(body, salt)
    302         return Pair(hash, salt)
    303     }
    304 }