commit 567bf82e855829652b2250f83acd1f8fcf82e37c
parent 2d87b329c99cb2b335df317fe00faab941055b1a
Author: Antoine A <>
Date: Tue, 5 Nov 2024 15:34:32 +0100
nexus: XML signature check while parsing
Diffstat:
5 files changed, 51 insertions(+), 29 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/XMLUtil.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/XMLUtil.kt
@@ -100,9 +100,7 @@ object XMLUtil {
}
}
- /**
- * Sign an EBICS document with the authentication and identity signature.
- */
+ /** Sign an EBICS document with the authentication and identity signature */
fun signEbicsDocument(
doc: Document,
signingPriv: PrivateKey
@@ -138,10 +136,12 @@ object XMLUtil {
authSigNode.removeChild(innerSig)
}
+ /** Check an EBICS document signature */
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)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/XmlCombinators.kt
@@ -125,34 +125,32 @@ private class XmlDOMBuilder(private val doc: Document, private val schema: Strin
class DestructionError(m: String) : Exception(m)
-private fun Element.childrenByTag(tag: String): Sequence<Element> = sequence {
+private fun Element.childrenByTag(tag: String, signed: Boolean): Sequence<Element> = sequence {
for (i in 0..childNodes.length) {
val el = childNodes.item(i)
- if (el !is Element) {
- continue
+ if (el is Element
+ && el.localName == tag
+ && (!signed || el.getAttribute("authenticate") == "true")) {
+ yield(el)
}
- if (el.localName != tag) {
- continue
- }
- yield(el)
}
}
class XmlDestructor internal constructor(private val el: Element) {
- fun each(path: String, f: XmlDestructor.() -> Unit) {
- el.childrenByTag(path).forEach {
+ fun each(path: String, signed: Boolean = false, f: XmlDestructor.() -> Unit) {
+ el.childrenByTag(path, signed).forEach {
f(XmlDestructor(it))
}
}
- fun <T> map(path: String, f: XmlDestructor.() -> T): List<T> {
- return el.childrenByTag(path).map {
+ fun <T> map(path: String, signed: Boolean = false, f: XmlDestructor.() -> T): List<T> {
+ return el.childrenByTag(path, signed).map {
f(XmlDestructor(it))
}.toList()
}
- fun one(tag: String): XmlDestructor {
- val children = el.childrenByTag(tag).iterator()
+ fun one(tag: String, signed: Boolean = false): XmlDestructor {
+ val children = el.childrenByTag(tag, signed).iterator()
if (!children.hasNext()) {
throw DestructionError("expected unique '${el.tagName}.$tag', got none")
}
@@ -162,8 +160,8 @@ class XmlDestructor internal constructor(private val el: Element) {
}
return XmlDestructor(child)
}
- fun opt(tag: String): XmlDestructor? {
- val children = el.childrenByTag(tag).iterator()
+ fun opt(tag: String, signed: Boolean = false): XmlDestructor? {
+ val children = el.childrenByTag(tag, signed).iterator()
if (!children.hasNext()) {
return null
}
@@ -174,8 +172,8 @@ class XmlDestructor internal constructor(private val el: Element) {
return XmlDestructor(child)
}
- fun <T> one(path: String, f: XmlDestructor.() -> T): T = f(one(path))
- fun <T> opt(path: String, f: XmlDestructor.() -> T): T? = opt(path)?.run(f)
+ fun <T> one(path: String, signed: Boolean = false, f: XmlDestructor.() -> T): T = f(one(path, signed))
+ fun <T> opt(path: String, signed: Boolean = false, f: XmlDestructor.() -> T): T? = opt(path, signed)?.run(f)
fun text(): String = el.textContent
fun bool(): Boolean = el.textContent.toBoolean()
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsBTS.kt
@@ -299,7 +299,7 @@ class EbicsBTS(
var segmentNumber: Int? = null
var segment: ByteArray? = null
var dataEncryptionInfo: DataEncryptionInfo? = null
- one("header") {
+ one("header", signed = true) {
one("static") {
transactionID = opt("TransactionID")?.text()
numSegments = opt("NumSegments")?.text()?.toInt()
@@ -313,14 +313,14 @@ class EbicsBTS(
one("body") {
opt("DataTransfer") {
segment = one("OrderData").text().decodeBase64()
- dataEncryptionInfo = opt("DataEncryptionInfo") {
+ dataEncryptionInfo = opt("DataEncryptionInfo", signed = true) {
DataEncryptionInfo(
one("TransactionKey").text().decodeBase64(),
one("EncryptionPubKeyDigest").text().decodeBase64()
)
}
}
- bankCode = EbicsReturnCode.lookup(one("ReturnCode").text())
+ bankCode = EbicsReturnCode.lookup(one("ReturnCode", signed = true).text())
}
EbicsResponse(
bankCode = bankCode,
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsKeyMng.kt
@@ -139,15 +139,15 @@ class EbicsKeyMng(
lateinit var technicalCode: EbicsReturnCode
lateinit var bankCode: EbicsReturnCode
var payload: InputStream? = null
- one("header") {
+ one("header", signed = true) {
one("mutable") {
technicalCode = EbicsReturnCode.lookup(one("ReturnCode").text())
}
}
one("body") {
- bankCode = EbicsReturnCode.lookup(one("ReturnCode").text())
+ bankCode = EbicsReturnCode.lookup(one("ReturnCode", signed = true).text())
payload = opt("DataTransfer") {
- val descriptionInfo = one("DataEncryptionInfo") {
+ val descriptionInfo = one("DataEncryptionInfo", signed = true) {
DataEncryptionInfo(
one("TransactionKey").text().decodeBase64(),
one("EncryptionPubKeyDigest").text().decodeBase64()
diff --git a/nexus/src/test/kotlin/XmlCombinatorsTest.kt b/nexus/src/test/kotlin/XmlCombinatorsTest.kt
@@ -17,17 +17,18 @@
* <http://www.gnu.org/licenses/>
*/
+import org.w3c.dom.Document
import org.junit.Test
-import tech.libeufin.nexus.XMLUtil
-import tech.libeufin.nexus.XmlBuilder
+import tech.libeufin.nexus.*
import kotlin.test.assertEquals
class XmlCombinatorsTest {
- fun testBuilder(expected: String, root: String, builder: XmlBuilder.() -> Unit) {
+ fun testBuilder(expected: String, root: String, builder: XmlBuilder.() -> Unit): Document {
val toBytes = XmlBuilder.toBytes(root, builder)
val toDom = XmlBuilder.toDom(root, null, builder)
//assertEquals(expected, toString) TODO fix empty tag being closed only with toString
assertEquals(expected, XMLUtil.convertDomToBytes(toDom).toString(Charsets.UTF_8))
+ return toDom
}
@Test
@@ -73,4 +74,27 @@ class XmlCombinatorsTest {
el("one_more")
}
}
+
+ @Test
+ fun signed() {
+ val trapped = XmlBuilder.toDom("document", "urn:org:ebics:test") {
+ el("order") {
+ text("not signed")
+ }
+ el("order") {
+ attr("authenticate", "true")
+ text("signed")
+ }
+ el("order") {
+ attr("authenticate", "false")
+ text("not signed 2")
+ }
+ }
+ XmlDestructor.fromDoc(trapped, "document") {
+ assertEquals(3, map("order") { text() }.size)
+ one("order", signed = true) {
+ assertEquals("signed", text())
+ }
+ }
+ }
}