libeufin

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

commit aa9064ac730b0cb530e827f3244f77920114f155
parent 3f08fc722ac7c3f29ac1e399dd3825be015da3a3
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Tue, 19 Nov 2019 00:30:28 +0100

Get upload TRANSACTION request to decrypt.

Diffstat:
Mnexus/src/main/kotlin/Main.kt | 63++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt | 26+++++++++++++++++++++-----
Msandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt | 2+-
Msandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsRequest.kt | 2+-
Msandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsResponse.kt | 16+++-------------
Msandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsTypes.kt | 11+++++++++++
6 files changed, 97 insertions(+), 23 deletions(-)

diff --git a/nexus/src/main/kotlin/Main.kt b/nexus/src/main/kotlin/Main.kt @@ -58,6 +58,7 @@ import java.security.SecureRandom import java.text.SimpleDateFormat import java.time.Instant.now import java.util.* +import java.util.zip.DeflaterInputStream import java.util.zip.InflaterInputStream import javax.xml.datatype.DatatypeFactory import javax.xml.datatype.XMLGregorianCalendar @@ -670,12 +671,14 @@ fun main() { post("/ebics/subscribers/{id}/sendTst") { val id = expectId(call.parameters["id"]) - val (url, doc) = transaction { + val innerPayload = "ES-PAYLOAD" + + val (url, doc, transactionKey) = transaction { val subscriber = EbicsSubscriberEntity.findById(id) ?: throw SubscriberNotFoundError(HttpStatusCode.NotFound) // first prepare ES content val ES_signature = CryptoUtil.signEbicsA006( - CryptoUtil.digestEbicsA006("ES-PAYLOAD".toByteArray()), + CryptoUtil.digestEbicsA006(innerPayload.toByteArray()), CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray()) ) @@ -767,12 +770,66 @@ fun main() { doc, CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()) ) - Pair(subscriber.ebicsURL, doc) + Triple(subscriber.ebicsURL, doc, usd_encrypted.plainTransactionKey) } // send document here val response = client.postToBank<EbicsResponse>(url, doc) + // MUST validate bank signature first (FIXME) + // MUST check that outcome is EBICS_OK (FIXME) + + val (urlTransfer, docTransfer) = transaction { + + val subscriber = EbicsSubscriberEntity.findById(id) ?: throw SubscriberNotFoundError(HttpStatusCode.NotFound) + + val compressedInnerPayload = DeflaterInputStream( + innerPayload.toByteArray().inputStream() + + ).use { it.readAllBytes() } + + val encryptedPayload = CryptoUtil.encryptEbicsE002withTransactionKey( + compressedInnerPayload, + CryptoUtil.loadRsaPublicKey(subscriber.bankEncryptionPublicKey!!.toByteArray()), + transactionKey!! + ) + + val tmp = EbicsRequest().apply { + header = EbicsRequest.Header().apply { + version = "H004" + revision = 1 + authenticate = true + static = EbicsRequest.StaticHeaderType().apply { + hostID = subscriber.hostID + transactionID = response.value.header._static.transactionID + } + mutable = EbicsRequest.MutableHeader().apply { + transactionPhase = EbicsTypes.TransactionPhaseType.TRANSFER + segmentNumber = EbicsTypes.SegmentNumber().apply { + lastSegment = true + value = BigInteger.ONE + } + } + } + + authSignature = SignatureType() + body = EbicsRequest.Body().apply { + dataTransfer = EbicsRequest.DataTransfer().apply { + orderData = encryptedPayload.encryptedData + } + } + } + + val doc = XMLUtil.convertJaxbToDocument(tmp) + XMLUtil.signEbicsDocument( + doc, + CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()) + ) + Pair(subscriber.ebicsURL, doc) + } + + val responseTransaction = client.postToBank<EbicsResponse>(urlTransfer, docTransfer) + call.respondText( "not implemented\n", ContentType.Text.Plain, diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt @@ -31,6 +31,7 @@ import java.security.interfaces.RSAPublicKey import java.security.spec.* import javax.crypto.Cipher import javax.crypto.KeyGenerator +import javax.crypto.SecretKey import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec @@ -47,7 +48,12 @@ object CryptoUtil { class EncryptionResult( val encryptedTransactionKey: ByteArray, val pubKeyDigest: ByteArray, - val encryptedData: ByteArray + val encryptedData: ByteArray, + + /** + * This key needs to be reused between different upload phases. + */ + val plainTransactionKey: SecretKey? = null ) private val bouncyCastleProvider = BouncyCastleProvider() @@ -131,13 +137,22 @@ object CryptoUtil { return digest.digest(keyBytes.toByteArray()) } - /** - * Encrypt data according to the EBICS E002 encryption process. - */ fun encryptEbicsE002(data: ByteArray, encryptionPublicKey: RSAPublicKey): EncryptionResult { val keygen = KeyGenerator.getInstance("AES", bouncyCastleProvider) keygen.init(128) val transactionKey = keygen.generateKey() + return encryptEbicsE002withTransactionKey(data, encryptionPublicKey, transactionKey) + } + + /** + * Encrypt data according to the EBICS E002 encryption process. + */ + fun encryptEbicsE002withTransactionKey( + data: ByteArray, + encryptionPublicKey: RSAPublicKey, + transactionKey: SecretKey + ): EncryptionResult { + val symmetricCipher = Cipher.getInstance("AES/CBC/X9.23Padding", bouncyCastleProvider) val ivParameterSpec = IvParameterSpec(ByteArray(16)) symmetricCipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec) @@ -146,7 +161,7 @@ object CryptoUtil { asymmetricCipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey) val encryptedTransactionKey = asymmetricCipher.doFinal(transactionKey.encoded) val pubKeyDigest = getEbicsPublicKeyHash(encryptionPublicKey) - return EncryptionResult(encryptedTransactionKey, pubKeyDigest, encryptedData) + return EncryptionResult(encryptedTransactionKey, pubKeyDigest, encryptedData, transactionKey) } fun decryptEbicsE002(enc: EncryptionResult, privateKey: RSAPrivateCrtKey): ByteArray { @@ -161,6 +176,7 @@ object CryptoUtil { val symmetricCipher = Cipher.getInstance("AES/CBC/X9.23Padding", bouncyCastleProvider) val ivParameterSpec = IvParameterSpec(ByteArray(16)) symmetricCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec) + logger.debug("decrypting: ${encryptedData.toHexString()}") val data = symmetricCipher.doFinal(encryptedData) return data } diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -649,7 +649,7 @@ suspend fun ApplicationCall.ebicsweb() { EbicsTypes.TransactionPhaseType.TRANSFER -> { requestTransactionID ?: throw EbicsInvalidRequestError() val requestSegmentNumber = - requestObject.header.mutable.segmentNumber?.toInt() ?: throw EbicsInvalidRequestError() + requestObject.header.mutable.segmentNumber?.value?.toInt() ?: throw EbicsInvalidRequestError() if (uploadTransaction != null) { if (requestSegmentNumber == 1 && uploadTransaction.numSegments == 1) { val encOrderData = diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsRequest.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsRequest.kt @@ -142,7 +142,7 @@ class EbicsRequest { * contains order data. */ @get:XmlElement(name = "SegmentNumber") - var segmentNumber: BigInteger? = null + var segmentNumber: EbicsTypes.SegmentNumber? = null } diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsResponse.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsResponse.kt @@ -68,7 +68,7 @@ class EbicsResponse { lateinit var transactionPhase: EbicsTypes.TransactionPhaseType @get:XmlElement(name = "SegmentNumber") - var segmentNumber: SegmentNumber? = null + var segmentNumber: EbicsTypes.SegmentNumber? = null @get:XmlElement(name = "OrderID") @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) @@ -87,16 +87,6 @@ class EbicsResponse { } @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["value"]) - class SegmentNumber { - @XmlValue - lateinit var value: BigInteger - - @XmlAttribute(name = "lastSegment") - var lastSegment: Boolean? = null - } - - @XmlAccessorType(XmlAccessType.NONE) class OrderData { @get:XmlValue lateinit var value: String @@ -209,7 +199,7 @@ class EbicsResponse { } this.mutable = EbicsResponse.MutableHeaderType().apply { this.transactionPhase = EbicsTypes.TransactionPhaseType.TRANSFER - this.segmentNumber = EbicsResponse.SegmentNumber().apply { + this.segmentNumber = EbicsTypes.SegmentNumber().apply { this.value = BigInteger.valueOf(segmentNumber.toLong()) if (lastSegment) { this.lastSegment = true @@ -248,7 +238,7 @@ class EbicsResponse { } this.mutable = EbicsResponse.MutableHeaderType().apply { this.transactionPhase = EbicsTypes.TransactionPhaseType.INITIALISATION - this.segmentNumber = EbicsResponse.SegmentNumber().apply { + this.segmentNumber = EbicsTypes.SegmentNumber().apply { this.lastSegment = (numSegments == 1) this.value = BigInteger.valueOf(1) } diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsTypes.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsTypes.kt @@ -21,6 +21,7 @@ package tech.libeufin.schema.ebics_h004 import org.apache.xml.security.binding.xmldsig.RSAKeyValueType import org.w3c.dom.Element +import java.math.BigInteger import java.util.* import javax.xml.bind.annotation.* import javax.xml.bind.annotation.adapters.CollapsedStringAdapter @@ -52,6 +53,16 @@ object EbicsTypes { var instituteID: String? = null } + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["value"]) + class SegmentNumber { + @XmlValue + lateinit var value: BigInteger + + @XmlAttribute(name = "lastSegment") + var lastSegment: Boolean? = null + } + @XmlType(name = "", propOrder = ["encryptionPubKeyDigest", "transactionKey"]) @XmlAccessorType(XmlAccessType.NONE)