gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (5cc79b33 -> 33e1a6ae)


From: gnunet
Subject: [libeufin] branch master updated (5cc79b33 -> 33e1a6ae)
Date: Wed, 24 Jan 2024 13:10:08 +0100

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

antoine pushed a change to branch master
in repository libeufin.

    from 5cc79b33 Update dependencies
     new 7edfaa73 Improve nexus keys files error logs
     new 33e1a6ae Use java.nio and typed Path everywhere

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 bank/conf/test_tan_err.conf                        |   1 -
 bank/src/main/kotlin/tech/libeufin/bank/Config.kt  |   8 +-
 .../main/kotlin/tech/libeufin/bank/CoreBankApi.kt  |   2 +-
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    |   8 +-
 .../main/kotlin/tech/libeufin/bank/db/Database.kt  |   1 -
 bank/src/test/kotlin/helpers.kt                    |   9 +-
 common/src/main/kotlin/DB.kt                       |  13 +-
 common/src/main/kotlin/TalerConfig.kt              | 122 +++++-------
 common/src/test/kotlin/CryptoUtilTest.kt           |   8 +-
 common/src/test/kotlin/TalerConfigTest.kt          |   5 +-
 contrib/bank.conf                                  |   4 +-
 debian/etc/libeufin/libeufin-bank.conf             |   4 +-
 .../main/kotlin/tech/libeufin/nexus/EbicsFetch.kt  |   2 -
 .../main/kotlin/tech/libeufin/nexus/EbicsSetup.kt  | 100 +++-------
 .../main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt |   8 +-
 .../main/kotlin/tech/libeufin/nexus/KeyFiles.kt    | 214 +++++++++++++++++++++
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  | 144 --------------
 nexus/src/test/kotlin/CliTest.kt                   |  24 ++-
 nexus/src/test/kotlin/Common.kt                    |   2 +-
 nexus/src/test/kotlin/Ebics.kt                     |   9 +-
 nexus/src/test/kotlin/Keys.kt                      |  32 +--
 testbench/src/main/kotlin/Main.kt                  |   8 +-
 testbench/src/test/kotlin/IntegrationTest.kt       |   4 +-
 23 files changed, 351 insertions(+), 381 deletions(-)
 create mode 100644 nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt

diff --git a/bank/conf/test_tan_err.conf b/bank/conf/test_tan_err.conf
index faaf9883..207d36a0 100644
--- a/bank/conf/test_tan_err.conf
+++ b/bank/conf/test_tan_err.conf
@@ -7,7 +7,6 @@ ALLOW_REGISTRATION = yes
 ALLOW_ACCOUNT_DELETION = yes
 ALLOW_EDIT_CASHOUT_PAYTO_URI = yes
 tan_sms = libeufin-tan-fail.sh
-tan_email =
 
 [libeufin-bankdb-postgres]
 CONFIG = postgresql:///libeufincheck
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
index a1dd7711..a7ee5c58 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -22,6 +22,8 @@ import tech.libeufin.common.*
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.Json
 import tech.libeufin.common.DatabaseConfig
+import java.nio.file.*
+import kotlin.io.path.*
 
 /**
  * Application the parsed configuration.
@@ -46,8 +48,8 @@ data class BankConfig(
     val allowConversion: Boolean,
     val fiatCurrency: String?,
     val fiatCurrencySpec: CurrencySpecification?,
-    val spaPath: String?,
-    val tanChannels: Map<TanChannel, String> 
+    val spaPath: Path?,
+    val tanChannels: Map<TanChannel, Path> 
 )
 
 @Serializable
@@ -101,7 +103,7 @@ fun TalerConfig.loadBankConfig(): BankConfig  {
     }
     val tanChannels = buildMap {
         for (channel in TanChannel.entries) {
-            lookupPath("libeufin-bank", "tan_$channel")?.notEmptyOrNull()?.let 
{
+            lookupPath("libeufin-bank", "tan_$channel")?.let {
                 put(channel, it)
             }
         }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
index d17842fe..ca8c114f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -630,7 +630,7 @@ private fun Routing.coreBankTanApi(db: Database, ctx: 
BankConfig) {
                         val tanScript = ctx.tanChannels.get(res.tanChannel) 
                             ?: throw unsupportedTanChannel(res.tanChannel)
                         val exitValue = withContext(Dispatchers.IO) {
-                            val process = ProcessBuilder(tanScript, 
res.tanInfo).start()
+                            val process = ProcessBuilder(tanScript.toString(), 
res.tanInfo).start()
                             try {
                                 process.outputWriter().use { 
it.write(res.tanCode) }
                                 process.onExit().await()
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index ceb7927b..daa27e2c 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -44,7 +44,6 @@ import java.time.Duration
 import java.util.zip.DataFormatException
 import java.util.zip.Inflater
 import java.sql.SQLException
-import java.io.File
 import kotlinx.coroutines.*
 import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.json.*
@@ -55,6 +54,7 @@ import org.postgresql.util.PSQLState
 import tech.libeufin.bank.db.AccountDAO.*
 import tech.libeufin.bank.db.*
 import tech.libeufin.common.*
+import kotlin.io.path.*
 
 private val logger: Logger = LoggerFactory.getLogger("libeufin-bank")
 // Dirty local variable to stop the server in test TODO remove this ugly hack
@@ -214,7 +214,7 @@ fun Application.corebankWebApp(db: Database, ctx: 
BankConfig) {
             get("/") {
                 call.respondRedirect("/webui/")
             }
-            staticFiles("/webui/", File(it))
+            staticFiles("/webui/", it.toFile())
         }
     }
 }
@@ -272,14 +272,14 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP 
server", name = "serve")
                         throw Exception("Account is not an exchange: an 
exchange account named 'exchange' is required for conversion to be enabled")
                     }
                     logger.info("Ensure conversion is enabled")
-                    val sqlProcedures = 
File("${dbCfg.sqlDir}/libeufin-conversion-setup.sql")
+                    val sqlProcedures = 
Path("${dbCfg.sqlDir}/libeufin-conversion-setup.sql")
                     if (!sqlProcedures.exists()) {
                         throw Exception("Missing libeufin-conversion-setup.sql 
file")
                     }
                     db.conn { it.execSQLUpdate(sqlProcedures.readText()) }
                 } else {
                     logger.info("Ensure conversion is disabled")
-                    val sqlProcedures = 
File("${dbCfg.sqlDir}/libeufin-conversion-drop.sql")
+                    val sqlProcedures = 
Path("${dbCfg.sqlDir}/libeufin-conversion-drop.sql")
                     if (!sqlProcedures.exists()) {
                         throw Exception("Missing libeufin-conversion-drop.sql 
file")
                     }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
index f652c3c5..3d9682ca 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
@@ -24,7 +24,6 @@ import org.postgresql.ds.PGSimpleDataSource
 import org.postgresql.util.PSQLState
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import java.io.File
 import java.sql.*
 import java.time.*
 import java.util.*
diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt
index c02a7e77..2b309441 100644
--- a/bank/src/test/kotlin/helpers.kt
+++ b/bank/src/test/kotlin/helpers.kt
@@ -23,9 +23,9 @@ import io.ktor.client.statement.*
 import io.ktor.http.*
 import io.ktor.server.testing.*
 import java.io.ByteArrayOutputStream
-import java.io.File
 import java.util.zip.DeflaterOutputStream
 import kotlin.test.*
+import kotlin.io.path.*
 import kotlin.random.Random
 import kotlinx.coroutines.*
 import kotlinx.serialization.json.*
@@ -66,7 +66,7 @@ fun setup(
                 initializeDatabaseTables(conn, dbCfg, "libeufin-nexus")
                 resetDatabaseTables(conn, dbCfg, "libeufin-bank")
                 initializeDatabaseTables(conn, dbCfg, "libeufin-bank")
-                val sqlProcedures = 
File("${dbCfg.sqlDir}/libeufin-conversion-setup.sql")
+                val sqlProcedures = 
Path("${dbCfg.sqlDir}/libeufin-conversion-setup.sql")
                 conn.execSQLUpdate(sqlProcedures.readText())
             }
             lambda(it, ctx)
@@ -301,10 +301,11 @@ suspend fun ApplicationTestBuilder.convert(amount: 
String): TalerAmount {
 }
 
 suspend fun tanCode(info: String): String? {
-    val file = File("/tmp/tan-$info.txt");
+    // TODO rewrite with only two files access
+    val file = Path("/tmp/tan-$info.txt")
     if (file.exists()) {
         val code = file.readText()
-        file.delete()
+        file.deleteExisting()
         return code;
     } else {
         return null
diff --git a/common/src/main/kotlin/DB.kt b/common/src/main/kotlin/DB.kt
index 5707bd7f..f669b9c2 100644
--- a/common/src/main/kotlin/DB.kt
+++ b/common/src/main/kotlin/DB.kt
@@ -24,7 +24,8 @@ import org.postgresql.jdbc.PgConnection
 import org.postgresql.util.PSQLState
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import java.io.File
+import kotlin.io.path.*
+import java.nio.file.*
 import java.net.URI
 import java.sql.PreparedStatement
 import java.sql.ResultSet
@@ -100,7 +101,7 @@ fun getJdbcConnectionFromPg(pgConn: String): String {
 
 data class DatabaseConfig(
     val dbConnStr: String,
-    val sqlDir: String
+    val sqlDir: Path
 )
 
 fun pgDataSource(dbConfig: String): PGSimpleDataSource {
@@ -219,7 +220,7 @@ fun maybeApplyV(conn: PgConnection, cfg: DatabaseConfig) {
         )
         if (!checkVSchema.executeQueryCheck()) {
             logger.debug("_v schema not found, applying versioning.sql")
-            val sqlVersioning = File("${cfg.sqlDir}/versioning.sql").readText()
+            val sqlVersioning = Path("${cfg.sqlDir}/versioning.sql").readText()
             conn.execSQLUpdate(sqlVersioning)
         }
     }
@@ -243,7 +244,7 @@ fun initializeDatabaseTables(conn: PgConnection, cfg: 
DatabaseConfig, sqlFilePre
                 continue
             }
 
-            val path = File("${cfg.sqlDir}/$sqlFilePrefix-$numStr.sql")
+            val path = Path("${cfg.sqlDir}/$sqlFilePrefix-$numStr.sql")
             if (!path.exists()) {
                 logger.info("path $path doesn't exist anymore, stopping")
                 break
@@ -252,7 +253,7 @@ fun initializeDatabaseTables(conn: PgConnection, cfg: 
DatabaseConfig, sqlFilePre
             val sqlPatchText = path.readText()
             conn.execSQLUpdate(sqlPatchText)
         }
-        val sqlProcedures = File("${cfg.sqlDir}/$sqlFilePrefix-procedures.sql")
+        val sqlProcedures = Path("${cfg.sqlDir}/$sqlFilePrefix-procedures.sql")
         if (!sqlProcedures.exists()) {
             logger.info("no procedures.sql for the SQL collection: 
$sqlFilePrefix")
             return@transaction
@@ -276,7 +277,7 @@ fun resetDatabaseTables(conn: PgConnection, cfg: 
DatabaseConfig, sqlFilePrefix:
         return
     }
 
-    val sqlDrop = File("${cfg.sqlDir}/$sqlFilePrefix-drop.sql").readText()
+    val sqlDrop = Path("${cfg.sqlDir}/$sqlFilePrefix-drop.sql").readText()
     conn.execSQLUpdate(sqlDrop)
 }
 
diff --git a/common/src/main/kotlin/TalerConfig.kt 
b/common/src/main/kotlin/TalerConfig.kt
index a45b33e8..f98f6e5e 100644
--- a/common/src/main/kotlin/TalerConfig.kt
+++ b/common/src/main/kotlin/TalerConfig.kt
@@ -21,11 +21,8 @@ package tech.libeufin.common
 
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import java.io.File
-import java.nio.file.Paths
-import kotlin.io.path.Path
-import kotlin.io.path.isReadable
-import kotlin.io.path.listDirectoryEntries
+import kotlin.io.path.*
+import java.nio.file.*
 
 private val logger: Logger = LoggerFactory.getLogger("libeufin-config")
 
@@ -82,7 +79,7 @@ class TalerConfig(
     private val installPathBinary = configSource.installPathBinary
     val sections: Set<String> get() = sectionMap.keys
 
-    private fun internalLoadFromString(s: String, sourceFilename: String?) {
+    private fun internalLoadFromString(s: String, source: Path?) {
         val lines = s.lines()
         var lineNum = 0
         var currentSection: String? = null
@@ -97,22 +94,20 @@ class TalerConfig(
 
             val directiveMatch = reDirective.matchEntire(line)
             if (directiveMatch != null) {
-                if (sourceFilename == null) {
+                if (source == null) {
                     throw TalerConfigError("Directives are only supported when 
loading from file")
                 }
                 val directiveName = 
directiveMatch.groups[1]!!.value.lowercase()
                 val directiveArg = directiveMatch.groups[2]!!.value
                 when (directiveName) {
                     "inline" -> {
-                        val innerFilename = 
normalizeInlineFilename(sourceFilename, directiveArg.trim())
+                        val innerFilename = 
source.resolveSibling(directiveArg.trim())
                         this.loadFromFilename(innerFilename)
                     }
-
                     "inline-matching" -> {
                         val glob = directiveArg.trim()
-                        this.loadFromGlob(sourceFilename, glob)
+                        this.loadFromGlob(source, glob)
                     }
-
                     "inline-secret" -> {
                         val arg = directiveArg.trim()
                         val sp = arg.split(" ")
@@ -120,7 +115,7 @@ class TalerConfig(
                             throw TalerConfigError("invalid configuration, 
@inline-secret@ directive requires exactly two arguments")
                         }
                         val sectionName = sp[0]
-                        val secretFilename = 
normalizeInlineFilename(sourceFilename, sp[1])
+                        val secretFilename = source.resolveSibling(sp[1])
                         loadSecret(sectionName, secretFilename)
                     }
 
@@ -152,42 +147,19 @@ class TalerConfig(
                 section.entries[optName] = optVal
                 continue
             }
-            throw TalerConfigError("expected section header, option assignment 
or directive in line $lineNum file ${sourceFilename ?: "<input>"}")
+            throw TalerConfigError("expected section header, option assignment 
or directive in line $lineNum file ${source ?: "<input>"}")
         }
     }
 
-    private fun loadFromGlob(parentFilename: String, glob: String) {
-        val fullFileglob: String
-        val parentDir = Path(parentFilename).parent!!.toString()
-        if (glob.startsWith("/")) {
-            fullFileglob = glob
-        } else {
-            fullFileglob = Paths.get(parentDir, glob).toString()
-        }
-
-        val head = Path(fullFileglob).parent.toString()
-        val tail = Path(fullFileglob).fileName.toString()
-
+    private fun loadFromGlob(source: Path, glob: String) {
         // FIXME: Check that the Kotlin glob matches the glob from our spec
-        for (entry in Path(head).listDirectoryEntries(tail)) {
-            loadFromFilename(entry.toString())
-        }
-    }
-
-    private fun normalizeInlineFilename(parentFilename: String, f: String): 
String {
-        if (f[0] == '/') {
-            return f
-        }
-        val parentDirPath = Path(parentFilename).toRealPath().parent
-        if (parentDirPath == null) {
-            throw TalerConfigError("unable to normalize inline path, cannot 
resolve parent directory of $parentFilename")
+        for (entry in source.parent.listDirectoryEntries(glob)) {
+            loadFromFilename(entry)
         }
-        val parentDir = parentDirPath.toString()
-        return Paths.get(parentDir, f).toRealPath().toString()
     }
 
-    private fun loadSecret(sectionName: String, secretFilename: String) {
-        if (!Path(secretFilename).isReadable()) {
+    private fun loadSecret(sectionName: String, secretFilename: Path) {
+        if (!secretFilename.isReadable()) {
             logger.warn("unable to read secrets from $secretFilename")
         } else {
             this.loadFromFilename(secretFilename)
@@ -245,15 +217,13 @@ class TalerConfig(
      * Read values into the configuration from the given entry point
      * filename.  Defaults are *not* loaded automatically.
      */
-    fun loadFromFilename(filename: String) {
-        val f = File(filename)
-        val contents = f.readText()
-        internalLoadFromString(contents, filename)
+    fun loadFromFilename(path: Path) {
+        internalLoadFromString(path.readText(), path)
     }
 
-    private fun loadDefaultsFromDir(dirname: String) {
-        for (filePath in Path(dirname).listDirectoryEntries()) {
-            loadFromFilename(filePath.toString())
+    private fun loadDefaultsFromDir(dirname: Path) {
+        for (filePath in dirname.listDirectoryEntries()) {
+            loadFromFilename(filePath)
         }
     }
 
@@ -263,26 +233,26 @@ class TalerConfig(
      */
     fun loadDefaults() {
         val installDir = getInstallPath()
-        val baseConfigDir = Paths.get(installDir, 
"share/$projectName/config.d").toString()
-        setSystemDefault("PATHS", "PREFIX", "${installDir}/")
-        setSystemDefault("PATHS", "BINDIR", "${installDir}/bin/")
-        setSystemDefault("PATHS", "LIBEXECDIR", 
"${installDir}/$projectName/libexec/")
-        setSystemDefault("PATHS", "DOCDIR", 
"${installDir}/share/doc/$projectName/")
-        setSystemDefault("PATHS", "ICONDIR", "${installDir}/share/icons/")
-        setSystemDefault("PATHS", "LOCALEDIR", "${installDir}/share/locale/")
-        setSystemDefault("PATHS", "LIBDIR", "${installDir}/lib/$projectName/")
-        setSystemDefault("PATHS", "DATADIR", 
"${installDir}/share/$projectName/")
+        val baseConfigDir = Path(installDir, "share/$projectName/config.d")
+        setSystemDefault("PATHS", "PREFIX", "$installDir/")
+        setSystemDefault("PATHS", "BINDIR", "$installDir/bin/")
+        setSystemDefault("PATHS", "LIBEXECDIR", 
"$installDir/$projectName/libexec/")
+        setSystemDefault("PATHS", "DOCDIR", 
"$installDir/share/doc/$projectName/")
+        setSystemDefault("PATHS", "ICONDIR", "$installDir/share/icons/")
+        setSystemDefault("PATHS", "LOCALEDIR", "$installDir/share/locale/")
+        setSystemDefault("PATHS", "LIBDIR", "$installDir/lib/$projectName/")
+        setSystemDefault("PATHS", "DATADIR", "$installDir/share/$projectName/")
         loadDefaultsFromDir(baseConfigDir)
     }
 
-    private fun variableLookup(x: String, recursionDepth: Int = 0): String? {
+    private fun variableLookup(x: String, recursionDepth: Int = 0): Path? {
         val pathRes = this.lookupString("PATHS", x)
         if (pathRes != null) {
             return pathsub(pathRes, recursionDepth + 1)
         }
         val envVal = System.getenv(x)
         if (envVal != null) {
-            return envVal
+            return Path(envVal)
         }
         return null
     }
@@ -294,7 +264,7 @@ class TalerConfig(
      *
      * This substitution is typically only done for paths.
      */
-    fun pathsub(x: String, recursionDepth: Int = 0): String {
+    fun pathsub(x: String, recursionDepth: Int = 0): Path {
         if (recursionDepth > 128) {
             throw TalerConfigError("recursion limit in path substitution 
exceeded")
         }
@@ -372,7 +342,7 @@ class TalerConfig(
                 l = varEnd
             }
         }
-        return result.toString()
+        return Path(result.toString())
     }
 
     /**
@@ -383,7 +353,7 @@ class TalerConfig(
     fun load(entrypoint: String? = null) {
         loadDefaults()
         if (entrypoint != null) {
-            loadFromFilename(entrypoint)
+            loadFromFilename(Path(entrypoint))
         } else {
             val defaultFilename = findDefaultConfigFilename()
             if (defaultFilename != null) {
@@ -397,25 +367,25 @@ class TalerConfig(
      *
      * If no such file can be found, return null.
      */
-    private fun findDefaultConfigFilename(): String? {
+    private fun findDefaultConfigFilename(): Path? {
         val xdg = System.getenv("XDG_CONFIG_HOME")
         val home = System.getenv("HOME")
 
-        var filename: String? = null
+        var filename: Path? = null
         if (xdg != null) {
-            filename = Paths.get(xdg, "$componentName.conf").toString()
+            filename = Path(xdg, "$componentName.conf")
         } else if (home != null) {
-            filename = Paths.get(home, 
".config/$componentName.conf").toString()
+            filename = Path(home, ".config/$componentName.conf")
         }
-        if (filename != null && File(filename).exists()) {
+        if (filename != null && filename.exists()) {
             return filename
         }
-        val etc1 = "/etc/$componentName.conf"
-        if (File(etc1).exists()) {
+        val etc1 = Path("/etc/$componentName.conf")
+        if (etc1.exists()) {
             return etc1
         }
-        val etc2 = "/etc/$projectName/$componentName.conf"
-        if (File(etc2).exists()) {
+        val etc2 = Path("/etc/$projectName/$componentName.conf")
+        if (etc2.exists()) {
             return etc2
         }
         return null
@@ -436,9 +406,9 @@ class TalerConfig(
         val pathEnv = System.getenv("PATH")
         val paths = pathEnv.split(":")
         for (p in paths) {
-            val possiblePath = Paths.get(p, name).toString()
-            if (File(possiblePath).exists()) {
-                return Paths.get(p, "..").toRealPath().toString()
+            val possiblePath = Path(p, name)
+            if (possiblePath.exists()) {
+                return Path(p, "..").toRealPath().toString()
             }
         }
         return "/usr"
@@ -478,12 +448,12 @@ class TalerConfig(
         lookupBoolean(section, option) ?:
             throw TalerConfigError("expected boolean in configuration section 
$section option $option")
 
-    fun lookupPath(section: String, option: String): String? {
+    fun lookupPath(section: String, option: String): Path? {
         val entry = lookupString(section, option) ?: return null
         return pathsub(entry)
     }
 
-    fun requirePath(section: String, option: String): String  =
+    fun requirePath(section: String, option: String): Path =
         lookupPath(section, option) ?:
             throw TalerConfigError("expected path for section $section option 
$option")
 }
diff --git a/common/src/test/kotlin/CryptoUtilTest.kt 
b/common/src/test/kotlin/CryptoUtilTest.kt
index 09f17fca..b14ada07 100644
--- a/common/src/test/kotlin/CryptoUtilTest.kt
+++ b/common/src/test/kotlin/CryptoUtilTest.kt
@@ -19,8 +19,8 @@
 
 import org.junit.Ignore
 import org.junit.Test
+import kotlin.io.path.*
 import tech.libeufin.common.*
-import java.io.File
 import java.security.KeyPairGenerator
 import java.security.interfaces.RSAPrivateCrtKey
 import java.util.*
@@ -179,7 +179,7 @@ class CryptoUtilTest {
     fun gnunetEncodeCheck() {
         val blob = ByteArray(30)
         Random().nextBytes(blob)
-        val b = File("/tmp/libeufin-blob.bin")
+        val b = Path("/tmp/libeufin-blob.bin")
         b.writeBytes(blob)
         val enc = Base32Crockford.encode(blob)
         // The following output needs to match the one from
@@ -194,8 +194,8 @@ class CryptoUtilTest {
     @Ignore
     fun gnunetDecodeCheck() {
         // condition: "gnunet-base32 -d /tmp/blob.enc" needs to decode to 
/tmp/blob.bin
-        val blob = File("/tmp/blob.bin").readBytes()
-        val blobEnc = File("/tmp/blob.enc").readText(Charsets.UTF_8)
+        val blob = Path("/tmp/blob.bin").readBytes()
+        val blobEnc = Path("/tmp/blob.enc").readText(Charsets.UTF_8)
         val dec = Base32Crockford.decode(blobEnc)
         assertTrue(blob.contentEquals(dec))
     }
diff --git a/common/src/test/kotlin/TalerConfigTest.kt 
b/common/src/test/kotlin/TalerConfigTest.kt
index 024ca460..0167aa00 100644
--- a/common/src/test/kotlin/TalerConfigTest.kt
+++ b/common/src/test/kotlin/TalerConfigTest.kt
@@ -20,6 +20,7 @@
 import org.junit.Test
 import kotlin.test.assertEquals
 import tech.libeufin.common.*
+import kotlin.io.path.*
 
 class TalerConfigTest {
 
@@ -54,10 +55,10 @@ class TalerConfigTest {
         conf.putValueString("foo", "bar2", "baz")
 
         assertEquals("baz", conf.lookupString("foo", "bar"))
-        assertEquals("baz", conf.lookupPath("foo", "bar"))
+        assertEquals(Path("baz"), conf.lookupPath("foo", "bar"))
 
         conf.putValueString("foo", "dir1", "foo/\$DATADIR/bar")
 
-        assertEquals("foo/mydir/bar", conf.lookupPath("foo", "dir1"))
+        assertEquals(Path("foo/mydir/bar"), conf.lookupPath("foo", "dir1"))
     }
 }
diff --git a/contrib/bank.conf b/contrib/bank.conf
index 7fe0c853..cb470a49 100644
--- a/contrib/bank.conf
+++ b/contrib/bank.conf
@@ -28,10 +28,10 @@ CURRENCY = KUDOS
 # FIAT_CURRENCY = EUR
 
 # Path to TAN challenge transmission script via sms. If not specified, this 
TAN channel will not be supported.
-# TAN_SMS = 
+# TAN_SMS = libeufin-tan-sms.sh
 
 # Path to TAN challenge transmission script via email. If not specified, this 
TAN channel will not be supported.
-# TAN_EMAIL =
+# TAN_EMAIL = libeufin-tan-email.sh
 
 # How "libeufin-bank serve" serves its API, this can either be tcp or unix
 SERVE = tcp
diff --git a/debian/etc/libeufin/libeufin-bank.conf 
b/debian/etc/libeufin/libeufin-bank.conf
index b0a94c14..3f92f49b 100644
--- a/debian/etc/libeufin/libeufin-bank.conf
+++ b/debian/etc/libeufin/libeufin-bank.conf
@@ -21,10 +21,10 @@ CURRENCY = KUDOS
 # ALLOW_CONVERSION = no
 
 # Path to TAN challenge transmission script via sms. If not specified, this 
TAN channel wil be unuspported.
-TAN_SMS = 
+# TAN_SMS = libeufin-tan-sms.sh
 
 # Path to TAN challenge transmission script via email. If not specified, this 
TAN channel wil be unuspported.
-TAN_EMAIL =
+# TAN_EMAIL = libeufin-tan-email.sh
 
 # Where "libeufin-bank serve" serves its API
 SERVE = tcp
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
index 2c26ea75..6fc7a7fb 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
@@ -26,9 +26,7 @@ import kotlinx.coroutines.*
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.common.*
 import tech.libeufin.ebics.ebics_h005.Ebics3Request
-import java.io.File
 import java.io.IOException
-import java.nio.file.Path
 import java.time.Instant
 import java.time.LocalDate
 import java.time.ZoneId
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index 11fe512c..1d81e719 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -25,56 +25,14 @@ import com.github.ajalt.clikt.parameters.groups.*
 import io.ktor.client.*
 import kotlinx.coroutines.runBlocking
 import tech.libeufin.ebics.ebics_h004.EbicsTypes
-import java.io.File
 import kotlinx.serialization.encodeToString
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.common.*
 import tech.libeufin.ebics.*
 import tech.libeufin.ebics.ebics_h004.HTDResponseOrderData
 import java.time.Instant
-import kotlin.reflect.typeOf
-import java.nio.file.Files
-import java.nio.file.StandardCopyOption
 import kotlin.io.path.*
-
-/**
- * Writes the JSON content to disk.  Used when we create or update
- * keys and other metadata JSON content to disk.  WARNING: this overrides
- * silently what's found under the given location!
- *
- * @param obj the class representing the JSON content to store to disk.
- * @param path where to store `obj`
- */
-inline fun <reified T> syncJsonToDisk(obj: T, path: String) {
-    val content = try {
-        myJson.encodeToString(obj)
-    } catch (e: Exception) {
-        throw Exception("Could not encode the input '${typeOf<T>()}' to JSON", 
e)
-    }
-    try {
-        // Write to temp file then rename to enable atomicity when possible
-        val path = Path(path).absolute()
-        val tmp = Files.createTempFile(path.parent, "tmp_", 
"_${path.fileName}")
-        tmp.writeText(content)
-        tmp.moveTo(path, StandardCopyOption.REPLACE_EXISTING);
-    } catch (e: Exception) {
-        throw Exception("Could not write JSON content at $path", e)
-    }
-}
-
-/**
- * Generates new client private keys.
- *
- * @return [ClientPrivateKeysFile]
- */
-fun generateNewKeys(): ClientPrivateKeysFile =
-    ClientPrivateKeysFile(
-        authentication_private_key = 
CryptoUtil.generateRsaKeyPair(2048).private,
-        encryption_private_key = CryptoUtil.generateRsaKeyPair(2048).private,
-        signature_private_key = CryptoUtil.generateRsaKeyPair(2048).private,
-        submitted_hia = false,
-        submitted_ini = false
-    )
+import java.nio.file.*
 
 /**
  * Obtains the client private keys, regardless of them being
@@ -84,20 +42,15 @@ fun generateNewKeys(): ClientPrivateKeysFile =
  * @param path path to the file that contains the keys.
  * @return current or new client keys
  */
-private fun preparePrivateKeys(path: String): ClientPrivateKeysFile {
+private fun loadOrGenerateClientKeys(path: Path): ClientPrivateKeysFile {
     // If exists load from disk
-    val current = loadPrivateKeysFromDisk(path)
+    val current = loadClientKeys(path)
     if (current != null) return current
     // Else create new keys
-    try {
-        val newKeys = generateNewKeys()
-        syncJsonToDisk(newKeys, path)
-        logger.info("New client keys created at: $path")
-        return newKeys
-    } catch (e: Exception) {
-        throw Exception("Could not create client keys at $path", e) 
-        // TODO Better log
-    }
+    val newKeys = generateNewKeys()
+    persistClientKeys(newKeys, path)
+    logger.info("New client private keys created at '$path'")
+    return newKeys
 }
 
 /**
@@ -159,7 +112,7 @@ private fun handleHpbResponse(
     val hpbObj = try {
         parseEbicsHpbOrder(hpbBytes)
     } catch (e: Exception) {
-        throw Exception("HPB response content seems invalid: e")
+        throw Exception("HPB response content seems invalid", e)
     }
     val encPub = try {
         CryptoUtil.loadRsaPublicKey(hpbObj.encryptionPubKey.encoded)
@@ -176,11 +129,7 @@ private fun handleHpbResponse(
         bank_encryption_public_key = encPub,
         accepted = false
     )
-    try {
-        syncJsonToDisk(json, cfg.bankPublicKeysFilename)
-    } catch (e: Exception) {
-        throw Exception("Failed to persist the bank keys to disk", e)
-    }
+    persistBankKeys(json, cfg.bankPublicKeysFilename)
 }
 
 /**
@@ -201,7 +150,7 @@ suspend fun doKeysRequestAndUpdateState(
     client: HttpClient,
     orderType: KeysOrderType
 ) {
-    logger.debug("Doing key request ${orderType.name}")
+    logger.info("Doing key request ${orderType.name}")
     val req = when(orderType) {
         KeysOrderType.INI -> generateIniMessage(cfg, privs)
         KeysOrderType.HIA -> generateHiaMessage(cfg, privs)
@@ -228,7 +177,7 @@ suspend fun doKeysRequestAndUpdateState(
         KeysOrderType.HPB -> return handleHpbResponse(cfg, ebics)
     }
     try {
-        syncJsonToDisk(privs, cfg.clientPrivateKeysFilename)
+        persistClientKeys(privs, cfg.clientPrivateKeysFilename)
     } catch (e: Exception) {
         throw Exception("Could not update the ${orderType.name} state on 
disk", e)
     }
@@ -254,9 +203,10 @@ fun extractEbicsConfig(configFile: String?): 
EbicsSetupConfig {
  */
 private fun makePdf(privs: ClientPrivateKeysFile, cfg: EbicsSetupConfig) {
     val pdf = generateKeysPdf(privs, cfg)
-    val pdfFile = 
File("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf")
+    // TODO rewrite with a single file access
+    val pdfFile = 
Path("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf")
     if (pdfFile.exists()) {
-        throw Exception("PDF file exists already at: ${pdfFile.path}, not 
overriding it")
+        throw Exception("PDF file exists already at: ${pdfFile}, not 
overriding it")
     }
     try {
         pdfFile.writeBytes(pdf)
@@ -286,7 +236,7 @@ class EbicsSetup: CliktCommand("Set up the EBICS 
subscriber") {
     override fun run() = cliCmd(logger, common.log) {
         val cfg = extractEbicsConfig(common.config)
         // Config is sane.  Go (maybe) making the private keys.
-        val clientKeys = preparePrivateKeys(cfg.clientPrivateKeysFilename)
+        val clientKeys = 
loadOrGenerateClientKeys(cfg.clientPrivateKeysFilename)
         val httpClient = HttpClient()
         // Privs exist.  Upload their pubs
         val keysNotSub = !clientKeys.submitted_ini || !clientKeys.submitted_hia
@@ -299,8 +249,8 @@ class EbicsSetup: CliktCommand("Set up the EBICS 
subscriber") {
         // Eject PDF if the keys were submitted for the first time, or the 
user asked.
         if (keysNotSub || generateRegistrationPdf) makePdf(clientKeys, cfg)
         // Checking if the bank keys exist on disk.
-        val bankKeysFile = File(cfg.bankPublicKeysFilename)
-        if (!bankKeysFile.exists()) {
+        var bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)
+        if (bankKeys == null) {
             runBlocking {
                 try {
                     doKeysRequestAndUpdateState(
@@ -314,23 +264,19 @@ class EbicsSetup: CliktCommand("Set up the EBICS 
subscriber") {
                 }
             }
             logger.info("Bank keys stored at ${cfg.bankPublicKeysFilename}")
-        }
-        // bank keys made it to the disk, check if they're accepted.
-        val bankKeysMaybe = loadBankKeys(cfg.bankPublicKeysFilename)
-        if (bankKeysMaybe == null) {
-            throw Exception("Although previous checks, could not load the bank 
keys file from: ${cfg.bankPublicKeysFilename}")
+            bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)!!
         }
 
-        if (!bankKeysMaybe.accepted) {
+        if (!bankKeys.accepted) {
             // Finishing the setup by accepting the bank keys.
-            if (autoAcceptKeys) bankKeysMaybe.accepted = true
-            else bankKeysMaybe.accepted = askUserToAcceptKeys(bankKeysMaybe)
+            if (autoAcceptKeys) bankKeys.accepted = true
+            else bankKeys.accepted = askUserToAcceptKeys(bankKeys)
 
-            if (!bankKeysMaybe.accepted) {
+            if (!bankKeys.accepted) {
                 throw Exception("Cannot successfully finish the setup without 
accepting the bank keys.")
             }
             try {
-                syncJsonToDisk(bankKeysMaybe, cfg.bankPublicKeysFilename)
+                persistBankKeys(bankKeys, cfg.bankPublicKeysFilename)
             } catch (e: Exception) {
                 throw Exception("Could not set bank keys as accepted on 
disk.", e)
             }
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
index dbd178b3..ca5e98fe 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -29,14 +29,8 @@ import tech.libeufin.nexus.ebics.EbicsSideException
 import tech.libeufin.nexus.ebics.EbicsUploadException
 import tech.libeufin.nexus.ebics.submitPain001
 import tech.libeufin.common.*
-import java.io.File
-import java.nio.file.Path
-import java.time.Instant
-import java.time.LocalDate
-import java.time.ZoneId
+import java.time.*
 import java.util.*
-import kotlin.io.path.createDirectories
-import kotlin.io.path.*
 
 /**
  * Possible stages when an error may occur.  These stages
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt
new file mode 100644
index 00000000..a33344d7
--- /dev/null
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt
@@ -0,0 +1,214 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2023 Stanisci and Dold.
+
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
+ * Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+package tech.libeufin.nexus
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.modules.SerializersModule
+import java.security.interfaces.RSAPrivateCrtKey
+import java.security.interfaces.RSAPublicKey
+import tech.libeufin.common.*
+import java.nio.file.*
+import kotlin.io.path.*
+
+val JSON = Json {
+    this.serializersModule = SerializersModule {
+        contextual(RSAPrivateCrtKey::class) { RSAPrivateCrtKeySerializer }
+        contextual(RSAPublicKey::class) { RSAPublicKeySerializer }
+    }
+}
+
+/**
+ * Converts base 32 representation of RSA public keys and vice versa.
+ */
+object RSAPublicKeySerializer : KSerializer<RSAPublicKey> {
+    override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("RSAPublicKey", PrimitiveKind.STRING)
+    override fun serialize(encoder: Encoder, value: RSAPublicKey) {
+        encoder.encodeString(Base32Crockford.encode(value.encoded))
+    }
+
+    // Caller must handle exceptions here.
+    override fun deserialize(decoder: Decoder): RSAPublicKey {
+        val fieldValue = decoder.decodeString()
+        val bytes = Base32Crockford.decode(fieldValue)
+        return CryptoUtil.loadRsaPublicKey(bytes)
+    }
+}
+
+/**
+ * Converts base 32 representation of RSA private keys and vice versa.
+ */
+object RSAPrivateCrtKeySerializer : KSerializer<RSAPrivateCrtKey> {
+    override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("RSAPrivateCrtKey", PrimitiveKind.STRING)
+    override fun serialize(encoder: Encoder, value: RSAPrivateCrtKey) {
+        encoder.encodeString(Base32Crockford.encode(value.encoded))
+    }
+
+    // Caller must handle exceptions here.
+    override fun deserialize(decoder: Decoder): RSAPrivateCrtKey {
+        val fieldValue = decoder.decodeString()
+        val bytes = Base32Crockford.decode(fieldValue)
+        return CryptoUtil.loadRsaPrivateKey(bytes)
+    }
+}
+
+/**
+ * Structure of the JSON file that contains the client
+ * private keys on disk.
+ */
+@Serializable
+data class ClientPrivateKeysFile(
+    @Contextual val signature_private_key: RSAPrivateCrtKey,
+    @Contextual val encryption_private_key: RSAPrivateCrtKey,
+    @Contextual val authentication_private_key: RSAPrivateCrtKey,
+    var submitted_ini: Boolean,
+    var submitted_hia: Boolean
+)
+
+/**
+ * Structure of the JSON file that contains the bank
+ * public keys on disk.
+ */
+@Serializable
+data class BankPublicKeysFile(
+    @Contextual val bank_encryption_public_key: RSAPublicKey,
+    @Contextual val bank_authentication_public_key: RSAPublicKey,
+    var accepted: Boolean
+)
+
+/**
+ * Generates new client private keys.
+ *
+ * @return [ClientPrivateKeysFile]
+ */
+fun generateNewKeys(): ClientPrivateKeysFile =
+    ClientPrivateKeysFile(
+        authentication_private_key = 
CryptoUtil.generateRsaKeyPair(2048).private,
+        encryption_private_key = CryptoUtil.generateRsaKeyPair(2048).private,
+        signature_private_key = CryptoUtil.generateRsaKeyPair(2048).private,
+        submitted_hia = false,
+        submitted_ini = false
+    )
+
+private inline fun <reified T> persistJsonFile(obj: T, path: Path, name: 
String) {
+    val content = try {
+        JSON.encodeToString(obj)
+    } catch (e: Exception) {
+        throw Exception("Could not encode $name", e)
+    }
+    val parent = try {
+        path.parent ?: path.absolute().parent
+    } catch (e: Exception) {
+        throw Exception("Could not write $name at '$path'", e)
+    }
+    try {
+        // Write to temp file then rename to enable atomicity when possible
+        val tmp = Files.createTempFile(parent, "tmp_", "_${path.fileName}")
+        tmp.writeText(content)
+        tmp.moveTo(path, StandardCopyOption.REPLACE_EXISTING);
+    } catch (e: Exception) {
+        when {
+            !parent.toFile().canWrite() -> throw Exception("Could not write 
$name at '$path': permission denied on '$parent'")
+            !path.toFile().canWrite() -> throw Exception("Could not write 
$name at '$path': permission denied")
+            else -> throw Exception("Could not write $name at '$path'", e)
+        }
+    }
+}
+
+/**
+ * Persist the bank keys file to disk
+ *
+ * @param location the keys file location
+ */
+fun persistBankKeys(keys: BankPublicKeysFile, location: Path) = 
persistJsonFile(keys, location, "bank public keys")
+
+/**
+ * Persist the client keys file to disk
+ *
+ * @param location the keys file location
+ */
+fun persistClientKeys(keys: ClientPrivateKeysFile, location: Path) = 
persistJsonFile(keys, location, "client private keys")
+
+
+private inline fun <reified T> loadJsonFile(path: Path, name: String): T? {
+    val content = try {
+        path.readText()
+    } catch (e: Exception) {
+        when {
+            e is NoSuchFileException -> return null
+            e is AccessDeniedException -> throw Exception("Could not read 
$name at '$path': permission denied")
+            else -> throw Exception("Could not read $name at '$path'", e)
+        }
+    }
+    return try {
+        JSON.decodeFromString(content)
+    } catch (e: Exception) {
+        throw Exception("Could not decode $name at '$path'", e)
+    }
+}
+
+/**
+ * Load the bank keys file from disk.
+ *
+ * @param location the keys file location.
+ * @return the internal JSON representation of the keys file,
+ *         or null if the file does not exist
+ */
+fun loadBankKeys(location: Path): BankPublicKeysFile? = loadJsonFile(location, 
"bank public keys")
+
+/**
+ * Load the client keys file from disk.
+ *
+ * @param location the keys file location.
+ * @return the internal JSON representation of the keys file,
+ *         or null if the file does not exist
+ */
+fun loadClientKeys(location: Path): ClientPrivateKeysFile? = 
loadJsonFile(location, "client private keys")
+
+/**
+ * Load client and bank keys from disk.
+ * Checks that the keying process has been fully completed.
+ * 
+ * Helps to fail before starting to talk EBICS to the bank.
+ *
+ * @param cfg configuration handle.
+ * @return both client and bank keys
+ */
+fun expectFullKeys(
+    cfg: EbicsSetupConfig
+): Pair<ClientPrivateKeysFile, BankPublicKeysFile> {
+    val clientKeys = loadClientKeys(cfg.clientPrivateKeysFilename)
+    if (clientKeys == null) {
+        throw Exception("Missing client private keys file at 
'${cfg.clientPrivateKeysFilename}', run 'libeufin-nexus ebics-setup' first")
+    } else if (!clientKeys.submitted_ini || !clientKeys.submitted_hia) {
+        throw Exception("Unsubmitted client private keys, run 'libeufin-nexus 
ebics-setup' first")
+    }
+    val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)
+    if (bankKeys == null) {
+        throw Exception("Missing bank public keys at 
'${cfg.bankPublicKeysFilename}', run 'libeufin-nexus ebics-setup' first")
+    } else if (!bankKeys.accepted) {
+        throw Exception("Unaccepted bank public keys, run 'libeufin-nexus 
ebics-setup' until accepting the bank keys")
+    }
+    return Pair(clientKeys, bankKeys)
+}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index 607e39c1..4d27910a 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -29,33 +29,13 @@ import com.github.ajalt.clikt.core.subcommands
 import com.github.ajalt.clikt.parameters.options.versionOption
 import io.ktor.client.*
 import io.ktor.util.*
-import kotlinx.serialization.Contextual
-import kotlinx.serialization.KSerializer
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import java.io.File
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.descriptors.PrimitiveKind
-import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
-import kotlinx.serialization.descriptors.SerialDescriptor
-import kotlinx.serialization.encoding.Decoder
-import kotlinx.serialization.encoding.Encoder
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.modules.SerializersModule
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.common.*
-import java.security.interfaces.RSAPrivateCrtKey
-import java.security.interfaces.RSAPublicKey
-import java.io.FileNotFoundException
 
 val NEXUS_CONFIG_SOURCE = ConfigSource("libeufin", "libeufin-nexus", 
"libeufin-nexus")
 val logger: Logger = LoggerFactory.getLogger("libeufin-nexus")
-val myJson = Json {
-    this.serializersModule = SerializersModule {
-        contextual(RSAPrivateCrtKey::class) { RSAPrivateCrtKeySerializer }
-        contextual(RSAPublicKey::class) { RSAPublicKeySerializer }
-    }
-}
 
 /**
  * Triple identifying one IBAN bank account.
@@ -209,130 +189,6 @@ class EbicsSetupConfig(val config: TalerConfig) {
     }
 }
 
-/**
- * Converts base 32 representation of RSA public keys and vice versa.
- */
-object RSAPublicKeySerializer : KSerializer<RSAPublicKey> {
-    override val descriptor: SerialDescriptor =
-        PrimitiveSerialDescriptor("RSAPublicKey", PrimitiveKind.STRING)
-    override fun serialize(encoder: Encoder, value: RSAPublicKey) {
-        encoder.encodeString(Base32Crockford.encode(value.encoded))
-    }
-
-    // Caller must handle exceptions here.
-    override fun deserialize(decoder: Decoder): RSAPublicKey {
-        val fieldValue = decoder.decodeString()
-        val bytes = Base32Crockford.decode(fieldValue)
-        return CryptoUtil.loadRsaPublicKey(bytes)
-    }
-}
-
-/**
- * Converts base 32 representation of RSA private keys and vice versa.
- */
-object RSAPrivateCrtKeySerializer : KSerializer<RSAPrivateCrtKey> {
-    override val descriptor: SerialDescriptor =
-        PrimitiveSerialDescriptor("RSAPrivateCrtKey", PrimitiveKind.STRING)
-    override fun serialize(encoder: Encoder, value: RSAPrivateCrtKey) {
-        encoder.encodeString(Base32Crockford.encode(value.encoded))
-    }
-
-    // Caller must handle exceptions here.
-    override fun deserialize(decoder: Decoder): RSAPrivateCrtKey {
-        val fieldValue = decoder.decodeString()
-        val bytes = Base32Crockford.decode(fieldValue)
-        return CryptoUtil.loadRsaPrivateKey(bytes)
-    }
-}
-
-/**
- * Structure of the JSON file that contains the client
- * private keys on disk.
- */
-@Serializable
-data class ClientPrivateKeysFile(
-    @Contextual val signature_private_key: RSAPrivateCrtKey,
-    @Contextual val encryption_private_key: RSAPrivateCrtKey,
-    @Contextual val authentication_private_key: RSAPrivateCrtKey,
-    var submitted_ini: Boolean,
-    var submitted_hia: Boolean
-)
-
-/**
- * Structure of the JSON file that contains the bank
- * public keys on disk.
- */
-@Serializable
-data class BankPublicKeysFile(
-    @Contextual val bank_encryption_public_key: RSAPublicKey,
-    @Contextual val bank_authentication_public_key: RSAPublicKey,
-    var accepted: Boolean
-)
-
-/**
- * Load client and bank keys from disk.
- * Checks that the keying process has been fully completed.
- * 
- * Helps to fail before starting to talk EBICS to the bank.
- *
- * @param cfg configuration handle.
- * @return both client and bank keys
- */
-fun expectFullKeys(
-    cfg: EbicsSetupConfig
-): Pair<ClientPrivateKeysFile, BankPublicKeysFile> {
-    val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)
-    if (clientKeys == null) {
-        throw Exception("Missing client private keys file at 
'${cfg.clientPrivateKeysFilename}', run 'libeufin-nexus ebics-setup' first")
-    } else if (!clientKeys.submitted_ini || !clientKeys.submitted_hia) {
-        throw Exception("Unsubmitted client private keys, run 'libeufin-nexus 
ebics-setup' first")
-    }
-    val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)
-    if (bankKeys == null) {
-        throw Exception("Missing bank public keys at 
'${cfg.bankPublicKeysFilename}', run 'libeufin-nexus ebics-setup' first")
-    } else if (!bankKeys.accepted) {
-        throw Exception("Unaccepted bank public keys, run 'libeufin-nexus 
ebics-setup' until accepting the bank keys")
-    }
-    return Pair(clientKeys, bankKeys)
-}
-
-private inline fun <reified T> loadJsonFile(path: String, name: String): T? {
-    val file = File(path)
-    val content = try {
-        file.readText()
-    } catch (e: Exception) {
-        // FileNotFoundException can be thrown if the file exists, but is not 
accessible...
-        when {
-            !file.exists() -> return null
-            !file.canRead() -> throw Exception("Could not read $name at 
'$path': permission denied")
-            else -> throw Exception("Could not read $name at '$path'", e)
-        }
-    }
-    return try {
-        myJson.decodeFromString(content)
-    } catch (e: Exception) {
-        throw Exception("Could not decode $name at '$path'", e)
-    }
-}
-
-/**
- * Load the bank keys file from disk.
- *
- * @param location the keys file location.
- * @return the internal JSON representation of the keys file,
- *         or null if the file does not exist
- */
-fun loadBankKeys(location: String): BankPublicKeysFile? = 
loadJsonFile(location, "bank public keys")
-
-/**
- * Load the client keys file from disk.
- *
- * @param location the keys file location.
- * @return the internal JSON representation of the keys file,
- *         or null if the file does not exist
- */
-fun loadPrivateKeysFromDisk(location: String): ClientPrivateKeysFile? = 
loadJsonFile(location, "client private keys")
-
 /**
  * Abstracts the config loading
  *
diff --git a/nexus/src/test/kotlin/CliTest.kt b/nexus/src/test/kotlin/CliTest.kt
index a57088c7..56570bea 100644
--- a/nexus/src/test/kotlin/CliTest.kt
+++ b/nexus/src/test/kotlin/CliTest.kt
@@ -50,10 +50,11 @@ class CliTest {
         val allCmds = listOf("ebics-submit", "ebics-fetch", "ebics-setup")
         val conf = "conf/test.conf"
         val cfg = loadConfig(conf)
-        val clientKeysPath = Path(cfg.requireString("nexus-ebics", 
"client_private_keys_file"))
-        val bankKeysPath = Path(cfg.requireString("nexus-ebics", 
"bank_public_keys_file"))
-        clientKeysPath.parent?.createDirectories()
-        bankKeysPath.parent?.createDirectories()
+        val clientKeysPath = cfg.requirePath("nexus-ebics", 
"client_private_keys_file")
+        val bankKeysPath = cfg.requirePath("nexus-ebics", 
"bank_public_keys_file")
+        clientKeysPath.parent!!.createDirectories()
+        clientKeysPath.parent!!.toFile().setWritable(true)
+        bankKeysPath.parent!!.createDirectories()
         
         // Missing client keys
         clientKeysPath.deleteIfExists()
@@ -71,16 +72,16 @@ class CliTest {
             nexusCmd.testErr("$cmd -c $conf", "Could not read client private 
keys at '$clientKeysPath': permission denied")
         }
         // Unfinished client
-        syncJsonToDisk(generateNewKeys(), clientKeysPath.toString())
+        persistClientKeys(generateNewKeys(), clientKeysPath)
         for (cmd in cmds) {
             nexusCmd.testErr("$cmd -c $conf", "Unsubmitted client private 
keys, run 'libeufin-nexus ebics-setup' first")
         }
 
         // Missing bank keys
-        syncJsonToDisk(generateNewKeys().apply {
+        persistClientKeys(generateNewKeys().apply {
             submitted_hia = true
             submitted_ini = true
-        }, clientKeysPath.toString())
+        }, clientKeysPath)
         bankKeysPath.deleteIfExists()
         for (cmd in cmds) {
             nexusCmd.testErr("$cmd -c $conf", "Missing bank public keys at 
'$bankKeysPath', run 'libeufin-nexus ebics-setup' first")
@@ -96,13 +97,18 @@ class CliTest {
             nexusCmd.testErr("$cmd -c $conf", "Could not read bank public keys 
at '$bankKeysPath': permission denied")
         }
         // Unfinished bank
-        syncJsonToDisk(BankPublicKeysFile(
+        persistBankKeys(BankPublicKeysFile(
             bank_authentication_public_key = 
CryptoUtil.generateRsaKeyPair(2048).public,
             bank_encryption_public_key = 
CryptoUtil.generateRsaKeyPair(2048).public,
             accepted = false
-        ), bankKeysPath.toString())
+        ), bankKeysPath)
         for (cmd in cmds) {
             nexusCmd.testErr("$cmd -c $conf", "Unaccepted bank public keys, 
run 'libeufin-nexus ebics-setup' until accepting the bank keys")
         }
+
+        // Missing permission
+        clientKeysPath.deleteIfExists()
+        clientKeysPath.parent!!.toFile().setWritable(false)
+        nexusCmd.testErr("ebics-setup -c $conf", "Could not write client 
private keys at '$clientKeysPath': permission denied on 
'${clientKeysPath.parent}'")
     }
 }
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt
index f5c2f07e..f6db6bfe 100644
--- a/nexus/src/test/kotlin/Common.kt
+++ b/nexus/src/test/kotlin/Common.kt
@@ -43,7 +43,7 @@ fun prepDb(cfg: TalerConfig): Database {
     cfg.loadDefaults()
     val dbCfg = DatabaseConfig(
         dbConnStr = "postgresql:///libeufincheck",
-        sqlDir = cfg.requirePath("paths", "datadir") + "sql"
+        sqlDir = cfg.requirePath("paths", "datadir").resolve("sql")
     )
     pgDataSource(dbCfg.dbConnStr).pgConnection().use { conn ->
         println("SQL dir for testing: ${dbCfg.sqlDir}")
diff --git a/nexus/src/test/kotlin/Ebics.kt b/nexus/src/test/kotlin/Ebics.kt
index 5f3a3782..f992c82d 100644
--- a/nexus/src/test/kotlin/Ebics.kt
+++ b/nexus/src/test/kotlin/Ebics.kt
@@ -27,11 +27,8 @@ import tech.libeufin.nexus.*
 import tech.libeufin.nexus.ebics.*
 import tech.libeufin.ebics.XMLUtil
 import tech.libeufin.ebics.ebics_h004.EbicsUnsecuredRequest
-import java.io.File
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
+import kotlin.test.*
+import kotlin.io.path.*
 
 class Ebics {
     // Checks XML is valid and INI.
@@ -83,6 +80,6 @@ class Ebics {
     @Test
     fun keysPdf() {
         val pdf = generateKeysPdf(clientKeys, config)
-        File("/tmp/libeufin-nexus-test-keys.pdf").writeBytes(pdf)
+        Path("/tmp/libeufin-nexus-test-keys.pdf").writeBytes(pdf)
     }
 }
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/Keys.kt b/nexus/src/test/kotlin/Keys.kt
index 41b6b11f..86d8022b 100644
--- a/nexus/src/test/kotlin/Keys.kt
+++ b/nexus/src/test/kotlin/Keys.kt
@@ -20,7 +20,7 @@
 import org.junit.Test
 import tech.libeufin.nexus.*
 import tech.libeufin.common.CryptoUtil
-import java.io.File
+import kotlin.io.path.*
 import kotlin.test.*
 
 class PublicKeys {
@@ -42,9 +42,9 @@ class PublicKeys {
             bank_encryption_public_key = 
CryptoUtil.generateRsaKeyPair(2028).public
         )
         // storing them on disk.
-        syncJsonToDisk(fileContent, "/tmp/nexus-tests-bank-keys.json")
+        persistBankKeys(fileContent, Path("/tmp/nexus-tests-bank-keys.json"))
         // loading them and check that values are the same.
-        val fromDisk = loadBankKeys("/tmp/nexus-tests-bank-keys.json")
+        val fromDisk = loadBankKeys(Path("/tmp/nexus-tests-bank-keys.json"))
         assertNotNull(fromDisk)
         assertTrue {
             fromDisk.accepted &&
@@ -54,25 +54,13 @@ class PublicKeys {
     }
     @Test
     fun loadNotFound() {
-        assertNull(loadBankKeys("/tmp/highly-unlikely-to-be-found.json"))
+        assertNull(loadBankKeys(Path("/tmp/highly-unlikely-to-be-found.json")))
     }
 }
 class PrivateKeys {
-    val f = File("/tmp/nexus-privs-test.json")
+    val f = Path("/tmp/nexus-privs-test.json")
     init {
-        if (f.exists())
-            f.delete()
-    }
-
-    // Testing write failure due to insufficient permissions.
-    @Test
-    fun createWrongPermissions() {
-        f.writeText("won't be overridden")
-        f.setReadOnly()
-        try {
-            syncJsonToDisk(clientKeys, f.path)
-            throw Exception("Should have failed")
-        } catch (e: Exception) { }
+        f.deleteIfExists()
     }
 
     /**
@@ -81,9 +69,9 @@ class PrivateKeys {
      */
     @Test
     fun load() {
-        assertFalse(f.exists())
-        syncJsonToDisk(clientKeys, f.path) // Artificially storing this to the 
file.
-        val fromDisk = loadPrivateKeysFromDisk(f.path) // loading it via the 
tested routine.
+        assert(f.notExists())
+        persistClientKeys(clientKeys, f) // Artificially storing this to the 
file.
+        val fromDisk = loadClientKeys(f) // loading it via the tested routine.
         assertNotNull(fromDisk)
         // Checking the values from disk match the initial object.
         assertTrue {
@@ -98,6 +86,6 @@ class PrivateKeys {
     // Testing failure on file not found.
     @Test
     fun loadNotFound() {
-        
assertNull(loadPrivateKeysFromDisk("/tmp/highly-unlikely-to-be-found.json"))
+        
assertNull(loadClientKeys(Path("/tmp/highly-unlikely-to-be-found.json")))
     }
 }
\ No newline at end of file
diff --git a/testbench/src/main/kotlin/Main.kt 
b/testbench/src/main/kotlin/Main.kt
index 7ef820ac..60055e42 100644
--- a/testbench/src/main/kotlin/Main.kt
+++ b/testbench/src/main/kotlin/Main.kt
@@ -30,12 +30,10 @@ import com.github.ajalt.clikt.testing.*
 import io.ktor.client.*
 import io.ktor.client.engine.cio.*
 import kotlin.test.*
-import java.io.File
-import java.nio.file.*
+import kotlin.io.path.*
 import java.time.Instant
 import kotlinx.coroutines.runBlocking
 import io.ktor.client.request.*
-import kotlin.io.path.*
 
 fun randBytes(lenght: Int): ByteArray {
     val bytes = ByteArray(lenght)
@@ -85,8 +83,8 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
             val ebicsFlags = "$flags --transient --debug-ebics test/$name"
             val cfg = loadConfig(conf)
 
-            val clientKeysPath = Path(cfg.requirePath("nexus-ebics", 
"client_private_keys_file"))
-            val bankKeysPath = Path(cfg.requirePath("nexus-ebics", 
"bank_public_keys_file"))
+            val clientKeysPath = cfg.requirePath("nexus-ebics", 
"client_private_keys_file")
+            val bankKeysPath = cfg.requirePath("nexus-ebics", 
"bank_public_keys_file")
         
             var hasClientKeys = clientKeysPath.exists()
             var hasBankKeys = bankKeysPath.exists()
diff --git a/testbench/src/test/kotlin/IntegrationTest.kt 
b/testbench/src/test/kotlin/IntegrationTest.kt
index dd4023aa..1150adb1 100644
--- a/testbench/src/test/kotlin/IntegrationTest.kt
+++ b/testbench/src/test/kotlin/IntegrationTest.kt
@@ -23,7 +23,6 @@ import tech.libeufin.nexus.*
 import tech.libeufin.nexus.Database as NexusDb
 import tech.libeufin.bank.db.AccountDAO.*
 import tech.libeufin.common.*
-import java.io.File
 import java.time.Instant
 import java.util.Arrays
 import java.sql.SQLException
@@ -32,6 +31,7 @@ import com.github.ajalt.clikt.testing.test
 import com.github.ajalt.clikt.core.CliktCommand
 import org.postgresql.jdbc.PgConnection
 import kotlin.test.*
+import kotlin.io.path.*
 import io.ktor.client.*
 import io.ktor.client.engine.cio.*
 import io.ktor.client.plugins.*
@@ -142,7 +142,7 @@ class IntegrationTest {
             val fiatPayTo = IbanPayto(genIbanPaytoUri())
     
             // Load conversion setup manually as the server would refuse to 
start without an exchange account
-            val sqlProcedures = 
File("../database-versioning/libeufin-conversion-setup.sql")
+            val sqlProcedures = 
Path("../database-versioning/libeufin-conversion-setup.sql")
             db.conn { 
                 it.execSQLUpdate(sqlProcedures.readText())
                 it.execSQLUpdate("SET search_path TO libeufin_nexus;")

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

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