libeufin

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

commit dcac56ce7860a880f7c54b3e9a2275c16e37f700
parent 611f2a37a9940d2241dea36b23b2a23ebed5324a
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Mon, 25 Nov 2019 19:00:06 +0100

EbicsRequest helper.

Diffstat:
Mnexus/src/main/kotlin/Containers.kt | 11+++++++----
Mnexus/src/main/kotlin/Main.kt | 241+++++++++++++++++++++++++++++++------------------------------------------------
Msandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsRequest.kt | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsResponse.kt | 2+-
4 files changed, 227 insertions(+), 151 deletions(-)

diff --git a/nexus/src/main/kotlin/Containers.kt b/nexus/src/main/kotlin/Containers.kt @@ -2,6 +2,9 @@ package tech.libeufin.nexus import javax.crypto.SecretKey import org.w3c.dom.Document +import java.security.PrivateKey +import java.security.interfaces.RSAPrivateCrtKey +import java.security.interfaces.RSAPublicKey import javax.xml.bind.JAXBElement @@ -14,9 +17,9 @@ import javax.xml.bind.JAXBElement data class EbicsContainer<T>( // needed to verify responses - val bankAuthPubBlob: ByteArray? = null, + val bankAuthPub: RSAPublicKey? = null, - val bankEncPubBlob: ByteArray? = null, + val bankEncPub: RSAPublicKey? = null, // needed to send the message val ebicsUrl: String? = null, @@ -28,10 +31,10 @@ data class EbicsContainer<T>( val plainTransactionKey: SecretKey? = null, // needed to decrypt data coming from the bank - val customerEncPrivBlob: ByteArray? = null, + val customerEncPriv: RSAPrivateCrtKey? = null, // needed to sign documents - val customerAuthPrivBlob: ByteArray? = null, + val customerAuthPriv: RSAPrivateCrtKey? = null, val jaxb: T? = null ) \ No newline at end of file diff --git a/nexus/src/main/kotlin/Main.kt b/nexus/src/main/kotlin/Main.kt @@ -30,7 +30,6 @@ import io.ktor.features.ContentNegotiation import io.ktor.features.StatusPages import io.ktor.gson.gson import io.ktor.http.ContentType -import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.request.receive import io.ktor.request.uri @@ -39,7 +38,6 @@ import io.ktor.response.respondText import io.ktor.routing.* import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty -import org.apache.commons.codec.digest.Crypt import org.apache.xml.security.binding.xmldsig.RSAKeyValueType import org.apache.xml.security.binding.xmldsig.SignatureType import org.jetbrains.exposed.sql.transactions.transaction @@ -49,22 +47,19 @@ import tech.libeufin.schema.ebics_h004.* import java.text.DateFormat import javax.sql.rowset.serial.SerialBlob import javax.xml.bind.JAXBElement -import org.w3c.dom.Document import tech.libeufin.schema.ebics_s001.SignatureTypes import tech.libeufin.schema.ebics_s001.UserSignatureData -import java.awt.Container import java.math.BigInteger import java.security.PrivateKey -import java.security.PublicKey import java.security.SecureRandom +import java.security.interfaces.RSAPrivateCrtKey import java.text.SimpleDateFormat -import java.time.Instant.now import java.util.* import java.util.zip.DeflaterInputStream -import java.util.zip.InflaterInputStream import javax.crypto.EncryptedPrivateKeyInfo import javax.xml.datatype.DatatypeFactory import javax.xml.datatype.XMLGregorianCalendar +import java.security.interfaces.RSAPublicKey fun testData() { @@ -125,6 +120,32 @@ fun expectId(param: String?): Int { } } +fun signOrder( + orderBlob: ByteArray, + signKey: ByteArray, + partnerId: String, + userId: String +): UserSignatureData { + + val ES_signature = CryptoUtil.signEbicsA006( + CryptoUtil.digestEbicsOrderA006(orderBlob), + CryptoUtil.loadRsaPrivateKey(signKey) + ) + val userSignatureData = UserSignatureData().apply { + orderSignatureList = listOf( + UserSignatureData.OrderSignatureData().apply { + signatureVersion = "A006" + signatureValue = ES_signature + partnerID = partnerId + userID = userId + } + ) + } + + return userSignatureData +} + + /** * @return null when the bank could not be reached, otherwise returns the * response already converted in JAXB. @@ -151,8 +172,8 @@ suspend inline fun HttpClient.postToBank(url: String, body: String): String { suspend inline fun <reified T, reified S>HttpClient.postToBankSignedAndVerify( url: String, body: T, - pub: PublicKey, - priv: PrivateKey): JAXBElement<S> { + pub: RSAPublicKey, + priv: RSAPrivateCrtKey): JAXBElement<S> { val doc = XMLUtil.convertJaxbToDocument(body) XMLUtil.signEbicsDocument(doc, priv) @@ -396,8 +417,8 @@ fun main() { EbicsContainer( ebicsUrl = subscriber.ebicsURL, - customerEncPrivBlob = subscriber.encryptionPrivateKey.toByteArray(), - customerAuthPrivBlob = subscriber.authenticationPrivateKey.toByteArray(), + customerEncPriv = CryptoUtil.loadRsaPrivateKey(subscriber.encryptionPrivateKey.toByteArray()), + customerAuthPriv = CryptoUtil.loadRsaPrivateKey(subscriber.authenticationPrivateKey.toByteArray()), jaxb = request, hostId = subscriber.hostID @@ -420,7 +441,10 @@ fun main() { Base64.getDecoder().decode(response.value.body.dataTransfer!!.orderData.value) ) - val dataCompr = CryptoUtil.decryptEbicsE002(er, CryptoUtil.loadRsaPrivateKey(bundle.customerEncPrivBlob!!)) + val dataCompr = CryptoUtil.decryptEbicsE002( + er, + bundle.customerEncPriv!! + ) val data = EbicsOrderUtil.decodeOrderDataXml<HTDResponseOrderData>(dataCompr) @@ -453,8 +477,8 @@ fun main() { val ackResponse = client.postToBankSignedAndVerify<EbicsRequest, EbicsResponse>( bundle.ebicsUrl, ackRequest, - CryptoUtil.loadRsaPublicKey(bundle.bankAuthPubBlob!!), - CryptoUtil.loadRsaPrivateKey(bundle.customerAuthPrivBlob!!) + bundle.bankAuthPub!!, + bundle.customerAuthPriv!! ) logger.debug("HTD final response: " + XMLUtil.convertJaxbToString<EbicsResponse>(response.value)) @@ -826,29 +850,19 @@ fun main() { val id = expectId(call.parameters["id"]) val innerPayload = "ES-PAYLOAD" - - val container = transaction { val subscriber = EbicsSubscriberEntity.findById(id) ?: throw SubscriberNotFoundError(HttpStatusCode.NotFound) - // first prepare ES content - val ES_signature = CryptoUtil.signEbicsA006( - CryptoUtil.digestEbicsOrderA006(innerPayload.toByteArray()), - CryptoUtil.loadRsaPrivateKey(subscriber.signaturePrivateKey.toByteArray()) - ) + val usd_compressed = EbicsOrderUtil.encodeOrderDataXml( - val userSignatureData = UserSignatureData().apply { - orderSignatureList = listOf( - UserSignatureData.OrderSignatureData().apply { - signatureVersion = "A006" - signatureValue = ES_signature - partnerID = subscriber.partnerID - userID = subscriber.userID - } + signOrder( + innerPayload.toByteArray(), + subscriber.signaturePrivateKey.toByteArray(), + subscriber.partnerID, + subscriber.userID ) - } - val usd_compressed = EbicsOrderUtil.encodeOrderDataXml(userSignatureData) + ) val usd_encrypted = CryptoUtil.encryptEbicsE002( usd_compressed, CryptoUtil.loadRsaPublicKey( @@ -856,97 +870,46 @@ fun main() { ) ) - val tmp = EbicsRequest().apply { - header = EbicsRequest.Header().apply { - version = "H004" - revision = 1 - authenticate = true - static = EbicsRequest.StaticHeaderType().apply { - hostID = subscriber.hostID - nonce = getNonce(128) - timestamp = getGregorianDate() - partnerID = subscriber.partnerID - userID = subscriber.userID - orderDetails = EbicsRequest.OrderDetails().apply { - orderType = "TST" - orderAttribute = "OZHNN" - orderParams = EbicsRequest.StandardOrderParams() - } - bankPubKeyDigests = EbicsRequest.BankPubKeyDigests().apply { - authentication = EbicsTypes.PubKeyDigest().apply { - algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" - version = "X002" - value = CryptoUtil.getEbicsPublicKeyHash( - CryptoUtil.loadRsaPublicKey( - subscriber.bankAuthenticationPublicKey?.toByteArray() ?: throw BankKeyMissing( - HttpStatusCode.PreconditionFailed) - ) - ) - } - encryption = EbicsTypes.PubKeyDigest().apply { - algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" - version = "E002" - value = CryptoUtil.getEbicsPublicKeyHash( - CryptoUtil.loadRsaPublicKey( - subscriber.bankEncryptionPublicKey?.toByteArray() ?: throw BankKeyMissing( - HttpStatusCode.PreconditionFailed - ) - ) - ) - } - } - securityMedium = "0000" - numSegments = BigInteger.ONE - } - mutable = EbicsRequest.MutableHeader().apply { - transactionPhase = EbicsTypes.TransactionPhaseType.INITIALISATION - } - } - authSignature = SignatureType() - body = EbicsRequest.Body().apply { - dataTransfer = EbicsRequest.DataTransfer().apply { - signatureData = EbicsRequest.SignatureData().apply { - authenticate = true - value = usd_encrypted.encryptedData - } - dataEncryptionInfo = EbicsTypes.DataEncryptionInfo().apply { - transactionKey = usd_encrypted.encryptedTransactionKey - authenticate = true - encryptionPubKeyDigest = EbicsTypes.PubKeyDigest().apply { - algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" - version = "E002" - value = CryptoUtil.getEbicsPublicKeyHash( - CryptoUtil.loadRsaPublicKey( - subscriber.bankEncryptionPublicKey?.toByteArray() ?: throw BankKeyMissing( - HttpStatusCode.PreconditionFailed - ) - ) - ) - } - } - } - } - } - EbicsContainer( - jaxb = tmp, + jaxb = EbicsRequest.createForUploadInitializationPhase( + usd_encrypted, + subscriber.hostID, + getNonce(128), + subscriber.partnerID, + subscriber.userID, + getGregorianDate(), + CryptoUtil.loadRsaPublicKey(subscriber.bankAuthenticationPublicKey?.toByteArray() ?: throw BankKeyMissing( + HttpStatusCode.PreconditionFailed + )), + CryptoUtil.loadRsaPublicKey(subscriber.bankEncryptionPublicKey?.toByteArray() ?: throw BankKeyMissing( + HttpStatusCode.PreconditionFailed + )), + BigInteger.ONE + ), + ebicsUrl = subscriber.ebicsURL, - bankAuthPubBlob = subscriber.bankAuthenticationPublicKey?.toByteArray() ?: throw BankKeyMissing( - HttpStatusCode.PreconditionFailed + bankAuthPub = CryptoUtil.loadRsaPublicKey( + subscriber.bankAuthenticationPublicKey?.toByteArray() ?: throw BankKeyMissing(HttpStatusCode.PreconditionFailed) ), + plainTransactionKey = usd_encrypted.plainTransactionKey, - customerAuthPrivBlob = subscriber.authenticationPrivateKey.toByteArray(), - bankEncPubBlob = subscriber.bankEncryptionPublicKey?.toByteArray() ?: throw BankKeyMissing( - HttpStatusCode.PreconditionFailed + customerAuthPriv = CryptoUtil.loadRsaPrivateKey( + subscriber.authenticationPrivateKey.toByteArray() ), + + bankEncPub = CryptoUtil.loadRsaPublicKey( + subscriber.bankEncryptionPublicKey?.toByteArray() ?: throw BankKeyMissing( + HttpStatusCode.PreconditionFailed) + ), + hostId = subscriber.hostID ) } val response = client.postToBankSignedAndVerify<EbicsRequest, EbicsResponse>( container.ebicsUrl!!, container.jaxb!!, - CryptoUtil.loadRsaPublicKey(container.bankAuthPubBlob!!), - CryptoUtil.loadRsaPrivateKey(container.customerAuthPrivBlob!!) + container.bankAuthPub!!, + container.customerAuthPriv!! ) if (response.value.body.returnCode.value != "000000") { @@ -961,41 +924,22 @@ fun main() { val encryptedPayload = CryptoUtil.encryptEbicsE002withTransactionKey( compressedInnerPayload, - CryptoUtil.loadRsaPublicKey(container.bankEncPubBlob!!), + container.bankEncPub!!, container.plainTransactionKey!! ) - val tmp = EbicsRequest().apply { - header = EbicsRequest.Header().apply { - version = "H004" - revision = 1 - authenticate = true - static = EbicsRequest.StaticHeaderType().apply { - hostID = container.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 tmpTmp = EbicsRequest.createForUploadTransferPhase( + container.hostId!!, + response.value.header._static.transactionID!!, + BigInteger.ONE, + encryptedPayload.encryptedData + ) val responseTransaction = client.postToBankSignedAndVerify<EbicsRequest, EbicsResponse>( container.ebicsUrl, - tmp, - CryptoUtil.loadRsaPublicKey(container.bankAuthPubBlob), - CryptoUtil.loadRsaPrivateKey(container.customerAuthPrivBlob) + tmpTmp, + container.bankAuthPub, + container.customerAuthPriv ) if (responseTransaction.value.body.returnCode.value != "000000") { @@ -1036,19 +980,23 @@ fun main() { authSignature = SignatureType() } - EbicsContainer<EbicsNpkdRequest>( + EbicsContainer( ebicsUrl = subscriber.ebicsURL, - customerEncPrivBlob = subscriber.encryptionPrivateKey.toByteArray(), - customerAuthPrivBlob = subscriber.authenticationPrivateKey.toByteArray(), - jaxb = hpbRequest + customerEncPriv = CryptoUtil.loadRsaPrivateKey( + subscriber.encryptionPrivateKey.toByteArray() + ), + customerAuthPriv = CryptoUtil.loadRsaPrivateKey( + subscriber.authenticationPrivateKey.toByteArray() + ), + jaxb = hpbRequest ) } val response = client.postToBankSigned<EbicsNpkdRequest, EbicsKeyManagementResponse>( bundle.ebicsUrl!!, bundle.jaxb!!, - CryptoUtil.loadRsaPrivateKey(bundle.customerAuthPrivBlob!!) + bundle.customerAuthPriv!! ) if (response.value.body.returnCode.value != "000000") { @@ -1064,7 +1012,8 @@ fun main() { val dataCompr = CryptoUtil.decryptEbicsE002( er, - CryptoUtil.loadRsaPrivateKey(bundle.customerEncPrivBlob!!)) + bundle.customerEncPriv!! + ) val data = EbicsOrderUtil.decodeOrderDataXml<HPBResponseOrderData>(dataCompr) val bankAuthPubBlob = CryptoUtil.loadRsaPublicKeyFromComponents( 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 @@ -1,7 +1,11 @@ package tech.libeufin.schema.ebics_h004 +import io.ktor.http.HttpStatusCode import org.apache.xml.security.binding.xmldsig.SignatureType +import tech.libeufin.sandbox.CryptoUtil +import tech.libeufin.sandbox.toByteArray import java.math.BigInteger +import java.security.interfaces.RSAPublicKey import javax.xml.bind.annotation.* import javax.xml.bind.annotation.adapters.CollapsedStringAdapter import javax.xml.bind.annotation.adapters.HexBinaryAdapter @@ -265,4 +269,124 @@ class EbicsRequest { @get:XmlElement(name = "Encryption") lateinit var encryption: EbicsTypes.PubKeyDigest } + + companion object { + + fun createForUploadInitializationPhase( + cryptoBundle: CryptoUtil.EncryptionResult, + hostId: String, + nonceArg: ByteArray, + partnerId: String, + userId: String, + date: XMLGregorianCalendar, + bankAuthPub: RSAPublicKey, + bankEncPub: RSAPublicKey, + segmentsNumber: BigInteger + ): EbicsRequest { + + return EbicsRequest().apply { + header = EbicsRequest.Header().apply { + version = "H004" + revision = 1 + authenticate = true + static = EbicsRequest.StaticHeaderType().apply { + hostID = hostId + nonce = nonceArg + timestamp = date + partnerID = partnerId + userID = userId + orderDetails = EbicsRequest.OrderDetails().apply { + orderType = "TST" + orderAttribute = "OZHNN" + orderParams = EbicsRequest.StandardOrderParams() + } + bankPubKeyDigests = EbicsRequest.BankPubKeyDigests().apply { + authentication = EbicsTypes.PubKeyDigest().apply { + algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" + version = "X002" + value = CryptoUtil.getEbicsPublicKeyHash(bankAuthPub) + } + encryption = EbicsTypes.PubKeyDigest().apply { + algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" + version = "E002" + value = CryptoUtil.getEbicsPublicKeyHash(bankEncPub) + } + } + securityMedium = "0000" + numSegments = segmentsNumber + } + mutable = EbicsRequest.MutableHeader().apply { + transactionPhase = EbicsTypes.TransactionPhaseType.INITIALISATION + } + } + authSignature = SignatureType() + body = EbicsRequest.Body().apply { + dataTransfer = EbicsRequest.DataTransfer().apply { + signatureData = EbicsRequest.SignatureData().apply { + authenticate = true + value = cryptoBundle.encryptedData + } + dataEncryptionInfo = EbicsTypes.DataEncryptionInfo().apply { + transactionKey = cryptoBundle.encryptedTransactionKey + authenticate = true + encryptionPubKeyDigest = EbicsTypes.PubKeyDigest().apply { + algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" + version = "E002" + value = CryptoUtil.getEbicsPublicKeyHash(bankEncPub) + } + } + } + } + } + + + } + + fun createForUploadTransferPhase( + hostId: String, + transactionId: String, + segNumber: BigInteger, + encryptedData: ByteArray + + ): EbicsRequest { + + return EbicsRequest().apply { + header = Header().apply { + version = "H004" + revision = 1 + authenticate = true + static = StaticHeaderType().apply { + hostID = hostId + transactionID = transactionId + } + mutable = MutableHeader().apply { + transactionPhase = EbicsTypes.TransactionPhaseType.TRANSFER + segmentNumber = EbicsTypes.SegmentNumber().apply { + lastSegment = true + value = segNumber + } + } + } + + authSignature = SignatureType() + body = EbicsRequest.Body().apply { + dataTransfer = EbicsRequest.DataTransfer().apply { + orderData = encryptedData + } + } + } + + fun createForDownloadInitializationPhase(): EbicsRequest { + + } + + fun createForDownloadTransferPhase(): EbicsRequest { + + + } + + fun createForDownloadReceiptPhase(): EbicsRequest { + + } + } } 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 @@ -22,7 +22,7 @@ class EbicsResponse { var revision: Int? = null @get:XmlElement(required = true) - lateinit var header: EbicsResponse.Header + lateinit var header: Header @get:XmlElement(name = "AuthSignature", required = true) lateinit var authSignature: SignatureType