commit 00137637638ad04989f60f6def9d95a69715893b parent c080d20b35f7397209d274596e34b539d080973a Author: Marcello Stanisci <stanisci.m@gmail.com> Date: Thu, 12 Dec 2019 16:08:14 +0100 more migration (compilation fails) Diffstat:
63 files changed, 2973 insertions(+), 2900 deletions(-)
diff --git a/.idea/gradle.xml b/.idea/gradle.xml @@ -15,6 +15,7 @@ <option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$/nexus" /> <option value="$PROJECT_DIR$/sandbox" /> + <option value="$PROJECT_DIR$/util" /> </set> </option> <option name="useQualifiedModuleNames" value="true" /> diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="Kotlin2JvmCompilerArguments"> - <option name="jvmTarget" value="11" /> + <option name="jvmTarget" value="1.8" /> </component> </project> \ No newline at end of file diff --git a/nexus/build.gradle b/nexus/build.gradle @@ -46,7 +46,7 @@ application { } dependencies { - implementation project(":sandbox") + implementation project(":util") compile "io.ktor:ktor-client-apache:1.2.4" } diff --git a/nexus/src/main/kotlin/Helpers.kt b/nexus/src/main/kotlin/Helpers.kt @@ -3,12 +3,11 @@ package tech.libeufin.nexus import io.ktor.client.HttpClient import io.ktor.client.request.post import io.ktor.http.HttpStatusCode -import tech.libeufin.sandbox.CryptoUtil -import tech.libeufin.sandbox.XMLUtil -import tech.libeufin.sandbox.LOGGER -import tech.libeufin.sandbox.toByteArray -import tech.libeufin.schema.ebics_h004.EbicsRequest -import tech.libeufin.schema.ebics_s001.UserSignatureData +import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.XMLUtil +import tech.libeufin.util.schema.ebics_h004.EbicsRequest +import tech.libeufin.util.schema.ebics_s001.UserSignatureData +import tech.libeufin.util.toByteArray import java.math.BigInteger import java.security.PrivateKey import java.security.SecureRandom diff --git a/nexus/src/main/kotlin/Main.kt b/nexus/src/main/kotlin/Main.kt @@ -25,7 +25,6 @@ import io.ktor.application.ApplicationCallPipeline import io.ktor.application.call import io.ktor.application.install import io.ktor.client.* -import io.ktor.client.request.post import io.ktor.features.ContentNegotiation import io.ktor.features.StatusPages import io.ktor.gson.gson @@ -38,30 +37,23 @@ import io.ktor.response.respondText import io.ktor.routing.* import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty -import org.apache.xml.security.binding.xmldsig.RSAKeyValueType -import org.apache.xml.security.binding.xmldsig.SignatureType import org.jetbrains.exposed.sql.transactions.transaction import org.joda.time.DateTime +import org.slf4j.Logger import org.slf4j.LoggerFactory -import tech.libeufin.sandbox.* -import tech.libeufin.schema.ebics_h004.* +import tech.libeufin.util.schema.ebics_h004.* +import tech.libeufin.util.* import java.text.DateFormat import javax.sql.rowset.serial.SerialBlob -import javax.xml.bind.JAXBElement -import tech.libeufin.schema.ebics_s001.SignatureTypes -import tech.libeufin.schema.ebics_s001.UserSignatureData +import tech.libeufin.util.toHexString +import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.EbicsOrderUtil +import tech.libeufin.util.XMLUtil import java.math.BigInteger -import java.security.PrivateKey -import java.security.SecureRandom -import java.security.interfaces.RSAPrivateCrtKey import java.text.SimpleDateFormat import java.util.* import java.util.zip.DeflaterInputStream import javax.crypto.EncryptedPrivateKeyInfo -import javax.xml.datatype.DatatypeFactory -import javax.xml.datatype.XMLGregorianCalendar -import java.security.interfaces.RSAPublicKey -import java.time.LocalDate fun testData() { @@ -94,7 +86,7 @@ data class BadSignature(val statusCode: HttpStatusCode) : Exception("Signature v data class BadBackup(val statusCode: HttpStatusCode) : Exception("Could not restore backed up keys") data class BankInvalidResponse(val statusCode: HttpStatusCode) : Exception("Missing data from bank response") - +val LOGGER: Logger = LoggerFactory.getLogger("tech.libeufin.nexus") fun main() { dbCreateTables() diff --git a/nexus/src/main/resources/logback.xml b/nexus/src/main/resources/logback.xml @@ -1,3 +1,4 @@ + <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> @@ -21,7 +22,7 @@ </encoder> </appender> - <logger name="tech.libeufin.sandbox" level="DEBUG" additivity="false"> + <logger name="tech.libeufin.util" level="TRACE" additivity="false"> <appender-ref ref="STDOUT" /> <appender-ref ref="SANDBOX-FILE" /> </logger> diff --git a/nexus/src/test/kotlin/LetterFormatTest.kt b/nexus/src/test/kotlin/LetterFormatTest.kt @@ -1,7 +1,7 @@ package tech.libeufin.nexus import org.junit.Test -import tech.libeufin.sandbox.toHexString +import tech.libeufin.util.toHexString class LetterFormatTest { diff --git a/nexus/src/test/kotlin/SignatureDataTest.kt b/nexus/src/test/kotlin/SignatureDataTest.kt @@ -1,10 +1,9 @@ package tech.libeufin.nexus -import okio.internal.commonAsUtf8ToByteArray -import tech.libeufin.sandbox.XMLUtil +import tech.libeufin.util.XMLUtil import org.apache.xml.security.binding.xmldsig.SignatureType import org.junit.Test -import tech.libeufin.sandbox.CryptoUtil +import tech.libeufin.util.CryptoUtil import tech.libeufin.schema.ebics_h004.EbicsRequest import tech.libeufin.schema.ebics_h004.EbicsTypes import java.math.BigInteger diff --git a/sandbox/build.gradle b/sandbox/build.gradle @@ -46,6 +46,8 @@ dependencies { testImplementation group: 'junit', name: 'junit', version: '4.12' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.3.50' testImplementation 'org.jetbrains.kotlin:kotlin-test:1.3.50' + + implementation project(":util") } diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt @@ -1,270 +0,0 @@ -/* - * 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 - -import org.bouncycastle.jce.provider.BouncyCastleProvider -import java.io.ByteArrayOutputStream -import java.math.BigInteger -import java.security.* -import java.security.interfaces.RSAPrivateCrtKey -import java.security.interfaces.RSAPublicKey -import java.security.spec.* -import javax.crypto.* -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.PBEKeySpec -import javax.crypto.spec.PBEParameterSpec -import javax.crypto.spec.SecretKeySpec - -/** - * Helpers for dealing with cryptographic operations in EBICS / LibEuFin. - */ -object CryptoUtil { - - /** - * RSA key pair. - */ - data class RsaCrtKeyPair(val private: RSAPrivateCrtKey, val public: RSAPublicKey) - - class EncryptionResult( - val encryptedTransactionKey: ByteArray, - val pubKeyDigest: ByteArray, - val encryptedData: ByteArray, - - /** - * This key needs to be reused between different upload phases. - */ - val plainTransactionKey: SecretKey? = null - ) - - private val bouncyCastleProvider = BouncyCastleProvider() - - /** - * 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) - if (priv !is RSAPrivateCrtKey) - 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) - if (pub !is RSAPublicKey) - 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) - if (pub !is RSAPublicKey) - 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) - val pair = gen.genKeyPair() - val priv = pair.private - val pub = pair.public - if (priv !is RSAPrivateCrtKey) - throw Exception("key generation failed") - if (pub !is RSAPublicKey) - 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 - } - - /** - * Hash an RSA public key according to the EBICS standard (EBICS 2.5: 4.4.1.2.3). - */ - fun getEbicsPublicKeyHash(publicKey: RSAPublicKey): ByteArray { - val keyBytes = ByteArrayOutputStream() - keyBytes.writeBytes(publicKey.publicExponent.toByteArray().toHexString().toByteArray()) - keyBytes.write(' '.toInt()) - keyBytes.writeBytes(publicKey.modulus.toByteArray().toHexString().toByteArray()) - val digest = MessageDigest.getInstance("SHA-256") - return digest.digest(keyBytes.toByteArray()) - } - - 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) - val encryptedData = symmetricCipher.doFinal(data) - val asymmetricCipher = Cipher.getInstance("RSA/None/PKCS1Padding", bouncyCastleProvider) - asymmetricCipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey) - val encryptedTransactionKey = asymmetricCipher.doFinal(transactionKey.encoded) - val pubKeyDigest = getEbicsPublicKeyHash(encryptionPublicKey) - return EncryptionResult(encryptedTransactionKey, pubKeyDigest, encryptedData, transactionKey) - } - - fun decryptEbicsE002(enc: EncryptionResult, privateKey: RSAPrivateCrtKey): ByteArray { - return decryptEbicsE002(enc.encryptedTransactionKey, enc.encryptedData, privateKey) - } - - fun decryptEbicsE002(encryptedTransactionKey: ByteArray, encryptedData: ByteArray, privateKey: RSAPrivateCrtKey): ByteArray { - val asymmetricCipher = Cipher.getInstance("RSA/None/PKCS1Padding", bouncyCastleProvider) - asymmetricCipher.init(Cipher.DECRYPT_MODE, privateKey) - val transactionKeyBytes = asymmetricCipher.doFinal(encryptedTransactionKey) - val secretKeySpec = SecretKeySpec(transactionKeyBytes, "AES") - 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 - } - - /** - * Signing algorithm corresponding to the EBICS A006 signing process. - * - * Note that while [data] can be arbitrary-length data, in EBICS, the order - * data is *always* hashed *before* passing it to the signing algorithm, which again - * uses a hash internally. - */ - fun signEbicsA006(data: ByteArray, privateKey: RSAPrivateCrtKey): ByteArray { - val signature = Signature.getInstance("SHA256withRSA/PSS", bouncyCastleProvider) - signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1)) - signature.initSign(privateKey) - signature.update(data) - return signature.sign() - } - - fun verifyEbicsA006(sig: ByteArray, data: ByteArray, publicKey: RSAPublicKey): Boolean { - val signature = Signature.getInstance("SHA256withRSA/PSS", bouncyCastleProvider) - signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1)) - signature.initVerify(publicKey) - signature.update(data) - return signature.verify(sig) - } - - fun digestEbicsOrderA006(orderData: ByteArray): ByteArray { - val digest = MessageDigest.getInstance("SHA-256") - for (b in orderData) { - when (b) { - '\r'.toByte(), '\n'.toByte(), (26).toByte() -> Unit - else -> digest.update(b) - } - } - return digest.digest() - } - - - fun decryptKey(data: EncryptedPrivateKeyInfo, passphrase: String): RSAPrivateCrtKey { - - /* make key out of passphrase */ - val pbeKeySpec = PBEKeySpec(passphrase.toCharArray()) - val keyFactory = SecretKeyFactory.getInstance(data.algName) - val secretKey = keyFactory.generateSecret(pbeKeySpec) - - /* Make a cipher */ - val cipher = Cipher.getInstance(data.algName) - cipher.init( - Cipher.DECRYPT_MODE, - secretKey, - data.algParameters // has hash count and salt - ) - - /* Ready to decrypt */ - val decryptedKeySpec: PKCS8EncodedKeySpec = data.getKeySpec(cipher) - val priv = KeyFactory.getInstance("RSA").generatePrivate(decryptedKeySpec) - if (priv !is RSAPrivateCrtKey) - throw Exception("wrong encoding") - return priv - } - - fun encryptKey(data: ByteArray, passphrase: String): ByteArray { - - /* Cipher parameters: salt and hash count */ - val hashIterations = 30 - val salt = ByteArray(8) - SecureRandom().nextBytes(salt) - val pbeParameterSpec = PBEParameterSpec(salt, hashIterations) - - /* *Other* cipher parameters: symmetric key (from password) */ - val pbeAlgorithm = "PBEWithSHA1AndDESede" - val pbeKeySpec = PBEKeySpec(passphrase.toCharArray()) - val keyFactory = SecretKeyFactory.getInstance(pbeAlgorithm) - val secretKey = keyFactory.generateSecret(pbeKeySpec) - - /* Make a cipher */ - val cipher = Cipher.getInstance(pbeAlgorithm) - cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec) - - /* ready to encrypt now */ - val cipherText = cipher.doFinal(data) - - /* Must now bundle a PKCS#8-compatible object, that contains - * algorithm, salt and hash count information */ - - val bundleAlgorithmParams = AlgorithmParameters.getInstance(pbeAlgorithm) - bundleAlgorithmParams.init(pbeParameterSpec) - - val bundle = EncryptedPrivateKeyInfo(bundleAlgorithmParams, cipherText) - - return bundle.encoded - } -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt @@ -95,10 +95,6 @@ enum class KeyState { RELEASED } -fun Blob.toByteArray(): ByteArray { - return this.binaryStream.readAllBytes() -} - /** * Any number can become a Amount IF it does NOT need to be rounded to comply to the scale == 2. */ diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt @@ -1,89 +0,0 @@ -/* - * 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 - -import java.lang.IllegalArgumentException -import java.security.SecureRandom -import java.util.zip.DeflaterInputStream -import java.util.zip.InflaterInputStream - -/** - * Helpers for dealing with order compression, encryption, decryption, chunking and re-assembly. - */ -object EbicsOrderUtil { - inline fun <reified T> decodeOrderDataXml(encodedOrderData: ByteArray): T { - return InflaterInputStream(encodedOrderData.inputStream()).use { - val bytes = it.readAllBytes() - XMLUtil.convertStringToJaxb<T>(bytes.toString(Charsets.UTF_8)).value - } - } - - inline fun <reified T> encodeOrderDataXml(obj: T): ByteArray { - val bytes = XMLUtil.convertJaxbToString(obj).toByteArray() - return DeflaterInputStream(bytes.inputStream()).use { - it.readAllBytes() - } - } - - fun generateTransactionId(): String { - val rng = SecureRandom() - val res = ByteArray(16) - rng.nextBytes(res) - return res.toHexString().toUpperCase() - } - - /** - * Calculate the resulting size of base64-encoding data of the given length, - * including padding. - */ - fun calculateBase64EncodedLength(dataLength: Int): Int { - val blocks = (dataLength + 3 - 1) / 3 - return blocks * 4 - } - - fun checkOrderIDOverflow(n: Int): Boolean { - if (n <= 0) - throw IllegalArgumentException() - val base = 10 + 26 - return n >= base * base - } - - private fun getDigitChar(x: Int): Char { - if (x < 10) { - return '0' + x - } - return 'A' + (x - 10) - } - - fun computeOrderIDFromNumber(n: Int): String { - if (n <= 0) - throw IllegalArgumentException() - if (checkOrderIDOverflow(n)) - throw IllegalArgumentException() - var ni = n - val base = 10 + 26 - val x1 = ni % base - ni = ni / base - val x2 = ni % base - val c1 = getDigitChar(x1) - val c2 = getDigitChar(x2) - return String(charArrayOf('O', 'R', c2, c1)) - } -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -27,16 +27,20 @@ import io.ktor.request.receiveText import io.ktor.response.respond import io.ktor.response.respondText import org.apache.xml.security.binding.xmldsig.RSAKeyValueType -import org.apache.xml.security.c14n.Canonicalizer import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.stringParam import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.upperCase import org.w3c.dom.Document -import tech.libeufin.schema.ebics_h004.* -import tech.libeufin.schema.ebics_hev.HEVResponse -import tech.libeufin.schema.ebics_hev.SystemReturnCodeType -import tech.libeufin.schema.ebics_s001.SignatureTypes -import tech.libeufin.schema.ebics_s001.UserSignatureData +import tech.libeufin.util.schema.ebics_h004.* +import tech.libeufin.util.schema.ebics_hev.HEVResponse +import tech.libeufin.util.schema.ebics_hev.SystemReturnCodeType +import tech.libeufin.util.schema.ebics_s001.SignatureTypes +import tech.libeufin.util.schema.ebics_s001.UserSignatureData +import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.EbicsOrderUtil +import tech.libeufin.util.XMLUtil +import tech.libeufin.util.* import java.security.interfaces.RSAPrivateCrtKey import java.util.* import java.util.zip.DeflaterInputStream @@ -59,7 +63,6 @@ private class EbicsInvalidOrderType : EbicsRequestError( "091005" ) - private suspend fun ApplicationCall.respondEbicsKeyManagement( errorText: String, errorCode: String, @@ -108,6 +111,56 @@ private suspend fun ApplicationCall.respondEbicsKeyManagement( respondText(text, ContentType.Application.Xml, HttpStatusCode.OK) } +private fun ApplicationCall.handleEbicsC52(header: EbicsRequest.Header) { + + val userId = header.static.userID!! + val od = header.static.orderDetails ?: throw Exception("Need 'OrderDetails'") + val op = od.orderParams ?: throw Exception("Need 'StandardOrderParams'") + + /** + * (StandardOrderParams (DateRange (Start, End))) + */ + + val subscriber = transaction { + EbicsSubscriberEntity.find { + stringParam(userId) eq EbicsSubscribersTable.userId // will have to match partner and system IDs + } + }.firstOrNull() ?: throw Exception("Unknown subscriber") + + val history = extractHistoryForEach( + subscriber.bankCustomer.id.value, + (op as EbicsRequest.StandardOrderParams).dateRange?.start.toString(), + op.dateRange?.end.toString()) { println(it) } + + val ret = constructXml(indent = true) { + namespace("foo", "bar") + root("foo:BkToCstmrAcctRpt") { + element("GrpHdr") { + + element("MsgId") { + text("id under group header") + } + element("CreDtTm") { + text("now") + } + } + element("Rpt") { + element("Id") { + text("id under report") + } + element("Acct") { + text("account identifier") + } + + } + + + } + } + + // val str = XMLUtil.convertJaxbToString(ret) + // return str.toByteArray() +} private suspend fun ApplicationCall.handleEbicsHia(header: EbicsUnsecuredRequest.Header, orderData: ByteArray) { val plainOrderData = InflaterInputStream(orderData.inputStream()).use { @@ -444,17 +497,6 @@ fun signEbicsResponseX002(ebicsResponse: EbicsResponse, privateKey: RSAPrivateCr return signedDoc } - -class EbicsTransactionDetails( - -) - - -fun queryEbicsTransactionDetails(ebicsRequest: EbicsRequest): EbicsTransactionDetails { - throw NotImplementedError() -} - - suspend fun ApplicationCall.ebicsweb() { val requestDocument = receiveEbicsXml() @@ -471,7 +513,6 @@ suspend fun ApplicationCall.ebicsweb() { when (header.static.orderDetails.orderType) { "INI" -> handleEbicsIni(header, orderData) "HIA" -> handleEbicsHia(header, orderData) - // "C52" -> handleEbicsC52(header, orderData) else -> throw EbicsInvalidXmlError() } } @@ -562,6 +603,7 @@ suspend fun ApplicationCall.ebicsweb() { val response = when (orderType) { "HTD" -> handleEbicsHtd() "HKD" -> handleEbicsHkd() + // "C52" -> handleEbicsC52(requestObject.header) else -> throw EbicsInvalidXmlError() } diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -38,16 +38,14 @@ 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.sql.GreaterEqOp -import org.jetbrains.exposed.sql.LessEqOp import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.dateTimeParam import org.jetbrains.exposed.sql.transactions.transaction import org.joda.time.DateTime import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level import org.w3c.dom.Document +import tech.libeufin.util.CryptoUtil import java.lang.ArithmeticException import java.math.BigDecimal import java.security.interfaces.RSAPublicKey @@ -163,11 +161,8 @@ fun sampleData() { date = DateTime.now() localCustomer = customerEntity } - } } - - } fun extractHistoryForEach(id: Int, start: String?, end: String?, builder: (BankTransactionEntity) -> Any) { diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/XMLUtil.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/XMLUtil.kt @@ -1,397 +0,0 @@ -/* - * 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 - -import com.sun.org.apache.xerces.internal.dom.DOMInputImpl -import org.w3c.dom.Document -import org.w3c.dom.Node -import org.w3c.dom.NodeList -import org.w3c.dom.ls.LSInput -import org.w3c.dom.ls.LSResourceResolver -import org.xml.sax.ErrorHandler -import org.xml.sax.InputSource -import org.xml.sax.SAXException -import org.xml.sax.SAXParseException -import java.io.* -import java.security.PrivateKey -import java.security.PublicKey -import javax.xml.XMLConstants -import javax.xml.bind.JAXBContext -import javax.xml.bind.JAXBElement -import javax.xml.bind.Marshaller -import javax.xml.crypto.* -import javax.xml.crypto.dom.DOMURIReference -import javax.xml.crypto.dsig.* -import javax.xml.crypto.dsig.dom.DOMSignContext -import javax.xml.crypto.dsig.dom.DOMValidateContext -import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec -import javax.xml.crypto.dsig.spec.TransformParameterSpec -import javax.xml.namespace.NamespaceContext -import javax.xml.parsers.DocumentBuilderFactory -import javax.xml.transform.OutputKeys -import javax.xml.transform.Source -import javax.xml.transform.TransformerFactory -import javax.xml.transform.dom.DOMSource -import javax.xml.transform.stream.StreamResult -import javax.xml.transform.stream.StreamSource -import javax.xml.validation.SchemaFactory -import javax.xml.validation.Validator -import javax.xml.xpath.XPath -import javax.xml.xpath.XPathConstants -import javax.xml.xpath.XPathFactory - -/** - * Helpers for dealing with XML in EBICS. - */ -class XMLUtil private constructor() { - /** - * This URI dereferencer allows handling the resource reference used for - * XML signatures in EBICS. - */ - private class EbicsSigUriDereferencer : URIDereferencer { - override fun dereference(myRef: URIReference?, myCtx: XMLCryptoContext?): Data { - val ebicsXpathExpr = "//*[@authenticate='true']" - if (myRef !is DOMURIReference) - throw Exception("invalid type") - if (myRef.uri != "#xpointer($ebicsXpathExpr)") - throw Exception("invalid EBICS XML signature URI: '${myRef.uri}'") - val xp: XPath = XPathFactory.newInstance().newXPath() - val nodeSet = xp.compile("//*[@authenticate='true']/descendant-or-self::node()").evaluate(myRef.here - .ownerDocument, XPathConstants - .NODESET) - if (nodeSet !is NodeList) - throw Exception("invalid type") - if (nodeSet.length <= 0) { - throw Exception("no nodes to sign") - } - val nodeList = ArrayList<Node>() - for (i in 0 until nodeSet.length) { - val node = nodeSet.item(i) - nodeList.add(node) - } - return NodeSetData { nodeList.iterator() } - } - } - - /** - * Validator for EBICS messages. - */ - private val validator = try { - - } catch (e: SAXException) { - e.printStackTrace() - throw e - } - - companion object { - - private var cachedEbicsValidator: Validator? = null - - private fun getEbicsValidator(): Validator { - val currentValidator = cachedEbicsValidator - if (currentValidator != null) - return currentValidator - val classLoader = ClassLoader.getSystemClassLoader() - val sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI) - sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file") - sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "") - sf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) - sf.errorHandler = object : ErrorHandler { - override fun warning(p0: SAXParseException?) { - println("Warning: $p0") - } - - override fun error(p0: SAXParseException?) { - println("Error: $p0") - } - - override fun fatalError(p0: SAXParseException?) { - println("Fatal error: $p0") - } - } - sf.resourceResolver = object : LSResourceResolver { - override fun resolveResource( - type: String?, - namespaceURI: String?, - publicId: String?, - systemId: String?, - baseUri: String? - ): LSInput? { - if (type != "http://www.w3.org/2001/XMLSchema") { - return null - } - val res = classLoader.getResourceAsStream("xsd/$systemId") ?: return null - return DOMInputImpl(publicId, systemId, baseUri, res, "UTF-8") - } - } - val schemaInputs: Array<Source> = listOf("xsd/ebics_H004.xsd", "xsd/ebics_hev.xsd").map { - val resUrl = classLoader.getResource(it) ?: throw FileNotFoundException("Schema file $it not found.") - StreamSource(File(resUrl.toURI())) - }.toTypedArray() - val bundle = sf.newSchema(schemaInputs) - val newValidator = bundle.newValidator() - cachedEbicsValidator = newValidator - return newValidator - } - - /** - * - * @param xmlDoc the XML document to validate - * @return true when validation passes, false otherwise - */ - fun validate(xmlDoc: StreamSource): Boolean { - try { - getEbicsValidator().validate(xmlDoc) - } catch (e: Exception) { - LOGGER.warn("Validation failed: ${e}") - return false - } - return true; - } - - /** - * Validates the DOM against the Schema(s) of this object. - * @param domDocument DOM to validate - * @return true/false if the document is valid/invalid - */ - fun validateFromDom(domDocument: Document): Boolean { - try { - getEbicsValidator().validate(DOMSource(domDocument)) - } catch (e: SAXException) { - e.printStackTrace() - return false - } - return true - } - - /** - * Craft object to be passed to the XML validator. - * @param xmlString XML body, as read from the POST body. - * @return InputStream object, as wanted by the validator. - */ - fun validateFromString(xmlString: String): Boolean { - val xmlInputStream: InputStream = ByteArrayInputStream(xmlString.toByteArray()) - val xmlSource = StreamSource(xmlInputStream) - return validate(xmlSource) - } - - - inline fun <reified T> convertJaxbToString(obj: T): String { - val sw = StringWriter() - val jc = JAXBContext.newInstance(T::class.java) - val m = jc.createMarshaller() - m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true) - m.marshal(obj, sw) - return sw.toString() - } - - inline fun <reified T> convertJaxbToDocument(obj: T): Document { - val dbf: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() - dbf.isNamespaceAware = true - val doc = dbf.newDocumentBuilder().newDocument() - val jc = JAXBContext.newInstance(T::class.java) - val m = jc.createMarshaller() - m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true) - m.marshal(obj, doc) - return doc - } - - /** - * Convert a XML string to the JAXB representation. - * - * @param documentString the string to convert into JAXB. - * @return the JAXB object reflecting the original XML document. - */ - inline fun <reified T> convertStringToJaxb(documentString: String): JAXBElement<T> { - val jc = JAXBContext.newInstance(T::class.java) - val u = jc.createUnmarshaller() - return u.unmarshal( /* Marshalling the object into the document. */ - StreamSource(StringReader(documentString)), - T::class.java - ) - } - - /** - * Extract String from DOM. - * - * @param document the DOM to extract the string from. - * @return the final String, or null if errors occur. - */ - fun convertDomToString(document: Document): String { - /* Make Transformer. */ - val tf = TransformerFactory.newInstance() - val t = tf.newTransformer() - - //t.setOutputProperty(OutputKeys.INDENT, "yes") - - /* Make string writer. */ - val sw = StringWriter() - - /* Extract string. */ - t.transform(DOMSource(document), StreamResult(sw)) - return sw.toString() - } - - /** - * Convert a node to a string without the XML declaration or - * indentation. - */ - fun convertNodeToString(node: Node): String { - /* Make Transformer. */ - val tf = TransformerFactory.newInstance() - val t = tf.newTransformer() - - t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - - /* Make string writer. */ - val sw = StringWriter() - - /* Extract string. */ - t.transform(DOMSource(node), StreamResult(sw)) - return sw.toString() - } - - /** - * Convert a DOM document to the JAXB representation. - * - * @param finalType class type of the output - * @param document the document to convert into JAXB. - * @return the JAXB object reflecting the original XML document. - */ - fun <T> convertDomToJaxb(finalType: Class<T>, document: Document): JAXBElement<T> { - - val jc = JAXBContext.newInstance(finalType) - - /* Marshalling the object into the document. */ - val m = jc.createUnmarshaller() - return m.unmarshal(document, finalType) // document "went" into Jaxb - } - - /** - * Parse string into XML DOM. - * @param xmlString the string to parse. - * @return the DOM representing @a xmlString - */ - fun parseStringIntoDom(xmlString: String): Document { - val factory = DocumentBuilderFactory.newInstance().apply { - isNamespaceAware = true - } - val xmlInputStream = ByteArrayInputStream(xmlString.toByteArray()) - val builder = factory.newDocumentBuilder() - return builder.parse(InputSource(xmlInputStream)) - } - - - /** - * Sign an EBICS document with the authentication and identity signature. - */ - fun signEbicsDocument(doc: Document, signingPriv: PrivateKey): Unit { - val xpath = XPathFactory.newInstance().newXPath() - xpath.namespaceContext = object : NamespaceContext { - override fun getNamespaceURI(p0: String?): String { - return when (p0) { - "ebics" -> "urn:org:ebics:H004" - else -> throw IllegalArgumentException() - } - } - - override fun getPrefix(p0: String?): String { - throw UnsupportedOperationException() - } - - override fun getPrefixes(p0: String?): MutableIterator<String> { - throw UnsupportedOperationException() - } - } - val authSigNode = xpath.compile("/*[1]/ebics:AuthSignature").evaluate(doc, XPathConstants.NODE) - if (authSigNode !is Node) - throw java.lang.Exception("no AuthSignature") - val fac = XMLSignatureFactory.getInstance("DOM") - val c14n = fac.newTransform(CanonicalizationMethod.INCLUSIVE, null as TransformParameterSpec?) - val ref: Reference = - fac.newReference( - "#xpointer(//*[@authenticate='true'])", - fac.newDigestMethod(DigestMethod.SHA256, null), - listOf(c14n), - null, - null - ) - val canon: CanonicalizationMethod = - fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null as C14NMethodParameterSpec?) - val signatureMethod = fac.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null) - val si: SignedInfo = fac.newSignedInfo(canon, signatureMethod, listOf(ref)) - val sig: XMLSignature = fac.newXMLSignature(si, null) - val dsc = DOMSignContext(signingPriv, authSigNode) - dsc.defaultNamespacePrefix = "ds" - dsc.uriDereferencer = EbicsSigUriDereferencer() - - dsc.setProperty("javax.xml.crypto.dsig.cacheReference", true) - - sig.sign(dsc) - - println("canon data: " + sig.signedInfo.canonicalizedData.readAllBytes().toString(Charsets.UTF_8)) - - val innerSig = authSigNode.firstChild - while (innerSig.hasChildNodes()) { - authSigNode.appendChild(innerSig.firstChild) - } - authSigNode.removeChild(innerSig) - } - - fun verifyEbicsDocument(doc: Document, signingPub: PublicKey): Boolean { - val xpath = XPathFactory.newInstance().newXPath() - xpath.namespaceContext = object : NamespaceContext { - override fun getNamespaceURI(p0: String?): String { - return when (p0) { - "ebics" -> "urn:org:ebics:H004" - else -> throw IllegalArgumentException() - } - } - - override fun getPrefix(p0: String?): String { - throw UnsupportedOperationException() - } - - override fun getPrefixes(p0: String?): MutableIterator<String> { - throw UnsupportedOperationException() - } - } - val doc2: Document = doc.cloneNode(true) as Document - val authSigNode = xpath.compile("/*[1]/ebics:AuthSignature").evaluate(doc2, XPathConstants.NODE) - if (authSigNode !is Node) - throw java.lang.Exception("no AuthSignature") - val sigEl = doc2.createElementNS("http://www.w3.org/2000/09/xmldsig#", "ds:Signature") - authSigNode.parentNode.insertBefore(sigEl, authSigNode) - while (authSigNode.hasChildNodes()) { - sigEl.appendChild(authSigNode.firstChild) - } - authSigNode.parentNode.removeChild(authSigNode) - val fac = XMLSignatureFactory.getInstance("DOM") - val dvc = DOMValidateContext(signingPub, sigEl) - dvc.setProperty("javax.xml.crypto.dsig.cacheReference", true) - dvc.uriDereferencer = EbicsSigUriDereferencer() - val sig = fac.unmarshalXMLSignature(dvc) - // FIXME: check that parameters are okay!s - val valResult = sig.validate(dvc) - sig.signedInfo.references[0].validate(dvc) - return valResult - } - } -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/XmlCombinators.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/XmlCombinators.kt @@ -1,92 +0,0 @@ -package tech.libeufin.sandbox - -import com.sun.xml.txw2.output.IndentingXMLStreamWriter -import org.jetbrains.exposed.sql.Op -import java.io.StringWriter -import java.util.* -import javax.xml.stream.XMLOutputFactory -import javax.xml.stream.XMLStreamWriter - -class XmlElementBuilder(val w: XMLStreamWriter) { - - fun element(path: MutableList<String>, f: XmlElementBuilder.() -> Unit = {}) { - - if (path.isEmpty()) { - f(this) - return - } - - w.writeStartElement(path.removeAt(0)) - this.element(path, f) - w.writeEndElement() - - } - - fun element(path: String, f: XmlElementBuilder.() -> Unit = {}) { - - val splitPath = path.trim('/').split("/").toMutableList() - this.element(splitPath, f) - } - - fun attribute(name: String, value: String) { - w.writeAttribute(name, value) - } - - fun text(content: String) { - w.writeCharacters(content) - } -} - -class XmlDocumentBuilder { - - private var maybeWriter: XMLStreamWriter? = null - - internal var writer: XMLStreamWriter - get() { - val w = maybeWriter - return w ?: throw AssertionError("no writer set") - } - set(w: XMLStreamWriter) { - maybeWriter = w - } - - - fun namespace(prefix: String, uri: String) { - writer.setPrefix(prefix, uri) - } - - fun defaultNamespace(uri: String) { - writer.setDefaultNamespace(uri) - } - - fun root(name: String, f: XmlElementBuilder.() -> Unit) { - val elementBuilder = XmlElementBuilder(writer) - writer.writeStartElement(name) - f(elementBuilder) - writer.writeEndElement() - } -} - -fun constructXml(indent: Boolean = false, f: XmlDocumentBuilder.() -> Unit): String { - val b = XmlDocumentBuilder() - val factory = XMLOutputFactory.newFactory() - factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true) - val stream = StringWriter() - var writer = factory.createXMLStreamWriter(stream) - if (indent) { - writer = IndentingXMLStreamWriter(writer) - } - b.writer = writer - writer.writeStartDocument() - f(b) - writer.writeEndDocument() - return stream.buffer.toString() -} - -class XmlDocumentDestructor { -} - -fun <T>destructXml(input: String, f: XmlDocumentDestructor.() -> T): T { - val d = XmlDocumentDestructor() - return f(d) -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/hex.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/hex.kt @@ -1,7 +0,0 @@ -package tech.libeufin.sandbox - -fun ByteArray.toHexString() : String { - return this.joinToString("") { - java.lang.String.format("%02x", it) - } -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsKeyManagementResponse.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsKeyManagementResponse.kt @@ -1,102 +0,0 @@ -package tech.libeufin.schema.ebics_h004 - -import javax.xml.bind.annotation.* -import javax.xml.bind.annotation.adapters.CollapsedStringAdapter -import javax.xml.bind.annotation.adapters.NormalizedStringAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter - - -@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: MutableHeaderType - - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["orderID", "returnCode", "reportText"]) - class MutableHeaderType { - @get:XmlElement(name = "OrderID") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - var orderID: String? = null - - @get:XmlElement(name = "ReturnCode", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - lateinit var returnCode: String - - @get:XmlElement(name = "ReportText", required = true) - @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) - @get:XmlSchemaType(name = "normalizedString") - lateinit var reportText: String - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "") - class EmptyStaticHeader - - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["dataTransfer", "returnCode", "timestampBankParameter"]) - class Body { - @get:XmlElement(name = "DataTransfer") - var dataTransfer: DataTransfer? = null - - @get:XmlElement(name = "ReturnCode", required = true) - lateinit var returnCode: ReturnCode - - @get:XmlElement(name = "TimestampBankParameter") - var timestampBankParameter: EbicsTypes.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: EbicsTypes.DataEncryptionInfo? = null - - @get:XmlElement(name = "OrderData", required = true) - lateinit var orderData: OrderData - } - - @XmlAccessorType(XmlAccessType.NONE) - class OrderData { - @get:XmlValue - lateinit var value: ByteArray - } -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsNpkdRequest.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsNpkdRequest.kt @@ -1,135 +0,0 @@ -package tech.libeufin.schema.ebics_h004 - -import org.apache.xml.security.binding.xmldsig.SignatureType -import javax.xml.bind.annotation.* -import javax.xml.bind.annotation.adapters.CollapsedStringAdapter -import javax.xml.bind.annotation.adapters.HexBinaryAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter -import javax.xml.datatype.XMLGregorianCalendar - - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType(name = "", propOrder = ["header", "authSignature", "body"]) -@XmlRootElement(name = "ebicsNoPubKeyDigestsRequest") -class EbicsNpkdRequest { - @get:XmlAttribute(name = "Version", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var version: String - - @get:XmlAttribute(name = "Revision") - var revision: Int? = null - - @get:XmlElement(name = "header", required = true) - lateinit var header: Header - - @get:XmlElement(name = "AuthSignature", required = true) - lateinit var authSignature: SignatureType - - @get:XmlElement(required = true) - lateinit var body: EmptyBody - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["static", "mutable"]) - class Header { - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - - @get:XmlElement(name = "static", required = true) - lateinit var static: StaticHeaderType - - @get:XmlElement(required = true) - lateinit var mutable: EmptyMutableHeader - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "StaticHeader", - propOrder = ["hostID", "nonce", "timestamp", "partnerID", "userID", "systemID", "product", "orderDetails", "securityMedium"] - ) - class StaticHeaderType { - @get:XmlElement(name = "HostID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var hostID: String - - @get:XmlElement(name = "Nonce", type = String::class) - @get:XmlJavaTypeAdapter(HexBinaryAdapter::class) - @get:XmlSchemaType(name = "hexBinary") - lateinit var nonce: ByteArray - - @get:XmlElement(name = "Timestamp") - @get:XmlSchemaType(name = "dateTime") - var timestamp: XMLGregorianCalendar? = null - - @get:XmlElement(name = "PartnerID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var partnerID: String - - @get:XmlElement(name = "UserID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var userID: String - - @get:XmlElement(name = "SystemID") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - var systemID: String? = null - - @get:XmlElement(name = "Product") - val product: EbicsTypes.Product? = null - - @get:XmlElement(name = "OrderDetails", required = true) - lateinit var orderDetails: OrderDetails - - @get:XmlElement(name = "SecurityMedium", required = true) - lateinit var securityMedium: String - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["orderType", "orderAttribute"]) - class OrderDetails { - @get:XmlElement(name = "OrderType", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var orderType: String - - @get:XmlElement(name = "OrderAttribute", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var orderAttribute: String - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "") - class EmptyMutableHeader - - @XmlAccessorType(XmlAccessType.NONE) - class EmptyBody - - companion object { - fun createRequest( - hostId: String, - partnerId: String, - userId: String, - aNonce: ByteArray, - date: XMLGregorianCalendar - ): EbicsNpkdRequest { - return EbicsNpkdRequest().apply { - version = "H004" - revision = 1 - header = EbicsNpkdRequest.Header().apply { - authenticate = true - mutable = EbicsNpkdRequest.EmptyMutableHeader() - static = EbicsNpkdRequest.StaticHeaderType().apply { - hostID = hostId - partnerID = partnerId - userID = userId - securityMedium = "0000" - orderDetails = EbicsNpkdRequest.OrderDetails() - orderDetails.orderType = "HPB" - orderDetails.orderAttribute = "DZHNN" - nonce = aNonce - timestamp = date - } - } - body = EbicsNpkdRequest.EmptyBody() - authSignature = SignatureType() - } - } - } -} -\ No newline at end of file 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,501 +0,0 @@ -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 -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter -import javax.xml.datatype.XMLGregorianCalendar - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType(name = "", propOrder = ["header", "authSignature", "body"]) -@XmlRootElement(name = "ebicsRequest") -class EbicsRequest { - @get:XmlAttribute(name = "Version", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var version: String - - @get:XmlAttribute(name = "Revision") - var revision: Int? = null - - @get:XmlElement(name = "header", required = true) - lateinit var header: Header - - @get:XmlElement(name = "AuthSignature", required = true) - lateinit var authSignature: SignatureType - - @get:XmlElement(name = "body") - lateinit var body: Body - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["static", "mutable"]) - class Header { - @get:XmlElement(name = "static", required = true) - lateinit var static: StaticHeaderType - - @get:XmlElement(required = true) - lateinit var mutable: MutableHeader - - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "", - propOrder = [ - "hostID", "nonce", "timestamp", "partnerID", "userID", "systemID", - "product", "orderDetails", "bankPubKeyDigests", "securityMedium", - "numSegments", "transactionID" - ] - ) - class StaticHeaderType { - @get:XmlElement(name = "HostID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var hostID: String - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "Nonce", type = String::class) - @get:XmlJavaTypeAdapter(HexBinaryAdapter::class) - @get:XmlSchemaType(name = "hexBinary") - var nonce: ByteArray? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "Timestamp") - @get:XmlSchemaType(name = "dateTime") - var timestamp: XMLGregorianCalendar? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "PartnerID") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - var partnerID: String? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "UserID") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - var userID: String? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "SystemID") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - var systemID: String? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "Product") - var product: EbicsTypes.Product? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "OrderDetails") - var orderDetails: OrderDetails? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "BankPubKeyDigests") - var bankPubKeyDigests: BankPubKeyDigests? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "SecurityMedium") - var securityMedium: String? = null - - /** - * Present only in the initialization phase. - */ - @get:XmlElement(name = "NumSegments") - var numSegments: BigInteger? = null - - /** - * Present only in the transaction / finalization phase. - */ - @get:XmlElement(name = "TransactionID") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - var transactionID: String? = null - } - - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["transactionPhase", "segmentNumber"]) - class MutableHeader { - @get:XmlElement(name = "TransactionPhase", required = true) - @get:XmlSchemaType(name = "token") - lateinit var transactionPhase: EbicsTypes.TransactionPhaseType - - /** - * Number of the currently transmitted segment, if this message - * contains order data. - */ - @get:XmlElement(name = "SegmentNumber") - var segmentNumber: EbicsTypes.SegmentNumber? = null - - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "", - propOrder = ["orderType", "orderID", "orderAttribute", "orderParams"] - ) - class OrderDetails { - @get:XmlElement(name = "OrderType", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var orderType: String - - /** - * Only present if this ebicsRequest is a upload order - * relating to an already existing order. - */ - @get:XmlElement(name = "OrderID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - var orderID: String? = null - - @get:XmlElement(name = "OrderAttribute", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var orderAttribute: String - - /** - * Present only in the initialization phase. - */ - @get:XmlElements( - XmlElement( - name = "StandardOrderParams", - type = StandardOrderParams::class - ) - ) - var orderParams: OrderParams? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(propOrder = ["preValidation", "dataTransfer", "transferReceipt"]) - class Body { - @get:XmlElement(name = "PreValidation") - var preValidation: PreValidation? = null - - @get:XmlElement(name = "DataTransfer") - var dataTransfer: DataTransfer? = null - - @get:XmlElement(name = "TransferReceipt") - var transferReceipt: TransferReceipt? = null - } - - /** - * FIXME: not implemented yet - */ - @XmlAccessorType(XmlAccessType.NONE) - class PreValidation { - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - } - - @XmlAccessorType(XmlAccessType.NONE) - class SignatureData { - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - - @get:XmlValue - var value: ByteArray? = null - } - - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(propOrder = ["dataEncryptionInfo", "signatureData", "orderData", "hostId"]) - class DataTransfer { - - @get:XmlElement(name = "DataEncryptionInfo") - var dataEncryptionInfo: EbicsTypes.DataEncryptionInfo? = null - - @get:XmlElement(name = "SignatureData") - var signatureData: SignatureData? = null - - @get:XmlElement(name = "OrderData") - var orderData: ByteArray? = null - - @get:XmlElement(name = "HostID") - var hostId: String? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["receiptCode"]) - class TransferReceipt { - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - - @get:XmlElement(name = "ReceiptCode") - var receiptCode: Int? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - abstract class OrderParams - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["dateRange"]) - class StandardOrderParams : OrderParams() { - @get:XmlElement(name = "DateRange") - var dateRange: DateRange? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["start", "end"]) - class DateRange { - @get:XmlElement(name = "Start") - lateinit var start: XMLGregorianCalendar - - @get:XmlElement(name = "END") - lateinit var end: XMLGregorianCalendar - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["authentication", "encryption"]) - class BankPubKeyDigests { - @get:XmlElement(name = "Authentication") - lateinit var authentication: EbicsTypes.PubKeyDigest - - @get:XmlElement(name = "Encryption") - lateinit var encryption: EbicsTypes.PubKeyDigest - } - - companion object { - - fun createForDownloadReceiptPhase( - transactionId: String, - hostId: String - - ): 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.RECEIPT - } - } - authSignature = SignatureType() - - body = Body().apply { - transferReceipt = TransferReceipt().apply { - authenticate = true - receiptCode = 0 // always true at this point. - } - } - } - - } - - /* Take a time range (useful for C52 and C53) */ - fun createForDownloadInitializationPhase( - userId: String, - partnerId: String, - hostId: String, - nonceArg: ByteArray, - date: XMLGregorianCalendar, - bankEncPub: RSAPublicKey, - bankAuthPub: RSAPublicKey, - aOrderType: String, - dateStart: XMLGregorianCalendar, - dateEnd: XMLGregorianCalendar - ): EbicsRequest { - - val tmp = this.createForDownloadInitializationPhase( - userId, - partnerId, - hostId, - nonceArg, - date, - bankEncPub, - bankAuthPub, - aOrderType - ) - - (tmp.header.static.orderDetails?.orderParams as StandardOrderParams).apply { - dateRange?.apply { - start = dateStart - end = dateEnd - } - } - - return tmp - } - - fun createForDownloadInitializationPhase( - userId: String, - partnerId: String, - hostId: String, - nonceArg: ByteArray, - date: XMLGregorianCalendar, - bankEncPub: RSAPublicKey, - bankAuthPub: RSAPublicKey, - aOrderType: String - - ): EbicsRequest { - - return EbicsRequest().apply { - version = "H004" - revision = 1 - authSignature = SignatureType() - body = Body() - header = Header().apply { - authenticate = true - static = EbicsRequest.StaticHeaderType().apply { - userID = userId - partnerID = partnerId - hostID = hostId - nonce = nonceArg - timestamp = date - partnerID = partnerId - orderDetails = EbicsRequest.OrderDetails().apply { - orderType = aOrderType - orderAttribute = "DZHNN" - 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" - } - mutable = EbicsRequest.MutableHeader().apply { - transactionPhase = EbicsTypes.TransactionPhaseType.INITIALISATION - } - } - } - } - } - - fun createForUploadInitializationPhase( - cryptoBundle: CryptoUtil.EncryptionResult, - hostId: String, - nonceArg: ByteArray, - partnerId: String, - userId: String, - date: XMLGregorianCalendar, - bankAuthPub: RSAPublicKey, - bankEncPub: RSAPublicKey, - segmentsNumber: BigInteger, - aOrderType: String - ): 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 = aOrderType - 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 - } - } - } - } - } -} -\ No newline at end of file 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 @@ -1,273 +0,0 @@ -package tech.libeufin.schema.ebics_h004 - -import org.apache.xml.security.binding.xmldsig.SignatureType -import tech.libeufin.sandbox.CryptoUtil -import java.math.BigInteger -import javax.xml.bind.annotation.* -import javax.xml.bind.annotation.adapters.CollapsedStringAdapter -import javax.xml.bind.annotation.adapters.HexBinaryAdapter -import javax.xml.bind.annotation.adapters.NormalizedStringAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter -import javax.xml.namespace.QName - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType(name = "", propOrder = ["header", "authSignature", "body"]) -@XmlRootElement(name = "ebicsResponse") -class EbicsResponse { - @get:XmlAttribute(name = "Version", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var version: String - - @get:XmlAttribute(name = "Revision") - var revision: Int? = null - - @get:XmlElement(required = true) - lateinit var header: Header - - @get:XmlElement(name = "AuthSignature", required = true) - lateinit var authSignature: SignatureType - - @get:XmlElement(required = true) - lateinit var body: Body - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["_static", "mutable"]) - class Header { - @get:XmlElement(name = "static", required = true) - lateinit var _static: StaticHeaderType - - @get:XmlElement(required = true) - lateinit var mutable: MutableHeaderType - - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["dataTransfer", "returnCode", "timestampBankParameter"]) - class Body { - @get:XmlElement(name = "DataTransfer") - var dataTransfer: DataTransferResponseType? = null - - @get:XmlElement(name = "ReturnCode", required = true) - lateinit var returnCode: ReturnCode - - @get:XmlElement(name = "TimestampBankParameter") - var timestampBankParameter: EbicsTypes.TimestampBankParameter? = null - } - - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "", - propOrder = ["transactionPhase", "segmentNumber", "orderID", "returnCode", "reportText"] - ) - class MutableHeaderType { - @get:XmlElement(name = "TransactionPhase", required = true) - @get:XmlSchemaType(name = "token") - lateinit var transactionPhase: EbicsTypes.TransactionPhaseType - - @get:XmlElement(name = "SegmentNumber") - var segmentNumber: EbicsTypes.SegmentNumber? = null - - @get:XmlElement(name = "OrderID") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - var orderID: String? = null - - @get:XmlElement(name = "ReturnCode", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - lateinit var returnCode: String - - @get:XmlElement(name = "ReportText", required = true) - @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) - @get:XmlSchemaType(name = "normalizedString") - lateinit var reportText: String - } - - @XmlAccessorType(XmlAccessType.NONE) - class OrderData { - @get:XmlValue - lateinit var value: String - } - - @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 = "DataTransferResponseType", propOrder = ["dataEncryptionInfo", "orderData"]) - class DataTransferResponseType { - @get:XmlElement(name = "DataEncryptionInfo") - var dataEncryptionInfo: EbicsTypes.DataEncryptionInfo? = null - - @get:XmlElement(name = "OrderData", required = true) - lateinit var orderData: OrderData - } - - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "ResponseStaticHeaderType", propOrder = ["transactionID", "numSegments"]) - class StaticHeaderType { - @get:XmlElement(name = "TransactionID") - var transactionID: String? = null - - @get:XmlElement(name = "NumSegments") - @get:XmlSchemaType(name = "positiveInteger") - var numSegments: BigInteger? = null - } - - companion object { - fun createForUploadInitializationPhase(transactionID: String, orderID: String): EbicsResponse { - return EbicsResponse().apply { - this.version = "H004" - this.revision = 1 - this.header = EbicsResponse.Header().apply { - this.authenticate = true - this._static = EbicsResponse.StaticHeaderType().apply { - this.transactionID = transactionID - } - this.mutable = EbicsResponse.MutableHeaderType().apply { - this.transactionPhase = EbicsTypes.TransactionPhaseType.INITIALISATION - this.orderID = orderID - this.reportText = "[EBICS_OK] OK" - this.returnCode = "000000" - } - } - this.authSignature = SignatureType() - this.body = EbicsResponse.Body().apply { - this.returnCode = EbicsResponse.ReturnCode().apply { - this.authenticate = true - this.value = "000000" - } - } - } - } - - - fun createForDownloadReceiptPhase(transactionID: String, positiveAck: Boolean): EbicsResponse { - return EbicsResponse().apply { - this.version = "H004" - this.revision = 1 - this.header = EbicsResponse.Header().apply { - this.authenticate = true - this._static = EbicsResponse.StaticHeaderType().apply { - this.transactionID = transactionID - } - this.mutable = EbicsResponse.MutableHeaderType().apply { - this.transactionPhase = EbicsTypes.TransactionPhaseType.RECEIPT - if (positiveAck) { - this.reportText = "[EBICS_DOWNLOAD_POSTPROCESS_DONE] Received positive receipt" - this.returnCode = "011000" - } else { - this.reportText = "[EBICS_DOWNLOAD_POSTPROCESS_SKIPPED] Received negative receipt" - this.returnCode = "011001" - } - } - } - this.authSignature = SignatureType() - this.body = EbicsResponse.Body().apply { - this.returnCode = EbicsResponse.ReturnCode().apply { - this.authenticate = true - this.value = "000000" - } - } - } - } - - - fun createForUploadTransferPhase( - transactionID: String, - segmentNumber: Int, - lastSegment: Boolean, - orderID: String - ): EbicsResponse { - return EbicsResponse().apply { - this.version = "H004" - this.revision = 1 - this.header = EbicsResponse.Header().apply { - this.authenticate = true - this._static = EbicsResponse.StaticHeaderType().apply { - this.transactionID = transactionID - } - this.mutable = EbicsResponse.MutableHeaderType().apply { - this.transactionPhase = EbicsTypes.TransactionPhaseType.TRANSFER - this.segmentNumber = EbicsTypes.SegmentNumber().apply { - this.value = BigInteger.valueOf(segmentNumber.toLong()) - if (lastSegment) { - this.lastSegment = true - } - } - this.orderID = orderID - this.reportText = "[EBICS_OK] OK" - this.returnCode = "000000" - } - } - this.authSignature = SignatureType() - this.body = EbicsResponse.Body().apply { - this.returnCode = EbicsResponse.ReturnCode().apply { - this.authenticate = true - this.value = "000000" - } - } - } - } - - fun createForDownloadInitializationPhase( - transactionID: String, - numSegments: Int, - segmentSize: Int, - enc: CryptoUtil.EncryptionResult, - encodedData: String - ): EbicsResponse { - return EbicsResponse().apply { - this.version = "H004" - this.revision = 1 - this.header = EbicsResponse.Header().apply { - this.authenticate = true - this._static = EbicsResponse.StaticHeaderType().apply { - this.transactionID = transactionID - this.numSegments = BigInteger.valueOf(numSegments.toLong()) - } - this.mutable = EbicsResponse.MutableHeaderType().apply { - this.transactionPhase = EbicsTypes.TransactionPhaseType.INITIALISATION - this.segmentNumber = EbicsTypes.SegmentNumber().apply { - this.lastSegment = (numSegments == 1) - this.value = BigInteger.valueOf(1) - } - this.reportText = "[EBICS_OK] OK" - this.returnCode = "000000" - } - } - this.authSignature = SignatureType() - this.body = EbicsResponse.Body().apply { - this.returnCode = EbicsResponse.ReturnCode().apply { - this.authenticate = true - this.value = "000000" - } - this.dataTransfer = EbicsResponse.DataTransferResponseType().apply { - this.dataEncryptionInfo = EbicsTypes.DataEncryptionInfo().apply { - this.authenticate = true - this.encryptionPubKeyDigest = EbicsTypes.PubKeyDigest().apply { - this.algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" - this.version = "E002" - this.value = enc.pubKeyDigest - } - this.transactionKey = enc.encryptedTransactionKey - } - this.orderData = EbicsResponse.OrderData().apply { - this.value = encodedData.substring(0, Math.min(segmentSize, encodedData.length)) - } - } - } - } - } - } -} 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 @@ -1,402 +0,0 @@ -/* - * 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.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 -import javax.xml.bind.annotation.adapters.NormalizedStringAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter -import javax.xml.datatype.XMLGregorianCalendar - - -/** - * EBICS type definitions that are shared between other requests / responses / order types. - */ -object EbicsTypes { - /** - * EBICS client product. Identifies the software that accesses the EBICS host. - */ - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "Product", propOrder = ["value"]) - class Product { - @get:XmlValue - @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) - lateinit var value: String - - @get:XmlAttribute(name = "Language", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var language: String - - @get:XmlAttribute(name = "InstituteID") - @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) - 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) - class DataEncryptionInfo { - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - - @get:XmlElement(name = "EncryptionPubKeyDigest", required = true) - lateinit var encryptionPubKeyDigest: PubKeyDigest - - @get:XmlElement(name = "TransactionKey", required = true) - lateinit var transactionKey: ByteArray - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["value"]) - class PubKeyDigest { - /** - * Version of the *digest* of the public key. - */ - @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 - - @get:XmlValue - lateinit var value: ByteArray - } - - @Suppress("UNUSED_PARAMETER") - enum class TransactionPhaseType(value: String) { - @XmlEnumValue("Initialisation") - INITIALISATION("Initialisation"), - - /** - * Auftragsdatentransfer - * - */ - @XmlEnumValue("Transfer") - TRANSFER("Transfer"), - - /** - * Quittungstransfer - * - */ - @XmlEnumValue("Receipt") - RECEIPT("Receipt"); - } - - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "") - class TimestampBankParameter { - @get:XmlValue - lateinit var value: XMLGregorianCalendar - - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - } - - - - @XmlType( - name = "PubKeyValueType", propOrder = [ - "rsaKeyValue", - "timeStamp" - ] - ) - @XmlAccessorType(XmlAccessType.NONE) - class PubKeyValueType { - @get:XmlElement(name = "RSAKeyValue", namespace = "http://www.w3.org/2000/09/xmldsig#", required = true) - lateinit var rsaKeyValue: RSAKeyValueType - - @get:XmlElement(name = "TimeStamp", required = false) - @get:XmlSchemaType(name = "dateTime") - var timeStamp: XMLGregorianCalendar? = null - } - - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "AuthenticationPubKeyInfoType", propOrder = [ - "x509Data", - "pubKeyValue", - "authenticationVersion" - ] - ) - class AuthenticationPubKeyInfoType { - @get:XmlAnyElement() - var x509Data: Element? = null - - @get:XmlElement(name = "PubKeyValue", required = true) - lateinit var pubKeyValue: PubKeyValueType - - @get:XmlElement(name = "AuthenticationVersion", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - lateinit var authenticationVersion: String - } - - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "EncryptionPubKeyInfoType", propOrder = [ - "x509Data", - "pubKeyValue", - "encryptionVersion" - ] - ) - class EncryptionPubKeyInfoType { - @get:XmlAnyElement() - var x509Data: Element? = null - - @get:XmlElement(name = "PubKeyValue", required = true) - lateinit var pubKeyValue: PubKeyValueType - - @get:XmlElement(name = "EncryptionVersion", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - lateinit var encryptionVersion: String - } - - @XmlAccessorType(XmlAccessType.NONE) - class FileFormatType { - @get:XmlAttribute(name = "CountryCode") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var language: String - - @get:XmlValue - @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) - lateinit var value: String - } - - /** - * Generic key-value pair. - */ - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["name", "value"]) - class Parameter { - @get:XmlAttribute(name = "Type", required = true) - lateinit var type: String - - @get:XmlElement(name = "Name", required = true) - lateinit var name: String - - @get:XmlElement(name = "Value", required = true) - lateinit var value: String - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["addressInfo", "bankInfo", "accountInfoList", "orderInfoList"]) - class PartnerInfo { - @get:XmlElement(name = "AddressInfo", required = true) - lateinit var addressInfo: AddressInfo - - @get:XmlElement(name = "BankInfo", required = true) - lateinit var bankInfo: BankInfo - - @get:XmlElement(name = "AccountInfo", type = AccountInfo::class) - var accountInfoList: List<AccountInfo>? = LinkedList<AccountInfo>() - - @get:XmlElement(name = "OrderInfo", type = AuthOrderInfoType::class) - var orderInfoList: List<AuthOrderInfoType> = LinkedList<AuthOrderInfoType>() - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "", - propOrder = ["orderType", "fileFormat", "transferType", "orderFormat", "description", "numSigRequired"] - ) - class AuthOrderInfoType { - @get:XmlElement(name = "OrderType") - lateinit var orderType: String - - @get:XmlElement(name = "FileFormat") - val fileFormat: EbicsTypes.FileFormatType? = null - - @get:XmlElement(name = "TransferType") - lateinit var transferType: String - - @get:XmlElement(name = "OrderFormat", required = false) - var orderFormat: String? = null - - @get:XmlElement(name = "Description") - lateinit var description: String - - @get:XmlElement(name = "NumSigRequired") - var numSigRequired: Int? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - class UserIDType { - @get:XmlValue - lateinit var value: String; - - @get:XmlAttribute(name = "Status") - var status: Int? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["userID", "name", "permissionList"]) - class UserInfo { - @get:XmlElement(name = "UserID", required = true) - lateinit var userID: UserIDType - - @get:XmlElement(name = "Name") - var name: String? = null - - @get:XmlElement(name = "Permission", type = UserPermission::class) - var permissionList: List<UserPermission>? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["orderTypes", "fileFormat", "accountID", "maxAmount"]) - class UserPermission { - @get:XmlAttribute(name = "AuthorizationLevel") - var authorizationLevel: String? = null - - @get:XmlElement(name = "OrderTypes") - var orderTypes: String? = null - - @get:XmlElement(name = "FileFormat") - val fileFormat: EbicsTypes.FileFormatType? = null - - @get:XmlElement(name = "AccountID") - val accountID: String? = null - - @get:XmlElement(name = "MaxAmount") - val maxAmount: String? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["name", "street", "postCode", "city", "region", "country"]) - class AddressInfo { - @get:XmlElement(name = "Name") - var name: String? = null - - @get:XmlElement(name = "Street") - var street: String? = null - - @get:XmlElement(name = "PostCode") - var postCode: String? = null - - @get:XmlElement(name = "City") - var city: String? = null - - @get:XmlElement(name = "Region") - var region: String? = null - - @get:XmlElement(name = "Country") - var country: String? = null - } - - - @XmlAccessorType(XmlAccessType.NONE) - class BankInfo { - @get:XmlElement(name = "HostID") - lateinit var hostID: String - - @get:XmlElement(type = EbicsTypes.Parameter::class) - var parameters: List<EbicsTypes.Parameter>? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["accountNumberList", "bankCodeList", "accountHolder"]) - class AccountInfo { - @get:XmlAttribute(name = "Currency") - var currency: String? = null - - @get:XmlAttribute(name = "ID") - lateinit var id: String - - @get:XmlAttribute(name = "Description") - var description: String? = null - - @get:XmlElements( - XmlElement(name = "AccountNumber", type = GeneralAccountNumber::class), - XmlElement(name = "NationalAccountNumber", type = NationalAccountNumber::class) - ) - var accountNumberList: List<AbstractAccountNumber>? = null - - @get:XmlElements( - XmlElement(name = "BankCode", type = GeneralBankCode::class), - XmlElement(name = "NationalBankCode", type = NationalBankCode::class) - ) - var bankCodeList: List<AbstractBankCode>? = null - - @get:XmlElement(name = "AccountHolder") - var accountHolder: String? = null - } - - interface AbstractAccountNumber - - @XmlAccessorType(XmlAccessType.NONE) - class GeneralAccountNumber : AbstractAccountNumber { - @get:XmlAttribute(name = "international") - var international: Boolean = false - - @get:XmlValue - lateinit var value: String - } - - @XmlAccessorType(XmlAccessType.NONE) - class NationalAccountNumber : AbstractAccountNumber { - @get:XmlAttribute(name = "format") - lateinit var format: String - - @get:XmlValue - lateinit var value: String - } - - interface AbstractBankCode - - @XmlAccessorType(XmlAccessType.NONE) - class GeneralBankCode : AbstractBankCode { - @get:XmlAttribute(name = "prefix") - var prefix: String? = null - - @get:XmlAttribute(name = "international") - var international: Boolean = false - - @get:XmlValue - lateinit var value: String - } - - @XmlAccessorType(XmlAccessType.NONE) - class NationalBankCode : AbstractBankCode { - @get:XmlValue - lateinit var value: String - - @get:XmlAttribute(name = "format") - lateinit var format: String - } -} -\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsUnsecuredRequest.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/EbicsUnsecuredRequest.kt @@ -1,221 +0,0 @@ -package tech.libeufin.schema.ebics_h004 - -import org.apache.xml.security.binding.xmldsig.RSAKeyValueType -import tech.libeufin.sandbox.EbicsOrderUtil -import tech.libeufin.schema.ebics_s001.SignatureTypes -import java.security.interfaces.RSAPrivateCrtKey -import javax.xml.bind.annotation.* -import javax.xml.bind.annotation.adapters.CollapsedStringAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType(name = "", propOrder = ["header", "body"]) -@XmlRootElement(name = "ebicsUnsecuredRequest") -class EbicsUnsecuredRequest { - @get:XmlAttribute(name = "Version", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var version: String - - @get:XmlAttribute(name = "Revision") - var revision: Int? = null - - @get:XmlElement(name = "header", required = true) - lateinit var header: Header - - @get:XmlElement(required = true) - lateinit var body: Body - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["static", "mutable"]) - class Header { - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "") - class EmptyMutableHeader - - @get:XmlElement(name = "static", required = true) - lateinit var static: StaticHeaderType - - @get:XmlElement(required = true) - lateinit var mutable: EmptyMutableHeader - - @get:XmlAttribute(name = "authenticate", required = true) - var authenticate: Boolean = false - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["dataTransfer"]) - class Body { - @get:XmlElement(name = "DataTransfer", required = true) - lateinit var dataTransfer: UnsecuredDataTransfer - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["orderData"]) - class UnsecuredDataTransfer { - @get:XmlElement(name = "OrderData", required = true) - lateinit var orderData: OrderData - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "") - class OrderData { - @get:XmlValue - lateinit var value: ByteArray - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "", - propOrder = ["hostID", "partnerID", "userID", "systemID", "product", "orderDetails", "securityMedium"] - ) - class StaticHeaderType { - @get:XmlElement(name = "HostID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var hostID: String - - @get:XmlElement(name = "PartnerID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var partnerID: String - - @get:XmlElement(name = "UserID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var userID: String - - @get:XmlElement(name = "SystemID") - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - var systemID: String? = null - - @get:XmlElement(name = "Product") - val product: EbicsTypes.Product? = null - - @get:XmlElement(name = "OrderDetails", required = true) - lateinit var orderDetails: OrderDetails - - @get:XmlElement(name = "SecurityMedium", required = true) - lateinit var securityMedium: String - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["orderType", "orderAttribute"]) - class OrderDetails { - @get:XmlElement(name = "OrderType", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var orderType: String - - @get:XmlElement(name = "OrderAttribute", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var orderAttribute: String - } - - companion object { - - fun createHia( - hostId: String, - userId: String, - partnerId: String, - authKey: RSAPrivateCrtKey, - encKey: RSAPrivateCrtKey - - ): EbicsUnsecuredRequest { - - return EbicsUnsecuredRequest().apply { - - version = "H004" - revision = 1 - header = EbicsUnsecuredRequest.Header().apply { - authenticate = true - static = EbicsUnsecuredRequest.StaticHeaderType().apply { - orderDetails = EbicsUnsecuredRequest.OrderDetails().apply { - orderAttribute = "DZNNN" - orderType = "HIA" - securityMedium = "0000" - hostID = hostId - userID = userId - partnerID = partnerId - } - } - mutable = EbicsUnsecuredRequest.Header.EmptyMutableHeader() - } - body = EbicsUnsecuredRequest.Body().apply { - dataTransfer = EbicsUnsecuredRequest.UnsecuredDataTransfer().apply { - orderData = EbicsUnsecuredRequest.OrderData().apply { - value = EbicsOrderUtil.encodeOrderDataXml( - HIARequestOrderData().apply { - authenticationPubKeyInfo = EbicsTypes.AuthenticationPubKeyInfoType().apply { - pubKeyValue = EbicsTypes.PubKeyValueType().apply { - rsaKeyValue = RSAKeyValueType().apply { - exponent = authKey.publicExponent.toByteArray() - modulus = authKey.modulus.toByteArray() - } - } - authenticationVersion = "X002" - } - encryptionPubKeyInfo = EbicsTypes.EncryptionPubKeyInfoType().apply { - pubKeyValue = EbicsTypes.PubKeyValueType().apply { - rsaKeyValue = RSAKeyValueType().apply { - exponent = encKey.publicExponent.toByteArray() - modulus = encKey.modulus.toByteArray() - } - } - encryptionVersion = "E002" - - } - partnerID = partnerId - userID = userId - } - ) - } - } - } - } - } - - fun createIni( - hostId: String, - userId: String, - partnerId: String, - signKey: RSAPrivateCrtKey - - ): EbicsUnsecuredRequest { - return EbicsUnsecuredRequest().apply { - version = "H004" - revision = 1 - header = EbicsUnsecuredRequest.Header().apply { - authenticate = true - static = EbicsUnsecuredRequest.StaticHeaderType().apply { - orderDetails = EbicsUnsecuredRequest.OrderDetails().apply { - orderAttribute = "DZNNN" - orderType = "INI" - securityMedium = "0000" - hostID = hostId - userID = userId - partnerID = partnerId - } - } - mutable = EbicsUnsecuredRequest.Header.EmptyMutableHeader() - } - body = Body().apply { - dataTransfer = EbicsUnsecuredRequest.UnsecuredDataTransfer().apply { - orderData = EbicsUnsecuredRequest.OrderData().apply { - value = EbicsOrderUtil.encodeOrderDataXml( - SignatureTypes.SignaturePubKeyOrderData().apply { - signaturePubKeyInfo = SignatureTypes.SignaturePubKeyInfoType().apply { - signatureVersion = "A006" - pubKeyValue = SignatureTypes.PubKeyValueType().apply { - rsaKeyValue = org.apache.xml.security.binding.xmldsig.RSAKeyValueType().apply { - exponent = signKey.publicExponent.toByteArray() - modulus = signKey.modulus.toByteArray() - } - } - } - userID = userId - partnerID = partnerId - } - ) - } - } - } - } - } - } -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/HIARequestOrderData.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/HIARequestOrderData.kt @@ -1,33 +0,0 @@ -package tech.libeufin.schema.ebics_h004 - -import javax.xml.bind.annotation.* -import javax.xml.bind.annotation.adapters.CollapsedStringAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter - - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType( - name = "HIARequestOrderDataType", - propOrder = ["authenticationPubKeyInfo", "encryptionPubKeyInfo", "partnerID", "userID", "any"] -) -@XmlRootElement(name = "HIARequestOrderData") -class HIARequestOrderData { - @get:XmlElement(name = "AuthenticationPubKeyInfo", required = true) - lateinit var authenticationPubKeyInfo: EbicsTypes.AuthenticationPubKeyInfoType - - @get:XmlElement(name = "EncryptionPubKeyInfo", required = true) - lateinit var encryptionPubKeyInfo: EbicsTypes.EncryptionPubKeyInfoType - - @get:XmlElement(name = "PartnerID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - lateinit var partnerID: String - - @get:XmlElement(name = "UserID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - lateinit var userID: String - - @get:XmlAnyElement(lax = true) - var any: List<Any>? = null -} -\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/HKDResponseOrderData.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/HKDResponseOrderData.kt @@ -1,15 +0,0 @@ -package tech.libeufin.schema.ebics_h004 - -import java.security.Permission -import javax.xml.bind.annotation.* - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType(name = "", propOrder = ["partnerInfo", "userInfoList"]) -@XmlRootElement(name = "HTDResponseOrderData") -class HKDResponseOrderData { - @get:XmlElement(name = "PartnerInfo", required = true) - lateinit var partnerInfo: EbicsTypes.PartnerInfo - - @get:XmlElement(name = "UserInfo", type = EbicsTypes.UserInfo::class, required = true) - lateinit var userInfoList: List<EbicsTypes.UserInfo> -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/HPBResponseOrderData.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/HPBResponseOrderData.kt @@ -1,21 +0,0 @@ -package tech.libeufin.schema.ebics_h004 - -import javax.xml.bind.annotation.* -import javax.xml.bind.annotation.adapters.CollapsedStringAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter - - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType(name = "", propOrder = ["authenticationPubKeyInfo", "encryptionPubKeyInfo", "hostID"]) -@XmlRootElement(name = "HPBResponseOrderData") -class HPBResponseOrderData { - @get:XmlElement(name = "AuthenticationPubKeyInfo", required = true) - lateinit var authenticationPubKeyInfo: EbicsTypes.AuthenticationPubKeyInfoType - - @get:XmlElement(name = "EncryptionPubKeyInfo", required = true) - lateinit var encryptionPubKeyInfo: EbicsTypes.EncryptionPubKeyInfoType - - @get:XmlElement(name = "HostID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var hostID: String -} -\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/HTDResponseOrderData.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/HTDResponseOrderData.kt @@ -1,15 +0,0 @@ -package tech.libeufin.schema.ebics_h004 - -import java.security.Permission -import javax.xml.bind.annotation.* - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType(name = "", propOrder = ["partnerInfo", "userInfo"]) -@XmlRootElement(name = "HTDResponseOrderData") -class HTDResponseOrderData { - @get:XmlElement(name = "PartnerInfo", required = true) - lateinit var partnerInfo: EbicsTypes.PartnerInfo - - @get:XmlElement(name = "UserInfo", required = true) - lateinit var userInfo: EbicsTypes.UserInfo -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/package-info.java b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_h004/package-info.java @@ -1,13 +0,0 @@ -/** - * This package-info.java file defines the default namespace for the JAXB bindings - * defined in the package. - */ - -@XmlSchema( - namespace = "urn:org:ebics:H004", - elementFormDefault = XmlNsForm.QUALIFIED -) -package tech.libeufin.schema.ebics_h004; - -import javax.xml.bind.annotation.XmlNsForm; -import javax.xml.bind.annotation.XmlSchema; -\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_hev/EbicsMessages.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_hev/EbicsMessages.kt @@ -1,81 +0,0 @@ -/* - * 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.schema.ebics_hev - -import javax.xml.bind.annotation.* -import javax.xml.bind.annotation.adapters.CollapsedStringAdapter -import javax.xml.bind.annotation.adapters.NormalizedStringAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType( - name = "HEVResponseDataType", - propOrder = ["systemReturnCode", "versionNumber", "any"] -) -@XmlRootElement(name = "ebicsHEVResponse") -class HEVResponse { - @get:XmlElement(name = "SystemReturnCode", required = true) - lateinit var systemReturnCode: SystemReturnCodeType - - @get:XmlElement(name = "VersionNumber", namespace = "http://www.ebics.org/H000") - var versionNumber: List<VersionNumber>? = null - - @get:XmlAnyElement(lax = true) - var any: List<Any>? = null - - @XmlAccessorType(XmlAccessType.NONE) - class VersionNumber { - @get:XmlValue - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var value: String - - @get:XmlAttribute(name = "ProtocolVersion", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var protocolVersion: String - - companion object { - fun create(protocolVersion: String, versionNumber: String): VersionNumber { - return VersionNumber().apply { - this.protocolVersion = protocolVersion - this.value = versionNumber - } - } - } - } -} - - -@XmlAccessorType(XmlAccessType.NONE) -@XmlType( - name = "SystemReturnCodeType", - propOrder = [ - "returnCode", - "reportText" - ] -) -class SystemReturnCodeType { - @get:XmlElement(name = "ReturnCode", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var returnCode: String - - @get:XmlElement(name = "ReportText", required = true) - @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) - lateinit var reportText: String -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_hev/package-info.java b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_hev/package-info.java @@ -1,13 +0,0 @@ -/** - * This package-info.java file defines the default namespace for the JAXB bindings - * defined in the package. - */ - -@XmlSchema( - namespace = "http://www.ebics.org/H000", - elementFormDefault = XmlNsForm.QUALIFIED -) -package tech.libeufin.schema.ebics_hev; - -import javax.xml.bind.annotation.XmlNsForm; -import javax.xml.bind.annotation.XmlSchema; diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_s001/SignatureTypes.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_s001/SignatureTypes.kt @@ -1,92 +0,0 @@ -/* - * 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.schema.ebics_s001 - -import org.apache.xml.security.binding.xmldsig.RSAKeyValueType -import org.apache.xml.security.binding.xmldsig.X509DataType -import javax.xml.bind.annotation.* -import javax.xml.bind.annotation.adapters.CollapsedStringAdapter -import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter -import javax.xml.datatype.XMLGregorianCalendar - - -object SignatureTypes { - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "PubKeyValueType", namespace = "http://www.ebics.org/S001", propOrder = [ - "rsaKeyValue", - "timeStamp" - ] - ) - class PubKeyValueType { - @get:XmlElement(name = "RSAKeyValue", namespace = "http://www.w3.org/2000/09/xmldsig#", required = true) - lateinit var rsaKeyValue: RSAKeyValueType - - @get:XmlElement(name = "TimeStamp") - @get:XmlSchemaType(name = "dateTime") - var timeStamp: XMLGregorianCalendar? = null - } - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "", - propOrder = [ - "x509Data", - "pubKeyValue", - "signatureVersion" - ] - ) - class SignaturePubKeyInfoType { - @get:XmlElement(name = "X509Data") - var x509Data: X509DataType? = null - - @get:XmlElement(name = "PubKeyValue", required = true) - lateinit var pubKeyValue: PubKeyValueType - - @get:XmlElement(name = "SignatureVersion", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - lateinit var signatureVersion: String - } - - /** - * EBICS INI payload. - */ - @XmlAccessorType(XmlAccessType.NONE) - @XmlType( - name = "", - propOrder = ["signaturePubKeyInfo", "partnerID", "userID"] - ) - @XmlRootElement(name = "SignaturePubKeyOrderData") - class SignaturePubKeyOrderData { - @get:XmlElement(name = "SignaturePubKeyInfo", required = true) - lateinit var signaturePubKeyInfo: SignaturePubKeyInfoType - - @get:XmlElement(name = "PartnerID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - lateinit var partnerID: String - - @get:XmlElement(name = "UserID", required = true) - @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) - @get:XmlSchemaType(name = "token") - lateinit var userID: String - } -} -\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_s001/UserSignatureData.kt b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_s001/UserSignatureData.kt @@ -1,27 +0,0 @@ -package tech.libeufin.schema.ebics_s001 - -import javax.xml.bind.annotation.* - -@XmlAccessorType(XmlAccessType.NONE) -@XmlRootElement(name = "UserSignatureData") -@XmlType(name = "", propOrder = ["orderSignatureList"]) -class UserSignatureData { - @XmlElement(name = "OrderSignatureData", type = OrderSignatureData::class) - var orderSignatureList: List<OrderSignatureData>? = null - - @XmlAccessorType(XmlAccessType.NONE) - @XmlType(name = "", propOrder = ["signatureVersion", "signatureValue", "partnerID", "userID"]) - class OrderSignatureData { - @XmlElement(name = "SignatureVersion") - lateinit var signatureVersion: String - - @XmlElement(name = "SignatureValue") - lateinit var signatureValue: ByteArray - - @XmlElement(name = "PartnerID") - lateinit var partnerID: String - - @XmlElement(name = "UserID") - lateinit var userID: String - } -} -\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_s001/package-info.java b/sandbox/src/main/kotlin/tech/libeufin/schema/ebics_s001/package-info.java @@ -1,13 +0,0 @@ -/** - * This package-info.java file defines the default namespace for the JAXB bindings - * defined in the package. - */ - -@XmlSchema( - namespace = "http://www.ebics.org/S001", - elementFormDefault = XmlNsForm.QUALIFIED -) -package tech.libeufin.schema.ebics_s001; - -import javax.xml.bind.annotation.XmlNsForm; -import javax.xml.bind.annotation.XmlSchema; -\ No newline at end of file diff --git a/sandbox/src/main/resources/logback.xml b/sandbox/src/main/resources/logback.xml @@ -13,24 +13,17 @@ </encoder> </appender> - <appender name="NEXUS-FILE" class="ch.qos.logback.core.FileAppender"> - <file>/tmp/nexus.log</file> - <append>false</append> - <encoder> - <pattern>%-5relative %-5level %logger{35} - %msg%n</pattern> - </encoder> - </appender> - <logger name="tech.libeufin.sandbox" level="DEBUG" additivity="false"> <appender-ref ref="STDOUT" /> <appender-ref ref="SANDBOX-FILE" /> </logger> - <logger name="tech.libeufin.nexus" level="TRACE" additivity="false"> + <logger name="tech.libeufin.util" level="TRACE" additivity="false"> <appender-ref ref="STDOUT" /> - <appender-ref ref="NEXUS-FILE" /> + <appender-ref ref="SANDBOX-FILE" /> </logger> + <logger name="io.netty" level="WARN" /> <logger name="ktor" level="WARN" /> <logger name="Exposed" level="WARN" /> diff --git a/sandbox/src/test/kotlin/CryptoUtilTest.kt b/sandbox/src/test/kotlin/CryptoUtilTest.kt @@ -20,6 +20,7 @@ package tech.libeufin.sandbox import org.junit.Test +import tech.libeufin.util.CryptoUtil import java.security.KeyPairGenerator import java.security.interfaces.RSAPrivateCrtKey import javax.crypto.EncryptedPrivateKeyInfo diff --git a/sandbox/src/test/kotlin/EbicsMessagesTest.kt b/sandbox/src/test/kotlin/EbicsMessagesTest.kt @@ -4,10 +4,12 @@ import junit.framework.TestCase.assertEquals import org.apache.xml.security.binding.xmldsig.SignatureType import org.junit.Test import org.w3c.dom.Element -import tech.libeufin.schema.ebics_h004.* -import tech.libeufin.schema.ebics_hev.HEVResponse -import tech.libeufin.schema.ebics_hev.SystemReturnCodeType -import tech.libeufin.schema.ebics_s001.SignatureTypes +import tech.libeufin.util.schema.ebics_h004.* +import tech.libeufin.util.schema.ebics_hev.HEVResponse +import tech.libeufin.util.schema.ebics_hev.SystemReturnCodeType +import tech.libeufin.util.schema.ebics_s001.SignatureTypes +import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.XMLUtil import javax.xml.datatype.DatatypeFactory import kotlin.test.assertNotNull import kotlin.test.assertTrue diff --git a/sandbox/src/test/kotlin/EbicsOrderUtilTest.kt b/sandbox/src/test/kotlin/EbicsOrderUtilTest.kt @@ -1,6 +1,7 @@ package tech.libeufin.sandbox import org.junit.Test +import tech.libeufin.util.EbicsOrderUtil import kotlin.test.assertEquals diff --git a/sandbox/src/test/kotlin/LogTest.kt b/sandbox/src/test/kotlin/LogTest.kt @@ -5,6 +5,8 @@ import org.junit.Test import org.junit.Assert.* import org.slf4j.LoggerFactory import java.net.URLClassLoader +import tech.libeufin.util.LOGGER as utilLogger + class LogTest { @@ -15,5 +17,11 @@ class LogTest { loggerSandbox.info("line") loggerNexus.trace("other line") } + + @Test + fun logFromUtil() { + /* This log should show up with 'util' name but without this latter owning a logback.xml */ + utilLogger.trace("shown") + } } diff --git a/sandbox/src/test/kotlin/XmlCombinatorsTest.kt b/sandbox/src/test/kotlin/XmlCombinatorsTest.kt @@ -20,6 +20,7 @@ package tech.libeufin.sandbox import org.junit.Test +import tech.libeufin.util.constructXml class XmlCombinatorsTest { @@ -36,6 +37,7 @@ class XmlCombinatorsTest { element("g/h/") } } + element("one more") } } println(s) diff --git a/sandbox/src/test/kotlin/XmlUtilTest.kt b/sandbox/src/test/kotlin/XmlUtilTest.kt @@ -4,13 +4,12 @@ import org.apache.xml.security.binding.xmldsig.SignatureType import org.junit.Test import org.junit.Assert.* import org.junit.Ignore -import org.junit.rules.ExpectedException -import org.xml.sax.SAXParseException -import tech.libeufin.schema.ebics_h004.EbicsKeyManagementResponse -import tech.libeufin.schema.ebics_h004.EbicsResponse -import tech.libeufin.schema.ebics_h004.EbicsTypes -import tech.libeufin.schema.ebics_h004.HTDResponseOrderData -import java.rmi.UnmarshalException +import tech.libeufin.util.schema.ebics_h004.EbicsKeyManagementResponse +import tech.libeufin.util.schema.ebics_h004.EbicsResponse +import tech.libeufin.util.schema.ebics_h004.EbicsTypes +import tech.libeufin.util.schema.ebics_h004.HTDResponseOrderData +import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.XMLUtil import java.security.KeyPairGenerator import java.util.* import javax.xml.transform.stream.StreamSource diff --git a/util/src/main/kotlin/CryptoUtil.kt b/util/src/main/kotlin/CryptoUtil.kt @@ -0,0 +1,293 @@ +/* + * 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.util + +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.io.ByteArrayOutputStream +import java.math.BigInteger +import java.security.* +import java.security.interfaces.RSAPrivateCrtKey +import java.security.interfaces.RSAPublicKey +import java.security.spec.* +import javax.crypto.* +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.PBEKeySpec +import javax.crypto.spec.PBEParameterSpec +import javax.crypto.spec.SecretKeySpec + +/** + * Helpers for dealing with cryptographic operations in EBICS / LibEuFin. + */ +object CryptoUtil { + + /** + * RSA key pair. + */ + data class RsaCrtKeyPair(val private: RSAPrivateCrtKey, val public: RSAPublicKey) + + class EncryptionResult( + val encryptedTransactionKey: ByteArray, + val pubKeyDigest: ByteArray, + val encryptedData: ByteArray, + + /** + * This key needs to be reused between different upload phases. + */ + val plainTransactionKey: SecretKey? = null + ) + + private val bouncyCastleProvider = BouncyCastleProvider() + + /** + * 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) + if (priv !is RSAPrivateCrtKey) + 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) + if (pub !is RSAPublicKey) + 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) + if (pub !is RSAPublicKey) + 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) + val pair = gen.genKeyPair() + val priv = pair.private + val pub = pair.public + if (priv !is RSAPrivateCrtKey) + throw Exception("key generation failed") + if (pub !is RSAPublicKey) + 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 + } + + /** + * Hash an RSA public key according to the EBICS standard (EBICS 2.5: 4.4.1.2.3). + */ + fun getEbicsPublicKeyHash(publicKey: RSAPublicKey): ByteArray { + val keyBytes = ByteArrayOutputStream() + keyBytes.writeBytes(publicKey.publicExponent.toByteArray().toHexString().toByteArray()) + keyBytes.write(' '.toInt()) + keyBytes.writeBytes(publicKey.modulus.toByteArray().toHexString().toByteArray()) + val digest = MessageDigest.getInstance("SHA-256") + return digest.digest(keyBytes.toByteArray()) + } + + 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) + val encryptedData = symmetricCipher.doFinal(data) + val asymmetricCipher = Cipher.getInstance("RSA/None/PKCS1Padding", + bouncyCastleProvider + ) + asymmetricCipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey) + val encryptedTransactionKey = asymmetricCipher.doFinal(transactionKey.encoded) + val pubKeyDigest = getEbicsPublicKeyHash(encryptionPublicKey) + return EncryptionResult( + encryptedTransactionKey, + pubKeyDigest, + encryptedData, + transactionKey + ) + } + + fun decryptEbicsE002(enc: EncryptionResult, privateKey: RSAPrivateCrtKey): ByteArray { + return decryptEbicsE002( + enc.encryptedTransactionKey, + enc.encryptedData, + privateKey + ) + } + + fun decryptEbicsE002(encryptedTransactionKey: ByteArray, encryptedData: ByteArray, privateKey: RSAPrivateCrtKey): ByteArray { + val asymmetricCipher = Cipher.getInstance("RSA/None/PKCS1Padding", + bouncyCastleProvider + ) + asymmetricCipher.init(Cipher.DECRYPT_MODE, privateKey) + val transactionKeyBytes = asymmetricCipher.doFinal(encryptedTransactionKey) + val secretKeySpec = SecretKeySpec(transactionKeyBytes, "AES") + 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 + } + + /** + * Signing algorithm corresponding to the EBICS A006 signing process. + * + * Note that while [data] can be arbitrary-length data, in EBICS, the order + * data is *always* hashed *before* passing it to the signing algorithm, which again + * uses a hash internally. + */ + fun signEbicsA006(data: ByteArray, privateKey: RSAPrivateCrtKey): ByteArray { + val signature = Signature.getInstance("SHA256withRSA/PSS", bouncyCastleProvider) + signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1)) + signature.initSign(privateKey) + signature.update(data) + return signature.sign() + } + + fun verifyEbicsA006(sig: ByteArray, data: ByteArray, publicKey: RSAPublicKey): Boolean { + val signature = Signature.getInstance("SHA256withRSA/PSS", bouncyCastleProvider) + signature.setParameter(PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1)) + signature.initVerify(publicKey) + signature.update(data) + return signature.verify(sig) + } + + fun digestEbicsOrderA006(orderData: ByteArray): ByteArray { + val digest = MessageDigest.getInstance("SHA-256") + for (b in orderData) { + when (b) { + '\r'.toByte(), '\n'.toByte(), (26).toByte() -> Unit + else -> digest.update(b) + } + } + return digest.digest() + } + + + fun decryptKey(data: EncryptedPrivateKeyInfo, passphrase: String): RSAPrivateCrtKey { + + /* make key out of passphrase */ + val pbeKeySpec = PBEKeySpec(passphrase.toCharArray()) + val keyFactory = SecretKeyFactory.getInstance(data.algName) + val secretKey = keyFactory.generateSecret(pbeKeySpec) + + /* Make a cipher */ + val cipher = Cipher.getInstance(data.algName) + cipher.init( + Cipher.DECRYPT_MODE, + secretKey, + data.algParameters // has hash count and salt + ) + + /* Ready to decrypt */ + val decryptedKeySpec: PKCS8EncodedKeySpec = data.getKeySpec(cipher) + val priv = KeyFactory.getInstance("RSA").generatePrivate(decryptedKeySpec) + if (priv !is RSAPrivateCrtKey) + throw Exception("wrong encoding") + return priv + } + + fun encryptKey(data: ByteArray, passphrase: String): ByteArray { + + /* Cipher parameters: salt and hash count */ + val hashIterations = 30 + val salt = ByteArray(8) + SecureRandom().nextBytes(salt) + val pbeParameterSpec = PBEParameterSpec(salt, hashIterations) + + /* *Other* cipher parameters: symmetric key (from password) */ + val pbeAlgorithm = "PBEWithSHA1AndDESede" + val pbeKeySpec = PBEKeySpec(passphrase.toCharArray()) + val keyFactory = SecretKeyFactory.getInstance(pbeAlgorithm) + val secretKey = keyFactory.generateSecret(pbeKeySpec) + + /* Make a cipher */ + val cipher = Cipher.getInstance(pbeAlgorithm) + cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec) + + /* ready to encrypt now */ + val cipherText = cipher.doFinal(data) + + /* Must now bundle a PKCS#8-compatible object, that contains + * algorithm, salt and hash count information */ + + val bundleAlgorithmParams = AlgorithmParameters.getInstance(pbeAlgorithm) + bundleAlgorithmParams.init(pbeParameterSpec) + + val bundle = EncryptedPrivateKeyInfo(bundleAlgorithmParams, cipherText) + + return bundle.encoded + } +} diff --git a/util/src/main/kotlin/EbicsOrderUtil.kt b/util/src/main/kotlin/EbicsOrderUtil.kt @@ -0,0 +1,89 @@ +/* + * 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.util + +import java.lang.IllegalArgumentException +import java.security.SecureRandom +import java.util.zip.DeflaterInputStream +import java.util.zip.InflaterInputStream + +/** + * Helpers for dealing with order compression, encryption, decryption, chunking and re-assembly. + */ +object EbicsOrderUtil { + inline fun <reified T> decodeOrderDataXml(encodedOrderData: ByteArray): T { + return InflaterInputStream(encodedOrderData.inputStream()).use { + val bytes = it.readAllBytes() + XMLUtil.convertStringToJaxb<T>(bytes.toString(Charsets.UTF_8)).value + } + } + + inline fun <reified T> encodeOrderDataXml(obj: T): ByteArray { + val bytes = XMLUtil.convertJaxbToString(obj).toByteArray() + return DeflaterInputStream(bytes.inputStream()).use { + it.readAllBytes() + } + } + + fun generateTransactionId(): String { + val rng = SecureRandom() + val res = ByteArray(16) + rng.nextBytes(res) + return res.toHexString().toUpperCase() + } + + /** + * Calculate the resulting size of base64-encoding data of the given length, + * including padding. + */ + fun calculateBase64EncodedLength(dataLength: Int): Int { + val blocks = (dataLength + 3 - 1) / 3 + return blocks * 4 + } + + fun checkOrderIDOverflow(n: Int): Boolean { + if (n <= 0) + throw IllegalArgumentException() + val base = 10 + 26 + return n >= base * base + } + + private fun getDigitChar(x: Int): Char { + if (x < 10) { + return '0' + x + } + return 'A' + (x - 10) + } + + fun computeOrderIDFromNumber(n: Int): String { + if (n <= 0) + throw IllegalArgumentException() + if (checkOrderIDOverflow(n)) + throw IllegalArgumentException() + var ni = n + val base = 10 + 26 + val x1 = ni % base + ni = ni / base + val x2 = ni % base + val c1 = getDigitChar(x1) + val c2 = getDigitChar(x2) + return String(charArrayOf('O', 'R', c2, c1)) + } +} diff --git a/util/src/main/kotlin/XMLUtil.kt b/util/src/main/kotlin/XMLUtil.kt @@ -0,0 +1,397 @@ +/* + * 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.util + +import com.sun.org.apache.xerces.internal.dom.DOMInputImpl +import org.w3c.dom.Document +import org.w3c.dom.Node +import org.w3c.dom.NodeList +import org.w3c.dom.ls.LSInput +import org.w3c.dom.ls.LSResourceResolver +import org.xml.sax.ErrorHandler +import org.xml.sax.InputSource +import org.xml.sax.SAXException +import org.xml.sax.SAXParseException +import java.io.* +import java.security.PrivateKey +import java.security.PublicKey +import javax.xml.XMLConstants +import javax.xml.bind.JAXBContext +import javax.xml.bind.JAXBElement +import javax.xml.bind.Marshaller +import javax.xml.crypto.* +import javax.xml.crypto.dom.DOMURIReference +import javax.xml.crypto.dsig.* +import javax.xml.crypto.dsig.dom.DOMSignContext +import javax.xml.crypto.dsig.dom.DOMValidateContext +import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec +import javax.xml.crypto.dsig.spec.TransformParameterSpec +import javax.xml.namespace.NamespaceContext +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.transform.OutputKeys +import javax.xml.transform.Source +import javax.xml.transform.TransformerFactory +import javax.xml.transform.dom.DOMSource +import javax.xml.transform.stream.StreamResult +import javax.xml.transform.stream.StreamSource +import javax.xml.validation.SchemaFactory +import javax.xml.validation.Validator +import javax.xml.xpath.XPath +import javax.xml.xpath.XPathConstants +import javax.xml.xpath.XPathFactory + +/** + * Helpers for dealing with XML in EBICS. + */ +class XMLUtil private constructor() { + /** + * This URI dereferencer allows handling the resource reference used for + * XML signatures in EBICS. + */ + private class EbicsSigUriDereferencer : URIDereferencer { + override fun dereference(myRef: URIReference?, myCtx: XMLCryptoContext?): Data { + val ebicsXpathExpr = "//*[@authenticate='true']" + if (myRef !is DOMURIReference) + throw Exception("invalid type") + if (myRef.uri != "#xpointer($ebicsXpathExpr)") + throw Exception("invalid EBICS XML signature URI: '${myRef.uri}'") + val xp: XPath = XPathFactory.newInstance().newXPath() + val nodeSet = xp.compile("//*[@authenticate='true']/descendant-or-self::node()").evaluate(myRef.here + .ownerDocument, XPathConstants + .NODESET) + if (nodeSet !is NodeList) + throw Exception("invalid type") + if (nodeSet.length <= 0) { + throw Exception("no nodes to sign") + } + val nodeList = ArrayList<Node>() + for (i in 0 until nodeSet.length) { + val node = nodeSet.item(i) + nodeList.add(node) + } + return NodeSetData { nodeList.iterator() } + } + } + + /** + * Validator for EBICS messages. + */ + private val validator = try { + + } catch (e: SAXException) { + e.printStackTrace() + throw e + } + + companion object { + + private var cachedEbicsValidator: Validator? = null + + private fun getEbicsValidator(): Validator { + val currentValidator = cachedEbicsValidator + if (currentValidator != null) + return currentValidator + val classLoader = ClassLoader.getSystemClassLoader() + val sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI) + sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file") + sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "") + sf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true) + sf.errorHandler = object : ErrorHandler { + override fun warning(p0: SAXParseException?) { + println("Warning: $p0") + } + + override fun error(p0: SAXParseException?) { + println("Error: $p0") + } + + override fun fatalError(p0: SAXParseException?) { + println("Fatal error: $p0") + } + } + sf.resourceResolver = object : LSResourceResolver { + override fun resolveResource( + type: String?, + namespaceURI: String?, + publicId: String?, + systemId: String?, + baseUri: String? + ): LSInput? { + if (type != "http://www.w3.org/2001/XMLSchema") { + return null + } + val res = classLoader.getResourceAsStream("xsd/$systemId") ?: return null + return DOMInputImpl(publicId, systemId, baseUri, res, "UTF-8") + } + } + val schemaInputs: Array<Source> = listOf("xsd/ebics_H004.xsd", "xsd/ebics_hev.xsd").map { + val resUrl = classLoader.getResource(it) ?: throw FileNotFoundException("Schema file $it not found.") + StreamSource(File(resUrl.toURI())) + }.toTypedArray() + val bundle = sf.newSchema(schemaInputs) + val newValidator = bundle.newValidator() + cachedEbicsValidator = newValidator + return newValidator + } + + /** + * + * @param xmlDoc the XML document to validate + * @return true when validation passes, false otherwise + */ + fun validate(xmlDoc: StreamSource): Boolean { + try { + getEbicsValidator().validate(xmlDoc) + } catch (e: Exception) { + LOGGER.warn("Validation failed: ${e}") + return false + } + return true; + } + + /** + * Validates the DOM against the Schema(s) of this object. + * @param domDocument DOM to validate + * @return true/false if the document is valid/invalid + */ + fun validateFromDom(domDocument: Document): Boolean { + try { + getEbicsValidator().validate(DOMSource(domDocument)) + } catch (e: SAXException) { + e.printStackTrace() + return false + } + return true + } + + /** + * Craft object to be passed to the XML validator. + * @param xmlString XML body, as read from the POST body. + * @return InputStream object, as wanted by the validator. + */ + fun validateFromString(xmlString: String): Boolean { + val xmlInputStream: InputStream = ByteArrayInputStream(xmlString.toByteArray()) + val xmlSource = StreamSource(xmlInputStream) + return validate(xmlSource) + } + + + inline fun <reified T> convertJaxbToString(obj: T): String { + val sw = StringWriter() + val jc = JAXBContext.newInstance(T::class.java) + val m = jc.createMarshaller() + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true) + m.marshal(obj, sw) + return sw.toString() + } + + inline fun <reified T> convertJaxbToDocument(obj: T): Document { + val dbf: DocumentBuilderFactory = DocumentBuilderFactory.newInstance() + dbf.isNamespaceAware = true + val doc = dbf.newDocumentBuilder().newDocument() + val jc = JAXBContext.newInstance(T::class.java) + val m = jc.createMarshaller() + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true) + m.marshal(obj, doc) + return doc + } + + /** + * Convert a XML string to the JAXB representation. + * + * @param documentString the string to convert into JAXB. + * @return the JAXB object reflecting the original XML document. + */ + inline fun <reified T> convertStringToJaxb(documentString: String): JAXBElement<T> { + val jc = JAXBContext.newInstance(T::class.java) + val u = jc.createUnmarshaller() + return u.unmarshal( /* Marshalling the object into the document. */ + StreamSource(StringReader(documentString)), + T::class.java + ) + } + + /** + * Extract String from DOM. + * + * @param document the DOM to extract the string from. + * @return the final String, or null if errors occur. + */ + fun convertDomToString(document: Document): String { + /* Make Transformer. */ + val tf = TransformerFactory.newInstance() + val t = tf.newTransformer() + + //t.setOutputProperty(OutputKeys.INDENT, "yes") + + /* Make string writer. */ + val sw = StringWriter() + + /* Extract string. */ + t.transform(DOMSource(document), StreamResult(sw)) + return sw.toString() + } + + /** + * Convert a node to a string without the XML declaration or + * indentation. + */ + fun convertNodeToString(node: Node): String { + /* Make Transformer. */ + val tf = TransformerFactory.newInstance() + val t = tf.newTransformer() + + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + + /* Make string writer. */ + val sw = StringWriter() + + /* Extract string. */ + t.transform(DOMSource(node), StreamResult(sw)) + return sw.toString() + } + + /** + * Convert a DOM document to the JAXB representation. + * + * @param finalType class type of the output + * @param document the document to convert into JAXB. + * @return the JAXB object reflecting the original XML document. + */ + fun <T> convertDomToJaxb(finalType: Class<T>, document: Document): JAXBElement<T> { + + val jc = JAXBContext.newInstance(finalType) + + /* Marshalling the object into the document. */ + val m = jc.createUnmarshaller() + return m.unmarshal(document, finalType) // document "went" into Jaxb + } + + /** + * Parse string into XML DOM. + * @param xmlString the string to parse. + * @return the DOM representing @a xmlString + */ + fun parseStringIntoDom(xmlString: String): Document { + val factory = DocumentBuilderFactory.newInstance().apply { + isNamespaceAware = true + } + val xmlInputStream = ByteArrayInputStream(xmlString.toByteArray()) + val builder = factory.newDocumentBuilder() + return builder.parse(InputSource(xmlInputStream)) + } + + + /** + * Sign an EBICS document with the authentication and identity signature. + */ + fun signEbicsDocument(doc: Document, signingPriv: PrivateKey): Unit { + val xpath = XPathFactory.newInstance().newXPath() + xpath.namespaceContext = object : NamespaceContext { + override fun getNamespaceURI(p0: String?): String { + return when (p0) { + "ebics" -> "urn:org:ebics:H004" + else -> throw IllegalArgumentException() + } + } + + override fun getPrefix(p0: String?): String { + throw UnsupportedOperationException() + } + + override fun getPrefixes(p0: String?): MutableIterator<String> { + throw UnsupportedOperationException() + } + } + val authSigNode = xpath.compile("/*[1]/ebics:AuthSignature").evaluate(doc, XPathConstants.NODE) + if (authSigNode !is Node) + throw java.lang.Exception("no AuthSignature") + val fac = XMLSignatureFactory.getInstance("DOM") + val c14n = fac.newTransform(CanonicalizationMethod.INCLUSIVE, null as TransformParameterSpec?) + val ref: Reference = + fac.newReference( + "#xpointer(//*[@authenticate='true'])", + fac.newDigestMethod(DigestMethod.SHA256, null), + listOf(c14n), + null, + null + ) + val canon: CanonicalizationMethod = + fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null as C14NMethodParameterSpec?) + val signatureMethod = fac.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", null) + val si: SignedInfo = fac.newSignedInfo(canon, signatureMethod, listOf(ref)) + val sig: XMLSignature = fac.newXMLSignature(si, null) + val dsc = DOMSignContext(signingPriv, authSigNode) + dsc.defaultNamespacePrefix = "ds" + dsc.uriDereferencer = EbicsSigUriDereferencer() + + dsc.setProperty("javax.xml.crypto.dsig.cacheReference", true) + + sig.sign(dsc) + + println("canon data: " + sig.signedInfo.canonicalizedData.readAllBytes().toString(Charsets.UTF_8)) + + val innerSig = authSigNode.firstChild + while (innerSig.hasChildNodes()) { + authSigNode.appendChild(innerSig.firstChild) + } + authSigNode.removeChild(innerSig) + } + + fun verifyEbicsDocument(doc: Document, signingPub: PublicKey): Boolean { + val xpath = XPathFactory.newInstance().newXPath() + xpath.namespaceContext = object : NamespaceContext { + override fun getNamespaceURI(p0: String?): String { + return when (p0) { + "ebics" -> "urn:org:ebics:H004" + else -> throw IllegalArgumentException() + } + } + + override fun getPrefix(p0: String?): String { + throw UnsupportedOperationException() + } + + override fun getPrefixes(p0: String?): MutableIterator<String> { + throw UnsupportedOperationException() + } + } + val doc2: Document = doc.cloneNode(true) as Document + val authSigNode = xpath.compile("/*[1]/ebics:AuthSignature").evaluate(doc2, XPathConstants.NODE) + if (authSigNode !is Node) + throw java.lang.Exception("no AuthSignature") + val sigEl = doc2.createElementNS("http://www.w3.org/2000/09/xmldsig#", "ds:Signature") + authSigNode.parentNode.insertBefore(sigEl, authSigNode) + while (authSigNode.hasChildNodes()) { + sigEl.appendChild(authSigNode.firstChild) + } + authSigNode.parentNode.removeChild(authSigNode) + val fac = XMLSignatureFactory.getInstance("DOM") + val dvc = DOMValidateContext(signingPub, sigEl) + dvc.setProperty("javax.xml.crypto.dsig.cacheReference", true) + dvc.uriDereferencer = EbicsSigUriDereferencer() + val sig = fac.unmarshalXMLSignature(dvc) + // FIXME: check that parameters are okay!s + val valResult = sig.validate(dvc) + sig.signedInfo.references[0].validate(dvc) + return valResult + } + } +} diff --git a/util/src/main/kotlin/XmlCombinators.kt b/util/src/main/kotlin/XmlCombinators.kt @@ -0,0 +1,89 @@ +package tech.libeufin.util + +import com.sun.xml.txw2.output.IndentingXMLStreamWriter +import java.io.StringWriter +import javax.xml.stream.XMLOutputFactory +import javax.xml.stream.XMLStreamWriter + +class XmlElementBuilder(val w: XMLStreamWriter) { + + fun element(path: MutableList<String>, f: XmlElementBuilder.() -> Unit = {}) { + + if (path.isEmpty()) { + f(this) + return + } + + w.writeStartElement(path.removeAt(0)) + this.element(path, f) + w.writeEndElement() + + } + + fun element(path: String, f: XmlElementBuilder.() -> Unit = {}) { + val splitPath = path.trim('/').split("/").toMutableList() + this.element(splitPath, f) + } + + fun attribute(name: String, value: String) { + w.writeAttribute(name, value) + } + + fun text(content: String) { + w.writeCharacters(content) + } +} + +class XmlDocumentBuilder { + + private var maybeWriter: XMLStreamWriter? = null + + internal var writer: XMLStreamWriter + get() { + val w = maybeWriter + return w ?: throw AssertionError("no writer set") + } + set(w: XMLStreamWriter) { + maybeWriter = w + } + + + fun namespace(prefix: String, uri: String) { + writer.setPrefix(prefix, uri) + } + + fun defaultNamespace(uri: String) { + writer.setDefaultNamespace(uri) + } + + fun root(name: String, f: XmlElementBuilder.() -> Unit) { + val elementBuilder = XmlElementBuilder(writer) + writer.writeStartElement(name) + f(elementBuilder) + writer.writeEndElement() + } +} + +fun constructXml(indent: Boolean = false, f: XmlDocumentBuilder.() -> Unit): String { + val b = XmlDocumentBuilder() + val factory = XMLOutputFactory.newFactory() + factory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true) + val stream = StringWriter() + var writer = factory.createXMLStreamWriter(stream) + if (indent) { + writer = IndentingXMLStreamWriter(writer) + } + b.writer = writer + writer.writeStartDocument() + f(b) + writer.writeEndDocument() + return stream.buffer.toString() +} + +class XmlDocumentDestructor { +} + +fun <T>destructXml(input: String, f: XmlDocumentDestructor.() -> T): T { + val d = XmlDocumentDestructor() + return f(d) +} diff --git a/util/src/main/kotlin/blob.kt b/util/src/main/kotlin/blob.kt @@ -0,0 +1,8 @@ +package tech.libeufin.util + +import java.sql.Blob + +fun Blob.toByteArray(): ByteArray { + return this.binaryStream.readAllBytes() +} + diff --git a/util/src/main/kotlin/hex.kt b/util/src/main/kotlin/hex.kt @@ -0,0 +1,7 @@ +package tech.libeufin.util + +fun ByteArray.toHexString() : String { + return this.joinToString("") { + java.lang.String.format("%02x", it) + } +} diff --git a/util/src/main/kotlin/logger.kt b/util/src/main/kotlin/logger.kt @@ -0,0 +1,6 @@ +package tech.libeufin.util + +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +val LOGGER: Logger = LoggerFactory.getLogger("tech.libeufin.util") +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_h004/EbicsKeyManagementResponse.kt b/util/src/main/kotlin/schema/ebics_h004/EbicsKeyManagementResponse.kt @@ -0,0 +1,102 @@ +package tech.libeufin.util.schema.ebics_h004 + +import javax.xml.bind.annotation.* +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter +import javax.xml.bind.annotation.adapters.NormalizedStringAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter + + +@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: MutableHeaderType + + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["orderID", "returnCode", "reportText"]) + class MutableHeaderType { + @get:XmlElement(name = "OrderID") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + var orderID: String? = null + + @get:XmlElement(name = "ReturnCode", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + lateinit var returnCode: String + + @get:XmlElement(name = "ReportText", required = true) + @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) + @get:XmlSchemaType(name = "normalizedString") + lateinit var reportText: String + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "") + class EmptyStaticHeader + + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["dataTransfer", "returnCode", "timestampBankParameter"]) + class Body { + @get:XmlElement(name = "DataTransfer") + var dataTransfer: DataTransfer? = null + + @get:XmlElement(name = "ReturnCode", required = true) + lateinit var returnCode: ReturnCode + + @get:XmlElement(name = "TimestampBankParameter") + var timestampBankParameter: EbicsTypes.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: EbicsTypes.DataEncryptionInfo? = null + + @get:XmlElement(name = "OrderData", required = true) + lateinit var orderData: OrderData + } + + @XmlAccessorType(XmlAccessType.NONE) + class OrderData { + @get:XmlValue + lateinit var value: ByteArray + } +} diff --git a/util/src/main/kotlin/schema/ebics_h004/EbicsNpkdRequest.kt b/util/src/main/kotlin/schema/ebics_h004/EbicsNpkdRequest.kt @@ -0,0 +1,135 @@ +package tech.libeufin.util.schema.ebics_h004 + +import org.apache.xml.security.binding.xmldsig.SignatureType +import javax.xml.bind.annotation.* +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter +import javax.xml.bind.annotation.adapters.HexBinaryAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter +import javax.xml.datatype.XMLGregorianCalendar + + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["header", "authSignature", "body"]) +@XmlRootElement(name = "ebicsNoPubKeyDigestsRequest") +class EbicsNpkdRequest { + @get:XmlAttribute(name = "Version", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var version: String + + @get:XmlAttribute(name = "Revision") + var revision: Int? = null + + @get:XmlElement(name = "header", required = true) + lateinit var header: Header + + @get:XmlElement(name = "AuthSignature", required = true) + lateinit var authSignature: SignatureType + + @get:XmlElement(required = true) + lateinit var body: EmptyBody + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["static", "mutable"]) + class Header { + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + + @get:XmlElement(name = "static", required = true) + lateinit var static: StaticHeaderType + + @get:XmlElement(required = true) + lateinit var mutable: EmptyMutableHeader + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "StaticHeader", + propOrder = ["hostID", "nonce", "timestamp", "partnerID", "userID", "systemID", "product", "orderDetails", "securityMedium"] + ) + class StaticHeaderType { + @get:XmlElement(name = "HostID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var hostID: String + + @get:XmlElement(name = "Nonce", type = String::class) + @get:XmlJavaTypeAdapter(HexBinaryAdapter::class) + @get:XmlSchemaType(name = "hexBinary") + lateinit var nonce: ByteArray + + @get:XmlElement(name = "Timestamp") + @get:XmlSchemaType(name = "dateTime") + var timestamp: XMLGregorianCalendar? = null + + @get:XmlElement(name = "PartnerID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var partnerID: String + + @get:XmlElement(name = "UserID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var userID: String + + @get:XmlElement(name = "SystemID") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + var systemID: String? = null + + @get:XmlElement(name = "Product") + val product: EbicsTypes.Product? = null + + @get:XmlElement(name = "OrderDetails", required = true) + lateinit var orderDetails: OrderDetails + + @get:XmlElement(name = "SecurityMedium", required = true) + lateinit var securityMedium: String + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["orderType", "orderAttribute"]) + class OrderDetails { + @get:XmlElement(name = "OrderType", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var orderType: String + + @get:XmlElement(name = "OrderAttribute", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var orderAttribute: String + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "") + class EmptyMutableHeader + + @XmlAccessorType(XmlAccessType.NONE) + class EmptyBody + + companion object { + fun createRequest( + hostId: String, + partnerId: String, + userId: String, + aNonce: ByteArray, + date: XMLGregorianCalendar + ): EbicsNpkdRequest { + return EbicsNpkdRequest().apply { + version = "H004" + revision = 1 + header = Header().apply { + authenticate = true + mutable = EmptyMutableHeader() + static = StaticHeaderType().apply { + hostID = hostId + partnerID = partnerId + userID = userId + securityMedium = "0000" + orderDetails = OrderDetails() + orderDetails.orderType = "HPB" + orderDetails.orderAttribute = "DZHNN" + nonce = aNonce + timestamp = date + } + } + body = EmptyBody() + authSignature = SignatureType() + } + } + } +} +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_h004/EbicsRequest.kt b/util/src/main/kotlin/schema/ebics_h004/EbicsRequest.kt @@ -0,0 +1,501 @@ +package tech.libeufin.util.schema.ebics_h004 + +import org.apache.xml.security.binding.xmldsig.SignatureType +import tech.libeufin.util.CryptoUtil +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 +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter +import javax.xml.datatype.XMLGregorianCalendar + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["header", "authSignature", "body"]) +@XmlRootElement(name = "ebicsRequest") +class EbicsRequest { + @get:XmlAttribute(name = "Version", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var version: String + + @get:XmlAttribute(name = "Revision") + var revision: Int? = null + + @get:XmlElement(name = "header", required = true) + lateinit var header: Header + + @get:XmlElement(name = "AuthSignature", required = true) + lateinit var authSignature: SignatureType + + @get:XmlElement(name = "body") + lateinit var body: Body + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["static", "mutable"]) + class Header { + @get:XmlElement(name = "static", required = true) + lateinit var static: StaticHeaderType + + @get:XmlElement(required = true) + lateinit var mutable: MutableHeader + + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "", + propOrder = [ + "hostID", "nonce", "timestamp", "partnerID", "userID", "systemID", + "product", "orderDetails", "bankPubKeyDigests", "securityMedium", + "numSegments", "transactionID" + ] + ) + class StaticHeaderType { + @get:XmlElement(name = "HostID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var hostID: String + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "Nonce", type = String::class) + @get:XmlJavaTypeAdapter(HexBinaryAdapter::class) + @get:XmlSchemaType(name = "hexBinary") + var nonce: ByteArray? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "Timestamp") + @get:XmlSchemaType(name = "dateTime") + var timestamp: XMLGregorianCalendar? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "PartnerID") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + var partnerID: String? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "UserID") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + var userID: String? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "SystemID") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + var systemID: String? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "Product") + var product: EbicsTypes.Product? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "OrderDetails") + var orderDetails: OrderDetails? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "BankPubKeyDigests") + var bankPubKeyDigests: BankPubKeyDigests? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "SecurityMedium") + var securityMedium: String? = null + + /** + * Present only in the initialization phase. + */ + @get:XmlElement(name = "NumSegments") + var numSegments: BigInteger? = null + + /** + * Present only in the transaction / finalization phase. + */ + @get:XmlElement(name = "TransactionID") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + var transactionID: String? = null + } + + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["transactionPhase", "segmentNumber"]) + class MutableHeader { + @get:XmlElement(name = "TransactionPhase", required = true) + @get:XmlSchemaType(name = "token") + lateinit var transactionPhase: EbicsTypes.TransactionPhaseType + + /** + * Number of the currently transmitted segment, if this message + * contains order data. + */ + @get:XmlElement(name = "SegmentNumber") + var segmentNumber: EbicsTypes.SegmentNumber? = null + + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "", + propOrder = ["orderType", "orderID", "orderAttribute", "orderParams"] + ) + class OrderDetails { + @get:XmlElement(name = "OrderType", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var orderType: String + + /** + * Only present if this ebicsRequest is a upload order + * relating to an already existing order. + */ + @get:XmlElement(name = "OrderID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + var orderID: String? = null + + @get:XmlElement(name = "OrderAttribute", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var orderAttribute: String + + /** + * Present only in the initialization phase. + */ + @get:XmlElements( + XmlElement( + name = "StandardOrderParams", + type = StandardOrderParams::class + ) + ) + var orderParams: OrderParams? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(propOrder = ["preValidation", "dataTransfer", "transferReceipt"]) + class Body { + @get:XmlElement(name = "PreValidation") + var preValidation: PreValidation? = null + + @get:XmlElement(name = "DataTransfer") + var dataTransfer: DataTransfer? = null + + @get:XmlElement(name = "TransferReceipt") + var transferReceipt: TransferReceipt? = null + } + + /** + * FIXME: not implemented yet + */ + @XmlAccessorType(XmlAccessType.NONE) + class PreValidation { + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + } + + @XmlAccessorType(XmlAccessType.NONE) + class SignatureData { + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + + @get:XmlValue + var value: ByteArray? = null + } + + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(propOrder = ["dataEncryptionInfo", "signatureData", "orderData", "hostId"]) + class DataTransfer { + + @get:XmlElement(name = "DataEncryptionInfo") + var dataEncryptionInfo: EbicsTypes.DataEncryptionInfo? = null + + @get:XmlElement(name = "SignatureData") + var signatureData: SignatureData? = null + + @get:XmlElement(name = "OrderData") + var orderData: ByteArray? = null + + @get:XmlElement(name = "HostID") + var hostId: String? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["receiptCode"]) + class TransferReceipt { + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + + @get:XmlElement(name = "ReceiptCode") + var receiptCode: Int? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + abstract class OrderParams + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["dateRange"]) + class StandardOrderParams : OrderParams() { + @get:XmlElement(name = "DateRange") + var dateRange: DateRange? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["start", "end"]) + class DateRange { + @get:XmlElement(name = "Start") + lateinit var start: XMLGregorianCalendar + + @get:XmlElement(name = "End") + lateinit var end: XMLGregorianCalendar + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["authentication", "encryption"]) + class BankPubKeyDigests { + @get:XmlElement(name = "Authentication") + lateinit var authentication: EbicsTypes.PubKeyDigest + + @get:XmlElement(name = "Encryption") + lateinit var encryption: EbicsTypes.PubKeyDigest + } + + companion object { + + fun createForDownloadReceiptPhase( + transactionId: String, + hostId: String + + ): 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.RECEIPT + } + } + authSignature = SignatureType() + + body = Body().apply { + transferReceipt = TransferReceipt().apply { + authenticate = true + receiptCode = 0 // always true at this point. + } + } + } + + } + + /* Take a time range (useful for C52 and C53) */ + fun createForDownloadInitializationPhase( + userId: String, + partnerId: String, + hostId: String, + nonceArg: ByteArray, + date: XMLGregorianCalendar, + bankEncPub: RSAPublicKey, + bankAuthPub: RSAPublicKey, + aOrderType: String, + dateStart: XMLGregorianCalendar, + dateEnd: XMLGregorianCalendar + ): EbicsRequest { + + val tmp = createForDownloadInitializationPhase( + userId, + partnerId, + hostId, + nonceArg, + date, + bankEncPub, + bankAuthPub, + aOrderType + ) + + (tmp.header.static.orderDetails?.orderParams as StandardOrderParams).apply { + dateRange?.apply { + start = dateStart + end = dateEnd + } + } + + return tmp + } + + fun createForDownloadInitializationPhase( + userId: String, + partnerId: String, + hostId: String, + nonceArg: ByteArray, + date: XMLGregorianCalendar, + bankEncPub: RSAPublicKey, + bankAuthPub: RSAPublicKey, + aOrderType: String + + ): EbicsRequest { + + return EbicsRequest().apply { + version = "H004" + revision = 1 + authSignature = SignatureType() + body = Body() + header = Header().apply { + authenticate = true + static = StaticHeaderType().apply { + userID = userId + partnerID = partnerId + hostID = hostId + nonce = nonceArg + timestamp = date + partnerID = partnerId + orderDetails = OrderDetails().apply { + orderType = aOrderType + orderAttribute = "DZHNN" + orderParams = StandardOrderParams() + } + bankPubKeyDigests = 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" + } + mutable = MutableHeader().apply { + transactionPhase = + EbicsTypes.TransactionPhaseType.INITIALISATION + } + } + } + } + } + + fun createForUploadInitializationPhase( + cryptoBundle: CryptoUtil.EncryptionResult, + hostId: String, + nonceArg: ByteArray, + partnerId: String, + userId: String, + date: XMLGregorianCalendar, + bankAuthPub: RSAPublicKey, + bankEncPub: RSAPublicKey, + segmentsNumber: BigInteger, + aOrderType: String + ): EbicsRequest { + + return EbicsRequest().apply { + header = Header().apply { + version = "H004" + revision = 1 + authenticate = true + static = StaticHeaderType().apply { + hostID = hostId + nonce = nonceArg + timestamp = date + partnerID = partnerId + userID = userId + orderDetails = OrderDetails().apply { + orderType = aOrderType + orderAttribute = "OZHNN" + orderParams = StandardOrderParams() + } + bankPubKeyDigests = 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 = MutableHeader().apply { + transactionPhase = + EbicsTypes.TransactionPhaseType.INITIALISATION + } + } + authSignature = SignatureType() + body = Body().apply { + dataTransfer = DataTransfer().apply { + signatureData = 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 = Body().apply { + dataTransfer = DataTransfer().apply { + orderData = encryptedData + } + } + } + } + } +} +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_h004/EbicsResponse.kt b/util/src/main/kotlin/schema/ebics_h004/EbicsResponse.kt @@ -0,0 +1,276 @@ +package tech.libeufin.util.schema.ebics_h004 + +import org.apache.xml.security.binding.xmldsig.SignatureType +import tech.libeufin.util.CryptoUtil +import java.math.BigInteger +import javax.xml.bind.annotation.* +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter +import javax.xml.bind.annotation.adapters.NormalizedStringAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["header", "authSignature", "body"]) +@XmlRootElement(name = "ebicsResponse") +class EbicsResponse { + @get:XmlAttribute(name = "Version", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var version: String + + @get:XmlAttribute(name = "Revision") + var revision: Int? = null + + @get:XmlElement(required = true) + lateinit var header: Header + + @get:XmlElement(name = "AuthSignature", required = true) + lateinit var authSignature: SignatureType + + @get:XmlElement(required = true) + lateinit var body: Body + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["_static", "mutable"]) + class Header { + @get:XmlElement(name = "static", required = true) + lateinit var _static: StaticHeaderType + + @get:XmlElement(required = true) + lateinit var mutable: MutableHeaderType + + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["dataTransfer", "returnCode", "timestampBankParameter"]) + class Body { + @get:XmlElement(name = "DataTransfer") + var dataTransfer: DataTransferResponseType? = null + + @get:XmlElement(name = "ReturnCode", required = true) + lateinit var returnCode: ReturnCode + + @get:XmlElement(name = "TimestampBankParameter") + var timestampBankParameter: EbicsTypes.TimestampBankParameter? = null + } + + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "", + propOrder = ["transactionPhase", "segmentNumber", "orderID", "returnCode", "reportText"] + ) + class MutableHeaderType { + @get:XmlElement(name = "TransactionPhase", required = true) + @get:XmlSchemaType(name = "token") + lateinit var transactionPhase: EbicsTypes.TransactionPhaseType + + @get:XmlElement(name = "SegmentNumber") + var segmentNumber: EbicsTypes.SegmentNumber? = null + + @get:XmlElement(name = "OrderID") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + var orderID: String? = null + + @get:XmlElement(name = "ReturnCode", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + lateinit var returnCode: String + + @get:XmlElement(name = "ReportText", required = true) + @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) + @get:XmlSchemaType(name = "normalizedString") + lateinit var reportText: String + } + + @XmlAccessorType(XmlAccessType.NONE) + class OrderData { + @get:XmlValue + lateinit var value: String + } + + @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 = "DataTransferResponseType", propOrder = ["dataEncryptionInfo", "orderData"]) + class DataTransferResponseType { + @get:XmlElement(name = "DataEncryptionInfo") + var dataEncryptionInfo: EbicsTypes.DataEncryptionInfo? = null + + @get:XmlElement(name = "OrderData", required = true) + lateinit var orderData: OrderData + } + + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "ResponseStaticHeaderType", propOrder = ["transactionID", "numSegments"]) + class StaticHeaderType { + @get:XmlElement(name = "TransactionID") + var transactionID: String? = null + + @get:XmlElement(name = "NumSegments") + @get:XmlSchemaType(name = "positiveInteger") + var numSegments: BigInteger? = null + } + + companion object { + fun createForUploadInitializationPhase(transactionID: String, orderID: String): EbicsResponse { + return EbicsResponse().apply { + this.version = "H004" + this.revision = 1 + this.header = Header().apply { + this.authenticate = true + this._static = StaticHeaderType().apply { + this.transactionID = transactionID + } + this.mutable = MutableHeaderType().apply { + this.transactionPhase = + EbicsTypes.TransactionPhaseType.INITIALISATION + this.orderID = orderID + this.reportText = "[EBICS_OK] OK" + this.returnCode = "000000" + } + } + this.authSignature = SignatureType() + this.body = Body().apply { + this.returnCode = ReturnCode().apply { + this.authenticate = true + this.value = "000000" + } + } + } + } + + + fun createForDownloadReceiptPhase(transactionID: String, positiveAck: Boolean): EbicsResponse { + return EbicsResponse().apply { + this.version = "H004" + this.revision = 1 + this.header = Header().apply { + this.authenticate = true + this._static = StaticHeaderType().apply { + this.transactionID = transactionID + } + this.mutable = MutableHeaderType().apply { + this.transactionPhase = + EbicsTypes.TransactionPhaseType.RECEIPT + if (positiveAck) { + this.reportText = "[EBICS_DOWNLOAD_POSTPROCESS_DONE] Received positive receipt" + this.returnCode = "011000" + } else { + this.reportText = "[EBICS_DOWNLOAD_POSTPROCESS_SKIPPED] Received negative receipt" + this.returnCode = "011001" + } + } + } + this.authSignature = SignatureType() + this.body = Body().apply { + this.returnCode = ReturnCode().apply { + this.authenticate = true + this.value = "000000" + } + } + } + } + + + fun createForUploadTransferPhase( + transactionID: String, + segmentNumber: Int, + lastSegment: Boolean, + orderID: String + ): EbicsResponse { + return EbicsResponse().apply { + this.version = "H004" + this.revision = 1 + this.header = Header().apply { + this.authenticate = true + this._static = StaticHeaderType().apply { + this.transactionID = transactionID + } + this.mutable = MutableHeaderType().apply { + this.transactionPhase = + EbicsTypes.TransactionPhaseType.TRANSFER + this.segmentNumber = EbicsTypes.SegmentNumber().apply { + this.value = BigInteger.valueOf(segmentNumber.toLong()) + if (lastSegment) { + this.lastSegment = true + } + } + this.orderID = orderID + this.reportText = "[EBICS_OK] OK" + this.returnCode = "000000" + } + } + this.authSignature = SignatureType() + this.body = Body().apply { + this.returnCode = ReturnCode().apply { + this.authenticate = true + this.value = "000000" + } + } + } + } + + fun createForDownloadInitializationPhase( + transactionID: String, + numSegments: Int, + segmentSize: Int, + enc: CryptoUtil.EncryptionResult, + encodedData: String + ): EbicsResponse { + return EbicsResponse().apply { + this.version = "H004" + this.revision = 1 + this.header = Header().apply { + this.authenticate = true + this._static = StaticHeaderType().apply { + this.transactionID = transactionID + this.numSegments = BigInteger.valueOf(numSegments.toLong()) + } + this.mutable = MutableHeaderType().apply { + this.transactionPhase = + EbicsTypes.TransactionPhaseType.INITIALISATION + this.segmentNumber = EbicsTypes.SegmentNumber().apply { + this.lastSegment = (numSegments == 1) + this.value = BigInteger.valueOf(1) + } + this.reportText = "[EBICS_OK] OK" + this.returnCode = "000000" + } + } + this.authSignature = SignatureType() + this.body = Body().apply { + this.returnCode = ReturnCode().apply { + this.authenticate = true + this.value = "000000" + } + this.dataTransfer = DataTransferResponseType().apply { + this.dataEncryptionInfo = EbicsTypes.DataEncryptionInfo().apply { + this.authenticate = true + this.encryptionPubKeyDigest = EbicsTypes.PubKeyDigest() + .apply { + this.algorithm = "http://www.w3.org/2001/04/xmlenc#sha256" + this.version = "E002" + this.value = enc.pubKeyDigest + } + this.transactionKey = enc.encryptedTransactionKey + } + this.orderData = OrderData().apply { + this.value = encodedData.substring(0, Math.min(segmentSize, encodedData.length)) + } + } + } + } + } + } +} diff --git a/util/src/main/kotlin/schema/ebics_h004/EbicsTypes.kt b/util/src/main/kotlin/schema/ebics_h004/EbicsTypes.kt @@ -0,0 +1,402 @@ +/* + * 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.util.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 +import javax.xml.bind.annotation.adapters.NormalizedStringAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter +import javax.xml.datatype.XMLGregorianCalendar + + +/** + * EBICS type definitions that are shared between other requests / responses / order types. + */ +object EbicsTypes { + /** + * EBICS client product. Identifies the software that accesses the EBICS host. + */ + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "Product", propOrder = ["value"]) + class Product { + @get:XmlValue + @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) + lateinit var value: String + + @get:XmlAttribute(name = "Language", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var language: String + + @get:XmlAttribute(name = "InstituteID") + @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) + 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) + class DataEncryptionInfo { + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + + @get:XmlElement(name = "EncryptionPubKeyDigest", required = true) + lateinit var encryptionPubKeyDigest: PubKeyDigest + + @get:XmlElement(name = "TransactionKey", required = true) + lateinit var transactionKey: ByteArray + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["value"]) + class PubKeyDigest { + /** + * Version of the *digest* of the public key. + */ + @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 + + @get:XmlValue + lateinit var value: ByteArray + } + + @Suppress("UNUSED_PARAMETER") + enum class TransactionPhaseType(value: String) { + @XmlEnumValue("Initialisation") + INITIALISATION("Initialisation"), + + /** + * Auftragsdatentransfer + * + */ + @XmlEnumValue("Transfer") + TRANSFER("Transfer"), + + /** + * Quittungstransfer + * + */ + @XmlEnumValue("Receipt") + RECEIPT("Receipt"); + } + + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "") + class TimestampBankParameter { + @get:XmlValue + lateinit var value: XMLGregorianCalendar + + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + } + + + + @XmlType( + name = "PubKeyValueType", propOrder = [ + "rsaKeyValue", + "timeStamp" + ] + ) + @XmlAccessorType(XmlAccessType.NONE) + class PubKeyValueType { + @get:XmlElement(name = "RSAKeyValue", namespace = "http://www.w3.org/2000/09/xmldsig#", required = true) + lateinit var rsaKeyValue: RSAKeyValueType + + @get:XmlElement(name = "TimeStamp", required = false) + @get:XmlSchemaType(name = "dateTime") + var timeStamp: XMLGregorianCalendar? = null + } + + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "AuthenticationPubKeyInfoType", propOrder = [ + "x509Data", + "pubKeyValue", + "authenticationVersion" + ] + ) + class AuthenticationPubKeyInfoType { + @get:XmlAnyElement() + var x509Data: Element? = null + + @get:XmlElement(name = "PubKeyValue", required = true) + lateinit var pubKeyValue: PubKeyValueType + + @get:XmlElement(name = "AuthenticationVersion", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + lateinit var authenticationVersion: String + } + + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "EncryptionPubKeyInfoType", propOrder = [ + "x509Data", + "pubKeyValue", + "encryptionVersion" + ] + ) + class EncryptionPubKeyInfoType { + @get:XmlAnyElement() + var x509Data: Element? = null + + @get:XmlElement(name = "PubKeyValue", required = true) + lateinit var pubKeyValue: PubKeyValueType + + @get:XmlElement(name = "EncryptionVersion", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + lateinit var encryptionVersion: String + } + + @XmlAccessorType(XmlAccessType.NONE) + class FileFormatType { + @get:XmlAttribute(name = "CountryCode") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var language: String + + @get:XmlValue + @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) + lateinit var value: String + } + + /** + * Generic key-value pair. + */ + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["name", "value"]) + class Parameter { + @get:XmlAttribute(name = "Type", required = true) + lateinit var type: String + + @get:XmlElement(name = "Name", required = true) + lateinit var name: String + + @get:XmlElement(name = "Value", required = true) + lateinit var value: String + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["addressInfo", "bankInfo", "accountInfoList", "orderInfoList"]) + class PartnerInfo { + @get:XmlElement(name = "AddressInfo", required = true) + lateinit var addressInfo: AddressInfo + + @get:XmlElement(name = "BankInfo", required = true) + lateinit var bankInfo: BankInfo + + @get:XmlElement(name = "AccountInfo", type = AccountInfo::class) + var accountInfoList: List<AccountInfo>? = LinkedList<AccountInfo>() + + @get:XmlElement(name = "OrderInfo", type = AuthOrderInfoType::class) + var orderInfoList: List<AuthOrderInfoType> = LinkedList<AuthOrderInfoType>() + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "", + propOrder = ["orderType", "fileFormat", "transferType", "orderFormat", "description", "numSigRequired"] + ) + class AuthOrderInfoType { + @get:XmlElement(name = "OrderType") + lateinit var orderType: String + + @get:XmlElement(name = "FileFormat") + val fileFormat: FileFormatType? = null + + @get:XmlElement(name = "TransferType") + lateinit var transferType: String + + @get:XmlElement(name = "OrderFormat", required = false) + var orderFormat: String? = null + + @get:XmlElement(name = "Description") + lateinit var description: String + + @get:XmlElement(name = "NumSigRequired") + var numSigRequired: Int? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + class UserIDType { + @get:XmlValue + lateinit var value: String; + + @get:XmlAttribute(name = "Status") + var status: Int? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["userID", "name", "permissionList"]) + class UserInfo { + @get:XmlElement(name = "UserID", required = true) + lateinit var userID: UserIDType + + @get:XmlElement(name = "Name") + var name: String? = null + + @get:XmlElement(name = "Permission", type = UserPermission::class) + var permissionList: List<UserPermission>? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["orderTypes", "fileFormat", "accountID", "maxAmount"]) + class UserPermission { + @get:XmlAttribute(name = "AuthorizationLevel") + var authorizationLevel: String? = null + + @get:XmlElement(name = "OrderTypes") + var orderTypes: String? = null + + @get:XmlElement(name = "FileFormat") + val fileFormat: FileFormatType? = null + + @get:XmlElement(name = "AccountID") + val accountID: String? = null + + @get:XmlElement(name = "MaxAmount") + val maxAmount: String? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["name", "street", "postCode", "city", "region", "country"]) + class AddressInfo { + @get:XmlElement(name = "Name") + var name: String? = null + + @get:XmlElement(name = "Street") + var street: String? = null + + @get:XmlElement(name = "PostCode") + var postCode: String? = null + + @get:XmlElement(name = "City") + var city: String? = null + + @get:XmlElement(name = "Region") + var region: String? = null + + @get:XmlElement(name = "Country") + var country: String? = null + } + + + @XmlAccessorType(XmlAccessType.NONE) + class BankInfo { + @get:XmlElement(name = "HostID") + lateinit var hostID: String + + @get:XmlElement(type = Parameter::class) + var parameters: List<Parameter>? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["accountNumberList", "bankCodeList", "accountHolder"]) + class AccountInfo { + @get:XmlAttribute(name = "Currency") + var currency: String? = null + + @get:XmlAttribute(name = "ID") + lateinit var id: String + + @get:XmlAttribute(name = "Description") + var description: String? = null + + @get:XmlElements( + XmlElement(name = "AccountNumber", type = GeneralAccountNumber::class), + XmlElement(name = "NationalAccountNumber", type = NationalAccountNumber::class) + ) + var accountNumberList: List<AbstractAccountNumber>? = null + + @get:XmlElements( + XmlElement(name = "BankCode", type = GeneralBankCode::class), + XmlElement(name = "NationalBankCode", type = NationalBankCode::class) + ) + var bankCodeList: List<AbstractBankCode>? = null + + @get:XmlElement(name = "AccountHolder") + var accountHolder: String? = null + } + + interface AbstractAccountNumber + + @XmlAccessorType(XmlAccessType.NONE) + class GeneralAccountNumber : AbstractAccountNumber { + @get:XmlAttribute(name = "international") + var international: Boolean = false + + @get:XmlValue + lateinit var value: String + } + + @XmlAccessorType(XmlAccessType.NONE) + class NationalAccountNumber : AbstractAccountNumber { + @get:XmlAttribute(name = "format") + lateinit var format: String + + @get:XmlValue + lateinit var value: String + } + + interface AbstractBankCode + + @XmlAccessorType(XmlAccessType.NONE) + class GeneralBankCode : AbstractBankCode { + @get:XmlAttribute(name = "prefix") + var prefix: String? = null + + @get:XmlAttribute(name = "international") + var international: Boolean = false + + @get:XmlValue + lateinit var value: String + } + + @XmlAccessorType(XmlAccessType.NONE) + class NationalBankCode : AbstractBankCode { + @get:XmlValue + lateinit var value: String + + @get:XmlAttribute(name = "format") + lateinit var format: String + } +} +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_h004/EbicsUnsecuredRequest.kt b/util/src/main/kotlin/schema/ebics_h004/EbicsUnsecuredRequest.kt @@ -0,0 +1,223 @@ +package tech.libeufin.util.schema.ebics_h004 + +import org.apache.xml.security.binding.xmldsig.RSAKeyValueType +import tech.libeufin.util.EbicsOrderUtil +import tech.libeufin.util.schema.ebics_s001.SignatureTypes +import java.security.interfaces.RSAPrivateCrtKey +import javax.xml.bind.annotation.* +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["header", "body"]) +@XmlRootElement(name = "ebicsUnsecuredRequest") +class EbicsUnsecuredRequest { + @get:XmlAttribute(name = "Version", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var version: String + + @get:XmlAttribute(name = "Revision") + var revision: Int? = null + + @get:XmlElement(name = "header", required = true) + lateinit var header: Header + + @get:XmlElement(required = true) + lateinit var body: Body + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["static", "mutable"]) + class Header { + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "") + class EmptyMutableHeader + + @get:XmlElement(name = "static", required = true) + lateinit var static: StaticHeaderType + + @get:XmlElement(required = true) + lateinit var mutable: EmptyMutableHeader + + @get:XmlAttribute(name = "authenticate", required = true) + var authenticate: Boolean = false + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["dataTransfer"]) + class Body { + @get:XmlElement(name = "DataTransfer", required = true) + lateinit var dataTransfer: UnsecuredDataTransfer + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["orderData"]) + class UnsecuredDataTransfer { + @get:XmlElement(name = "OrderData", required = true) + lateinit var orderData: OrderData + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "") + class OrderData { + @get:XmlValue + lateinit var value: ByteArray + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "", + propOrder = ["hostID", "partnerID", "userID", "systemID", "product", "orderDetails", "securityMedium"] + ) + class StaticHeaderType { + @get:XmlElement(name = "HostID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var hostID: String + + @get:XmlElement(name = "PartnerID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var partnerID: String + + @get:XmlElement(name = "UserID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var userID: String + + @get:XmlElement(name = "SystemID") + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + var systemID: String? = null + + @get:XmlElement(name = "Product") + val product: EbicsTypes.Product? = null + + @get:XmlElement(name = "OrderDetails", required = true) + lateinit var orderDetails: OrderDetails + + @get:XmlElement(name = "SecurityMedium", required = true) + lateinit var securityMedium: String + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["orderType", "orderAttribute"]) + class OrderDetails { + @get:XmlElement(name = "OrderType", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var orderType: String + + @get:XmlElement(name = "OrderAttribute", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var orderAttribute: String + } + + companion object { + + fun createHia( + hostId: String, + userId: String, + partnerId: String, + authKey: RSAPrivateCrtKey, + encKey: RSAPrivateCrtKey + + ): EbicsUnsecuredRequest { + + return EbicsUnsecuredRequest().apply { + + version = "H004" + revision = 1 + header = Header().apply { + authenticate = true + static = StaticHeaderType().apply { + orderDetails = OrderDetails().apply { + orderAttribute = "DZNNN" + orderType = "HIA" + securityMedium = "0000" + hostID = hostId + userID = userId + partnerID = partnerId + } + } + mutable = Header.EmptyMutableHeader() + } + body = Body().apply { + dataTransfer = UnsecuredDataTransfer().apply { + orderData = OrderData().apply { + value = EbicsOrderUtil.encodeOrderDataXml( + HIARequestOrderData().apply { + authenticationPubKeyInfo = EbicsTypes.AuthenticationPubKeyInfoType() + .apply { + pubKeyValue = EbicsTypes.PubKeyValueType().apply { + rsaKeyValue = RSAKeyValueType().apply { + exponent = authKey.publicExponent.toByteArray() + modulus = authKey.modulus.toByteArray() + } + } + authenticationVersion = "X002" + } + encryptionPubKeyInfo = EbicsTypes.EncryptionPubKeyInfoType() + .apply { + pubKeyValue = EbicsTypes.PubKeyValueType().apply { + rsaKeyValue = RSAKeyValueType().apply { + exponent = encKey.publicExponent.toByteArray() + modulus = encKey.modulus.toByteArray() + } + } + encryptionVersion = "E002" + + } + partnerID = partnerId + userID = userId + } + ) + } + } + } + } + } + + fun createIni( + hostId: String, + userId: String, + partnerId: String, + signKey: RSAPrivateCrtKey + + ): EbicsUnsecuredRequest { + return EbicsUnsecuredRequest().apply { + version = "H004" + revision = 1 + header = Header().apply { + authenticate = true + static = StaticHeaderType().apply { + orderDetails = OrderDetails().apply { + orderAttribute = "DZNNN" + orderType = "INI" + securityMedium = "0000" + hostID = hostId + userID = userId + partnerID = partnerId + } + } + mutable = Header.EmptyMutableHeader() + } + body = Body().apply { + dataTransfer = UnsecuredDataTransfer().apply { + orderData = OrderData().apply { + value = EbicsOrderUtil.encodeOrderDataXml( + SignatureTypes.SignaturePubKeyOrderData().apply { + signaturePubKeyInfo = SignatureTypes.SignaturePubKeyInfoType().apply { + signatureVersion = "A006" + pubKeyValue = SignatureTypes.PubKeyValueType().apply { + rsaKeyValue = org.apache.xml.security.binding.xmldsig.RSAKeyValueType().apply { + exponent = signKey.publicExponent.toByteArray() + modulus = signKey.modulus.toByteArray() + } + } + } + userID = userId + partnerID = partnerId + } + ) + } + } + } + } + } + } +} diff --git a/util/src/main/kotlin/schema/ebics_h004/HIARequestOrderData.kt b/util/src/main/kotlin/schema/ebics_h004/HIARequestOrderData.kt @@ -0,0 +1,33 @@ +package tech.libeufin.util.schema.ebics_h004 + +import javax.xml.bind.annotation.* +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter + + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType( + name = "HIARequestOrderDataType", + propOrder = ["authenticationPubKeyInfo", "encryptionPubKeyInfo", "partnerID", "userID", "any"] +) +@XmlRootElement(name = "HIARequestOrderData") +class HIARequestOrderData { + @get:XmlElement(name = "AuthenticationPubKeyInfo", required = true) + lateinit var authenticationPubKeyInfo: EbicsTypes.AuthenticationPubKeyInfoType + + @get:XmlElement(name = "EncryptionPubKeyInfo", required = true) + lateinit var encryptionPubKeyInfo: EbicsTypes.EncryptionPubKeyInfoType + + @get:XmlElement(name = "PartnerID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + lateinit var partnerID: String + + @get:XmlElement(name = "UserID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + lateinit var userID: String + + @get:XmlAnyElement(lax = true) + var any: List<Any>? = null +} +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_h004/HKDResponseOrderData.kt b/util/src/main/kotlin/schema/ebics_h004/HKDResponseOrderData.kt @@ -0,0 +1,15 @@ +package tech.libeufin.util.schema.ebics_h004 + +import java.security.Permission +import javax.xml.bind.annotation.* + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["partnerInfo", "userInfoList"]) +@XmlRootElement(name = "HTDResponseOrderData") +class HKDResponseOrderData { + @get:XmlElement(name = "PartnerInfo", required = true) + lateinit var partnerInfo: EbicsTypes.PartnerInfo + + @get:XmlElement(name = "UserInfo", type = EbicsTypes.UserInfo::class, required = true) + lateinit var userInfoList: List<EbicsTypes.UserInfo> +} diff --git a/util/src/main/kotlin/schema/ebics_h004/HPBResponseOrderData.kt b/util/src/main/kotlin/schema/ebics_h004/HPBResponseOrderData.kt @@ -0,0 +1,21 @@ +package tech.libeufin.util.schema.ebics_h004 + +import javax.xml.bind.annotation.* +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter + + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["authenticationPubKeyInfo", "encryptionPubKeyInfo", "hostID"]) +@XmlRootElement(name = "HPBResponseOrderData") +class HPBResponseOrderData { + @get:XmlElement(name = "AuthenticationPubKeyInfo", required = true) + lateinit var authenticationPubKeyInfo: EbicsTypes.AuthenticationPubKeyInfoType + + @get:XmlElement(name = "EncryptionPubKeyInfo", required = true) + lateinit var encryptionPubKeyInfo: EbicsTypes.EncryptionPubKeyInfoType + + @get:XmlElement(name = "HostID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var hostID: String +} +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_h004/HTDResponseOrderData.kt b/util/src/main/kotlin/schema/ebics_h004/HTDResponseOrderData.kt @@ -0,0 +1,15 @@ +package tech.libeufin.util.schema.ebics_h004 + +import java.security.Permission +import javax.xml.bind.annotation.* + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType(name = "", propOrder = ["partnerInfo", "userInfo"]) +@XmlRootElement(name = "HTDResponseOrderData") +class HTDResponseOrderData { + @get:XmlElement(name = "PartnerInfo", required = true) + lateinit var partnerInfo: EbicsTypes.PartnerInfo + + @get:XmlElement(name = "UserInfo", required = true) + lateinit var userInfo: EbicsTypes.UserInfo +} diff --git a/util/src/main/kotlin/schema/ebics_h004/package-info.java b/util/src/main/kotlin/schema/ebics_h004/package-info.java @@ -0,0 +1,13 @@ +/** + * This package-info.java file defines the default namespace for the JAXB bindings + * defined in the package. + */ + +@XmlSchema( + namespace = "urn:org:ebics:H004", + elementFormDefault = XmlNsForm.QUALIFIED +) +package schema.ebics_h004; + +import javax.xml.bind.annotation.XmlNsForm; +import javax.xml.bind.annotation.XmlSchema; +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_hev/EbicsMessages.kt b/util/src/main/kotlin/schema/ebics_hev/EbicsMessages.kt @@ -0,0 +1,81 @@ +/* + * 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.util.schema.ebics_hev + +import javax.xml.bind.annotation.* +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter +import javax.xml.bind.annotation.adapters.NormalizedStringAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType( + name = "HEVResponseDataType", + propOrder = ["systemReturnCode", "versionNumber", "any"] +) +@XmlRootElement(name = "ebicsHEVResponse") +class HEVResponse { + @get:XmlElement(name = "SystemReturnCode", required = true) + lateinit var systemReturnCode: SystemReturnCodeType + + @get:XmlElement(name = "VersionNumber", namespace = "http://www.ebics.org/H000") + var versionNumber: List<VersionNumber>? = null + + @get:XmlAnyElement(lax = true) + var any: List<Any>? = null + + @XmlAccessorType(XmlAccessType.NONE) + class VersionNumber { + @get:XmlValue + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var value: String + + @get:XmlAttribute(name = "ProtocolVersion", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var protocolVersion: String + + companion object { + fun create(protocolVersion: String, versionNumber: String): VersionNumber { + return VersionNumber().apply { + this.protocolVersion = protocolVersion + this.value = versionNumber + } + } + } + } +} + + +@XmlAccessorType(XmlAccessType.NONE) +@XmlType( + name = "SystemReturnCodeType", + propOrder = [ + "returnCode", + "reportText" + ] +) +class SystemReturnCodeType { + @get:XmlElement(name = "ReturnCode", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var returnCode: String + + @get:XmlElement(name = "ReportText", required = true) + @get:XmlJavaTypeAdapter(NormalizedStringAdapter::class) + lateinit var reportText: String +} diff --git a/util/src/main/kotlin/schema/ebics_hev/package-info.java b/util/src/main/kotlin/schema/ebics_hev/package-info.java @@ -0,0 +1,13 @@ +/** + * This package-info.java file defines the default namespace for the JAXB bindings + * defined in the package. + */ + +@XmlSchema( + namespace = "http://www.ebics.org/H000", + elementFormDefault = XmlNsForm.QUALIFIED +) +package schema.ebics_hev; + +import javax.xml.bind.annotation.XmlNsForm; +import javax.xml.bind.annotation.XmlSchema; diff --git a/util/src/main/kotlin/schema/ebics_s001/SignatureTypes.kt b/util/src/main/kotlin/schema/ebics_s001/SignatureTypes.kt @@ -0,0 +1,92 @@ +/* + * 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.util.schema.ebics_s001 + +import org.apache.xml.security.binding.xmldsig.RSAKeyValueType +import org.apache.xml.security.binding.xmldsig.X509DataType +import javax.xml.bind.annotation.* +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter +import javax.xml.datatype.XMLGregorianCalendar + + +object SignatureTypes { + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "PubKeyValueType", namespace = "http://www.ebics.org/S001", propOrder = [ + "rsaKeyValue", + "timeStamp" + ] + ) + class PubKeyValueType { + @get:XmlElement(name = "RSAKeyValue", namespace = "http://www.w3.org/2000/09/xmldsig#", required = true) + lateinit var rsaKeyValue: RSAKeyValueType + + @get:XmlElement(name = "TimeStamp") + @get:XmlSchemaType(name = "dateTime") + var timeStamp: XMLGregorianCalendar? = null + } + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "", + propOrder = [ + "x509Data", + "pubKeyValue", + "signatureVersion" + ] + ) + class SignaturePubKeyInfoType { + @get:XmlElement(name = "X509Data") + var x509Data: X509DataType? = null + + @get:XmlElement(name = "PubKeyValue", required = true) + lateinit var pubKeyValue: PubKeyValueType + + @get:XmlElement(name = "SignatureVersion", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + lateinit var signatureVersion: String + } + + /** + * EBICS INI payload. + */ + @XmlAccessorType(XmlAccessType.NONE) + @XmlType( + name = "", + propOrder = ["signaturePubKeyInfo", "partnerID", "userID"] + ) + @XmlRootElement(name = "SignaturePubKeyOrderData") + class SignaturePubKeyOrderData { + @get:XmlElement(name = "SignaturePubKeyInfo", required = true) + lateinit var signaturePubKeyInfo: SignaturePubKeyInfoType + + @get:XmlElement(name = "PartnerID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + lateinit var partnerID: String + + @get:XmlElement(name = "UserID", required = true) + @get:XmlJavaTypeAdapter(CollapsedStringAdapter::class) + @get:XmlSchemaType(name = "token") + lateinit var userID: String + } +} +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_s001/UserSignatureData.kt b/util/src/main/kotlin/schema/ebics_s001/UserSignatureData.kt @@ -0,0 +1,27 @@ +package tech.libeufin.util.schema.ebics_s001 + +import javax.xml.bind.annotation.* + +@XmlAccessorType(XmlAccessType.NONE) +@XmlRootElement(name = "UserSignatureData") +@XmlType(name = "", propOrder = ["orderSignatureList"]) +class UserSignatureData { + @XmlElement(name = "OrderSignatureData", type = OrderSignatureData::class) + var orderSignatureList: List<OrderSignatureData>? = null + + @XmlAccessorType(XmlAccessType.NONE) + @XmlType(name = "", propOrder = ["signatureVersion", "signatureValue", "partnerID", "userID"]) + class OrderSignatureData { + @XmlElement(name = "SignatureVersion") + lateinit var signatureVersion: String + + @XmlElement(name = "SignatureValue") + lateinit var signatureValue: ByteArray + + @XmlElement(name = "PartnerID") + lateinit var partnerID: String + + @XmlElement(name = "UserID") + lateinit var userID: String + } +} +\ No newline at end of file diff --git a/util/src/main/kotlin/schema/ebics_s001/package-info.java b/util/src/main/kotlin/schema/ebics_s001/package-info.java @@ -0,0 +1,13 @@ +/** + * This package-info.java file defines the default namespace for the JAXB bindings + * defined in the package. + */ + +@XmlSchema( + namespace = "http://www.ebics.org/S001", + elementFormDefault = XmlNsForm.QUALIFIED +) +package schema.ebics_s001; + +import javax.xml.bind.annotation.XmlNsForm; +import javax.xml.bind.annotation.XmlSchema; +\ No newline at end of file