commit bd6cbe43c27144b5537cd96e3745777351768272 parent 7db6c2a92931d72d35216d923bc02f3a9af005df Author: Florian Dold <florian.dold@gmail.com> Date: Thu, 31 Oct 2019 19:01:16 +0100 database rework, make INI succeed Diffstat:
16 files changed, 579 insertions(+), 643 deletions(-)
diff --git a/sandbox/build.gradle b/sandbox/build.gradle @@ -28,7 +28,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "io.ktor:ktor-gson:1.1.5" compile group: 'io.ktor', name: 'ktor-gson', version: '0.9.0' - compile "org.jetbrains.exposed:exposed:0.17.3" + compile "org.jetbrains.exposed:exposed:0.17.6" compile "io.ktor:ktor-server-netty:1.2.4" compile "ch.qos.logback:logback-classic:1.2.3" compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1' diff --git a/sandbox/src/main/kotlin/CryptoUtil.kt b/sandbox/src/main/kotlin/CryptoUtil.kt @@ -20,8 +20,10 @@ package tech.libeufin.sandbox import java.lang.Exception +import java.math.BigInteger import java.security.KeyFactory import java.security.KeyPairGenerator +import java.security.PublicKey import java.security.interfaces.RSAPrivateCrtKey import java.security.interfaces.RSAPublicKey import java.security.spec.PKCS8EncodedKeySpec @@ -38,6 +40,9 @@ data class RsaCrtKeyPair(val private: RSAPrivateCrtKey, val public: RSAPublicKey */ class CryptoUtil { companion object { + /** + * Load an RSA private key from its binary PKCS#8 encoding. + */ fun loadRsaPrivateKey(encodedPrivateKey: ByteArray): RSAPrivateCrtKey { val spec = PKCS8EncodedKeySpec(encodedPrivateKey) val priv = KeyFactory.getInstance("RSA").generatePrivate(spec) @@ -45,6 +50,10 @@ class CryptoUtil { throw Exception("wrong encoding") return priv } + + /** + * Load an RSA public key from its binary X509 encoding. + */ fun loadRsaPublicKey(encodedPublicKey: ByteArray): RSAPublicKey { val spec = X509EncodedKeySpec(encodedPublicKey) val pub = KeyFactory.getInstance("RSA").generatePublic(spec) @@ -52,6 +61,10 @@ class CryptoUtil { throw Exception("wrong encoding") return pub } + + /** + * Load an RSA public key from its binary X509 encoding. + */ fun getRsaPublicFromPrivate(rsaPrivateCrtKey: RSAPrivateCrtKey): RSAPublicKey { val spec = RSAPublicKeySpec(rsaPrivateCrtKey.modulus, rsaPrivateCrtKey.publicExponent) val pub = KeyFactory.getInstance("RSA").generatePublic(spec) @@ -59,6 +72,12 @@ class CryptoUtil { throw Exception("wrong encoding") return pub } + + /** + * Generate a fresh RSA key pair. + * + * @param nbits size of the modulus in bits + */ fun generateRsaKeyPair(nbits: Int): RsaCrtKeyPair { val gen = KeyPairGenerator.getInstance("RSA") gen.initialize(nbits) @@ -71,5 +90,21 @@ class CryptoUtil { throw Exception("key generation failed") return RsaCrtKeyPair(priv, pub) } + + /** + * Load an RSA public key from its components. + * + * @param exponent + * @param modulus + * @return key + */ + fun loadRsaPublicKeyFromComponents(modulus: ByteArray, exponent: ByteArray): RSAPublicKey { + val modulusBigInt = BigInteger(1, modulus) + val exponentBigInt = BigInteger(1, exponent) + + val keyFactory = KeyFactory.getInstance("RSA") + val tmp = RSAPublicKeySpec(modulusBigInt, exponentBigInt) + return keyFactory.generatePublic(tmp) as RSAPublicKey + } } -} -\ No newline at end of file +} diff --git a/sandbox/src/main/kotlin/DB.kt b/sandbox/src/main/kotlin/DB.kt @@ -1,3 +1,22 @@ +/* + * 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.sandbox.db import org.jetbrains.exposed.dao.* @@ -5,18 +24,14 @@ import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.transaction const val CUSTOMER_NAME_MAX_LENGTH = 20 +const val EBICS_HOST_ID_MAX_LENGTH = 10 const val EBICS_USER_ID_MAX_LENGTH = 10 const val EBICS_PARTNER_ID_MAX_LENGTH = 10 const val EBICS_SYSTEM_ID_MAX_LENGTH = 10 -const val PUBLIC_KEY_MAX_MODULUS_LENGTH = 2048 // FIXME review this value! -const val PUBLIC_KEY_MAX_EXPONENT_LENGTH = 64 // FIXME review this value! -const val PRIVATE_KEY_MODULUS_LENGTH = 1024 // FIXME review this value! -const val PRIVATE_KEY_EXPONENT_LENGTH = 10 - /** * All the states to give a subscriber. */ -enum class SubscriberStates { +enum class SubscriberState { /** * No keys at all given to the bank. */ @@ -27,7 +42,7 @@ enum class SubscriberStates { */ PARTIALLY_INITIALIZED_INI, - /** + /**r * Only HIA electronic message was successfully sent. */ PARTIALLY_INITIALIZED_HIA, @@ -47,7 +62,7 @@ enum class SubscriberStates { /** * All the states that one key can be assigned. */ -enum class KeyStates { +enum class KeyState { /** * The key was never communicated. @@ -83,89 +98,42 @@ class BankCustomer(id: EntityID<Int>) : IntEntity(id) { var ebicsSubscriber by EbicsSubscriber referencedOn BankCustomers.ebicsSubscriber } -/** - * The following tables define IDs that make a EBCIS - * 'subscriber' exist. Each EBICS subscriber is the tuple: - * - * - UserID, the human who is performing a EBICS task. - * - PartnerID, the legal entity that signed a formal agreement with the financial institution. - * - SystemID, (optional) the machine that is handling the EBICS task on behalf of the UserID. - */ - -object EbicsUsers: IntIdTable() { - /* EBICS user ID in the string form. */ - val userId = varchar("userId", EBICS_USER_ID_MAX_LENGTH).primaryKey().nullable() - -} - -class EbicsUser(id: EntityID<Int>) : IntEntity(id){ - companion object : IntEntityClass<EbicsUser>(EbicsUsers) { - override fun new(init: EbicsUser.() -> Unit): EbicsUser { - var row = super.new(init) - row.userId = "u${row.id}" - return row - } - } - var userId by EbicsUsers.userId -} /** - * Table for UserID. + * This table stores RSA public keys of subscribers. */ -object EbicsPartners: IntIdTable() { - val partnerId = varchar("partnerId", EBICS_PARTNER_ID_MAX_LENGTH).primaryKey().nullable() -} - - -class EbicsPartner(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<EbicsPartner>(EbicsPartners) { - override fun new(init: EbicsPartner.() -> Unit): EbicsPartner { - var row = super.new(init) - row.partnerId = "p${row.id}" - return row - } - } - var partnerId by EbicsPartners.partnerId +object EbicsPublicKeys : IntIdTable() { + val rsaPublicKey = blob("rsaPublicKey") + val state = enumeration("state", KeyState::class) } /** - * Table for UserID. + * Definition of a row in the [EbicsPublicKey] table */ -object EbicsSystems: IntIdTable() { - val systemId = EbicsPartners.varchar("systemId", EBICS_SYSTEM_ID_MAX_LENGTH).nullable() +class EbicsPublicKey(id: EntityID<Int>) : IntEntity(id) { + companion object : IntEntityClass<EbicsPublicKey>(EbicsPublicKeys) + var rsaPublicKey by EbicsPublicKeys.rsaPublicKey + var state by EbicsPublicKeys.state } -class EbicsSystem(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<EbicsSystem>(EbicsSystems) { - override fun new(init: EbicsSystem.() -> Unit): EbicsSystem { - val row = super.new(init) - row.systemId = "s${row.id}" - return row - } - } - - var systemId by EbicsSystems.systemId -} -/** - * This table stores RSA public keys. - */ -object EbicsPublicKeys: IntIdTable() { - val modulus = binary("modulus", PUBLIC_KEY_MAX_MODULUS_LENGTH) - val exponent = binary("exponent", PUBLIC_KEY_MAX_EXPONENT_LENGTH) - val state = enumeration("state", KeyStates::class) +object EbicsHosts : IntIdTable() { + val hostID = text("hostID") + val ebicsVersion = text("ebicsVersion") + val signaturePrivateKey = blob("signaturePrivateKey") + val encryptionPrivateKey = blob("encryptionPrivateKey") + val authenticationPrivateKey = blob("authenticationPrivateKey") } -/** - * Definition of a row in the keys table - */ -class EbicsPublicKey(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<EbicsPublicKey>(EbicsPublicKeys) - var modulus by EbicsPublicKeys.modulus - var exponent by EbicsPublicKeys.exponent - var state by EbicsPublicKeys.state +class EbicsHost(id: EntityID<Int>) : IntEntity(id) { + companion object : IntEntityClass<EbicsHost>(EbicsHosts) + var hostId by EbicsHosts.hostID + var ebicsVersion by EbicsHosts.ebicsVersion + var signaturePrivateKey by EbicsHosts.signaturePrivateKey + var encryptionPrivateKey by EbicsHosts.encryptionPrivateKey + var authenticationPrivateKey by EbicsHosts.authenticationPrivateKey } /** @@ -173,60 +141,31 @@ class EbicsPublicKey(id: EntityID<Int>) : IntEntity(id) { * and systems. Each value can appear multiple times in the same column. */ object EbicsSubscribers: IntIdTable() { - - val userId = reference("userId", EbicsUsers) - val partnerId = reference("partnerId", EbicsPartners) - val systemId = reference("systemId", EbicsSystems) + val userId = text("userID") + val partnerId = text("partnerID") + val systemId = text("systemID").nullable() val signatureKey = reference("signatureKey", EbicsPublicKeys).nullable() val encryptionKey = reference("encryptionKey", EbicsPublicKeys).nullable() val authenticationKey = reference("authorizationKey", EbicsPublicKeys).nullable() - val state = enumeration("state", SubscriberStates::class) + val state = enumeration("state", SubscriberState::class) } class EbicsSubscriber(id: EntityID<Int>) : IntEntity(id) { companion object : IntEntityClass<EbicsSubscriber>(EbicsSubscribers) - var userId by EbicsUser referencedOn EbicsSubscribers.userId - var partnerId by EbicsPartner referencedOn EbicsSubscribers.partnerId - var systemId by EbicsSystem referencedOn EbicsSubscribers.systemId + var userId by EbicsSubscribers.userId + var partnerId by EbicsSubscribers.partnerId + var systemId by EbicsSubscribers.systemId var signatureKey by EbicsPublicKey optionalReferencedOn EbicsSubscribers.signatureKey var encryptionKey by EbicsPublicKey optionalReferencedOn EbicsSubscribers.encryptionKey var authenticationKey by EbicsPublicKey optionalReferencedOn EbicsSubscribers.authenticationKey - var state by EbicsSubscribers.state -} - -/** - * Helper function that makes a new subscriber - * @return new object - */ -fun createSubscriber() : EbicsSubscriber { - - return EbicsSubscriber.new { - userId = EbicsUser.new { } - partnerId = EbicsPartner.new { } - systemId = EbicsSystem.new { } - state = SubscriberStates.NEW - } -} - -/** - * This table stores RSA private keys. - */ -object EbicsBankPrivateKeys: IntIdTable() { - val modulus = binary("modulus", PRIVATE_KEY_MODULUS_LENGTH) - val exponent = binary("exponent", PRIVATE_KEY_EXPONENT_LENGTH) + var state by EbicsSubscribers.state } -class EbicsBankPrivateKey(id: EntityID<Int>) : IntEntity(id) { - companion object : IntEntityClass<EbicsBankPrivateKey>(EbicsBankPrivateKeys) - - var modulus by EbicsBankPrivateKeys.modulus - var exponent by EbicsBankPrivateKeys.exponent -} fun dbCreateTables() { Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver") @@ -236,11 +175,8 @@ fun dbCreateTables() { SchemaUtils.create( BankCustomers, - EbicsUsers, - EbicsPartners, - EbicsSystems, EbicsSubscribers, - EbicsBankPrivateKeys + EbicsHosts ) } } diff --git a/sandbox/src/main/kotlin/JSON.kt b/sandbox/src/main/kotlin/JSON.kt @@ -1,3 +1,22 @@ +/* + * 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.sandbox /** @@ -44,7 +63,6 @@ data class IniHiaLetters( * Request for INI letter. */ data class IniLetter( - val userId: String, val customerId: String, val name: String, @@ -79,3 +97,29 @@ data class HiaLetter( val enc_public_modulus: String, val enc_hash: String ) + +data class EbicsSubscribersResponse( + val subscribers: List<String> +) + +data class EbicsSubscriberResponse( + val id: String, + val partnerID: String, + val userID: String, + val systemID: String?, + val state: String +) + +data class EbicsHostsResponse( + val ebicsHosts: List<String> +) + +data class EbicsHostResponse( + val hostID: String, + val ebicsVersion: String +) + +data class EbicsHostCreateRequest( + val hostID: String, + val ebicsVersion: String +) +\ No newline at end of file diff --git a/sandbox/src/main/kotlin/Main.kt b/sandbox/src/main/kotlin/Main.kt @@ -20,15 +20,18 @@ package tech.libeufin.sandbox import io.ktor.application.ApplicationCall +import io.ktor.application.ApplicationCallPipeline import io.ktor.application.call import io.ktor.application.install import io.ktor.features.CallLogging import io.ktor.features.ContentNegotiation +import io.ktor.features.StatusPages import io.ktor.gson.gson import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.request.receive import io.ktor.request.receiveText +import io.ktor.request.uri import io.ktor.response.respond import io.ktor.response.respondText import io.ktor.routing.get @@ -36,285 +39,86 @@ import io.ktor.routing.post import io.ktor.routing.routing import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty -import org.jetbrains.exposed.dao.EntityID +import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.transactions.transaction +import org.slf4j.Logger import org.slf4j.LoggerFactory import org.w3c.dom.Document import tech.libeufin.sandbox.db.* -import tech.libeufin.schema.ebics_h004.* +import tech.libeufin.schema.ebics_h004.EbicsKeyManagementResponse +import tech.libeufin.schema.ebics_h004.EbicsUnsecuredRequest +import tech.libeufin.schema.ebics_h004.HIARequestOrderDataType import tech.libeufin.schema.ebics_hev.HEVResponse import tech.libeufin.schema.ebics_hev.SystemReturnCodeType import tech.libeufin.schema.ebics_s001.SignaturePubKeyOrderData -import java.math.BigInteger import java.nio.charset.StandardCharsets.US_ASCII import java.nio.charset.StandardCharsets.UTF_8 -import java.security.KeyFactory -import java.security.PrivateKey -import java.security.PublicKey -import java.security.spec.RSAPrivateKeySpec -import java.security.spec.RSAPublicKeySpec +import java.security.interfaces.RSAPublicKey import java.text.DateFormat -import java.util.* import java.util.zip.InflaterInputStream +import javax.sql.rowset.serial.SerialBlob -val logger = LoggerFactory.getLogger("tech.libeufin.sandbox") -val xmlProcess = XMLUtil() -val getEbicsHostId = { "LIBEUFIN-SANDBOX" } -val getEbicsVersion = { "H004" } -val getEbicsRevision = { 1 } - - -/** - * Instantiate a new RSA public key. - * - * @param exponent - * @param modulus - * @return key - */ -fun loadRsaPublicKey(modulus: ByteArray, exponent: ByteArray): PublicKey { - - val modulusBigInt = BigInteger(1, modulus) - val exponentBigInt = BigInteger(1, exponent) - - val keyFactory = KeyFactory.getInstance("RSA") - val tmp = RSAPublicKeySpec(modulusBigInt, exponentBigInt) - return keyFactory.generatePublic(tmp) -} - -/** - * The function tries to get the bank private key from the database. - * If it does not find it, it generates a new one and stores it in - * database. - * - * @return the key (whether from database or freshly created) - */ -fun getOrMakePrivateKey(): PrivateKey { - - // bank has always one private key in database. - var tmp = transaction { - EbicsBankPrivateKey.findById(1) - } - - // must generate one now - if (tmp == null) { - - val privateExponent = - BigInteger(PRIVATE_KEY_EXPONENT_LENGTH, Random()) // shall be set to some well-known value? - val privateModulus = BigInteger(PRIVATE_KEY_MODULUS_LENGTH, Random()) - - tmp = transaction { - EbicsBankPrivateKey.new { - modulus = privateModulus.toByteArray() - exponent = privateExponent.toByteArray() - } - } - } - - val keySpec = RSAPrivateKeySpec( - BigInteger(tmp.modulus), - BigInteger(tmp.exponent) - ) - - val factory = KeyFactory.getInstance("RSA") - val privateKey = factory.generatePrivate(keySpec) - - return privateKey -} - - -private suspend fun ApplicationCall.adminCustomers() { - val body = try { - receive<CustomerRequest>() - } catch (e: Exception) { - e.printStackTrace() - respond( - HttpStatusCode.BadRequest, - SandboxError(e.message.toString()) - ) - return - } - logger.info(body.toString()) - - val returnId = transaction { - val myUserId = EbicsUser.new { } - val myPartnerId = EbicsPartner.new { } - val mySystemId = EbicsSystem.new { } - val subscriber = EbicsSubscriber.new { - userId = myUserId - partnerId = myPartnerId - systemId = mySystemId - state = SubscriberStates.NEW - } - println("subscriber ID: ${subscriber.id.value}") - val customer = BankCustomer.new { - name = body.name - ebicsSubscriber = subscriber - } - println("name: ${customer.name}") - return@transaction customer.id.value - } - - respond( - HttpStatusCode.OK, - CustomerResponse(id = returnId) - ) -} - -private suspend fun ApplicationCall.adminCustomersInfo() { - val id: Int = try { - parameters["id"]!!.toInt() - } catch (e: NumberFormatException) { - respond( - HttpStatusCode.BadRequest, - SandboxError(e.message.toString()) - ) - return - } - - val customerInfo = transaction { - val customer = BankCustomer.findById(id) ?: return@transaction null - CustomerInfo( - customer.name, - ebicsInfo = CustomerEbicsInfo( - customer.ebicsSubscriber.userId.userId!! - ) - ) - } - - if (null == customerInfo) { - respond( - HttpStatusCode.NotFound, - SandboxError("id $id not found") - ) - return - } - - respond(HttpStatusCode.OK, customerInfo) -} - -private suspend fun ApplicationCall.adminCustomersKeyletter() { - val body = try { - receive<IniHiaLetters>() - } catch (e: Exception) { - e.printStackTrace() - respond( - HttpStatusCode.BadRequest, - SandboxError(e.message.toString()) - ) - return - } - - val ebicsUserID = transaction { - EbicsUser.find { EbicsUsers.userId eq body.ini.userId }.firstOrNull() - } - - if (ebicsUserID == null) { - respond( - HttpStatusCode.NotFound, - SandboxError("User ID not found") - ) - return - } - - val ebicsSubscriber = EbicsSubscriber.find { - EbicsSubscribers.userId eq EntityID(ebicsUserID.id.value, EbicsUsers) - }.firstOrNull() - - if (ebicsSubscriber == null) { - respond( - HttpStatusCode.InternalServerError, - SandboxError("Bank had internal errors retrieving the Subscriber") - ) - return - } - - // check signature key - var modulusFromDd = BigInteger(ebicsSubscriber.signatureKey?.modulus) - var exponentFromDb = BigInteger(ebicsSubscriber.signatureKey?.exponent) - var modulusFromLetter = body.ini.public_modulus.toBigInteger(16) - var exponentFromLetter = body.ini.public_modulus.toBigInteger(16) - - if (!((modulusFromDd == modulusFromLetter) && (exponentFromDb == exponentFromLetter))) { - logger.info("Signature key mismatches for ${ebicsUserID.userId}") - respond( - HttpStatusCode.NotAcceptable, - SandboxError("Signature Key mismatches!") - ) - return - } - - logger.info("Signature key from user ${ebicsUserID.userId} becomes RELEASED") - ebicsSubscriber.signatureKey?.state = KeyStates.RELEASED - - // check identification and authentication key - modulusFromDd = BigInteger(ebicsSubscriber.authenticationKey?.modulus) - exponentFromDb = BigInteger(ebicsSubscriber.authenticationKey?.exponent) - modulusFromLetter = body.hia.ia_public_modulus.toBigInteger(16) - exponentFromLetter = body.hia.ia_public_exponent.toBigInteger(16) - - if (!((modulusFromDd == modulusFromLetter) && (exponentFromDb == exponentFromLetter))) { - logger.info("Identification and authorization key mismatches for ${ebicsUserID.userId}") - respond( - HttpStatusCode.NotAcceptable, - SandboxError("Identification and authorization key mismatches!") - ) - return - } - - logger.info("Authentication key from user ${ebicsUserID.userId} becomes RELEASED") - ebicsSubscriber.authenticationKey?.state = KeyStates.RELEASED - - // check encryption key - modulusFromDd = BigInteger(ebicsSubscriber.encryptionKey?.modulus) - exponentFromDb = BigInteger(ebicsSubscriber.encryptionKey?.exponent) - modulusFromLetter = body.hia.enc_public_modulus.toBigInteger(16) - exponentFromLetter = body.hia.enc_public_exponent.toBigInteger(16) - - if (!((modulusFromDd == modulusFromLetter) && (exponentFromDb == exponentFromLetter))) { - logger.info("Encryption key mismatches for ${ebicsUserID.userId}") - respond( - HttpStatusCode.NotAcceptable, - SandboxError("Encryption key mismatches!") - ) - return - } - - logger.info("Encryption key from user ${ebicsUserID.userId} becomes RELEASED") - ebicsSubscriber.encryptionKey?.state = KeyStates.RELEASED +val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox") +val xmlProcess = XMLUtil() - // TODO change subscriber status! - ebicsSubscriber.state = SubscriberStates.READY - - respond( - HttpStatusCode.OK, - "Your status has changed to READY" - ) -} +data class EbicsRequestError(val statusCode: HttpStatusCode) : Exception("Ebics request error") private suspend fun ApplicationCall.respondEbicsKeyManagement( errorText: String, errorCode: String, - statusCode: HttpStatusCode + statusCode: HttpStatusCode, + orderId: String? = null, + bankReturnCode: String? = null ) { - val responseXml = EbicsResponse().apply { - header = EbicsResponse.Header().apply { - mutable = ResponseMutableHeaderType().apply { + val responseXml = EbicsKeyManagementResponse().apply { + version = "H004" + header = EbicsKeyManagementResponse.Header().apply { + authenticate = true + mutable = EbicsKeyManagementResponse.Header.KeyManagementResponseMutableHeaderType().apply { reportText = errorText returnCode = errorCode + if (orderId != null) { + this.orderID = orderId + } + } + _static = EbicsKeyManagementResponse.Header.EmptyStaticHeader() + } + body = EbicsKeyManagementResponse.Body().apply { + if (bankReturnCode != null) { + this.returnCode = EbicsKeyManagementResponse.Body.ReturnCode().apply { + this.authenticate = true + this.value = bankReturnCode + } } } } val text = XMLUtil.convertJaxbToString(responseXml) + logger.info("responding with:\n${text}") respondText(text, ContentType.Application.Xml, statusCode) } + private suspend fun ApplicationCall.respondEbicsInvalidXml() { respondEbicsKeyManagement("[EBICS_INVALID_XML]", "091010", HttpStatusCode.BadRequest) } -private suspend fun ApplicationCall.ebicsweb() { +fun findEbicsSubscriber(partnerID: String, userID: String, systemID: String?): EbicsSubscriber? { + return if (systemID == null) { + EbicsSubscriber.find { + (EbicsSubscribers.partnerId eq partnerID) and (EbicsSubscribers.userId eq userID) + } + } else { + EbicsSubscriber.find { + (EbicsSubscribers.partnerId eq partnerID) and + (EbicsSubscribers.userId eq userID) and + (EbicsSubscribers.systemId eq systemID) + } + }.firstOrNull() +} + +private suspend fun ApplicationCall.ebicsweb() { val body: String = receiveText() logger.debug("Data received: $body") @@ -335,28 +139,16 @@ private suspend fun ApplicationCall.ebicsweb() { bodyDocument ) - if (bodyJaxb.value.header.static.hostID != getEbicsHostId()) { - respondEbicsKeyManagement("[EBICS_INVALID_HOST_ID]", "091011", HttpStatusCode.NotFound) - return - } - - val ebicsUserID = transaction { - EbicsUser.find { EbicsUsers.userId eq bodyJaxb.value.header.static.userID }.firstOrNull() - } + val staticHeader = bodyJaxb.value.header.static + val requestHostID = bodyJaxb.value.header.static.hostID - if (ebicsUserID == null) { - respondEbicsKeyManagement("[EBICS_UNKNOWN_USER]", "091003", HttpStatusCode.NotFound) - return + val ebicsHost = transaction { + EbicsHost.find { EbicsHosts.hostID eq requestHostID }.firstOrNull() } - val ebicsSubscriber = transaction { - EbicsSubscriber.find { - EbicsSubscribers.userId eq EntityID(ebicsUserID.id.value, EbicsUsers) - }.firstOrNull() - } - - if (ebicsSubscriber == null) { - respondEbicsKeyManagement("[EBICS_INTERNAL_ERROR]", "061099", HttpStatusCode.InternalServerError) + if (ebicsHost == null) { + logger.warn("client requested unknown HostID") + respondEbicsKeyManagement("[EBICS_INVALID_HOST_ID]", "091011", HttpStatusCode.NotFound) return } @@ -403,12 +195,11 @@ private suspend fun ApplicationCall.ebicsweb() { logger.debug("Found payload: ${payload.toString(US_ASCII)}") when (bodyJaxb.value.header.static.orderDetails.orderType) { - "INI" -> { val keyObject = XMLUtil.convertStringToJaxb<SignaturePubKeyOrderData>(payload.toString(UTF_8)) - try { - loadRsaPublicKey( + val rsaPublicKey: RSAPublicKey = try { + CryptoUtil.loadRsaPublicKeyFromComponents( keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.modulus, keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.exponent ) @@ -421,64 +212,93 @@ private suspend fun ApplicationCall.ebicsweb() { // put try-catch block here? (FIXME) transaction { + val ebicsSubscriber = + findEbicsSubscriber(staticHeader.partnerID, staticHeader.userID, staticHeader.systemID) + if (ebicsSubscriber == null) { + logger.warn("ebics subscriber ('${staticHeader.partnerID}' / '${staticHeader.userID}' / '${staticHeader.systemID}') not found") + throw EbicsRequestError(HttpStatusCode.NotFound) + } ebicsSubscriber.signatureKey = EbicsPublicKey.new { - modulus = keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.modulus - exponent = keyObject.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.exponent - state = KeyStates.NEW + this.rsaPublicKey = SerialBlob(rsaPublicKey.encoded) + state = KeyState.NEW } - if (ebicsSubscriber.state == SubscriberStates.NEW) { - ebicsSubscriber.state = SubscriberStates.PARTIALLY_INITIALIZED_INI + if (ebicsSubscriber.state == SubscriberState.NEW) { + ebicsSubscriber.state = SubscriberState.PARTIALLY_INITIALIZED_INI } - if (ebicsSubscriber.state == SubscriberStates.PARTIALLY_INITIALIZED_HIA) { - ebicsSubscriber.state = SubscriberStates.INITIALIZED + if (ebicsSubscriber.state == SubscriberState.PARTIALLY_INITIALIZED_HIA) { + ebicsSubscriber.state = SubscriberState.INITIALIZED } } logger.info("Signature key inserted in database _and_ subscriber state changed accordingly") + respondEbicsKeyManagement( + "[EBICS_OK]", + "000000", + HttpStatusCode.OK, + bankReturnCode = "000000", + orderId = "OR01" + ) + return } "HIA" -> { val keyObject = XMLUtil.convertStringToJaxb<HIARequestOrderDataType>(payload.toString(US_ASCII)) - try { - loadRsaPublicKey( + val authenticationPublicKey = try { + CryptoUtil.loadRsaPublicKeyFromComponents( keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.modulus, keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.exponent ) - loadRsaPublicKey( + } catch (e: Exception) { + logger.info("auth public key invalid") + e.printStackTrace() + respondEbicsInvalidXml() + return + } + + val encryptionPublicKey = try { + CryptoUtil.loadRsaPublicKeyFromComponents( keyObject.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.modulus, keyObject.value.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue.exponent ) } catch (e: Exception) { - logger.info("User gave at least one invalid HIA key") + logger.info("auth public key invalid") e.printStackTrace() respondEbicsInvalidXml() return } - // put try-catch block here? (FIXME) transaction { + val ebicsSubscriber = + findEbicsSubscriber(staticHeader.partnerID, staticHeader.userID, staticHeader.systemID) + if (ebicsSubscriber == null) { + logger.warn("ebics subscriber not found") + throw EbicsRequestError(HttpStatusCode.NotFound) + } ebicsSubscriber.authenticationKey = EbicsPublicKey.new { - modulus = keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.modulus - exponent = keyObject.value.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue.exponent - state = KeyStates.NEW + this.rsaPublicKey = SerialBlob(authenticationPublicKey.encoded) + state = KeyState.NEW + } + ebicsSubscriber.encryptionKey = EbicsPublicKey.new { + this.rsaPublicKey = SerialBlob(encryptionPublicKey.encoded) + state = KeyState.NEW } - if (ebicsSubscriber.state == SubscriberStates.NEW) { - ebicsSubscriber.state = SubscriberStates.PARTIALLY_INITIALIZED_HIA + if (ebicsSubscriber.state == SubscriberState.NEW) { + ebicsSubscriber.state = SubscriberState.PARTIALLY_INITIALIZED_HIA } - if (ebicsSubscriber.state == SubscriberStates.PARTIALLY_INITIALIZED_INI) { - ebicsSubscriber.state = SubscriberStates.INITIALIZED + if (ebicsSubscriber.state == SubscriberState.PARTIALLY_INITIALIZED_INI) { + ebicsSubscriber.state = SubscriberState.INITIALIZED } } + respondEbicsKeyManagement("[EBICS_OK]", "000000", HttpStatusCode.OK) } } - respondEbicsKeyManagement("[EBICS_OK]", "000000", HttpStatusCode.OK) - return + throw AssertionError("not reached") } "ebicsHEVRequest" -> { @@ -509,8 +329,28 @@ private suspend fun ApplicationCall.ebicsweb() { fun main() { dbCreateTables() - val server = embeddedServer(Netty, port = 5000) { + transaction { + val pairA = CryptoUtil.generateRsaKeyPair(2048) + val pairB = CryptoUtil.generateRsaKeyPair(2048) + val pairC = CryptoUtil.generateRsaKeyPair(2048) + EbicsHost.new { + hostId = "host01" + ebicsVersion = "H004" + authenticationPrivateKey = SerialBlob(pairA.private.encoded) + encryptionPrivateKey = SerialBlob(pairB.private.encoded) + signaturePrivateKey = SerialBlob(pairC.private.encoded) + } + + EbicsSubscriber.new { + partnerId = "PARTNER1" + userId = "USER1" + systemId = null + state = SubscriberState.NEW + } + } + + val server = embeddedServer(Netty, port = 5000) { install(CallLogging) install(ContentNegotiation) { gson { @@ -518,35 +358,72 @@ fun main() { setPrettyPrinting() } } + install(StatusPages) { + exception<Throwable> { cause -> + logger.error("Exception while handling '${call.request.uri.toString()}'", cause) + call.respondText("Internal server error.", ContentType.Text.Plain, HttpStatusCode.InternalServerError) + } + } + intercept(ApplicationCallPipeline.Fallback) { + if (this.call.response.status() == null) { + call.respondText("Not found (no route matched).\n", ContentType.Text.Plain, HttpStatusCode.NotFound) + return@intercept finish() + } + } routing { + //trace { logger.info(it.buildText()) } get("/") { - logger.debug("GET: not implemented") call.respondText("Hello LibEuFin!\n", ContentType.Text.Plain) - return@get } - - post("/admin/customers") { - call.adminCustomers() - return@post + get("/ebics/hosts") { + val ebicsHosts = transaction { + EbicsHost.all().map { it.hostId } + } + call.respond(EbicsHostsResponse(ebicsHosts)) } - - get("/admin/customers/{id}") { - call.adminCustomersInfo() - return@get + post("/ebics/hosts") { + val req = call.receive<EbicsHostCreateRequest>() + transaction { + EbicsHost.new { + this.ebicsVersion = req.ebicsVersion + this.hostId = hostId + } + } } - - post("/admin/customers/{id}/ebics/keyletter") { - call.adminCustomersKeyletter() - return@post + get("/ebics/hosts/{id}") { + val resp = transaction { + val host = EbicsHost.find { EbicsHosts.hostID eq call.parameters["id"]!! }.firstOrNull() + if (host == null) null + else EbicsHostResponse(host.hostId, host.ebicsVersion) + } + if (resp == null) call.respond(HttpStatusCode.NotFound, SandboxError("host not found")) + else call.respond(resp) + } + get("/ebics/subscribers") { + val subscribers = transaction { + EbicsSubscriber.all().map { it.id.value.toString() } + } + call.respond(EbicsSubscribersResponse(subscribers)) + } + get("/ebics/subscribers/{id}") { + val resp = transaction { + val id = call.parameters["id"]!! + val subscriber = EbicsSubscriber.findById(id.toInt())!! + EbicsSubscriberResponse( + id, + subscriber.partnerId, + subscriber.userId, + subscriber.systemId, + subscriber.state.name + ) + } + call.respond(resp) } - post("/ebicsweb") { call.ebicsweb() - return@post } } } logger.info("Up and running") server.start(wait = true) } - diff --git a/sandbox/src/main/kotlin/ProtocolAndVersion.kt b/sandbox/src/main/kotlin/ProtocolAndVersion.kt @@ -1,6 +0,0 @@ -package tech.libeufin.sandbox - -class ProtocolAndVersion(protocol: String, version: String) { - val protocol = protocol - val version = version -} -\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsMessages.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsMessages.kt @@ -132,11 +132,11 @@ class EbicsUnsecuredRequest { @XmlType(name = "", propOrder = ["dataTransfer"]) class Body { @get:XmlElement(name = "DataTransfer", required = true) - lateinit var dataTransfer: DataTransfer + lateinit var dataTransfer: UnsecuredDataTransfer @XmlAccessorType(XmlAccessType.NONE) @XmlType(name = "", propOrder = ["orderData"]) - class DataTransfer { + class UnsecuredDataTransfer { @get:XmlElement(name = "OrderData", required = true) lateinit var orderData: OrderData @@ -166,55 +166,32 @@ class EbicsUnsecuredRequest { lateinit var body: Body } - @XmlAccessorType(XmlAccessType.NONE) -@XmlType(name = "DataTransferResponseType", propOrder = ["dataEncryptionInfo", "orderData", "any"]) -class DataTransferResponse { - @get:XmlElement(name = "DataEncryptionInfo") - var dataEncryptionInfo: DataEncryptionInfo? = null +class DataEncryptionInfo { + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + + @get:XmlElement(name = "EncryptionPubKeyDigest", required = true) + lateinit var encryptionPubKeyDigest: EncryptionPubKeyDigest - @get:XmlElement(name = "OrderData", required = true) - lateinit var orderData: OrderData + @get:XmlElement(name = "TransactionKey", required = true) + lateinit var transactionKey: ByteArray @get:XmlAnyElement(lax = true) var any: List<Any>? = null @XmlAccessorType(XmlAccessType.NONE) - class OrderData { - @get:XmlValue - lateinit var value: ByteArray - - @get:XmlAnyAttribute - var otherAttributes = HashMap<QName, String>() - } - - @XmlAccessorType(XmlAccessType.NONE) - class DataEncryptionInfo { - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - - @get:XmlElement(name = "EncryptionPubKeyDigest", required = true) - lateinit var encryptionPubKeyDigest: EncryptionPubKeyDigest - - @get:XmlElement(name = "TransactionKey", required = true) - lateinit var transactionKey: ByteArray - - @get:XmlAnyElement(lax = true) - var any: List<Any>? = null - - @XmlAccessorType(XmlAccessType.NONE) - class EncryptionPubKeyDigest { - @get:XmlAttribute(name = "Version", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var version: String + class EncryptionPubKeyDigest { + @get:XmlAttribute(name = "Version", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var version: String - @XmlAttribute(name = "Algorithm", required = true) - @XmlSchemaType(name = "anyURI") - lateinit var algorithm: String + @XmlAttribute(name = "Algorithm", required = true) + @XmlSchemaType(name = "anyURI") + lateinit var algorithm: String - @get:XmlValue - lateinit var value: ByteArray - } + @get:XmlValue + lateinit var value: ByteArray } } @@ -224,7 +201,7 @@ class DataTransferResponse { name = "ResponseMutableHeaderType", propOrder = ["transactionPhase", "segmentNumber", "orderID", "returnCode", "reportText", "any"] ) -class ResponseMutableHeaderType { +class EbicsResponseMutableHeaderType { @XmlElement(name = "TransactionPhase", required = true) @XmlSchemaType(name = "token") lateinit var transactionPhase: TransactionPhaseType @@ -296,6 +273,16 @@ class ResponseStaticHeaderType { @XmlAccessorType(XmlAccessType.NONE) +class TimestampBankParameter { + @XmlValue + lateinit var value: XMLGregorianCalendar + + @XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false +} + + +@XmlAccessorType(XmlAccessType.NONE) @XmlType(name = "", propOrder = ["header", "authSignature", "body"]) @XmlRootElement(name = "ebicsResponse") class EbicsResponse { @@ -321,16 +308,11 @@ class EbicsResponse { @XmlAccessorType(XmlAccessType.NONE) @XmlType(name = "", propOrder = ["_static", "mutable"]) class Header { - - init { - println("creating header") - } - @get:XmlElement(name = "static", required = true) - var _static: ResponseStaticHeaderType? = null + lateinit var _static: ResponseStaticHeaderType @get:XmlElement(required = true) - var mutable: ResponseMutableHeaderType? = null + lateinit var mutable: EbicsResponseMutableHeaderType @get:XmlAttribute(name = "authenticate", required = true) var authenticate: Boolean = false @@ -340,7 +322,7 @@ class EbicsResponse { @XmlType(name = "", propOrder = ["dataTransfer", "returnCode", "timestampBankParameter"]) class Body { @XmlElement(name = "DataTransfer") - var dataTransfer: DataTransferResponse? = null + var dataTransfer: DataTransferResponseType? = null @XmlElement(name = "ReturnCode", required = true) lateinit var returnCode: ReturnCode @@ -349,15 +331,6 @@ class EbicsResponse { var timestampBankParameter: TimestampBankParameter? = null @XmlAccessorType(XmlAccessType.NONE) - class TimestampBankParameter { - @XmlValue - lateinit var value: XMLGregorianCalendar - - @XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - } - - @XmlAccessorType(XmlAccessType.NONE) class ReturnCode { @get:XmlValue @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) @@ -366,19 +339,48 @@ class EbicsResponse { @get:XmlAttribute(name = "authenticate", required = true) var authenticate: Boolean = false } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "DataTransferResponseType", propOrder = ["dataEncryptionInfo", "orderData", "any"]) + class DataTransferResponseType { + @get:XmlElement(name = "DataEncryptionInfo") + var dataEncryptionInfo: DataEncryptionInfo? = null + + @get:XmlElement(name = "OrderData", required = true) + lateinit var orderData: OrderData + + @get:XmlAnyElement(lax = true) + var any: List<Any>? = null + + @XmlAccessorType(XmlAccessType.NONE) + class OrderData { + @get:XmlValue + lateinit var value: ByteArray + + @get:XmlAnyAttribute + var otherAttributes = HashMap<QName, String>() + } + } } } +@XmlAccessorType(XmlAccessType.NONE) +@XmlType( + name = "PubKeyValueType", propOrder = [ + "rsaKeyValue", + "timeStamp" + ] +) class PubKeyValueType { - @XmlElement(name = "RSAKeyValue", namespace = "http://www.w3.org/2000/09/xmldsig#", required = true) + @get:XmlElement(name = "RSAKeyValue", namespace = "http://www.w3.org/2000/09/xmldsig#", required = true) lateinit var rsaKeyValue: RSAKeyValueType - @XmlElement(name = "TimeStamp") - @XmlSchemaType(name = "dateTime") + @get:XmlElement(name = "TimeStamp", required = false) + @get:XmlSchemaType(name = "dateTime") var timeStamp: XMLGregorianCalendar? = null - @XmlAnyElement(lax = true) + @get:XmlAnyElement(lax = true) var any: List<Any>? = null } @@ -407,7 +409,7 @@ class AuthenticationPubKeyInfoType { @XmlAccessorType(XmlAccessType.NONE) @XmlType( - name = "AuthenticationPubKeyInfoType", propOrder = [ + name = "EncryptionPubKeyInfoType", propOrder = [ "x509Data", "pubKeyValue", "encryptionVersion" @@ -427,28 +429,115 @@ class EncryptionPubKeyInfoType { } -@XmlAccessorType(XmlAccessType.FIELD) +@XmlAccessorType(XmlAccessType.NONE) @XmlType( name = "HIARequestOrderDataType", propOrder = ["authenticationPubKeyInfo", "encryptionPubKeyInfo", "partnerID", "userID", "any"] ) class HIARequestOrderDataType { - @XmlElement(name = "AuthenticationPubKeyInfo", required = true) + @get:XmlElement(name = "AuthenticationPubKeyInfo", required = true) lateinit var authenticationPubKeyInfo: AuthenticationPubKeyInfoType - @XmlElement(name = "EncryptionPubKeyInfo", required = true) + @get:XmlElement(name = "EncryptionPubKeyInfo", required = true) lateinit var encryptionPubKeyInfo: EncryptionPubKeyInfoType - @XmlElement(name = "PartnerID", required = true) - @XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @XmlSchemaType(name = "token") + @get:XmlElement(name = "PartnerID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") lateinit var partnerID: String - @XmlElement(name = "UserID", required = true) - @XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @XmlSchemaType(name = "token") + @get:XmlElement(name = "UserID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") lateinit var userID: String - @XmlAnyElement(lax = true) + @get:XmlAnyElement(lax = true) var any: List<Any>? = null } + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["header", "body"]) +@XmlRootElement(name = "ebicsKeyManagementResponse") +class EbicsKeyManagementResponse { + @get:XmlElement(required = true) + lateinit var header: Header + + @get:XmlElement(required = true) + lateinit var body: Body + + @get:XmlAttribute(name = "Version", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var version: String + + @get:XmlAttribute(name = "Revision") + var revision: Int? = null + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["_static", "mutable"]) + class Header { + @get:XmlElement(name = "static", required = true) + lateinit var _static: EmptyStaticHeader + + @get:XmlElement(required = true) + lateinit var mutable: KeyManagementResponseMutableHeaderType + + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "") + class EmptyStaticHeader + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["orderID", "returnCode", "reportText"]) + class KeyManagementResponseMutableHeaderType { + @XmlElement(name = "OrderID") + @XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @XmlSchemaType(name = "token") + var orderID: String? = null + + @XmlElement(name = "ReturnCode", required = true) + @XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @XmlSchemaType(name = "token") + lateinit var returnCode: String + + @XmlElement(name = "ReportText", required = true) + @XmlJavaTypeAdapter(NormalizedStringAdapter::class) + @XmlSchemaType(name = "normalizedString") + lateinit var reportText: String + } + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["dataTransfer", "returnCode", "timestampBankParameter"]) + class Body { + @XmlElement(name = "DataTransfer") + val dataTransfer: DataTransfer? = null + + @XmlElement(name = "ReturnCode", required = true) + lateinit var returnCode: ReturnCode + + @XmlElement(name = "TimestampBankParameter") + var timestampBankParameter: TimestampBankParameter? = null + + @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 = "", propOrder = ["dataEncryptionInfo", "orderData"]) + class DataTransfer { + @get:XmlElement(name = "DataEncryptionInfo") + var dataEncryptionInfo: DataEncryptionInfo? = null + + @get:XmlElement(name = "OrderData", required = true) + lateinit var orderData: EbicsResponse.Body.DataTransferResponseType.OrderData + } + } +} diff --git a/sandbox/src/test/kotlin/DbTest.kt b/sandbox/src/test/kotlin/DbTest.kt @@ -1,65 +0,0 @@ -package tech.libeufin.sandbox - -import junit.framework.TestCase.assertFalse -import org.jetbrains.exposed.dao.EntityID -import org.jetbrains.exposed.sql.transactions.transaction -import org.junit.Test -import org.junit.Before -import tech.libeufin.sandbox.db.* - -class DbTest { - - @Before - fun setUp() { - dbCreateTables() - } - - /** - * This function creates a EBICS subscriber _first_, and - * subsequently tries to insert a mock bianry value into - * the keys columns of the subscriber. - */ - @Test - fun storeBinary() { - transaction { - // join table - val subscriber = createSubscriber() - - val key = EbicsPublicKey.new { - modulus = "BINARYVALUE".toByteArray() - exponent = "BINARYVALUE".toByteArray() - state = KeyStates.NEW - } - subscriber.authenticationKey = key - } - } - - @Test - fun nestedQuery() { - - /*** - * Some query like the following is needed: - * - * val result = EbicsSubscriber.find { - * EbicsSubscribers.userId.userId eq "u1" - * }.first() - */ - - transaction { - createSubscriber() - - val tmp = EbicsUser.find { EbicsUsers.userId eq "u1" }.firstOrNull() - if (tmp == null) { - logger.error("No such user found in database.") - return@transaction - } - println("Found user with id: ${tmp.id.value}") - - val found = EbicsSubscriber.find { - EbicsSubscribers.userId eq EntityID(tmp.id.value, EbicsUsers) - } - - assertFalse(found.empty()) - } - } -} -\ No newline at end of file diff --git a/sandbox/src/test/kotlin/HiaLoadTest.kt b/sandbox/src/test/kotlin/HiaLoadTest.kt @@ -11,7 +11,7 @@ class HiaLoadTest { val processor = XMLUtil() val classLoader = ClassLoader.getSystemClassLoader() - val hia = classLoader.getResource("HIA.xml") + val hia = classLoader.getResource("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ebicsUnsecuredRequest xmlns=\"urn:org:ebics:H004\"\n xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"urn:org:ebics:H004 ebics_keymgmt_request_H004.xsd\"\n Version=\"H004\"\n Revision=\"1\">\n <header authenticate=\"true\">\n <static>\n <HostID>LIBEUFIN-SANDBOX</HostID>\n <PartnerID>CUSTM001</PartnerID>\n <UserID>u1</UserID>\n <OrderDetails>\n <OrderType>HIA</OrderType>\n <OrderAttribute>DZNNN</OrderAttribute>\n </OrderDetails>\n <SecurityMedium>0000</SecurityMedium>\n </static>\n <mutable/>\n </header>\n <body>\n <DataTransfer>\n <OrderData>eJzNlsmyo0YWhvf1FBW3l0SZZBY3VHIwCwESM0g7ZpCYxAxPb/mWh7ZdrlUvmhX8Z/gzyPNl5P7npSo/T0nXF0399Q35Cbz9fPi0P8qMmTzHpB8uXZx0fDAEn1+Jdf8e91/f8mFo32F4nuefZuynpstgFAAAAxp+5cR9kf3n7dPn7z0fLb6+jV39/qp6T8Ii6t+PAOA/yn9PXh3/YvpR9+FrAYD8sHbpi39ZLwL7mmpFeVIFX4q6H4I6St4Or157ZhzypB6KKBheP0UfQyVZ5TptDh9G+2+CG5RjcvjNeh/376bF/F3+FtCaeCzH/sCccjF6CEpIHO9qNg7sk6vEJqcu5+zCjy3Te4YaE00r3WCcLidQGGEqJ4uoRMrcaQFlAnAK2xDVb1T2KGFMbW87hbMe9m5XzjCb6kuIareAuStQx66uzs+PUlk5oQim7B6eoNW2qo4Ljxu4jbFWLM4M7n4geL2ZNSiMnxum8+d+YTntqIqVV3eS06KTHYeBuOmqaXA1Ar/csDRAAFFNmr8cGc07u8ZC9TidnjhurtHURIQpvLBIf6Uj/5xQT2pGj62hFy2jbldl1wQtTkKbBKm0d2pWZIxX6Q6brViRT8Hl5UeW6FnsgIC1YQslccVX8AhjALHjgHF/ENYD8+3O7ZLOjPhJWvOeMniZ3ayU8F9rAxgdA9xMO1mN0ITDKKWuzZ23hRWH6P3ZKRn3FBhahVPVUOgmF1Br6mWEGkoWQ9h1eXcWlHsA5sGwVg6PurNwXCdWaw70kceyi3Gye6IjoQTPmksNZ5SAyYFlYQD2HLzenvaur+j7YOw20fN9ZEQK+yYyl0AYctO+tL7HmmVR6lw3hpVuLQpfJxvUcjJfsQ+pQCzmVCuiBpbg+DR1uktRiLu3+LHTSiTqQrWN+QF3a4lGdo/NMirzSlakYYUsCgekexluTTKjt4LKV65gMNMX5fIM3xBPu3PUGepNk19iM6yPa4K3z9m8X4KU3gVayt7E+jGR7DWwoZlcyj38X4P7l2kWlrapX3QcGINhP9L+UH6HAf4+DXu7qBJrCKr2gAJk9wVBvgDERpB3BLxjxG0P/xn/Bhr8D9L+hqb77dg6+ACge/j7sV+Bhn9E9F6oo25t/7eoqyBWNpkKhBnWdoKGuhmCBRJ82aiLXKO+c69WObdyykqpGrcg2A+PPp8it7LPdjNhK5BiMEIn6h6QgJHjEWsmk3Jz+9arzzIBzYNz3qj0mNT8xVTYUR8xVxXWklfOR9nz6FQ6z/gI7Gwkg6kTjx2OnisyF3DN823VzoSr4g5ryJwbHMquYT4NDSEM0PbsLwYsRRPq9lvpA5nnoqlXi9vTCYT4FkyoE7QDt5aBImu2fMdO5d0bBJftAd5cSFpop0IvJKXEH4KxYVdK8B7lsp0JfSmIiwiriH9a+plTZiGxZc8hezlARdah8KgLdshknHN6xLyi0SWHjCm6cYu2J4n6VkCYMjfdzMMxRs1sRCsAt9LXlrr01YT4jeM2RE95x8qg+zShwUrm7MyWCdonrmK+SL8U8gsGwbCZbECR3PP5ihjNrA6ryssn9bR6BnTlRnBkNYmdG6aEuzBLGnrk1WxY2VwBGH5S6tCp/VFGBobwT2FY2JSrPquMjHjlFnGFWDCutPVlcNIwljSFU34Ws5h4bdgq2eKUHV+jcHVQx5v74z2uToGp3xmRjGxpTkTEnnnaf7zQb+rdLpo0DlJL7ZwxCJ8OJIRM6iQvetZRo4sQoCTSbQp1HVovaZ95jUK/zljDup8ehRVrEBGCRGZmzukmaSZn61EXF/pRszUNjc8sVSdM9+Wuq1WA6f+vqP+J5e8oCx+Y/1P/QPzfKN7rQTfUSSfzB8HnjsxZEl5uf2i/Zjj9x6vNqIK5h3/7+rSHv3MJOnz6BXLJ7gw=</OrderData>\n </DataTransfer>\n </body>\n</ebicsUnsecuredRequest>\n") val hiaDom = XMLUtil.parseStringIntoDom(hia.readText()) val x: Element = hiaDom.getElementsByTagNameNS( "urn:org:ebics:H004", diff --git a/sandbox/src/test/kotlin/InnerIniLoadTest.kt b/sandbox/src/test/kotlin/InnerIniLoadTest.kt @@ -1,24 +1,31 @@ package tech.libeufin.sandbox import org.junit.Test +import tech.libeufin.schema.ebics_h004.EbicsUnsecuredRequest import tech.libeufin.schema.ebics_s001.SignaturePubKeyOrderData +import kotlin.test.assertNotNull class InnerIniLoadTest { - - val jaxbKey = { - val classLoader = ClassLoader.getSystemClassLoader() - val file = classLoader.getResource( - "ebics_ini_inner_key.xml" - ) - XMLUtil.convertStringToJaxb<SignaturePubKeyOrderData>(file.readText()) - }() - @Test fun loadInnerKey() { + val jaxbKey = run { + val classLoader = ClassLoader.getSystemClassLoader() + val file = classLoader.getResource( + "ebics_ini_inner_key.xml" + ) + assertNotNull(file) + XMLUtil.convertStringToJaxb<SignaturePubKeyOrderData>(file.readText()) + } val modulus = jaxbKey.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.modulus val exponent = jaxbKey.value.signaturePubKeyInfo.pubKeyValue.rsaKeyValue.exponent + CryptoUtil.loadRsaPublicKeyFromComponents(modulus, exponent) + } - loadRsaPublicKey(modulus, exponent) + @Test + fun loadIniMessage() { + val classLoader = ClassLoader.getSystemClassLoader() + val text = classLoader.getResource("ebics_ini_request_sample.xml")!!.readText() + XMLUtil.convertStringToJaxb<EbicsUnsecuredRequest>(text) } } \ No newline at end of file diff --git a/sandbox/src/test/kotlin/JaxbTest.kt b/sandbox/src/test/kotlin/JaxbTest.kt @@ -2,10 +2,13 @@ package tech.libeufin.sandbox import junit.framework.TestCase.assertEquals import org.junit.Test +import tech.libeufin.schema.ebics_h004.EbicsKeyManagementResponse import tech.libeufin.schema.ebics_h004.EbicsUnsecuredRequest +import tech.libeufin.schema.ebics_h004.HIARequestOrderDataType import tech.libeufin.schema.ebics_hev.HEVResponse import tech.libeufin.schema.ebics_hev.SystemReturnCodeType import tech.libeufin.schema.ebics_s001.SignaturePubKeyOrderData +import kotlin.test.assertTrue class JaxbTest { /** @@ -65,4 +68,28 @@ class JaxbTest { iniDom ) } + + @Test + fun testKeyMgmgResponse() { + val responseXml = EbicsKeyManagementResponse().apply { + header = EbicsKeyManagementResponse.Header().apply { + mutable = EbicsKeyManagementResponse.Header.KeyManagementResponseMutableHeaderType().apply { + reportText = "foo" + returnCode = "bar" + } + _static = EbicsKeyManagementResponse.Header.EmptyStaticHeader() + } + version = "H004" + body = EbicsKeyManagementResponse.Body() + } + val text = XMLUtil.convertJaxbToString(responseXml) + assertTrue(text.isNotEmpty()) + } + + @Test + fun testParseHiaRequestOrderData() { + val classLoader = ClassLoader.getSystemClassLoader() + val hia = classLoader.getResource("hia_request_order_data.xml")!!.readText() + XMLUtil.convertStringToJaxb<HIARequestOrderDataType>(hia) + } } \ No newline at end of file diff --git a/sandbox/src/test/resources/ebics_ini_request_sample.xml b/sandbox/src/test/resources/ebics_ini_request_sample.xml @@ -1,35 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> -<ebicsUnsecuredRequest Revision="1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:org:ebics:H004 ebics_keymgmt_request_H004.xsd" Version="H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns="urn:org:ebics:H004"> - <header authenticate="true"> - <static> - <HostID>LIBEUFIN-SANDBOX</HostID> - <PartnerID>flokid</PartnerID> - <UserID>u1</UserID> - <!-- - - Such a not allowed renaming like this fixes the import of DOM into JAXB. - - <UnsecuredReqOrderDetails> - <OrderType>INI</OrderType> - <OrderAttribute>DZNNN</OrderAttribute> - </UnsecuredReqOrderDetails - - Also putting the following attribute on OrderDetails fixes it. - - xsi:type="UnsecuredReqOrderDetailsType" - - --> - <OrderDetails> - <OrderType>INI</OrderType> - <OrderAttribute>DZNNN</OrderAttribute> - </OrderDetails> - <SecurityMedium>0000</SecurityMedium> - </static> - <mutable /> - </header> - <body> - <DataTransfer> - <OrderData>eJx9U1tzmkAUfu9M/wNDH524XIINjprBaLxSUASDL51FlvvFsIus/vpSYm3a1D6e7/vO/ZzeI00T5ogKHOZZn+XbHMugbJ+7Yeb32ZJ4dw/s4+Dzp54R+hkkZYH00lmgk1a4qBhBApnaP8NdisM+GxBy6AJQVVW7Ett54QOB43jwoi6NfYBSeBdmmMBsj1im1ndxAy7zPSRN8nfuyAn3uIlg1BGYmwxozO/4V3Ftil32UpKLb1TEAU4Gtcat3b5c1P/JztbtM8zfA5hlXt4QNfWGWDAp0QWqQRd314byAX9j1NwtkxIPsBSTgy/SSeTsdr61scm2xaEOxVufvxfM6pirYuqVauu1iGdQcnRzslpOO8P1Up6hyM6xHuRKJ6rUoUG1h6UHoY9OuzkRVKmaxsncPoueMiKrwuKdWFrLggZtIO927pRWIrVlkaTS3LMKdbSVR+OFNOQD82w/u1H5DbyWAsDuWE6rgAp2ZFeVa6zOx44aOMRYyJoyj/RJLnoxxQHppNBI10+uTw/JTDM0IU11vorMs2OZ6r5Yx/JipB4zR4yfaLhJW+h4pC+d+X11sHlH2ZZxnswdqCynyTPxNzm/zMrERGBIwwWVp4e0HCtQx/FD7LvlCz5L2utU8IF1qr5KVOLhwtKpthnqJ711H6/6/R54N+Q/Rz+mhzxDGRkoK2XY6K7IdXfgn8vrgY+L/n0W1tsPDRSO6/TAB7i5IXDriHo6LEiGitlo4CX5XRy6dbIr1ChMfKXLn/TFrl8T3PrNwQ+qBEY2</OrderData> - </DataTransfer> - </body> -</ebicsUnsecuredRequest> +<ebicsUnsecuredRequest Revision="1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="urn:org:ebics:H004 ebics_keymgmt_request_H004.xsd" Version="H004" + xmlns="urn:org:ebics:H004"> + <header authenticate="true"> + <static> + <HostID>myhost</HostID> + <PartnerID>k1</PartnerID> + <UserID>u1</UserID> + <OrderDetails> + <OrderType>INI</OrderType> + <OrderAttribute>DZNNN</OrderAttribute> + </OrderDetails> + <SecurityMedium>0000</SecurityMedium> + </static> + <mutable/> + </header> + <body> + <DataTransfer> + <OrderData> + eJx9U1tzmkAUfu9M/wNDH524XIINjprBaOIFCopg8CWzyHK/GHaR1V9fhtg0bWrf9nzfdy57LoN7mqXMEZU4KvIhy3c5lkH5vvCiPBiyFfFv7tj70dcvAzMKckiqEhmVu0QnvfRQOYEEMo1/jvsUR0M2JOTQB6Cu624tdosyAALH8eBZU819iDJ4E+WYwHyPWKbR93ELqsUekjb5B3fkRnvcRjCbCMxVBrTmC/5VXJdij72U5OErFXGAk0Gj8Rq3bxf1f7KzzfcZ5u8GzHO/aImGekNsmFboAjWgh/trU/mEvzFa4VVphUdYSsghEOlT7O52gb1xyLbDoR7F24C/Faz6WGhi5lda57VM5lByDetppc5647Uqz1HsFNgIC6UX19rYpPqd6kMYoNNuQQRNqmdJunDOoq9MyKq0eTeR1rKgQwfIu503o7VIHVkkmbTw7VKbbOXJdCmN+dA6O49eXP0Ar5UAsDeVszqkghM7de2Zq/Oxp4UuMZeyrixi46kQ/YTikPQyaGbrBy+gh3Sum7qQZQZfx9bZtS1tX64TeTnRjrkrJg802mQddDzS597itj44vKtsq6RIFy5U1Fn6SIJNwat5lVoIjGm0pPLskFVTBRo4uUsCr3rGZ0l/nQkBsE/1d4lKPFzaBtU3Y+NkdG6T1XA4AB+a/Gfrp/RQ5CgnI2WljFvdO/I+O/DP4Q3A50H/Xgv77YZGCsf1BuAT3O4QuLZEAwOWJEflfDJK+CbPu9WSFm7fVcNcns1BgmsXOfoJ1l5CIg== + </OrderData> + </DataTransfer> + </body> +</ebicsUnsecuredRequest> +\ No newline at end of file diff --git a/sandbox/src/test/resources/ebics_ini_request_sample_patched.xml b/sandbox/src/test/resources/ebics_ini_request_sample_patched.xml @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<ebicsUnsecuredRequest Revision="1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:org:ebics:H004 ebics_keymgmt_request_H004.xsd" Version="H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns="urn:org:ebics:H004"> - <header authenticate="true"> - <static> - <HostID>foo</HostID> - <PartnerID>flokid</PartnerID> - <UserID>flouid</UserID> - <OrderDetails xsi:type="UnsecuredReqOrderDetailsType"> - <OrderType>INI</OrderType> - <OrderAttribute>DZNNN</OrderAttribute> - </OrderDetails> - <SecurityMedium>0000</SecurityMedium> - </static> - <mutable /> - </header> - <body> - <DataTransfer> - <OrderData>eJx9U1tzmkAUfu9M/wNDH524XIINjprBaLxSUASDL51FlvvFsIus/vpSYm3a1D6e7/vO/ZzeI00T5ogKHOZZn+XbHMugbJ+7Yeb32ZJ4dw/s4+Dzp54R+hkkZYH00lmgk1a4qBhBApnaP8NdisM+GxBy6AJQVVW7Ett54QOB43jwoi6NfYBSeBdmmMBsj1im1ndxAy7zPSRN8nfuyAn3uIlg1BGYmwxozO/4V3Ftil32UpKLb1TEAU4Gtcat3b5c1P/JztbtM8zfA5hlXt4QNfWGWDAp0QWqQRd314byAX9j1NwtkxIPsBSTgy/SSeTsdr61scm2xaEOxVufvxfM6pirYuqVauu1iGdQcnRzslpOO8P1Up6hyM6xHuRKJ6rUoUG1h6UHoY9OuzkRVKmaxsncPoueMiKrwuKdWFrLggZtIO927pRWIrVlkaTS3LMKdbSVR+OFNOQD82w/u1H5DbyWAsDuWE6rgAp2ZFeVa6zOx44aOMRYyJoyj/RJLnoxxQHppNBI10+uTw/JTDM0IU11vorMs2OZ6r5Yx/JipB4zR4yfaLhJW+h4pC+d+X11sHlH2ZZxnswdqCynyTPxNzm/zMrERGBIwwWVp4e0HCtQx/FD7LvlCz5L2utU8IF1qr5KVOLhwtKpthnqJ711H6/6/R54N+Q/Rz+mhzxDGRkoK2XY6K7IdXfgn8vrgY+L/n0W1tsPDRSO6/TAB7i5IXDriHo6LEiGitlo4CX5XRy6dbIr1ChMfKXLn/TFrl8T3PrNwQ+qBEY2</OrderData> - </DataTransfer> - </body> -</ebicsUnsecuredRequest> diff --git a/sandbox/src/test/resources/HIA.xml b/sandbox/src/test/resources/hia_request.xml diff --git a/sandbox/src/test/resources/hia_request_order_data.xml b/sandbox/src/test/resources/hia_request_order_data.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<HIARequestOrderData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:org:ebics:H004 ebics_orders_H004.xsd" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns="urn:org:ebics:H004"> + <AuthenticationPubKeyInfo> + <PubKeyValue> + <ds:RSAKeyValue> + <ds:Modulus>0Ekicvrcj2+8tsF+DZsWihl9W7AyVwtMLxq3qefSWagpfnV7BVsKYIJ/OhiWpvr3dz6K5lHSatzhG1x//jrZt6VHn5Wkkb0M0vayPUiZbe5s2aLabqfOTrt8TPnHwjZMChDHRmGoKI0OzLyQJ6MIfQrHZ5t61ccWubYO/bgbSnP9H39k8QEp0kmW4Tf4u+28GTLgueNAaaPTdCozZjrST4fH9nyhBUZ3nl+vZ+AiUNdl5UfV109CXhCm3safLboUus6ZcYLm6gTaiwJEdRX7HYbnAQZ5gcoXVz/oyxJqTkicVOLPrTAfi3UmFrnIVF8XBtOPdIXHzSpxZ3yT8gH4zQ==</ds:Modulus> + <ds:Exponent>AQAB</ds:Exponent> + </ds:RSAKeyValue> + </PubKeyValue> + <AuthenticationVersion>X002</AuthenticationVersion> + </AuthenticationPubKeyInfo> + <EncryptionPubKeyInfo> + <PubKeyValue> + <ds:RSAKeyValue> + <ds:Modulus>0Ekicvrcj2+8tsF+DZsWihl9W7AyVwtMLxq3qefSWagpfnV7BVsKYIJ/OhiWpvr3dz6K5lHSatzhG1x//jrZt6VHn5Wkkb0M0vayPUiZbe5s2aLabqfOTrt8TPnHwjZMChDHRmGoKI0OzLyQJ6MIfQrHZ5t61ccWubYO/bgbSnP9H39k8QEp0kmW4Tf4u+28GTLgueNAaaPTdCozZjrST4fH9nyhBUZ3nl+vZ+AiUNdl5UfV109CXhCm3safLboUus6ZcYLm6gTaiwJEdRX7HYbnAQZ5gcoXVz/oyxJqTkicVOLPrTAfi3UmFrnIVF8XBtOPdIXHzSpxZ3yT8gH4zQ==</ds:Modulus> + <ds:Exponent>AQAB</ds:Exponent> + </ds:RSAKeyValue> + </PubKeyValue> + <EncryptionVersion>E002</EncryptionVersion> + </EncryptionPubKeyInfo> + <PartnerID>PARTNER1</PartnerID> + <UserID>USER1</UserID> +</HIARequestOrderData> +\ No newline at end of file diff --git a/sandbox/src/test/resources/HPB.xml b/sandbox/src/test/resources/hpb_request.xml