diff options
author | Antoine A <> | 2024-03-27 01:06:48 +0100 |
---|---|---|
committer | Antoine A <> | 2024-03-27 01:08:18 +0100 |
commit | dc03013dfcc3acc17bb8f54e842ccc6740caa040 (patch) | |
tree | bbe96517ac64ba33a78f89b5f1a46dacacd82ece | |
parent | 9dfbc9391fe27fc631bdf61e3e51dd3a124a4c60 (diff) | |
download | libeufin-dc03013dfcc3acc17bb8f54e842ccc6740caa040.tar.gz libeufin-dc03013dfcc3acc17bb8f54e842ccc6740caa040.tar.bz2 libeufin-dc03013dfcc3acc17bb8f54e842ccc6740caa040.zip |
Clean and optimize EBICS mess
17 files changed, 159 insertions, 281 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt index 54f2c44b..f48a1b35 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt @@ -28,9 +28,7 @@ import java.time.Duration private val logger: Logger = LoggerFactory.getLogger("libeufin-bank") -/** - * Application the parsed configuration. - */ +/** Configuration for libeufin-bank */ data class BankConfig( val name: String, val regionalCurrency: String, diff --git a/common/src/main/kotlin/crypto/utils.kt b/common/src/main/kotlin/crypto/CryptoUtil.kt index 10fa195e..a508c5b2 100644 --- a/common/src/main/kotlin/crypto/utils.kt +++ b/common/src/main/kotlin/crypto/CryptoUtil.kt @@ -45,18 +45,6 @@ import tech.libeufin.common.* /** Helpers for dealing with cryptographic operations in EBICS / LibEuFin */ object CryptoUtil { - /** - * RSA key pair. - */ - data class RsaCrtKeyPair(val private: RSAPrivateCrtKey, val public: RSAPublicKey) - - // FIXME(dold): This abstraction needs to be improved. - class EncryptionResult( - val encryptedTransactionKey: ByteArray, - val pubKeyDigest: ByteArray, - val encryptedData: ByteArray, - val plainTransactionKey: SecretKey - ) private val provider = BouncyCastleProvider() @@ -137,23 +125,17 @@ object CryptoUtil { } - /** - * Generate a fresh RSA key pair. - * - * @param nbits size of the modulus in bits - */ - fun generateRsaKeyPair(nbits: Int): RsaCrtKeyPair { + /** Generate an RSA key pair of [keysize] */ + fun genRSAPair(keysize: Int): Pair<RSAPrivateCrtKey, RSAPublicKey> { val gen = KeyPairGenerator.getInstance("RSA") - gen.initialize(nbits) + gen.initialize(keysize) val pair = gen.genKeyPair() - val priv = pair.private - val pub = pair.public - if (priv !is RSAPrivateCrtKey) - throw Exception("key generation failed") - if (pub !is RSAPublicKey) - throw Exception("key generation failed") - return RsaCrtKeyPair(priv, pub) + return Pair(pair.private as RSAPrivateCrtKey, pair.public as RSAPublicKey) } + /** Generate an RSA private key of [keysize] */ + fun genRSAPrivate(keysize: Int): RSAPrivateCrtKey = genRSAPair(keysize).first + /** Generate an RSA public key of [keysize] */ + fun genRSAPublic(keysize: Int): RSAPublicKey = genRSAPair(keysize).second /** * Hash an RSA public key according to the EBICS standard (EBICS 2.5: 4.4.1.2.3). @@ -167,73 +149,61 @@ object CryptoUtil { return digest.digest(keyBytes.toByteArray()) } - fun encryptEbicsE002(data: InputStream, encryptionPublicKey: RSAPublicKey): EncryptionResult { + fun genEbicsE002Key(encryptionPublicKey: RSAPublicKey): Pair<SecretKey, ByteArray> { + // Gen transaction key val keygen = KeyGenerator.getInstance("AES", provider) keygen.init(128) val transactionKey = keygen.generateKey() - return encryptEbicsE002withTransactionKey( - data, - encryptionPublicKey, - transactionKey + // Encrypt transaction keyA + val cipher = Cipher.getInstance( + "RSA/None/PKCS1Padding", + provider ) + cipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey) + val encryptedTransactionKey = cipher.doFinal(transactionKey.encoded) + return Pair(transactionKey, encryptedTransactionKey) } + /** * Encrypt data according to the EBICS E002 encryption process. */ - fun encryptEbicsE002withTransactionKey( - data: InputStream, - encryptionPublicKey: RSAPublicKey, - transactionKey: SecretKey - ): EncryptionResult { - val symmetricCipher = Cipher.getInstance( + fun encryptEbicsE002( + transactionKey: SecretKey, + data: InputStream + ): CipherInputStream { + val cipher = Cipher.getInstance( "AES/CBC/X9.23Padding", provider ) val ivParameterSpec = IvParameterSpec(ByteArray(16)) - symmetricCipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec) - val encryptedData = CipherInputStream(data, symmetricCipher).readAllBytes() - val asymmetricCipher = Cipher.getInstance( + cipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec) + return CipherInputStream(data, cipher) + } + + fun decryptEbicsE002Key( + privateKey: RSAPrivateCrtKey, + encryptedTransactionKey: ByteArray + ): SecretKeySpec { + val cipher = Cipher.getInstance( "RSA/None/PKCS1Padding", provider ) - asymmetricCipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey) - val encryptedTransactionKey = asymmetricCipher.doFinal(transactionKey.encoded) - val pubKeyDigest = getEbicsPublicKeyHash(encryptionPublicKey) - return EncryptionResult( - encryptedTransactionKey, - pubKeyDigest, - encryptedData, - transactionKey - ) - } - - fun decryptEbicsE002(enc: EncryptionResult, privateKey: RSAPrivateCrtKey): ByteArray { - return decryptEbicsE002( - enc.encryptedTransactionKey, - enc.encryptedData.inputStream(), - privateKey - ).readBytes() + cipher.init(Cipher.DECRYPT_MODE, privateKey) + val transactionKeyBytes = cipher.doFinal(encryptedTransactionKey) + return SecretKeySpec(transactionKeyBytes, "AES") } fun decryptEbicsE002( - encryptedTransactionKey: ByteArray, - encryptedData: InputStream, - privateKey: RSAPrivateCrtKey + transactionKey: SecretKeySpec, + encryptedData: InputStream ): CipherInputStream { - val asymmetricCipher = Cipher.getInstance( - "RSA/None/PKCS1Padding", - provider - ) - asymmetricCipher.init(Cipher.DECRYPT_MODE, privateKey) - val transactionKeyBytes = asymmetricCipher.doFinal(encryptedTransactionKey) - val secretKeySpec = SecretKeySpec(transactionKeyBytes, "AES") - val symmetricCipher = Cipher.getInstance( + val cipher = Cipher.getInstance( "AES/CBC/X9.23Padding", provider ) val ivParameterSpec = IvParameterSpec(ByteArray(16)) - symmetricCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) - return CipherInputStream(encryptedData, symmetricCipher) + cipher.init(Cipher.DECRYPT_MODE, transactionKey, ivParameterSpec) + return CipherInputStream(encryptedData, cipher) } /** @@ -290,31 +260,6 @@ object CryptoUtil { return priv } - fun encryptKey(data: ByteArray, passphrase: String): ByteArray { - /* Cipher parameters: salt and hash count */ - val hashIterations = 30 - val salt = ByteArray(8) - SecureRandom().nextBytes(salt) - val pbeParameterSpec = PBEParameterSpec(salt, hashIterations) - /* *Other* cipher parameters: symmetric key (from password) */ - val pbeAlgorithm = "PBEWithSHA1AndDESede" - val pbeKeySpec = PBEKeySpec(passphrase.toCharArray()) - val keyFactory = SecretKeyFactory.getInstance(pbeAlgorithm) - val secretKey = keyFactory.generateSecret(pbeKeySpec) - /* Make a cipher */ - val cipher = Cipher.getInstance(pbeAlgorithm) - cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec) - /* ready to encrypt now */ - val cipherText = cipher.doFinal(data) - /* Must now bundle a PKCS#8-compatible object, that contains - * algorithm, salt and hash count information */ - val bundleAlgorithmParams = AlgorithmParameters.getInstance(pbeAlgorithm) - bundleAlgorithmParams.init(pbeParameterSpec) - val bundle = EncryptedPrivateKeyInfo(bundleAlgorithmParams, cipherText) - return bundle.encoded - } - - fun hashStringSHA256(input: String): ByteArray { - return MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8)) - } + fun hashStringSHA256(input: String): ByteArray = + MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8)) }
\ No newline at end of file diff --git a/common/src/main/kotlin/crypto/password.kt b/common/src/main/kotlin/crypto/PwCrypto.kt index 2dd8af07..2dd8af07 100644 --- a/common/src/main/kotlin/crypto/password.kt +++ b/common/src/main/kotlin/crypto/PwCrypto.kt diff --git a/common/src/test/kotlin/CryptoUtilTest.kt b/common/src/test/kotlin/CryptoUtilTest.kt index f76701ad..6e062b1a 100644 --- a/common/src/test/kotlin/CryptoUtilTest.kt +++ b/common/src/test/kotlin/CryptoUtilTest.kt @@ -34,71 +34,38 @@ class CryptoUtilTest { @Test fun loadFromModulusAndExponent() { - val keyPair = CryptoUtil.generateRsaKeyPair(1024) + val public = CryptoUtil.genRSAPublic(1024) val pub2 = CryptoUtil.RSAPublicFromComponents( - keyPair.public.modulus.toByteArray(), - keyPair.public.publicExponent.toByteArray() + public.modulus.toByteArray(), + public.publicExponent.toByteArray() ) - assertEquals(keyPair.public, pub2) - } - - @Test - fun keyGeneration() { - val gen: KeyPairGenerator = KeyPairGenerator.getInstance("RSA") - gen.initialize(2048) - val pair = gen.genKeyPair() - println(pair.private) - assertTrue(pair.private is RSAPrivateCrtKey) + assertEquals(public, pub2) } @Test fun testCryptoUtilBasics() { - val keyPair = CryptoUtil.generateRsaKeyPair(1024) - val encodedPriv = keyPair.private.encoded - val encodedPub = keyPair.public.encoded - val otherKeyPair = - CryptoUtil.RsaCrtKeyPair(CryptoUtil.loadRSAPrivate(encodedPriv), CryptoUtil.loadRSAPublic(encodedPub)) - assertEquals(keyPair.private, otherKeyPair.private) - assertEquals(keyPair.public, otherKeyPair.public) + val (private, public) = CryptoUtil.genRSAPair(1024) + assertEquals(private, CryptoUtil.loadRSAPrivate(private.encoded)) + assertEquals(public, CryptoUtil.loadRSAPublic(public.encoded)) } @Test fun testEbicsE002() { val data = "Hello, World!".toByteArray() - val keyPair = CryptoUtil.generateRsaKeyPair(1024) - val enc = CryptoUtil.encryptEbicsE002(data.inputStream(), keyPair.public) - val dec = CryptoUtil.decryptEbicsE002(enc, keyPair.private) + val (private, public) = CryptoUtil.genRSAPair(1024) + val (txKey, encryptedKey) = CryptoUtil.genEbicsE002Key(public) + val enc = CryptoUtil.encryptEbicsE002(txKey, data.inputStream()) + val txKey2 = CryptoUtil.decryptEbicsE002Key(private, encryptedKey) + val dec = CryptoUtil.decryptEbicsE002(txKey2, enc).readBytes() assertTrue(data.contentEquals(dec)) } @Test fun testEbicsA006() { - val keyPair = CryptoUtil.generateRsaKeyPair(1024) + val (private, public) = CryptoUtil.genRSAPair(1024) val data = "Hello, World".toByteArray(Charsets.UTF_8) - val sig = CryptoUtil.signEbicsA006(data, keyPair.private) - assertTrue(CryptoUtil.verifyEbicsA006(sig, data, keyPair.public)) - } - - @Test - fun testPassphraseEncryption() { - - val keyPair = CryptoUtil.generateRsaKeyPair(1024) - - /* encrypt with original key */ - val data = "Hello, World!".toByteArray(Charsets.UTF_8) - val secret = CryptoUtil.encryptEbicsE002(data.inputStream(), keyPair.public) - - /* encrypt and decrypt private key */ - val encPriv = CryptoUtil.encryptKey(keyPair.private.encoded, "secret") - val plainPriv = CryptoUtil.decryptKey(EncryptedPrivateKeyInfo(encPriv), "secret") - - /* decrypt with decrypted private key */ - val revealed = CryptoUtil.decryptEbicsE002(secret, plainPriv) - - assertEquals( - String(revealed, charset = Charsets.UTF_8), - String(data, charset = Charsets.UTF_8) - ) + val sig = CryptoUtil.signEbicsA006(data, private) + assertTrue(CryptoUtil.verifyEbicsA006(sig, data, public)) } @Test diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt index 298d84b4..f25934cd 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -46,7 +46,7 @@ data class FetchContext( /** * Config handle. */ - val cfg: EbicsSetupConfig, + val cfg: NexusConfig, /** * HTTP client handle to reach the bank */ @@ -359,7 +359,7 @@ class EbicsFetch: CliktCommand("Fetches EBICS files") { * FIXME: reduce code duplication with the submit subcommand. */ override fun run() = cliCmd(logger, common.log) { - val cfg: EbicsSetupConfig = extractEbicsConfig(common.config) + val cfg = extractEbicsConfig(common.config) val dbCfg = cfg.config.dbConfig() Database(dbCfg.dbConnStr).use { db -> diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt index a4870834..dd13eb30 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -95,7 +95,7 @@ private fun askUserToAcceptKeys(bankKeys: BankPublicKeysFile): Boolean { * the --auto-accept-key CLI flag. */ suspend fun doKeysRequestAndUpdateState( - cfg: EbicsSetupConfig, + cfg: NexusConfig, privs: ClientPrivateKeysFile, client: HttpClient, order: EbicsKeyMng.Order @@ -133,7 +133,7 @@ suspend fun doKeysRequestAndUpdateState( accepted = false ) try { - persistBankKeys(bankKeys, cfg.bankPublicKeysFilename) + persistBankKeys(bankKeys, cfg.bankPublicKeysPath) } catch (e: Exception) { throw Exception("Could not update the $order state on disk", e) } @@ -141,7 +141,7 @@ suspend fun doKeysRequestAndUpdateState( } if (order != HPB) { try { - persistClientKeys(privs, cfg.clientPrivateKeysFilename) + persistClientKeys(privs, cfg.clientPrivateKeysPath) } catch (e: Exception) { throw Exception("Could not update the $order state on disk", e) } @@ -155,9 +155,9 @@ suspend fun doKeysRequestAndUpdateState( * @param configFile location of the configuration entry point. * @return internal representation of the configuration. */ -fun extractEbicsConfig(configFile: Path?): EbicsSetupConfig { +fun extractEbicsConfig(configFile: Path?): NexusConfig { val config = loadConfig(configFile) - return EbicsSetupConfig(config) + return NexusConfig(config) } /** @@ -167,7 +167,7 @@ fun extractEbicsConfig(configFile: Path?): EbicsSetupConfig { * @param privs client private keys. * @param cfg configuration handle. */ -private fun makePdf(privs: ClientPrivateKeysFile, cfg: EbicsSetupConfig) { +private fun makePdf(privs: ClientPrivateKeysFile, cfg: NexusConfig) { val pdf = generateKeysPdf(privs, cfg) val path = Path("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf") try { @@ -199,7 +199,7 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { override fun run() = cliCmd(logger, common.log) { val cfg = extractEbicsConfig(common.config) // Config is sane. Go (maybe) making the private keys. - val clientKeys = loadOrGenerateClientKeys(cfg.clientPrivateKeysFilename) + val clientKeys = loadOrGenerateClientKeys(cfg.clientPrivateKeysPath) val client = HttpClient { install(HttpTimeout) { // It can take a lot of time for the bank to generate documents @@ -216,11 +216,11 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { doKeysRequestAndUpdateState(cfg, clientKeys, client, HIA) // Checking if the bank keys exist on disk. - var bankKeys = loadBankKeys(cfg.bankPublicKeysFilename) + var bankKeys = loadBankKeys(cfg.bankPublicKeysPath) if (bankKeys == null) { doKeysRequestAndUpdateState(cfg, clientKeys, client, HPB) - logger.info("Bank keys stored at ${cfg.bankPublicKeysFilename}") - bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)!! + logger.info("Bank keys stored at ${cfg.bankPublicKeysPath}") + bankKeys = loadBankKeys(cfg.bankPublicKeysPath)!! } if (!bankKeys.accepted) { @@ -232,7 +232,7 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { throw Exception("Cannot successfully finish the setup without accepting the bank keys") } try { - persistBankKeys(bankKeys, cfg.bankPublicKeysFilename) + persistBankKeys(bankKeys, cfg.bankPublicKeysPath) } catch (e: Exception) { throw Exception("Could not set bank keys as accepted on disk", e) } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt index cf206380..34d57969 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt @@ -42,7 +42,7 @@ data class SubmissionContext( /** * Configuration handle. */ - val cfg: EbicsSetupConfig, + val cfg: NexusConfig, /** * Subscriber EBICS private keys. */ @@ -83,7 +83,7 @@ private suspend fun submitInitiatedPayment( initiationTimestamp = payment.initiationTime, amount = payment.amount, creditAccount = creditAccount, - debitAccount = ctx.cfg.myIbanAccount, + debitAccount = ctx.cfg.account, wireTransferSubject = payment.wireTransferSubject ) ctx.fileLogger.logSubmit(xml) @@ -146,7 +146,7 @@ class EbicsSubmit : CliktCommand("Submits any initiated payment found in the dat * FIXME: reduce code duplication with the fetch subcommand. */ override fun run() = cliCmd(logger, common.log) { - val cfg: EbicsSetupConfig = extractEbicsConfig(common.config) + val cfg = extractEbicsConfig(common.config) val dbCfg = cfg.config.dbConfig() val (clientKeys, bankKeys) = expectFullKeys(cfg) val ctx = SubmissionContext( diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt index b4acbb75..172cffa2 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt @@ -111,9 +111,9 @@ data class BankPublicKeysFile( */ fun generateNewKeys(): ClientPrivateKeysFile = ClientPrivateKeysFile( - authentication_private_key = CryptoUtil.generateRsaKeyPair(2048).private, - encryption_private_key = CryptoUtil.generateRsaKeyPair(2048).private, - signature_private_key = CryptoUtil.generateRsaKeyPair(2048).private, + authentication_private_key = CryptoUtil.genRSAPrivate(2048), + encryption_private_key = CryptoUtil.genRSAPrivate(2048), + signature_private_key = CryptoUtil.genRSAPrivate(2048), submitted_hia = false, submitted_ini = false ) @@ -202,18 +202,16 @@ fun loadClientKeys(location: Path): ClientPrivateKeysFile? = loadJsonFile(locati * @param cfg configuration handle. * @return both client and bank keys */ -fun expectFullKeys( - cfg: EbicsSetupConfig -): Pair<ClientPrivateKeysFile, BankPublicKeysFile> { - val clientKeys = loadClientKeys(cfg.clientPrivateKeysFilename) +fun expectFullKeys(cfg: NexusConfig): Pair<ClientPrivateKeysFile, BankPublicKeysFile> { + val clientKeys = loadClientKeys(cfg.clientPrivateKeysPath) if (clientKeys == null) { - throw Exception("Missing client private keys file at '${cfg.clientPrivateKeysFilename}', run 'libeufin-nexus ebics-setup' first") + throw Exception("Missing client private keys file at '${cfg.clientPrivateKeysPath}', run 'libeufin-nexus ebics-setup' first") } else if (!clientKeys.submitted_ini || !clientKeys.submitted_hia) { throw Exception("Unsubmitted client private keys, run 'libeufin-nexus ebics-setup' first") } - val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename) + val bankKeys = loadBankKeys(cfg.bankPublicKeysPath) if (bankKeys == null) { - throw Exception("Missing bank public keys at '${cfg.bankPublicKeysFilename}', run 'libeufin-nexus ebics-setup' first") + throw Exception("Missing bank public keys at '${cfg.bankPublicKeysPath}', run 'libeufin-nexus ebics-setup' first") } else if (!bankKeys.accepted) { throw Exception("Unaccepted bank public keys, run 'libeufin-nexus ebics-setup' until accepting the bank keys") } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt index b9582976..4cc67eec 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -61,68 +61,37 @@ fun Instant.fmtDate(): String = fun Instant.fmtDateTime(): String = DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneId.of("UTC")).format(this) -/** - * Keeps all the options of the ebics-setup subcommand. The - * caller has to handle TalerConfigError if values are missing. - * If even one of the fields could not be instantiated, then - * throws TalerConfigError. - */ -class EbicsSetupConfig(val config: TalerConfig) { - // abstracts the section name. - private val ebicsSetupRequireString = { option: String -> - config.requireString("nexus-ebics", option) - } - private val ebicsSetupRequirePath = { option: String -> - config.requirePath("nexus-ebics", option) - } - // debug utility to inspect what was loaded. - fun _dump() { - this.javaClass.declaredFields.forEach { - println("cfg obj: ${it.name} -> ${it.get(this)}") - } - } - /** - * The bank's currency. - */ - val currency = ebicsSetupRequireString("currency") - /** - * The bank base URL. - */ - val hostBaseUrl = ebicsSetupRequireString("host_base_url") - /** - * The bank EBICS host ID. - */ - val ebicsHostId = ebicsSetupRequireString("host_id") - /** - * EBICS user ID. - */ - val ebicsUserId = ebicsSetupRequireString("user_id") - /** - * EBICS partner ID. - */ - val ebicsPartnerId = ebicsSetupRequireString("partner_id") - /** - * Bank account metadata. - */ - val myIbanAccount = IbanAccountMetadata( - iban = ebicsSetupRequireString("iban"), - bic = ebicsSetupRequireString("bic"), - name = ebicsSetupRequireString("name") +/** Configuration for libeufin-nexus */ +class NexusConfig(val config: TalerConfig) { + private fun requireString(option: String): String = config.requireString("nexus-ebics", option) + private fun requirePath(option: String): Path = config.requirePath("nexus-ebics", option) + + /** The bank's currency */ + val currency = requireString("currency") + /** The bank base URL */ + val hostBaseUrl = requireString("host_base_url") + /** The bank EBICS host ID */ + val ebicsHostId = requireString("host_id") + /** EBICS user ID */ + val ebicsUserId = requireString("user_id") + /** EBICS partner ID */ + val ebicsPartnerId = requireString("partner_id") + /** Bank account metadata */ + val account = IbanAccountMetadata( + iban = requireString("iban"), + bic = requireString("bic"), + name = requireString("name") ) - /** - * Filename where we store the bank public keys. - */ - val bankPublicKeysFilename = ebicsSetupRequirePath("bank_public_keys_file") - /** - * Filename where we store our private keys. - */ - val clientPrivateKeysFilename = ebicsSetupRequirePath("client_private_keys_file") + /** Path where we store the bank public keys */ + val bankPublicKeysPath = requirePath("bank_public_keys_file") + /** Path where we store our private keys */ + val clientPrivateKeysPath = requirePath("client_private_keys_file") /** * A name that identifies the EBICS and ISO20022 flavour * that Nexus should honor in the communication with the * bank. */ - val bankDialect: String = ebicsSetupRequireString("bank_dialect").run { + val bankDialect: String = requireString("bank_dialect").run { if (this != "postfinance") throw Exception("Only 'postfinance' dialect is supported.") return@run this } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt index dd595d31..71937a42 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt @@ -37,7 +37,7 @@ import tech.libeufin.common.crypto.* */ fun generateKeysPdf( clientKeys: ClientPrivateKeysFile, - cfg: EbicsSetupConfig + cfg: NexusConfig ): ByteArray { val po = ByteArrayOutputStream() val pdfWriter = PdfWriter(po) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt index 9e5ee4e2..1c4294e3 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt @@ -40,7 +40,7 @@ fun Instant.xmlDateTime(): String = /** EBICS protocol for business transactions */ class EbicsBTS( - val cfg: EbicsSetupConfig, + val cfg: NexusConfig, val bankKeys: BankPublicKeysFile, val clientKeys: ClientPrivateKeysFile, val order: EbicsOrder @@ -217,7 +217,7 @@ class EbicsBTS( } el("SignatureData") { attr("authenticate", "true") - text(uploadData.userSignatureDataEncrypted.encodeBase64()) + text(uploadData.userSignatureDataEncrypted) } el("DataDigest") { attr("SignatureVersion", "A006") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt index 30bcd8de..f87c55b3 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt @@ -80,15 +80,17 @@ fun decryptAndDecompressPayload( clientEncryptionKey: RSAPrivateCrtKey, encryptionInfo: DataEncryptionInfo, chunks: List<ByteArray> -): InputStream = - SequenceInputStream(Collections.enumeration(chunks.map { it.inputStream() })) // Aggregate +): InputStream { + val transactionKey = CryptoUtil.decryptEbicsE002Key(clientEncryptionKey, encryptionInfo.transactionKey) + return SequenceInputStream(Collections.enumeration(chunks.map { it.inputStream() })) // Aggregate .run { CryptoUtil.decryptEbicsE002( - encryptionInfo.transactionKey, - this, - clientEncryptionKey + transactionKey, + this ) }.inflate() +} + sealed class EbicsError(msg: String, cause: Throwable? = null): Exception(msg, cause) { /** Http and network errors */ @@ -162,7 +164,7 @@ suspend fun EbicsBTS.postBTS( */ suspend fun ebicsDownload( client: HttpClient, - cfg: EbicsSetupConfig, + cfg: NexusConfig, clientKeys: ClientPrivateKeysFile, bankKeys: BankPublicKeysFile, order: EbicsOrder, @@ -267,43 +269,43 @@ suspend fun ebicsDownload( * @return [PreparedUploadData] */ fun prepareUploadPayload( - cfg: EbicsSetupConfig, + cfg: NexusConfig, clientKeys: ClientPrivateKeysFile, bankKeys: BankPublicKeysFile, payload: ByteArray, ): PreparedUploadData { + val payloadDigest = CryptoUtil.digestEbicsOrderA006(payload) val innerSignedEbicsXml = XmlBuilder.toBytes("UserSignatureData") { attr("xmlns", "http://www.ebics.org/S002") el("OrderSignatureData") { el("SignatureVersion", "A006") el("SignatureValue", CryptoUtil.signEbicsA006( - CryptoUtil.digestEbicsOrderA006(payload), + payloadDigest, clientKeys.signature_private_key, ).encodeBase64()) el("PartnerID", cfg.ebicsPartnerId) el("UserID", cfg.ebicsUserId) } } - val encryptionResult = CryptoUtil.encryptEbicsE002( - innerSignedEbicsXml.inputStream().deflate(), - bankKeys.bank_encryption_public_key - ) - // Then only E002 symmetric (with ephemeral key) encrypt. - val compressedInnerPayload = payload.inputStream().deflate() - // TODO stream - val encryptedPayload = CryptoUtil.encryptEbicsE002withTransactionKey( - compressedInnerPayload, - bankKeys.bank_encryption_public_key, - encryptionResult.plainTransactionKey - ) - val segment = encryptedPayload.encryptedData.encodeBase64() - // Split 1MB segment when we have payloads that big + // Generate ephemeral transaction key + val (transactionKey, encryptedTransactionKey) = CryptoUtil.genEbicsE002Key(bankKeys.bank_encryption_public_key) + // Compress and encrypt order signature + val orderSignature = CryptoUtil.encryptEbicsE002( + transactionKey, + innerSignedEbicsXml.inputStream().deflate() + ).encodeBase64() + // Compress and encrypt payload + val segment = CryptoUtil.encryptEbicsE002( + transactionKey, + payload.inputStream().deflate() + ).encodeBase64() + // TODO split 1MB segment when we have payloads that big return PreparedUploadData( - encryptionResult.encryptedTransactionKey, // ephemeral key - encryptionResult.encryptedData, // bank-pub-encrypted A006 signature. - CryptoUtil.digestEbicsOrderA006(payload), // used by EBICS 3 - listOf(segment) // actual payload E002 encrypted. + encryptedTransactionKey, + orderSignature, + payloadDigest, + listOf(segment) ) } @@ -321,7 +323,7 @@ fun prepareUploadPayload( */ suspend fun doEbicsUpload( client: HttpClient, - cfg: EbicsSetupConfig, + cfg: NexusConfig, clientKeys: ClientPrivateKeysFile, bankKeys: BankPublicKeysFile, order: EbicsOrder, @@ -361,7 +363,7 @@ fun getNonce(size: Int): ByteArray { class PreparedUploadData( val transactionKey: ByteArray, - val userSignatureDataEncrypted: ByteArray, + val userSignatureDataEncrypted: String, val dataDigest: ByteArray, val segments: List<String> ) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt index 9141430a..37e26e1c 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt @@ -25,7 +25,6 @@ import tech.libeufin.common.* import tech.libeufin.nexus.* import tech.libeufin.nexus.BankPublicKeysFile import tech.libeufin.nexus.ClientPrivateKeysFile -import tech.libeufin.nexus.EbicsSetupConfig import java.io.InputStream import java.time.Instant import java.time.ZoneId @@ -36,7 +35,7 @@ import tech.libeufin.nexus.ebics.EbicsKeyMng.Order.* /** EBICS protocol for key management */ class EbicsKeyMng( - private val cfg: EbicsSetupConfig, + private val cfg: NexusConfig, private val clientKeys: ClientPrivateKeysFile, private val ebics3: Boolean ) { @@ -54,13 +53,13 @@ class EbicsKeyMng( HPB -> Triple("ebicsNoPubKeyDigestsRequest", "0000", "DZHNN") } val data = when (order) { - INI -> XMLOrderData(cfg, "SignaturePubKeyOrderData", "http://www.ebics.org/S00${if (ebics3) 2 else 1}") { + INI -> XMLOrderData("SignaturePubKeyOrderData", "http://www.ebics.org/S00${if (ebics3) 2 else 1}") { el("SignaturePubKeyInfo") { RSAKeyXml(clientKeys.signature_private_key) el("SignatureVersion", "A006") } } - HIA -> XMLOrderData(cfg, "HIARequestOrderData", "urn:org:ebics:$schema") { + HIA -> XMLOrderData("HIARequestOrderData", "urn:org:ebics:$schema") { el("AuthenticationPubKeyInfo") { RSAKeyXml(clientKeys.authentication_private_key) el("AuthenticationVersion", "X002") @@ -111,7 +110,7 @@ class EbicsKeyMng( private fun XmlBuilder.RSAKeyXml(key: RSAPrivateCrtKey) { if (ebics3) { - val cert = CryptoUtil.X509CertificateFromRSAPrivate(key, cfg.myIbanAccount.name) + val cert = CryptoUtil.X509CertificateFromRSAPrivate(key, cfg.account.name) el("ds:X509Data") { el("ds:X509Certificate", cert.encoded.encodeBase64()) } @@ -125,7 +124,7 @@ class EbicsKeyMng( } } - private fun XMLOrderData(cfg: EbicsSetupConfig, name: String, schema: String, build: XmlBuilder.() -> Unit): String { + private fun XMLOrderData(name: String, schema: String, build: XmlBuilder.() -> Unit): String { return XmlBuilder.toBytes(name) { attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") attr("xmlns", schema) diff --git a/nexus/src/test/kotlin/CliTest.kt b/nexus/src/test/kotlin/CliTest.kt index 7e03053d..19bc0853 100644 --- a/nexus/src/test/kotlin/CliTest.kt +++ b/nexus/src/test/kotlin/CliTest.kt @@ -103,8 +103,8 @@ class CliTest { } // Unfinished bank persistBankKeys(BankPublicKeysFile( - bank_authentication_public_key = CryptoUtil.generateRsaKeyPair(2048).public, - bank_encryption_public_key = CryptoUtil.generateRsaKeyPair(2048).public, + bank_authentication_public_key = CryptoUtil.genRSAPublic(2048), + bank_encryption_public_key = CryptoUtil.genRSAPublic(2048), accepted = false ), bankKeysPath) for (cmd in cmds) { diff --git a/nexus/src/test/kotlin/Keys.kt b/nexus/src/test/kotlin/Keys.kt index 826ec77e..36dd6487 100644 --- a/nexus/src/test/kotlin/Keys.kt +++ b/nexus/src/test/kotlin/Keys.kt @@ -43,8 +43,8 @@ class PublicKeys { // artificially creating the keys. val fileContent = BankPublicKeysFile( accepted = true, - bank_authentication_public_key = CryptoUtil.generateRsaKeyPair(2028).public, - bank_encryption_public_key = CryptoUtil.generateRsaKeyPair(2028).public + bank_authentication_public_key = CryptoUtil.genRSAPublic(2028), + bank_encryption_public_key = CryptoUtil.genRSAPublic(2028) ) // storing them on disk. persistBankKeys(fileContent, Path("/tmp/nexus-tests-bank-keys.json")) diff --git a/nexus/src/test/kotlin/MySerializers.kt b/nexus/src/test/kotlin/MySerializers.kt index 98707948..1f2d8969 100644 --- a/nexus/src/test/kotlin/MySerializers.kt +++ b/nexus/src/test/kotlin/MySerializers.kt @@ -28,9 +28,9 @@ class MySerializers { // Testing deserialization of RSA private keys. @Test fun rsaPrivDeserialization() { - val s = Base32Crockford.encode(CryptoUtil.generateRsaKeyPair(2048).private.encoded) - val a = Base32Crockford.encode(CryptoUtil.generateRsaKeyPair(2048).private.encoded) - val e = Base32Crockford.encode(CryptoUtil.generateRsaKeyPair(2048).private.encoded) + val s = Base32Crockford.encode(CryptoUtil.genRSAPrivate(2048).encoded) + val a = Base32Crockford.encode(CryptoUtil.genRSAPrivate(2048).encoded) + val e = Base32Crockford.encode(CryptoUtil.genRSAPrivate(2048).encoded) val obj = JSON.decodeFromString<ClientPrivateKeysFile>(""" { "signature_private_key": "$s", @@ -40,8 +40,8 @@ class MySerializers { "submitted_hia": true } """.trimIndent()) - assertEquals(obj.signature_private_key, CryptoUtil.loadRsaPrivateKey(Base32Crockford.decode(s))) - assertEquals(obj.authentication_private_key, CryptoUtil.loadRsaPrivateKey(Base32Crockford.decode(a))) - assertEquals(obj.encryption_private_key, CryptoUtil.loadRsaPrivateKey(Base32Crockford.decode(e))) + assertEquals(obj.signature_private_key, CryptoUtil.loadRSAPrivate(Base32Crockford.decode(s))) + assertEquals(obj.authentication_private_key, CryptoUtil.loadRSAPrivate(Base32Crockford.decode(a))) + assertEquals(obj.encryption_private_key, CryptoUtil.loadRSAPrivate(Base32Crockford.decode(e))) } }
\ No newline at end of file diff --git a/nexus/src/test/kotlin/helpers.kt b/nexus/src/test/kotlin/helpers.kt index 57a1d2da..61aed19d 100644 --- a/nexus/src/test/kotlin/helpers.kt +++ b/nexus/src/test/kotlin/helpers.kt @@ -29,20 +29,20 @@ import kotlin.io.path.Path fun conf( conf: String = "test.conf", - lambda: suspend (EbicsSetupConfig) -> Unit + lambda: suspend (NexusConfig) -> Unit ) = runBlocking { val config = NEXUS_CONFIG_SOURCE.fromFile(Path("conf/$conf")) - val ctx = EbicsSetupConfig(config) + val ctx = NexusConfig(config) lambda(ctx) } fun setup( conf: String = "test.conf", - lambda: suspend (Database, EbicsSetupConfig) -> Unit + lambda: suspend (Database, NexusConfig) -> Unit ) = runBlocking { val config = NEXUS_CONFIG_SOURCE.fromFile(Path("conf/$conf")) val dbCfg = config.dbConfig() - val ctx = EbicsSetupConfig(config) + val ctx = NexusConfig(config) Database(dbCfg.dbConnStr).use { it.conn { conn -> resetDatabaseTables(conn, dbCfg, "libeufin-nexus") |