[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: refactor, partially implement order up
From: |
gnunet |
Subject: |
[libeufin] branch master updated: refactor, partially implement order upload |
Date: |
Fri, 08 Nov 2019 12:44:46 +0100 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository libeufin.
The following commit(s) were added to refs/heads/master by this push:
new ebeb475 refactor, partially implement order upload
ebeb475 is described below
commit ebeb47583ae9f5c612bf3412545a4cddda574826
Author: Florian Dold <address@hidden>
AuthorDate: Fri Nov 8 12:42:20 2019 +0100
refactor, partially implement order upload
---
.idea/codeStyles/Project.xml | 1 -
.../kotlin/tech/libeufin/sandbox/CryptoUtil.kt | 236 +++++----
.../src/main/kotlin/tech/libeufin/sandbox/DB.kt | 50 +-
.../kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt | 78 ++-
.../sandbox/{Main.kt => EbicsProtocolBackend.kt} | 463 +++++++----------
.../src/main/kotlin/tech/libeufin/sandbox/Main.kt | 558 +--------------------
sandbox/src/test/kotlin/CryptoUtilTest.kt | 2 +-
sandbox/src/test/kotlin/EbicsOrderUtilTest.kt | 16 +
8 files changed, 408 insertions(+), 996 deletions(-)
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index e40fad6..1bec35e 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -5,7 +5,6 @@
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
- <option name="WRAP_ON_TYPING" value="1" />
</codeStyleSettings>
</code_scheme>
</component>
\ No newline at end of file
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt
index 93be1be..b3ca595 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/CryptoUtil.kt
@@ -21,10 +21,10 @@ package tech.libeufin.sandbox
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.io.ByteArrayOutputStream
-import java.lang.Exception
import java.math.BigInteger
import java.security.KeyFactory
import java.security.KeyPairGenerator
+import java.security.MessageDigest
import java.security.interfaces.RSAPrivateCrtKey
import java.security.interfaces.RSAPublicKey
import java.security.spec.PKCS8EncodedKeySpec
@@ -32,20 +32,18 @@ import java.security.spec.RSAPublicKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
import javax.crypto.KeyGenerator
-import java.security.MessageDigest
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
-
-/**
- * RSA key pair.
- */
-data class RsaCrtKeyPair(val private: RSAPrivateCrtKey, val public:
RSAPublicKey)
-
/**
* Helpers for dealing with cryptographic operations in EBICS / LibEuFin.
*/
-class CryptoUtil {
+object CryptoUtil {
+
+ /**
+ * RSA key pair.
+ */
+ data class RsaCrtKeyPair(val private: RSAPrivateCrtKey, val public:
RSAPublicKey)
class EncryptionResult(
val encryptedTransactionKey: ByteArray,
@@ -53,116 +51,114 @@ class CryptoUtil {
val encryptedData: ByteArray
)
- companion object {
- 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())
- }
-
- /**
- * Encrypt data according to the EBICS E002 encryption process.
- */
- fun encryptEbicsE002(data: ByteArray, encryptionPublicKey:
RSAPublicKey): EncryptionResult {
- val keygen = KeyGenerator.getInstance("AES", bouncyCastleProvider)
- keygen.init(128)
- val transactionKey = keygen.generateKey()
- 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)
- }
-
- fun decryptEbicsE002(enc: EncryptionResult, privateKey:
RSAPrivateCrtKey): ByteArray {
- val asymmetricCipher = Cipher.getInstance("RSA/None/PKCS1Padding",
bouncyCastleProvider)
- asymmetricCipher.init(Cipher.DECRYPT_MODE, privateKey)
- val transactionKeyBytes =
asymmetricCipher.doFinal(enc.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)
- val data = symmetricCipher.doFinal(enc.encryptedData)
- return data
- }
+ 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())
+ }
+
+ /**
+ * Encrypt data according to the EBICS E002 encryption process.
+ */
+ fun encryptEbicsE002(data: ByteArray, encryptionPublicKey: RSAPublicKey):
EncryptionResult {
+ val keygen = KeyGenerator.getInstance("AES", bouncyCastleProvider)
+ keygen.init(128)
+ val transactionKey = keygen.generateKey()
+ 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)
+ }
+
+ fun decryptEbicsE002(enc: EncryptionResult, privateKey: RSAPrivateCrtKey):
ByteArray {
+ val asymmetricCipher = Cipher.getInstance("RSA/None/PKCS1Padding",
bouncyCastleProvider)
+ asymmetricCipher.init(Cipher.DECRYPT_MODE, privateKey)
+ val transactionKeyBytes =
asymmetricCipher.doFinal(enc.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)
+ val data = symmetricCipher.doFinal(enc.encryptedData)
+ return data
}
}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
index 8e1d678..b4cb2f6 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/DB.kt
@@ -20,7 +20,9 @@
package tech.libeufin.sandbox
import org.jetbrains.exposed.dao.*
-import org.jetbrains.exposed.sql.*
+import org.jetbrains.exposed.sql.Database
+import org.jetbrains.exposed.sql.ReferenceOption
+import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import java.sql.Blob
@@ -29,6 +31,7 @@ const val EBICS_HOST_ID_MAX_LENGTH = 10
const val EBICS_USER_ID_MAX_LENGTH = 10
const val EBICS_PARTNER_ID_MAX_LENGTH = 10
const val EBICS_SYSTEM_ID_MAX_LENGTH = 10
+
/**
* All the states to give a subscriber.
*/
@@ -90,7 +93,7 @@ fun Blob.toByteArray(): ByteArray {
* This table information *not* related to EBICS, for all
* its customers.
*/
-object BankCustomersTable: IntIdTable() {
+object BankCustomersTable : IntIdTable() {
// Customer ID is the default 'id' field provided by the constructor.
val name = varchar("name", CUSTOMER_NAME_MAX_LENGTH).primaryKey()
val ebicsSubscriber = reference("ebicsSubscriber", EbicsSubscribersTable)
@@ -118,6 +121,7 @@ object EbicsSubscriberPublicKeysTable : IntIdTable() {
*/
class EbicsSubscriberPublicKeyEntity(id: EntityID<Int>) : IntEntity(id) {
companion object :
IntEntityClass<EbicsSubscriberPublicKeyEntity>(EbicsSubscriberPublicKeysTable)
+
var rsaPublicKey by EbicsSubscriberPublicKeysTable.rsaPublicKey
var state by EbicsSubscriberPublicKeysTable.state
}
@@ -134,6 +138,7 @@ object EbicsHostsTable : IntIdTable() {
class EbicsHostEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<EbicsHostEntity>(EbicsHostsTable)
+
var hostId by EbicsHostsTable.hostID
var ebicsVersion by EbicsHostsTable.ebicsVersion
var signaturePrivateKey by EbicsHostsTable.signaturePrivateKey
@@ -203,6 +208,47 @@ class EbicsDownloadTransactionEntity(id: EntityID<String>)
: Entity<String>(id)
}
+object EbicsUploadTransactionsTable : IdTable<String>() {
+ override val id = text("transactionID").entityId()
+ val orderType = text("orderType")
+ val orderID = text("orderID")
+ val host = reference("host", EbicsHostsTable)
+ val subscriber = reference("subscriber", EbicsSubscribersTable)
+ val numSegments = integer("numSegments")
+ val lastSeenSegment = integer("lastSeenSegment")
+ val transactionKeyEnc = blob("transactionKeyEnc")
+}
+
+
+class EbicsUploadTransactionEntity(id: EntityID<String>) : Entity<String>(id) {
+ companion object : EntityClass<String,
EbicsUploadTransactionEntity>(EbicsUploadTransactionsTable)
+
+ var orderType by EbicsUploadTransactionsTable.orderType
+ var orderID by EbicsUploadTransactionsTable.orderID
+ var host by EbicsHostEntity referencedOn EbicsUploadTransactionsTable.host
+ var subscriber by EbicsSubscriberEntity referencedOn
EbicsUploadTransactionsTable.subscriber
+ var numSegments by EbicsUploadTransactionsTable.numSegments
+ var lastSeenSegment by EbicsUploadTransactionsTable.lastSeenSegment
+ var transactionKeyEnc by EbicsDownloadTransactionsTable.transactionKeyEnc
+}
+
+
+object EbicsUploadTransactionChunksTable : IdTable<String>() {
+ override val id =
+
text("transactionID").entityId().references(EbicsUploadTransactionsTable.id,
ReferenceOption.CASCADE)
+ val chunkIndex = integer("chunkIndex")
+ val chunkContent = blob("chunkContent")
+}
+
+
+class EbicsUploadTransactionChunkEntity(id : EntityID<String>):
Entity<String>(id) {
+ companion object : EntityClass<String,
EbicsUploadTransactionChunkEntity>(EbicsUploadTransactionChunksTable)
+
+ var chunkIndex by EbicsUploadTransactionChunksTable.chunkIndex
+ var chunkContent by EbicsUploadTransactionChunksTable.chunkContent
+}
+
+
fun dbCreateTables() {
Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver =
"org.h2.Driver")
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt
index 5652011..7fcd812 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsOrderUtil.kt
@@ -19,6 +19,7 @@
package tech.libeufin.sandbox
+import java.lang.IllegalArgumentException
import java.security.SecureRandom
import java.util.zip.DeflaterInputStream
import java.util.zip.InflaterInputStream
@@ -26,36 +27,63 @@ import java.util.zip.InflaterInputStream
/**
* Helpers for dealing with order compression, encryption, decryption,
chunking and re-assembly.
*/
-class EbicsOrderUtil private constructor() {
- companion object {
- 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
- }
+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()
- }
+ 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()
- }
+ fun generateTransactionId(): String {
+ val rng = SecureRandom()
+ val res = ByteArray(16)
+ rng.nextBytes(res)
+ return res.toHexString()
+ }
+
+ /**
+ * 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
+ }
- /**
- * 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
+ 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/Main.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
similarity index 61%
copy from sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
copy to sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
index 435a4e8..cd6a922 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt
@@ -17,48 +17,28 @@
* <http://www.gnu.org/licenses/>
*/
+
package tech.libeufin.sandbox
import io.ktor.application.ApplicationCall
-import io.ktor.application.ApplicationCallPipeline
-import io.ktor.application.call
-import io.ktor.application.install
-import io.ktor.features.CallLogging
-import io.ktor.features.ContentNegotiation
-import io.ktor.features.StatusPages
-import io.ktor.gson.gson
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
-import io.ktor.request.receive
import io.ktor.request.receiveText
-import io.ktor.request.uri
import io.ktor.response.respond
import io.ktor.response.respondText
-import io.ktor.routing.get
-import io.ktor.routing.post
-import io.ktor.routing.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.and
import org.jetbrains.exposed.sql.transactions.transaction
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
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.SignaturePubKeyOrderData
import java.math.BigInteger
-import java.security.interfaces.RSAPublicKey
-import java.text.DateFormat
import java.util.*
import java.util.zip.DeflaterInputStream
import javax.sql.rowset.serial.SerialBlob
-import javax.xml.bind.JAXBContext
-val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
open class EbicsRequestError(val errorText: String, val errorCode: String) :
Exception("EBICS request management error: $errorText ($errorCode)")
@@ -68,9 +48,13 @@ class EbicsInvalidRequestError :
EbicsRequestError("[EBICS_INVALID_REQUEST] Inva
open class EbicsKeyManagementError(val errorText: String, val errorCode:
String) :
Exception("EBICS key management error: $errorText ($errorCode)")
-class EbicsInvalidXmlError : EbicsKeyManagementError("[EBICS_INVALID_XML]",
"091010")
+private class EbicsInvalidXmlError :
EbicsKeyManagementError("[EBICS_INVALID_XML]", "091010")
+
+private class EbicsInvalidOrderType : EbicsRequestError(
+ "[EBICS_UNSUPPORTED_ORDER_TYPE] Order type not supported",
+ "091005"
+)
-class EbicsInvalidOrderType :
EbicsRequestError("[EBICS_UNSUPPORTED_ORDER_TYPE] Order type not supported",
"091005")
private suspend fun ApplicationCall.respondEbicsKeyManagement(
errorText: String,
@@ -121,41 +105,6 @@ private suspend fun
ApplicationCall.respondEbicsKeyManagement(
}
-fun findEbicsSubscriber(partnerID: String, userID: String, systemID: String?):
EbicsSubscriberEntity? {
- return if (systemID == null) {
- EbicsSubscriberEntity.find {
- (EbicsSubscribersTable.partnerId eq partnerID) and
(EbicsSubscribersTable.userId eq userID)
- }
- } else {
- EbicsSubscriberEntity.find {
- (EbicsSubscribersTable.partnerId eq partnerID) and
- (EbicsSubscribersTable.userId eq userID) and
- (EbicsSubscribersTable.systemId eq systemID)
- }
- }.firstOrNull()
-}
-
-
-data class Subscriber(
- val partnerID: String,
- val userID: String,
- val systemID: String?
-)
-
-data class SubscriberKeys(
- val authenticationPublicKey: RSAPublicKey,
- val encryptionPublicKey: RSAPublicKey,
- val signaturePublicKey: RSAPublicKey
-)
-
-
-data class EbicsHostInfo(
- val hostID: String,
- val encryptionPublicKey: RSAPublicKey,
- val authenticationPublicKey: RSAPublicKey
-)
-
-
private suspend fun ApplicationCall.handleEbicsHia(header:
EbicsUnsecuredRequest.Header, orderData: ByteArray) {
val keyObject =
EbicsOrderUtil.decodeOrderDataXml<HIARequestOrderData>(orderData)
val encPubXml = keyObject.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue
@@ -214,7 +163,7 @@ private suspend fun ApplicationCall.handleEbicsIni(header:
EbicsUnsecuredRequest
}
private suspend fun ApplicationCall.handleEbicsHpb(
- ebicsHostInfo: EbicsHostInfo,
+ ebicsHostInfo: EbicsHostPublicInfo,
requestDocument: Document,
header: EbicsNpkdRequest.Header
) {
@@ -274,7 +223,7 @@ private suspend fun ApplicationCall.handleEbicsHpb(
/**
* Find the ebics host corresponding to the one specified in the header.
*/
-private fun ApplicationCall.ensureEbicsHost(requestHostID: String):
EbicsHostInfo {
+private fun ApplicationCall.ensureEbicsHost(requestHostID: String):
EbicsHostPublicInfo {
return transaction {
val ebicsHost = EbicsHostEntity.find { EbicsHostsTable.hostID eq
requestHostID }.firstOrNull()
if (ebicsHost == null) {
@@ -283,7 +232,7 @@ private fun ApplicationCall.ensureEbicsHost(requestHostID:
String): EbicsHostInf
}
val encryptionPrivateKey =
CryptoUtil.loadRsaPrivateKey(ebicsHost.encryptionPrivateKey.toByteArray())
val authenticationPrivateKey =
CryptoUtil.loadRsaPrivateKey(ebicsHost.authenticationPrivateKey.toByteArray())
- EbicsHostInfo(
+ EbicsHostPublicInfo(
requestHostID,
CryptoUtil.getRsaPublicFromPrivate(encryptionPrivateKey),
CryptoUtil.getRsaPublicFromPrivate(authenticationPrivateKey)
@@ -303,13 +252,6 @@ private suspend fun ApplicationCall.receiveEbicsXml():
Document {
}
-inline fun <reified T> Document.toObject(): T {
- val jc = JAXBContext.newInstance(T::class.java)
- val m = jc.createUnmarshaller()
- return m.unmarshal(this, T::class.java).value
-}
-
-
fun handleEbicsHtd(): ByteArray {
val htd = HTDResponseOrderData().apply {
this.partnerInfo = HTDResponseOrderData.PartnerInfo().apply {
@@ -331,6 +273,24 @@ fun handleEbicsHtd(): ByteArray {
this.value = "INGDDEFFXXX"
}
)
+ },
+ HTDResponseOrderData.AccountInfo().apply {
+ this.id = "glsdemo"
+ this.accountHolder = "Mina Musterfrau"
+ this.accountNumberList = listOf(
+ HTDResponseOrderData.GeneralAccountNumber().apply {
+ this.international = true
+ this.value = "DE91430609670123123123"
+ }
+ )
+ this.currency = "EUR"
+ this.description = "glsdemoacct"
+ this.bankCodeList = listOf(
+ HTDResponseOrderData.GeneralBankCode().apply {
+ this.international = true
+ this.value = "GENODEM1GLS"
+ }
+ )
}
)
this.addressInfo = HTDResponseOrderData.AddressInfo().apply {
@@ -427,11 +387,6 @@ fun createEbicsResponseForDownloadInitializationPhase(
}
-fun createEbicsResponseForDownloadTransferPhase() {
-
-}
-
-
fun createEbicsResponseForDownloadReceiptPhase(transactionID: String,
positiveAck: Boolean): EbicsResponse {
return EbicsResponse().apply {
this.version = "H004"
@@ -447,7 +402,7 @@ fun
createEbicsResponseForDownloadReceiptPhase(transactionID: String, positiveAc
this.reportText = "[EBICS_DOWNLOAD_POSTPROCESS_DONE]
Received positive receipt"
this.returnCode = "011000"
} else {
- this.reportText = "[EBICS_DOWNLOAD_POSTPROCESS_DONE]
Received negative receipt"
+ this.reportText = "[EBICS_DOWNLOAD_POSTPROCESS_SKIPPED]
Received negative receipt"
this.returnCode = "011001"
}
}
@@ -462,25 +417,34 @@ fun
createEbicsResponseForDownloadReceiptPhase(transactionID: String, positiveAc
}
}
-
-private suspend fun ApplicationCall.handleEbicsDownloadInitialization() {
-
-}
-
-private suspend fun ApplicationCall.handleEbicsDownloadTransfer() {
-
-}
-
-private suspend fun ApplicationCall.handleEbicsDownloadReceipt() {
-
-}
-
-private suspend fun ApplicationCall.handleEbicsUploadInitialization() {
-
+fun createEbicsResponseForUploadInitializationPhase(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"
+ }
+ }
+ }
}
-private suspend fun ApplicationCall.ebicsweb() {
+suspend fun ApplicationCall.ebicsweb() {
val requestDocument = receiveEbicsXml()
logger.info("Processing ${requestDocument.documentElement.localName}")
@@ -523,114 +487,131 @@ private suspend fun ApplicationCall.ebicsweb() {
println("ebicsRequest
${XMLUtil.convertDomToString(requestDocument)}")
val requestObject = requestDocument.toObject<EbicsRequest>()
val staticHeader = requestObject.header.static
+ val requestedHostId = staticHeader.hostID
+
+ val responseXmlStr = transaction {
+ // Step 1 of 3: Get information about the host and subscriber
+
+ val ebicsHost = EbicsHostEntity.find { EbicsHostsTable.hostID
eq requestedHostId }.firstOrNull()
+ val requestTransactionID =
requestObject.header.static.transactionID
+ var downloadTransaction: EbicsDownloadTransactionEntity? = null
+ var uploadTransaction: EbicsUploadTransactionEntity? = null
+ val subscriber = if (requestTransactionID != null) {
+ downloadTransaction =
EbicsDownloadTransactionEntity.findById(requestTransactionID)
+ if (downloadTransaction != null) {
+ downloadTransaction.subscriber
+ } else {
+ uploadTransaction =
EbicsUploadTransactionEntity.findById(requestTransactionID)
+ uploadTransaction?.subscriber
+ }
+ } else {
+ val partnerID = staticHeader.partnerID ?: throw
EbicsInvalidRequestError()
+ val userID = staticHeader.userID ?: throw
EbicsInvalidRequestError()
+ findEbicsSubscriber(partnerID, userID,
staticHeader.systemID)
+ }
- when (requestObject.header.mutable.transactionPhase) {
- EbicsTypes.TransactionPhaseType.INITIALISATION -> {
- val partnerID = staticHeader.partnerID ?: throw
EbicsInvalidXmlError()
- val userID = staticHeader.userID ?: throw
EbicsInvalidXmlError()
- val respText = transaction {
- val subscriber =
- findEbicsSubscriber(partnerID, userID,
staticHeader.systemID)
- ?: throw EbicsInvalidXmlError()
- val requestedHostId =
requestObject.header.static.hostID
- val ebicsHost = EbicsHostEntity.find {
EbicsHostsTable.hostID eq requestedHostId }.firstOrNull()
- if (ebicsHost == null)
- throw EbicsInvalidRequestError()
- val hostAuthPriv = CryptoUtil.loadRsaPrivateKey(
- ebicsHost.authenticationPrivateKey
- .toByteArray()
- )
- val clientAuthPub =
-
CryptoUtil.loadRsaPublicKey(subscriber.authenticationKey!!.rsaPublicKey.toByteArray())
- val clientEncPub =
-
CryptoUtil.loadRsaPublicKey(subscriber.encryptionKey!!.rsaPublicKey.toByteArray())
- val verifyResult =
XMLUtil.verifyEbicsDocument(requestDocument, clientAuthPub)
- println("ebicsRequest verification result:
$verifyResult")
- val transactionID =
EbicsOrderUtil.generateTransactionId()
- val orderType =
requestObject.header.static.orderDetails?.orderType
-
- val response = when (orderType) {
- "HTD" -> handleEbicsHtd()
- else -> throw EbicsInvalidXmlError()
- }
-
- val compressedResponse =
DeflaterInputStream(response.inputStream()).use {
- it.readAllBytes()
- }
-
- val enc =
CryptoUtil.encryptEbicsE002(compressedResponse, clientEncPub)
- val encodedResponse =
Base64.getEncoder().encodeToString(enc.encryptedData)
-
- val segmentSize = 4096
- val totalSize = encodedResponse.length
- val numSegments = ((totalSize + segmentSize - 1) /
segmentSize)
-
- println("inner response: " +
response.toString(Charsets.UTF_8))
+ if (ebicsHost == null) throw EbicsInvalidRequestError()
+ if (subscriber == null) throw EbicsInvalidRequestError()
- println("total size: $totalSize")
- println("num segments: $numSegments")
+ val hostAuthPriv = CryptoUtil.loadRsaPrivateKey(
+ ebicsHost.authenticationPrivateKey
+ .toByteArray()
+ )
+ val clientAuthPub =
+
CryptoUtil.loadRsaPublicKey(subscriber.authenticationKey!!.rsaPublicKey.toByteArray())
+ val clientEncPub =
+
CryptoUtil.loadRsaPublicKey(subscriber.encryptionKey!!.rsaPublicKey.toByteArray())
+
+ // Step 2 of 3: Validate the signature
+ val verifyResult =
XMLUtil.verifyEbicsDocument(requestDocument, clientAuthPub)
+ if (!verifyResult) {
+ throw EbicsInvalidRequestError()
+ }
- EbicsDownloadTransactionEntity.new(transactionID) {
- this.subscriber = subscriber
- this.host = ebicsHost
- this.orderType = orderType
- this.segmentSize = segmentSize
- this.transactionKeyEnc =
SerialBlob(enc.encryptedTransactionKey)
- this.encodedResponse = encodedResponse
- this.numSegments = numSegments
- this.receiptReceived = false
+ val ebicsResponse: EbicsResponse = when
(requestObject.header.mutable.transactionPhase) {
+ EbicsTypes.TransactionPhaseType.INITIALISATION -> {
+ val transactionID =
EbicsOrderUtil.generateTransactionId()
+ val orderType =
+
requestObject.header.static.orderDetails?.orderType ?: throw
EbicsInvalidRequestError()
+ if (staticHeader.numSegments == null) {
+ val response = when (orderType) {
+ "HTD" -> handleEbicsHtd()
+ else -> throw EbicsInvalidXmlError()
+ }
+
+ val compressedResponse =
DeflaterInputStream(response.inputStream()).use {
+ it.readAllBytes()
+ }
+
+ val enc =
CryptoUtil.encryptEbicsE002(compressedResponse, clientEncPub)
+ val encodedResponse =
Base64.getEncoder().encodeToString(enc.encryptedData)
+
+ val segmentSize = 4096
+ val totalSize = encodedResponse.length
+ val numSegments = ((totalSize + segmentSize - 1) /
segmentSize)
+
+ EbicsDownloadTransactionEntity.new(transactionID) {
+ this.subscriber = subscriber
+ this.host = ebicsHost
+ this.orderType = orderType
+ this.segmentSize = segmentSize
+ this.transactionKeyEnc =
SerialBlob(enc.encryptedTransactionKey)
+ this.encodedResponse = encodedResponse
+ this.numSegments = numSegments
+ this.receiptReceived = false
+ }
+ createEbicsResponseForDownloadInitializationPhase(
+ transactionID,
+ numSegments,
+ segmentSize,
+ enc,
+ encodedResponse
+ )
+ } else {
+ val oidn = subscriber.nextOrderID++
+ if (EbicsOrderUtil.checkOrderIDOverflow(oidn))
throw NotImplementedError()
+ val orderID =
EbicsOrderUtil.computeOrderIDFromNumber(oidn)
+ val signatureData =
requestObject.body.dataTransfer?.signatureData
+ if (signatureData != null) {
+ println("signature data:
${signatureData.toString(Charsets.UTF_8)}")
+ }
+ val numSegments =
+ requestObject.header.static.numSegments ?:
throw EbicsInvalidRequestError()
+ val transactionKeyEnc =
+
requestObject.body.dataTransfer?.dataEncryptionInfo?.transactionKey
+ ?: throw EbicsInvalidRequestError()
+ EbicsUploadTransactionEntity.new(transactionID) {
+ this.host = ebicsHost
+ this.subscriber = subscriber
+ this.lastSeenSegment = 0
+ this.orderType = orderType
+ this.orderID = orderID
+ this.numSegments = numSegments.toInt()
+ this.transactionKeyEnc =
SerialBlob(transactionKeyEnc)
+ }
+
createEbicsResponseForUploadInitializationPhase(transactionID, orderID)
}
-
- val ebicsResponse =
createEbicsResponseForDownloadInitializationPhase(
- transactionID,
- numSegments, segmentSize, enc, encodedResponse
- )
- val docText =
XMLUtil.convertJaxbToString(ebicsResponse)
- val doc = XMLUtil.parseStringIntoDom(docText)
- XMLUtil.signEbicsDocument(doc, hostAuthPriv)
- val signedDoc = XMLUtil.convertDomToString(doc)
- println("response: $signedDoc")
- docText
}
- respondText(respText, ContentType.Application.Xml,
HttpStatusCode.OK)
- return
- }
- EbicsTypes.TransactionPhaseType.TRANSFER -> {
-
- }
- EbicsTypes.TransactionPhaseType.RECEIPT -> {
- val respText = transaction {
- val requestedHostId =
requestObject.header.static.hostID
- val ebicsHost = EbicsHostEntity.find {
EbicsHostsTable.hostID eq requestedHostId }.firstOrNull()
- if (ebicsHost == null)
- throw EbicsInvalidRequestError()
- val hostAuthPriv = CryptoUtil.loadRsaPrivateKey(
- ebicsHost.authenticationPrivateKey
- .toByteArray()
- )
- val transactionID =
requestObject.header.static.transactionID
- if (transactionID == null)
- throw EbicsInvalidRequestError()
- val downloadTransaction =
EbicsDownloadTransactionEntity.findById(transactionID)
+ EbicsTypes.TransactionPhaseType.TRANSFER -> {
+ throw NotImplementedError()
+ }
+ EbicsTypes.TransactionPhaseType.RECEIPT -> {
+ requestTransactionID ?: throw
EbicsInvalidRequestError()
if (downloadTransaction == null)
throw EbicsInvalidRequestError()
- println("sending receipt for transaction ID
$transactionID")
- val receiptCode =
requestObject.body.transferReceipt?.receiptCode
- if (receiptCode == null)
- throw EbicsInvalidRequestError()
- val ebicsResponse =
createEbicsResponseForDownloadReceiptPhase(transactionID, receiptCode == 0)
- val docText =
XMLUtil.convertJaxbToString(ebicsResponse)
- val doc = XMLUtil.parseStringIntoDom(docText)
- XMLUtil.signEbicsDocument(doc, hostAuthPriv)
- val signedDoc = XMLUtil.convertDomToString(doc)
- println("response: $signedDoc")
- docText
+ val receiptCode =
+ requestObject.body.transferReceipt?.receiptCode ?:
throw EbicsInvalidRequestError()
+
createEbicsResponseForDownloadReceiptPhase(requestTransactionID, receiptCode ==
0)
}
- respondText(respText, ContentType.Application.Xml,
HttpStatusCode.OK)
- return
-
}
+ val docText = XMLUtil.convertJaxbToString(ebicsResponse)
+ val doc = XMLUtil.parseStringIntoDom(docText)
+ XMLUtil.signEbicsDocument(doc, hostAuthPriv)
+ val signedDoc = XMLUtil.convertDomToString(doc)
+ println("response: $signedDoc")
+ docText
}
+ respondText(responseXmlStr, ContentType.Application.Xml,
HttpStatusCode.OK)
}
else -> {
/* Log to console and return "unknown type" */
@@ -642,109 +623,3 @@ private suspend fun ApplicationCall.ebicsweb() {
}
}
}
-
-fun main() {
- dbCreateTables()
-
- transaction {
- val pairA = CryptoUtil.generateRsaKeyPair(2048)
- val pairB = CryptoUtil.generateRsaKeyPair(2048)
- val pairC = CryptoUtil.generateRsaKeyPair(2048)
- EbicsHostEntity.new {
- hostId = "host01"
- ebicsVersion = "H004"
- authenticationPrivateKey = SerialBlob(pairA.private.encoded)
- encryptionPrivateKey = SerialBlob(pairB.private.encoded)
- signaturePrivateKey = SerialBlob(pairC.private.encoded)
- }
-
- EbicsSubscriberEntity.new {
- partnerId = "PARTNER1"
- userId = "USER1"
- systemId = null
- state = SubscriberState.NEW
- nextOrderID = 1
- }
- }
-
- val server = embeddedServer(Netty, port = 5000) {
- install(CallLogging)
- install(ContentNegotiation) {
- gson {
- setDateFormat(DateFormat.LONG)
- setPrettyPrinting()
- }
- }
- install(StatusPages) {
- exception<Throwable> { cause ->
- logger.error("Exception while handling '${call.request.uri}'",
cause)
- call.respondText("Internal server error.",
ContentType.Text.Plain, HttpStatusCode.InternalServerError)
- }
- }
- // TODO: add another intercept call that adds schema validation before
the response is sent
- intercept(ApplicationCallPipeline.Fallback) {
- if (this.call.response.status() == null) {
- call.respondText("Not found (no route matched).\n",
ContentType.Text.Plain, HttpStatusCode.NotFound)
- return@intercept finish()
- }
- }
- routing {
- //trace { logger.info(it.buildText()) }
- get("/") {
- call.respondText("Hello LibEuFin!\n", ContentType.Text.Plain)
- }
- get("/ebics/hosts") {
- val ebicsHosts = transaction {
- EbicsHostEntity.all().map { it.hostId }
- }
- call.respond(EbicsHostsResponse(ebicsHosts))
- }
- post("/ebics/hosts") {
- val req = call.receive<EbicsHostCreateRequest>()
- transaction {
- EbicsHostEntity.new {
- this.ebicsVersion = req.ebicsVersion
- this.hostId = hostId
- }
- }
- }
- get("/ebics/hosts/{id}") {
- val resp = transaction {
- val host = EbicsHostEntity.find { EbicsHostsTable.hostID
eq call.parameters["id"]!! }.firstOrNull()
- if (host == null) null
- else EbicsHostResponse(host.hostId, host.ebicsVersion)
- }
- if (resp == null) call.respond(
- HttpStatusCode.NotFound,
- SandboxError("host not found")
- )
- else call.respond(resp)
- }
- get("/ebics/subscribers") {
- val subscribers = transaction {
- EbicsSubscriberEntity.all().map { it.id.value.toString() }
- }
- call.respond(EbicsSubscribersResponse(subscribers))
- }
- get("/ebics/subscribers/{id}") {
- val resp = transaction {
- val id = call.parameters["id"]!!
- val subscriber =
EbicsSubscriberEntity.findById(id.toInt())!!
- EbicsSubscriberResponse(
- id,
- subscriber.partnerId,
- subscriber.userId,
- subscriber.systemId,
- subscriber.state.name
- )
- }
- call.respond(resp)
- }
- post("/ebicsweb") {
- call.ebicsweb()
- }
- }
- }
- logger.info("Up and running")
- server.start(wait = true)
-}
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
index 435a4e8..4c70c90 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt
@@ -19,7 +19,6 @@
package tech.libeufin.sandbox
-import io.ktor.application.ApplicationCall
import io.ktor.application.ApplicationCallPipeline
import io.ktor.application.call
import io.ktor.application.install
@@ -30,7 +29,6 @@ import io.ktor.gson.gson
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.request.receive
-import io.ktor.request.receiveText
import io.ktor.request.uri
import io.ktor.response.respond
import io.ktor.response.respondText
@@ -39,86 +37,18 @@ import io.ktor.routing.post
import io.ktor.routing.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.and
import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.Logger
import org.slf4j.LoggerFactory
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.SignaturePubKeyOrderData
-import java.math.BigInteger
import java.security.interfaces.RSAPublicKey
import java.text.DateFormat
-import java.util.*
-import java.util.zip.DeflaterInputStream
import javax.sql.rowset.serial.SerialBlob
import javax.xml.bind.JAXBContext
-val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
-
-open class EbicsRequestError(val errorText: String, val errorCode: String) :
- Exception("EBICS request management error: $errorText ($errorCode)")
-
-class EbicsInvalidRequestError : EbicsRequestError("[EBICS_INVALID_REQUEST]
Invalid request", "060102")
-
-open class EbicsKeyManagementError(val errorText: String, val errorCode:
String) :
- Exception("EBICS key management error: $errorText ($errorCode)")
-
-class EbicsInvalidXmlError : EbicsKeyManagementError("[EBICS_INVALID_XML]",
"091010")
-class EbicsInvalidOrderType :
EbicsRequestError("[EBICS_UNSUPPORTED_ORDER_TYPE] Order type not supported",
"091005")
-
-private suspend fun ApplicationCall.respondEbicsKeyManagement(
- errorText: String,
- errorCode: String,
- bankReturnCode: String,
- dataTransfer: CryptoUtil.EncryptionResult? = null,
- orderId: String? = null
-) {
- val responseXml = EbicsKeyManagementResponse().apply {
- version = "H004"
- header = EbicsKeyManagementResponse.Header().apply {
- authenticate = true
- mutable = EbicsKeyManagementResponse.MutableHeaderType().apply {
- reportText = errorText
- returnCode = errorCode
- if (orderId != null) {
- this.orderID = orderId
- }
- }
- _static = EbicsKeyManagementResponse.EmptyStaticHeader()
- }
- body = EbicsKeyManagementResponse.Body().apply {
- this.returnCode = EbicsKeyManagementResponse.ReturnCode().apply {
- this.authenticate = true
- this.value = bankReturnCode
- }
- if (dataTransfer != null) {
- this.dataTransfer =
EbicsKeyManagementResponse.DataTransfer().apply {
- this.dataEncryptionInfo =
EbicsTypes.DataEncryptionInfo().apply {
- this.authenticate = true
- this.transactionKey =
dataTransfer.encryptedTransactionKey
- this.encryptionPubKeyDigest =
EbicsTypes.PubKeyDigest().apply {
- this.algorithm =
"http://www.w3.org/2001/04/xmlenc#sha256"
- this.version = "E002"
- this.value = dataTransfer.pubKeyDigest
- }
- }
- this.orderData =
EbicsKeyManagementResponse.OrderData().apply {
- this.value = dataTransfer.encryptedData
- }
- }
- }
- }
- }
- val text = XMLUtil.convertJaxbToString(responseXml)
- logger.info("responding with:\n${text}")
- respondText(text, ContentType.Application.Xml, HttpStatusCode.OK)
-}
+val logger: Logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
fun findEbicsSubscriber(partnerID: String, userID: String, systemID: String?):
EbicsSubscriberEntity? {
@@ -139,9 +69,11 @@ fun findEbicsSubscriber(partnerID: String, userID: String,
systemID: String?): E
data class Subscriber(
val partnerID: String,
val userID: String,
- val systemID: String?
+ val systemID: String?,
+ val keys: SubscriberKeys
)
+
data class SubscriberKeys(
val authenticationPublicKey: RSAPublicKey,
val encryptionPublicKey: RSAPublicKey,
@@ -149,160 +81,13 @@ data class SubscriberKeys(
)
-data class EbicsHostInfo(
+data class EbicsHostPublicInfo(
val hostID: String,
val encryptionPublicKey: RSAPublicKey,
val authenticationPublicKey: RSAPublicKey
)
-private suspend fun ApplicationCall.handleEbicsHia(header:
EbicsUnsecuredRequest.Header, orderData: ByteArray) {
- val keyObject =
EbicsOrderUtil.decodeOrderDataXml<HIARequestOrderData>(orderData)
- val encPubXml = keyObject.encryptionPubKeyInfo.pubKeyValue.rsaKeyValue
- val authPubXml = keyObject.authenticationPubKeyInfo.pubKeyValue.rsaKeyValue
- val encPub = CryptoUtil.loadRsaPublicKeyFromComponents(encPubXml.modulus,
encPubXml.exponent)
- val authPub =
CryptoUtil.loadRsaPublicKeyFromComponents(authPubXml.modulus,
authPubXml.exponent)
-
- transaction {
- val ebicsSubscriber = findEbicsSubscriber(header.static.partnerID,
header.static.userID, header.static.systemID)
- if (ebicsSubscriber == null) {
- logger.warn("ebics subscriber not found")
- throw EbicsInvalidRequestError()
- }
- ebicsSubscriber.authenticationKey = EbicsSubscriberPublicKeyEntity.new
{
- this.rsaPublicKey = SerialBlob(authPub.encoded)
- state = KeyState.NEW
- }
- ebicsSubscriber.encryptionKey = EbicsSubscriberPublicKeyEntity.new {
- this.rsaPublicKey = SerialBlob(encPub.encoded)
- state = KeyState.NEW
- }
- ebicsSubscriber.state = when (ebicsSubscriber.state) {
- SubscriberState.NEW -> SubscriberState.PARTIALLY_INITIALIZED_HIA
- SubscriberState.PARTIALLY_INITIALIZED_INI ->
SubscriberState.INITIALIZED
- else -> ebicsSubscriber.state
- }
- }
- respondEbicsKeyManagement("[EBICS_OK]", "000000", "000000")
-}
-
-
-private suspend fun ApplicationCall.handleEbicsIni(header:
EbicsUnsecuredRequest.Header, orderData: ByteArray) {
- val keyObject =
EbicsOrderUtil.decodeOrderDataXml<SignaturePubKeyOrderData>(orderData)
- val sigPubXml = keyObject.signaturePubKeyInfo.pubKeyValue.rsaKeyValue
- val sigPub = CryptoUtil.loadRsaPublicKeyFromComponents(sigPubXml.modulus,
sigPubXml.exponent)
-
- transaction {
- val ebicsSubscriber =
- findEbicsSubscriber(header.static.partnerID, header.static.userID,
header.static.systemID)
- if (ebicsSubscriber == null) {
- logger.warn("ebics subscriber ('${header.static.partnerID}' /
'${header.static.userID}' / '${header.static.systemID}') not found")
- throw EbicsInvalidRequestError()
- }
- ebicsSubscriber.signatureKey = EbicsSubscriberPublicKeyEntity.new {
- this.rsaPublicKey = SerialBlob(sigPub.encoded)
- state = KeyState.NEW
- }
- ebicsSubscriber.state = when (ebicsSubscriber.state) {
- SubscriberState.NEW -> SubscriberState.PARTIALLY_INITIALIZED_INI
- SubscriberState.PARTIALLY_INITIALIZED_HIA ->
SubscriberState.INITIALIZED
- else -> ebicsSubscriber.state
- }
- }
- logger.info("Signature key inserted in database _and_ subscriber state
changed accordingly")
- respondEbicsKeyManagement("[EBICS_OK]", "000000", bankReturnCode =
"000000", orderId = "OR01")
-}
-
-private suspend fun ApplicationCall.handleEbicsHpb(
- ebicsHostInfo: EbicsHostInfo,
- requestDocument: Document,
- header: EbicsNpkdRequest.Header
-) {
- val subscriberKeys = transaction {
- val ebicsSubscriber =
- findEbicsSubscriber(header.static.partnerID, header.static.userID,
header.static.systemID)
- if (ebicsSubscriber == null) {
- throw EbicsInvalidRequestError()
- }
- if (ebicsSubscriber.state != SubscriberState.INITIALIZED) {
- throw EbicsInvalidRequestError()
- }
- val authPubBlob = ebicsSubscriber.authenticationKey!!.rsaPublicKey
- val encPubBlob = ebicsSubscriber.encryptionKey!!.rsaPublicKey
- val sigPubBlob = ebicsSubscriber.signatureKey!!.rsaPublicKey
- SubscriberKeys(
- CryptoUtil.loadRsaPublicKey(authPubBlob.toByteArray()),
- CryptoUtil.loadRsaPublicKey(encPubBlob.toByteArray()),
- CryptoUtil.loadRsaPublicKey(sigPubBlob.toByteArray())
- )
- }
- val validationResult =
- XMLUtil.verifyEbicsDocument(requestDocument,
subscriberKeys.authenticationPublicKey)
- logger.info("validationResult: $validationResult")
- if (!validationResult) {
- throw EbicsKeyManagementError("invalid signature", "90000");
- }
- val hpbRespondeData = HPBResponseOrderData().apply {
- this.authenticationPubKeyInfo =
EbicsTypes.AuthenticationPubKeyInfoType().apply {
- this.authenticationVersion = "X002"
- this.pubKeyValue = EbicsTypes.PubKeyValueType().apply {
- this.rsaKeyValue = RSAKeyValueType().apply {
- this.exponent =
ebicsHostInfo.authenticationPublicKey.publicExponent.toByteArray()
- this.modulus =
ebicsHostInfo.authenticationPublicKey.modulus.toByteArray()
- }
- }
- }
- this.encryptionPubKeyInfo =
EbicsTypes.EncryptionPubKeyInfoType().apply {
- this.encryptionVersion = "E002"
- this.pubKeyValue = EbicsTypes.PubKeyValueType().apply {
- this.rsaKeyValue = RSAKeyValueType().apply {
- this.exponent =
ebicsHostInfo.encryptionPublicKey.publicExponent.toByteArray()
- this.modulus =
ebicsHostInfo.encryptionPublicKey.modulus.toByteArray()
- }
- }
- }
- this.hostID = ebicsHostInfo.hostID
- }
-
- val compressedOrderData =
EbicsOrderUtil.encodeOrderDataXml(hpbRespondeData)
-
- val encryptionResult = CryptoUtil.encryptEbicsE002(compressedOrderData,
subscriberKeys.encryptionPublicKey)
-
- respondEbicsKeyManagement("[EBICS_OK]", "000000", "000000",
encryptionResult, "OR01")
-}
-
-/**
- * Find the ebics host corresponding to the one specified in the header.
- */
-private fun ApplicationCall.ensureEbicsHost(requestHostID: String):
EbicsHostInfo {
- return transaction {
- val ebicsHost = EbicsHostEntity.find { EbicsHostsTable.hostID eq
requestHostID }.firstOrNull()
- if (ebicsHost == null) {
- logger.warn("client requested unknown HostID")
- throw EbicsKeyManagementError("[EBICS_INVALID_HOST_ID]", "091011")
- }
- val encryptionPrivateKey =
CryptoUtil.loadRsaPrivateKey(ebicsHost.encryptionPrivateKey.toByteArray())
- val authenticationPrivateKey =
CryptoUtil.loadRsaPrivateKey(ebicsHost.authenticationPrivateKey.toByteArray())
- EbicsHostInfo(
- requestHostID,
- CryptoUtil.getRsaPublicFromPrivate(encryptionPrivateKey),
- CryptoUtil.getRsaPublicFromPrivate(authenticationPrivateKey)
- )
- }
-}
-
-
-private suspend fun ApplicationCall.receiveEbicsXml(): Document {
- val body: String = receiveText()
- logger.debug("Data received: $body")
- val requestDocument: Document? = XMLUtil.parseStringIntoDom(body)
- if (requestDocument == null ||
(!XMLUtil.validateFromDom(requestDocument))) {
- throw EbicsInvalidXmlError()
- }
- return requestDocument
-}
-
-
inline fun <reified T> Document.toObject(): T {
val jc = JAXBContext.newInstance(T::class.java)
val m = jc.createUnmarshaller()
@@ -310,339 +95,6 @@ inline fun <reified T> Document.toObject(): T {
}
-fun handleEbicsHtd(): ByteArray {
- val htd = HTDResponseOrderData().apply {
- this.partnerInfo = HTDResponseOrderData.PartnerInfo().apply {
- this.accountInfoList = listOf(
- HTDResponseOrderData.AccountInfo().apply {
- this.id = "acctid1"
- this.accountHolder = "Mina Musterfrau"
- this.accountNumberList = listOf(
- HTDResponseOrderData.GeneralAccountNumber().apply {
- this.international = true
- this.value = "DE21500105174751659277"
- }
- )
- this.currency = "EUR"
- this.description = "ACCT"
- this.bankCodeList = listOf(
- HTDResponseOrderData.GeneralBankCode().apply {
- this.international = true
- this.value = "INGDDEFFXXX"
- }
- )
- }
- )
- this.addressInfo = HTDResponseOrderData.AddressInfo().apply {
- this.name = "Foo"
- }
- this.bankInfo = HTDResponseOrderData.BankInfo().apply {
- this.hostID = "host01"
- }
- this.orderInfoList = listOf(
- HTDResponseOrderData.AuthOrderInfoType().apply {
- this.description = "foo"
- this.orderType = "C53"
- this.transferType = "Download"
- },
- HTDResponseOrderData.AuthOrderInfoType().apply {
- this.description = "foo"
- this.orderType = "C52"
- this.transferType = "Download"
- },
- HTDResponseOrderData.AuthOrderInfoType().apply {
- this.description = "foo"
- this.orderType = "CCC"
- this.transferType = "Upload"
- }
- )
- }
- this.userInfo = HTDResponseOrderData.UserInfo().apply {
- this.name = "Some User"
- this.userID = HTDResponseOrderData.UserIDType().apply {
- this.status = 5
- this.value = "USER1"
- }
- this.permissionList = listOf(
- HTDResponseOrderData.UserPermission().apply {
- this.orderTypes = "C54 C53 C52 CCC"
- }
- )
- }
- }
-
- val str = XMLUtil.convertJaxbToString(htd)
- return str.toByteArray()
-}
-
-
-fun createEbicsResponseForDownloadInitializationPhase(
- 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 = EbicsResponse.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))
- }
- }
- }
- }
-}
-
-
-fun createEbicsResponseForDownloadTransferPhase() {
-
-}
-
-
-fun createEbicsResponseForDownloadReceiptPhase(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_DONE]
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"
- }
- }
- }
-}
-
-
-private suspend fun ApplicationCall.handleEbicsDownloadInitialization() {
-
-}
-
-private suspend fun ApplicationCall.handleEbicsDownloadTransfer() {
-
-}
-
-private suspend fun ApplicationCall.handleEbicsDownloadReceipt() {
-
-}
-
-private suspend fun ApplicationCall.handleEbicsUploadInitialization() {
-
-}
-
-
-private suspend fun ApplicationCall.ebicsweb() {
- val requestDocument = receiveEbicsXml()
-
- logger.info("Processing ${requestDocument.documentElement.localName}")
-
- when (requestDocument.documentElement.localName) {
- "ebicsUnsecuredRequest" -> {
- val requestObject =
requestDocument.toObject<EbicsUnsecuredRequest>()
- logger.info("Serving a
${requestObject.header.static.orderDetails.orderType} request")
-
- val orderData = requestObject.body.dataTransfer.orderData.value
- val header = requestObject.header
-
- when (header.static.orderDetails.orderType) {
- "INI" -> handleEbicsIni(header, orderData)
- "HIA" -> handleEbicsHia(header, orderData)
- else -> throw EbicsInvalidXmlError()
- }
- }
- "ebicsHEVRequest" -> {
- val hevResponse = HEVResponse().apply {
- this.systemReturnCode = SystemReturnCodeType().apply {
- this.reportText = "[EBICS_OK]"
- this.returnCode = "000000"
- }
- this.versionNumber =
listOf(HEVResponse.VersionNumber.create("H004", "02.50"))
- }
-
- val strResp = XMLUtil.convertJaxbToString(hevResponse)
- respondText(strResp, ContentType.Application.Xml,
HttpStatusCode.OK)
- }
- "ebicsNoPubKeyDigestsRequest" -> {
- val requestObject = requestDocument.toObject<EbicsNpkdRequest>()
- val hostInfo = ensureEbicsHost(requestObject.header.static.hostID)
- when (requestObject.header.static.orderDetails.orderType) {
- "HPB" -> handleEbicsHpb(hostInfo, requestDocument,
requestObject.header)
- else -> throw EbicsInvalidXmlError()
- }
- }
- "ebicsRequest" -> {
- println("ebicsRequest
${XMLUtil.convertDomToString(requestDocument)}")
- val requestObject = requestDocument.toObject<EbicsRequest>()
- val staticHeader = requestObject.header.static
-
- when (requestObject.header.mutable.transactionPhase) {
- EbicsTypes.TransactionPhaseType.INITIALISATION -> {
- val partnerID = staticHeader.partnerID ?: throw
EbicsInvalidXmlError()
- val userID = staticHeader.userID ?: throw
EbicsInvalidXmlError()
- val respText = transaction {
- val subscriber =
- findEbicsSubscriber(partnerID, userID,
staticHeader.systemID)
- ?: throw EbicsInvalidXmlError()
- val requestedHostId =
requestObject.header.static.hostID
- val ebicsHost = EbicsHostEntity.find {
EbicsHostsTable.hostID eq requestedHostId }.firstOrNull()
- if (ebicsHost == null)
- throw EbicsInvalidRequestError()
- val hostAuthPriv = CryptoUtil.loadRsaPrivateKey(
- ebicsHost.authenticationPrivateKey
- .toByteArray()
- )
- val clientAuthPub =
-
CryptoUtil.loadRsaPublicKey(subscriber.authenticationKey!!.rsaPublicKey.toByteArray())
- val clientEncPub =
-
CryptoUtil.loadRsaPublicKey(subscriber.encryptionKey!!.rsaPublicKey.toByteArray())
- val verifyResult =
XMLUtil.verifyEbicsDocument(requestDocument, clientAuthPub)
- println("ebicsRequest verification result:
$verifyResult")
- val transactionID =
EbicsOrderUtil.generateTransactionId()
- val orderType =
requestObject.header.static.orderDetails?.orderType
-
- val response = when (orderType) {
- "HTD" -> handleEbicsHtd()
- else -> throw EbicsInvalidXmlError()
- }
-
- val compressedResponse =
DeflaterInputStream(response.inputStream()).use {
- it.readAllBytes()
- }
-
- val enc =
CryptoUtil.encryptEbicsE002(compressedResponse, clientEncPub)
- val encodedResponse =
Base64.getEncoder().encodeToString(enc.encryptedData)
-
- val segmentSize = 4096
- val totalSize = encodedResponse.length
- val numSegments = ((totalSize + segmentSize - 1) /
segmentSize)
-
- println("inner response: " +
response.toString(Charsets.UTF_8))
-
- println("total size: $totalSize")
- println("num segments: $numSegments")
-
- EbicsDownloadTransactionEntity.new(transactionID) {
- this.subscriber = subscriber
- this.host = ebicsHost
- this.orderType = orderType
- this.segmentSize = segmentSize
- this.transactionKeyEnc =
SerialBlob(enc.encryptedTransactionKey)
- this.encodedResponse = encodedResponse
- this.numSegments = numSegments
- this.receiptReceived = false
- }
-
- val ebicsResponse =
createEbicsResponseForDownloadInitializationPhase(
- transactionID,
- numSegments, segmentSize, enc, encodedResponse
- )
- val docText =
XMLUtil.convertJaxbToString(ebicsResponse)
- val doc = XMLUtil.parseStringIntoDom(docText)
- XMLUtil.signEbicsDocument(doc, hostAuthPriv)
- val signedDoc = XMLUtil.convertDomToString(doc)
- println("response: $signedDoc")
- docText
- }
- respondText(respText, ContentType.Application.Xml,
HttpStatusCode.OK)
- return
- }
- EbicsTypes.TransactionPhaseType.TRANSFER -> {
-
- }
- EbicsTypes.TransactionPhaseType.RECEIPT -> {
- val respText = transaction {
- val requestedHostId =
requestObject.header.static.hostID
- val ebicsHost = EbicsHostEntity.find {
EbicsHostsTable.hostID eq requestedHostId }.firstOrNull()
- if (ebicsHost == null)
- throw EbicsInvalidRequestError()
- val hostAuthPriv = CryptoUtil.loadRsaPrivateKey(
- ebicsHost.authenticationPrivateKey
- .toByteArray()
- )
- val transactionID =
requestObject.header.static.transactionID
- if (transactionID == null)
- throw EbicsInvalidRequestError()
- val downloadTransaction =
EbicsDownloadTransactionEntity.findById(transactionID)
- if (downloadTransaction == null)
- throw EbicsInvalidRequestError()
- println("sending receipt for transaction ID
$transactionID")
- val receiptCode =
requestObject.body.transferReceipt?.receiptCode
- if (receiptCode == null)
- throw EbicsInvalidRequestError()
- val ebicsResponse =
createEbicsResponseForDownloadReceiptPhase(transactionID, receiptCode == 0)
- val docText =
XMLUtil.convertJaxbToString(ebicsResponse)
- val doc = XMLUtil.parseStringIntoDom(docText)
- XMLUtil.signEbicsDocument(doc, hostAuthPriv)
- val signedDoc = XMLUtil.convertDomToString(doc)
- println("response: $signedDoc")
- docText
- }
- respondText(respText, ContentType.Application.Xml,
HttpStatusCode.OK)
- return
-
- }
- }
- }
- else -> {
- /* Log to console and return "unknown type" */
- logger.info("Unknown message, just logging it!")
- respond(
- HttpStatusCode.NotImplemented,
- SandboxError("Not Implemented")
- )
- }
- }
-}
-
fun main() {
dbCreateTables()
diff --git a/sandbox/src/test/kotlin/CryptoUtilTest.kt
b/sandbox/src/test/kotlin/CryptoUtilTest.kt
index a2b0901..0846836 100644
--- a/sandbox/src/test/kotlin/CryptoUtilTest.kt
+++ b/sandbox/src/test/kotlin/CryptoUtilTest.kt
@@ -53,7 +53,7 @@ class CryptoUtilTest {
val encodedPriv = keyPair.private.encoded
val encodedPub = keyPair.public.encoded
val otherKeyPair =
- RsaCrtKeyPair(CryptoUtil.loadRsaPrivateKey(encodedPriv),
CryptoUtil.loadRsaPublicKey(encodedPub))
+
CryptoUtil.RsaCrtKeyPair(CryptoUtil.loadRsaPrivateKey(encodedPriv),
CryptoUtil.loadRsaPublicKey(encodedPub))
assertEquals(keyPair.private, otherKeyPair.private)
assertEquals(keyPair.public, otherKeyPair.public)
}
diff --git a/sandbox/src/test/kotlin/EbicsOrderUtilTest.kt
b/sandbox/src/test/kotlin/EbicsOrderUtilTest.kt
new file mode 100644
index 0000000..c35794e
--- /dev/null
+++ b/sandbox/src/test/kotlin/EbicsOrderUtilTest.kt
@@ -0,0 +1,16 @@
+package tech.libeufin.sandbox
+
+import org.junit.Test
+import kotlin.test.assertEquals
+
+
+class EbicsOrderUtilTest {
+
+ @Test
+ fun testComputeOrderIDFromNumber() {
+ assertEquals("OR01", EbicsOrderUtil.computeOrderIDFromNumber(1))
+ assertEquals("OR0A", EbicsOrderUtil.computeOrderIDFromNumber(10))
+ assertEquals("OR10", EbicsOrderUtil.computeOrderIDFromNumber(36))
+ assertEquals("OR11", EbicsOrderUtil.computeOrderIDFromNumber(37))
+ }
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
address@hidden.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: refactor, partially implement order upload,
gnunet <=