gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: EBICS (3) upload doer and helpers.


From: gnunet
Subject: [libeufin] branch master updated: EBICS (3) upload doer and helpers.
Date: Wed, 25 Oct 2023 13:23:08 +0200

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

ms pushed a commit to branch master
in repository libeufin.

The following commit(s) were added to refs/heads/master by this push:
     new 7769c74a EBICS (3) upload doer and helpers.
7769c74a is described below

commit 7769c74a2f4e756ebf8f1ca79a885810ebc75765
Author: MS <ms@taler.net>
AuthorDate: Wed Oct 25 13:21:49 2023 +0200

    EBICS (3) upload doer and helpers.
---
 .../src/main/kotlin/tech/libeufin/nexus/Ebics3.kt  |  90 +++++++++++++
 .../main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt | 142 ++++++++++++++++++++-
 util/src/main/kotlin/Ebics.kt                      |  54 +-------
 3 files changed, 229 insertions(+), 57 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Ebics3.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Ebics3.kt
new file mode 100644
index 00000000..e94d8105
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Ebics3.kt
@@ -0,0 +1,90 @@
+package tech.libeufin.nexus
+
+import tech.libeufin.util.PreparedUploadData
+import tech.libeufin.util.XMLUtil
+import tech.libeufin.util.ebics_h005.Ebics3Request
+import tech.libeufin.util.getNonce
+import tech.libeufin.util.toHexString
+import java.math.BigInteger
+import java.util.*
+import javax.xml.datatype.DatatypeFactory
+
+/**
+ * Creates the EBICS 3 document for the init phase of an upload
+ * transaction.
+ *
+ * @param cfg configuration handle.
+ * @param preparedUploadData business payload to send.
+ * @param bankkeys bank public keys.
+ * @param clientKeys client private keys.
+ * @param orderService EBICS 3 document defining the request type
+ * @return raw XML of the EBICS 3 init phase.
+ */
+fun createEbics3RequestForUploadInitialization(
+    cfg: EbicsSetupConfig,
+    preparedUploadData: PreparedUploadData,
+    bankkeys: BankPublicKeysFile,
+    clientKeys: ClientPrivateKeysFile,
+    orderService: Ebics3Request.OrderDetails.Service
+): String {
+    val nonce = getNonce(128)
+    val req = Ebics3Request.createForUploadInitializationPhase(
+        preparedUploadData.transactionKey,
+        preparedUploadData.userSignatureDataEncrypted,
+        preparedUploadData.dataDigest,
+        cfg.ebicsHostId,
+        nonce,
+        cfg.ebicsPartnerId,
+        cfg.ebicsUserId,
+        
DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar()),
+        bankkeys.bank_authentication_public_key,
+        bankkeys.bank_encryption_public_key,
+        BigInteger.ONE,
+        orderService
+    )
+    val doc = XMLUtil.convertJaxbToDocument(
+        req,
+        withSchemaLocation = "urn:org:ebics:H005 ebics_request_H005.xsd"
+    )
+    tech.libeufin.util.logger.debug("Created EBICS 3 document for upload 
initialization," +
+            " nonce: ${nonce.toHexString()}")
+    XMLUtil.signEbicsDocument(
+        doc,
+        clientKeys.authentication_private_key,
+        withEbics3 = true
+    )
+    return XMLUtil.convertDomToString(doc)
+}
+
+/**
+ * Crafts one EBICS 3 request for the upload transfer phase.  Currently
+ * only 1-chunk payloads are supported.
+ *
+ * @param cfg configuration handle.
+ * @param clientKeys client private keys.
+ * @param transactionId EBICS transaction ID obtained from an init phase.
+ * @param uploadData business content to upload.
+ *
+ * @return raw XML document.
+ */
+fun createEbics3RequestForUploadTransferPhase(
+    cfg: EbicsSetupConfig,
+    clientKeys: ClientPrivateKeysFile,
+    transactionId: String,
+    uploadData: PreparedUploadData
+): String {
+    val chunkIndex = 1 // only 1-chunk communication currently supported.
+    val req = Ebics3Request.createForUploadTransferPhase(
+        cfg.ebicsHostId,
+        transactionId,
+        BigInteger.valueOf(chunkIndex.toLong()),
+        uploadData.encryptedPayloadChunks[chunkIndex]
+    )
+    val doc = XMLUtil.convertJaxbToDocument(req)
+    XMLUtil.signEbicsDocument(
+        doc,
+        clientKeys.authentication_private_key,
+        withEbics3 = true
+    )
+    return XMLUtil.convertDomToString(doc)
+}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt
index 2a6f7b9f..3609c72d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics.kt
@@ -42,15 +42,16 @@ import io.ktor.client.plugins.*
 import io.ktor.client.request.*
 import io.ktor.client.statement.*
 import io.ktor.http.*
-import tech.libeufin.nexus.BankPublicKeysFile
-import tech.libeufin.nexus.ClientPrivateKeysFile
-import tech.libeufin.nexus.EbicsSetupConfig
+import tech.libeufin.nexus.*
 import tech.libeufin.util.*
+import tech.libeufin.util.ebics_h005.Ebics3Request
+import tech.libeufin.util.logger
 import java.io.ByteArrayOutputStream
 import java.security.interfaces.RSAPrivateCrtKey
 import java.time.LocalDateTime
 import java.time.format.DateTimeFormatter
 import java.util.*
+import java.util.zip.DeflaterInputStream
 
 /**
  * Decrypts and decompresses the business payload that was
@@ -257,7 +258,7 @@ suspend fun postEbicsAndCheckReturnCodes(
     return respObj
 }
 /**
- * Collects all the steps of an EBICS download transaction.  Namely
+ * Collects all the steps of an EBICS download transaction.  Namely,
  * it conducts: init -> transfer -> receipt phases.
  *
  * @param client HTTP client for POSTing to the bank.
@@ -279,7 +280,7 @@ suspend fun doEbicsDownload(
 ): String? {
     val initResp = postEbicsAndCheckReturnCodes(client, cfg, bankKeys, reqXml, 
isEbics3)
     if (initResp == null) {
-        tech.libeufin.nexus.logger.error("Could not get past the EBICS init 
phase, failing.")
+        tech.libeufin.nexus.logger.error("EBICS download: could not get past 
the EBICS init phase, failing.")
         return null
     }
     val howManySegments = initResp.numSegments
@@ -381,4 +382,135 @@ fun parseAndValidateEbicsResponse(
     if (withEbics3)
         return ebics3toInternalRepr(responseStr)
     return ebics25toInternalRepr(responseStr)
+}
+
+/**
+ * Signs and the encrypts the data to send via EBICS.
+ *
+ * @param cfg configuration handle.
+ * @param clientKeys client keys.
+ * @param bankKeys bank keys.
+ * @param payload business payload to send to the bank, typically ISO20022.
+ * @param isEbics3 true if the payload travels on EBICS 3.
+ * @return [PreparedUploadData]
+ */
+fun prepareUloadPayload(
+    cfg: EbicsSetupConfig,
+    clientKeys: ClientPrivateKeysFile,
+    bankKeys: BankPublicKeysFile,
+    payload: ByteArray,
+    isEbics3: Boolean
+): PreparedUploadData {
+    val encryptionResult: CryptoUtil.EncryptionResult = if (isEbics3) {
+        val innerSignedEbicsXml = signOrderEbics3( // A006 signature.
+            payload,
+            clientKeys.signature_private_key,
+            cfg.ebicsPartnerId,
+            cfg.ebicsUserId
+        )
+        val userSignatureDataEncrypted = CryptoUtil.encryptEbicsE002(
+            EbicsOrderUtil.encodeOrderDataXml(innerSignedEbicsXml),
+            bankKeys.bank_encryption_public_key
+        )
+        userSignatureDataEncrypted
+    } else {
+        val innerSignedEbicsXml = signOrder( // A006 signature.
+            payload,
+            clientKeys.signature_private_key,
+            cfg.ebicsPartnerId,
+            cfg.ebicsUserId
+        )
+        val userSignatureDataEncrypted = CryptoUtil.encryptEbicsE002(
+            EbicsOrderUtil.encodeOrderDataXml(innerSignedEbicsXml),
+            bankKeys.bank_encryption_public_key
+        )
+        userSignatureDataEncrypted
+    }
+    val plainTransactionKey = encryptionResult.plainTransactionKey
+    if (plainTransactionKey == null)
+        throw Exception("Could not generate the transaction key, cannot 
encrypt the payload!")
+    // Then only E002 symmetric (with ephemeral key) encrypt.
+    val compressedInnerPayload = DeflaterInputStream(
+        payload.inputStream()
+    ).use { it.readAllBytes() }
+    val encryptedPayload = CryptoUtil.encryptEbicsE002withTransactionKey(
+        compressedInnerPayload,
+        bankKeys.bank_encryption_public_key,
+        plainTransactionKey
+    )
+    val encodedEncryptedPayload = 
Base64.getEncoder().encodeToString(encryptedPayload.encryptedData)
+
+    return PreparedUploadData(
+        encryptionResult.encryptedTransactionKey, // ephemeral key
+        encryptionResult.encryptedData, // bank-pub-encrypted A006 signature.
+        CryptoUtil.digestEbicsOrderA006(payload), // used by EBICS 3
+        listOf(encodedEncryptedPayload) // actual payload E002 encrypted.
+    )
+}
+
+/**
+ * Collects all the steps of an EBICS 3 upload transaction.
+ * NOTE: this function could conveniently be reused for an EBICS 2.x
+ * transaction, hence this function stays in this file.
+ *
+ * @param client HTTP client for POSTing to the bank.
+ * @param cfg configuration handle.
+ * @param clientKeys client EBICS private keys.
+ * @param bankKeys bank EBICS public keys.
+ * @param payload binary business paylaod.
+ * @return [EbicsResponseContent] or null upon errors.
+ */
+suspend fun doEbicsUpload(
+    client: HttpClient,
+    cfg: EbicsSetupConfig,
+    clientKeys: ClientPrivateKeysFile,
+    bankKeys: BankPublicKeysFile,
+    orderService: Ebics3Request.OrderDetails.Service,
+    payload: ByteArray
+): EbicsResponseContent? {
+    val preparedPayload = prepareUloadPayload(cfg, clientKeys, bankKeys, 
payload, isEbics3 = true)
+    val initXml = createEbics3RequestForUploadInitialization(
+        cfg,
+        preparedPayload,
+        bankKeys,
+        clientKeys,
+        orderService
+    )
+    val initResp = postEbicsAndCheckReturnCodes(
+        client,
+        cfg,
+        bankKeys,
+        initXml,
+        isEbics3 = true
+    )
+    if (initResp == null) {
+        tech.libeufin.nexus.logger.error("EBICS upload init phase failed.")
+        return null
+    }
+
+    // Init phase OK, proceeding with the transfer phase.
+    val tId = initResp.transactionID
+    if (tId == null) {
+        logger.error("EBICS upload init phase did not return a transaction ID, 
cannot do the transfer phase.")
+        return null
+    }
+    val transferXml = createEbics3RequestForUploadTransferPhase(
+        cfg,
+        clientKeys,
+        tId,
+        preparedPayload
+    )
+    val transferResp = postEbicsAndCheckReturnCodes(
+        client,
+        cfg,
+        bankKeys,
+        initXml,
+        isEbics3 = true
+    )
+    if (transferResp == null) {
+        tech.libeufin.nexus.logger.error("EBICS transfer phase failed.")
+        return null
+    }
+    // EBICS- and bank-technical codes were both EBICS_OK, success!
+    return transferResp
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/Ebics.kt b/util/src/main/kotlin/Ebics.kt
index 80585f1c..5c295962 100644
--- a/util/src/main/kotlin/Ebics.kt
+++ b/util/src/main/kotlin/Ebics.kt
@@ -156,7 +156,7 @@ fun makeEbics3DateRange(ebicsDateRange: EbicsDateRange?): 
Ebics3Request.DateRang
     else null
 }
 
-private fun signOrder(
+fun signOrder(
     orderBlob: ByteArray,
     signKey: RSAPrivateCrtKey,
     partnerId: String,
@@ -179,7 +179,7 @@ private fun signOrder(
     return userSignatureData
 }
 
-private fun signOrderEbics3(
+fun signOrderEbics3(
     orderBlob: ByteArray,
     signKey: RSAPrivateCrtKey,
     partnerId: String,
@@ -256,56 +256,6 @@ data class PreparedUploadData(
     }
 }
 
-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!!,
-        encryptionResult.plainTransactionKey!!
-    )
-    val encodedEncryptedPayload = 
Base64.getEncoder().encodeToString(encryptedPayload.encryptedData)
-
-    return PreparedUploadData(
-        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,

-- 
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]