commit 264d6c496a6df8b32a5c7aca7b9ecce1947d3998
parent a9c4bb1841fa79220799f35feb587aaa8b247f2d
Author: Florian Dold <florian.dold@gmail.com>
Date: Mon, 28 Oct 2019 18:37:39 +0100
signing / verification WIP
Diffstat:
11 files changed, 409 insertions(+), 400 deletions(-)
diff --git a/sandbox/build.gradle b/sandbox/build.gradle
@@ -33,6 +33,8 @@ dependencies {
compile "javax.activation:activation:1.1"
compile "org.glassfish.jaxb:jaxb-runtime:2.3.1"
testCompile group: 'junit', name: 'junit', version: '4.12'
+ testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.3.50'
+ testImplementation 'org.jetbrains.kotlin:kotlin-test:1.3.50'
compile 'org.apache.santuario:xmlsec:2.1.4'
runtime rootProject.files("resources")
}
diff --git a/sandbox/src/main/kotlin/Main.kt b/sandbox/src/main/kotlin/Main.kt
@@ -36,6 +36,7 @@ import io.ktor.routing.post
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
+import io.ktor.util.pipeline.PipelineContext
import org.jetbrains.exposed.dao.EntityID
import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.LoggerFactory
@@ -48,17 +49,15 @@ import java.math.BigInteger
import java.nio.charset.StandardCharsets.US_ASCII
import java.text.DateFormat
import java.security.KeyFactory
-import java.security.KeyPairGenerator
import java.security.PrivateKey
import java.security.PublicKey
-import java.security.interfaces.RSAPrivateKey
import java.security.spec.RSAPrivateKeySpec
import java.security.spec.RSAPublicKeySpec
import java.util.*
import java.util.zip.InflaterInputStream
val logger = LoggerFactory.getLogger("tech.libeufin.sandbox")
-val xmlProcess = XML()
+val xmlProcess = XMLUtil()
val getEbicsHostId = {"LIBEUFIN-SANDBOX"}
val getEbicsVersion = {"H004"}
val getEbicsRevision = {1}
@@ -149,7 +148,7 @@ object OkHelper {
* @return the modified document
*/
fun downcastXml(document: Document, node: String, type: String) : Document {
- logger.debug("Downcasting: ${XML.convertDomToString(document)}")
+ logger.debug("Downcasting: ${XMLUtil.convertDomToString(document)}")
val x: Element = document.getElementsByTagNameNS(
"urn:org:ebics:H004",
"OrderDetails"
@@ -234,7 +233,7 @@ private suspend fun ApplicationCall.adminCustomers() {
logger.info(body.toString())
val returnId = transaction {
- var myUserId = EbicsUser.new { }
+ val myUserId = EbicsUser.new { }
val myPartnerId = EbicsPartner.new { }
val mySystemId = EbicsSystem.new { }
val subscriber = EbicsSubscriber.new {
@@ -395,10 +394,10 @@ private suspend fun ApplicationCall.ebicsweb() {
val body: String = receiveText()
logger.debug("Data received: $body")
- val bodyDocument: Document? = XML.parseStringIntoDom(body)
+ val bodyDocument: Document? = XMLUtil.parseStringIntoDom(body)
if (bodyDocument == null || (!xmlProcess.validateFromDom(bodyDocument))) {
- var response = EbicsResponse(
+ val response = EbicsResponse(
returnCode = InvalidXmlHelper.getCode(),
reportText = InvalidXmlHelper.getMessage()
)
@@ -493,7 +492,7 @@ private suspend fun ApplicationCall.ebicsweb() {
*/
if (zkey.isEmpty()) {
logger.info("0-length key element given, invalid request")
- var response = KeyManagementResponse(
+ val response = KeyManagementResponse(
returnCode = InvalidXmlHelper.getCode(),
reportText = InvalidXmlHelper.getMessage("Key field was empty")
)
@@ -678,6 +677,7 @@ private suspend fun ApplicationCall.ebicsweb() {
}
}
+
fun main() {
dbCreateTables()
val server = embeddedServer(Netty, port = 5000) {
diff --git a/sandbox/src/main/kotlin/XML.kt b/sandbox/src/main/kotlin/XML.kt
@@ -1,361 +0,0 @@
-/*
- * This file is part of LibEuFin.
- * Copyright (C) 2019 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.sandbox
-
-import com.sun.org.apache.xerces.internal.dom.DOMInputImpl
-import org.w3c.dom.Document
-import org.w3c.dom.Node
-import org.w3c.dom.NodeList
-import org.w3c.dom.ls.LSInput
-import org.w3c.dom.ls.LSResourceResolver
-import org.xml.sax.ErrorHandler
-import org.xml.sax.InputSource
-import org.xml.sax.SAXException
-import org.xml.sax.SAXParseException
-import java.io.*
-import java.security.PrivateKey
-import java.security.PublicKey
-import java.util.*
-import javax.xml.XMLConstants
-import javax.xml.bind.JAXBContext
-import javax.xml.bind.JAXBElement
-import javax.xml.bind.JAXBException
-import javax.xml.bind.Marshaller
-import javax.xml.crypto.*
-import javax.xml.crypto.dom.DOMURIReference
-import javax.xml.crypto.dsig.*
-import javax.xml.crypto.dsig.dom.DOMSignContext
-import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec
-import javax.xml.crypto.dsig.spec.TransformParameterSpec
-import javax.xml.parsers.DocumentBuilderFactory
-import javax.xml.parsers.ParserConfigurationException
-import javax.xml.transform.OutputKeys
-import javax.xml.transform.Source
-import javax.xml.transform.TransformerFactory
-import javax.xml.transform.dom.DOMSource
-import javax.xml.transform.stream.StreamResult
-import javax.xml.transform.stream.StreamSource
-import javax.xml.validation.SchemaFactory
-import javax.xml.xpath.XPath
-import javax.xml.xpath.XPathConstants
-import javax.xml.xpath.XPathFactory
-
-/**
- * This class takes care of importing XSDs and validate
- * XMLs against those.
- */
-class XML {
- private class EbicsSigUriDereferencer : URIDereferencer {
- override fun dereference(myRef: URIReference?, myCtx: XMLCryptoContext?): Data {
- val ebicsXpathExpr = "//*[@authenticate='true']"
- if (myRef !is DOMURIReference)
- throw Exception("invalid type")
- if (myRef.uri != "#xpointer($ebicsXpathExpr)")
- throw Exception("invalid EBICS XML signature URI: '${myRef.uri}'")
- val xp: XPath = XPathFactory.newInstance().newXPath()
- val nodeSet = xp.compile(ebicsXpathExpr).evaluate(myRef.here.ownerDocument, XPathConstants.NODESET)
- if (nodeSet !is NodeList)
- throw Exception("invalid type")
- if (nodeSet.length <= 0) {
- throw Exception("no nodes to sign")
- }
- val nodeList = LinkedList<Node>()
- for (i in 0 until nodeSet.length) {
- nodeList.add(nodeSet.item(i))
- }
- return NodeSetData { nodeList.iterator() }
- }
- }
-
- /**
- * Validator for EBICS messages.
- */
- private val validator = try {
- val classLoader = ClassLoader.getSystemClassLoader()
- val sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
- sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file")
- sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "")
- sf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
- sf.errorHandler = object : ErrorHandler {
- override fun warning(p0: SAXParseException?) {
- println("Warning: $p0")
- }
-
- override fun error(p0: SAXParseException?) {
- println("Error: $p0")
- }
-
- override fun fatalError(p0: SAXParseException?) {
- println("Fatal error: $p0")
- }
- }
- sf.resourceResolver = object : LSResourceResolver {
- override fun resolveResource(
- type: String?,
- namespaceURI: String?,
- publicId: String?,
- systemId: String?,
- baseUri: String?
- ): LSInput? {
- if (type != "http://www.w3.org/2001/XMLSchema") {
- return null
- }
- val res = classLoader.getResourceAsStream(systemId) ?: return null
- return DOMInputImpl(publicId, systemId, baseUri, res, "UTF-8")
- }
- }
- val schemaInputs: Array<Source> = listOf("ebics_H004.xsd", "ebics_hev.xsd").map {
- val resUrl = classLoader.getResource(it) ?: throw FileNotFoundException("Schema file $it not found.")
- StreamSource(File(resUrl.toURI()))
- }.toTypedArray()
- val bundle = sf.newSchema(schemaInputs)
- bundle.newValidator()
- } catch (e: SAXException) {
- e.printStackTrace()
- throw e
- }
-
-
- /**
- *
- * @param xmlDoc the XML document to validate
- * @return true when validation passes, false otherwise
- */
- fun validate(xmlDoc: StreamSource): Boolean {
- try {
- validator?.validate(xmlDoc)
- } catch (e: SAXException) {
- println(e.message)
- return false
- } catch (e: IOException) {
- e.printStackTrace()
- return false
- }
-
- return true
- }
-
- /**
- * Validates the DOM against the Schema(s) of this object.
- * @param domDocument DOM to validate
- * @return true/false if the document is valid/invalid
- */
- fun validateFromDom(domDocument: Document): Boolean {
- try {
- validator?.validate(DOMSource(domDocument))
- } catch (e: SAXException) {
- e.printStackTrace()
- return false
- }
- return true
- }
-
- /**
- * Craft object to be passed to the XML validator.
- * @param xmlString XML body, as read from the POST body.
- * @return InputStream object, as wanted by the validator.
- */
- fun validateFromString(xmlString: String): Boolean {
- val xmlInputStream: InputStream = ByteArrayInputStream(xmlString.toByteArray())
- val xmlSource = StreamSource(xmlInputStream)
- return this.validate(xmlSource)
- }
-
- /**
- * Convert a DOM document - of a XML document - to the JAXB representation.
- *
- * @param finalType class type of the output
- * @param document the document to convert into JAXB.
- * @return the JAXB object reflecting the original XML document.
- */
- fun <T> convertDomToJaxb(finalType: Class<T>, document: Document): JAXBElement<T> {
-
- val jc = JAXBContext.newInstance(finalType)
-
- /* Marshalling the object into the document. */
- val m = jc.createUnmarshaller()
- return m.unmarshal(document, finalType) // document "went" into Jaxb
- }
-
- /**
- * Convert a XML string to the JAXB representation.
- *
- * @param finalType class type of the object to instantiate
- * @param documentString the string to convert into JAXB.
- * @return the JAXB object reflecting the original XML document.
- */
- fun <T> convertStringToJaxb(finalType: Class<T>, documentString: String): JAXBElement<T> {
-
- val jc = JAXBContext.newInstance(finalType.packageName)
-
- /* Marshalling the object into the document. */
- val u = jc.createUnmarshaller()
- return u.unmarshal(
- StreamSource(StringReader(documentString)),
- finalType
- ) // document "went" into Jaxb
- }
-
-
- /**
- * Return the DOM representation of the Java object, using the JAXB
- * interface. FIXME: narrow input type to JAXB type!
- *
- * @param object to be transformed into DOM. Typically, the object
- * has already got its setters called.
- * @return the DOM Document, or null (if errors occur).
- */
- fun <T> convertJaxbToDom(obj: JAXBElement<T>): Document? {
-
- try {
- val jc = JAXBContext.newInstance(obj.declaredType)
-
- /* Make the target document. */
- val dbf = DocumentBuilderFactory.newInstance()
- val db = dbf.newDocumentBuilder()
- val document = db.newDocument()
-
- /* Marshalling the object into the document. */
- val m = jc.createMarshaller()
- m.marshal(obj, document) // document absorbed the XML!
- return document
-
- } catch (e: JAXBException) {
- e.printStackTrace()
- } catch (e: ParserConfigurationException) {
- e.printStackTrace()
- }
- return null
- }
-
- /**
- * Extract String from JAXB.
- *
- * @param obj the JAXB instance
- * @return String representation of @a object, or null if errors occur
- */
- fun <T> convertJaxbToString(obj: JAXBElement<T>): String? {
- val sw = StringWriter()
-
- try {
- val jc = JAXBContext.newInstance(obj.declaredType)
- /* Getting the string. */
- val m = jc.createMarshaller()
- m.marshal(obj, sw)
- m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
-
- } catch (e: JAXBException) {
- e.printStackTrace()
- return "Bank fatal error."
- }
-
- return sw.toString()
- }
-
- companion object {
- /**
- * Extract String from DOM.
- *
- * @param document the DOM to extract the string from.
- * @return the final String, or null if errors occur.
- */
- fun convertDomToString(document: Document): String? {
- /* Make Transformer. */
- val tf = TransformerFactory.newInstance()
- val t = tf.newTransformer()
-
- t.setOutputProperty(OutputKeys.INDENT, "yes")
-
- /* Make string writer. */
- val sw = StringWriter()
-
- /* Extract string. */
- t.transform(DOMSource(document), StreamResult(sw))
- return sw.toString()
- }
-
- fun convertNodeToString(node: Node): String? {
- /* Make Transformer. */
- val tf = TransformerFactory.newInstance()
- val t = tf.newTransformer()
-
- t.setOutputProperty(OutputKeys.INDENT, "yes")
-
- /* Make string writer. */
- val sw = StringWriter()
-
- /* Extract string. */
- t.transform(DOMSource(node), StreamResult(sw))
- return sw.toString()
- }
-
- /**
- * Parse string into XML DOM.
- * @param xmlString the string to parse.
- * @return the DOM representing @a xmlString
- */
- fun parseStringIntoDom(xmlString: String): Document {
- val factory = DocumentBuilderFactory.newInstance().apply {
- isNamespaceAware = true
- }
- val xmlInputStream = ByteArrayInputStream(xmlString.toByteArray())
- val builder = factory.newDocumentBuilder()
- return builder.parse(InputSource(xmlInputStream))
- }
-
-
- fun signEbicsDocument(doc: Document, signingPriv: PrivateKey): Unit {
- val xpath = XPathFactory.newInstance().newXPath()
- val authSigNode = xpath.compile("/*[1]/AuthSignature").evaluate(doc, XPathConstants.NODE)
- if (authSigNode !is Node)
- throw java.lang.Exception("no AuthSignature")
- val fac = XMLSignatureFactory.getInstance("DOM")
- val c14n = fac.newTransform(CanonicalizationMethod.INCLUSIVE, null as TransformParameterSpec?)
- val ref: Reference =
- fac.newReference(
- "#xpointer(//*[@authenticate='true'])",
- fac.newDigestMethod(DigestMethod.SHA256, null),
- listOf(c14n),
- null,
- null
- )
- val canon: CanonicalizationMethod =
- fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null as C14NMethodParameterSpec?)
- val signatureMethod = fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null)
- val si: SignedInfo = fac.newSignedInfo(canon, signatureMethod, listOf(ref))
- val sig: XMLSignature = fac.newXMLSignature(si, null)
- val dsc = DOMSignContext(signingPriv, authSigNode)
- dsc.defaultNamespacePrefix = "ds"
- dsc.uriDereferencer = EbicsSigUriDereferencer()
-
- sig.sign(dsc)
-
- val innerSig = authSigNode.firstChild
- while (innerSig.hasChildNodes()) {
- authSigNode.appendChild(innerSig.firstChild)
- }
- authSigNode.removeChild(innerSig)
- }
-
- fun verifyEbicsDocument(doc: Document, signingPub: PublicKey): Boolean {
- return false
- }
- }
-}
-\ No newline at end of file
diff --git a/sandbox/src/main/kotlin/XMLUtil.kt b/sandbox/src/main/kotlin/XMLUtil.kt
@@ -0,0 +1,381 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2019 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.sandbox
+
+import com.sun.org.apache.xerces.internal.dom.DOMInputImpl
+import org.w3c.dom.Document
+import org.w3c.dom.Node
+import org.w3c.dom.NodeList
+import org.w3c.dom.ls.LSInput
+import org.w3c.dom.ls.LSResourceResolver
+import org.xml.sax.ErrorHandler
+import org.xml.sax.InputSource
+import org.xml.sax.SAXException
+import org.xml.sax.SAXParseException
+import java.io.*
+import java.security.PrivateKey
+import java.security.PublicKey
+import java.util.*
+import javax.xml.XMLConstants
+import javax.xml.bind.JAXBContext
+import javax.xml.bind.JAXBElement
+import javax.xml.bind.JAXBException
+import javax.xml.bind.Marshaller
+import javax.xml.crypto.*
+import javax.xml.crypto.dom.DOMURIReference
+import javax.xml.crypto.dsig.*
+import javax.xml.crypto.dsig.dom.DOMSignContext
+import javax.xml.crypto.dsig.dom.DOMValidateContext
+import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec
+import javax.xml.crypto.dsig.spec.TransformParameterSpec
+import javax.xml.parsers.DocumentBuilderFactory
+import javax.xml.parsers.ParserConfigurationException
+import javax.xml.transform.OutputKeys
+import javax.xml.transform.Source
+import javax.xml.transform.TransformerFactory
+import javax.xml.transform.dom.DOMSource
+import javax.xml.transform.stream.StreamResult
+import javax.xml.transform.stream.StreamSource
+import javax.xml.validation.SchemaFactory
+import javax.xml.xpath.XPath
+import javax.xml.xpath.XPathConstants
+import javax.xml.xpath.XPathFactory
+
+/**
+ * Helpers for dealing with XML in EBICS.
+ */
+class XMLUtil {
+ /**
+ * This URI dereferencer allows handling the resource reference used for
+ * XML signatures in EBICS.
+ */
+ private class EbicsSigUriDereferencer : URIDereferencer {
+ override fun dereference(myRef: URIReference?, myCtx: XMLCryptoContext?): Data {
+ val ebicsXpathExpr = "//*[@authenticate='true']"
+ if (myRef !is DOMURIReference)
+ throw Exception("invalid type")
+ if (myRef.uri != "#xpointer($ebicsXpathExpr)")
+ throw Exception("invalid EBICS XML signature URI: '${myRef.uri}'")
+ val xp: XPath = XPathFactory.newInstance().newXPath()
+ val nodeSet = xp.compile(ebicsXpathExpr).evaluate(myRef.here.ownerDocument, XPathConstants.NODESET)
+ if (nodeSet !is NodeList)
+ throw Exception("invalid type")
+ if (nodeSet.length <= 0) {
+ throw Exception("no nodes to sign")
+ }
+ val nodeList = LinkedList<Node>()
+ for (i in 0 until nodeSet.length) {
+ nodeList.add(nodeSet.item(i))
+ }
+ return NodeSetData { nodeList.iterator() }
+ }
+ }
+
+ /**
+ * Validator for EBICS messages.
+ */
+ private val validator = try {
+ val classLoader = ClassLoader.getSystemClassLoader()
+ val sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
+ sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "file")
+ sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "")
+ sf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
+ sf.errorHandler = object : ErrorHandler {
+ override fun warning(p0: SAXParseException?) {
+ println("Warning: $p0")
+ }
+
+ override fun error(p0: SAXParseException?) {
+ println("Error: $p0")
+ }
+
+ override fun fatalError(p0: SAXParseException?) {
+ println("Fatal error: $p0")
+ }
+ }
+ sf.resourceResolver = object : LSResourceResolver {
+ override fun resolveResource(
+ type: String?,
+ namespaceURI: String?,
+ publicId: String?,
+ systemId: String?,
+ baseUri: String?
+ ): LSInput? {
+ if (type != "http://www.w3.org/2001/XMLSchema") {
+ return null
+ }
+ val res = classLoader.getResourceAsStream(systemId) ?: return null
+ return DOMInputImpl(publicId, systemId, baseUri, res, "UTF-8")
+ }
+ }
+ val schemaInputs: Array<Source> = listOf("ebics_H004.xsd", "ebics_hev.xsd").map {
+ val resUrl = classLoader.getResource(it) ?: throw FileNotFoundException("Schema file $it not found.")
+ StreamSource(File(resUrl.toURI()))
+ }.toTypedArray()
+ val bundle = sf.newSchema(schemaInputs)
+ bundle.newValidator()
+ } catch (e: SAXException) {
+ e.printStackTrace()
+ throw e
+ }
+
+
+ /**
+ *
+ * @param xmlDoc the XML document to validate
+ * @return true when validation passes, false otherwise
+ */
+ fun validate(xmlDoc: StreamSource): Boolean {
+ try {
+ validator?.validate(xmlDoc)
+ } catch (e: Exception) {
+ logger.warn("Validation failed: {}", e)
+ return false
+ }
+ return true;
+ }
+
+ /**
+ * Validates the DOM against the Schema(s) of this object.
+ * @param domDocument DOM to validate
+ * @return true/false if the document is valid/invalid
+ */
+ fun validateFromDom(domDocument: Document): Boolean {
+ try {
+ validator?.validate(DOMSource(domDocument))
+ } catch (e: SAXException) {
+ e.printStackTrace()
+ return false
+ }
+ return true
+ }
+
+ /**
+ * Craft object to be passed to the XML validator.
+ * @param xmlString XML body, as read from the POST body.
+ * @return InputStream object, as wanted by the validator.
+ */
+ fun validateFromString(xmlString: String): Boolean {
+ val xmlInputStream: InputStream = ByteArrayInputStream(xmlString.toByteArray())
+ val xmlSource = StreamSource(xmlInputStream)
+ return this.validate(xmlSource)
+ }
+
+ /**
+ * Convert a DOM document - of a XML document - to the JAXB representation.
+ *
+ * @param finalType class type of the output
+ * @param document the document to convert into JAXB.
+ * @return the JAXB object reflecting the original XML document.
+ */
+ fun <T> convertDomToJaxb(finalType: Class<T>, document: Document): JAXBElement<T> {
+
+ val jc = JAXBContext.newInstance(finalType)
+
+ /* Marshalling the object into the document. */
+ val m = jc.createUnmarshaller()
+ return m.unmarshal(document, finalType) // document "went" into Jaxb
+ }
+
+ /**
+ * Convert a XML string to the JAXB representation.
+ *
+ * @param finalType class type of the object to instantiate
+ * @param documentString the string to convert into JAXB.
+ * @return the JAXB object reflecting the original XML document.
+ */
+ fun <T> convertStringToJaxb(finalType: Class<T>, documentString: String): JAXBElement<T> {
+
+ val jc = JAXBContext.newInstance(finalType.packageName)
+
+ /* Marshalling the object into the document. */
+ val u = jc.createUnmarshaller()
+ return u.unmarshal(
+ StreamSource(StringReader(documentString)),
+ finalType
+ ) // document "went" into Jaxb
+ }
+
+
+ /**
+ * Return the DOM representation of the Java object, using the JAXB
+ * interface. FIXME: narrow input type to JAXB type!
+ *
+ * @param object to be transformed into DOM. Typically, the object
+ * has already got its setters called.
+ * @return the DOM Document, or null (if errors occur).
+ */
+ fun <T> convertJaxbToDom(obj: JAXBElement<T>): Document? {
+
+ try {
+ val jc = JAXBContext.newInstance(obj.declaredType)
+
+ /* Make the target document. */
+ val dbf = DocumentBuilderFactory.newInstance()
+ val db = dbf.newDocumentBuilder()
+ val document = db.newDocument()
+
+ /* Marshalling the object into the document. */
+ val m = jc.createMarshaller()
+ m.marshal(obj, document) // document absorbed the XML!
+ return document
+
+ } catch (e: JAXBException) {
+ e.printStackTrace()
+ } catch (e: ParserConfigurationException) {
+ e.printStackTrace()
+ }
+ return null
+ }
+
+ /**
+ * Extract String from JAXB.
+ *
+ * @param obj the JAXB instance
+ * @return String representation of @a object, or null if errors occur
+ */
+ fun <T> convertJaxbToString(obj: JAXBElement<T>): String? {
+ val sw = StringWriter()
+
+ try {
+ val jc = JAXBContext.newInstance(obj.declaredType)
+ /* Getting the string. */
+ val m = jc.createMarshaller()
+ m.marshal(obj, sw)
+ m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true)
+
+ } catch (e: JAXBException) {
+ e.printStackTrace()
+ return "Bank fatal error."
+ }
+
+ return sw.toString()
+ }
+
+ companion object {
+ /**
+ * Extract String from DOM.
+ *
+ * @param document the DOM to extract the string from.
+ * @return the final String, or null if errors occur.
+ */
+ fun convertDomToString(document: Document): String? {
+ /* Make Transformer. */
+ val tf = TransformerFactory.newInstance()
+ val t = tf.newTransformer()
+
+ t.setOutputProperty(OutputKeys.INDENT, "yes")
+
+ /* Make string writer. */
+ val sw = StringWriter()
+
+ /* Extract string. */
+ t.transform(DOMSource(document), StreamResult(sw))
+ return sw.toString()
+ }
+
+ fun convertNodeToString(node: Node): String? {
+ /* Make Transformer. */
+ val tf = TransformerFactory.newInstance()
+ val t = tf.newTransformer()
+
+ t.setOutputProperty(OutputKeys.INDENT, "yes")
+
+ /* Make string writer. */
+ val sw = StringWriter()
+
+ /* Extract string. */
+ t.transform(DOMSource(node), StreamResult(sw))
+ return sw.toString()
+ }
+
+ /**
+ * Parse string into XML DOM.
+ * @param xmlString the string to parse.
+ * @return the DOM representing @a xmlString
+ */
+ fun parseStringIntoDom(xmlString: String): Document {
+ val factory = DocumentBuilderFactory.newInstance().apply {
+ isNamespaceAware = true
+ }
+ val xmlInputStream = ByteArrayInputStream(xmlString.toByteArray())
+ val builder = factory.newDocumentBuilder()
+ return builder.parse(InputSource(xmlInputStream))
+ }
+
+
+ /**
+ * Sign an EBICS document with the authentication and identity signature.
+ */
+ fun signEbicsDocument(doc: Document, signingPriv: PrivateKey): Unit {
+ val xpath = XPathFactory.newInstance().newXPath()
+ val authSigNode = xpath.compile("/*[1]/AuthSignature").evaluate(doc, XPathConstants.NODE)
+ if (authSigNode !is Node)
+ throw java.lang.Exception("no AuthSignature")
+ val fac = XMLSignatureFactory.getInstance("DOM")
+ val c14n = fac.newTransform(CanonicalizationMethod.INCLUSIVE, null as TransformParameterSpec?)
+ val ref: Reference =
+ fac.newReference(
+ "#xpointer(//*[@authenticate='true'])",
+ fac.newDigestMethod(DigestMethod.SHA256, null),
+ listOf(c14n),
+ null,
+ null
+ )
+ val canon: CanonicalizationMethod =
+ fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, null as C14NMethodParameterSpec?)
+ val signatureMethod = fac.newSignatureMethod(SignatureMethod.RSA_SHA256, null)
+ val si: SignedInfo = fac.newSignedInfo(canon, signatureMethod, listOf(ref))
+ val sig: XMLSignature = fac.newXMLSignature(si, null)
+ val dsc = DOMSignContext(signingPriv, authSigNode)
+ dsc.defaultNamespacePrefix = "ds"
+ dsc.uriDereferencer = EbicsSigUriDereferencer()
+
+ sig.sign(dsc)
+
+ val innerSig = authSigNode.firstChild
+ while (innerSig.hasChildNodes()) {
+ authSigNode.appendChild(innerSig.firstChild)
+ }
+ authSigNode.removeChild(innerSig)
+ }
+
+ fun verifyEbicsDocument(doc: Document, signingPub: PublicKey): Boolean {
+ val xpath = XPathFactory.newInstance().newXPath()
+ val doc2: Document = doc.cloneNode(true) as Document
+ val authSigNode = xpath.compile("/*[1]/AuthSignature").evaluate(doc2, XPathConstants.NODE)
+ if (authSigNode !is Node)
+ throw java.lang.Exception("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)
+ }
+ authSigNode.parentNode.removeChild(authSigNode)
+ val fac = XMLSignatureFactory.getInstance("DOM")
+ println(convertDomToString(doc2))
+ val dvc = DOMValidateContext(signingPub, sigEl)
+ dvc.uriDereferencer = EbicsSigUriDereferencer()
+ val sig = fac.unmarshalXMLSignature(dvc)
+ // FIXME: check that parameters are okay!
+ return sig.validate(dvc)
+ }
+ }
+}
+\ No newline at end of file
diff --git a/sandbox/src/test/kotlin/HiaLoadTest.kt b/sandbox/src/test/kotlin/HiaLoadTest.kt
@@ -9,10 +9,10 @@ class HiaLoadTest {
@Test
fun hiaLoad() {
- val processor = XML()
+ val processor = XMLUtil()
val classLoader = ClassLoader.getSystemClassLoader()
val hia = classLoader.getResource("HIA.xml")
- val hiaDom = XML.parseStringIntoDom(hia.readText())
+ val hiaDom = XMLUtil.parseStringIntoDom(hia.readText())
val x: Element = hiaDom.getElementsByTagNameNS(
"urn:org:ebics:H004",
"OrderDetails"
diff --git a/sandbox/src/test/kotlin/JaxbTest.kt b/sandbox/src/test/kotlin/JaxbTest.kt
@@ -7,7 +7,7 @@ import tech.libeufin.messages.ebics.keyrequest.SignaturePubKeyOrderDataType
class JaxbTest {
- val processor = XML()
+ val processor = XMLUtil()
val classLoader = ClassLoader.getSystemClassLoader()
val hevResponseJaxb = HEVResponse(
"000000",
@@ -81,7 +81,7 @@ class JaxbTest {
@Test
fun domToJaxb() {
val ini = classLoader.getResource("ebics_ini_request_sample_patched.xml")
- val iniDom = XML.parseStringIntoDom(ini.readText())
+ val iniDom = XMLUtil.parseStringIntoDom(ini.readText())
processor.convertDomToJaxb<EbicsUnsecuredRequest>(
EbicsUnsecuredRequest::class.java,
iniDom
diff --git a/sandbox/src/test/kotlin/MarshalNonJaxbTest.kt b/sandbox/src/test/kotlin/MarshalNonJaxbTest.kt
@@ -34,7 +34,7 @@ class MarshalNonJaxbTest {
"EBICS_NON_OK"
)
- val proc = XML()
+ val proc = XMLUtil()
println(proc.convertJaxbToString(obj.get()))
}
}
\ No newline at end of file
diff --git a/sandbox/src/test/kotlin/ResponseTest.kt b/sandbox/src/test/kotlin/ResponseTest.kt
@@ -4,7 +4,7 @@ import org.junit.Test
class ResponseTest {
- val xmlprocess = XML()
+ val xmlprocess = XMLUtil()
@Test
fun loadResponse() {
diff --git a/sandbox/src/test/kotlin/XmlSigTest.kt b/sandbox/src/test/kotlin/XmlSigTest.kt
@@ -1,27 +1,16 @@
package tech.libeufin.sandbox
import org.junit.Test
-import org.w3c.dom.Node
-import org.w3c.dom.NodeList
import java.security.KeyPairGenerator
-import java.util.*
-import javax.xml.crypto.NodeSetData
-import javax.xml.crypto.URIDereferencer
-import javax.xml.crypto.dom.DOMURIReference
-import javax.xml.crypto.dsig.*
-import javax.xml.crypto.dsig.dom.DOMSignContext
-import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec
-import javax.xml.crypto.dsig.spec.TransformParameterSpec
-import javax.xml.xpath.XPath
-import javax.xml.xpath.XPathConstants
-import javax.xml.xpath.XPathFactory
+import kotlin.test.*
+
class XmlSigTest {
@Test
fun basicSigningTest() {
- val doc = XML.parseStringIntoDom("""
+ val doc = XMLUtil.parseStringIntoDom("""
<foo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<AuthSignature />
<bar authenticate='true'>bla</bar>Hello World
@@ -35,7 +24,10 @@ class XmlSigTest {
val kpg = KeyPairGenerator.getInstance("RSA")
kpg.initialize(2048)
val pair = kpg.genKeyPair()
- XML.signEbicsDocument(doc, pair.private)
- println(XML.convertDomToString(doc))
+ val otherPair = kpg.genKeyPair()
+ XMLUtil.signEbicsDocument(doc, pair.private)
+ println(XMLUtil.convertDomToString(doc))
+ assertTrue(XMLUtil.verifyEbicsDocument(doc, pair.public))
+ assertFalse(XMLUtil.verifyEbicsDocument(doc, otherPair.public))
}
}
\ No newline at end of file
diff --git a/sandbox/src/test/kotlin/XmlTest.kt b/sandbox/src/test/kotlin/XmlTest.kt
@@ -1,15 +1,12 @@
package tech.libeufin.sandbox
-import org.junit.Assert
import org.junit.Test
import org.junit.Assert.*
-import java.io.File
-import javax.xml.transform.Source
import javax.xml.transform.stream.StreamSource
class XmlTest {
- val processor = tech.libeufin.sandbox.XML()
+ val processor = tech.libeufin.sandbox.XMLUtil()
@Test
fun hevValidation(){
diff --git a/sandbox/src/test/kotlin/XsiTypeAttributeTest.kt b/sandbox/src/test/kotlin/XsiTypeAttributeTest.kt
@@ -3,18 +3,16 @@ package tech.libeufin.sandbox
import org.junit.Test
import org.w3c.dom.Element
import tech.libeufin.messages.ebics.keyrequest.EbicsUnsecuredRequest
-import tech.libeufin.messages.ebics.keyrequest.OrderDetailsType
-import tech.libeufin.messages.ebics.keyrequest.UnsecuredReqOrderDetailsType
class XsiTypeAttributeTest {
@Test
fun domToJaxb() {
- val processor = XML()
+ val processor = XMLUtil()
val classLoader = ClassLoader.getSystemClassLoader()
val ini = classLoader.getResource("ebics_ini_request_sample.xml")
- val iniDom = XML.parseStringIntoDom(ini.readText())
+ val iniDom = XMLUtil.parseStringIntoDom(ini.readText())
val x: Element = iniDom.getElementsByTagName("OrderDetails")?.item(0) as Element
x.setAttributeNS(