diff options
author | Antoine A <> | 2024-03-08 17:58:26 +0100 |
---|---|---|
committer | Antoine A <> | 2024-03-08 17:58:26 +0100 |
commit | 298557c74a4fa8112cb4c8b0be644496d77e74a1 (patch) | |
tree | 1c124c5e2d259864219b89bc02446a11c2628218 | |
parent | 8b0ca3dbd7359a402b1671a7cb0fa37b92543d1e (diff) | |
download | libeufin-298557c74a4fa8112cb4c8b0be644496d77e74a1.tar.gz libeufin-298557c74a4fa8112cb4c8b0be644496d77e74a1.tar.bz2 libeufin-298557c74a4fa8112cb4c8b0be644496d77e74a1.zip |
More simplification & refactoring
5 files changed, 222 insertions, 324 deletions
diff --git a/common/src/main/kotlin/crypto/utils.kt b/common/src/main/kotlin/crypto/utils.kt index 4e272b15..2f98e064 100644 --- a/common/src/main/kotlin/crypto/utils.kt +++ b/common/src/main/kotlin/crypto/utils.kt @@ -78,6 +78,22 @@ object CryptoUtil { } /** + * Load an RSA public key from its components. + * + * @param exponent + * @param modulus + * @return key + */ + fun loadRsaPublicKeyFromComponents(modulus: ByteArray, exponent: ByteArray): RSAPublicKey { + val modulusBigInt = BigInteger(1, modulus) + val exponentBigInt = BigInteger(1, exponent) + + val keyFactory = KeyFactory.getInstance("RSA") + val tmp = RSAPublicKeySpec(modulusBigInt, exponentBigInt) + return keyFactory.generatePublic(tmp) as RSAPublicKey + } + + /** * Load an RSA public key from its binary X509 encoding. */ fun getRsaPublicFromPrivate(rsaPrivateCrtKey: RSAPrivateCrtKey): RSAPublicKey { @@ -107,22 +123,6 @@ object CryptoUtil { } /** - * Load an RSA public key from its components. - * - * @param exponent - * @param modulus - * @return key - */ - fun loadRsaPublicKeyFromComponents(modulus: ByteArray, exponent: ByteArray): RSAPublicKey { - val modulusBigInt = BigInteger(1, modulus) - val exponentBigInt = BigInteger(1, exponent) - - val keyFactory = KeyFactory.getInstance("RSA") - val tmp = RSAPublicKeySpec(modulusBigInt, exponentBigInt) - return keyFactory.generatePublic(tmp) as RSAPublicKey - } - - /** * Hash an RSA public key according to the EBICS standard (EBICS 2.5: 4.4.1.2.3). */ fun getEbicsPublicKeyHash(publicKey: RSAPublicKey): ByteArray { diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt index e690dab8..916014e7 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -92,43 +92,6 @@ private fun askUserToAcceptKeys(bankKeys: BankPublicKeysFile): Boolean { } /** - * Parses the HPB response and stores the bank keys as "NOT accepted" to disk. - * - * @param cfg used to get the location of the bank keys file. - * @param bankKeys bank response to the HPB message. - */ -private fun handleHpbResponse( - cfg: EbicsSetupConfig, - bankKeys: EbicsKeyManagementResponseContent -) { - val hpbBytes = bankKeys.orderData // silences compiler. - if (hpbBytes == null) { - throw Exception("HPB content not found in a EBICS response with successful return codes.") - } - val hpbObj = try { - parseEbicsHpbOrder(hpbBytes.inputStream()) - } catch (e: Exception) { - throw Exception("HPB response content seems invalid", e) - } - val encPub = try { - CryptoUtil.loadRsaPublicKey(hpbObj.encryptionPubKey.encoded) - } catch (e: Exception) { - throw Exception("Could not import bank encryption key from HPB response", e) - } - val authPub = try { - CryptoUtil.loadRsaPublicKey(hpbObj.authenticationPubKey.encoded) - } catch (e: Exception) { - throw Exception("Could not import bank authentication key from HPB response", e) - } - val json = BankPublicKeysFile( - bank_authentication_public_key = authPub, - bank_encryption_public_key = encPub, - accepted = false - ) - persistBankKeys(json, cfg.bankPublicKeysFilename) -} - -/** * Collects all the steps from generating the message, to * sending it to the bank, and finally updating the state * on disk according to the response. @@ -147,34 +110,45 @@ suspend fun doKeysRequestAndUpdateState( orderType: KeysOrderType ) { logger.info("Doing key request ${orderType.name}") + val impl = Ebics3KeyMng(cfg, privs) val req = when(orderType) { - KeysOrderType.INI -> generateIniMessage(cfg, privs) - KeysOrderType.HIA -> generateHiaMessage(cfg, privs) - KeysOrderType.HPB -> generateHpbMessage(cfg, privs) + KeysOrderType.INI -> impl.INI() + KeysOrderType.HIA -> impl.HIA() + KeysOrderType.HPB -> impl.HPB() } - val xml = try { - client.postToBank(cfg.hostBaseUrl, req) - } catch (e: Exception) { - throw Exception("Could not POST the ${orderType.name} message to the bank at '${cfg.hostBaseUrl}'", e) - } - val ebics = parseKeysMgmtResponse(privs.encryption_private_key, xml) - if (ebics.technicalReturnCode != EbicsReturnCode.EBICS_OK) { - throw Exception("EBICS ${orderType.name} failed with code: ${ebics.technicalReturnCode}") - } - if (ebics.bankReturnCode != EbicsReturnCode.EBICS_OK) { - throw Exception("EBICS ${orderType.name} reached the bank, but could not be fulfilled, error code: ${ebics.bankReturnCode}") - } - + val xml = client.postToBank(cfg.hostBaseUrl, req) + val resp = Ebics3KeyMng.parseResponse(xml, privs.encryption_private_key) + // TODO better error messages for expected errros + + val orderData = resp.okOrFail("${orderType.name}") when (orderType) { KeysOrderType.INI -> privs.submitted_ini = true KeysOrderType.HIA -> privs.submitted_hia = true - KeysOrderType.HPB -> return handleHpbResponse(cfg, ebics) + KeysOrderType.HPB -> { + val orderData = requireNotNull(orderData) { + "HPB: missing order data" + } + val (authPub, encPub) = Ebics3KeyMng.parseHpbOrder(orderData) + val bankKeys = BankPublicKeysFile( + bank_authentication_public_key = authPub, + bank_encryption_public_key = encPub, + accepted = false + ) + try { + persistBankKeys(bankKeys, cfg.bankPublicKeysFilename) + } catch (e: Exception) { + throw Exception("Could not update the ${orderType.name} state on disk", e) + } + } } - try { - persistClientKeys(privs, cfg.clientPrivateKeysFilename) - } catch (e: Exception) { - throw Exception("Could not update the ${orderType.name} state on disk", e) + if (orderType != KeysOrderType.HPB) { + try { + persistClientKeys(privs, cfg.clientPrivateKeysFilename) + } catch (e: Exception) { + throw Exception("Could not update the ${orderType.name} state on disk", e) + } } + } /** @@ -266,12 +240,12 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { else bankKeys.accepted = askUserToAcceptKeys(bankKeys) if (!bankKeys.accepted) { - throw Exception("Cannot successfully finish the setup without accepting the bank keys.") + throw Exception("Cannot successfully finish the setup without accepting the bank keys") } try { persistBankKeys(bankKeys, cfg.bankPublicKeysFilename) } catch (e: Exception) { - throw Exception("Could not set bank keys as accepted on disk.", e) + throw Exception("Could not set bank keys as accepted on disk", e) } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt index 9f2d2afd..bee41525 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt @@ -23,8 +23,6 @@ package tech.libeufin.nexus.ebics -import org.slf4j.Logger -import org.slf4j.LoggerFactory import org.w3c.dom.Document import tech.libeufin.common.crypto.CryptoUtil import tech.libeufin.common.* @@ -39,237 +37,179 @@ import java.util.* import javax.xml.datatype.DatatypeFactory import java.security.interfaces.* -private val logger: Logger = LoggerFactory.getLogger("libeufin-nexus-ebics2") - -/** - * Parses the raw XML that came from the bank into the Nexus representation. - * - * @param clientEncryptionKey client private encryption key, used to decrypt - * the transaction key. - * @param xml the bank raw XML response - * @return the internal representation of the XML response, or null if the parsing or the decryption failed. - * Note: it _is_ possible to successfully return the internal repr. of this response, where - * the payload is null. That's however still useful, because the returned type provides bank - * and EBICS return codes. - */ -fun parseKeysMgmtResponse( - clientEncryptionKey: RSAPrivateCrtKey, - xml: Document -): EbicsKeyManagementResponseContent { - return XmlDestructor.fromDoc(xml, "ebicsKeyManagementResponse") { - lateinit var technicalReturnCode: EbicsReturnCode - lateinit var bankReturnCode: EbicsReturnCode - lateinit var reportText: String - var payload: ByteArray? = null - one("header") { - one("mutable") { - technicalReturnCode = EbicsReturnCode.lookup(one("ReturnCode").text()) - reportText = one("ReportText").text() +/** Ebics 3 protocol for */ +class Ebics3KeyMng( + private val cfg: EbicsSetupConfig, + private val clientKeys: ClientPrivateKeysFile +) { + fun INI(): ByteArray { + val inner = XMLOrderData(cfg, "ns2:SignaturePubKeyOrderData", "http://www.ebics.org/S001") { + el("ns2:SignaturePubKeyInfo") { + RSAKeyXml(clientKeys.signature_private_key) + el("ns2:SignatureVersion", "A006") } } - one("body") { - bankReturnCode = EbicsReturnCode.lookup(one("ReturnCode").text()) - payload = opt("DataTransfer") { - val descriptionInfo = one("DataEncryptionInfo") { - DataEncryptionInfo( - one("TransactionKey").text().decodeBase64(), - one("EncryptionPubKeyDigest").text().decodeBase64() - ) + val doc = XmlBuilder.toDom("ebicsUnsecuredRequest", "urn:org:ebics:H004") { + attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H004") + attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") + attr("Version", "H004") + attr("Revision", "1") + el("header") { + attr("authenticate", "true") + el("static") { + el("HostID", cfg.ebicsHostId) + el("PartnerID", cfg.ebicsPartnerId) + el("UserID", cfg.ebicsUserId) + el("OrderDetails") { + el("OrderType", "INI") + el("OrderAttribute", "DZNNN") + } + el("SecurityMedium", "0200") } - decryptAndDecompressPayload( - clientEncryptionKey, - descriptionInfo, - listOf(one("OrderData").text()) - ).readBytes() + el("mutable") } + el("body/DataTransfer/OrderData", inner) } - EbicsKeyManagementResponseContent(technicalReturnCode, bankReturnCode, payload) + return XMLUtil.convertDomToBytes(doc) } -} -private fun XmlBuilder.RSAKeyXml(key: RSAPrivateCrtKey) { - el("ns2:PubKeyValue") { - el("ds:RSAKeyValue") { - el("ds:Modulus", key.modulus.encodeBase64()) - el("ds:Exponent", key.publicExponent.encodeBase64()) - } - } -} - -private fun XMLOrderData(cfg: EbicsSetupConfig, name: String, schema: String, build: XmlBuilder.() -> Unit): String { - return XmlBuilder.toBytes(name) { - attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") - attr("xmlns:ns2", schema) - build() - el("ns2:PartnerID", cfg.ebicsPartnerId) - el("ns2:UserID", cfg.ebicsUserId) - }.inputStream().deflate().encodeBase64() -} - -/** - * Generates the INI message to upload the signature key. - * - * @param cfg handle to the configuration. - * @param clientKeys set of all the client keys. - * @return the raw EBICS INI message. - */ -fun generateIniMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { - val inner = XMLOrderData(cfg, "ns2:SignaturePubKeyOrderData", "http://www.ebics.org/S001") { - el("ns2:SignaturePubKeyInfo") { - RSAKeyXml(clientKeys.signature_private_key) - el("ns2:SignatureVersion", "A006") + fun HIA(): ByteArray { + val inner = XMLOrderData(cfg, "ns2:HIARequestOrderData", "urn:org:ebics:H004") { + el("ns2:AuthenticationPubKeyInfo") { + RSAKeyXml(clientKeys.authentication_private_key) + el("ns2:AuthenticationVersion", "X002") + } + el("ns2:EncryptionPubKeyInfo") { + RSAKeyXml(clientKeys.encryption_private_key) + el("ns2:EncryptionVersion", "E002") + } } - } - val doc = XmlBuilder.toDom("ebicsUnsecuredRequest", "urn:org:ebics:H004") { - attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H004") - attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") - attr("Version", "H004") - attr("Revision", "1") - el("header") { - attr("authenticate", "true") - el("static") { - el("HostID", cfg.ebicsHostId) - el("PartnerID", cfg.ebicsPartnerId) - el("UserID", cfg.ebicsUserId) - el("OrderDetails") { - el("OrderType", "INI") - el("OrderAttribute", "DZNNN") + val doc = XmlBuilder.toDom("ebicsUnsecuredRequest", "urn:org:ebics:H004") { + attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H004") + attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") + attr("Version", "H004") + attr("Revision", "1") + el("header") { + attr("authenticate", "true") + el("static") { + el("HostID", cfg.ebicsHostId) + el("PartnerID", cfg.ebicsPartnerId) + el("UserID", cfg.ebicsUserId) + el("OrderDetails") { + el("OrderType", "HIA") + el("OrderAttribute", "DZNNN") + } + el("SecurityMedium", "0200") } - el("SecurityMedium", "0200") + el("mutable") } - el("mutable") + el("body/DataTransfer/OrderData", inner) } - el("body/DataTransfer/OrderData", inner) + return XMLUtil.convertDomToBytes(doc) } - return XMLUtil.convertDomToBytes(doc) -} -/** - * Generates the HIA message: uploads the authentication and - * encryption keys. - * - * @param cfg handle to the configuration. - * @param clientKeys set of all the client keys. - * @return the raw EBICS HIA message. - */ -fun generateHiaMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { - val inner = XMLOrderData(cfg, "ns2:HIARequestOrderData", "urn:org:ebics:H004") { - el("ns2:AuthenticationPubKeyInfo") { - RSAKeyXml(clientKeys.authentication_private_key) - el("ns2:AuthenticationVersion", "X002") - } - el("ns2:EncryptionPubKeyInfo") { - RSAKeyXml(clientKeys.encryption_private_key) - el("ns2:EncryptionVersion", "E002") - } - } - val doc = XmlBuilder.toDom("ebicsUnsecuredRequest", "urn:org:ebics:H004") { - attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H004") - attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") - attr("Version", "H004") - attr("Revision", "1") - el("header") { - attr("authenticate", "true") - el("static") { - el("HostID", cfg.ebicsHostId) - el("PartnerID", cfg.ebicsPartnerId) - el("UserID", cfg.ebicsUserId) - el("OrderDetails") { - el("OrderType", "HIA") - el("OrderAttribute", "DZNNN") + fun HPB(): ByteArray { + val nonce = getNonce(128) + val doc = XmlBuilder.toDom("ebicsNoPubKeyDigestsRequest", "urn:org:ebics:H004") { + attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H004") + attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") + attr("Version", "H004") + attr("Revision", "1") + el("header") { + attr("authenticate", "true") + el("static") { + el("HostID", cfg.ebicsHostId) + el("Nonce", nonce.encodeUpHex()) + el("Timestamp", Instant.now().xmlDateTime()) + el("PartnerID", cfg.ebicsPartnerId) + el("UserID", cfg.ebicsUserId) + el("OrderDetails") { + el("OrderType", "HPB") + el("OrderAttribute", "DZHNN") + } + el("SecurityMedium", "0000") } - el("SecurityMedium", "0200") + el("mutable") } - el("mutable") + el("AuthSignature") + el("body") } - el("body/DataTransfer/OrderData", inner) + XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key) + return XMLUtil.convertDomToBytes(doc) } - return XMLUtil.convertDomToBytes(doc) -} -/** - * Generates the HPB message: downloads the bank keys. - * - * @param cfg handle to the configuration. - * @param clientKeys set of all the client keys. - * @return the raw EBICS HPB message. - */ -fun generateHpbMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { - val nonce = getNonce(128) - val doc = XmlBuilder.toDom("ebicsNoPubKeyDigestsRequest", "urn:org:ebics:H004") { - attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H004") - attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") - attr("Version", "H004") - attr("Revision", "1") - el("header") { - attr("authenticate", "true") - el("static") { - el("HostID", cfg.ebicsHostId) - el("Nonce", nonce.encodeUpHex()) - el("Timestamp", Instant.now().xmlDateTime()) - el("PartnerID", cfg.ebicsPartnerId) - el("UserID", cfg.ebicsUserId) - el("OrderDetails") { - el("OrderType", "HPB") - el("OrderAttribute", "DZHNN") - } - el("SecurityMedium", "0000") + /* ----- Helpers ----- */ + + private fun XmlBuilder.RSAKeyXml(key: RSAPrivateCrtKey) { + el("ns2:PubKeyValue") { + el("ds:RSAKeyValue") { + el("ds:Modulus", key.modulus.encodeBase64()) + el("ds:Exponent", key.publicExponent.encodeBase64()) } - el("mutable") } - el("AuthSignature") - el("body") } - XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key) - return XMLUtil.convertDomToBytes(doc) -} + + private fun XMLOrderData(cfg: EbicsSetupConfig, name: String, schema: String, build: XmlBuilder.() -> Unit): String { + return XmlBuilder.toBytes(name) { + attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") + attr("xmlns:ns2", schema) + build() + el("ns2:PartnerID", cfg.ebicsPartnerId) + el("ns2:UserID", cfg.ebicsUserId) + }.inputStream().deflate().encodeBase64() + } -class HpbResponseData( - val hostID: String, - val encryptionPubKey: RSAPublicKey, - val encryptionVersion: String, - val authenticationPubKey: RSAPublicKey, - val authenticationVersion: String -) + companion object { + fun parseResponse(doc: Document, clientEncryptionKey: RSAPrivateCrtKey): EbicsResponse<ByteArray?> { + return XmlDestructor.fromDoc(doc, "ebicsKeyManagementResponse") { + lateinit var technicalCode: EbicsReturnCode + lateinit var bankCode: EbicsReturnCode + var payload: ByteArray? = null + one("header") { + one("mutable") { + technicalCode = EbicsReturnCode.lookup(one("ReturnCode").text()) + } + } + one("body") { + bankCode = EbicsReturnCode.lookup(one("ReturnCode").text()) + payload = opt("DataTransfer") { + val descriptionInfo = one("DataEncryptionInfo") { + DataEncryptionInfo( + one("TransactionKey").text().decodeBase64(), + one("EncryptionPubKeyDigest").text().decodeBase64() + ) + } + decryptAndDecompressPayload( + clientEncryptionKey, + descriptionInfo, + listOf(one("OrderData").text()) + ).readBytes() + } + } + EbicsResponse( + technicalCode = technicalCode, + bankCode, + content = payload + ) + } + } -fun parseEbicsHpbOrder(orderDataRaw: InputStream): HpbResponseData { - return XmlDestructor.fromStream(orderDataRaw, "HPBResponseOrderData") { - val (authenticationPubKey, authenticationVersion) = one("AuthenticationPubKeyInfo") { - Pair( - one("PubKeyValue").one("RSAKeyValue") { + fun parseHpbOrder(data: ByteArray): Pair<RSAPublicKey, RSAPublicKey> { + return XmlDestructor.fromStream(data.inputStream(), "HPBResponseOrderData") { + val authPub = one("AuthenticationPubKeyInfo").one("PubKeyValue").one("RSAKeyValue") { CryptoUtil.loadRsaPublicKeyFromComponents( one("Modulus").text().decodeBase64(), one("Exponent").text().decodeBase64(), ) - }, - one("AuthenticationVersion").text() - ) - } - val (encryptionPubKey, encryptionVersion) = one("EncryptionPubKeyInfo") { - Pair( - one("PubKeyValue").one("RSAKeyValue") { + } + val encPub = one("EncryptionPubKeyInfo").one("PubKeyValue").one("RSAKeyValue") { CryptoUtil.loadRsaPublicKeyFromComponents( one("Modulus").text().decodeBase64(), one("Exponent").text().decodeBase64(), ) - }, - one("EncryptionVersion").text() - ) - + } + Pair(authPub, encPub) + } } - val hostID: String = one("HostID").text() - HpbResponseData( - hostID = hostID, - encryptionPubKey = encryptionPubKey, - encryptionVersion = encryptionVersion, - authenticationPubKey = authenticationPubKey, - authenticationVersion = authenticationVersion - ) } -} - -data class EbicsKeyManagementResponseContent( - val technicalReturnCode: EbicsReturnCode, - val bankReturnCode: EbicsReturnCode?, - val orderData: ByteArray? -)
\ No newline at end of file +}
\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt index d930104f..e4306ada 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt @@ -331,7 +331,7 @@ class Ebics3BTS( } companion object { - fun parseResponse(doc: Document): EbicsResponse { + fun parseResponse(doc: Document): EbicsResponse<BTSResponse> { return XmlDestructor.fromDoc(doc, "ebicsResponse") { var transactionID: String? = null var numSegments: Int? = null @@ -367,7 +367,7 @@ class Ebics3BTS( EbicsResponse( bankCode = bankCode, technicalCode = technicalCode, - content = EbicsResponseContent( + content = BTSResponse( transactionID = transactionID, orderID = orderID, payloadChunk = payloadChunk, @@ -381,26 +381,7 @@ class Ebics3BTS( } } - -data class EbicsResponse( - val technicalCode: EbicsReturnCode, - val bankCode: EbicsReturnCode, - private val content: EbicsResponseContent -) { - /** Checks that return codes are both EBICS_OK or throw an exception */ - fun okOrFail(phase: String): EbicsResponseContent { - logger.debug("$phase return codes: $technicalCode & $bankCode") - require(technicalCode.kind() != EbicsReturnCode.Kind.Error) { - "$phase has technical error: $technicalCode" - } - require(bankCode.kind() != EbicsReturnCode.Kind.Error) { - "$phase has bank error: $bankCode" - } - return content - } -} - -data class EbicsResponseContent( +data class BTSResponse( val transactionID: String?, val orderID: String?, val dataEncryptionInfo: DataEncryptionInfo?, 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 79fcb30b..63cac5cf 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt @@ -130,23 +130,12 @@ suspend fun HttpClient.postToBank(bankUrl: String, msg: ByteArray): Document { throw EbicsError.Transport("failed read bank response", e) } } - -/** - * POSTs raw EBICS XML to the bank and checks the two return codes: - * EBICS- and bank-technical. - * - * @param clientKeys client keys, used to sign the request. - * @param bankKeys bank keys, used to decrypt and validate the response. - * @param xmlReq raw EBICS request in XML. - * @param isEbics3 true in case the communication is EBICS 3, false - * @return [EbicsResponseContent] or throws [EbicsSideException] - */ -suspend fun postEbics( +suspend fun postBTS( client: HttpClient, cfg: EbicsSetupConfig, bankKeys: BankPublicKeysFile, xmlReq: ByteArray -): EbicsResponse { +): EbicsResponse<BTSResponse> { val doc = client.postToBank(cfg.hostBaseUrl, xmlReq) if (!XMLUtil.verifyEbicsDocument( doc, @@ -193,7 +182,7 @@ suspend fun ebicsDownload( // TODO find a way to cancel the pending transaction ? withContext(NonCancellable) { // Init phase - val initResp = postEbics(client, cfg, bankKeys, reqXml) + val initResp = postBTS(client, cfg, bankKeys, reqXml) if (initResp.bankCode == EbicsReturnCode.EBICS_NO_DOWNLOAD_DATA_AVAILABLE) { logger.debug("Download content is empty") return@withContext @@ -216,12 +205,8 @@ suspend fun ebicsDownload( /** Send download receipt */ suspend fun receipt(success: Boolean) { - postEbics( - client, - cfg, - bankKeys, - impl.downloadReceipt(tId, success) - ).okOrFail("Download receipt phase") + val xml = impl.downloadReceipt(tId, success) + postBTS(client, cfg, bankKeys, xml).okOrFail("Download receipt phase") } /** Throw if parent scope have been canceled */ suspend fun checkCancellation() { @@ -238,7 +223,7 @@ suspend fun ebicsDownload( for (x in 2 .. howManySegments) { checkCancellation() val transReq = impl.downloadTransfer(x, howManySegments, tId) - val transResp = postEbics(client, cfg, bankKeys, transReq).okOrFail("Download transfer phase") + val transResp = postBTS(client, cfg, bankKeys, transReq).okOrFail("Download transfer phase") val chunk = requireNotNull(transResp.payloadChunk) { "Download transfer phase: missing encrypted chunk" } @@ -349,14 +334,14 @@ suspend fun doEbicsUpload( // Init phase val initXml = impl.uploadInitialization(service, preparedPayload) - val initResp = postEbics(client, cfg, bankKeys, initXml).okOrFail("Upload init phase") + val initResp = postBTS(client, cfg, bankKeys, initXml).okOrFail("Upload init phase") val tId = requireNotNull(initResp.transactionID) { "Upload init phase: missing transaction ID" } // Transfer phase val transferXml = impl.uploadTransfer(tId, preparedPayload) - val transferResp = postEbics(client, cfg, bankKeys, transferXml).okOrFail("Upload transfer phase") + val transferResp = postBTS(client, cfg, bankKeys, transferXml).okOrFail("Upload transfer phase") val orderId = requireNotNull(transferResp.orderID) { "Upload transfer phase: missing order ID" } @@ -424,6 +409,24 @@ suspend fun submitPain001( ) } +class EbicsResponse<T>( + val technicalCode: EbicsReturnCode, + val bankCode: EbicsReturnCode, + private val content: T +) { + /** Checks that return codes are both EBICS_OK or throw an exception */ + fun okOrFail(phase: String): T { + logger.debug("$phase return codes: $technicalCode & $bankCode") + require(technicalCode.kind() != EbicsReturnCode.Kind.Error) { + "$phase has technical error: $technicalCode" + } + require(bankCode.kind() != EbicsReturnCode.Kind.Error) { + "$phase has bank error: $bankCode" + } + return content + } +} + // TODO import missing using a script @Suppress("SpellCheckingInspection") enum class EbicsReturnCode(val code: String) { |