[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: EBICS (3) upload doer and helpers.,
gnunet <=