summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-03-27 01:06:48 +0100
committerAntoine A <>2024-03-27 01:08:18 +0100
commitdc03013dfcc3acc17bb8f54e842ccc6740caa040 (patch)
treebbe96517ac64ba33a78f89b5f1a46dacacd82ece
parent9dfbc9391fe27fc631bdf61e3e51dd3a124a4c60 (diff)
downloadlibeufin-dc03013dfcc3acc17bb8f54e842ccc6740caa040.tar.gz
libeufin-dc03013dfcc3acc17bb8f54e842ccc6740caa040.tar.bz2
libeufin-dc03013dfcc3acc17bb8f54e842ccc6740caa040.zip
Clean and optimize EBICS mess
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Config.kt4
-rw-r--r--common/src/main/kotlin/crypto/CryptoUtil.kt (renamed from common/src/main/kotlin/crypto/utils.kt)139
-rw-r--r--common/src/main/kotlin/crypto/PwCrypto.kt (renamed from common/src/main/kotlin/crypto/password.kt)0
-rw-r--r--common/src/test/kotlin/CryptoUtilTest.kt63
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt4
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt22
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt6
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt18
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt81
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt2
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt4
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt58
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt11
-rw-r--r--nexus/src/test/kotlin/CliTest.kt4
-rw-r--r--nexus/src/test/kotlin/Keys.kt4
-rw-r--r--nexus/src/test/kotlin/MySerializers.kt12
-rw-r--r--nexus/src/test/kotlin/helpers.kt8
17 files changed, 159 insertions, 281 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
index 54f2c44b..f48a1b35 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -28,9 +28,7 @@ import java.time.Duration
private val logger: Logger = LoggerFactory.getLogger("libeufin-bank")
-/**
- * Application the parsed configuration.
- */
+/** Configuration for libeufin-bank */
data class BankConfig(
val name: String,
val regionalCurrency: String,
diff --git a/common/src/main/kotlin/crypto/utils.kt b/common/src/main/kotlin/crypto/CryptoUtil.kt
index 10fa195e..a508c5b2 100644
--- a/common/src/main/kotlin/crypto/utils.kt
+++ b/common/src/main/kotlin/crypto/CryptoUtil.kt
@@ -45,18 +45,6 @@ import tech.libeufin.common.*
/** Helpers for dealing with cryptographic operations in EBICS / LibEuFin */
object CryptoUtil {
- /**
- * RSA key pair.
- */
- data class RsaCrtKeyPair(val private: RSAPrivateCrtKey, val public: RSAPublicKey)
-
- // FIXME(dold): This abstraction needs to be improved.
- class EncryptionResult(
- val encryptedTransactionKey: ByteArray,
- val pubKeyDigest: ByteArray,
- val encryptedData: ByteArray,
- val plainTransactionKey: SecretKey
- )
private val provider = BouncyCastleProvider()
@@ -137,23 +125,17 @@ object CryptoUtil {
}
- /**
- * Generate a fresh RSA key pair.
- *
- * @param nbits size of the modulus in bits
- */
- fun generateRsaKeyPair(nbits: Int): RsaCrtKeyPair {
+ /** Generate an RSA key pair of [keysize] */
+ fun genRSAPair(keysize: Int): Pair<RSAPrivateCrtKey, RSAPublicKey> {
val gen = KeyPairGenerator.getInstance("RSA")
- gen.initialize(nbits)
+ gen.initialize(keysize)
val pair = gen.genKeyPair()
- val priv = pair.private
- val pub = pair.public
- if (priv !is RSAPrivateCrtKey)
- throw Exception("key generation failed")
- if (pub !is RSAPublicKey)
- throw Exception("key generation failed")
- return RsaCrtKeyPair(priv, pub)
+ return Pair(pair.private as RSAPrivateCrtKey, pair.public as RSAPublicKey)
}
+ /** Generate an RSA private key of [keysize] */
+ fun genRSAPrivate(keysize: Int): RSAPrivateCrtKey = genRSAPair(keysize).first
+ /** Generate an RSA public key of [keysize] */
+ fun genRSAPublic(keysize: Int): RSAPublicKey = genRSAPair(keysize).second
/**
* Hash an RSA public key according to the EBICS standard (EBICS 2.5: 4.4.1.2.3).
@@ -167,73 +149,61 @@ object CryptoUtil {
return digest.digest(keyBytes.toByteArray())
}
- fun encryptEbicsE002(data: InputStream, encryptionPublicKey: RSAPublicKey): EncryptionResult {
+ fun genEbicsE002Key(encryptionPublicKey: RSAPublicKey): Pair<SecretKey, ByteArray> {
+ // Gen transaction key
val keygen = KeyGenerator.getInstance("AES", provider)
keygen.init(128)
val transactionKey = keygen.generateKey()
- return encryptEbicsE002withTransactionKey(
- data,
- encryptionPublicKey,
- transactionKey
+ // Encrypt transaction keyA
+ val cipher = Cipher.getInstance(
+ "RSA/None/PKCS1Padding",
+ provider
)
+ cipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey)
+ val encryptedTransactionKey = cipher.doFinal(transactionKey.encoded)
+ return Pair(transactionKey, encryptedTransactionKey)
}
+
/**
* Encrypt data according to the EBICS E002 encryption process.
*/
- fun encryptEbicsE002withTransactionKey(
- data: InputStream,
- encryptionPublicKey: RSAPublicKey,
- transactionKey: SecretKey
- ): EncryptionResult {
- val symmetricCipher = Cipher.getInstance(
+ fun encryptEbicsE002(
+ transactionKey: SecretKey,
+ data: InputStream
+ ): CipherInputStream {
+ val cipher = Cipher.getInstance(
"AES/CBC/X9.23Padding",
provider
)
val ivParameterSpec = IvParameterSpec(ByteArray(16))
- symmetricCipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec)
- val encryptedData = CipherInputStream(data, symmetricCipher).readAllBytes()
- val asymmetricCipher = Cipher.getInstance(
+ cipher.init(Cipher.ENCRYPT_MODE, transactionKey, ivParameterSpec)
+ return CipherInputStream(data, cipher)
+ }
+
+ fun decryptEbicsE002Key(
+ privateKey: RSAPrivateCrtKey,
+ encryptedTransactionKey: ByteArray
+ ): SecretKeySpec {
+ val cipher = Cipher.getInstance(
"RSA/None/PKCS1Padding",
provider
)
- asymmetricCipher.init(Cipher.ENCRYPT_MODE, encryptionPublicKey)
- val encryptedTransactionKey = asymmetricCipher.doFinal(transactionKey.encoded)
- val pubKeyDigest = getEbicsPublicKeyHash(encryptionPublicKey)
- return EncryptionResult(
- encryptedTransactionKey,
- pubKeyDigest,
- encryptedData,
- transactionKey
- )
- }
-
- fun decryptEbicsE002(enc: EncryptionResult, privateKey: RSAPrivateCrtKey): ByteArray {
- return decryptEbicsE002(
- enc.encryptedTransactionKey,
- enc.encryptedData.inputStream(),
- privateKey
- ).readBytes()
+ cipher.init(Cipher.DECRYPT_MODE, privateKey)
+ val transactionKeyBytes = cipher.doFinal(encryptedTransactionKey)
+ return SecretKeySpec(transactionKeyBytes, "AES")
}
fun decryptEbicsE002(
- encryptedTransactionKey: ByteArray,
- encryptedData: InputStream,
- privateKey: RSAPrivateCrtKey
+ transactionKey: SecretKeySpec,
+ encryptedData: InputStream
): CipherInputStream {
- val asymmetricCipher = Cipher.getInstance(
- "RSA/None/PKCS1Padding",
- provider
- )
- asymmetricCipher.init(Cipher.DECRYPT_MODE, privateKey)
- val transactionKeyBytes = asymmetricCipher.doFinal(encryptedTransactionKey)
- val secretKeySpec = SecretKeySpec(transactionKeyBytes, "AES")
- val symmetricCipher = Cipher.getInstance(
+ val cipher = Cipher.getInstance(
"AES/CBC/X9.23Padding",
provider
)
val ivParameterSpec = IvParameterSpec(ByteArray(16))
- symmetricCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
- return CipherInputStream(encryptedData, symmetricCipher)
+ cipher.init(Cipher.DECRYPT_MODE, transactionKey, ivParameterSpec)
+ return CipherInputStream(encryptedData, cipher)
}
/**
@@ -290,31 +260,6 @@ object CryptoUtil {
return priv
}
- fun encryptKey(data: ByteArray, passphrase: String): ByteArray {
- /* Cipher parameters: salt and hash count */
- val hashIterations = 30
- val salt = ByteArray(8)
- SecureRandom().nextBytes(salt)
- val pbeParameterSpec = PBEParameterSpec(salt, hashIterations)
- /* *Other* cipher parameters: symmetric key (from password) */
- val pbeAlgorithm = "PBEWithSHA1AndDESede"
- val pbeKeySpec = PBEKeySpec(passphrase.toCharArray())
- val keyFactory = SecretKeyFactory.getInstance(pbeAlgorithm)
- val secretKey = keyFactory.generateSecret(pbeKeySpec)
- /* Make a cipher */
- val cipher = Cipher.getInstance(pbeAlgorithm)
- cipher.init(Cipher.ENCRYPT_MODE, secretKey, pbeParameterSpec)
- /* ready to encrypt now */
- val cipherText = cipher.doFinal(data)
- /* Must now bundle a PKCS#8-compatible object, that contains
- * algorithm, salt and hash count information */
- val bundleAlgorithmParams = AlgorithmParameters.getInstance(pbeAlgorithm)
- bundleAlgorithmParams.init(pbeParameterSpec)
- val bundle = EncryptedPrivateKeyInfo(bundleAlgorithmParams, cipherText)
- return bundle.encoded
- }
-
- fun hashStringSHA256(input: String): ByteArray {
- return MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8))
- }
+ fun hashStringSHA256(input: String): ByteArray =
+ MessageDigest.getInstance("SHA-256").digest(input.toByteArray(Charsets.UTF_8))
} \ No newline at end of file
diff --git a/common/src/main/kotlin/crypto/password.kt b/common/src/main/kotlin/crypto/PwCrypto.kt
index 2dd8af07..2dd8af07 100644
--- a/common/src/main/kotlin/crypto/password.kt
+++ b/common/src/main/kotlin/crypto/PwCrypto.kt
diff --git a/common/src/test/kotlin/CryptoUtilTest.kt b/common/src/test/kotlin/CryptoUtilTest.kt
index f76701ad..6e062b1a 100644
--- a/common/src/test/kotlin/CryptoUtilTest.kt
+++ b/common/src/test/kotlin/CryptoUtilTest.kt
@@ -34,71 +34,38 @@ class CryptoUtilTest {
@Test
fun loadFromModulusAndExponent() {
- val keyPair = CryptoUtil.generateRsaKeyPair(1024)
+ val public = CryptoUtil.genRSAPublic(1024)
val pub2 = CryptoUtil.RSAPublicFromComponents(
- keyPair.public.modulus.toByteArray(),
- keyPair.public.publicExponent.toByteArray()
+ public.modulus.toByteArray(),
+ public.publicExponent.toByteArray()
)
- assertEquals(keyPair.public, pub2)
- }
-
- @Test
- fun keyGeneration() {
- val gen: KeyPairGenerator = KeyPairGenerator.getInstance("RSA")
- gen.initialize(2048)
- val pair = gen.genKeyPair()
- println(pair.private)
- assertTrue(pair.private is RSAPrivateCrtKey)
+ assertEquals(public, pub2)
}
@Test
fun testCryptoUtilBasics() {
- val keyPair = CryptoUtil.generateRsaKeyPair(1024)
- val encodedPriv = keyPair.private.encoded
- val encodedPub = keyPair.public.encoded
- val otherKeyPair =
- CryptoUtil.RsaCrtKeyPair(CryptoUtil.loadRSAPrivate(encodedPriv), CryptoUtil.loadRSAPublic(encodedPub))
- assertEquals(keyPair.private, otherKeyPair.private)
- assertEquals(keyPair.public, otherKeyPair.public)
+ val (private, public) = CryptoUtil.genRSAPair(1024)
+ assertEquals(private, CryptoUtil.loadRSAPrivate(private.encoded))
+ assertEquals(public, CryptoUtil.loadRSAPublic(public.encoded))
}
@Test
fun testEbicsE002() {
val data = "Hello, World!".toByteArray()
- val keyPair = CryptoUtil.generateRsaKeyPair(1024)
- val enc = CryptoUtil.encryptEbicsE002(data.inputStream(), keyPair.public)
- val dec = CryptoUtil.decryptEbicsE002(enc, keyPair.private)
+ val (private, public) = CryptoUtil.genRSAPair(1024)
+ val (txKey, encryptedKey) = CryptoUtil.genEbicsE002Key(public)
+ val enc = CryptoUtil.encryptEbicsE002(txKey, data.inputStream())
+ val txKey2 = CryptoUtil.decryptEbicsE002Key(private, encryptedKey)
+ val dec = CryptoUtil.decryptEbicsE002(txKey2, enc).readBytes()
assertTrue(data.contentEquals(dec))
}
@Test
fun testEbicsA006() {
- val keyPair = CryptoUtil.generateRsaKeyPair(1024)
+ val (private, public) = CryptoUtil.genRSAPair(1024)
val data = "Hello, World".toByteArray(Charsets.UTF_8)
- val sig = CryptoUtil.signEbicsA006(data, keyPair.private)
- assertTrue(CryptoUtil.verifyEbicsA006(sig, data, keyPair.public))
- }
-
- @Test
- fun testPassphraseEncryption() {
-
- val keyPair = CryptoUtil.generateRsaKeyPair(1024)
-
- /* encrypt with original key */
- val data = "Hello, World!".toByteArray(Charsets.UTF_8)
- val secret = CryptoUtil.encryptEbicsE002(data.inputStream(), keyPair.public)
-
- /* encrypt and decrypt private key */
- val encPriv = CryptoUtil.encryptKey(keyPair.private.encoded, "secret")
- val plainPriv = CryptoUtil.decryptKey(EncryptedPrivateKeyInfo(encPriv), "secret")
-
- /* decrypt with decrypted private key */
- val revealed = CryptoUtil.decryptEbicsE002(secret, plainPriv)
-
- assertEquals(
- String(revealed, charset = Charsets.UTF_8),
- String(data, charset = Charsets.UTF_8)
- )
+ val sig = CryptoUtil.signEbicsA006(data, private)
+ assertTrue(CryptoUtil.verifyEbicsA006(sig, data, public))
}
@Test
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
index 298d84b4..f25934cd 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
@@ -46,7 +46,7 @@ data class FetchContext(
/**
* Config handle.
*/
- val cfg: EbicsSetupConfig,
+ val cfg: NexusConfig,
/**
* HTTP client handle to reach the bank
*/
@@ -359,7 +359,7 @@ class EbicsFetch: CliktCommand("Fetches EBICS files") {
* FIXME: reduce code duplication with the submit subcommand.
*/
override fun run() = cliCmd(logger, common.log) {
- val cfg: EbicsSetupConfig = extractEbicsConfig(common.config)
+ val cfg = extractEbicsConfig(common.config)
val dbCfg = cfg.config.dbConfig()
Database(dbCfg.dbConnStr).use { db ->
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index a4870834..dd13eb30 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -95,7 +95,7 @@ private fun askUserToAcceptKeys(bankKeys: BankPublicKeysFile): Boolean {
* the --auto-accept-key CLI flag.
*/
suspend fun doKeysRequestAndUpdateState(
- cfg: EbicsSetupConfig,
+ cfg: NexusConfig,
privs: ClientPrivateKeysFile,
client: HttpClient,
order: EbicsKeyMng.Order
@@ -133,7 +133,7 @@ suspend fun doKeysRequestAndUpdateState(
accepted = false
)
try {
- persistBankKeys(bankKeys, cfg.bankPublicKeysFilename)
+ persistBankKeys(bankKeys, cfg.bankPublicKeysPath)
} catch (e: Exception) {
throw Exception("Could not update the $order state on disk", e)
}
@@ -141,7 +141,7 @@ suspend fun doKeysRequestAndUpdateState(
}
if (order != HPB) {
try {
- persistClientKeys(privs, cfg.clientPrivateKeysFilename)
+ persistClientKeys(privs, cfg.clientPrivateKeysPath)
} catch (e: Exception) {
throw Exception("Could not update the $order state on disk", e)
}
@@ -155,9 +155,9 @@ suspend fun doKeysRequestAndUpdateState(
* @param configFile location of the configuration entry point.
* @return internal representation of the configuration.
*/
-fun extractEbicsConfig(configFile: Path?): EbicsSetupConfig {
+fun extractEbicsConfig(configFile: Path?): NexusConfig {
val config = loadConfig(configFile)
- return EbicsSetupConfig(config)
+ return NexusConfig(config)
}
/**
@@ -167,7 +167,7 @@ fun extractEbicsConfig(configFile: Path?): EbicsSetupConfig {
* @param privs client private keys.
* @param cfg configuration handle.
*/
-private fun makePdf(privs: ClientPrivateKeysFile, cfg: EbicsSetupConfig) {
+private fun makePdf(privs: ClientPrivateKeysFile, cfg: NexusConfig) {
val pdf = generateKeysPdf(privs, cfg)
val path = Path("/tmp/libeufin-nexus-keys-${Instant.now().epochSecond}.pdf")
try {
@@ -199,7 +199,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 = loadOrGenerateClientKeys(cfg.clientPrivateKeysFilename)
+ val clientKeys = loadOrGenerateClientKeys(cfg.clientPrivateKeysPath)
val client = HttpClient {
install(HttpTimeout) {
// It can take a lot of time for the bank to generate documents
@@ -216,11 +216,11 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") {
doKeysRequestAndUpdateState(cfg, clientKeys, client, HIA)
// Checking if the bank keys exist on disk.
- var bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)
+ var bankKeys = loadBankKeys(cfg.bankPublicKeysPath)
if (bankKeys == null) {
doKeysRequestAndUpdateState(cfg, clientKeys, client, HPB)
- logger.info("Bank keys stored at ${cfg.bankPublicKeysFilename}")
- bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)!!
+ logger.info("Bank keys stored at ${cfg.bankPublicKeysPath}")
+ bankKeys = loadBankKeys(cfg.bankPublicKeysPath)!!
}
if (!bankKeys.accepted) {
@@ -232,7 +232,7 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") {
throw Exception("Cannot successfully finish the setup without accepting the bank keys")
}
try {
- persistBankKeys(bankKeys, cfg.bankPublicKeysFilename)
+ persistBankKeys(bankKeys, cfg.bankPublicKeysPath)
} 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 cf206380..34d57969 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -42,7 +42,7 @@ data class SubmissionContext(
/**
* Configuration handle.
*/
- val cfg: EbicsSetupConfig,
+ val cfg: NexusConfig,
/**
* Subscriber EBICS private keys.
*/
@@ -83,7 +83,7 @@ private suspend fun submitInitiatedPayment(
initiationTimestamp = payment.initiationTime,
amount = payment.amount,
creditAccount = creditAccount,
- debitAccount = ctx.cfg.myIbanAccount,
+ debitAccount = ctx.cfg.account,
wireTransferSubject = payment.wireTransferSubject
)
ctx.fileLogger.logSubmit(xml)
@@ -146,7 +146,7 @@ class EbicsSubmit : CliktCommand("Submits any initiated payment found in the dat
* FIXME: reduce code duplication with the fetch subcommand.
*/
override fun run() = cliCmd(logger, common.log) {
- val cfg: EbicsSetupConfig = extractEbicsConfig(common.config)
+ val cfg = extractEbicsConfig(common.config)
val dbCfg = cfg.config.dbConfig()
val (clientKeys, bankKeys) = expectFullKeys(cfg)
val ctx = SubmissionContext(
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt
index b4acbb75..172cffa2 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/KeyFiles.kt
@@ -111,9 +111,9 @@ data class BankPublicKeysFile(
*/
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,
+ authentication_private_key = CryptoUtil.genRSAPrivate(2048),
+ encryption_private_key = CryptoUtil.genRSAPrivate(2048),
+ signature_private_key = CryptoUtil.genRSAPrivate(2048),
submitted_hia = false,
submitted_ini = false
)
@@ -202,18 +202,16 @@ fun loadClientKeys(location: Path): ClientPrivateKeysFile? = loadJsonFile(locati
* @param cfg configuration handle.
* @return both client and bank keys
*/
-fun expectFullKeys(
- cfg: EbicsSetupConfig
-): Pair<ClientPrivateKeysFile, BankPublicKeysFile> {
- val clientKeys = loadClientKeys(cfg.clientPrivateKeysFilename)
+fun expectFullKeys(cfg: NexusConfig): Pair<ClientPrivateKeysFile, BankPublicKeysFile> {
+ val clientKeys = loadClientKeys(cfg.clientPrivateKeysPath)
if (clientKeys == null) {
- throw Exception("Missing client private keys file at '${cfg.clientPrivateKeysFilename}', run 'libeufin-nexus ebics-setup' first")
+ throw Exception("Missing client private keys file at '${cfg.clientPrivateKeysPath}', 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)
+ val bankKeys = loadBankKeys(cfg.bankPublicKeysPath)
if (bankKeys == null) {
- throw Exception("Missing bank public keys at '${cfg.bankPublicKeysFilename}', run 'libeufin-nexus ebics-setup' first")
+ throw Exception("Missing bank public keys at '${cfg.bankPublicKeysPath}', 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")
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index b9582976..4cc67eec 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -61,68 +61,37 @@ fun Instant.fmtDate(): String =
fun Instant.fmtDateTime(): String =
DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneId.of("UTC")).format(this)
-/**
- * Keeps all the options of the ebics-setup subcommand. The
- * caller has to handle TalerConfigError if values are missing.
- * If even one of the fields could not be instantiated, then
- * throws TalerConfigError.
- */
-class EbicsSetupConfig(val config: TalerConfig) {
- // abstracts the section name.
- private val ebicsSetupRequireString = { option: String ->
- config.requireString("nexus-ebics", option)
- }
- private val ebicsSetupRequirePath = { option: String ->
- config.requirePath("nexus-ebics", option)
- }
- // debug utility to inspect what was loaded.
- fun _dump() {
- this.javaClass.declaredFields.forEach {
- println("cfg obj: ${it.name} -> ${it.get(this)}")
- }
- }
- /**
- * The bank's currency.
- */
- val currency = ebicsSetupRequireString("currency")
- /**
- * The bank base URL.
- */
- val hostBaseUrl = ebicsSetupRequireString("host_base_url")
- /**
- * The bank EBICS host ID.
- */
- val ebicsHostId = ebicsSetupRequireString("host_id")
- /**
- * EBICS user ID.
- */
- val ebicsUserId = ebicsSetupRequireString("user_id")
- /**
- * EBICS partner ID.
- */
- val ebicsPartnerId = ebicsSetupRequireString("partner_id")
- /**
- * Bank account metadata.
- */
- val myIbanAccount = IbanAccountMetadata(
- iban = ebicsSetupRequireString("iban"),
- bic = ebicsSetupRequireString("bic"),
- name = ebicsSetupRequireString("name")
+/** Configuration for libeufin-nexus */
+class NexusConfig(val config: TalerConfig) {
+ private fun requireString(option: String): String = config.requireString("nexus-ebics", option)
+ private fun requirePath(option: String): Path = config.requirePath("nexus-ebics", option)
+
+ /** The bank's currency */
+ val currency = requireString("currency")
+ /** The bank base URL */
+ val hostBaseUrl = requireString("host_base_url")
+ /** The bank EBICS host ID */
+ val ebicsHostId = requireString("host_id")
+ /** EBICS user ID */
+ val ebicsUserId = requireString("user_id")
+ /** EBICS partner ID */
+ val ebicsPartnerId = requireString("partner_id")
+ /** Bank account metadata */
+ val account = IbanAccountMetadata(
+ iban = requireString("iban"),
+ bic = requireString("bic"),
+ name = requireString("name")
)
- /**
- * Filename where we store the bank public keys.
- */
- val bankPublicKeysFilename = ebicsSetupRequirePath("bank_public_keys_file")
- /**
- * Filename where we store our private keys.
- */
- val clientPrivateKeysFilename = ebicsSetupRequirePath("client_private_keys_file")
+ /** Path where we store the bank public keys */
+ val bankPublicKeysPath = requirePath("bank_public_keys_file")
+ /** Path where we store our private keys */
+ val clientPrivateKeysPath = requirePath("client_private_keys_file")
/**
* A name that identifies the EBICS and ISO20022 flavour
* that Nexus should honor in the communication with the
* bank.
*/
- val bankDialect: String = ebicsSetupRequireString("bank_dialect").run {
+ val bankDialect: String = requireString("bank_dialect").run {
if (this != "postfinance") throw Exception("Only 'postfinance' dialect is supported.")
return@run this
}
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt
index dd595d31..71937a42 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/PDF.kt
@@ -37,7 +37,7 @@ import tech.libeufin.common.crypto.*
*/
fun generateKeysPdf(
clientKeys: ClientPrivateKeysFile,
- cfg: EbicsSetupConfig
+ cfg: NexusConfig
): ByteArray {
val po = ByteArrayOutputStream()
val pdfWriter = PdfWriter(po)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt
index 9e5ee4e2..1c4294e3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt
@@ -40,7 +40,7 @@ fun Instant.xmlDateTime(): String =
/** EBICS protocol for business transactions */
class EbicsBTS(
- val cfg: EbicsSetupConfig,
+ val cfg: NexusConfig,
val bankKeys: BankPublicKeysFile,
val clientKeys: ClientPrivateKeysFile,
val order: EbicsOrder
@@ -217,7 +217,7 @@ class EbicsBTS(
}
el("SignatureData") {
attr("authenticate", "true")
- text(uploadData.userSignatureDataEncrypted.encodeBase64())
+ text(uploadData.userSignatureDataEncrypted)
}
el("DataDigest") {
attr("SignatureVersion", "A006")
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
index 30bcd8de..f87c55b3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -80,15 +80,17 @@ fun decryptAndDecompressPayload(
clientEncryptionKey: RSAPrivateCrtKey,
encryptionInfo: DataEncryptionInfo,
chunks: List<ByteArray>
-): InputStream =
- SequenceInputStream(Collections.enumeration(chunks.map { it.inputStream() })) // Aggregate
+): InputStream {
+ val transactionKey = CryptoUtil.decryptEbicsE002Key(clientEncryptionKey, encryptionInfo.transactionKey)
+ return SequenceInputStream(Collections.enumeration(chunks.map { it.inputStream() })) // Aggregate
.run {
CryptoUtil.decryptEbicsE002(
- encryptionInfo.transactionKey,
- this,
- clientEncryptionKey
+ transactionKey,
+ this
)
}.inflate()
+}
+
sealed class EbicsError(msg: String, cause: Throwable? = null): Exception(msg, cause) {
/** Http and network errors */
@@ -162,7 +164,7 @@ suspend fun EbicsBTS.postBTS(
*/
suspend fun ebicsDownload(
client: HttpClient,
- cfg: EbicsSetupConfig,
+ cfg: NexusConfig,
clientKeys: ClientPrivateKeysFile,
bankKeys: BankPublicKeysFile,
order: EbicsOrder,
@@ -267,43 +269,43 @@ suspend fun ebicsDownload(
* @return [PreparedUploadData]
*/
fun prepareUploadPayload(
- cfg: EbicsSetupConfig,
+ cfg: NexusConfig,
clientKeys: ClientPrivateKeysFile,
bankKeys: BankPublicKeysFile,
payload: ByteArray,
): PreparedUploadData {
+ val payloadDigest = CryptoUtil.digestEbicsOrderA006(payload)
val innerSignedEbicsXml = XmlBuilder.toBytes("UserSignatureData") {
attr("xmlns", "http://www.ebics.org/S002")
el("OrderSignatureData") {
el("SignatureVersion", "A006")
el("SignatureValue", CryptoUtil.signEbicsA006(
- CryptoUtil.digestEbicsOrderA006(payload),
+ payloadDigest,
clientKeys.signature_private_key,
).encodeBase64())
el("PartnerID", cfg.ebicsPartnerId)
el("UserID", cfg.ebicsUserId)
}
}
- val encryptionResult = CryptoUtil.encryptEbicsE002(
- innerSignedEbicsXml.inputStream().deflate(),
- bankKeys.bank_encryption_public_key
- )
- // Then only E002 symmetric (with ephemeral key) encrypt.
- val compressedInnerPayload = payload.inputStream().deflate()
- // TODO stream
- val encryptedPayload = CryptoUtil.encryptEbicsE002withTransactionKey(
- compressedInnerPayload,
- bankKeys.bank_encryption_public_key,
- encryptionResult.plainTransactionKey
- )
- val segment = encryptedPayload.encryptedData.encodeBase64()
- // Split 1MB segment when we have payloads that big
+ // Generate ephemeral transaction key
+ val (transactionKey, encryptedTransactionKey) = CryptoUtil.genEbicsE002Key(bankKeys.bank_encryption_public_key)
+ // Compress and encrypt order signature
+ val orderSignature = CryptoUtil.encryptEbicsE002(
+ transactionKey,
+ innerSignedEbicsXml.inputStream().deflate()
+ ).encodeBase64()
+ // Compress and encrypt payload
+ val segment = CryptoUtil.encryptEbicsE002(
+ transactionKey,
+ payload.inputStream().deflate()
+ ).encodeBase64()
+ // TODO split 1MB segment when we have payloads that big
return PreparedUploadData(
- encryptionResult.encryptedTransactionKey, // ephemeral key
- encryptionResult.encryptedData, // bank-pub-encrypted A006 signature.
- CryptoUtil.digestEbicsOrderA006(payload), // used by EBICS 3
- listOf(segment) // actual payload E002 encrypted.
+ encryptedTransactionKey,
+ orderSignature,
+ payloadDigest,
+ listOf(segment)
)
}
@@ -321,7 +323,7 @@ fun prepareUploadPayload(
*/
suspend fun doEbicsUpload(
client: HttpClient,
- cfg: EbicsSetupConfig,
+ cfg: NexusConfig,
clientKeys: ClientPrivateKeysFile,
bankKeys: BankPublicKeysFile,
order: EbicsOrder,
@@ -361,7 +363,7 @@ fun getNonce(size: Int): ByteArray {
class PreparedUploadData(
val transactionKey: ByteArray,
- val userSignatureDataEncrypted: ByteArray,
+ val userSignatureDataEncrypted: String,
val dataDigest: ByteArray,
val segments: List<String>
)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
index 9141430a..37e26e1c 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
@@ -25,7 +25,6 @@ import tech.libeufin.common.*
import tech.libeufin.nexus.*
import tech.libeufin.nexus.BankPublicKeysFile
import tech.libeufin.nexus.ClientPrivateKeysFile
-import tech.libeufin.nexus.EbicsSetupConfig
import java.io.InputStream
import java.time.Instant
import java.time.ZoneId
@@ -36,7 +35,7 @@ import tech.libeufin.nexus.ebics.EbicsKeyMng.Order.*
/** EBICS protocol for key management */
class EbicsKeyMng(
- private val cfg: EbicsSetupConfig,
+ private val cfg: NexusConfig,
private val clientKeys: ClientPrivateKeysFile,
private val ebics3: Boolean
) {
@@ -54,13 +53,13 @@ class EbicsKeyMng(
HPB -> Triple("ebicsNoPubKeyDigestsRequest", "0000", "DZHNN")
}
val data = when (order) {
- INI -> XMLOrderData(cfg, "SignaturePubKeyOrderData", "http://www.ebics.org/S00${if (ebics3) 2 else 1}") {
+ INI -> XMLOrderData("SignaturePubKeyOrderData", "http://www.ebics.org/S00${if (ebics3) 2 else 1}") {
el("SignaturePubKeyInfo") {
RSAKeyXml(clientKeys.signature_private_key)
el("SignatureVersion", "A006")
}
}
- HIA -> XMLOrderData(cfg, "HIARequestOrderData", "urn:org:ebics:$schema") {
+ HIA -> XMLOrderData("HIARequestOrderData", "urn:org:ebics:$schema") {
el("AuthenticationPubKeyInfo") {
RSAKeyXml(clientKeys.authentication_private_key)
el("AuthenticationVersion", "X002")
@@ -111,7 +110,7 @@ class EbicsKeyMng(
private fun XmlBuilder.RSAKeyXml(key: RSAPrivateCrtKey) {
if (ebics3) {
- val cert = CryptoUtil.X509CertificateFromRSAPrivate(key, cfg.myIbanAccount.name)
+ val cert = CryptoUtil.X509CertificateFromRSAPrivate(key, cfg.account.name)
el("ds:X509Data") {
el("ds:X509Certificate", cert.encoded.encodeBase64())
}
@@ -125,7 +124,7 @@ class EbicsKeyMng(
}
}
- private fun XMLOrderData(cfg: EbicsSetupConfig, name: String, schema: String, build: XmlBuilder.() -> Unit): String {
+ private fun XMLOrderData(name: String, schema: String, build: XmlBuilder.() -> Unit): String {
return XmlBuilder.toBytes(name) {
attr("xmlns:ds", "http://www.w3.org/2000/09/xmldsig#")
attr("xmlns", schema)
diff --git a/nexus/src/test/kotlin/CliTest.kt b/nexus/src/test/kotlin/CliTest.kt
index 7e03053d..19bc0853 100644
--- a/nexus/src/test/kotlin/CliTest.kt
+++ b/nexus/src/test/kotlin/CliTest.kt
@@ -103,8 +103,8 @@ class CliTest {
}
// Unfinished bank
persistBankKeys(BankPublicKeysFile(
- bank_authentication_public_key = CryptoUtil.generateRsaKeyPair(2048).public,
- bank_encryption_public_key = CryptoUtil.generateRsaKeyPair(2048).public,
+ bank_authentication_public_key = CryptoUtil.genRSAPublic(2048),
+ bank_encryption_public_key = CryptoUtil.genRSAPublic(2048),
accepted = false
), bankKeysPath)
for (cmd in cmds) {
diff --git a/nexus/src/test/kotlin/Keys.kt b/nexus/src/test/kotlin/Keys.kt
index 826ec77e..36dd6487 100644
--- a/nexus/src/test/kotlin/Keys.kt
+++ b/nexus/src/test/kotlin/Keys.kt
@@ -43,8 +43,8 @@ class PublicKeys {
// artificially creating the keys.
val fileContent = BankPublicKeysFile(
accepted = true,
- bank_authentication_public_key = CryptoUtil.generateRsaKeyPair(2028).public,
- bank_encryption_public_key = CryptoUtil.generateRsaKeyPair(2028).public
+ bank_authentication_public_key = CryptoUtil.genRSAPublic(2028),
+ bank_encryption_public_key = CryptoUtil.genRSAPublic(2028)
)
// storing them on disk.
persistBankKeys(fileContent, Path("/tmp/nexus-tests-bank-keys.json"))
diff --git a/nexus/src/test/kotlin/MySerializers.kt b/nexus/src/test/kotlin/MySerializers.kt
index 98707948..1f2d8969 100644
--- a/nexus/src/test/kotlin/MySerializers.kt
+++ b/nexus/src/test/kotlin/MySerializers.kt
@@ -28,9 +28,9 @@ class MySerializers {
// Testing deserialization of RSA private keys.
@Test
fun rsaPrivDeserialization() {
- val s = Base32Crockford.encode(CryptoUtil.generateRsaKeyPair(2048).private.encoded)
- val a = Base32Crockford.encode(CryptoUtil.generateRsaKeyPair(2048).private.encoded)
- val e = Base32Crockford.encode(CryptoUtil.generateRsaKeyPair(2048).private.encoded)
+ val s = Base32Crockford.encode(CryptoUtil.genRSAPrivate(2048).encoded)
+ val a = Base32Crockford.encode(CryptoUtil.genRSAPrivate(2048).encoded)
+ val e = Base32Crockford.encode(CryptoUtil.genRSAPrivate(2048).encoded)
val obj = JSON.decodeFromString<ClientPrivateKeysFile>("""
{
"signature_private_key": "$s",
@@ -40,8 +40,8 @@ class MySerializers {
"submitted_hia": true
}
""".trimIndent())
- assertEquals(obj.signature_private_key, CryptoUtil.loadRsaPrivateKey(Base32Crockford.decode(s)))
- assertEquals(obj.authentication_private_key, CryptoUtil.loadRsaPrivateKey(Base32Crockford.decode(a)))
- assertEquals(obj.encryption_private_key, CryptoUtil.loadRsaPrivateKey(Base32Crockford.decode(e)))
+ assertEquals(obj.signature_private_key, CryptoUtil.loadRSAPrivate(Base32Crockford.decode(s)))
+ assertEquals(obj.authentication_private_key, CryptoUtil.loadRSAPrivate(Base32Crockford.decode(a)))
+ assertEquals(obj.encryption_private_key, CryptoUtil.loadRSAPrivate(Base32Crockford.decode(e)))
}
} \ No newline at end of file
diff --git a/nexus/src/test/kotlin/helpers.kt b/nexus/src/test/kotlin/helpers.kt
index 57a1d2da..61aed19d 100644
--- a/nexus/src/test/kotlin/helpers.kt
+++ b/nexus/src/test/kotlin/helpers.kt
@@ -29,20 +29,20 @@ import kotlin.io.path.Path
fun conf(
conf: String = "test.conf",
- lambda: suspend (EbicsSetupConfig) -> Unit
+ lambda: suspend (NexusConfig) -> Unit
) = runBlocking {
val config = NEXUS_CONFIG_SOURCE.fromFile(Path("conf/$conf"))
- val ctx = EbicsSetupConfig(config)
+ val ctx = NexusConfig(config)
lambda(ctx)
}
fun setup(
conf: String = "test.conf",
- lambda: suspend (Database, EbicsSetupConfig) -> Unit
+ lambda: suspend (Database, NexusConfig) -> Unit
) = runBlocking {
val config = NEXUS_CONFIG_SOURCE.fromFile(Path("conf/$conf"))
val dbCfg = config.dbConfig()
- val ctx = EbicsSetupConfig(config)
+ val ctx = NexusConfig(config)
Database(dbCfg.dbConnStr).use {
it.conn { conn ->
resetDatabaseTables(conn, dbCfg, "libeufin-nexus")