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:
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))
+ }
}