gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] 01/01: EBICS 3.


From: gnunet
Subject: [libeufin] 01/01: EBICS 3.
Date: Thu, 29 Jun 2023 10:44:40 +0200

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

ms pushed a commit to branch master
in repository libeufin.

commit 06452b9adc4d149bdb1532a3ea3160909eb51c9a
Author: MS <ms@taler.net>
AuthorDate: Thu Jun 29 10:40:06 2023 +0200

    EBICS 3.
    
    Getting to both upload and download documents to/from the PostFinance test 
platform.
---
 .../tech/libeufin/nexus/ebics/EbicsClient.kt       |  92 +++--
 .../kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt |  68 +--
 nexus/src/test/kotlin/EbicsTest.kt                 |  69 ++--
 nexus/src/test/kotlin/PostFinance.kt               |  31 +-
 util/src/main/kotlin/Ebics.kt                      | 457 +++++++++++++--------
 util/src/main/kotlin/XMLUtil.kt                    |  22 +-
 util/src/main/kotlin/ebics_h005/Ebics3Request.kt   |  48 ++-
 util/src/main/kotlin/ebics_h005/Ebics3Response.kt  | 351 ++++++++++++++++
 util/src/main/kotlin/ebics_h005/Ebics3Types.kt     |   1 -
 util/src/main/kotlin/ebics_h005/package-info.java  |   1 +
 util/src/main/kotlin/ebics_s002/SignatureTypes.kt  |  91 ++++
 .../kotlin/ebics_s002/UserSignatureDataEbics3.kt   |  27 ++
 .../{ebics_h005 => ebics_s002}/package-info.java   |   5 +-
 13 files changed, 999 insertions(+), 264 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
index 8bd1db6c..f7944d55 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsClient.kt
@@ -112,16 +112,31 @@ suspend fun doEbicsDownloadTransaction(
 ): EbicsDownloadResult {
 
     // Initialization phase
-    val initDownloadRequestStr = createEbicsRequestForDownloadInitialization(
-        subscriberDetails,
-        fetchSpec.orderType,
-        fetchSpec.orderParams,
-        fetchSpec.ebics3Service
-    )
+    val initDownloadRequestStr = if (fetchSpec.isEbics3) {
+        if (fetchSpec.ebics3Service == null)
+            throw internalServerError("Expected EBICS 3 fetch spec but null 
was found.")
+        createEbicsRequestForDownloadInitialization(
+            subscriberDetails,
+            fetchSpec.ebics3Service,
+            fetchSpec.orderParams
+        )
+    }
+    else {
+        if (fetchSpec.orderType == null)
+            throw internalServerError("Expected EBICS 2.5 order type but null 
was found.")
+        createEbicsRequestForDownloadInitialization(
+            subscriberDetails,
+            fetchSpec.orderType,
+            fetchSpec.orderParams
+        )
+    }
     val payloadChunks = LinkedList<String>()
     val initResponseStr = client.postToBank(subscriberDetails.ebicsUrl, 
initDownloadRequestStr)
-    val initResponse = parseAndValidateEbicsResponse(subscriberDetails, 
initResponseStr)
-
+    val initResponse = parseAndValidateEbicsResponse(
+        subscriberDetails,
+        initResponseStr,
+        withEbics3 = fetchSpec.isEbics3
+    )
     val transactionID: String? = initResponse.transactionID
     // Checking for EBICS communication problems.
     when (initResponse.technicalReturnCode) {
@@ -192,7 +207,8 @@ suspend fun doEbicsDownloadTransaction(
                 subscriberDetails,
                 transactionID,
                 x,
-                numSegments
+                numSegments,
+                fetchSpec.isEbics3
             )
         logger.debug("EBICS download transfer phase of ${transactionID}: 
sending segment $x")
         val transferResponseStr = 
client.postToBank(subscriberDetails.ebicsUrl, transferReqStr)
@@ -236,13 +252,18 @@ suspend fun doEbicsDownloadTransaction(
     // Acknowledgement phase
     val ackRequest = createEbicsRequestForDownloadReceipt(
         subscriberDetails,
-        transactionID
+        transactionID,
+        fetchSpec.isEbics3
     )
     val ackResponseStr = client.postToBank(
         subscriberDetails.ebicsUrl,
         ackRequest
     )
-    val ackResponse = parseAndValidateEbicsResponse(subscriberDetails, 
ackResponseStr)
+    val ackResponse = parseAndValidateEbicsResponse(
+        subscriberDetails,
+        ackResponseStr,
+        withEbics3 = fetchSpec.isEbics3
+    )
     when (ackResponse.technicalReturnCode) {
         EbicsReturnCode.EBICS_DOWNLOAD_POSTPROCESS_DONE -> {
         }
@@ -263,28 +284,46 @@ suspend fun doEbicsDownloadTransaction(
 suspend fun doEbicsUploadTransaction(
     client: HttpClient,
     subscriberDetails: EbicsClientSubscriberDetails,
-    orderType: String? = null,
-    payload: ByteArray,
-    orderParams: EbicsOrderParams,
-    ebics3OrderService: Ebics3Request.OrderDetails.Service? = null
+    uploadSpec: EbicsUploadSpec,
+    payload: ByteArray
 ) {
     if (subscriberDetails.bankEncPub == null) {
         throw NexusError(HttpStatusCode.BadRequest,
             "bank encryption key unknown, request HPB first"
         )
     }
-    val preparedUploadData = prepareUploadPayload(subscriberDetails, payload)
-    val req = createEbicsRequestForUploadInitialization(
+    val preparedUploadData = prepareUploadPayload(
         subscriberDetails,
-        orderType,
-        orderParams,
-        preparedUploadData,
-        ebics3OrderService = ebics3OrderService
+        payload,
+        isEbics3 = uploadSpec.isEbics3
     )
+    val req: String = if (uploadSpec.isEbics3) {
+        if (uploadSpec.ebics3Service == null)
+            throw internalServerError("EBICS 3 service data was expected, but 
null was found.")
+        createEbicsRequestForUploadInitialization(
+            subscriberDetails,
+            uploadSpec.ebics3Service,
+            uploadSpec.orderParams,
+            preparedUploadData
+        )
+    } else {
+        if (uploadSpec.orderType == null)
+            throw internalServerError("EBICS 2.5 order type was expected, but 
null was found.")
+        createEbicsRequestForUploadInitialization(
+            subscriberDetails,
+            uploadSpec.orderType,
+            uploadSpec.orderParams ?: EbicsStandardOrderParams(),
+            preparedUploadData
+        )
+    }
     logger.debug("EBICS upload message to: ${subscriberDetails.ebicsUrl}")
     val responseStr = client.postToBank(subscriberDetails.ebicsUrl, req)
 
-    val initResponse = parseAndValidateEbicsResponse(subscriberDetails, 
responseStr)
+    val initResponse = parseAndValidateEbicsResponse(
+        subscriberDetails,
+        responseStr,
+        withEbics3 = uploadSpec.isEbics3
+    )
     // The bank indicated one error, hence Nexus sent invalid data.
     if (initResponse.technicalReturnCode != EbicsReturnCode.EBICS_OK) {
         throw NexusError(
@@ -311,13 +350,18 @@ suspend fun doEbicsUploadTransaction(
         subscriberDetails,
         transactionID,
         preparedUploadData,
-        0
+        0,
+        withEbics3 = uploadSpec.isEbics3
     )
     val txRespStr = client.postToBank(
         subscriberDetails.ebicsUrl,
         ebicsPayload
     )
-    val txResp = parseAndValidateEbicsResponse(subscriberDetails, txRespStr)
+    val txResp = parseAndValidateEbicsResponse(
+        subscriberDetails,
+        txRespStr,
+        withEbics3 = uploadSpec.isEbics3
+    )
     when (txResp.technicalReturnCode) {
         EbicsReturnCode.EBICS_OK -> {/* do nothing */}
         else -> {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
index d1c44e0f..5e1c14d1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsNexus.kt
@@ -78,26 +78,21 @@ data class EbicsFetchSpec(
     val ebics3Service: Ebics3Request.OrderDetails.Service? = null, // unused 
for 2.5
     // Not always available, for example at raw POST 
/download/${ebicsMessageName} calls.
     // It helps to trace back the original level.
-    val originalLevel: FetchLevel? = null
+    val originalLevel: FetchLevel? = null,
+    val isEbics3: Boolean = false
 )
 
 /**
- * Maps EBICS specific history types to their camt
- * counterparts.  That allows the database to store
- * camt types per-se, without any reference to the
- * EBICS message that brought them.  For example, a
- * EBICS "Z52" and "C52" will both bring a camt.052.
- * Such camt.052 is associated with the more generic
- * type of FetchLevel.REPORT.
+ * Collects EBICS 2.5 and/or 3.0 parameters for a unified
+ * way of passing parameters.  Individual helpers will then
+ * act according to the EBICS version.
  */
-private fun getFetchLevelFromEbicsOrder(ebicsHistoryType: String): FetchLevel {
-    return when(ebicsHistoryType) {
-        "C52", "Z52" -> FetchLevel.REPORT
-        "C53", "Z53" -> FetchLevel.STATEMENT
-        "C54", "Z54" -> FetchLevel.NOTIFICATION
-        else -> throw internalServerError("EBICS history type 
'$ebicsHistoryType' not supported")
-    }
-}
+data class EbicsUploadSpec(
+    val isEbics3: Boolean = false,
+    val ebics3Service: Ebics3Request.OrderDetails.Service? = null, // unused 
for 2.5
+    val orderType: String? = null,
+    val orderParams: EbicsOrderParams? = null
+)
 
 // Validate and store the received document for later ingestion.
 private fun validateAndStoreCamt(
@@ -206,7 +201,6 @@ private suspend fun fetchEbicsTransactions(
         // re-throw in any other error case.
         throw e
     }
-
     handleEbicsDownloadResult(
         response,
         bankConnectionId,
@@ -523,10 +517,17 @@ private fun getNotificationSpecAfterDialect(dialect: 
String? = null, p: EbicsOrd
             orderParams = p,
             ebics3Service = Ebics3Request.OrderDetails.Service().apply {
                 serviceName = "REP"
-                messageName = "camt.054"
+                messageName = 
Ebics3Request.OrderDetails.Service.MessageName().apply {
+                    value = "camt.054"
+                    version = "04"
+                }
                 scope = "CH"
+                container = 
Ebics3Request.OrderDetails.Service.Container().apply {
+                    containerType = "ZIP"
+                }
             },
-            originalLevel = FetchLevel.NOTIFICATION
+            originalLevel = FetchLevel.NOTIFICATION,
+            isEbics3 = true
         )
         else -> EbicsFetchSpec(
             orderType = "C54",
@@ -737,7 +738,7 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol {
                     subscriberDetails
                 )
             } catch (e: Exception) {
-                logger.warn("Fetching transactions (${spec.orderType}) 
excepted: ${e.message}.")
+                logger.warn("Fetching transactions (${spec.originalLevel}) 
excepted: ${e.message}.")
                 errors.add(e)
             }
         }
@@ -782,20 +783,27 @@ class EbicsBankConnectionProtocol: BankConnectionProtocol 
{
                 val subscriberDetails = subscriberDetails
             }
         }
-        // Used to validate here, then removed for performances reasons.
-        doEbicsUploadTransaction(
-            httpClient,
-            dbData.subscriberDetails,
-            getSubmissionTypeAfterDialect(dbData.subscriberDetails.dialect),
-            dbData.painXml.toByteArray(Charsets.UTF_8),
-            EbicsStandardOrderParams(),
-            ebics3OrderService = if (dbData.subscriberDetails.dialect == "pf") 
{
+        val isPoFi = dbData.subscriberDetails.dialect == "pf"
+        val uploadSpec = EbicsUploadSpec(
+            isEbics3 = isPoFi,
+            orderType = if (!isPoFi) 
getSubmissionTypeAfterDialect(dbData.subscriberDetails.dialect) else null,
+            orderParams = EbicsStandardOrderParams(),
+            ebics3Service = if (isPoFi)
                 Ebics3Request.OrderDetails.Service().apply {
                     serviceName = "MCT"
                     scope = "CH"
-                    messageName = "pain.001"
+                    messageName = 
Ebics3Request.OrderDetails.Service.MessageName().apply {
+                        value = "pain.001"
+                        version = "09"
+                    }
                 }
-            } else null
+                else null
+        )
+        doEbicsUploadTransaction(
+            httpClient,
+            dbData.subscriberDetails,
+            uploadSpec,
+            dbData.painXml.toByteArray(Charsets.UTF_8)
         )
         transaction {
             val payment = getPaymentInitiation(paymentInitiationId)
diff --git a/nexus/src/test/kotlin/EbicsTest.kt 
b/nexus/src/test/kotlin/EbicsTest.kt
index 7fe09f38..19646d6a 100644
--- a/nexus/src/test/kotlin/EbicsTest.kt
+++ b/nexus/src/test/kotlin/EbicsTest.kt
@@ -13,9 +13,7 @@ import tech.libeufin.nexus.*
 import tech.libeufin.nexus.bankaccount.addPaymentInitiation
 import tech.libeufin.nexus.bankaccount.fetchBankAccountTransactions
 import tech.libeufin.nexus.bankaccount.submitAllPaymentInitiations
-import tech.libeufin.nexus.ebics.EbicsBankConnectionProtocol
-import tech.libeufin.nexus.ebics.doEbicsUploadTransaction
-import tech.libeufin.nexus.ebics.getEbicsSubscriberDetails
+import tech.libeufin.nexus.ebics.*
 import tech.libeufin.nexus.iso20022.NexusPaymentInitiationData
 import tech.libeufin.nexus.iso20022.createPain001document
 import tech.libeufin.nexus.server.FetchLevel
@@ -204,9 +202,12 @@ class DownloadAndSubmit {
                         doEbicsUploadTransaction(
                             client,
                             unallowedSubscriber,
-                            "CCT",
-                            painMessage.toByteArray(Charsets.UTF_8),
-                            EbicsStandardOrderParams()
+                            EbicsUploadSpec(
+                                orderType = "CCT",
+                                isEbics3 = false,
+                                orderParams = EbicsStandardOrderParams()
+                            ),
+                            painMessage.toByteArray(Charsets.UTF_8)
                         )
                     } catch (e: EbicsProtocolError) {
                         if (e.ebicsTechnicalCode ==
@@ -322,36 +323,56 @@ class DownloadAndSubmit {
 
 class EbicsTest {
 
+    @Test
+    fun genEbics3Upload() {
+        withTestDatabase {
+            prepNexusDb()
+            val foo = transaction { getEbicsSubscriberDetails("foo") }
+            val uploadDoc = createEbicsRequestForUploadInitialization(
+                subscriberDetails = foo,
+                ebics3OrderService = 
Ebics3Request.OrderDetails.Service().apply {
+                    serviceName = "OTH"
+                    scope = "BIL"
+                    serviceOption = "CH002LMF"
+                    messageName = 
Ebics3Request.OrderDetails.Service.MessageName().apply {
+                        value = "csv"
+                    }
+                },
+                null,
+                prepareUploadPayload(
+                    foo,
+                    "foo".toByteArray(),
+                    isEbics3 = true
+                )
+            )
+            assert(XMLUtil.validateFromString(uploadDoc))
+        }
+    }
+
     /**
      * Tests the validity of EBICS 3.0 messages.
      */
     @Test
-    fun genEbics3() {
+    fun genEbics3Download() {
         withTestDatabase {
             prepNexusDb()
             val foo = transaction { getEbicsSubscriberDetails("foo") }
             val downloadDoc = createEbicsRequestForDownloadInitialization(
-                foo,
-                orderType = null, // triggers 3.0
-                EbicsStandardOrderParams(),
-                Ebics3Request.OrderDetails.Service().apply {
-                    messageName = "camt.054"
+                subscriberDetails = foo,
+                ebics3OrderService = 
Ebics3Request.OrderDetails.Service().apply {
+                    messageName = 
Ebics3Request.OrderDetails.Service.MessageName().apply {
+                        value = "camt.054"
+                        version = "04"
+                    }
                     scope = "CH"
                     serviceName = "REP"
-                }
+                    container = 
Ebics3Request.OrderDetails.Service.Container().apply {
+                        containerType = "ZIP"
+                    }
+                },
+                orderParams = EbicsStandardOrderParams()
             )
             assert(XMLUtil.validateFromString(downloadDoc))
-            val uploadDoc = createEbicsRequestForDownloadInitialization(
-                foo,
-                orderType = null, // triggers 3.0
-                EbicsStandardOrderParams(),
-                Ebics3Request.OrderDetails.Service().apply {
-                    messageName = "pain.001"
-                    scope = "EU"
-                    serviceName = "MCT"
-                }
-            )
-            assert(XMLUtil.validateFromString(uploadDoc))
         }
     }
 }
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/PostFinance.kt 
b/nexus/src/test/kotlin/PostFinance.kt
index fd798dc1..daec6c77 100644
--- a/nexus/src/test/kotlin/PostFinance.kt
+++ b/nexus/src/test/kotlin/PostFinance.kt
@@ -5,16 +5,18 @@ import org.jetbrains.exposed.sql.transactions.transaction
 import tech.libeufin.nexus.bankaccount.addPaymentInitiation
 import tech.libeufin.nexus.bankaccount.fetchBankAccountTransactions
 import tech.libeufin.nexus.bankaccount.getBankAccount
+import tech.libeufin.nexus.ebics.EbicsUploadSpec
 import tech.libeufin.nexus.ebics.doEbicsUploadTransaction
 import tech.libeufin.nexus.ebics.getEbicsSubscriberDetails
 import tech.libeufin.nexus.getConnectionPlugin
 import tech.libeufin.nexus.getNexusUser
 import tech.libeufin.nexus.server.*
 import tech.libeufin.util.EbicsStandardOrderParams
+import tech.libeufin.util.ebics_h005.Ebics3Request
 import java.io.BufferedReader
 import java.io.File
 
-// Submits a Z54 to the bank, expecting a camt.054 back.
+// Asks a camt.054 to the bank.
 private fun downloadPayment() {
     val httpClient = HttpClient()
     runBlocking {
@@ -42,15 +44,24 @@ private fun uploadQrrPayment() {
         doEbicsUploadTransaction(
             httpClient,
             getEbicsSubscriberDetails("postfinance"),
-            "XTC",
-            qrr.toByteArray(Charsets.UTF_8),
-            EbicsStandardOrderParams()
+            EbicsUploadSpec(
+                ebics3Service = Ebics3Request.OrderDetails.Service().apply {
+                    serviceName = "OTH"
+                    scope = "BIL"
+                    serviceOption = "CH002LMF"
+                    messageName = 
Ebics3Request.OrderDetails.Service.MessageName().apply {
+                        value = "csv"
+                    }
+                },
+                isEbics3 = true
+            ),
+            qrr.toByteArray(Charsets.UTF_8)
         )
     }
 }
 
 /**
- * Submits a XE2 (+ pain.001 version 2019) message to the bank.
+ * Submits a pain.001 version 2019 message to the bank.
  *
  * Causes one DBIT payment to show up in the camt.054.  This one
  * however lacks the AcctSvcrRef, so other ways to pin it are needed.
@@ -75,13 +86,12 @@ private fun uploadPain001Payment() {
     }
     val ebicsConn = getConnectionPlugin("ebics")
     val httpClient = HttpClient()
-    runBlocking {
-        ebicsConn.submitPaymentInitiation(httpClient, 1L)
-    }
+    runBlocking { ebicsConn.submitPaymentInitiation(httpClient, 1L) }
 }
 
 fun main() {
     // Loads EBICS subscriber's keys from disk.
+    // The keys should be found under libeufin-internal.git/convenience/
     val bufferedReader: BufferedReader = 
File("/tmp/pofi.json").bufferedReader()
     val accessDataTxt = bufferedReader.use { it.readText() }
     val ebicsConn = getConnectionPlugin("ebics")
@@ -102,6 +112,7 @@ fun main() {
             fooBankAccount.iban = "CH9789144829733648596"
         }
     }
-    // uploadQrrPayment()
-    // downloadPayment()
+    uploadQrrPayment()
+    downloadPayment()
+    uploadPain001Payment()
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
index ae5e5cda..2d90dd2c 100644
--- a/util/src/main/kotlin/Ebics.kt
+++ b/util/src/main/kotlin/Ebics.kt
@@ -29,7 +29,7 @@ import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.util.ebics_h004.*
 import tech.libeufin.util.ebics_h005.Ebics3Request
-import tech.libeufin.util.ebics_h005.Ebics3Types
+import tech.libeufin.util.ebics_h005.Ebics3Response
 import tech.libeufin.util.ebics_hev.HEVRequest
 import tech.libeufin.util.ebics_hev.HEVResponse
 import tech.libeufin.util.ebics_s001.UserSignatureData
@@ -40,6 +40,7 @@ import java.security.interfaces.RSAPublicKey
 import java.time.ZonedDateTime
 import java.util.*
 import java.util.zip.DeflaterInputStream
+import javax.xml.bind.JAXBElement
 import javax.xml.datatype.DatatypeFactory
 import javax.xml.datatype.XMLGregorianCalendar
 
@@ -167,15 +168,48 @@ private fun signOrder(
     return userSignatureData
 }
 
+private fun signOrderEbics3(
+    orderBlob: ByteArray,
+    signKey: RSAPrivateCrtKey,
+    partnerId: String,
+    userId: String
+): tech.libeufin.util.ebics_s002.UserSignatureDataEbics3 {
+    val ES_signature = CryptoUtil.signEbicsA006(
+        CryptoUtil.digestEbicsOrderA006(orderBlob),
+        signKey
+    )
+    val userSignatureData = 
tech.libeufin.util.ebics_s002.UserSignatureDataEbics3().apply {
+        orderSignatureList = listOf(
+            
tech.libeufin.util.ebics_s002.UserSignatureDataEbics3.OrderSignatureData().apply
 {
+                signatureVersion = "A006"
+                signatureValue = ES_signature
+                partnerID = partnerId
+                userID = userId
+            }
+        )
+    }
+    return userSignatureData
+}
+
 fun createEbicsRequestForDownloadReceipt(
     subscriberDetails: EbicsClientSubscriberDetails,
-    transactionID: String?
+    transactionID: String?,
+    withEbics3: Boolean = false
 ): String {
-    val req = EbicsRequest.createForDownloadReceiptPhase(
-        transactionID,
-        subscriberDetails.hostId
-    )
-    val doc = XMLUtil.convertJaxbToDocument(req)
+    val doc = if (withEbics3) {
+        val req = Ebics3Request.createForDownloadReceiptPhase(
+            transactionID,
+            subscriberDetails.hostId
+        )
+        XMLUtil.convertJaxbToDocument(req)
+
+    } else {
+        val req = EbicsRequest.createForDownloadReceiptPhase(
+            transactionID,
+            subscriberDetails.hostId
+        )
+        XMLUtil.convertJaxbToDocument(req)
+    }
     XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv)
     return XMLUtil.convertDomToString(doc)
 }
@@ -183,6 +217,7 @@ fun createEbicsRequestForDownloadReceipt(
 data class PreparedUploadData(
     val transactionKey: ByteArray,
     val userSignatureDataEncrypted: ByteArray,
+    val dataDigest: ByteArray,
     val encryptedPayloadChunks: List<String>
 ) {
     override fun equals(other: Any?): Boolean {
@@ -206,32 +241,90 @@ data class PreparedUploadData(
     }
 }
 
-fun prepareUploadPayload(subscriberDetails: EbicsClientSubscriberDetails, 
payload: ByteArray): PreparedUploadData {
-    val userSignatureDataEncrypted = CryptoUtil.encryptEbicsE002(
-        EbicsOrderUtil.encodeOrderDataXml(
-            signOrder(
-                payload,
-                subscriberDetails.customerSignPriv,
-                subscriberDetails.partnerId,
-                subscriberDetails.userId
-            )
-        ),
-        subscriberDetails.bankEncPub!!
-    )
+fun prepareUploadPayload(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    payload: ByteArray,
+    isEbics3: Boolean = false
+): PreparedUploadData {
+    // First A006-sign the payload, then E002-encrypt with bank's pub.
+    val encryptionResult = if (isEbics3) {
+        val innerSignedEbicsXml = signOrderEbics3( // A006 signature.
+            payload,
+            subscriberDetails.customerSignPriv,
+            subscriberDetails.partnerId,
+            subscriberDetails.userId
+        )
+        val userSignatureDataEncrypted = CryptoUtil.encryptEbicsE002(
+            EbicsOrderUtil.encodeOrderDataXml(innerSignedEbicsXml),
+            subscriberDetails.bankEncPub!!
+        )
+        userSignatureDataEncrypted
+    } else {
+        val innerSignedEbicsXml = signOrder( // A006 signature.
+            payload,
+            subscriberDetails.customerSignPriv,
+            subscriberDetails.partnerId,
+            subscriberDetails.userId
+        )
+        val userSignatureDataEncrypted = CryptoUtil.encryptEbicsE002(
+            EbicsOrderUtil.encodeOrderDataXml(innerSignedEbicsXml),
+            subscriberDetails.bankEncPub!!
+        )
+        userSignatureDataEncrypted
+    }
+    // Then only E002 symmetric (with ephemeral key) encrypt.
     val compressedInnerPayload = DeflaterInputStream(
         payload.inputStream()
     ).use { it.readAllBytes() }
     val encryptedPayload = CryptoUtil.encryptEbicsE002withTransactionKey(
         compressedInnerPayload,
         subscriberDetails.bankEncPub!!,
-        userSignatureDataEncrypted.plainTransactionKey!!
+        encryptionResult.plainTransactionKey!!
     )
     val encodedEncryptedPayload = 
Base64.getEncoder().encodeToString(encryptedPayload.encryptedData)
+
     return PreparedUploadData(
-        userSignatureDataEncrypted.encryptedTransactionKey,
-        userSignatureDataEncrypted.encryptedData,
-        listOf(encodedEncryptedPayload)
+        encryptionResult.encryptedTransactionKey, // ephemeral key
+        encryptionResult.encryptedData, // bank-pub-encrypted A006 signature.
+        CryptoUtil.digestEbicsOrderA006(payload), // used by EBICS 3
+        listOf(encodedEncryptedPayload) // actual payload E002 encrypted.
+    )
+}
+
+// Creates the EBICS 3 upload init request.
+fun createEbicsRequestForUploadInitialization(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    ebics3OrderService: Ebics3Request.OrderDetails.Service,
+    orderParams: EbicsOrderParams? = null,
+    preparedUploadData: PreparedUploadData
+): String {
+    val nonce = getNonce(128)
+    val req = Ebics3Request.createForUploadInitializationPhase(
+        preparedUploadData.transactionKey,
+        preparedUploadData.userSignatureDataEncrypted,
+        preparedUploadData.dataDigest,
+        subscriberDetails.hostId,
+        nonce,
+        subscriberDetails.partnerId,
+        subscriberDetails.userId,
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        subscriberDetails.bankAuthPub!!,
+        subscriberDetails.bankEncPub!!,
+        BigInteger.ONE,
+        ebics3OrderService
+    )
+    val doc = XMLUtil.convertJaxbToDocument(
+        req,
+        withSchemaLocation = "urn:org:ebics:H005 ebics_request_H005.xsd"
+    )
+    logger.debug("Created EBICS 3 document for upload initialization," +
+            " nonce: ${nonce.toHexString()}")
+    XMLUtil.signEbicsDocument(
+        doc,
+        subscriberDetails.customerAuthPriv,
+        withEbics3 = true
     )
+    return XMLUtil.convertDomToString(doc)
 }
 
 /**
@@ -241,124 +334,96 @@ fun prepareUploadPayload(subscriberDetails: 
EbicsClientSubscriberDetails, payloa
  */
 fun createEbicsRequestForUploadInitialization(
     subscriberDetails: EbicsClientSubscriberDetails,
-    orderType: String? = null,
+    orderType: String,
     orderParams: EbicsOrderParams,
-    preparedUploadData: PreparedUploadData,
-    ebics3OrderService: Ebics3Request.OrderDetails.Service? = null
+    preparedUploadData: PreparedUploadData
 ): String {
-    // Check if the call is consistent: (only) ONE instruction is expected.
-    if (orderType == null && ebics3OrderService == null)
-        throw internalServerError("Need exactly one upload instruction but 
zero was found.")
-    if (orderType != null && ebics3OrderService != null)
-        throw internalServerError("Need exactly one upload instruction but two 
were found")
     val nonce = getNonce(128)
-    val doc = if (orderType != null) {
-        val req = EbicsRequest.createForUploadInitializationPhase(
-            preparedUploadData.transactionKey,
-            preparedUploadData.userSignatureDataEncrypted,
-            subscriberDetails.hostId,
-            nonce,
-            subscriberDetails.partnerId,
-            subscriberDetails.userId,
-            
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
-            subscriberDetails.bankAuthPub!!,
-            subscriberDetails.bankEncPub!!,
-            BigInteger.ONE,
-            orderType,
-            makeOrderParams(orderParams)
-        )
-        XMLUtil.convertJaxbToDocument(req)
-    } else {
-        val req = Ebics3Request.createForUploadInitializationPhase(
-            preparedUploadData.transactionKey,
-            preparedUploadData.userSignatureDataEncrypted,
-            subscriberDetails.hostId,
-            nonce,
-            subscriberDetails.partnerId,
-            subscriberDetails.userId,
-            
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
-            subscriberDetails.bankAuthPub!!,
-            subscriberDetails.bankEncPub!!,
-            BigInteger.ONE,
-            ebics3OrderService!!
-        )
-        XMLUtil.convertJaxbToDocument(req)
-    }
-    /**
-     * FIXME: this log should be made by the caller.
-     * That way, all the EBICS transaction steps would be logged in only one 
function,
-     * as opposed to have them spread through the helpers here.  This function
-     * returning a string blocks now, since the caller should parse and 
stringify
-     * again the message, only to get its nonce.
-     */
+    val req = EbicsRequest.createForUploadInitializationPhase(
+        preparedUploadData.transactionKey,
+        preparedUploadData.userSignatureDataEncrypted,
+        subscriberDetails.hostId,
+        nonce,
+        subscriberDetails.partnerId,
+        subscriberDetails.userId,
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        subscriberDetails.bankAuthPub!!,
+        subscriberDetails.bankEncPub!!,
+        BigInteger.ONE,
+        orderType,
+        makeOrderParams(orderParams)
+    )
+    val doc = XMLUtil.convertJaxbToDocument(req)
     logger.debug("Created EBICS $orderType document for upload 
initialization," +
             " nonce: ${nonce.toHexString()}")
+    XMLUtil.signEbicsDocument(
+        doc,
+        subscriberDetails.customerAuthPriv
+    )
+    return XMLUtil.convertDomToString(doc)
+}
+
+// Generates a EBICS 2.5 signed document for the download init phase.
+fun createEbicsRequestForDownloadInitialization(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    orderType: String,
+    orderParams: EbicsOrderParams
+): String {
+    val nonce = getNonce(128)
+    val req = EbicsRequest.createForDownloadInitializationPhase(
+        subscriberDetails.userId,
+        subscriberDetails.partnerId,
+        subscriberDetails.hostId,
+        nonce,
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        subscriberDetails.bankEncPub ?: throw EbicsProtocolError(
+            HttpStatusCode.BadRequest,
+            "Invalid subscriber state 'bankEncPub' missing, please send HPB 
first"
+        ),
+        subscriberDetails.bankAuthPub ?: throw EbicsProtocolError(
+            HttpStatusCode.BadRequest,
+            "Invalid subscriber state 'bankAuthPub' missing, please send HPB 
first"
+        ),
+        orderType,
+        makeOrderParams(orderParams)
+    )
+    logger.debug("Created EBICS document for download initialization, nonce: 
${nonce.toHexString()}")
+    val doc = XMLUtil.convertJaxbToDocument(req)
     XMLUtil.signEbicsDocument(
         doc,
         subscriberDetails.customerAuthPriv,
-        withEbics3 = ebics3OrderService != null
+        withEbics3 = false
     )
     return XMLUtil.convertDomToString(doc)
 }
 
 fun createEbicsRequestForDownloadInitialization(
     subscriberDetails: EbicsClientSubscriberDetails,
-    orderType: String? = null,
+    ebics3OrderService: Ebics3Request.OrderDetails.Service,
     orderParams: EbicsOrderParams,
-    ebics3OrderService: Ebics3Request.OrderDetails.Service? = null
 ): String {
-    // Check if the call is consistent: (only) ONE instruction is expected.
-    if (orderType == null && ebics3OrderService == null)
-        throw internalServerError("Need exactly one download instruction but 
zero was found.")
-    if (orderType != null && ebics3OrderService != null)
-        throw internalServerError("Need exactly one download instruction but 
two were found")
     val nonce = getNonce(128)
-
-    val doc = if (orderType != null) {
-        val req = EbicsRequest.createForDownloadInitializationPhase(
-            subscriberDetails.userId,
-            subscriberDetails.partnerId,
-            subscriberDetails.hostId,
-            nonce,
-            
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
-            subscriberDetails.bankEncPub ?: throw EbicsProtocolError(
-                HttpStatusCode.BadRequest,
-                "Invalid subscriber state 'bankEncPub' missing, please send 
HPB first"
-            ),
-            subscriberDetails.bankAuthPub ?: throw EbicsProtocolError(
-                HttpStatusCode.BadRequest,
-                "Invalid subscriber state 'bankAuthPub' missing, please send 
HPB first"
-            ),
-            orderType,
-            makeOrderParams(orderParams)
-        )
-        XMLUtil.convertJaxbToDocument(req)
-    } else {
-        val req = Ebics3Request.createForDownloadInitializationPhase(
-            subscriberDetails.userId,
-            subscriberDetails.partnerId,
-            subscriberDetails.hostId,
-            nonce,
-            
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
-            subscriberDetails.bankEncPub ?: throw EbicsProtocolError(
-                HttpStatusCode.BadRequest,
-                "Invalid subscriber state 'bankEncPub' missing, please send 
HPB first"
-            ),
-            subscriberDetails.bankAuthPub ?: throw EbicsProtocolError(
-                HttpStatusCode.BadRequest,
-                "Invalid subscriber state 'bankAuthPub' missing, please send 
HPB first"
-            ),
-            ebics3OrderService!!
-        )
-        XMLUtil.convertJaxbToDocument(req)
-    }
-
-    logger.debug("Created EBICS $orderType document for download 
initialization," +
-            " nonce: ${nonce.toHexString()}")
+    val req = Ebics3Request.createForDownloadInitializationPhase(
+        subscriberDetails.userId,
+        subscriberDetails.partnerId,
+        subscriberDetails.hostId,
+        nonce,
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        subscriberDetails.bankEncPub ?: throw EbicsProtocolError(
+            HttpStatusCode.BadRequest,
+            "Invalid subscriber state 'bankEncPub' missing, please send HPB 
first"
+        ),
+        subscriberDetails.bankAuthPub ?: throw EbicsProtocolError(
+            HttpStatusCode.BadRequest,
+            "Invalid subscriber state 'bankAuthPub' missing, please send HPB 
first"
+        ),
+        ebics3OrderService
+    )
+    val doc = XMLUtil.convertJaxbToDocument(req)
     XMLUtil.signEbicsDocument(
         doc,
         subscriberDetails.customerAuthPriv,
-        withEbics3 = ebics3OrderService != null
+        withEbics3 = true
     )
     return XMLUtil.convertDomToString(doc)
 }
@@ -367,16 +432,31 @@ fun createEbicsRequestForDownloadTransferPhase(
     subscriberDetails: EbicsClientSubscriberDetails,
     transactionID: String?,
     segmentNumber: Int,
-    numSegments: Int
+    numSegments: Int,
+    withEbics3: Boolean = false
 ): String {
-    val req = EbicsRequest.createForDownloadTransferPhase(
-        subscriberDetails.hostId,
-        transactionID,
-        segmentNumber,
-        numSegments
+    val doc = if (withEbics3) {
+        val req = Ebics3Request.createForDownloadTransferPhase(
+            subscriberDetails.hostId,
+            transactionID,
+            segmentNumber,
+            numSegments
+        )
+        XMLUtil.convertJaxbToDocument(req)
+    } else {
+        val req = EbicsRequest.createForDownloadTransferPhase(
+            subscriberDetails.hostId,
+            transactionID,
+            segmentNumber,
+            numSegments
+        )
+        XMLUtil.convertJaxbToDocument(req)
+    }
+    XMLUtil.signEbicsDocument(
+        doc,
+        subscriberDetails.customerAuthPriv,
+        withEbics3 = withEbics3
     )
-    val doc = XMLUtil.convertJaxbToDocument(req)
-    XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv)
     return XMLUtil.convertDomToString(doc)
 }
 
@@ -384,17 +464,29 @@ fun createEbicsRequestForUploadTransferPhase(
     subscriberDetails: EbicsClientSubscriberDetails,
     transactionID: String?,
     preparedUploadData: PreparedUploadData,
-    chunkIndex: Int
+    chunkIndex: Int,
+    withEbics3: Boolean = false
 ): 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)
+    val doc = if (withEbics3) {
+        val req = Ebics3Request.createForUploadTransferPhase(
+            subscriberDetails.hostId,
+            transactionID,
+            // chunks are 1-indexed
+            BigInteger.valueOf(chunkIndex.toLong() + 1),
+            preparedUploadData.encryptedPayloadChunks[chunkIndex]
+        )
+        XMLUtil.convertJaxbToDocument(req)
+    } else {
+        val req = EbicsRequest.createForUploadTransferPhase(
+            subscriberDetails.hostId,
+            transactionID,
+            // chunks are 1-indexed
+            BigInteger.valueOf(chunkIndex.toLong() + 1),
+            preparedUploadData.encryptedPayloadChunks[chunkIndex]
+        )
+        XMLUtil.convertJaxbToDocument(req)
+    }
+    XMLUtil.signEbicsDocument(doc, subscriberDetails.customerAuthPriv, 
withEbics3)
     return XMLUtil.convertDomToString(doc)
 }
 
@@ -434,6 +526,7 @@ enum class EbicsReturnCode(val errorCode: String) {
     EBICS_ACCOUNT_AUTHORISATION_FAILED("091302"),
     EBICS_AMOUNT_CHECK_FAILED("091303"),
     EBICS_EBICS_AUTHORISATION_ORDER_IDENTIFIER_FAILED("090003"),
+    EBICS_INVALID_XML("091010"),
     EBICS_NO_DOWNLOAD_DATA_AVAILABLE("090005");
 
     companion object {
@@ -525,40 +618,51 @@ fun parseEbicsHpbOrder(orderDataRaw: ByteArray): 
HpbResponseData {
     )
 }
 
-fun parseAndValidateEbicsResponse(
-    subscriberDetails: EbicsClientSubscriberDetails,
-    responseStr: String
-): EbicsResponseContent {
-    val responseDocument = try {
-        XMLUtil.parseStringIntoDom(responseStr)
+private fun ebics3toInternalRepr(response: String): EbicsResponseContent {
+    val resp: JAXBElement<Ebics3Response> = try {
+        XMLUtil.convertStringToJaxb(response)
     } catch (e: Exception) {
         throw EbicsProtocolError(
             HttpStatusCode.InternalServerError,
-            "Invalid XML (as EbicsResponse) received from bank"
+            "Could not transform string-response from bank into JAXB"
         )
     }
-    if (!XMLUtil.verifyEbicsDocument(
-            responseDocument,
-            subscriberDetails.bankAuthPub ?: throw EbicsProtocolError(
-                HttpStatusCode.InternalServerError,
-                "Bank's signature verification failed"
-            )
-        )
-    ) {
-        throw EbicsProtocolError(
-            HttpStatusCode.InternalServerError,
-            "Bank's signature verification failed"
-        )
+    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 reportText = resp.value.header.mutable.reportText
+
+    val daeXml = resp.value.body.dataTransfer?.dataEncryptionInfo
+    val dataEncryptionInfo = if (daeXml == null) {
+        null
+    } else {
+        DataEncryptionInfo(daeXml.transactionKey, 
daeXml.encryptionPubKeyDigest.value)
     }
-    val resp = try {
-        XMLUtil.convertStringToJaxb<EbicsResponse>(responseStr)
+
+    return EbicsResponseContent(
+        transactionID = resp.value.header._static.transactionID,
+        bankReturnCode = bankReturnCode,
+        technicalReturnCode = techReturnCode,
+        reportText = reportText,
+        orderDataEncChunk = resp.value.body.dataTransfer?.orderData?.value,
+        dataEncryptionInfo = dataEncryptionInfo,
+        numSegments = resp.value.header._static.numSegments?.toInt(),
+        segmentNumber = resp.value.header.mutable.segmentNumber?.value?.toInt()
+    )
+}
+
+private fun ebics25toInternalRepr(response: String): EbicsResponseContent {
+    val resp: JAXBElement<EbicsResponse> = try {
+        XMLUtil.convertStringToJaxb(response)
     } catch (e: Exception) {
         throw EbicsProtocolError(
             HttpStatusCode.InternalServerError,
             "Could not transform string-response from bank into JAXB"
         )
     }
-
     val bankReturnCodeStr = resp.value.body.returnCode.value
     val bankReturnCode = EbicsReturnCode.lookup(bankReturnCodeStr)
 
@@ -585,6 +689,37 @@ fun parseAndValidateEbicsResponse(
         segmentNumber = resp.value.header.mutable.segmentNumber?.value?.toInt()
     )
 }
+fun parseAndValidateEbicsResponse(
+    subscriberDetails: EbicsClientSubscriberDetails,
+    responseStr: String,
+    withEbics3: Boolean = false
+): EbicsResponseContent {
+    val responseDocument = try {
+        XMLUtil.parseStringIntoDom(responseStr)
+    } catch (e: Exception) {
+        throw EbicsProtocolError(
+            HttpStatusCode.InternalServerError,
+            "Invalid XML (as EbicsResponse) received from bank"
+        )
+    }
+    if (!XMLUtil.verifyEbicsDocument(
+            responseDocument,
+            subscriberDetails.bankAuthPub ?: throw EbicsProtocolError(
+                HttpStatusCode.InternalServerError,
+                "Bank's signature verification failed"
+            ),
+            withEbics3 = withEbics3
+        )
+    ) {
+        throw EbicsProtocolError(
+            HttpStatusCode.InternalServerError,
+            "Bank's signature verification failed"
+        )
+    }
+    if (withEbics3)
+        return ebics3toInternalRepr(responseStr)
+    return ebics25toInternalRepr(responseStr)
+}
 
 /**
  * Get the private key that matches the given public key digest.
diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt
index 6d923ae6..97c56180 100644
--- a/util/src/main/kotlin/XMLUtil.kt
+++ b/util/src/main/kotlin/XMLUtil.kt
@@ -66,11 +66,11 @@ import logger
 class DefaultNamespaces : NamespacePrefixMapper() {
     override fun getPreferredPrefix(namespaceUri: String?, suggestion: 
String?, requirePrefix: Boolean): String? {
         if (namespaceUri == "http://www.w3.org/2000/09/xmldsig#";) return "ds"
+        if (namespaceUri == XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI) 
return "xsi"
         return null
     }
 }
 
-
 class DOMInputImpl : LSInput {
     var fPublicId: String? = null
     var fSystemId: String? = null
@@ -292,23 +292,35 @@ class XMLUtil private constructor() {
             return validate(xmlSource)
         }
 
-        inline fun <reified T> convertJaxbToString(obj: T): String {
+        inline fun <reified T> convertJaxbToString(
+            obj: T,
+            withSchemaLocation: String? = null
+            ): String {
             val sw = StringWriter()
             val jc = JAXBContext.newInstance(T::class.java)
             val m = jc.createMarshaller()
             m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
+            if (withSchemaLocation != null) {
+                m.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, 
withSchemaLocation)
+            }
             m.setProperty("com.sun.xml.bind.namespacePrefixMapper", 
DefaultNamespaces())
             m.marshal(obj, sw)
             return sw.toString()
         }
 
-        inline fun <reified T> convertJaxbToDocument(obj: T): Document {
+        inline fun <reified T> convertJaxbToDocument(
+            obj: T,
+            withSchemaLocation: String? = null
+        ): Document {
             val dbf: DocumentBuilderFactory = 
DocumentBuilderFactory.newInstance()
             dbf.isNamespaceAware = true
             val doc = dbf.newDocumentBuilder().newDocument()
             val jc = JAXBContext.newInstance(T::class.java)
             val m = jc.createMarshaller()
             m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
+            if (withSchemaLocation != null) {
+                m.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, 
withSchemaLocation)
+            }
             m.setProperty("com.sun.xml.bind.namespacePrefixMapper", 
DefaultNamespaces())
             m.marshal(obj, doc)
             return doc
@@ -340,8 +352,6 @@ class XMLUtil private constructor() {
             val tf = TransformerFactory.newInstance()
             val t = tf.newTransformer()
 
-            //t.setOutputProperty(OutputKeys.INDENT, "yes")
-
             /* Make string writer.  */
             val sw = StringWriter()
 
@@ -409,7 +419,7 @@ class XMLUtil private constructor() {
             doc: Document,
             signingPriv: PrivateKey,
             withEbics3: Boolean = false
-        ): Unit {
+        ) {
             val xpath = XPathFactory.newInstance().newXPath()
             xpath.namespaceContext = object : NamespaceContext {
                 override fun getNamespaceURI(p0: String?): String {
diff --git a/util/src/main/kotlin/ebics_h005/Ebics3Request.kt 
b/util/src/main/kotlin/ebics_h005/Ebics3Request.kt
index b9bcf2ab..2af81601 100644
--- a/util/src/main/kotlin/ebics_h005/Ebics3Request.kt
+++ b/util/src/main/kotlin/ebics_h005/Ebics3Request.kt
@@ -163,8 +163,7 @@ class Ebics3Request {
         lateinit var adminOrderType: String
 
         @XmlAccessorType(XmlAccessType.NONE)
-        @XmlType(propOrder = ["serviceName", "scope", "messageName"])
-
+        @XmlType(propOrder = ["serviceName", "scope", "serviceOption", 
"container", "messageName"])
         class Service {
             @get:XmlElement(name = "ServiceName", required = true)
             @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
@@ -174,9 +173,31 @@ class Ebics3Request {
             @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
             lateinit var scope: String
 
+            @XmlAccessorType(XmlAccessType.NONE)
+            @XmlType(name = "", propOrder = ["value"])
+            class MessageName {
+                @XmlValue
+                lateinit var value: String
+
+                @XmlAttribute(name = "version")
+                var version: String? = null
+            }
+
             @get:XmlElement(name = "MsgName", required = true)
+            lateinit var messageName: MessageName
+
+            @get:XmlElement(name = "ServiceOption", required = true)
             @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
-            lateinit var messageName: String
+            var serviceOption: String? = null
+
+            @XmlAccessorType(XmlAccessType.NONE)
+            class Container {
+                @XmlAttribute(name = "containerType")
+                lateinit var containerType: String
+            }
+
+            @get:XmlElement(name = "Container", required = true)
+            var container: Container? = null
         }
 
         @XmlAccessorType(XmlAccessType.NONE)
@@ -250,9 +271,8 @@ class Ebics3Request {
         var value: ByteArray? = null
     }
 
-
     @XmlAccessorType(XmlAccessType.NONE)
-    @XmlType(propOrder = ["dataEncryptionInfo", "signatureData", "orderData", 
"hostId"])
+    @XmlType(propOrder = ["dataEncryptionInfo", "signatureData", "dataDigest", 
"orderData", "hostId"])
     class DataTransfer {
 
         @get:XmlElement(name = "DataEncryptionInfo")
@@ -261,6 +281,18 @@ class Ebics3Request {
         @get:XmlElement(name = "SignatureData")
         var signatureData: SignatureData? = null
 
+        @XmlAccessorType(XmlAccessType.NONE)
+        class DataDigest {
+            @get:XmlAttribute(name = "SignatureVersion", required = true)
+            var signatureVersion: String = "A006"
+
+            @get:XmlValue
+            var value: ByteArray? = null
+        }
+
+        @get:XmlElement(name = "DataDigest")
+        var dataDigest: DataDigest? = null
+
         @get:XmlElement(name = "OrderData")
         var orderData: String? = null
 
@@ -411,6 +443,7 @@ class Ebics3Request {
         fun createForUploadInitializationPhase(
             encryptedTransactionKey: ByteArray,
             encryptedSignatureData: ByteArray,
+            aDataDigest: ByteArray,
             hostId: String,
             nonceArg: ByteArray,
             partnerId: String,
@@ -436,7 +469,7 @@ class Ebics3Request {
                         userID = userId
                         orderDetails = OrderDetails().apply {
                             this.adminOrderType = "BTU"
-                            this.btdOrderParams = 
OrderDetails.BTOrderParams().apply {
+                            this.btuOrderParams = 
OrderDetails.BTOrderParams().apply {
                                 service = aOrderService
                             }
                         }
@@ -467,6 +500,9 @@ class Ebics3Request {
                             authenticate = true
                             value = encryptedSignatureData
                         }
+                        dataDigest = DataTransfer.DataDigest().apply {
+                            value = aDataDigest
+                        }
                         dataEncryptionInfo = 
Ebics3Types.DataEncryptionInfo().apply {
                             transactionKey = encryptedTransactionKey
                             authenticate = true
diff --git a/util/src/main/kotlin/ebics_h005/Ebics3Response.kt 
b/util/src/main/kotlin/ebics_h005/Ebics3Response.kt
new file mode 100644
index 00000000..56de5ffb
--- /dev/null
+++ b/util/src/main/kotlin/ebics_h005/Ebics3Response.kt
@@ -0,0 +1,351 @@
+package tech.libeufin.util.ebics_h005
+
+import org.apache.xml.security.binding.xmldsig.SignatureType
+import org.apache.xml.security.binding.xmldsig.SignedInfoType
+import tech.libeufin.util.CryptoUtil
+import tech.libeufin.util.XMLUtil
+import tech.libeufin.util.ebics_h004.EbicsTypes
+import java.math.BigInteger
+import javax.xml.bind.annotation.*
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
+import javax.xml.bind.annotation.adapters.NormalizedStringAdapter
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter
+import kotlin.math.min
+
+@XmlAccessorType(XmlAccessType.NONE)
+@XmlType(name = "", propOrder = ["header", "authSignature", "body"])
+@XmlRootElement(name = "ebicsResponse")
+class Ebics3Response {
+    @get:XmlAttribute(name = "Version", required = true)
+    @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
+    lateinit var version: String
+
+    @get:XmlAttribute(name = "Revision")
+    var revision: Int? = null
+
+    @get:XmlElement(required = true)
+    lateinit var header: Header
+
+    @get:XmlElement(name = "AuthSignature", required = true)
+    lateinit var authSignature: SignatureType
+
+    @get:XmlElement(required = true)
+    lateinit var body: Body
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(name = "", propOrder = ["_static", "mutable"])
+    class Header {
+        @get:XmlElement(name = "static", required = true)
+        lateinit var _static: StaticHeaderType
+
+        @get:XmlElement(required = true)
+        lateinit var mutable: MutableHeaderType
+
+        @get:XmlAttribute(name = "authenticate", required = true)
+        var authenticate: Boolean = false
+    }
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(name = "", propOrder = ["dataTransfer", "returnCode", 
"timestampBankParameter"])
+    class Body {
+        @get:XmlElement(name = "DataTransfer")
+        var dataTransfer: DataTransferResponseType? = null
+
+        @get:XmlElement(name = "ReturnCode", required = true)
+        lateinit var returnCode: ReturnCode
+
+        @get:XmlElement(name = "TimestampBankParameter")
+        var timestampBankParameter: EbicsTypes.TimestampBankParameter? = null
+    }
+
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(
+        name = "",
+        propOrder = ["transactionPhase", "segmentNumber", "orderID", 
"returnCode", "reportText"]
+    )
+    class MutableHeaderType {
+        @get:XmlElement(name = "TransactionPhase", required = true)
+        @get:XmlSchemaType(name = "token")
+        lateinit var transactionPhase: EbicsTypes.TransactionPhaseType
+
+        @get:XmlElement(name = "SegmentNumber")
+        var segmentNumber: EbicsTypes.SegmentNumber? = null
+
+        @get:XmlElement(name = "OrderID")
+        @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
+        @get:XmlSchemaType(name = "token")
+        var orderID: String? = null
+
+        @get:XmlElement(name = "ReturnCode", required = true)
+        @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
+        @get:XmlSchemaType(name = "token")
+        lateinit var returnCode: String
+
+        @get:XmlElement(name = "ReportText", required = true)
+        @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class)
+        @get:XmlSchemaType(name = "normalizedString")
+        lateinit var reportText: String
+    }
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    class OrderData {
+        @get:XmlValue
+        lateinit var value: String
+    }
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    class ReturnCode {
+        @get:XmlValue
+        @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
+        lateinit var value: String
+
+        @get:XmlAttribute(name = "authenticate", required = true)
+        var authenticate: Boolean = false
+    }
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(name = "DataTransferResponseType", propOrder = 
["dataEncryptionInfo", "orderData"])
+    class DataTransferResponseType {
+        @get:XmlElement(name = "DataEncryptionInfo")
+        var dataEncryptionInfo: EbicsTypes.DataEncryptionInfo? = null
+
+        @get:XmlElement(name = "OrderData", required = true)
+        lateinit var orderData: OrderData
+    }
+
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(name = "ResponseStaticHeaderType", propOrder = ["transactionID", 
"numSegments"])
+    class StaticHeaderType {
+        @get:XmlElement(name = "TransactionID")
+        var transactionID: String? = null
+
+        @get:XmlElement(name = "NumSegments")
+        @get:XmlSchemaType(name = "positiveInteger")
+        var numSegments: BigInteger? = null
+    }
+
+    companion object {
+
+        fun createForUploadWithError(
+            errorText: String, errorCode: String, phase: 
EbicsTypes.TransactionPhaseType
+        ): Ebics3Response {
+            val resp = Ebics3Response().apply {
+                this.version = "H005"
+                this.revision = 1
+                this.header = Ebics3Response.Header().apply {
+                    this.authenticate = true
+                    this.mutable = Ebics3Response.MutableHeaderType().apply {
+                        this.reportText = errorText
+                        this.returnCode = errorCode
+                        this.transactionPhase = phase
+                    }
+                    _static = Ebics3Response.StaticHeaderType()
+                }
+                this.authSignature = SignatureType()
+                this.body = Ebics3Response.Body().apply {
+                    this.returnCode = Ebics3Response.ReturnCode().apply {
+                        this.authenticate = true
+                        this.value = errorCode
+                    }
+                }
+            }
+            return resp
+        }
+
+        fun createForUploadInitializationPhase(transactionID: String, orderID: 
String): Ebics3Response {
+            return Ebics3Response().apply {
+                this.version = "H005"
+                this.revision = 1
+                this.header = Header().apply {
+                    this.authenticate = true
+                    this._static = StaticHeaderType().apply {
+                        this.transactionID = transactionID
+                    }
+                    this.mutable = MutableHeaderType().apply {
+                        this.transactionPhase =
+                            EbicsTypes.TransactionPhaseType.INITIALISATION
+                        this.orderID = orderID
+                        this.reportText = "[EBICS_OK] OK"
+                        this.returnCode = "000000"
+                    }
+                }
+                this.authSignature = SignatureType()
+                this.body = Body().apply {
+                    this.returnCode = ReturnCode().apply {
+                        this.authenticate = true
+                        this.value = "000000"
+                    }
+                }
+            }
+        }
+
+        fun createForDownloadReceiptPhase(transactionID: String, positiveAck: 
Boolean): Ebics3Response {
+            return Ebics3Response().apply {
+                this.version = "H005"
+                this.revision = 1
+                this.header = Header().apply {
+                    this.authenticate = true
+                    this._static = StaticHeaderType().apply {
+                        this.transactionID = transactionID
+                    }
+                    this.mutable = MutableHeaderType().apply {
+                        this.transactionPhase =
+                            EbicsTypes.TransactionPhaseType.RECEIPT
+                        if (positiveAck) {
+                            this.reportText = 
"[EBICS_DOWNLOAD_POSTPROCESS_DONE] Received positive receipt"
+                            this.returnCode = "011000"
+                        } else {
+                            this.reportText = 
"[EBICS_DOWNLOAD_POSTPROCESS_SKIPPED] Received negative receipt"
+                            this.returnCode = "011001"
+                        }
+                    }
+                }
+                this.authSignature = SignatureType()
+                this.body = Body().apply {
+                    this.returnCode = ReturnCode().apply {
+                        this.authenticate = true
+                        this.value = "000000"
+                    }
+                }
+            }
+        }
+
+        fun createForUploadTransferPhase(
+            transactionID: String,
+            segmentNumber: Int,
+            lastSegment: Boolean,
+            orderID: String
+        ): Ebics3Response {
+            return Ebics3Response().apply {
+                this.version = "H005"
+                this.revision = 1
+                this.header = Header().apply {
+                    this.authenticate = true
+                    this._static = StaticHeaderType().apply {
+                        this.transactionID = transactionID
+                    }
+                    this.mutable = MutableHeaderType().apply {
+                        this.transactionPhase =
+                            EbicsTypes.TransactionPhaseType.TRANSFER
+                        this.segmentNumber = EbicsTypes.SegmentNumber().apply {
+                            this.value = 
BigInteger.valueOf(segmentNumber.toLong())
+                            if (lastSegment) {
+                                this.lastSegment = true
+                            }
+                        }
+                        this.orderID = orderID
+                        this.reportText = "[EBICS_OK] OK"
+                        this.returnCode = "000000"
+                    }
+                }
+                this.authSignature = SignatureType()
+                this.body = Body().apply {
+                    this.returnCode = ReturnCode().apply {
+                        this.authenticate = true
+                        this.value = "000000"
+                    }
+                }
+            }
+        }
+
+        /**
+         * @param requestedSegment requested segment as a 1-based index
+         */
+        fun createForDownloadTransferPhase(
+            transactionID: String,
+            numSegments: Int,
+            segmentSize: Int,
+            encodedData: String,
+            requestedSegment: Int
+        ): Ebics3Response {
+            return Ebics3Response().apply {
+                this.version = "H005"
+                this.revision = 1
+                this.header = Header().apply {
+                    this.authenticate = true
+                    this._static = StaticHeaderType().apply {
+                        this.transactionID = transactionID
+                        this.numSegments = 
BigInteger.valueOf(numSegments.toLong())
+                    }
+                    this.mutable = MutableHeaderType().apply {
+                        this.transactionPhase =
+                            EbicsTypes.TransactionPhaseType.TRANSFER
+                        this.segmentNumber = EbicsTypes.SegmentNumber().apply {
+                            this.lastSegment = numSegments == requestedSegment
+                            this.value = 
BigInteger.valueOf(requestedSegment.toLong())
+                        }
+                        this.reportText = "[EBICS_OK] OK"
+                        this.returnCode = "000000"
+                    }
+                }
+                this.authSignature = SignatureType()
+                this.body = Body().apply {
+                    this.returnCode = ReturnCode().apply {
+                        this.authenticate = true
+                        this.value = "000000"
+                    }
+                    this.dataTransfer = DataTransferResponseType().apply {
+                        this.orderData = OrderData().apply {
+                            val start = segmentSize * (requestedSegment - 1)
+                            this.value = encodedData.substring(start, 
min(start + segmentSize, encodedData.length))
+                        }
+                    }
+                }
+            }
+        }
+
+        fun createForDownloadInitializationPhase(
+            transactionID: String,
+            numSegments: Int,
+            segmentSize: Int,
+            enc: CryptoUtil.EncryptionResult,
+            encodedData: String
+        ): Ebics3Response {
+            return Ebics3Response().apply {
+                this.version = "H005"
+                this.revision = 1
+                this.header = Header().apply {
+                    this.authenticate = true
+                    this._static = StaticHeaderType().apply {
+                        this.transactionID = transactionID
+                        this.numSegments = 
BigInteger.valueOf(numSegments.toLong())
+                    }
+                    this.mutable = MutableHeaderType().apply {
+                        this.transactionPhase =
+                            EbicsTypes.TransactionPhaseType.INITIALISATION
+                        this.segmentNumber = EbicsTypes.SegmentNumber().apply {
+                            this.lastSegment = (numSegments == 1)
+                            this.value = BigInteger.valueOf(1)
+                        }
+                        this.reportText = "[EBICS_OK] OK"
+                        this.returnCode = "000000"
+                    }
+                }
+                this.authSignature = SignatureType()
+                this.body = Body().apply {
+                    this.returnCode = ReturnCode().apply {
+                        this.authenticate = true
+                        this.value = "000000"
+                    }
+                    this.dataTransfer = DataTransferResponseType().apply {
+                        this.dataEncryptionInfo = 
EbicsTypes.DataEncryptionInfo().apply {
+                            this.authenticate = true
+                            this.encryptionPubKeyDigest = 
EbicsTypes.PubKeyDigest()
+                                .apply {
+                                this.algorithm = 
"http://www.w3.org/2001/04/xmlenc#sha256";
+                                this.version = "E002"
+                                this.value = enc.pubKeyDigest
+                            }
+                            this.transactionKey = enc.encryptedTransactionKey
+                        }
+                        this.orderData = OrderData().apply {
+                            this.value = encodedData.substring(0, 
min(segmentSize, encodedData.length))
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/util/src/main/kotlin/ebics_h005/Ebics3Types.kt 
b/util/src/main/kotlin/ebics_h005/Ebics3Types.kt
index 74c7af0f..2bb0659a 100644
--- a/util/src/main/kotlin/ebics_h005/Ebics3Types.kt
+++ b/util/src/main/kotlin/ebics_h005/Ebics3Types.kt
@@ -63,7 +63,6 @@ object Ebics3Types {
         var lastSegment: Boolean? = null
     }
 
-
     @XmlType(name = "", propOrder = ["encryptionPubKeyDigest", 
"transactionKey"])
     @XmlAccessorType(XmlAccessType.NONE)
     class DataEncryptionInfo {
diff --git a/util/src/main/kotlin/ebics_h005/package-info.java 
b/util/src/main/kotlin/ebics_h005/package-info.java
index 3300f657..6f65a4b2 100644
--- a/util/src/main/kotlin/ebics_h005/package-info.java
+++ b/util/src/main/kotlin/ebics_h005/package-info.java
@@ -8,5 +8,6 @@
         elementFormDefault = XmlNsForm.QUALIFIED
 )
 package tech.libeufin.util.ebics_h005;
+import javax.xml.bind.annotation.XmlNs;
 import javax.xml.bind.annotation.XmlNsForm;
 import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file
diff --git a/util/src/main/kotlin/ebics_s002/SignatureTypes.kt 
b/util/src/main/kotlin/ebics_s002/SignatureTypes.kt
new file mode 100644
index 00000000..9e367fc9
--- /dev/null
+++ b/util/src/main/kotlin/ebics_s002/SignatureTypes.kt
@@ -0,0 +1,91 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2019 Stanisci and Dold.
+
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
+ * Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+package tech.libeufin.util.ebics_s002
+
+import org.apache.xml.security.binding.xmldsig.RSAKeyValueType
+import org.apache.xml.security.binding.xmldsig.X509DataType
+import javax.xml.bind.annotation.*
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter
+import javax.xml.datatype.XMLGregorianCalendar
+
+
+object SignatureTypes {
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(
+        name = "PubKeyValueType", namespace = "http://www.ebics.org/S002";, 
propOrder = [
+            "rsaKeyValue",
+            "timeStamp"
+        ]
+    )
+    class PubKeyValueType {
+        @get:XmlElement(name = "RSAKeyValue", namespace = 
"http://www.w3.org/2000/09/xmldsig#";, required = true)
+        lateinit var rsaKeyValue: RSAKeyValueType
+
+        @get:XmlElement(name = "TimeStamp")
+        @get:XmlSchemaType(name = "dateTime")
+        var timeStamp: XMLGregorianCalendar? = null
+    }
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(
+        name = "",
+        propOrder = [
+            "x509Data",
+            "pubKeyValue",
+            "signatureVersion"
+        ]
+    )
+    class SignaturePubKeyInfoType {
+        @get:XmlElement(name = "X509Data")
+        var x509Data: X509DataType? = null
+
+        @get:XmlElement(name = "PubKeyValue", required = true)
+        lateinit var pubKeyValue: PubKeyValueType
+
+        @get:XmlElement(name = "SignatureVersion", required = true)
+        @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
+        lateinit var signatureVersion: String
+    }
+
+    /**
+     * EBICS INI payload.
+     */
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(
+        name = "",
+        propOrder = ["signaturePubKeyInfo", "partnerID", "userID"]
+    )
+    @XmlRootElement(name = "SignaturePubKeyOrderData")
+    class SignaturePubKeyOrderData {
+        @get:XmlElement(name = "SignaturePubKeyInfo", required = true)
+        lateinit var signaturePubKeyInfo: SignaturePubKeyInfoType
+
+        @get:XmlElement(name = "PartnerID", required = true)
+        @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
+        @get:XmlSchemaType(name = "token")
+        lateinit var partnerID: String
+
+        @get:XmlElement(name = "UserID", required = true)
+        @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class)
+        @get:XmlSchemaType(name = "token")
+        lateinit var userID: String
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt 
b/util/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt
new file mode 100644
index 00000000..6d7012a1
--- /dev/null
+++ b/util/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt
@@ -0,0 +1,27 @@
+package tech.libeufin.util.ebics_s002
+
+import javax.xml.bind.annotation.*
+
+@XmlAccessorType(XmlAccessType.NONE)
+@XmlRootElement(name = "UserSignatureData")
+@XmlType(name = "", propOrder = ["orderSignatureList"])
+class UserSignatureDataEbics3 {
+    @XmlElement(name = "OrderSignatureData", type = OrderSignatureData::class)
+    var orderSignatureList: List<OrderSignatureData>? = null
+
+    @XmlAccessorType(XmlAccessType.NONE)
+    @XmlType(name = "", propOrder = ["signatureVersion", "signatureValue", 
"partnerID", "userID"])
+    class OrderSignatureData {
+        @XmlElement(name = "SignatureVersion")
+        lateinit var signatureVersion: String
+
+        @XmlElement(name = "SignatureValue")
+        lateinit var signatureValue: ByteArray
+
+        @XmlElement(name = "PartnerID")
+        lateinit var partnerID: String
+
+        @XmlElement(name = "UserID")
+        lateinit var userID: String
+    }
+}
\ No newline at end of file
diff --git a/util/src/main/kotlin/ebics_h005/package-info.java 
b/util/src/main/kotlin/ebics_s002/package-info.java
similarity index 63%
copy from util/src/main/kotlin/ebics_h005/package-info.java
copy to util/src/main/kotlin/ebics_s002/package-info.java
index 3300f657..a9f7729a 100644
--- a/util/src/main/kotlin/ebics_h005/package-info.java
+++ b/util/src/main/kotlin/ebics_s002/package-info.java
@@ -4,9 +4,10 @@
  */
 
 @XmlSchema(
-        namespace = "urn:org:ebics:H005",
+        namespace = "http://www.ebics.org/S002";,
         elementFormDefault = XmlNsForm.QUALIFIED
 )
-package tech.libeufin.util.ebics_h005;
+package tech.libeufin.util.ebics_s002;
+
 import javax.xml.bind.annotation.XmlNsForm;
 import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

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