diff options
Diffstat (limited to 'nexus/src/main/kotlin/tech')
9 files changed, 87 insertions, 119 deletions
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) |