66 lines
2.7 KiB
Python
66 lines
2.7 KiB
Python
|
import base64
|
||
|
import hashlib
|
||
|
from copy import deepcopy
|
||
|
from lxml import etree
|
||
|
|
||
|
from odoo.exceptions import UserError
|
||
|
|
||
|
NS_MAP = {'ds': "http://www.w3.org/2000/09/xmldsig#"}
|
||
|
|
||
|
|
||
|
def _canonicalize_node(node):
|
||
|
"""
|
||
|
Returns the canonical (C14N 1.0, without comments, non exclusive) representation of node.
|
||
|
Speficied in: https://www.w3.org/TR/2001/REC-xml-c14n-20010315
|
||
|
Required for computing digests and signatures.
|
||
|
Returns an UTF-8 encoded bytes string.
|
||
|
"""
|
||
|
|
||
|
return etree.tostring(node, method="c14n", with_comments=False, exclusive=False)
|
||
|
|
||
|
|
||
|
def _get_uri(uri, reference, base_uri=""):
|
||
|
"""
|
||
|
Returns the content within `reference` that is identified by `uri`.
|
||
|
Canonicalization is used to convert node reference to an octet stream.
|
||
|
- URIs starting with # are same-document references
|
||
|
https://www.w3.org/TR/xmldsig-core/#sec-URI
|
||
|
- Empty URIs point to the whole document tree, without the signature
|
||
|
https://www.w3.org/TR/xmldsig-core/#sec-EnvelopedSignature
|
||
|
Returns an UTF-8 encoded bytes string.
|
||
|
"""
|
||
|
node = deepcopy(reference.getroottree().getroot())
|
||
|
if uri == base_uri:
|
||
|
# Base URI: whole document, without signature (default is empty URI)
|
||
|
for signature in node.xpath('ds:Signature', namespaces=NS_MAP):
|
||
|
if signature.tail:
|
||
|
# move the tail to the previous node or to the parent
|
||
|
if (previous := signature.getprevious()) is not None:
|
||
|
previous.tail = "".join([previous.tail or "", signature.tail or ""])
|
||
|
else:
|
||
|
signature.getparent().text = "".join([signature.getparent().text or "", signature.tail or ""])
|
||
|
node.remove(signature)
|
||
|
return _canonicalize_node(node)
|
||
|
|
||
|
if uri.startswith("#"):
|
||
|
path = "//*[@*[local-name() = '{}' ]=$uri]"
|
||
|
results = node.xpath(path.format("Id"), uri=uri.lstrip("#")) # case-sensitive 'Id'
|
||
|
if len(results) == 1:
|
||
|
return _canonicalize_node(results[0])
|
||
|
if len(results) > 1:
|
||
|
raise UserError(f"Ambiguous reference URI {uri} resolved to {len(results)} nodes")
|
||
|
|
||
|
raise UserError(f'URI {uri} not found')
|
||
|
|
||
|
|
||
|
def _reference_digests(node, base_uri=""):
|
||
|
"""
|
||
|
Processes the references from node and computes their digest values as specified in
|
||
|
https://www.w3.org/TR/xmldsig-core/#sec-DigestMethod
|
||
|
https://www.w3.org/TR/xmldsig-core/#sec-DigestValue
|
||
|
"""
|
||
|
for reference in node.findall("ds:Reference", namespaces=NS_MAP):
|
||
|
ref_node = _get_uri(reference.get("URI", ""), reference, base_uri=base_uri)
|
||
|
lib = hashlib.new("sha256", ref_node)
|
||
|
reference.find("ds:DigestValue", namespaces=NS_MAP).text = base64.b64encode(lib.digest())
|