libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 197e69071b66347e4eccab3a6bba72eaef50826d
parent 0800e6a51177dcfa6d15c4ff6dca7362603c10d4
Author: Florian Dold <florian.dold@gmail.com>
Date:   Tue,  5 Nov 2019 00:23:35 +0100

implement HPB

Diffstat:
Asandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt | 44++++++++++++++++++++++++++++++++++++++++++++
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 402+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsMessages.kt | 36+++++++++++++++++++++++++++++++++++-
3 files changed, 276 insertions(+), 206 deletions(-)

diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt @@ -0,0 +1,44 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2019 Stanisci and Dold. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +package tech.libeufin.sandbox + +import java.util.zip.DeflaterInputStream +import java.util.zip.InflaterInputStream + +/** + * Helpers for dealing with order compression, encryption, decryption, chunking and re-assembly. + */ +class EbicsOrderUtil private constructor() { + companion object { + inline fun <reified T> decodeOrderDataXml(encodedOrderData: ByteArray): T { + return InflaterInputStream(encodedOrderData.inputStream()).use { + val bytes = it.readAllBytes() + XMLUtil.convertStringToJaxb<T>(bytes.toString(Charsets.UTF_8)).value + } + } + + inline fun <reified T>encodeOrderDataXml(obj: T): ByteArray { + val bytes = XMLUtil.convertJaxbToString(obj).toByteArray() + return DeflaterInputStream(bytes.inputStream()).use { + it.readAllBytes() + } + } + } +} diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -39,34 +39,36 @@ import io.ktor.routing.post import io.ktor.routing.routing import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty +import org.apache.xml.security.binding.xmldsig.RSAKeyValueType import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.Logger import org.slf4j.LoggerFactory import org.w3c.dom.Document -import tech.libeufin.schema.ebics_h004.EbicsKeyManagementResponse -import tech.libeufin.schema.ebics_h004.EbicsNoPubKeyDigestsRequest -import tech.libeufin.schema.ebics_h004.EbicsUnsecuredRequest -import tech.libeufin.schema.ebics_h004.HIARequestOrderDataType +import tech.libeufin.schema.ebics_h004.* import tech.libeufin.schema.ebics_hev.HEVResponse import tech.libeufin.schema.ebics_hev.SystemReturnCodeType import tech.libeufin.schema.ebics_s001.SignaturePubKeyOrderData -import java.nio.charset.StandardCharsets.US_ASCII -import java.nio.charset.StandardCharsets.UTF_8 import java.security.interfaces.RSAPublicKey import java.text.DateFormat -import java.util.zip.InflaterInputStream import javax.sql.rowset.serial.SerialBlob +import javax.xml.bind.JAXBContext +import javax.xml.datatype.XMLGregorianCalendar val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox") data class EbicsRequestError(val statusCode: HttpStatusCode) : Exception("Ebics request error") +open class EbicsKeyManagementError(val errorText: String, val errorCode: String) : + Exception("EBICS key management error: $errorText ($errorCode)") + +class EbicsInvalidXmlError : EbicsKeyManagementError("[EBICS_INVALID_XML]", "091010") + private suspend fun ApplicationCall.respondEbicsKeyManagement( errorText: String, errorCode: String, - statusCode: HttpStatusCode, bankReturnCode: String, + dataTransfer: CryptoUtil.EncryptionResult? = null, orderId: String? = null ) { val responseXml = EbicsKeyManagementResponse().apply { @@ -87,16 +89,27 @@ private suspend fun ApplicationCall.respondEbicsKeyManagement( this.authenticate = true this.value = bankReturnCode } + if (dataTransfer != null) { + this.dataTransfer = EbicsKeyManagementResponse.Body.DataTransfer().apply { + this.dataEncryptionInfo = DataEncryptionInfo().apply { + this.authenticate = true + this.transactionKey = dataTransfer.encryptedTransactionKey + this.encryptionPubKeyDigest = DataEncryptionInfo.EncryptionPubKeyDigest().apply { + this.algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" + this.version = "E002" + this.value = dataTransfer.pubKeyDigest + } + } + this.orderData = EbicsResponse.Body.DataTransferResponseType.OrderData().apply { + this.value = dataTransfer.encryptedData + } + } + } } } val text = XMLUtil.convertJaxbToString(responseXml) logger.info("responding with:\n${text}") - respondText(text, ContentType.Application.Xml, statusCode) -} - - -private suspend fun ApplicationCall.respondEbicsInvalidXml() { - respondEbicsKeyManagement("[EBICS_INVALID_XML]", "091010", HttpStatusCode.BadRequest, "000000") + respondText(text, ContentType.Application.Xml, HttpStatusCode.OK) } @@ -114,196 +127,200 @@ fun findEbicsSubscriber(partnerID: String, userID: String, systemID: String?): E }.firstOrNull() } + +data class Subscriber( + val partnerID: String, + val userID: String, + val systemID: String? +) + data class SubscriberKeys( val authenticationPublicKey: RSAPublicKey, val encryptionPublicKey: RSAPublicKey, val signaturePublicKey: RSAPublicKey ) -private suspend fun ApplicationCall.ebicsweb() { - val body: String = receiveText() - logger.debug("Data received: $body") - - val bodyDocument: Document? = XMLUtil.parseStringIntoDom(body) - if (bodyDocument == null || (!XMLUtil.validateFromDom(bodyDocument))) { - respondEbicsInvalidXml() - return - } +data class EbicsHostInfo( + val hostID: String, + val encryptionPublicKey: RSAPublicKey, + val authenticationPublicKey: RSAPublicKey +) - logger.info("Processing ${bodyDocument.documentElement.localName}") - when (bodyDocument.documentElement.localName) { - "ebicsUnsecuredRequest" -> { +private suspend fun ApplicationCall.handleEbicsHia(header: EbicsUnsecuredRequest.Header, orderData: ByteArray) { + val keyObject = EbicsOrderUtil.decodeOrderDataXml<HIARequestOrderDataType>(orderData) + val encPubXml = keyObject.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue + val authPubXml = keyObject.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue + val encPub = CryptoUtil.loadRsaPublicKeyFromComponents(encPubXml.modulus, encPubXml.exponent) + val authPub = CryptoUtil.loadRsaPublicKeyFromComponents(authPubXml.modulus, authPubXml.exponent) - val bodyJaxb = XMLUtil.convertDomToJaxb( - EbicsUnsecuredRequest::class.java, - bodyDocument - ) + transaction { + val ebicsSubscriber = findEbicsSubscriber(header.static.partnerID, header.static.userID, header.static.systemID) + if (ebicsSubscriber == null) { + logger.warn("ebics subscriber not found") + throw EbicsRequestError(HttpStatusCode.NotFound) + } + ebicsSubscriber.authenticationKey = EbicsPublicKey.new { + this.rsaPublicKey = SerialBlob(authPub.encoded) + state = KeyState.NEW + } + ebicsSubscriber.encryptionKey = EbicsPublicKey.new { + this.rsaPublicKey = SerialBlob(encPub.encoded) + state = KeyState.NEW + } + ebicsSubscriber.state = when (ebicsSubscriber.state) { + SubscriberState.NEW -> SubscriberState.PARTIALLY_INITIALIZED_HIA + SubscriberState.PARTIALLY_INITIALIZED_INI -> SubscriberState.INITIALIZED + else -> ebicsSubscriber.state + } + } + respondEbicsKeyManagement("[EBICS_OK]", "000000", "000000") +} - val staticHeader = bodyJaxb.value.header.static - val requestHostID = bodyJaxb.value.header.static.hostID - val ebicsHost = transaction { - EbicsHost.find { EbicsHosts.hostID eq requestHostID }.firstOrNull() - } +private suspend fun ApplicationCall.handleEbicsIni(header: EbicsUnsecuredRequest.Header, orderData: ByteArray) { + val keyObject = EbicsOrderUtil.decodeOrderDataXml<SignaturePubKeyOrderData>(orderData) + val sigPubXml = keyObject.signaturePubKeyInfo.pubKeyValue.rsaKeyValue + val sigPub = CryptoUtil.loadRsaPublicKeyFromComponents(sigPubXml.modulus, sigPubXml.exponent) - if (ebicsHost == null) { - logger.warn("client requested unknown HostID") - respondEbicsKeyManagement("[EBICS_INVALID_HOST_ID]", "091011", HttpStatusCode.NotFound, "000000") - return - } + transaction { + val ebicsSubscriber = + findEbicsSubscriber(header.static.partnerID, header.static.userID, header.static.systemID) + if (ebicsSubscriber == null) { + logger.warn("ebics subscriber ('${header.static.partnerID}' / '${header.static.userID}' / '${header.static.systemID}') not found") + throw EbicsRequestError(HttpStatusCode.NotFound) + } + ebicsSubscriber.signatureKey = EbicsPublicKey.new { + this.rsaPublicKey = SerialBlob(sigPub.encoded) + state = KeyState.NEW + } + ebicsSubscriber.state = when (ebicsSubscriber.state) { + SubscriberState.NEW -> SubscriberState.PARTIALLY_INITIALIZED_INI + SubscriberState.PARTIALLY_INITIALIZED_HIA -> SubscriberState.INITIALIZED + else -> ebicsSubscriber.state + } + } + logger.info("Signature key inserted in database _and_ subscriber state changed accordingly") + respondEbicsKeyManagement("[EBICS_OK]", "000000", bankReturnCode = "000000", orderId = "OR01") +} - logger.info("Serving a ${bodyJaxb.value.header.static.orderDetails.orderType} request") - - /** - * NOTE: the JAXB interface has some automagic mechanism that decodes - * the Base64 string into its byte[] form _at the same time_ it instantiates - * the object; in other words, there is no need to perform here the decoding. - */ - val zkey = bodyJaxb.value.body.dataTransfer.orderData.value - - /** - * The validation enforces zkey to be a base64 value, but does not check - * whether it is given _empty_ or not; will check explicitly here. FIXME: - * shall the schema be patched to avoid having this if-block here? - */ - if (zkey.isEmpty()) { - logger.info("0-length key element given, invalid request") - respondEbicsInvalidXml() - return +private suspend fun ApplicationCall.handleEbicsHpb( + ebicsHostInfo: EbicsHostInfo, + requestDocument: Document, + header: EbicsNoPubKeyDigestsRequest.Header +) { + val subscriberKeys = transaction { + val ebicsSubscriber = + findEbicsSubscriber(header.static.partnerID, header.static.userID, header.static.systemID) + if (ebicsSubscriber == null) { + throw EbicsRequestError(HttpStatusCode.Unauthorized) + } + if (ebicsSubscriber.state != SubscriberState.INITIALIZED) { + throw EbicsRequestError(HttpStatusCode.Forbidden) + } + val authPubBlob = ebicsSubscriber.authenticationKey!!.rsaPublicKey + val encPubBlob = ebicsSubscriber.encryptionKey!!.rsaPublicKey + val sigPubBlob = ebicsSubscriber.signatureKey!!.rsaPublicKey + SubscriberKeys( + CryptoUtil.loadRsaPublicKey(authPubBlob.toByteArray()), + CryptoUtil.loadRsaPublicKey(encPubBlob.toByteArray()), + CryptoUtil.loadRsaPublicKey(sigPubBlob.toByteArray()) + ) + } + val validationResult = + XMLUtil.verifyEbicsDocument(requestDocument, subscriberKeys.authenticationPublicKey) + logger.info("validationResult: $validationResult") + if (!validationResult) { + throw EbicsKeyManagementError("invalid signature", "90000"); + } + val hpbRespondeData = HPBResponseOrderData().apply { + this.authenticationPubKeyInfo = AuthenticationPubKeyInfoType().apply { + this.authenticationVersion = "X002" + this.pubKeyValue = PubKeyValueType().apply { + this.rsaKeyValue = RSAKeyValueType().apply { + this.exponent = ebicsHostInfo.authenticationPublicKey.publicExponent.toByteArray() + this.modulus = ebicsHostInfo.authenticationPublicKey.modulus.toByteArray() + } } - - /** - * This value holds the bytes[] of a XML "SignaturePubKeyOrderData" document - * and at this point is valid and _never_ empty. - */ - val inflater = InflaterInputStream(zkey.inputStream()) - - var payload = try { - ByteArray(1) { inflater.read().toByte() } - } catch (e: Exception) { - e.printStackTrace() - respondEbicsInvalidXml() - return + } + this.encryptionPubKeyInfo = EncryptionPubKeyInfoType().apply { + this.encryptionVersion = "E002" + this.pubKeyValue = PubKeyValueType().apply { + this.rsaKeyValue = RSAKeyValueType().apply { + this.exponent = ebicsHostInfo.encryptionPublicKey.publicExponent.toByteArray() + this.modulus = ebicsHostInfo.encryptionPublicKey.modulus.toByteArray() + } } + } + this.hostID = ebicsHostInfo.hostID + } - while (inflater.available() == 1) { - payload += inflater.read().toByte() - } + val compressedOrderData = EbicsOrderUtil.encodeOrderDataXml(hpbRespondeData) - inflater.close() + val encryptionResult = CryptoUtil.encryptEbicsE002(compressedOrderData, subscriberKeys.encryptionPublicKey) - logger.debug("Found payload: ${payload.toString(US_ASCII)}") + respondEbicsKeyManagement("[EBICS_OK]", "000000", "000000", encryptionResult, "OR01") +} - when (bodyJaxb.value.header.static.orderDetails.orderType) { - "INI" -> { - val keyObject = XMLUtil.convertStringToJaxb<SignaturePubKeyOrderData>(payload.toString(UTF_8)) +/** + * Find the ebics host corresponding to the one specified in the header. + */ +private fun ApplicationCall.ensureEbicsHost(requestHostID: String): EbicsHostInfo { + return transaction { + val ebicsHost = EbicsHost.find { EbicsHosts.hostID eq requestHostID }.firstOrNull() + if (ebicsHost == null) { + logger.warn("client requested unknown HostID") + throw EbicsKeyManagementError("[EBICS_INVALID_HOST_ID]", "091011") + } + val encryptionPrivateKey = CryptoUtil.loadRsaPrivateKey(ebicsHost.encryptionPrivateKey.toByteArray()) + val authenticationPrivateKey = CryptoUtil.loadRsaPrivateKey(ebicsHost.authenticationPrivateKey.toByteArray()) + EbicsHostInfo( + requestHostID, + CryptoUtil.getRsaPublicFromPrivate(encryptionPrivateKey), + CryptoUtil.getRsaPublicFromPrivate(authenticationPrivateKey) + ) + } +} - val rsaPublicKey: RSAPublicKey = try { - CryptoUtil.loadRsaPublicKeyFromComponents( - keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.modulus, - keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.exponent - ) - } catch (e: Exception) { - logger.info("User gave bad key, not storing it") - e.printStackTrace() - respondEbicsInvalidXml() - return - } - // put try-catch block here? (FIXME) - transaction { - val ebicsSubscriber = - findEbicsSubscriber(staticHeader.partnerID, staticHeader.userID, staticHeader.systemID) - if (ebicsSubscriber == null) { - logger.warn("ebics subscriber ('${staticHeader.partnerID}' / '${staticHeader.userID}' / '${staticHeader.systemID}') not found") - throw EbicsRequestError(HttpStatusCode.NotFound) - } - ebicsSubscriber.signatureKey = EbicsPublicKey.new { - this.rsaPublicKey = SerialBlob(rsaPublicKey.encoded) - state = KeyState.NEW - } +private suspend fun ApplicationCall.receiveEbicsXml(): Document { + val body: String = receiveText() + logger.debug("Data received: $body") + val requestDocument: Document? = XMLUtil.parseStringIntoDom(body) + if (requestDocument == null || (!XMLUtil.validateFromDom(requestDocument))) { + throw EbicsInvalidXmlError() + } + return requestDocument +} - if (ebicsSubscriber.state == SubscriberState.NEW) { - ebicsSubscriber.state = SubscriberState.PARTIALLY_INITIALIZED_INI - } - if (ebicsSubscriber.state == SubscriberState.PARTIALLY_INITIALIZED_HIA) { - ebicsSubscriber.state = SubscriberState.INITIALIZED - } - } +inline fun <reified T> Document.toObject(): T { + val jc = JAXBContext.newInstance(T::class.java) + val m = jc.createUnmarshaller() + return m.unmarshal(this, T::class.java).value +} - logger.info("Signature key inserted in database _and_ subscriber state changed accordingly") - respondEbicsKeyManagement( - "[EBICS_OK]", - "000000", - HttpStatusCode.OK, - bankReturnCode = "000000", - orderId = "OR01" - ) - return - } - "HIA" -> { - val keyObject = XMLUtil.convertStringToJaxb<HIARequestOrderDataType>(payload.toString(US_ASCII)) - - val authenticationPublicKey = try { - CryptoUtil.loadRsaPublicKeyFromComponents( - keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.modulus, - keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.exponent - ) - } catch (e: Exception) { - logger.info("auth public key invalid") - e.printStackTrace() - respondEbicsInvalidXml() - return - } +private suspend fun ApplicationCall.ebicsweb() { + val requestDocument = receiveEbicsXml() - val encryptionPublicKey = try { - CryptoUtil.loadRsaPublicKeyFromComponents( - keyObject.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.modulus, - keyObject.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.exponent - ) - } catch (e: Exception) { - logger.info("auth public key invalid") - e.printStackTrace() - respondEbicsInvalidXml() - return - } + logger.info("Processing ${requestDocument.documentElement.localName}") - transaction { - val ebicsSubscriber = - findEbicsSubscriber(staticHeader.partnerID, staticHeader.userID, staticHeader.systemID) - if (ebicsSubscriber == null) { - logger.warn("ebics subscriber not found") - throw EbicsRequestError(HttpStatusCode.NotFound) - } - ebicsSubscriber.authenticationKey = EbicsPublicKey.new { - this.rsaPublicKey = SerialBlob(authenticationPublicKey.encoded) - state = KeyState.NEW - } - ebicsSubscriber.encryptionKey = EbicsPublicKey.new { - this.rsaPublicKey = SerialBlob(encryptionPublicKey.encoded) - state = KeyState.NEW - } + when (requestDocument.documentElement.localName) { + "ebicsUnsecuredRequest" -> { + val requestObject = requestDocument.toObject<EbicsUnsecuredRequest>() + logger.info("Serving a ${requestObject.header.static.orderDetails.orderType} request") - if (ebicsSubscriber.state == SubscriberState.NEW) { - ebicsSubscriber.state = SubscriberState.PARTIALLY_INITIALIZED_HIA - } + val orderData = requestObject.body.dataTransfer.orderData.value + val header = requestObject.header - if (ebicsSubscriber.state == SubscriberState.PARTIALLY_INITIALIZED_INI) { - ebicsSubscriber.state = SubscriberState.INITIALIZED - } - } - respondEbicsKeyManagement("[EBICS_OK]", "000000", HttpStatusCode.OK, "000000") - return - } + when (header.static.orderDetails.orderType) { + "INI" -> handleEbicsIni(header, orderData) + "HIA" -> handleEbicsHia(header, orderData) + else -> throw EbicsInvalidXmlError() } - - throw AssertionError("not reached") } - "ebicsHEVRequest" -> { val hevResponse = HEVResponse().apply { this.systemReturnCode = SystemReturnCodeType().apply { @@ -315,41 +332,17 @@ private suspend fun ApplicationCall.ebicsweb() { val strResp = XMLUtil.convertJaxbToString(hevResponse) respondText(strResp, ContentType.Application.Xml, HttpStatusCode.OK) - return } "ebicsNoPubKeyDigestsRequest" -> { - val requestJaxb = XMLUtil.convertDomToJaxb(EbicsNoPubKeyDigestsRequest::class.java, bodyDocument) - val staticHeader = requestJaxb.value.header.static - when (val orderType = staticHeader.orderDetails.orderType) { - "HPB" -> { - val subscriberKeys = transaction { - val ebicsSubscriber = - findEbicsSubscriber(staticHeader.partnerID, staticHeader.userID, staticHeader.systemID) - if (ebicsSubscriber == null) { - throw EbicsRequestError(HttpStatusCode.Unauthorized) - } - if (ebicsSubscriber.state != SubscriberState.INITIALIZED) { - throw EbicsRequestError(HttpStatusCode.Forbidden) - } - val authPubBlob = ebicsSubscriber.authenticationKey!!.rsaPublicKey - val encPubBlob = ebicsSubscriber.encryptionKey!!.rsaPublicKey - val sigPubBlob = ebicsSubscriber.signatureKey!!.rsaPublicKey - SubscriberKeys( - CryptoUtil.loadRsaPublicKey(authPubBlob.toByteArray()), - CryptoUtil.loadRsaPublicKey(encPubBlob.toByteArray()), - CryptoUtil.loadRsaPublicKey(sigPubBlob.toByteArray()) - ) - } - val validationResult = - XMLUtil.verifyEbicsDocument(bodyDocument, subscriberKeys.authenticationPublicKey) - logger.info("validationResult: $validationResult") - } - else -> { - logger.warn("order type '${orderType}' not supported for ebicsNoPubKeyDigestsRequest") - respondEbicsInvalidXml() - } + val requestObject = requestDocument.toObject<EbicsNoPubKeyDigestsRequest>() + val hostInfo = ensureEbicsHost(requestObject.header.static.hostID) + when (requestObject.header.static.orderDetails.orderType) { + "HPB" -> handleEbicsHpb(hostInfo, requestDocument, requestObject.header) + else -> throw EbicsInvalidXmlError() } } + "ebicsRequest" -> { + } else -> { /* Log to console and return "unknown type" */ logger.info("Unknown message, just logging it!") @@ -357,7 +350,6 @@ private suspend fun ApplicationCall.ebicsweb() { HttpStatusCode.NotImplemented, SandboxError("Not Implemented") ) - return } } } @@ -396,7 +388,7 @@ fun main() { } install(StatusPages) { exception<Throwable> { cause -> - logger.error("Exception while handling '${call.request.uri.toString()}'", cause) + logger.error("Exception while handling '${call.request.uri}'", cause) call.respondText("Internal server error.", ContentType.Text.Plain, HttpStatusCode.InternalServerError) } } diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsMessages.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsMessages.kt @@ -182,6 +182,9 @@ class DataEncryptionInfo { @XmlAccessorType(XmlAccessType.NONE) class EncryptionPubKeyDigest { + /** + * Version of the *digest* of the public key. + */ @get:XmlAttribute(name = "Version", required = true) @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) lateinit var version: String @@ -511,7 +514,7 @@ class EbicsKeyManagementResponse { @XmlType(name = "", propOrder = ["dataTransfer", "returnCode", "timestampBankParameter"]) class Body { @get:XmlElement(name = "DataTransfer") - val dataTransfer: DataTransfer? = null + var dataTransfer: DataTransfer? = null @get:XmlElement(name = "ReturnCode", required = true) lateinit var returnCode: ReturnCode @@ -597,4 +600,35 @@ class HPBResponseOrderData { @get:XmlElement(name = "HostID", required = true) @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) lateinit var hostID: String +} + + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["partnerInfo", "userInfo"]) +@XmlRootElement(name = "HTDResponseOrderData") +class HTDesponseOrderData { + @get:XmlElement(name = "PartnerInfo", required = true) + lateinit var partnerInfo: PartnerInfo + + @get:XmlElement(name = "UserInfo", required = true) + lateinit var userInfo: UserInfo + + @XmlAccessorType(XmlAccessType.NONE) + class PartnerInfo { + + } + + @XmlAccessorType(XmlAccessType.NONE) + class UserInfo { + + @get:XmlElement(name = "AddressInfo", required = true) + lateinit var addressInfo: AddressInfo + + @get:XmlElement(name = "BankInfo", required = true) + lateinit var bankInfo: BankInfo + + class AddressInfo + class BankInfo + + } } \ No newline at end of file