diff options
author | Antoine A <> | 2024-03-07 22:10:10 +0100 |
---|---|---|
committer | Antoine A <> | 2024-03-07 22:10:10 +0100 |
commit | 867490864a27a7a74b122c5a6fb3cd34fb3ec8ae (patch) | |
tree | 57310c37921b18a683fe8b64c02026968218a733 | |
parent | c7344ce7d8466aa6d36d428dd730978d15ceb720 (diff) | |
download | libeufin-867490864a27a7a74b122c5a6fb3cd34fb3ec8ae.tar.gz libeufin-867490864a27a7a74b122c5a6fb3cd34fb3ec8ae.tar.bz2 libeufin-867490864a27a7a74b122c5a6fb3cd34fb3ec8ae.zip |
Replace all Ebics2 JAXB logic with custom XML DSL
-rw-r--r-- | ebics/src/main/kotlin/Ebics.kt | 1 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt | 8 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt | 175 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt | 49 | ||||
-rw-r--r-- | testbench/src/main/kotlin/Main.kt | 3 |
5 files changed, 184 insertions, 52 deletions
diff --git a/ebics/src/main/kotlin/Ebics.kt b/ebics/src/main/kotlin/Ebics.kt index e3ec9175..d0679c37 100644 --- a/ebics/src/main/kotlin/Ebics.kt +++ b/ebics/src/main/kotlin/Ebics.kt @@ -232,6 +232,7 @@ enum class EbicsReturnCode(val errorCode: String) { EBICS_INTERNAL_ERROR("061099"), EBICS_TX_RECOVERY_SYNC("061101"), EBICS_AUTHORISATION_ORDER_IDENTIFIER_FAILED("090003"), + EBICS_INVALID_ORDER_DATA_FORMAT("090004"), EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005"), EBICS_INVALID_USER_OR_USER_STATE("091002"), EBICS_USER_UNKNOWN("091003"), diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt index a9e595f4..d0e1a7f3 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -23,6 +23,7 @@ import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.groups.* import com.github.ajalt.clikt.parameters.options.* import io.ktor.client.* +import io.ktor.client.plugins.* import tech.libeufin.common.* import tech.libeufin.common.crypto.* import tech.libeufin.ebics.* @@ -231,7 +232,12 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { val cfg = extractEbicsConfig(common.config) // Config is sane. Go (maybe) making the private keys. val clientKeys = loadOrGenerateClientKeys(cfg.clientPrivateKeysFilename) - val httpClient = HttpClient() + val httpClient = HttpClient { + install(HttpTimeout) { + // It can take a lot of time for the bank to generate documents + socketTimeoutMillis = 5 * 60 * 1000 + } + } // Privs exist. Upload their pubs val keysNotSub = !clientKeys.submitted_ini if ((!clientKeys.submitted_ini) || forceKeysResubmission) 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 1c791886..3de5632e 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt @@ -25,6 +25,7 @@ package tech.libeufin.nexus.ebics import org.slf4j.Logger import org.slf4j.LoggerFactory +import tech.libeufin.common.* import tech.libeufin.ebics.* import tech.libeufin.ebics.ebics_h004.EbicsKeyManagementResponse import tech.libeufin.ebics.ebics_h004.EbicsNpkdRequest @@ -57,32 +58,54 @@ fun parseKeysMgmtResponse( clientEncryptionKey: RSAPrivateCrtKey, xml: InputStream ): EbicsKeyManagementResponseContent? { - // TODO throw instead of null - val jaxb = try { - XMLUtil.convertToJaxb<EbicsKeyManagementResponse>(xml) - } catch (e: Exception) { - tech.libeufin.nexus.logger.error("Could not parse the raw response from bank into JAXB.") - return null - } - var payload: ByteArray? = null - jaxb.value.body.dataTransfer?.dataEncryptionInfo.apply { - // non-null indicates that an encrypted payload should be found. - if (this != null) { - val encOrderData = jaxb.value.body.dataTransfer?.orderData?.value - if (encOrderData == null) { - tech.libeufin.nexus.logger.error("Despite a non-null DataEncryptionInfo, OrderData could not be found, can't decrypt any payload!") - return null + return XmlDestructor.fromStream(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() + } + } + one("body") { + bankReturnCode = 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() } - payload = decryptAndDecompressPayload( - clientEncryptionKey, - DataEncryptionInfo(this.transactionKey, this.encryptionPubKeyDigest.value), - listOf(encOrderData) - ).readBytes() + } + EbicsKeyManagementResponseContent(technicalReturnCode, bankReturnCode, payload) + } +} + +private fun XmlBuilder.RSAKeyXml(key: RSAPrivateCrtKey) { + el("ns2:PubKeyValue") { + el("ds:RSAKeyValue") { + el("ds:Modulus", key.modulus.toByteArray().encodeBase64()) + el("ds:Exponent", key.publicExponent.toByteArray().encodeBase64()) } } - val bankReturnCode = EbicsReturnCode.lookup(jaxb.value.body.returnCode.value) // business error - val ebicsReturnCode = EbicsReturnCode.lookup(jaxb.value.header.mutable.returnCode) // ebics error - return EbicsKeyManagementResponseContent(ebicsReturnCode, bankReturnCode, payload) +} + +private fun XMLOrderData(cfg: EbicsSetupConfig, name: String, schema: String, build: XmlBuilder.() -> Unit): String { + return XmlBuilder.toString(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) + }.toByteArray().inputStream().deflate().readAllBytes().encodeBase64() // TODO opti } /** @@ -93,13 +116,33 @@ fun parseKeysMgmtResponse( * @return the raw EBICS INI message. */ fun generateIniMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { - val iniRequest = EbicsUnsecuredRequest.createIni( - cfg.ebicsHostId, - cfg.ebicsUserId, - cfg.ebicsPartnerId, - clientKeys.signature_private_key - ) - val doc = XMLUtil.convertJaxbToDocument(iniRequest) + val inner = XMLOrderData(cfg, "ns2:SignaturePubKeyOrderData", "http://www.ebics.org/S001") { + el("ns2:SignaturePubKeyInfo") { + RSAKeyXml(clientKeys.signature_private_key) + el("ns2:SignatureVersion", "A006") + } + } + 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") + } + el("mutable") + } + el("body/DataTransfer/OrderData", inner) + } return XMLUtil.convertDomToBytes(doc) } @@ -112,14 +155,37 @@ fun generateIniMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile) * @return the raw EBICS HIA message. */ fun generateHiaMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { - val hiaRequest = EbicsUnsecuredRequest.createHia( - cfg.ebicsHostId, - cfg.ebicsUserId, - cfg.ebicsPartnerId, - clientKeys.authentication_private_key, - clientKeys.encryption_private_key - ) - val doc = XMLUtil.convertJaxbToDocument(hiaRequest) + 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") + } + el("SecurityMedium", "0200") + } + el("mutable") + } + el("body/DataTransfer/OrderData", inner) + } return XMLUtil.convertDomToBytes(doc) } @@ -131,14 +197,31 @@ fun generateHiaMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile) * @return the raw EBICS HPB message. */ fun generateHpbMessage(cfg: EbicsSetupConfig, clientKeys: ClientPrivateKeysFile): ByteArray { - val hpbRequest = EbicsNpkdRequest.createRequest( - cfg.ebicsHostId, - cfg.ebicsPartnerId, - cfg.ebicsUserId, - getNonce(128), - DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()) - ) - val doc = XMLUtil.convertJaxbToDocument(hpbRequest) + 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.toHexString()) + 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("mutable") + } + el("AuthSignature") + el("body") + } XMLUtil.signEbicsDocument(doc, clientKeys.authentication_private_key) return XMLUtil.convertDomToBytes(doc) }
\ 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 deb25360..e7df9c83 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt @@ -52,6 +52,51 @@ data class Ebics3Service( val container: String? ) + + +fun iniRequest( + cfg: EbicsSetupConfig, + clientKeys: ClientPrivateKeysFile +): ByteArray { + val temp = XmlBuilder.toString("ns2:SignaturePubKeyOrderData") { + attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") + attr("xmlns:ns2", "http://www.ebics.org/S001") + el("ns2:SignaturePubKeyInfo") { + el("ns2:PubKeyValue") { + el("ds:RSAKeyValue") { + el("ds:Modulus", clientKeys.signature_private_key.modulus.toByteArray().encodeBase64()) + el("ds:Exponent", clientKeys.signature_private_key.publicExponent.toByteArray().encodeBase64()) + } + } + el("ns2:SignatureVersion", "A006") + } + el("ns2:PartnerID", cfg.ebicsPartnerId) + el("ns2:UserID", cfg.ebicsUserId) + } + // TODO in ebics:H005 we MUST use x509 certificates ... + println(temp) + val inner = temp.toByteArray().inputStream().deflate().readAllBytes().encodeBase64() + val doc = XmlBuilder.toDom("ebicsUnsecuredRequest", "urn:org:ebics:H005") { + attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H005") + attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") + attr("Version", "H005") + attr("Revision", "1") + el("header") { + attr("authenticate", "true") + el("static") { + el("HostID", cfg.ebicsHostId) + el("PartnerID", cfg.ebicsPartnerId) + el("UserID", cfg.ebicsUserId) + el("OrderDetails/AdminOrderType", "INI") + el("SecurityMedium", "0200") + } + el("mutable") + } + el("body/DataTransfer/OrderData", inner) + } + return XMLUtil.convertDomToBytes(doc) +} + class Ebics3Impl( private val cfg: EbicsSetupConfig, private val bankKeys: BankPublicKeysFile, @@ -62,8 +107,6 @@ class Ebics3Impl( val doc = XmlBuilder.toDom("ebicsRequest", "urn:org:ebics:H005") { attr("http://www.w3.org/2000/xmlns/", "xmlns", "urn:org:ebics:H005") attr("http://www.w3.org/2000/xmlns/", "xmlns:ds", "http://www.w3.org/2000/09/xmldsig#") - attr("http://www.w3.org/2000/xmlns/", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") - attr("http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", "urn:org:ebics:H005 ebics_request_H005.xsd") attr("Version", "H005") attr("Revision", "1") lambda() @@ -182,7 +225,7 @@ class Ebics3Impl( SupportedDocument.CAMT_054 -> Pair("BTD", Ebics3Service("REP", "CH", "camt.054", "08", "ZIP")) SupportedDocument.PAIN_002_LOGS -> Pair("HAC", null) } - return downloadInitialization(orderType, service) + return downloadInitialization(orderType, service, startDate, endDate) } fun downloadInitialization(orderType: String, service: Ebics3Service? = null, startDate: Instant? = null, endDate: Instant? = null): ByteArray { diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt index cf50c59d..1c21d8da 100644 --- a/testbench/src/main/kotlin/Main.kt +++ b/testbench/src/main/kotlin/Main.kt @@ -121,8 +121,7 @@ class Cli : CliktCommand("Run integration tests on banks provider") { runBlocking { step("Init ${kind.name}") - if (ask("Reset DB ? y/n>") == "y") nexusCmd.test("dbinit -r $flags").assertOk() - else nexusCmd.test("dbinit $flags").assertOk() + nexusCmd.test("dbinit $flags").assertOk() val cmds = buildMap<String, suspend () -> Unit> { put("reset-db", suspend { |