funga

Signer and keystore daemon and library for cryptocurrency software development
Log | Files | Refs | README | LICENSE

commit c9634a6d5735d64d59b39e534476c8e01002d6e7
parent 278b815ce53d111d64c9e127a743098cee0b7c93
Author: lash <dev@holbrook.no>
Date:   Thu, 22 Jun 2023 23:45:35 +0100

Add xml sig parse module

Diffstat:
Afunga/xml/__init__.py | 1+
Afunga/xml/xml.py | 147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/test_xml.py | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/testdata/sign.xml | 47+++++++++++++++++++++++++++++++++++++++++++++++
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>