commit 767e006ac586ca437c1ded8897f3be38a92441f2
parent 370bb52694a7aab5a93d43944a2d6681cee29f3b
Author: Antoine A <>
Date: Tue, 11 Mar 2025 18:14:47 +0100
nexus: improve EBICS signature verify
Diffstat:
4 files changed, 36 insertions(+), 33 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/XMLUtil.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/XMLUtil.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2024 Taler Systems S.A.
+ * Copyright (C) 2024-2025 Taler Systems S.A.
* LibEuFin is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -140,27 +140,28 @@ object XMLUtil {
fun verifyEbicsDocument(
doc: Document,
signingPub: PublicKey
- ): Boolean {
- // TODO can we simplify this ?
- val doc2: Document = doc.cloneNode(true) as Document
- val authSigNode = XPathFactory.newInstance().newXPath()
- .evaluate("/*[1]/*[local-name()='AuthSignature']", doc2, XPathConstants.NODE)
- if (authSigNode !is Node)
- throw java.lang.Exception("verify: no AuthSignature")
- val sigEl = doc2.createElementNS("http://www.w3.org/2000/09/xmldsig#", "ds:Signature")
- authSigNode.parentNode.insertBefore(sigEl, authSigNode)
- while (authSigNode.hasChildNodes()) {
- sigEl.appendChild(authSigNode.firstChild)
+ ) {
+ // Find SignedInfo
+ val sigInfos = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "SignedInfo");
+ if (sigInfos.length == 0) {
+ throw Exception("missing SignedInfo")
+ } else if (sigInfos.length != 1) {
+ throw Exception("many SignedInfo")
}
- authSigNode.parentNode.removeChild(authSigNode)
+ val sigInfo = sigInfos.item(0)
+
+ // Rename AuthSignature
+ val authSig = sigInfo.parentNode
+ doc.renameNode(authSig, XMLSignature.XMLNS, "${sigInfo.prefix}:Signature")
+
+ // Check signature
val fac = XMLSignatureFactory.getInstance("DOM")
- val dvc = DOMValidateContext(signingPub, sigEl)
+ val dvc = DOMValidateContext(signingPub, authSig)
dvc.setProperty("javax.xml.crypto.dsig.cacheReference", true)
dvc.uriDereferencer = EbicsSigUriDereferencer()
val sig = fac.unmarshalXMLSignature(dvc)
- // FIXME: check that parameters are okay!
- val valResult = sig.validate(dvc)
- sig.signedInfo.references[0].validate(dvc)
- return valResult
+ if (!sig.validate(dvc)) {
+ throw Exception("bank signature did not verify")
+ }
}
}
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt
@@ -192,13 +192,13 @@ suspend fun registerTxs(
var nbTx: Int = 0
parseTx(xml, cfg.ebics.dialect).forEach { accountTx ->
if (accountTx.iban == cfg.ebics.account.iban) {
- require(accountTx.currency == cfg.currency) { "Expected transactions of currency ${cfg.currency} for ${accountTx.currency}" }
+ require(accountTx.currency == null || accountTx.currency == cfg.currency) { "Expected transactions of currency ${cfg.currency} got ${accountTx.currency}" }
accountTx.txs.forEach { tx ->
when (tx) {
is IncomingPayment ->
- require(tx.amount.currency == cfg.currency) { "Expected transactions of currency ${cfg.currency} for ${tx.amount.currency}" }
+ require(tx.amount.currency == cfg.currency) { "Expected transactions of currency ${cfg.currency} got ${tx.amount.currency}" }
is OutgoingPayment ->
- require(tx.amount.currency == cfg.currency) { "Expected transactions of currency ${cfg.currency} for ${tx.amount.currency}" }
+ require(tx.amount.currency == cfg.currency) { "Expected transactions of currency ${cfg.currency} got ${tx.amount.currency}" }
is OutgoingBatch, is OutgoingReversal -> {}
}
registerTransaction(db, cfg.ingest, tx)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2024 Taler Systems S.A.
+ * Copyright (C) 2024-2025 Taler Systems S.A.
* LibEuFin is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -97,11 +97,13 @@ suspend fun EbicsBTS.postBTS(
stepLogger: StepLogger? = null
): EbicsResponse<BTSResponse> {
val doc = client.postToBank(cfg.host.baseUrl, xmlReq, phase, stepLogger)
- if (!XMLUtil.verifyEbicsDocument(
- doc,
- bankKeys.bank_authentication_public_key
- )) {
- throw EbicsError.Protocol("$phase ${order.description()}: bank signature did not verify")
+ try {
+ XMLUtil.verifyEbicsDocument(
+ doc,
+ bankKeys.bank_authentication_public_key
+ )
+ } catch (e: Exception) {
+ throw EbicsError.Protocol("$phase ${order.description()}: invalid signature", e)
}
val response = try {
EbicsBTS.parseResponse(doc)
diff --git a/nexus/src/test/kotlin/XmlUtilTest.kt b/nexus/src/test/kotlin/XmlUtilTest.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2024 Taler Systems S.A.
+ * Copyright (C) 2024-2025 Taler Systems S.A.
* LibEuFin is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@@ -17,7 +17,7 @@
* <http://www.gnu.org/licenses/>
*/
-import org.junit.Assert.assertTrue
+import kotlin.test.*
import org.junit.Test
import tech.libeufin.common.crypto.CryptoUtil
import tech.libeufin.common.decodeBase64
@@ -39,8 +39,8 @@ class XmlUtilTest {
val pair = kpg.genKeyPair()
val otherPair = kpg.genKeyPair()
XMLUtil.signEbicsDocument(doc, pair.private)
- kotlin.test.assertTrue(XMLUtil.verifyEbicsDocument(doc, pair.public))
- kotlin.test.assertFalse(XMLUtil.verifyEbicsDocument(doc, otherPair.public))
+ XMLUtil.verifyEbicsDocument(doc, pair.public)
+ assertFails { XMLUtil.verifyEbicsDocument(doc, otherPair.public) }
}
@Test
@@ -56,7 +56,7 @@ class XmlUtilTest {
kpg.initialize(2048)
val pair = kpg.genKeyPair()
XMLUtil.signEbicsDocument(doc, pair.private)
- kotlin.test.assertTrue(XMLUtil.verifyEbicsDocument(doc, pair.public))
+ XMLUtil.verifyEbicsDocument(doc, pair.public)
}
@Test
@@ -67,6 +67,6 @@ class XmlUtilTest {
val keyStream = classLoader.getResourceAsStream("signature1/public_key.txt")
val keyBytes = keyStream.decodeBase64().readAllBytes()
val key = CryptoUtil.loadRSAPublic(keyBytes)
- assertTrue(XMLUtil.verifyEbicsDocument(doc, key))
+ XMLUtil.verifyEbicsDocument(doc, key)
}
}
\ No newline at end of file