[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] 01/03: implement XML signing, minor refactoring
From: |
gnunet |
Subject: |
[libeufin] 01/03: implement XML signing, minor refactoring |
Date: |
Mon, 28 Oct 2019 22:46:41 +0100 |
This is an automated email from the git hooks/post-receive script.
dold pushed a commit to branch master
in repository libeufin.
commit a9c4bb1841fa79220799f35feb587aaa8b247f2d
Author: Florian Dold <address@hidden>
AuthorDate: Mon Oct 28 15:51:43 2019 +0100
implement XML signing, minor refactoring
---
.idea/dictionaries/dold.xml | 7 +
.idea/gradle.xml | 2 +-
.idea/misc.xml | 2 +-
sandbox/build.gradle | 1 +
.../libeufin/messages/ebics/hev/ObjectFactory.java | 2 +-
sandbox/src/main/kotlin/Main.kt | 4 +-
sandbox/src/main/kotlin/XML.kt | 193 ++++++++++++++-------
sandbox/src/test/kotlin/GeneratePrivateKeyTest.kt | 2 +-
sandbox/src/test/kotlin/HiaLoadTest.kt | 13 +-
sandbox/src/test/kotlin/JaxbTest.kt | 6 +-
sandbox/src/test/kotlin/XmlSigTest.kt | 41 +++++
sandbox/src/test/kotlin/XsiTypeAttributeTest.kt | 4 +-
12 files changed, 197 insertions(+), 80 deletions(-)
diff --git a/.idea/dictionaries/dold.xml b/.idea/dictionaries/dold.xml
new file mode 100644
index 0000000..bef9337
--- /dev/null
+++ b/.idea/dictionaries/dold.xml
@@ -0,0 +1,7 @@
+<component name="ProjectDictionaryState">
+ <dictionary name="dold">
+ <words>
+ <w>ebics</w>
+ </words>
+ </dictionary>
+</component>
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 49f0342..b8ed810 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -9,7 +9,7 @@
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/share/java/gradle" />
- <option name="gradleJvm" value="11" />
+ <option name="gradleJvm" value="#JAVA_INTERNAL" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 25d34a4..c1d4bf5 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
- <component name="ProjectRootManager" version="2" languageLevel="JDK_11"
project-jdk-name="11" project-jdk-type="JavaSDK" />
+ <component name="ProjectRootManager" version="2" languageLevel="JDK_11"
default="false" project-jdk-name="12" project-jdk-type="JavaSDK" />
</project>
\ No newline at end of file
diff --git a/sandbox/build.gradle b/sandbox/build.gradle
index 876e554..4c14e89 100644
--- a/sandbox/build.gradle
+++ b/sandbox/build.gradle
@@ -33,6 +33,7 @@ dependencies {
compile "javax.activation:activation:1.1"
compile "org.glassfish.jaxb:jaxb-runtime:2.3.1"
testCompile group: 'junit', name: 'junit', version: '4.12'
+ compile 'org.apache.santuario:xmlsec:2.1.4'
runtime rootProject.files("resources")
}
diff --git
a/sandbox/src/main/java/tech/libeufin/messages/ebics/hev/ObjectFactory.java
b/sandbox/src/main/java/tech/libeufin/messages/ebics/hev/ObjectFactory.java
index 9eb2b69..8674e2d 100644
--- a/sandbox/src/main/java/tech/libeufin/messages/ebics/hev/ObjectFactory.java
+++ b/sandbox/src/main/java/tech/libeufin/messages/ebics/hev/ObjectFactory.java
@@ -11,7 +11,7 @@ import javax.xml.namespace.QName;
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the tech.libeufin package.
- * <p>An ObjectFactory allows you to programatically
+ * <p>An ObjectFactory allows you to programmatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
diff --git a/sandbox/src/main/kotlin/Main.kt b/sandbox/src/main/kotlin/Main.kt
index 7122e03..4cca079 100644
--- a/sandbox/src/main/kotlin/Main.kt
+++ b/sandbox/src/main/kotlin/Main.kt
@@ -149,7 +149,7 @@ object OkHelper {
* @return the modified document
*/
fun downcastXml(document: Document, node: String, type: String) : Document {
- logger.debug("Downcasting: ${xmlProcess.convertDomToString(document)}")
+ logger.debug("Downcasting: ${XML.convertDomToString(document)}")
val x: Element = document.getElementsByTagNameNS(
"urn:org:ebics:H004",
"OrderDetails"
@@ -395,7 +395,7 @@ private suspend fun ApplicationCall.ebicsweb() {
val body: String = receiveText()
logger.debug("Data received: $body")
- val bodyDocument: Document? = xmlProcess.parseStringIntoDom(body)
+ val bodyDocument: Document? = XML.parseStringIntoDom(body)
if (bodyDocument == null || (!xmlProcess.validateFromDom(bodyDocument))) {
var response = EbicsResponse(
diff --git a/sandbox/src/main/kotlin/XML.kt b/sandbox/src/main/kotlin/XML.kt
index c9b96e4..5d0b124 100644
--- a/sandbox/src/main/kotlin/XML.kt
+++ b/sandbox/src/main/kotlin/XML.kt
@@ -21,6 +21,8 @@ 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
@@ -28,25 +30,60 @@ 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.*
+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.
*/
@@ -96,33 +133,6 @@ class XML {
}
- /**
- * 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()
- factory.isNamespaceAware = true
-
- try {
- val xmlInputStream = ByteArrayInputStream(xmlString.toByteArray())
- val builder = factory.newDocumentBuilder()
- val document = builder.parse(InputSource(xmlInputStream))
-
- return document
-
- } catch (e: ParserConfigurationException) {
- e.printStackTrace()
- } catch (e: SAXException) {
- e.printStackTrace()
- } catch (e: IOException) {
- e.printStackTrace()
- }
- return null
- }
-
/**
*
* @param xmlDoc the XML document to validate
@@ -175,7 +185,7 @@ class XML {
* @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> {
+ fun <T> convertDomToJaxb(finalType: Class<T>, document: Document):
JAXBElement<T> {
val jc = JAXBContext.newInstance(finalType)
@@ -191,7 +201,7 @@ class XML {
* @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> {
+ fun <T> convertStringToJaxb(finalType: Class<T>, documentString: String):
JAXBElement<T> {
val jc = JAXBContext.newInstance(finalType.packageName)
@@ -204,7 +214,6 @@ class XML {
}
-
/**
* Return the DOM representation of the Java object, using the JAXB
* interface. FIXME: narrow input type to JAXB type!
@@ -213,7 +222,7 @@ class XML {
* has already got its setters called.
* @return the DOM Document, or null (if errors occur).
*/
- fun <T>convertJaxbToDom(obj: JAXBElement<T>): Document? {
+ fun <T> convertJaxbToDom(obj: JAXBElement<T>): Document? {
try {
val jc = JAXBContext.newInstance(obj.declaredType)
@@ -237,19 +246,42 @@ class XML {
}
/**
- * Extract String from DOM.
+ * Extract String from JAXB.
*
- * @param document the DOM to extract the string from.
- * @return the final String, or null if errors occur.
+ * @param obj the JAXB instance
+ * @return String representation of @a object, or null if errors occur
*/
- fun convertDomToString(document: Document): String? {
+ 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, "no")
+ t.setOutputProperty(OutputKeys.INDENT, "yes")
/* Make string writer. */
val sw = StringWriter()
@@ -257,36 +289,73 @@ class XML {
/* Extract string. */
t.transform(DOMSource(document), StreamResult(sw))
return sw.toString()
+ }
- } catch (e: TransformerConfigurationException) {
- e.printStackTrace()
- } catch (e: TransformerException) {
- e.printStackTrace()
+ 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()
}
- 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()
+ /**
+ * 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))
+ }
- 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."
+ 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)
}
- return sw.toString()
+ fun verifyEbicsDocument(doc: Document, signingPub: PublicKey): Boolean
{
+ return false
+ }
}
}
\ No newline at end of file
diff --git a/sandbox/src/test/kotlin/GeneratePrivateKeyTest.kt
b/sandbox/src/test/kotlin/GeneratePrivateKeyTest.kt
index d5f6496..cddd900 100644
--- a/sandbox/src/test/kotlin/GeneratePrivateKeyTest.kt
+++ b/sandbox/src/test/kotlin/GeneratePrivateKeyTest.kt
@@ -15,7 +15,7 @@ class GeneratePrivateKeyTest {
@Test
fun loadOrGeneratePrivateKey() {
- val x = getOrMakePrivateKey()
+ getOrMakePrivateKey()
assertTrue(
transaction {
diff --git a/sandbox/src/test/kotlin/HiaLoadTest.kt
b/sandbox/src/test/kotlin/HiaLoadTest.kt
index 349b180..89810a1 100644
--- a/sandbox/src/test/kotlin/HiaLoadTest.kt
+++ b/sandbox/src/test/kotlin/HiaLoadTest.kt
@@ -12,10 +12,11 @@ class HiaLoadTest {
val processor = XML()
val classLoader = ClassLoader.getSystemClassLoader()
val hia = classLoader.getResource("HIA.xml")
- val hiaDom = processor.parseStringIntoDom(hia.readText())
- val x: Element = hiaDom?.getElementsByTagNameNS(
+ val hiaDom = XML.parseStringIntoDom(hia.readText())
+ val x: Element = hiaDom.getElementsByTagNameNS(
"urn:org:ebics:H004",
- "OrderDetails")?.item(0) as Element
+ "OrderDetails"
+ )?.item(0) as Element
x.setAttributeNS(
"http://www.w3.org/2001/XMLSchema-instance",
@@ -25,9 +26,7 @@ class HiaLoadTest {
processor.convertDomToJaxb<EbicsUnsecuredRequest>(
EbicsUnsecuredRequest::class.java,
- hiaDom!!)
+ hiaDom
+ )
}
-
-
-
}
\ No newline at end of file
diff --git a/sandbox/src/test/kotlin/JaxbTest.kt
b/sandbox/src/test/kotlin/JaxbTest.kt
index 5de2cec..f4c9f90 100644
--- a/sandbox/src/test/kotlin/JaxbTest.kt
+++ b/sandbox/src/test/kotlin/JaxbTest.kt
@@ -80,11 +80,11 @@ class JaxbTest {
*/
@Test
fun domToJaxb() {
-
val ini =
classLoader.getResource("ebics_ini_request_sample_patched.xml")
- val iniDom = processor.parseStringIntoDom(ini.readText())
+ val iniDom = XML.parseStringIntoDom(ini.readText())
processor.convertDomToJaxb<EbicsUnsecuredRequest>(
EbicsUnsecuredRequest::class.java,
- iniDom!!)
+ iniDom
+ )
}
}
\ No newline at end of file
diff --git a/sandbox/src/test/kotlin/XmlSigTest.kt
b/sandbox/src/test/kotlin/XmlSigTest.kt
new file mode 100644
index 0000000..e80b880
--- /dev/null
+++ b/sandbox/src/test/kotlin/XmlSigTest.kt
@@ -0,0 +1,41 @@
+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
+
+
+class XmlSigTest {
+
+ @Test
+ fun basicSigningTest() {
+ val doc = XML.parseStringIntoDom("""
+ <foo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+ <AuthSignature />
+ <bar authenticate='true'>bla</bar>Hello World
+ <spam>
+ eggs
+
+ ham
+ </spam>
+ </foo>
+ """.trimIndent())
+ val kpg = KeyPairGenerator.getInstance("RSA")
+ kpg.initialize(2048)
+ val pair = kpg.genKeyPair()
+ XML.signEbicsDocument(doc, pair.private)
+ println(XML.convertDomToString(doc))
+ }
+}
\ No newline at end of file
diff --git a/sandbox/src/test/kotlin/XsiTypeAttributeTest.kt
b/sandbox/src/test/kotlin/XsiTypeAttributeTest.kt
index 73c4811..030f9a9 100644
--- a/sandbox/src/test/kotlin/XsiTypeAttributeTest.kt
+++ b/sandbox/src/test/kotlin/XsiTypeAttributeTest.kt
@@ -14,8 +14,8 @@ class XsiTypeAttributeTest {
val processor = XML()
val classLoader = ClassLoader.getSystemClassLoader()
val ini = classLoader.getResource("ebics_ini_request_sample.xml")
- val iniDom = processor.parseStringIntoDom(ini.readText())
- val x: Element = iniDom?.getElementsByTagName("OrderDetails")?.item(0)
as Element
+ val iniDom = XML.parseStringIntoDom(ini.readText())
+ val x: Element = iniDom.getElementsByTagName("OrderDetails")?.item(0)
as Element
x.setAttributeNS(
"http://www.w3.org/2001/XMLSchema-instance",
--
To stop receiving notification emails like this one, please contact
address@hidden.