gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] 02/02: CLI for superuser management, abstract PW hashing algo


From: gnunet
Subject: [libeufin] 02/02: CLI for superuser management, abstract PW hashing algo
Date: Tue, 19 May 2020 10:46:16 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository libeufin.

commit 4dbd3ae0898c120666329b9b90edea7dc73e777d
Author: Florian Dold <address@hidden>
AuthorDate: Tue May 19 14:16:05 2020 +0530

    CLI for superuser management, abstract PW hashing algo
---
 nexus/build.gradle                                 |  6 +++
 nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt    |  5 ++-
 .../src/main/kotlin/tech/libeufin/nexus/Helpers.kt | 24 +++++++-----
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 45 +++++++++++++++++++++-
 .../kotlin/tech/libeufin/nexus/MainDeprecated.kt   |  2 +-
 nexus/src/test/kotlin/authentication.kt            | 24 ++----------
 util/src/main/kotlin/CryptoUtil.kt                 | 19 +++++++++
 util/src/test/kotlin/CryptoUtilTest.kt             |  6 +++
 8 files changed, 96 insertions(+), 35 deletions(-)

diff --git a/nexus/build.gradle b/nexus/build.gradle
index ff58a78..2c6f3b2 100644
--- a/nexus/build.gradle
+++ b/nexus/build.gradle
@@ -68,6 +68,8 @@ dependencies {
     implementation 'org.apache.santuario:xmlsec:2.1.4'
     implementation group: 'org.apache.commons', name: 'commons-compress', 
version: '1.20'
 
+    implementation("com.github.ajalt:clikt:2.7.0")
+
     testImplementation group: 'junit', name: 'junit', version: '4.12'
 }
 
@@ -85,4 +87,8 @@ jar {
     manifest {
         attributes "Main-Class": "tech.libeufin.nexus.MainKt"
     }
+}
+
+run {
+    standardInput = System.in
 }
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
index ec31dce..9c7e225 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DB.kt
@@ -140,6 +140,7 @@ object PreparedPaymentsTable : IdTable<String>() {
     /** never really used, but it makes sure the user always exists  */
     val nexusUser = reference("nexusUser", NexusUsersTable)
 }
+
 class PreparedPaymentEntity(id: EntityID<String>) : Entity<String>(id) {
     companion object : EntityClass<String, 
PreparedPaymentEntity>(PreparedPaymentsTable)
     var paymentId by PreparedPaymentsTable.paymentId
@@ -209,12 +210,12 @@ class EbicsSubscriberEntity(id: EntityID<String>) : 
Entity<String>(id) {
 
 object NexusUsersTable : IdTable<String>() {
     override val id = varchar("id", ID_MAX_LENGTH).entityId().primaryKey()
-    val password = blob("password").nullable()
+    val passwordHash = text("password")
 }
 
 class NexusUserEntity(id: EntityID<String>) : Entity<String>(id) {
     companion object : EntityClass<String, NexusUserEntity>(NexusUsersTable)
-    var password by NexusUsersTable.password
+    var passwordHash by NexusUsersTable.passwordHash
 }
 
 object BankAccountMapsTable : IntIdTable() {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
index cd54e81..ac77efb 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Helpers.kt
@@ -11,7 +11,6 @@ import tech.libeufin.util.Amount
 import tech.libeufin.util.CryptoUtil
 import tech.libeufin.util.EbicsClientSubscriberDetails
 import tech.libeufin.util.base64ToBytes
-import javax.sql.rowset.serial.SerialBlob
 import java.util.Random
 import tech.libeufin.util.ebics_h004.EbicsTypes
 import java.security.interfaces.RSAPublicKey
@@ -440,7 +439,7 @@ fun extractNexusUser(param: String?): NexusUserEntity {
  * 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> {
+fun extractUserAndHashedPassword(authorizationHeader: String): Pair<String, 
String> {
     logger.debug("Authenticating: $authorizationHeader")
     val (username, password) = try {
         val split = authorizationHeader.split(" ")
@@ -452,7 +451,7 @@ fun extractUserAndHashedPassword(authorizationHeader: 
String): Pair<String, Byte
             "invalid Authorization:-header received"
         )
     }
-    return Pair(username, CryptoUtil.hashStringSHA256(password))
+    return Pair(username, password)
 }
 
 /**
@@ -466,13 +465,20 @@ fun authenticateRequest(authorization: String?): String {
     val headerLine = if (authorization == null) throw NexusError(
         HttpStatusCode.BadRequest, "Authentication:-header line not found"
     ) else authorization
-    val subscriber = transaction {
-        val (user, pass) = extractUserAndHashedPassword(headerLine)
-        NexusUserEntity.find {
-            NexusUsersTable.id eq user and (NexusUsersTable.password eq 
SerialBlob(pass))
+    val nexusUserId = transaction {
+        val (username, password) = extractUserAndHashedPassword(headerLine)
+        val user = NexusUserEntity.find {
+            NexusUsersTable.id eq username
         }.firstOrNull()
-    } ?: throw NexusError(HttpStatusCode.Forbidden, "Wrong password")
-    return subscriber.id.value
+        if (user == null) {
+            throw NexusError(HttpStatusCode.Unauthorized, "Unknown user")
+        }
+        if (!CryptoUtil.checkpw(password, user.passwordHash)) {
+            throw NexusError(HttpStatusCode.Forbidden, "Wrong password")
+        }
+        return@transaction user.id.value
+    }
+    return nexusUserId
 }
 
 fun authenticateAdminRequest(authorization: String?): String {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 15b4a8b..ec2080e 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -19,6 +19,11 @@
 
 package tech.libeufin.nexus
 
+import com.github.ajalt.clikt.core.CliktCommand
+import com.github.ajalt.clikt.core.subcommands
+import com.github.ajalt.clikt.parameters.arguments.argument
+import com.github.ajalt.clikt.parameters.options.option
+import com.github.ajalt.clikt.parameters.options.prompt
 import com.google.gson.Gson
 import com.google.gson.JsonObject
 import io.ktor.application.ApplicationCallPipeline
@@ -52,6 +57,7 @@ import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
 import tech.libeufin.util.*
+import tech.libeufin.util.CryptoUtil.hashpw
 import tech.libeufin.util.ebics_h004.HTDResponseOrderData
 import java.text.DateFormat
 import java.util.zip.InflaterInputStream
@@ -157,9 +163,44 @@ suspend fun handleEbicsSendMSG(
     return response
 }
 
+class NexusCommand: CliktCommand() {
+    override fun run() = Unit
+}
+
+class Serve: CliktCommand("Run nexus HTTP server") {
+    override fun run() {
+        serverMain()
+    }
+}
+
+class Superuser: CliktCommand("Add superuser or change pw") {
+    val username by argument()
+    val password by option().prompt(requireConfirmation = true, hideInput = 
true)
+    override fun run() {
+        dbCreateTables()
+        transaction {
+            val hashedPw = hashpw(password)
+            val user = NexusUserEntity.findById(username)
+            if (user == null) {
+                NexusUserEntity.new(username) {
+                    this.passwordHash = hashedPw
+                }
+            } else {
+                user.passwordHash = hashedPw
+            }
+        }
+    }
+}
+
+fun main(args: Array<String>) {
+    NexusCommand()
+        .subcommands(Serve(), Superuser())
+        .main(args)
+}
+
 @ExperimentalIoApi
 @KtorExperimentalAPI
-fun main() {
+fun serverMain() {
     dbCreateTables()
     val client = HttpClient() {
         expectSuccess = false // this way, it does not throw exceptions on != 
200 responses.
@@ -249,7 +290,7 @@ fun main() {
                 )
                 transaction {
                     NexusUserEntity.new(body.username) {
-                        password = 
SerialBlob(CryptoUtil.hashStringSHA256(body.password))
+                        passwordHash = hashpw(body.password)
                     }
                 }
                 call.respondText(
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
index d730001..b65acd3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/MainDeprecated.kt
@@ -206,7 +206,7 @@ fun main() {
                 val body = call.receive<NexusUserRequest>()
                 transaction {
                     NexusUserEntity.new(id = newUserId) {
-                        password = if (body.password != null) {
+                        passwordHash = if (body.password != null) {
                             
SerialBlob(CryptoUtil.hashStringSHA256(body.password))
                         } else {
                             logger.debug("No password set for $newUserId")
diff --git a/nexus/src/test/kotlin/authentication.kt 
b/nexus/src/test/kotlin/authentication.kt
index 7cbb36d..602f990 100644
--- a/nexus/src/test/kotlin/authentication.kt
+++ b/nexus/src/test/kotlin/authentication.kt
@@ -1,37 +1,19 @@
 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.Test
+import junit.framework.TestCase.assertEquals
 import tech.libeufin.util.CryptoUtil
 import javax.sql.rowset.serial.SerialBlob
 
 class AuthenticationTest {
-    @Test
-    fun dbInvolvingTest() {
-        Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = 
"org.h2.Driver")
-        transaction {
-            SchemaUtils.create(NexusUsersTable)
-            NexusUserEntity.new(id = "username") {
-                password = SerialBlob(CryptoUtil.hashStringSHA256("password"))
-            }
-            // base64 of "username:password" == "dXNlcm5hbWU6cGFzc3dvcmQ="
-            val hashedPass= extractUserAndHashedPassword(
-                "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
-            ).second
-            val row = NexusUserEntity.findById("username")
-            assert(row?.password == SerialBlob(hashedPass))
-        }
-    }
-
     @Test
     fun basicAuthHeaderTest() {
-        val hashedPass = extractUserAndHashedPassword(
+        val pass = extractUserAndHashedPassword(
             "Basic dXNlcm5hbWU6cGFzc3dvcmQ="
         ).second
-        
assert(CryptoUtil.hashStringSHA256("password").contentEquals(hashedPass))
+        assertEquals("password", pass);
     }
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/CryptoUtil.kt 
b/util/src/main/kotlin/CryptoUtil.kt
index fe578a3..f8711c0 100644
--- a/util/src/main/kotlin/CryptoUtil.kt
+++ b/util/src/main/kotlin/CryptoUtil.kt
@@ -300,4 +300,23 @@ object CryptoUtil {
     fun hashStringSHA256(input: String): ByteArray {
         return 
MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8))
     }
+
+    fun hashpw(pw: String): String {
+        val pwh = bytesToBase64(CryptoUtil.hashStringSHA256(pw))
+        return "sha256\$$pwh"
+    }
+
+    fun checkpw(pw: String, storedPwHash: String): Boolean {
+        val idx = storedPwHash.indexOf("\$")
+        if (idx <= 0) {
+            throw Exception("bad password hash")
+        }
+        val algo = storedPwHash.substring(0, idx)
+        if (algo != "sha256") {
+            throw Exception("unsupported hash algo")
+        }
+        val rest = storedPwHash.substring(idx + 1)
+        val pwh = bytesToBase64(CryptoUtil.hashStringSHA256(pw))
+        return pwh == rest
+    }
 }
diff --git a/util/src/test/kotlin/CryptoUtilTest.kt 
b/util/src/test/kotlin/CryptoUtilTest.kt
index da545bf..0bbabc9 100644
--- a/util/src/test/kotlin/CryptoUtilTest.kt
+++ b/util/src/test/kotlin/CryptoUtilTest.kt
@@ -160,5 +160,11 @@ class CryptoUtilTest {
         val expectedEncoding = "C9P6YRG"
         
assert(Base32Crockford.decode(expectedEncoding).toString(Charsets.UTF_8) == 
"blob")
     }
+
+    @Test
+    fun passwordHashing() {
+        val x = CryptoUtil.hashpw("myinsecurepw")
+        assertTrue(CryptoUtil.checkpw("myinsecurepw", x))
+    }
 }
 

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]