summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-03-08 17:58:26 +0100
committerAntoine A <>2024-03-08 17:58:26 +0100
commit298557c74a4fa8112cb4c8b0be644496d77e74a1 (patch)
tree1c124c5e2d259864219b89bc02446a11c2628218
parent8b0ca3dbd7359a402b1671a7cb0fa37b92543d1e (diff)
downloadlibeufin-298557c74a4fa8112cb4c8b0be644496d77e74a1.tar.gz
libeufin-298557c74a4fa8112cb4c8b0be644496d77e74a1.tar.bz2
libeufin-298557c74a4fa8112cb4c8b0be644496d77e74a1.zip
More simplification & refactoring
-rw-r--r--common/src/main/kotlin/crypto/utils.kt32
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt94
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt346
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt25
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt49
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) {