libeufin

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

commit aa91f30becd9fe2bff2e15c31d0fc592aa70cba3
parent 7bf8e0489d68e0f70abe4cee11c3b9177566a908
Author: Marcello Stanisci <stanisci.m@gmail.com>
Date:   Tue,  7 Apr 2020 20:21:38 +0200

String manipulators for HTTP basic auth.

Diffstat:
Mnexus/build.gradle | 1+
Mnexus/src/main/kotlin/tech/libeufin/nexus/DB.kt | 2++
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 12++++++++++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/taler.kt | 22++++++++++++++++++++--
Anexus/src/test/kotlin/authentication.kt | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt | 2+-
Mutil/src/main/kotlin/CryptoUtil.kt | 5++++-
7 files changed, 95 insertions(+), 4 deletions(-)

diff --git a/nexus/build.gradle b/nexus/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation group: 'io.ktor', name: 'ktor-gson', version: '0.9.0' implementation "org.jetbrains.exposed:exposed:0.17.6" implementation "io.ktor:ktor-server-netty:1.2.4" + implementation "io.ktor:ktor-auth:1.2.4" implementation "ch.qos.logback:logback-classic:1.2.3" implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1' implementation "javax.xml.bind:jaxb-api:2.3.0" diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt @@ -119,6 +119,7 @@ class EbicsAccountInfoEntity(id: EntityID<String>) : Entity<String>(id) { object EbicsSubscribersTable : IdTable<String>() { override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey() + val password = blob("password").nullable() val ebicsURL = text("ebicsURL") val hostID = text("hostID") val partnerID = text("partnerID") @@ -133,6 +134,7 @@ object EbicsSubscribersTable : IdTable<String>() { class EbicsSubscriberEntity(id: EntityID<String>) : Entity<String>(id) { companion object : EntityClass<String, EbicsSubscriberEntity>(EbicsSubscribersTable) + var password by EbicsSubscribersTable.password var ebicsURL by EbicsSubscribersTable.ebicsURL var hostID by EbicsSubscribersTable.hostID var partnerID by EbicsSubscribersTable.partnerID diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -22,6 +22,8 @@ package tech.libeufin.nexus import io.ktor.application.ApplicationCallPipeline import io.ktor.application.call import io.ktor.application.install +import io.ktor.auth.Authentication +import io.ktor.auth.basic import io.ktor.client.HttpClient import io.ktor.features.CallLogging import io.ktor.features.ContentNegotiation @@ -323,6 +325,16 @@ fun main() { this.level = Level.DEBUG this.logger = tech.libeufin.nexus.logger } + /* + install(Authentication) { + basic("taler") { + validate {credentials -> + + + } + } + }*/ + install(ContentNegotiation) { gson { setDateFormat(DateFormat.LONG) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/taler.kt @@ -1,6 +1,5 @@ package tech.libeufin.nexus -import io.ktor.application.Application import io.ktor.application.call import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode @@ -9,8 +8,27 @@ import io.ktor.routing.Route import io.ktor.routing.post import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.transactions.transaction -import tech.libeufin.util.Amount import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.base64ToBytes +import java.lang.Exception + +/** + * This helper function parses a Authorization:-header line, decode the credentials + * and returns a pair made of username and hashed (sha256) password. The hashed value + * will then be compared with the one kept into the database. + */ +fun extractUserAndHashedPassword(authorizationHeader: String): Pair<String, ByteArray> { + val (username, password) = try { + val split = authorizationHeader.split(" ") + val valueUtf8 = String(base64ToBytes(split[1]), Charsets.UTF_8) // newline introduced here: BUG! + valueUtf8.split(":") + } catch (e: Exception) { + throw NexusError( + HttpStatusCode.BadRequest, "invalid Authorization:-header received" + ) + } + return Pair(username, CryptoUtil.hashStringSHA256(password)) +} class Taler(app: Route) { diff --git a/nexus/src/test/kotlin/authentication.kt b/nexus/src/test/kotlin/authentication.kt @@ -0,0 +1,54 @@ +package tech.libeufin.nexus + +import org.apache.commons.compress.utils.IOUtils +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.Before +import org.junit.Test +import tech.libeufin.util.CryptoUtil +import tech.libeufin.util.toByteArray +import tech.libeufin.util.toHexString +import java.sql.Blob +import javax.sql.rowset.serial.SerialBlob + +class AuthenticationTest { + + @Before + fun connectAndMakeTables() { + Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver") + transaction { + SchemaUtils.create(EbicsSubscribersTable) + EbicsSubscriberEntity.new(id = "username") { + password = SerialBlob(CryptoUtil.hashStringSHA256("password")) + ebicsURL = "ebics url" + hostID = "host" + partnerID = "partner" + userID = "user" + systemID = "system" + signaturePrivateKey = SerialBlob("signturePrivateKey".toByteArray()) + authenticationPrivateKey = SerialBlob("authenticationPrivateKey".toByteArray()) + encryptionPrivateKey = SerialBlob("encryptionPrivateKey".toByteArray()) + } + } + } + + @Test + fun manualMethod() { + // base64 of "username:password" == "dXNlcm5hbWU6cGFzc3dvcmQ=" + val (username: String, hashedPass: ByteArray) = extractUserAndHashedPassword("Basic dXNlcm5hbWU6cGFzc3dvcmQ=") + val result = transaction { + val row = EbicsSubscriberEntity.find { + EbicsSubscribersTable.id eq username and (EbicsSubscribersTable.password eq SerialBlob(hashedPass)) + }.firstOrNull() + assert(row != null) + } + } + + @Test + fun testExtractor() { + val (username: String, hashedPass: ByteArray) = extractUserAndHashedPassword("Basic dXNlcm5hbWU6cGFzc3dvcmQ=") + assert(CryptoUtil.hashStringSHA256("password").contentEquals(hashedPass)) + } +} +\ No newline at end of file diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/EbicsProtocolBackend.kt @@ -815,7 +815,7 @@ private fun handleEbicsDownloadTransactionInitialization(requestContext: Request "HKD" -> handleEbicsHkd() /* Temporarily handling C52/C53 with same logic */ "C52" -> handleEbicsC52(requestContext) - "C53" -> handleEbicsC53(requestContext) + "C53" -> handleEbicsC53(requestContext) "TSD" -> handleEbicsTSD(requestContext) "PTK" -> handleEbicsPTK(requestContext) else -> throw EbicsInvalidXmlError() diff --git a/util/src/main/kotlin/CryptoUtil.kt b/util/src/main/kotlin/CryptoUtil.kt @@ -147,7 +147,6 @@ object CryptoUtil { transactionKey ) } - /** * Encrypt data according to the EBICS E002 encryption process. */ @@ -297,4 +296,8 @@ object CryptoUtil { } return true } + + fun hashStringSHA256(input: String): ByteArray { + return MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8)) + } }