commit c9634a6d5735d64d59b39e534476c8e01002d6e7
parent 278b815ce53d111d64c9e127a743098cee0b7c93
Author: lash <dev@holbrook.no>
Date: Thu, 22 Jun 2023 23:45:35 +0100
Add xml sig parse module
Diffstat:
4 files changed, 270 insertions(+), 0 deletions(-)
diff --git a/funga/xml/__init__.py b/funga/xml/__init__.py
@@ -0,0 +1 @@
+from .xml import *
diff --git a/funga/xml/xml.py b/funga/xml/xml.py
@@ -0,0 +1,147 @@
+# standard imports
+from enum import Enum
+import logging
+import xml.etree.ElementTree as ET
+from base64 import b64decode
+
+logg = logging.getLogger(__name__)
+
+SignatureAccept = Enum('SignatureAccept', 'CANONICALIZATION SIGNING TRANSFORM DIGEST')
+SignatureVerify = Enum('SignatureVerify', 'SIGNATURE DIGEST PUBLICKEY')
+
+
+def cryptobinary_to_int(v):
+ b = b64decode(v)
+ return int.from_bytes(b, byteorder='big')
+
+
+class SignatureParser:
+
+ namespaces = {
+ '': "http://www.w3.org/2000/09/xmldsig#",
+ 'dsig11': "http://www.w3.org/2009/xmldsig11",
+ }
+
+ def __init__(self):
+ self.__tree = None
+ self.__signature_verifier = None
+ self.__settings = [
+ [],
+ [],
+ [],
+ [],
+ ]
+ self.__verify = [
+ None,
+ None,
+ None,
+ ]
+ self.__r = None
+ self.clear()
+
+
+ def clear(self):
+ self.__r = {
+ 'sig': None,
+ 'pubkey': None,
+ 'prime': None,
+ 'curve_a': None,
+ 'curve_b': None,
+ 'base': None,
+ 'order': None,
+ 'cofactor': None,
+ 'keyname': None,
+ }
+
+
+ def set(self, k, v):
+ if k.__class__.__name__ == 'SignatureAccept':
+ self.__settings[k.value - 1].append(v)
+ elif k.__class__.__name__ == 'SignatureVerify':
+ self.__verify[k.value - 1] = v
+ else:
+ raise ValueError('invalid key: {}'.format(k))
+
+ def get(self, k):
+ if k.__class__.__name__ == 'SignatureAccept':
+ return self.__settings[k.value - 1]
+ elif k.__class__.__name__ == 'SignatureVerify':
+ return self.__verify[k.value - 1]
+ raise ValueError('invalid key: {}'.format(k))
+
+
+ def process_file(self, fp):
+ self.__tree = ET.parse(fp)
+ self.__root = self.__tree.getroot()
+ self.__verify_canonicalization(self.__root[0][0])
+ self.__verify_sign_method(self.__root[0][1])
+ self.__verify_signature(self.__root[1])
+ r = self.__root.find('./SignedInfo/Reference', namespaces=self.namespaces)
+ if r != None:
+ self.__opt_verify_signedinfo(r)
+ r = self.__root.find('./KeyInfo', namespaces=self.namespaces)
+ if r != None:
+ self.__opt_verify_keyinfo(r)
+ logg.debug('result {}'.format(self.__r))
+
+
+ def __verify_canonicalization(self, el):
+ assert el.attrib['Algorithm'] in self.get(SignatureAccept.CANONICALIZATION)
+
+
+ def __verify_sign_method(self, el):
+ assert el.attrib['Algorithm'] in self.get(SignatureAccept.SIGNING)
+
+
+ def __verify_signature(self, el):
+ b = b64decode(el.text)
+ m = self.get(SignatureVerify.SIGNATURE)
+ if m != None:
+ assert m(b)
+ self.__r['sig'] = b
+
+ def __opt_verify_signedinfo(self, el):
+ r = el.find('./DigestMethod', namespaces=self.namespaces)
+ if r != None:
+ assert r.attrib['Algorithm'] in self.get(SignatureAccept.DIGEST)
+ r = el.find('./DigestValue', namespaces=self.namespaces)
+ if r != None:
+ b = b64decode(r.text)
+ m = self.get(SignatureVerify.DIGEST)
+ if m != None:
+ assert m(b)
+ self.__r['digest'] = b
+
+
+ def __opt_verify_keyinfo(self, el):
+ r = el.find('./dsig11:ECKeyValue', namespaces=self.namespaces)
+ if r != None:
+ assert self.__opt_verify_keyinfo_eckey(r)
+
+ r = el.find('./KeyName', namespaces=self.namespaces)
+ if r != None:
+ self.__r['keyname'] = r.text
+
+
+ def __opt_verify_keyinfo_eckey(self, el):
+ r = el.find('./dsig11:PublicKey', namespaces=self.namespaces)
+ if r != None:
+ b = b64decode(r.text)
+ m = self.get(SignatureVerify.PUBLICKEY)
+ if m != None:
+ assert m(b)
+ self.__r['pubkey'] = b
+ r = el.find('./dsig11:ECParameters/dsig11:FieldID/dsig11:Prime/dsig11:P', namespaces=self.namespaces)
+ self.__r['prime'] = cryptobinary_to_int(r.text)
+ r = el.find('./dsig11:ECParameters/dsig11:Curve/dsig11:A', namespaces=self.namespaces)
+ self.__r['curve_a'] = cryptobinary_to_int(r.text)
+ r = el.find('./dsig11:ECParameters/dsig11:Curve/dsig11:B', namespaces=self.namespaces)
+ self.__r['curve_b'] = cryptobinary_to_int(r.text)
+ r = el.find('./dsig11:ECParameters/dsig11:Base', namespaces=self.namespaces)
+ self.__r['base'] = cryptobinary_to_int(r.text)
+ r = el.find('./dsig11:ECParameters/dsig11:Order', namespaces=self.namespaces)
+ self.__r['order'] = cryptobinary_to_int(r.text)
+ r = el.find('./dsig11:ECParameters/dsig11:CoFactor', namespaces=self.namespaces)
+ self.__r['cofactor'] = int(r.text)
+
+ return True
diff --git a/tests/test_xml.py b/tests/test_xml.py
@@ -0,0 +1,75 @@
+# standard imports
+import logging
+import unittest
+import os
+from base64 import b64decode
+
+# local imports
+from funga.xml import SignatureParser
+from funga.xml import SignatureAccept
+from funga.xml import SignatureVerify
+
+logging.basicConfig(level=logging.DEBUG)
+logg = logging.getLogger()
+
+test_dir = os.path.dirname(os.path.realpath(__file__))
+
+
+def verify_fail(v):
+ return False
+
+
+def verify_sig_ok(v):
+ return len(v) == 65
+
+verify_pub_ok = verify_sig_ok
+
+def verify_digest_ok(v):
+ return len(v) == 32
+
+
+class TestXmlSig(unittest.TestCase):
+
+ def setUp(self):
+ self.xml_file = os.path.join(test_dir, 'testdata', 'sign.xml')
+ self.parser = SignatureParser()
+
+
+ def test_base(self):
+ with self.assertRaises(AssertionError):
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureAccept.CANONICALIZATION, 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315')
+ with self.assertRaises(AssertionError):
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureAccept.SIGNING, 'http://tools.ietf.org/html/rfc6931')
+ with self.assertRaises(AssertionError):
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureAccept.DIGEST, 'https://csrc.nist.gov/glossary/term/sha_256')
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureVerify.SIGNATURE, verify_fail)
+ with self.assertRaises(AssertionError):
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureVerify.SIGNATURE, verify_sig_ok)
+ self.parser.process_file(self.xml_file)
+
+ self.parser.set(SignatureVerify.DIGEST, verify_fail)
+ with self.assertRaises(AssertionError):
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureVerify.DIGEST, verify_sig_ok)
+ with self.assertRaises(AssertionError):
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureVerify.DIGEST, verify_digest_ok)
+ self.parser.process_file(self.xml_file)
+
+ self.parser.set(SignatureVerify.PUBLICKEY, verify_fail)
+ with self.assertRaises(AssertionError):
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureVerify.PUBLICKEY, verify_digest_ok)
+ with self.assertRaises(AssertionError):
+ self.parser.process_file(self.xml_file)
+ self.parser.set(SignatureVerify.PUBLICKEY, verify_pub_ok)
+ self.parser.process_file(self.xml_file)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/testdata/sign.xml b/tests/testdata/sign.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<Signature Id="sig-a0f819f9-6813-4f14-a7c8-f33c12f0c846" xmlns="http://www.w3.org/2000/09/xmldsig#">
+ <SignedInfo>
+ <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
+ <SignatureMethod Algorithm="http://tools.ietf.org/html/rfc6931"/>
+ <Reference Type="http://www.w3.org/2000/09/xmldsig#SignatureProperties">
+ <Transforms>
+ <Transform Algorithm="https://eips.ethereum.org/EIPS/eip-191#version-0x45-e" />
+ <Transform Algorithm="https://csrc.nist.gov/glossary/term/sha_256" />
+ </Transforms>
+ <DigestMethod Algorithm="https://csrc.nist.gov/glossary/term/sha_256" />
+ <DigestValue>drLpZxTTtebrHRxQkmVDC5B7RPcrKiKwb81NljcrhWU=</DigestValue>
+ </Reference>
+ </SignedInfo>
+ <SignatureValue>r3d2ftvM30Y4D+1vBq9DyAfeS+3OLtoSmSM0DpV3yX52vFXVhxA6NnBXFn81GuHP1rjepuAoIlfeNZT749h4BwA=</SignatureValue>
+
+ <KeyInfo>
+ <KeyName>eb3907ecad74a0013c259d5874ae7f22dcbcc95c</KeyName>
+ <!-- public key: 049f6bb6a7e3f5b7ee71756a891233d1415658f8712bac740282e083dc9240f5368bdb3b256a5bf40a8f7f9753414cb447ee3f796c5f30f7eb40a7f5018fc7f02e -->
+ <ECKeyValue xmlns="http://www.w3.org/2009/xmldsig11">
+ <PublicKey>BJ9rtqfj9bfucXVqiRIz0UFWWPhxK6x0AoLgg9ySQPU2i9s7JWpb9AqPf5dTQUy0R+4/eWxfMPfrQKf1AY/H8C4=</PublicKey>
+ <ECParameters>
+ <FieldID>
+ <Prime>
+ <P>/////////////////////////////////////v///C8=</P>
+ </Prime>
+ </FieldID>
+ <Curve>
+ <A>AAo=</A>
+ <B>Bwo=</B>
+ </Curve>
+ <Base>BHm+Zn753LusVaBilc6HCwcCm/zbLc4o2VnygVsW+BeYSDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj/sQ1Lg=</Base>
+ <Order>/////////////////////rqu3OavSKA7v9JejNA2QUE=</Order>
+ <CoFactor>1</CoFactor>
+ </ECParameters>
+ </ECKeyValue>
+ </KeyInfo>
+ <Object>
+ <SignatureProperties>
+ <SignatureProperty Id="ts-a0f819f9-6813-4f14-a7c8-f33c12f0c846" Target="sig-a0f819f9-6813-4f14-a7c8-f33c12f0c846">
+ <timestamp xmlns="https://www.epochconverter.com/">
+ <epochSeconds>1686900525</epochSeconds>
+ </timestamp>
+ </SignatureProperty>
+ </SignatureProperties>
+ </Object>
+</Signature>