libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 767e006ac586ca437c1ded8897f3be38a92441f2
parent 370bb52694a7aab5a93d43944a2d6681cee29f3b
Author: Antoine A <>
Date:   Tue, 11 Mar 2025 18:14:47 +0100

nexus: improve EBICS signature verify

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/XMLUtil.kt | 37+++++++++++++++++++------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt | 6+++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt | 14++++++++------
Mnexus/src/test/kotlin/XmlUtilTest.kt | 12++++++------
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