[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.