gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated: refactor EBICS protocol into nicer int


From: gnunet
Subject: [libeufin] branch master updated: refactor EBICS protocol into nicer interface, unzip responses, allow date ranges
Date: Tue, 11 Feb 2020 11:37:13 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new b57e69e  refactor EBICS protocol into nicer interface, unzip 
responses, allow date ranges
b57e69e is described below

commit b57e69e8cd4fd4f8d793e5698bcb864626382384
Author: Florian Dold <address@hidden>
AuthorDate: Tue Feb 11 11:36:52 2020 +0100

    refactor EBICS protocol into nicer interface, unzip responses, allow date 
ranges
---
 nexus/build.gradle                                 |   1 +
 .../main/kotlin/tech/libeufin/nexus/EbicsClient.kt | 285 +++++------
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 138 ------
 nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt  |  61 ++-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 483 +++++++++++-------
 .../tech/libeufin/sandbox/EbicsProtocolBackend.kt  |   9 +-
 sandbox/src/main/python/libeufin-cli               | 102 +++-
 util/src/main/kotlin/CryptoUtil.kt                 |   1 -
 util/src/main/kotlin/Ebics.kt                      | 542 +++++++++++++++++++++
 util/src/main/kotlin/XMLUtil.kt                    |   6 +-
 .../ebics_h004/EbicsKeyManagementResponse.kt       |   2 +-
 util/src/main/kotlin/ebics_h004/EbicsRequest.kt    |  26 +-
 util/src/main/kotlin/ebics_hev/EbicsMessages.kt    |   3 +-
 util/src/main/kotlin/logger.kt                     |   6 -
 util/src/main/kotlin/time.kt                       |  19 -
 util/src/test/kotlin/SignatureDataTest.kt          |   5 +-
 16 files changed, 1119 insertions(+), 570 deletions(-)

diff --git a/nexus/build.gradle b/nexus/build.gradle
index f33be92..849283d 100644
--- a/nexus/build.gradle
+++ b/nexus/build.gradle
@@ -35,6 +35,7 @@ dependencies {
     implementation "javax.activation:activation:1.1"
     implementation "org.glassfish.jaxb:jaxb-runtime:2.3.1"
     implementation 'org.apache.santuario:xmlsec:2.1.4'
+    implementation group: 'org.apache.commons', name: 'commons-compress', 
version: '1.20'
 
     testImplementation group: 'junit', name: 'junit', version: '4.12'
 }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
index 4245ba9..a0f7dde 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsClient.kt
@@ -1,82 +1,38 @@
 package tech.libeufin.nexus
 
 import io.ktor.client.HttpClient
+import io.ktor.client.request.post
 import io.ktor.http.HttpStatusCode
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.EbicsOrderUtil
-import tech.libeufin.util.ebics_h004.EbicsRequest
-import tech.libeufin.util.ebics_h004.EbicsResponse
-import tech.libeufin.util.getGregorianCalendarNow
-import java.lang.StringBuilder
-import java.math.BigInteger
-import java.security.interfaces.RSAPrivateCrtKey
-import java.security.interfaces.RSAPublicKey
+import tech.libeufin.util.*
 import java.util.*
-import java.util.zip.DeflaterInputStream
 
-/**
- * This class is a mere container that keeps data found
- * in the database and that is further needed to sign / verify
- * / make messages.  And not all the values are needed all
- * the time.
- */
-data class EbicsSubscriberDetails(
-    val partnerId: String,
-    val userId: String,
-    var bankAuthPub: RSAPublicKey?,
-    var bankEncPub: RSAPublicKey?,
-    // needed to send the message
-    val ebicsUrl: String,
-    // needed to craft further messages
-    val hostId: String,
-    // needed to decrypt data coming from the bank
-    val customerEncPriv: RSAPrivateCrtKey,
-    // needed to sign documents
-    val customerAuthPriv: RSAPrivateCrtKey,
-    val customerSignPriv: RSAPrivateCrtKey
-)
+suspend inline fun HttpClient.postToBank(url: String, body: String): String {
+    logger.debug("Posting: $body")
+    val response = try {
+        this.post<String>(
+            urlString = url,
+            block = {
+                this.body = body
+            }
+        )
+    } catch (e: Exception) {
+        throw UnreachableBankError(HttpStatusCode.InternalServerError)
+    }
+    return response
+}
 
+sealed class EbicsDownloadResult
 
+class EbicsDownloadSuccessResult(
+    val orderData: ByteArray
+) : EbicsDownloadResult()
 
 /**
- * Wrapper around the lower decryption routine, that takes a EBICS response
- * object containing a encrypted payload, and return the plain version of it
- * (including decompression).
+ * Some bank-technical error occured.
  */
-fun decryptAndDecompressResponse(chunks: List<String>, transactionKey: 
ByteArray, privateKey: RSAPrivateCrtKey, pubDigest: ByteArray): ByteArray {
-    val buf = StringBuilder()
-    chunks.forEach { buf.append(it) }
-    val decoded = Base64.getDecoder().decode(buf.toString())
-    val er = CryptoUtil.EncryptionResult(
-        transactionKey,
-        pubDigest,
-        decoded
-    )
-    val dataCompr = CryptoUtil.decryptEbicsE002(
-        er,
-        privateKey
-    )
-    return EbicsOrderUtil.decodeOrderData(dataCompr)
-}
-
-
-/**
- * Get the private key that matches the given public key digest.
- */
-fun getDecryptionKey(subscriberDetails: EbicsSubscriberDetails, pubDigest: 
ByteArray): RSAPrivateCrtKey {
-    val authPub = 
CryptoUtil.getRsaPublicFromPrivate(subscriberDetails.customerAuthPriv)
-    val encPub = 
CryptoUtil.getRsaPublicFromPrivate(subscriberDetails.customerEncPriv)
-    val authPubDigest = CryptoUtil.getEbicsPublicKeyHash(authPub)
-    val encPubDigest = CryptoUtil.getEbicsPublicKeyHash(encPub)
-    if (pubDigest.contentEquals(authPubDigest)) {
-        return subscriberDetails.customerAuthPriv
-    }
-    if (pubDigest.contentEquals(encPubDigest)) {
-        return subscriberDetails.customerEncPriv
-    }
-    throw Exception("no matching private key to decrypt response")
-}
-
+class EbicsDownloadBankErrorResult(
+    val returnCode: EbicsReturnCode
+) : EbicsDownloadResult()
 
 /**
  * Do an EBICS download transaction.  This includes the initialization phase, 
transaction phase
@@ -84,133 +40,112 @@ fun getDecryptionKey(subscriberDetails: 
EbicsSubscriberDetails, pubDigest: ByteA
  */
 suspend fun doEbicsDownloadTransaction(
     client: HttpClient,
-    subscriberDetails: EbicsSubscriberDetails,
-    orderType: String
-): ByteArray {
-    val initDownloadRequest = 
EbicsRequest.createForDownloadInitializationPhase(
-        subscriberDetails.userId,
-        subscriberDetails.partnerId,
-        subscriberDetails.hostId,
-        getNonce(128),
-        getGregorianCalendarNow(),
-        subscriberDetails.bankEncPub ?: throw BankKeyMissing(
-            HttpStatusCode.PreconditionFailed
-        ),
-        subscriberDetails.bankAuthPub ?: throw BankKeyMissing(
-            HttpStatusCode.PreconditionFailed
-        ),
-        orderType
-    )
+    subscriberDetails: EbicsClientSubscriberDetails,
+    orderType: String,
+    orderParams: EbicsOrderParams
+): EbicsDownloadResult {
+
+    // Initialization phase
+    val initDownloadRequestStr = 
createEbicsRequestForDownloadInitialization(subscriberDetails, orderType, 
orderParams)
     val payloadChunks = LinkedList<String>();
-    val initResponse = client.postToBankSigned<EbicsRequest, EbicsResponse>(
-        subscriberDetails.ebicsUrl,
-        initDownloadRequest,
-        subscriberDetails.customerAuthPriv
-    )
-    if (initResponse.value.body.returnCode.value != "000000") {
-        throw EbicsError(initResponse.value.body.returnCode.value)
+    val initResponseStr = client.postToBank(subscriberDetails.ebicsUrl, 
initDownloadRequestStr)
+
+    val initResponse = parseAndValidateEbicsResponse(subscriberDetails, 
initResponseStr)
+
+    when (initResponse.technicalReturnCode) {
+        EbicsReturnCode.EBICS_OK -> {
+            // Success, nothing to do!
+        }
+        else -> {
+            throw ProtocolViolationError("unexpected return code 
${initResponse.technicalReturnCode}")
+        }
+    }
+
+    when (initResponse.bankReturnCode) {
+        EbicsReturnCode.EBICS_OK -> {
+            // Success, nothing to do!
+        }
+        else -> {
+            return EbicsDownloadBankErrorResult(initResponse.bankReturnCode)
+        }
     }
-    val initDataTransfer = initResponse.value.body.dataTransfer
+
+    val transactionID =
+        initResponse.transactionID ?: throw ProtocolViolationError("initial 
response must contain transaction ID")
+
+    val encryptionInfo = initResponse.dataEncryptionInfo
+        ?: throw ProtocolViolationError("initial response did not contain 
encryption info")
+
+    val initOrderDataEncChunk = initResponse.orderDataEncChunk
         ?: throw ProtocolViolationError("initial response for download 
transaction does not contain data transfer")
-    val dataEncryptionInfo = initDataTransfer.dataEncryptionInfo
-        ?: throw ProtocolViolationError("initial response for download 
transaction does not contain date encryption info")
-    val initOrderData = initDataTransfer.orderData.value
-    // FIXME: Also verify that algorithm matches!
-    val decryptionKey = getDecryptionKey(subscriberDetails, 
dataEncryptionInfo.encryptionPubKeyDigest.value)
-    payloadChunks.add(initOrderData)
-    val respPayload = decryptAndDecompressResponse(
-        payloadChunks,
-        dataEncryptionInfo.transactionKey,
-        decryptionKey,
-        dataEncryptionInfo.encryptionPubKeyDigest.value
-    )
-    val ackRequest = EbicsRequest.createForDownloadReceiptPhase(
-        initResponse.value.header._static.transactionID ?: throw 
BankInvalidResponse(
-            HttpStatusCode.ExpectationFailed
-        ),
-        subscriberDetails.hostId
-    )
-    val ackResponse = client.postToBankSignedAndVerify<EbicsRequest, 
EbicsResponse>(
+
+    payloadChunks.add(initOrderDataEncChunk)
+
+    val respPayload = decryptAndDecompressResponse(subscriberDetails, 
encryptionInfo, payloadChunks)
+
+    // Acknowledgement phase
+
+    val ackRequest = createEbicsRequestForDownloadReceipt(subscriberDetails, 
transactionID)
+    val ackResponseStr = client.postToBank(
         subscriberDetails.ebicsUrl,
-        ackRequest,
-        subscriberDetails.bankAuthPub ?: throw BankKeyMissing(
-            HttpStatusCode.PreconditionFailed
-        ),
-        subscriberDetails.customerAuthPriv
+        ackRequest
     )
-    if (ackResponse.value.body.returnCode.value != "000000") {
-        throw EbicsError(ackResponse.value.body.returnCode.value)
+    val ackResponse = parseAndValidateEbicsResponse(subscriberDetails, 
ackResponseStr)
+    when (ackResponse.technicalReturnCode) {
+        EbicsReturnCode.EBICS_DOWNLOAD_POSTPROCESS_DONE -> {
+        }
+        else -> {
+            throw ProtocolViolationError("unexpected return code")
+        }
     }
-    return respPayload
+    return EbicsDownloadSuccessResult(respPayload)
 }
 
 
 suspend fun doEbicsUploadTransaction(
     client: HttpClient,
-    subscriberDetails: EbicsSubscriberDetails,
+    subscriberDetails: EbicsClientSubscriberDetails,
     orderType: String,
-    payload: ByteArray
+    payload: ByteArray,
+    orderParams: EbicsOrderParams
 ) {
     if (subscriberDetails.bankEncPub == null) {
         throw InvalidSubscriberStateError("bank encryption key unknown, 
request HPB first")
     }
-    val userSignatureDateEncrypted = CryptoUtil.encryptEbicsE002(
-        EbicsOrderUtil.encodeOrderDataXml(
-            signOrder(
-                payload,
-                subscriberDetails.customerSignPriv,
-                subscriberDetails.partnerId,
-                subscriberDetails.userId
-            )
-        ),
-        subscriberDetails.bankEncPub!!
-    )
-    val response = client.postToBankSignedAndVerify<EbicsRequest, 
EbicsResponse>(
-        subscriberDetails.ebicsUrl,
-        EbicsRequest.createForUploadInitializationPhase(
-            userSignatureDateEncrypted,
-            subscriberDetails.hostId,
-            getNonce(128),
-            subscriberDetails.partnerId,
-            subscriberDetails.userId,
-            getGregorianCalendarNow(),
-            subscriberDetails.bankAuthPub!!,
-            subscriberDetails.bankEncPub!!,
-            BigInteger.ONE,
-            orderType
-        ),
-        subscriberDetails.bankAuthPub!!,
-        subscriberDetails.customerAuthPriv
-    )
-    if (response.value.header.mutable.returnCode != "000000") {
-        throw EbicsError(response.value.header.mutable.returnCode)
-    }
-    if (response.value.body.returnCode.value != "000000") {
-        throw EbicsError(response.value.body.returnCode.value)
+    val preparedUploadData = prepareUploadPayload(subscriberDetails, payload)
+    val req = createEbicsRequestForUploadInitialization(subscriberDetails, 
orderType, orderParams, preparedUploadData)
+    val responseStr = client.postToBank(subscriberDetails.ebicsUrl, req)
+
+    val initResponse = parseAndValidateEbicsResponse(subscriberDetails, 
responseStr)
+    if (initResponse.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
+        throw ProtocolViolationError("unexpected return code")
     }
+
+    val transactionID =
+        initResponse.transactionID ?: throw ProtocolViolationError("init 
response must have transaction ID")
+
     logger.debug("INIT phase passed!")
     /* now send actual payload */
-    val compressedInnerPayload = DeflaterInputStream(
-        payload.inputStream()
-    ).use { it.readAllBytes() }
-    val encryptedPayload = CryptoUtil.encryptEbicsE002withTransactionKey(
-        compressedInnerPayload,
-        subscriberDetails.bankEncPub!!,
-        userSignatureDateEncrypted.plainTransactionKey!!
-    )
-    val tmp = EbicsRequest.createForUploadTransferPhase(
-        subscriberDetails.hostId,
-        response.value.header._static.transactionID!!,
-        BigInteger.ONE,
-        encryptedPayload.encryptedData
+
+    val tmp = createEbicsRequestForUploadTransferPhase(
+        subscriberDetails,
+        transactionID,
+        preparedUploadData,
+        0
     )
-    val responseTransaction = client.postToBankSignedAndVerify<EbicsRequest, 
EbicsResponse>(
+
+    val txRespStr = client.postToBank(
         subscriberDetails.ebicsUrl,
-        tmp,
-        subscriberDetails.bankAuthPub!!,
-        subscriberDetails.customerAuthPriv
+        tmp
     )
-    if (responseTransaction.value.body.returnCode.value != "000000") {
-        throw EbicsError(response.value.body.returnCode.value)
+
+    val txResp = parseAndValidateEbicsResponse(subscriberDetails, txRespStr)
+
+    when (txResp.technicalReturnCode) {
+        EbicsReturnCode.EBICS_OK -> {
+        }
+        else -> {
+            throw ProtocolViolationError("unexpected return code")
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index ed65340..e17c550 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -1,21 +1,6 @@
 package tech.libeufin.nexus
 
-import io.ktor.client.HttpClient
-import io.ktor.client.request.post
 import io.ktor.http.HttpStatusCode
-import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.EbicsRequest
-import tech.libeufin.util.ebics_h004.EbicsResponse
-import tech.libeufin.util.ebics_h004.EbicsTypes
-import tech.libeufin.util.ebics_s001.UserSignatureData
-import java.math.BigInteger
-import java.security.PrivateKey
-import java.security.SecureRandom
-import java.security.interfaces.RSAPrivateCrtKey
-import java.security.interfaces.RSAPublicKey
-import java.util.*
-import javax.xml.bind.JAXBElement
-import javax.xml.datatype.XMLGregorianCalendar
 
 /**
  * Inserts spaces every 2 characters, and a newline after 8 pairs.
@@ -42,126 +27,3 @@ fun chunkString(input: String): String {
 fun expectId(param: String?): String {
     return param ?: throw NotAnIdError(HttpStatusCode.BadRequest)
 }
-
-fun signOrder(
-    orderBlob: ByteArray,
-    signKey: RSAPrivateCrtKey,
-    partnerId: String,
-    userId: String
-): UserSignatureData {
-    val ES_signature = CryptoUtil.signEbicsA006(
-        CryptoUtil.digestEbicsOrderA006(orderBlob),
-        signKey
-    )
-    val userSignatureData = UserSignatureData().apply {
-        orderSignatureList = listOf(
-            UserSignatureData.OrderSignatureData().apply {
-                signatureVersion = "A006"
-                signatureValue = ES_signature
-                partnerID = partnerId
-                userID = userId
-            }
-        )
-    }
-    return userSignatureData
-}
-
-/**
- * @return null when the bank could not be reached, otherwise returns the
- * response already converted in JAXB.
- */
-suspend inline fun HttpClient.postToBank(url: String, body: String): String {
-    logger.debug("Posting: $body")
-    val response = try {
-        this.post<String>(
-            urlString = url,
-            block = {
-                this.body = body
-            }
-        )
-    } catch (e: Exception) {
-        throw UnreachableBankError(HttpStatusCode.InternalServerError)
-    }
-    return response
-}
-
-/**
- * DO verify the bank's signature
- */
-suspend inline fun <reified T, reified S> HttpClient.postToBankSignedAndVerify(
-    url: String,
-    body: T,
-    pub: RSAPublicKey,
-    priv: RSAPrivateCrtKey
-): JAXBElement<S> {
-    val doc = XMLUtil.convertJaxbToDocument(body)
-    XMLUtil.signEbicsDocument(doc, priv)
-    val response: String = this.postToBank(url, 
XMLUtil.convertDomToString(doc))
-    logger.debug("About to verify: ${response}")
-    val responseDocument = try {
-        XMLUtil.parseStringIntoDom(response)
-    } catch (e: Exception) {
-        throw UnparsableResponse(
-            HttpStatusCode.BadRequest,
-            response
-        )
-    }
-    if (!XMLUtil.verifyEbicsDocument(responseDocument, pub)) {
-        throw BadSignature(HttpStatusCode.NotAcceptable)
-    }
-    try {
-        return XMLUtil.convertStringToJaxb(response)
-    } catch (e: Exception) {
-        throw UnparsableResponse(
-            HttpStatusCode.BadRequest,
-            response
-        )
-    }
-}
-
-suspend inline fun <reified T, reified S> HttpClient.postToBankSigned(
-    url: String,
-    body: T,
-    priv: PrivateKey
-): JAXBElement<S> {
-    val doc = XMLUtil.convertJaxbToDocument(body)
-    XMLUtil.signEbicsDocument(doc, priv)
-    val response: String = this.postToBank(url, 
XMLUtil.convertDomToString(doc))
-    println("bank response: $response")
-    try {
-        return XMLUtil.convertStringToJaxb(response)
-    } catch (e: Exception) {
-        throw UnparsableResponse(
-            HttpStatusCode.BadRequest,
-            response
-        )
-    }
-}
-
-/**
- * do NOT verify the bank's signature
- */
-suspend inline fun <reified T, reified S> HttpClient.postToBankUnsigned(
-    url: String,
-    body: T
-): JAXBElement<S> {
-    val response: String = this.postToBank(url, 
XMLUtil.convertJaxbToString(body))
-    try {
-        return XMLUtil.convertStringToJaxb(response)
-    } catch (e: Exception) {
-        throw UnparsableResponse(
-            HttpStatusCode.BadRequest,
-            response
-        )
-    }
-}
-
-/**
- * @param size in bits
- */
-fun getNonce(size: Int): ByteArray {
-    val sr = SecureRandom()
-    val ret = ByteArray(size / 8)
-    sr.nextBytes(ret)
-    return ret
-}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
index dd63042..dcd6fd6 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/JSON.kt
@@ -1,23 +1,40 @@
 package tech.libeufin.nexus
 
-data class EbicsBackupRequest(
+import tech.libeufin.util.EbicsDateRange
+import tech.libeufin.util.EbicsOrderParams
+import tech.libeufin.util.EbicsStandardOrderParams
+import java.time.LocalDate
+
+data class EbicsBackupRequestJson(
     val passphrase: String
 )
 
-data class NexusError(
+data class NexusErrorJson(
     val message: String
 )
 
-data class EbicsStandardOrderParams(
-    val dateRange: EbicsDateRange?
-)
+data class EbicsStandardOrderParamsJson(
+    val dateRange: EbicsDateRangeJson?
+) {
+    fun toOrderParams(): EbicsOrderParams {
+        var dateRange: EbicsDateRange? = if (this.dateRange != null) {
+            EbicsDateRange(
+                LocalDate.parse(this.dateRange.start),
+                LocalDate.parse(this.dateRange.end)
+            )
+        } else {
+            null
+        }
+        return EbicsStandardOrderParams(dateRange)
+    }
+}
 
-data class EbicsDateRange(
+data class EbicsDateRangeJson(
     /**
      * ISO 8601 calendar dates: YEAR-MONTH(01-12)-DAY(1-31)
      */
-    val start: String,
-    val end: String
+    val start: String?,
+    val end: String?
 )
 
 /**
@@ -25,7 +42,7 @@ data class EbicsDateRange(
  * and as a request to the backup restore.  Note: in the second case
  * the client must provide the passphrase.
  */
-data class EbicsKeysBackup(
+data class EbicsKeysBackupJson(
     val userID: String,
     val partnerID: String,
     val hostID: String,
@@ -47,7 +64,7 @@ data class EbicsPubKeyInfo(
  * This object is POSTed by clients _after_ having created
  * a EBICS subscriber at the sandbox.
  */
-data class EbicsSubscriberInfoRequest(
+data class EbicsSubscriberInfoRequestJson(
     val ebicsURL: String,
     val hostID: String,
     val partnerID: String,
@@ -58,7 +75,7 @@ data class EbicsSubscriberInfoRequest(
 /**
  * Contain the ID that identifies the new user in the Nexus system.
  */
-data class EbicsSubscriberInfoResponse(
+data class EbicsSubscriberInfoResponseJson(
     val accountID: String,
     val ebicsURL: String,
     val hostID: String,
@@ -70,16 +87,24 @@ data class EbicsSubscriberInfoResponse(
 /**
  * Admin call that tells all the subscribers managed by Nexus.
  */
-data class EbicsSubscribersResponse(
-    val ebicsSubscribers: MutableList<EbicsSubscriberInfoResponse> = 
mutableListOf()
+data class EbicsSubscribersResponseJson(
+    val ebicsSubscribers: MutableList<EbicsSubscriberInfoResponseJson> = 
mutableListOf()
 )
 
-data class ProtocolAndVersion(
+data class ProtocolAndVersionJson(
     val protocol: String,
-    val version: String,
-    val host: String
+    val version: String
+)
+
+data class EbicsHevResponseJson(
+    val versions: List<ProtocolAndVersionJson>
+)
+
+data class EbicsErrorDetailJson(
+    val type: String,
+    val ebicsReturnCode: String
 )
 
-data class EbicsHevResponse(
-    val versions: List<ProtocolAndVersion>
+data class EbicsErrorJson(
+    val error: EbicsErrorDetailJson
 )
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index b88819d..d32f31d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -22,7 +22,7 @@ package tech.libeufin.nexus
 import io.ktor.application.ApplicationCallPipeline
 import io.ktor.application.call
 import io.ktor.application.install
-import io.ktor.client.*
+import io.ktor.client.HttpClient
 import io.ktor.features.CallLogging
 import io.ktor.features.ContentNegotiation
 import io.ktor.features.StatusPages
@@ -33,9 +33,14 @@ import io.ktor.request.receive
 import io.ktor.request.uri
 import io.ktor.response.respond
 import io.ktor.response.respondText
-import io.ktor.routing.*
+import io.ktor.routing.get
+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.commons.compress.archivers.zip.ZipArchiveOutputStream
+import org.apache.commons.compress.archivers.zip.ZipFile
+import org.apache.commons.compress.utils.SeekableInMemoryByteChannel
 import org.jetbrains.exposed.exceptions.ExposedSQLException
 import org.jetbrains.exposed.sql.StdOutSqlLogger
 import org.jetbrains.exposed.sql.addLogger
@@ -43,21 +48,15 @@ import org.jetbrains.exposed.sql.transactions.transaction
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
-import tech.libeufin.util.ebics_h004.*
 import tech.libeufin.util.*
-import java.text.DateFormat
-import javax.sql.rowset.serial.SerialBlob
-import tech.libeufin.util.toHexString
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.EbicsOrderUtil
-import tech.libeufin.util.ebics_hev.HEVRequest
-import tech.libeufin.util.ebics_hev.HEVResponse
-import java.math.BigInteger
+import tech.libeufin.util.InvalidSubscriberStateError
+import java.lang.StringBuilder
 import java.security.interfaces.RSAPublicKey
+import java.text.DateFormat
 import java.text.SimpleDateFormat
 import java.util.*
-import java.util.zip.DeflaterInputStream
 import javax.crypto.EncryptedPrivateKeyInfo
+import javax.sql.rowset.serial.SerialBlob
 
 fun testData() {
     val pairA = CryptoUtil.generateRsaKeyPair(2048)
@@ -97,7 +96,7 @@ data class BankInvalidResponse(val statusCode: 
HttpStatusCode) : Exception("Miss
 
 val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus")
 
-fun getSubscriberDetailsFromId(id: String): EbicsSubscriberDetails {
+fun getSubscriberDetailsFromId(id: String): EbicsClientSubscriberDetails {
     return transaction {
         val subscriber = EbicsSubscriberEntity.findById(
             id
@@ -116,7 +115,7 @@ fun getSubscriberDetailsFromId(id: String): 
EbicsSubscriberDetails {
                 subscriber.bankEncryptionPublicKey?.toByteArray()!!
             )
         }
-        EbicsSubscriberDetails(
+        EbicsClientSubscriberDetails(
             bankAuthPub = bankAuthPubValue,
             bankEncPub = bankEncPubValue,
 
@@ -243,123 +242,288 @@ fun main() {
 
             post("/ebics/subscribers/{id}/sendPTK") {
                 val id = expectId(call.parameters["id"])
-                val params = call.receive<EbicsStandardOrderParams>()
-                println("PTK order params: $params")
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                println("PTK order params: $orderParams")
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "PTK")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "PTK", orderParams)
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
             post("/ebics/subscribers/{id}/sendHAC") {
                 val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HAC")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
-                return@post
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HAC", orderParams)
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
             }
 
             post("/ebics/subscribers/{id}/sendC52") {
                 val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C52")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C52", orderParams)
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
+            }
+
+            post("/ebics/subscribers/{id}/sendC53") {
+                val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                val subscriberData = getSubscriberDetailsFromId(id)
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C53", orderParams)
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        val mem = 
SeekableInMemoryByteChannel(response.orderData)
+                        val zipFile = ZipFile(mem)
+
+                        val s = StringBuilder()
+
+                        zipFile.getEntriesInPhysicalOrder().iterator().forEach 
{ entry ->
+                            s.append("<=== File ${entry.name} ===>\n")
+                            
s.append(zipFile.getInputStream(entry).readAllBytes().toString(Charsets.UTF_8))
+                            s.append("\n")
+                        }
+
+                        call.respondText(
+                            s.toString(),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
+            }
+
+            post("/ebics/subscribers/{id}/sendC54") {
+                val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
+                val subscriberData = getSubscriberDetailsFromId(id)
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "C54", orderParams)
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
             post("/ebics/subscribers/{id}/sendHtd") {
                 val id = expectId(call.parameters["id"])
+                val paramsJson = call.receive<EbicsStandardOrderParamsJson>()
+                val orderParams = paramsJson.toOrderParams()
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HTD")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HTD", orderParams)
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
             post("/ebics/subscribers/{id}/sendHAA") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HAA")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HAA", EbicsStandardOrderParams())
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
             post("/ebics/subscribers/{id}/sendHVZ") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HVZ")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                // FIXME: order params are wrong
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HVZ", EbicsStandardOrderParams())
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
             post("/ebics/subscribers/{id}/sendHVU") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HVU")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                // FIXME: order params are wrong
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HVU", EbicsStandardOrderParams())
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
             post("/ebics/subscribers/{id}/sendHPD") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HPD")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HPD", EbicsStandardOrderParams())
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
             post("/ebics/subscribers/{id}/sendHKD") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HKD")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "HKD", EbicsStandardOrderParams())
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
             post("/ebics/subscribers/{id}/sendTSD") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val response = doEbicsDownloadTransaction(client, 
subscriberData, "TSD")
-                call.respondText(
-                    response.toString(Charsets.UTF_8),
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
+                val response = doEbicsDownloadTransaction(client, 
subscriberData, "TSD", EbicsGenericOrderParams())
+                when (response) {
+                    is EbicsDownloadSuccessResult -> {
+                        call.respondText(
+                            response.orderData.toString(Charsets.UTF_8),
+                            ContentType.Text.Plain,
+                            HttpStatusCode.OK
+                        )
+                    }
+                    is EbicsDownloadBankErrorResult -> {
+                        call.respond(
+                            HttpStatusCode.BadGateway,
+                            EbicsErrorJson(EbicsErrorDetailJson("bankError", 
response.returnCode.errorCode))
+                        )
+                    }
+                }
                 return@post
             }
 
@@ -494,11 +658,11 @@ fun main() {
             }
 
             get("/ebics/subscribers") {
-                val ret = EbicsSubscribersResponse()
+                val ret = EbicsSubscribersResponseJson()
                 transaction {
                     EbicsSubscriberEntity.all().forEach {
                         ret.ebicsSubscribers.add(
-                            EbicsSubscriberInfoResponse(
+                            EbicsSubscriberInfoResponseJson(
                                 accountID = it.id.value,
                                 hostID = it.hostID,
                                 partnerID = it.partnerID,
@@ -519,7 +683,7 @@ fun main() {
                     val tmp = EbicsSubscriberEntity.findById(id) ?: throw 
SubscriberNotFoundError(
                         HttpStatusCode.NotFound
                     )
-                    EbicsSubscriberInfoResponse(
+                    EbicsSubscriberInfoResponseJson(
                         accountID = tmp.id.value,
                         hostID = tmp.hostID,
                         partnerID = tmp.partnerID,
@@ -534,27 +698,24 @@ fun main() {
 
             get("/ebics/{id}/sendHev") {
                 val id = expectId(call.parameters["id"])
-                val (ebicsUrl, hostID) = transaction {
-                    val customer = EbicsSubscriberEntity.findById(id)
-                        ?: throw 
SubscriberNotFoundError(HttpStatusCode.NotFound)
-                    Pair(customer.ebicsURL, customer.hostID)
-                }
-                val request = HEVRequest().apply {
-                    hostId = hostID
-                }
-                val response = client.postToBankUnsigned<HEVRequest, 
HEVResponse>(ebicsUrl, request)
-                // here, response is gueranteed to be successful, no need to 
check the status code.
+                val subscriberData = getSubscriberDetailsFromId(id)
+                val request = makeEbicsHEVRequest(subscriberData)
+                val response = client.postToBank(subscriberData.ebicsUrl, 
request)
+                val versionDetails = parseEbicsHEVResponse(subscriberData, 
response)
                 call.respond(
                     HttpStatusCode.OK,
-                    EbicsHevResponse(response.value.versionNumber!!.map {
-                        ProtocolAndVersion(it.value, it.protocolVersion, 
hostID)
+                    EbicsHevResponseJson(versionDetails.versions.map { 
ebicsVersionSpec ->
+                        ProtocolAndVersionJson(
+                            ebicsVersionSpec.protocol,
+                            ebicsVersionSpec.version
+                        )
                     })
                 )
                 return@get
             }
 
             post("/ebics/{id}/subscribers") {
-                val body = call.receive<EbicsSubscriberInfoRequest>()
+                val body = call.receive<EbicsSubscriberInfoRequestJson>()
                 val pairA = CryptoUtil.generateRsaKeyPair(2048)
                 val pairB = CryptoUtil.generateRsaKeyPair(2048)
                 val pairC = CryptoUtil.generateRsaKeyPair(2048)
@@ -573,7 +734,7 @@ fun main() {
                     }
                 } catch (e: Exception) {
                     print(e)
-                    call.respond(NexusError("Could not store the new account 
into database"))
+                    call.respond(NexusErrorJson("Could not store the new 
account into database"))
                     return@post
                 }
                 call.respondText(
@@ -587,25 +748,41 @@ fun main() {
             post("/ebics/subscribers/{id}/sendIni") {
                 val id = expectId(call.parameters["id"])
                 val subscriberData = getSubscriberDetailsFromId(id)
-                val iniRequest = EbicsUnsecuredRequest.createIni(
-                    subscriberData.hostId,
-                    subscriberData.userId,
-                    subscriberData.partnerId,
-                    subscriberData.customerSignPriv
-                )
-                val responseJaxb = 
client.postToBankUnsigned<EbicsUnsecuredRequest, EbicsKeyManagementResponse>(
+                val iniRequest = makeEbicsIniRequest(subscriberData)
+                val responseStr = client.postToBank(
                     subscriberData.ebicsUrl,
                     iniRequest
                 )
-                if (responseJaxb.value.body.returnCode.value != "000000") {
-                    throw EbicsError(responseJaxb.value.body.returnCode.value)
+                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
+                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
+                    throw EbicsError("Unexpected INI response code: 
${resp.technicalReturnCode}")
                 }
                 call.respondText("Bank accepted signature key\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
                 return@post
             }
 
+            post("/ebics/subscribers/{id}/sendHia") {
+                val id = expectId(call.parameters["id"])
+                val subscriberData = getSubscriberDetailsFromId(id)
+                val hiaRequest = makeEbicsHiaRequest(subscriberData)
+                val responseStr = client.postToBank(
+                    subscriberData.ebicsUrl,
+                    hiaRequest
+                )
+                val resp = 
parseAndDecryptEbicsKeyManagementResponse(subscriberData, responseStr)
+                if (resp.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
+                    throw EbicsError("Unexpected HIA response code: 
${resp.technicalReturnCode}")
+                }
+                call.respondText(
+                    "Bank accepted authentication and encryption keys\n",
+                    ContentType.Text.Plain,
+                    HttpStatusCode.OK
+                )
+                return@post
+            }
+
             post("/ebics/subscribers/{id}/restoreBackup") {
-                val body = call.receive<EbicsKeysBackup>()
+                val body = call.receive<EbicsKeysBackupJson>()
                 val id = expectId(call.parameters["id"])
                 val subscriber = transaction {
                     EbicsSubscriberEntity.findById(id)
@@ -613,7 +790,7 @@ fun main() {
                 if (subscriber != null) {
                     call.respond(
                         HttpStatusCode.Conflict,
-                        NexusError("ID exists, please choose a new one")
+                        NexusErrorJson("ID exists, please choose a new one")
                     )
                     return@post
                 }
@@ -649,7 +826,7 @@ fun main() {
                     }
                 } catch (e: Exception) {
                     print(e)
-                    call.respond(NexusError("Could not store the new account 
$id into database"))
+                    call.respond(NexusErrorJson("Could not store the new 
account $id into database"))
                     return@post
                 }
                 call.respondText(
@@ -687,12 +864,12 @@ fun main() {
             /* performs a keys backup */
             post("/ebics/subscribers/{id}/backup") {
                 val id = expectId(call.parameters["id"])
-                val body = call.receive<EbicsBackupRequest>()
+                val body = call.receive<EbicsBackupRequestJson>()
                 val response = transaction {
                     val subscriber = EbicsSubscriberEntity.findById(id) ?: 
throw SubscriberNotFoundError(
                         HttpStatusCode.NotFound
                     )
-                    EbicsKeysBackup(
+                    EbicsKeysBackupJson(
                         userID = subscriber.userID,
                         hostID = subscriber.hostID,
                         partnerID = subscriber.partnerID,
@@ -729,7 +906,13 @@ fun main() {
                 val subscriberData = getSubscriberDetailsFromId(id)
                 val payload = "PAYLOAD"
 
-                doEbicsUploadTransaction(client, subscriberData, "TSU", 
payload.toByteArray(Charsets.UTF_8))
+                doEbicsUploadTransaction(
+                    client,
+                    subscriberData,
+                    "TSU",
+                    payload.toByteArray(Charsets.UTF_8),
+                    EbicsGenericOrderParams()
+                )
 
                 call.respondText(
                     "TST INITIALIZATION & TRANSACTION phases succeeded\n",
@@ -741,83 +924,23 @@ fun main() {
             post("/ebics/subscribers/{id}/sync") {
                 val id = expectId(call.parameters["id"])
                 val subscriberDetails = getSubscriberDetailsFromId(id)
-                val response = client.postToBankSigned<EbicsNpkdRequest, 
EbicsKeyManagementResponse>(
-                    subscriberDetails.ebicsUrl,
-                    EbicsNpkdRequest.createRequest(
-                        subscriberDetails.hostId,
-                        subscriberDetails.partnerId,
-                        subscriberDetails.userId,
-                        getNonce(128),
-                        getGregorianCalendarNow()
-                    ),
-                    subscriberDetails.customerAuthPriv
-                )
-                if (response.value.body.returnCode.value != "000000") {
-                    throw EbicsError(response.value.body.returnCode.value)
-                }
-                val encPubKeyDigestViaBank =
-                    (response.value.body.dataTransfer!!.dataEncryptionInfo as 
EbicsTypes.DataEncryptionInfo)
-                        .encryptionPubKeyDigest.value;
-                val customerEncPub = 
CryptoUtil.getRsaPublicFromPrivate(subscriberDetails.customerEncPriv);
-                val encPubKeyDigestViaNexus = 
CryptoUtil.getEbicsPublicKeyHash(customerEncPub)
-                println("encPubKeyDigestViaBank: 
${encPubKeyDigestViaBank.toHexString()}")
-                println("encPubKeyDigestViaNexus: 
${encPubKeyDigestViaNexus.toHexString()}")
-                val decryptionKey = getDecryptionKey(subscriberDetails, 
encPubKeyDigestViaBank)
-                val er = CryptoUtil.EncryptionResult(
-                    
response.value.body.dataTransfer!!.dataEncryptionInfo!!.transactionKey,
-                    encPubKeyDigestViaBank,
-                    response.value.body.dataTransfer!!.orderData.value
-                )
-                val dataCompr = CryptoUtil.decryptEbicsE002(
-                    er,
-                    decryptionKey
-                )
-                val data = 
EbicsOrderUtil.decodeOrderDataXml<HPBResponseOrderData>(dataCompr)
-                // put bank's keys into database.
-                transaction {
-                    val subscriber = EbicsSubscriberEntity.findById(id)
+                val hpbRequest = makeEbicsHpbRequest(subscriberDetails)
+                val responseStr = 
client.postToBank(subscriberDetails.ebicsUrl, hpbRequest)
 
-                    subscriber!!.bankAuthenticationPublicKey = SerialBlob(
+                val response = 
parseAndDecryptEbicsKeyManagementResponse(subscriberDetails, responseStr)
+                val orderData =
+                    response.orderData ?: throw 
ProtocolViolationError("expected order data in HPB response")
+                val hpbData = parseEbicsHpbOrder(orderData)
 
-                        CryptoUtil.loadRsaPublicKeyFromComponents(
-                            
data.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.modulus,
-                            
data.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.exponent
-                        ).encoded
-                    )
-                    subscriber.bankEncryptionPublicKey = SerialBlob(
-                        CryptoUtil.loadRsaPublicKeyFromComponents(
-                            
data.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.modulus,
-                            
data.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.exponent
-                        ).encoded
-                    )
+                // put bank's keys into database.
+                transaction {
+                    val subscriber = EbicsSubscriberEntity.findById(id) ?: 
throw InvalidSubscriberStateError()
+                    subscriber.bankAuthenticationPublicKey = 
SerialBlob(hpbData.authenticationPubKey.encoded)
+                    subscriber.bankEncryptionPublicKey = 
SerialBlob(hpbData.encryptionPubKey.encoded)
                 }
                 call.respondText("Bank keys stored in database\n", 
ContentType.Text.Plain, HttpStatusCode.OK)
                 return@post
             }
-
-            post("/ebics/subscribers/{id}/sendHia") {
-                val id = expectId(call.parameters["id"])
-                val subscriberData = getSubscriberDetailsFromId(id)
-                val responseJaxb = 
client.postToBankUnsigned<EbicsUnsecuredRequest, EbicsKeyManagementResponse>(
-                    subscriberData.ebicsUrl,
-                    EbicsUnsecuredRequest.createHia(
-                        subscriberData.hostId,
-                        subscriberData.userId,
-                        subscriberData.partnerId,
-                        subscriberData.customerAuthPriv,
-                        subscriberData.customerEncPriv
-                    )
-                )
-                if (responseJaxb.value.body.returnCode.value != "000000") {
-                    throw EbicsError(responseJaxb.value.body.returnCode.value)
-                }
-                call.respondText(
-                    "Bank accepted authentication and encryption keys\n",
-                    ContentType.Text.Plain,
-                    HttpStatusCode.OK
-                )
-                return@post
-            }
         }
     }
     logger.info("Up and running")
diff --git 
a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index 4f20ef0..670ec69 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -47,6 +47,7 @@ import java.util.*
 import java.util.zip.DeflaterInputStream
 import java.util.zip.InflaterInputStream
 import javax.sql.rowset.serial.SerialBlob
+import javax.xml.datatype.DatatypeFactory
 
 
 open class EbicsRequestError(errorText: String, errorCode: String) :
@@ -118,7 +119,7 @@ private suspend fun 
ApplicationCall.respondEbicsKeyManagement(
                         }
                     }
                     this.orderData = 
EbicsKeyManagementResponse.OrderData().apply {
-                        this.value = dataTransfer.encryptedData
+                        this.value = 
Base64.getEncoder().encodeToString(dataTransfer.encryptedData)
                     }
                 }
             }
@@ -147,13 +148,13 @@ private fun iterHistory(customerId: Int, header: 
EbicsRequest.Header, base: XmlE
             (header.static.orderDetails?.orderParams as 
EbicsRequest.StandardOrderParams).dateRange!!.start.toString()
         } catch (e: Exception) {
             LOGGER.debug("Asked to iterate over history with NO start date; 
default to now")
-            getGregorianCalendarNow().toString()
+            
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()).toString()
         },
         try {
             (header.static.orderDetails?.orderParams as 
EbicsRequest.StandardOrderParams).dateRange!!.end.toString()
         } catch (e: Exception) {
             LOGGER.debug("Asked to iterate over history with NO end date; 
default to now")
-            getGregorianCalendarNow().toString()
+            
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()).toString()
         }
     ) {
 
@@ -758,7 +759,7 @@ private fun 
handleEbicsUploadTransactionTransmission(requestContext: RequestCont
             requestObject.body.dataTransfer?.orderData ?: throw 
EbicsInvalidRequestError()
         val zippedData = CryptoUtil.decryptEbicsE002(
             uploadTransaction.transactionKeyEnc.toByteArray(),
-            encOrderData,
+            Base64.getDecoder().decode(encOrderData),
             requestContext.hostEncPriv
         )
         val unzippedData =
diff --git a/sandbox/src/main/python/libeufin-cli 
b/sandbox/src/main/python/libeufin-cli
index 9a4fe52..4f35dbf 100755
--- a/sandbox/src/main/python/libeufin-cli
+++ b/sandbox/src/main/python/libeufin-cli
@@ -377,18 +377,74 @@ def hkd(obj, account_id, nexus_base_url):
     help="Numerical ID of the customer at the Nexus",
     required=True
 )
+@click.option(
+    "--date-range",
+    help="Date range for the query",
+    nargs=2,
+    required=False,
+)
 @click.argument(
   "nexus-base-url"
 )
-def c52(obj, account_id, nexus_base_url):
-    
+def c52(obj, account_id, date_range, nexus_base_url):
+    if date_range is not None and len(date_range) == 2:
+        req = dict(dateRange=dict(start=date_range[0], end=date_range[1]))
+    else:
+        req = dict()
     url = urljoin(nexus_base_url, 
"/ebics/subscribers/{}/sendC52".format(account_id))
-    try:
-        resp = post(url, json=dict(start="2020-01-15", end="2020-02-03"))
-    except Exception:
-        print("Could not reach the bank")
-        return
+    resp = post(url, json=req)
+    print(resp.content.decode("utf-8"))
 
+
+@ebics.command(help="Send C53 message")
+@click.pass_obj
+@click.option(
+    "--account-id",
+    help="Numerical ID of the customer at the Nexus",
+    required=True
+)
+@click.option(
+    "--date-range",
+    help="Date range for the query",
+    nargs=2,
+    required=False,
+)
+@click.argument(
+  "nexus-base-url"
+)
+def c53(obj, account_id, date_range, nexus_base_url):
+    if date_range is not None and len(date_range) == 2:
+        req = dict(dateRange=dict(start=date_range[0], end=date_range[1]))
+    else:
+        req = dict()
+    url = urljoin(nexus_base_url, 
"/ebics/subscribers/{}/sendC53".format(account_id))
+    resp = post(url, json=req)
+    print(resp.content.decode("utf-8"))
+
+
+@ebics.command(help="Send C54 message")
+@click.pass_obj
+@click.option(
+    "--account-id",
+    help="Numerical ID of the customer at the Nexus",
+    required=True
+)
+@click.option(
+    "--date-range",
+    help="Date range for the query",
+    nargs=2,
+    required=False,
+)
+@click.argument(
+  "nexus-base-url"
+)
+def c54(obj, account_id, date_range, nexus_base_url):
+    if date_range is not None and len(date_range) == 2:
+        req = dict(dateRange=dict(start=date_range[0], end=date_range[1]))
+    else:
+        req = dict()
+    url = urljoin(nexus_base_url, 
"/ebics/subscribers/{}/sendC54".format(account_id))
+    resp = post(url, json=req)
     print(resp.content.decode("utf-8"))
 
 
@@ -399,13 +455,24 @@ def c52(obj, account_id, nexus_base_url):
     help="Numerical ID of the customer at the Nexus",
     required=True
 )
+@click.option(
+    "--date-range",
+    help="Date range for the query",
+    nargs=2,
+    required=False,
+)
 @click.argument(
   "nexus-base-url"
 )
-def ptk(obj, account_id, nexus_base_url):
+def ptk(obj, account_id, date_range, nexus_base_url):
     url = urljoin(nexus_base_url, 
"/ebics/subscribers/{}/sendPTK".format(account_id))
+    if date_range is not None and len(date_range) == 2:
+        req = dict(dateRange=dict(start=date_range[0], end=date_range[1]))
+    else:
+        req = dict()
+    print("requesting PTK", repr(req))
     try:
-        resp = post(url, json=dict())
+        resp = post(url, json=req)
     except Exception:
         print("Could not reach the bank")
         return
@@ -420,13 +487,23 @@ def ptk(obj, account_id, nexus_base_url):
     help="Numerical ID of the customer at the Nexus",
     required=True
 )
+@click.option(
+    "--date-range",
+    help="Date range for the query",
+    nargs=2,
+    required=False,
+)
 @click.argument(
   "nexus-base-url"
 )
-def hac(obj, account_id, nexus_base_url):
+def hac(obj, account_id, date_range, nexus_base_url):
     url = urljoin(nexus_base_url, 
"/ebics/subscribers/{}/sendHAC".format(account_id))
+    if date_range is not None and len(date_range) == 2:
+        req = dict(dateRange=dict(start=date_range[0], end=date_range[1]))
+    else:
+        req = dict()
     try:
-        resp = post(url)
+        resp = post(url, json=req)
     except Exception:
         print("Could not reach the bank")
         return
@@ -500,6 +577,7 @@ def htd(ctx, account_id, prepare, nexus_base_url):
 
     print(resp.content.decode("utf-8"))
 
+
 @ebics.command(help="Send HIA message")
 @click.pass_obj
 @click.option(
@@ -519,6 +597,7 @@ def hia(obj, account_id, nexus_base_url):
         return
     print(resp.content.decode("utf-8"))
 
+
 @ebics.command(help="Send HPB message")
 @click.pass_obj
 @click.option(
@@ -610,6 +689,7 @@ def new_subscriber(obj, account_id, user_id, partner_id, 
system_id, host_id, ebi
 
     print(resp.content.decode("utf-8"))
 
+
 @native.command(help="Ask the list of transactions related to one account")
 @click.option(
     "--user-id",
diff --git a/util/src/main/kotlin/CryptoUtil.kt 
b/util/src/main/kotlin/CryptoUtil.kt
index 752c0e9..b73dee4 100644
--- a/util/src/main/kotlin/CryptoUtil.kt
+++ b/util/src/main/kotlin/CryptoUtil.kt
@@ -203,7 +203,6 @@ object CryptoUtil {
         )
         val ivParameterSpec = IvParameterSpec(ByteArray(16))
         symmetricCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, 
ivParameterSpec)
-        LOGGER.debug("decrypting: ${encryptedData.toHexString()}")
         val data = symmetricCipher.doFinal(encryptedData)
         return data
     }
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
new file mode 100644
index 0000000..0d0c7c9
--- /dev/null
+++ b/util/src/main/kotlin/Ebics.kt
@@ -0,0 +1,542 @@
+/*
+ * 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/>
+ */
+
+/**
+ * This is the main "EBICS library interface".  Functions here are stateless 
helpers
+ * used to implement both an EBICS server and EBICS client.
+ */
+
+package tech.libeufin.util
+
+import tech.libeufin.util.ebics_h004.*
+import tech.libeufin.util.ebics_hev.HEVRequest
+import tech.libeufin.util.ebics_hev.HEVResponse
+import tech.libeufin.util.ebics_s001.UserSignatureData
+import java.math.BigInteger
+import java.security.SecureRandom
+import java.security.interfaces.RSAPrivateCrtKey
+import java.security.interfaces.RSAPublicKey
+import java.time.LocalDate
+import java.util.*
+import java.util.zip.DeflaterInputStream
+import javax.xml.datatype.DatatypeFactory
+
+class InvalidSubscriberStateError : Exception("Invalid EBICS subscriber state")
+class InvalidXmlError : Exception("Invalid EBICS XML")
+class BadSignatureError : Exception("Invalid EBICS XML Signature")
+class EbicsUnknownReturnCodeError(msg: String) : Exception(msg)
+
+data class EbicsDateRange(val start: LocalDate, val end: LocalDate)
+
+sealed class EbicsOrderParams
+
+data class EbicsStandardOrderParams(
+    val dateRange: EbicsDateRange? = null
+) : EbicsOrderParams()
+
+data class EbicsGenericOrderParams(
+    val params: Map<String, String> = mapOf()
+) : EbicsOrderParams()
+
+/**
+ * This class is a mere container that keeps data found
+ * in the database and that is further needed to sign / verify
+ * / make messages.  And not all the values are needed all
+ * the time.
+ */
+data class EbicsClientSubscriberDetails(
+    val partnerId: String,
+    val userId: String,
+    var bankAuthPub: RSAPublicKey?,
+    var bankEncPub: RSAPublicKey?,
+    val ebicsUrl: String,
+    val hostId: String,
+    val customerEncPriv: RSAPrivateCrtKey,
+    val customerAuthPriv: RSAPrivateCrtKey,
+    val customerSignPriv: RSAPrivateCrtKey
+)
+
+/**
+ * @param size in bits
+ */
+private fun getNonce(size: Int): ByteArray {
+    val sr = SecureRandom()
+    val ret = ByteArray(size / 8)
+    sr.nextBytes(ret)
+    return ret
+}
+
+private fun makeOrderParams(orderParams: EbicsOrderParams): 
EbicsRequest.OrderParams {
+    return when (orderParams) {
+        is EbicsStandardOrderParams -> {
+            EbicsRequest.StandardOrderParams().apply {
+                val r = orderParams.dateRange
+                if (r != null) {
+                    this.dateRange = EbicsRequest.DateRange().apply {
+                        this.start = 
DatatypeFactory.newInstance().newXMLGregorianCalendar(r.start.toString())
+                        this.end = 
DatatypeFactory.newInstance().newXMLGregorianCalendar(r.end.toString())
+                    }
+                }
+            }
+        }
+        is EbicsGenericOrderParams -> {
+            EbicsRequest.GenericOrderParams().apply {
+                this.parameterList = orderParams.params.map { entry ->
+                    EbicsTypes.Parameter().apply {
+                        this.name = entry.key
+                        this.value = entry.value
+                        this.type = "string"
+                    }
+                }
+            }
+        }
+        else -> {
+            throw NotImplementedError()
+        }
+    }
+}
+
+
+private fun signOrder(
+    orderBlob: ByteArray,
+    signKey: RSAPrivateCrtKey,
+    partnerId: String,
+    userId: String
+): UserSignatureData {
+    val ES_signature = CryptoUtil.signEbicsA006(
+        CryptoUtil.digestEbicsOrderA006(orderBlob),
+        signKey
+    )
+    val userSignatureData = UserSignatureData().apply {
+        orderSignatureList = listOf(
+            UserSignatureData.OrderSignatureData().apply {
+                signatureVersion = "A006"
+                signatureValue = ES_signature
+                partnerID = partnerId
+                userID = userId
+            }
+        )
+    }
+    return userSignatureData
+}
+
+
+fun createEbicsRequestForDownloadReceipt(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    transactionID: String
+): String {
+    val req = EbicsRequest.createForDownloadReceiptPhase(transactionID, 
subscriberDetails.hostId)
+    val doc = XMLUtil.convertJaxbToDocument(req)
+    XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv)
+    return XMLUtil.convertDomToString(doc)
+}
+
+data class PreparedUploadData(
+    val transactionKey: ByteArray,
+    val userSignatureDataEncrypted: ByteArray,
+    val encryptedPayloadChunks: List<String>
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as PreparedUploadData
+
+        if (!transactionKey.contentEquals(other.transactionKey)) return false
+        if 
(!userSignatureDataEncrypted.contentEquals(other.userSignatureDataEncrypted)) 
return false
+        if (encryptedPayloadChunks != other.encryptedPayloadChunks) return 
false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = transactionKey.contentHashCode()
+        result = 31 * result + userSignatureDataEncrypted.contentHashCode()
+        result = 31 * result + encryptedPayloadChunks.hashCode()
+        return result
+    }
+}
+
+fun prepareUploadPayload(subscriberDetails: EbicsClientSubscriberDetails, 
payload: ByteArray): PreparedUploadData {
+    val userSignatureDataEncrypted = CryptoUtil.encryptEbicsE002(
+        EbicsOrderUtil.encodeOrderDataXml(
+            signOrder(
+                payload,
+                subscriberDetails.customerSignPriv,
+                subscriberDetails.partnerId,
+                subscriberDetails.userId
+            )
+        ),
+        subscriberDetails.bankEncPub!!
+    )
+    val compressedInnerPayload = DeflaterInputStream(
+        payload.inputStream()
+    ).use { it.readAllBytes() }
+    val encryptedPayload = CryptoUtil.encryptEbicsE002withTransactionKey(
+        compressedInnerPayload,
+        subscriberDetails.bankEncPub!!,
+        userSignatureDataEncrypted.plainTransactionKey!!
+    )
+    val encodedEncryptedPayload = 
Base64.getEncoder().encodeToString(encryptedPayload.encryptedData)
+    return PreparedUploadData(
+        userSignatureDataEncrypted.encryptedTransactionKey,
+        userSignatureDataEncrypted.encryptedData,
+        listOf(encodedEncryptedPayload)
+    )
+}
+
+/**
+ * Create an EBICS request for the initialization phase of an upload EBICS 
transaction.
+ *
+ * The payload is only passed to generate the signature.
+ */
+fun createEbicsRequestForUploadInitialization(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    orderType: String,
+    orderParams: EbicsOrderParams,
+    preparedUploadData: PreparedUploadData
+): String {
+    val req = EbicsRequest.createForUploadInitializationPhase(
+        preparedUploadData.transactionKey,
+        preparedUploadData.userSignatureDataEncrypted,
+        subscriberDetails.hostId,
+        getNonce(128),
+        subscriberDetails.partnerId,
+        subscriberDetails.userId,
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        subscriberDetails.bankAuthPub!!,
+        subscriberDetails.bankEncPub!!,
+        BigInteger.ONE,
+        orderType,
+        makeOrderParams(orderParams)
+    )
+    val doc = XMLUtil.convertJaxbToDocument(req)
+    XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv)
+    return XMLUtil.convertDomToString(doc)
+}
+
+
+fun createEbicsRequestForDownloadInitialization(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    orderType: String,
+    orderParams: EbicsOrderParams
+): String {
+    val req = EbicsRequest.createForDownloadInitializationPhase(
+        subscriberDetails.userId,
+        subscriberDetails.partnerId,
+        subscriberDetails.hostId,
+        getNonce(128),
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        subscriberDetails.bankEncPub ?: throw InvalidSubscriberStateError(),
+        subscriberDetails.bankAuthPub ?: throw InvalidSubscriberStateError(),
+        orderType,
+        makeOrderParams(orderParams)
+    )
+    val doc = XMLUtil.convertJaxbToDocument(req)
+    XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv)
+    return XMLUtil.convertDomToString(doc)
+}
+
+
+fun createEbicsRequestForUploadTransferPhase(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    transactionID: String,
+    preparedUploadData: PreparedUploadData,
+    chunkIndex: Int
+): String {
+    val req = EbicsRequest.createForUploadTransferPhase(
+        subscriberDetails.hostId,
+        transactionID,
+        // chunks are 1-indexed
+        BigInteger.valueOf(chunkIndex.toLong() + 1),
+        preparedUploadData.encryptedPayloadChunks[chunkIndex]
+    )
+    val doc = XMLUtil.convertJaxbToDocument(req)
+    XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv)
+    return XMLUtil.convertDomToString(doc)
+}
+
+data class DataEncryptionInfo(
+    val transactionKey: ByteArray,
+    val bankPubDigest: ByteArray
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as DataEncryptionInfo
+
+        if (!transactionKey.contentEquals(other.transactionKey)) return false
+        if (!bankPubDigest.contentEquals(other.bankPubDigest)) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = transactionKey.contentHashCode()
+        result = 31 * result + bankPubDigest.contentHashCode()
+        return result
+    }
+}
+
+
+@Suppress("SpellCheckingInspection")
+enum class EbicsReturnCode(val errorCode: String) {
+    EBICS_OK("000000"),
+    EBICS_DOWNLOAD_POSTPROCESS_DONE("011000"),
+    EBICS_DOWNLOAD_POSTPROCESS_SKIPPED("011001"),
+    EBICS_TX_SEGMENT_NUMBER_UNDERRUN("011101"),
+    EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005");
+
+    companion object {
+        fun lookup(errorCode: String): EbicsReturnCode {
+            for (x in values()) {
+                if (x.errorCode == errorCode) {
+                    return x;
+                }
+            }
+            throw EbicsUnknownReturnCodeError("Unknown return code: 
$errorCode")
+        }
+    }
+}
+
+data class EbicsResponseContent(
+    val transactionID: String?,
+    val dataEncryptionInfo: DataEncryptionInfo?,
+    val orderDataEncChunk: String?,
+    val technicalReturnCode: EbicsReturnCode,
+    val bankReturnCode: EbicsReturnCode
+)
+
+data class EbicsKeyManagementResponseContent(
+    val technicalReturnCode: EbicsReturnCode,
+    val bankReturnCode: EbicsReturnCode?,
+    val orderData: ByteArray?
+)
+
+fun parseAndDecryptEbicsKeyManagementResponse(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    responseStr: String
+): EbicsKeyManagementResponseContent {
+    val resp = try {
+        XMLUtil.convertStringToJaxb<EbicsKeyManagementResponse>(responseStr)
+    } catch (e: Exception) {
+        throw InvalidXmlError()
+    }
+    val retCode = EbicsReturnCode.lookup(resp.value.header.mutable.returnCode)
+
+    val daeXml = resp.value.body.dataTransfer?.dataEncryptionInfo
+    val orderData = if (daeXml != null) {
+        val dae = DataEncryptionInfo(daeXml.transactionKey, 
daeXml.encryptionPubKeyDigest.value)
+        val encOrderData = resp.value.body.dataTransfer?.orderData?.value ?: 
throw InvalidXmlError()
+        decryptAndDecompressResponse(subscriberDetails, dae, 
listOf(encOrderData))
+    } else {
+        null
+    }
+
+    val bankReturnCodeStr = resp.value.body.returnCode.value
+    val bankReturnCode = EbicsReturnCode.lookup(bankReturnCodeStr)
+
+    return EbicsKeyManagementResponseContent(retCode, bankReturnCode, 
orderData)
+}
+
+class HpbResponseData(
+    val hostID: String,
+    val encryptionPubKey: RSAPublicKey,
+    val encryptionVersion: String,
+    val authenticationPubKey: RSAPublicKey,
+    val authenticationVersion: String
+)
+
+fun parseEbicsHpbOrder(orderDataRaw: ByteArray): HpbResponseData {
+    val resp = try {
+        
XMLUtil.convertStringToJaxb<HPBResponseOrderData>(orderDataRaw.toString(Charsets.UTF_8))
+    } catch (e: Exception) {
+        throw InvalidXmlError()
+    }
+    val encPubKey = CryptoUtil.loadRsaPublicKeyFromComponents(
+        resp.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.modulus,
+        resp.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.exponent
+    )
+    val authPubKey = CryptoUtil.loadRsaPublicKeyFromComponents(
+        resp.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.modulus,
+        resp.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.exponent
+    )
+    return HpbResponseData(
+        hostID = resp.value.hostID,
+        encryptionPubKey = encPubKey,
+        encryptionVersion = resp.value.encryptionPubKeyInfo.encryptionVersion,
+        authenticationPubKey = authPubKey,
+        authenticationVersion = 
resp.value.authenticationPubKeyInfo.authenticationVersion
+    )
+}
+
+fun parseAndValidateEbicsResponse(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    responseStr: String
+): EbicsResponseContent {
+    val responseDocument = try {
+        XMLUtil.parseStringIntoDom(responseStr)
+    } catch (e: Exception) {
+        throw InvalidXmlError()
+    }
+
+    if (!XMLUtil.verifyEbicsDocument(
+            responseDocument,
+            subscriberDetails.bankAuthPub ?: throw 
InvalidSubscriberStateError()
+        )
+    ) {
+        throw BadSignatureError()
+    }
+    val resp = try {
+        XMLUtil.convertStringToJaxb<EbicsResponse>(responseStr)
+    } catch (e: Exception) {
+        throw InvalidXmlError()
+    }
+
+    val bankReturnCodeStr = resp.value.body.returnCode.value
+    val bankReturnCode = EbicsReturnCode.lookup(bankReturnCodeStr)
+
+    val techReturnCodeStr = resp.value.header.mutable.returnCode
+    val techReturnCode = EbicsReturnCode.lookup(techReturnCodeStr)
+
+    val daeXml = resp.value.body.dataTransfer?.dataEncryptionInfo
+    val dataEncryptionInfo = if (daeXml == null) {
+        null
+    } else {
+        DataEncryptionInfo(daeXml.transactionKey, 
daeXml.encryptionPubKeyDigest.value)
+    }
+
+    return EbicsResponseContent(
+        transactionID = resp.value.header._static.transactionID,
+        bankReturnCode = bankReturnCode,
+        technicalReturnCode = techReturnCode,
+        orderDataEncChunk = resp.value.body.dataTransfer?.orderData?.value,
+        dataEncryptionInfo = dataEncryptionInfo
+    )
+}
+
+/**
+ * Get the private key that matches the given public key digest.
+ */
+fun getDecryptionKey(subscriberDetails: EbicsClientSubscriberDetails, 
pubDigest: ByteArray): RSAPrivateCrtKey {
+    val authPub = 
CryptoUtil.getRsaPublicFromPrivate(subscriberDetails.customerAuthPriv)
+    val encPub = 
CryptoUtil.getRsaPublicFromPrivate(subscriberDetails.customerEncPriv)
+    val authPubDigest = CryptoUtil.getEbicsPublicKeyHash(authPub)
+    val encPubDigest = CryptoUtil.getEbicsPublicKeyHash(encPub)
+    if (pubDigest.contentEquals(authPubDigest)) {
+        return subscriberDetails.customerAuthPriv
+    }
+    if (pubDigest.contentEquals(encPubDigest)) {
+        return subscriberDetails.customerEncPriv
+    }
+    throw Exception("no matching private key to decrypt response")
+}
+
+/**
+ * Wrapper around the lower decryption routine, that takes a EBICS response
+ * object containing a encrypted payload, and return the plain version of it
+ * (including decompression).
+ */
+fun decryptAndDecompressResponse(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    encryptionInfo: DataEncryptionInfo,
+    chunks: List<String>
+): ByteArray {
+    val privateKey = getDecryptionKey(subscriberDetails, 
encryptionInfo.bankPubDigest)
+    val buf = StringBuilder()
+    chunks.forEach { buf.append(it) }
+    val decoded = Base64.getDecoder().decode(buf.toString())
+    val er = CryptoUtil.EncryptionResult(
+        encryptionInfo.transactionKey,
+        encryptionInfo.bankPubDigest,
+        decoded
+    )
+    val dataCompr = CryptoUtil.decryptEbicsE002(
+        er,
+        privateKey
+    )
+    return EbicsOrderUtil.decodeOrderData(dataCompr)
+}
+
+data class EbicsVersionSpec(
+    val protocol: String,
+    val version: String
+)
+
+data class EbicsHevDetails(
+    val versions: List<EbicsVersionSpec>
+)
+
+fun makeEbicsHEVRequest(subscriberDetails: EbicsClientSubscriberDetails): 
String {
+    val req = HEVRequest().apply {
+        hostId = subscriberDetails.hostId
+    }
+    val doc = XMLUtil.convertJaxbToDocument(req)
+    return XMLUtil.convertDomToString(doc)
+}
+
+fun parseEbicsHEVResponse(subscriberDetails: EbicsClientSubscriberDetails, 
respStr: String): EbicsHevDetails {
+    val resp = try {
+        XMLUtil.convertStringToJaxb<HEVResponse>(respStr)
+    } catch (e: Exception) {
+        logger.error("Exception while parsing HEV response", e)
+        throw InvalidXmlError()
+    }
+    val versions = resp.value.versionNumber.map { versionNumber ->
+        EbicsVersionSpec(versionNumber.protocolVersion, versionNumber.value)
+    }
+    return EbicsHevDetails(versions)
+}
+
+fun makeEbicsIniRequest(subscriberDetails: EbicsClientSubscriberDetails): 
String {
+    val iniRequest = EbicsUnsecuredRequest.createIni(
+        subscriberDetails.hostId,
+        subscriberDetails.userId,
+        subscriberDetails.partnerId,
+        subscriberDetails.customerSignPriv
+    )
+    val doc = XMLUtil.convertJaxbToDocument(iniRequest)
+    return XMLUtil.convertDomToString(doc)
+}
+
+fun makeEbicsHiaRequest(subscriberDetails: EbicsClientSubscriberDetails): 
String {
+    val hiaRequest = EbicsUnsecuredRequest.createHia(
+        subscriberDetails.hostId,
+        subscriberDetails.userId,
+        subscriberDetails.partnerId,
+        subscriberDetails.customerAuthPriv,
+        subscriberDetails.customerEncPriv
+    )
+    val doc = XMLUtil.convertJaxbToDocument(hiaRequest)
+    return XMLUtil.convertDomToString(doc)
+}
+
+fun makeEbicsHpbRequest(subscriberDetails: EbicsClientSubscriberDetails): 
String {
+    val hpbRequest = EbicsNpkdRequest.createRequest(
+        subscriberDetails.hostId,
+        subscriberDetails.partnerId,
+        subscriberDetails.userId,
+        getNonce(128),
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar())
+    )
+    val doc = XMLUtil.convertJaxbToDocument(hpbRequest)
+    XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv)
+    return XMLUtil.convertDomToString(doc)
+}
\ No newline at end of file
diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt
index a61b345..f4de82a 100644
--- a/util/src/main/kotlin/XMLUtil.kt
+++ b/util/src/main/kotlin/XMLUtil.kt
@@ -21,6 +21,8 @@ package tech.libeufin.util
 
 import com.sun.org.apache.xerces.internal.dom.DOMInputImpl
 import com.sun.xml.bind.marshaller.NamespacePrefixMapper
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
 import org.w3c.dom.Document
 import org.w3c.dom.Node
 import org.w3c.dom.NodeList
@@ -60,6 +62,8 @@ import javax.xml.xpath.XPath
 import javax.xml.xpath.XPathConstants
 import javax.xml.xpath.XPathFactory
 
+val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
+
 class DefaultNamespaces : NamespacePrefixMapper() {
     override fun getPreferredPrefix(namespaceUri: String?, suggestion: 
String?, requirePrefix: Boolean): String? {
         if (namespaceUri == "http://www.w3.org/2000/09/xmldsig#";) return "ds"
@@ -171,7 +175,7 @@ class XMLUtil private constructor() {
             try {
                 getEbicsValidator().validate(xmlDoc)
             } catch (e: Exception) {
-                LOGGER.warn("Validation failed: ${e}")
+                logger.warn("Validation failed: ${e}")
                 return false
             }
             return true;
diff --git a/util/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt 
b/util/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt
index d26bc4e..6db6eab 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt
+++ b/util/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt
@@ -97,6 +97,6 @@ class EbicsKeyManagementResponse {
     @XmlAccessorType(XmlAccessType.NONE)
     class OrderData {
         @get:XmlValue
-        lateinit var value: ByteArray
+        lateinit var value: String
     }
 }
diff --git a/util/src/main/kotlin/ebics_h004/EbicsRequest.kt 
b/util/src/main/kotlin/ebics_h004/EbicsRequest.kt
index d6ded86..71f5f8e 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsRequest.kt
+++ b/util/src/main/kotlin/ebics_h004/EbicsRequest.kt
@@ -230,7 +230,7 @@ class EbicsRequest {
         var signatureData: SignatureData? = null
 
         @get:XmlElement(name = "OrderData")
-        var orderData: ByteArray? = null
+        var orderData: String? = null
 
         @get:XmlElement(name = "HostID")
         var hostId: String? = null
@@ -317,7 +317,6 @@ class EbicsRequest {
 
         }
 
-
         fun createForDownloadInitializationPhase(
             userId: String,
             partnerId: String,
@@ -326,7 +325,8 @@ class EbicsRequest {
             date: XMLGregorianCalendar,
             bankEncPub: RSAPublicKey,
             bankAuthPub: RSAPublicKey,
-            aOrderType: String
+            myOrderType: String,
+            myOrderParams: OrderParams
         ): EbicsRequest {
             return EbicsRequest().apply {
                 version = "H004"
@@ -343,9 +343,9 @@ class EbicsRequest {
                         timestamp = date
                         partnerID = partnerId
                         orderDetails = OrderDetails().apply {
-                            orderType = aOrderType
+                            orderType = myOrderType
                             orderAttribute = "DZHNN"
-                            orderParams = StandardOrderParams()
+                            orderParams = myOrderParams
                         }
                         bankPubKeyDigests = BankPubKeyDigests().apply {
                             authentication = EbicsTypes.PubKeyDigest().apply {
@@ -370,7 +370,8 @@ class EbicsRequest {
         }
 
         fun createForUploadInitializationPhase(
-            cryptoBundle: CryptoUtil.EncryptionResult,
+            encryptedTransactionKey: ByteArray,
+            encryptedSignatureData: ByteArray,
             hostId: String,
             nonceArg: ByteArray,
             partnerId: String,
@@ -379,7 +380,8 @@ class EbicsRequest {
             bankAuthPub: RSAPublicKey,
             bankEncPub: RSAPublicKey,
             segmentsNumber: BigInteger,
-            aOrderType: String
+            aOrderType: String,
+            aOrderParams: OrderParams
         ): EbicsRequest {
 
             return EbicsRequest().apply {
@@ -396,7 +398,7 @@ class EbicsRequest {
                         orderDetails = OrderDetails().apply {
                             orderType = aOrderType
                             orderAttribute = "OZHNN"
-                            orderParams = StandardOrderParams()
+                            orderParams = aOrderParams
                         }
                         bankPubKeyDigests = BankPubKeyDigests().apply {
                             authentication = EbicsTypes.PubKeyDigest().apply {
@@ -423,10 +425,10 @@ class EbicsRequest {
                     dataTransfer = DataTransfer().apply {
                         signatureData = SignatureData().apply {
                             authenticate = true
-                            value = cryptoBundle.encryptedData
+                            value = encryptedSignatureData
                         }
                         dataEncryptionInfo = 
EbicsTypes.DataEncryptionInfo().apply {
-                            transactionKey = 
cryptoBundle.encryptedTransactionKey
+                            transactionKey = encryptedTransactionKey
                             authenticate = true
                             encryptionPubKeyDigest = 
EbicsTypes.PubKeyDigest().apply {
                                 algorithm = 
"http://www.w3.org/2001/04/xmlenc#sha256";
@@ -443,10 +445,8 @@ class EbicsRequest {
             hostId: String,
             transactionId: String,
             segNumber: BigInteger,
-            encryptedData: ByteArray
-
+            encryptedData: String
         ): EbicsRequest {
-
             return EbicsRequest().apply {
                 header = Header().apply {
                     version = "H004"
diff --git a/util/src/main/kotlin/ebics_hev/EbicsMessages.kt 
b/util/src/main/kotlin/ebics_hev/EbicsMessages.kt
index 5b9ff71..0d4302b 100644
--- a/util/src/main/kotlin/ebics_hev/EbicsMessages.kt
+++ b/util/src/main/kotlin/ebics_hev/EbicsMessages.kt
@@ -19,6 +19,7 @@
 
 package tech.libeufin.util.ebics_hev
 
+import java.util.*
 import javax.xml.bind.annotation.*
 import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
 import javax.xml.bind.annotation.adapters.NormalizedStringAdapter
@@ -45,7 +46,7 @@ class HEVResponse {
     lateinit var systemReturnCode: SystemReturnCodeType
 
     @get:XmlElement(name = "VersionNumber", namespace = 
"http://www.ebics.org/H000";)
-    var versionNumber: List<VersionNumber>? = null
+    var versionNumber: List<VersionNumber> = LinkedList()
 
     @get:XmlAnyElement(lax = true)
     var any: List<Any>? = null
diff --git a/util/src/main/kotlin/logger.kt b/util/src/main/kotlin/logger.kt
deleted file mode 100644
index f42b470..0000000
--- a/util/src/main/kotlin/logger.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package tech.libeufin.util
-
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-
-val LOGGER: Logger = LoggerFactory.getLogger("tech.libeufin.util")
\ No newline at end of file
diff --git a/util/src/main/kotlin/time.kt b/util/src/main/kotlin/time.kt
deleted file mode 100644
index 9db12d8..0000000
--- a/util/src/main/kotlin/time.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package tech.libeufin.util
-
-import java.util.*
-import javax.xml.datatype.DatatypeFactory
-import javax.xml.datatype.XMLGregorianCalendar
-
-/* now */
-fun getGregorianCalendarNow(): XMLGregorianCalendar {
-    val gregorianCalendar = GregorianCalendar()
-    val datatypeFactory = DatatypeFactory.newInstance()
-    return datatypeFactory.newXMLGregorianCalendar(gregorianCalendar)
-}
-
-/* explicit point in time */
-fun getGregorianCalendar(year: Int, month: Int, day: Int): 
XMLGregorianCalendar {
-    val gregorianCalendar = GregorianCalendar(year, month, day)
-    val datatypeFactory = DatatypeFactory.newInstance()
-    return datatypeFactory.newXMLGregorianCalendar(gregorianCalendar)
-}
\ No newline at end of file
diff --git a/util/src/test/kotlin/SignatureDataTest.kt 
b/util/src/test/kotlin/SignatureDataTest.kt
index c4239cc..2c4669d 100644
--- a/util/src/test/kotlin/SignatureDataTest.kt
+++ b/util/src/test/kotlin/SignatureDataTest.kt
@@ -4,8 +4,9 @@ import tech.libeufin.util.CryptoUtil
 import tech.libeufin.util.XMLUtil
 import tech.libeufin.util.ebics_h004.EbicsRequest
 import tech.libeufin.util.ebics_h004.EbicsTypes
-import tech.libeufin.util.getGregorianCalendarNow
 import java.math.BigInteger
+import java.util.*
+import javax.xml.datatype.DatatypeFactory
 
 class SignatureDataTest {
 
@@ -22,7 +23,7 @@ class SignatureDataTest {
                 static = EbicsRequest.StaticHeaderType().apply {
                     hostID = "some host ID"
                     nonce = "nonce".toByteArray()
-                    timestamp = getGregorianCalendarNow()
+                    timestamp = 
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar())
                     partnerID = "some partner ID"
                     userID = "some user ID"
                     orderDetails = EbicsRequest.OrderDetails().apply {

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]