11from base64 import b64encode
2+ from dataclasses import dataclass
23from typing import List , Optional , Union
34
45from cryptography .hazmat .primitives .asymmetric import ec , utils
3233)
3334
3435
36+ @dataclass
37+ class XMLSignatureReference :
38+ URI : str
39+ """
40+ The reference URI, for example ``#elementId`` to refer to an element whose Id attribute is set to ``elementId``.
41+ """
42+
43+ c14n_method : Optional [CanonicalizationMethod ] = None
44+ """
45+ Use this parameter to set a canonicalization method for the reference value that is distinct from that for the
46+ signature itself.
47+ """
48+
49+ inclusive_ns_prefixes : Optional [List ] = None
50+ """
51+ When using exclusive XML canonicalization, use this parameter to provide a list of XML namespace prefixes whose
52+ declarations should be preserved when canonicalizing the reference value (**InclusiveNamespaces PrefixList**).
53+ """
54+
55+
3556class XMLSigner (XMLSignatureProcessor ):
3657 """
3758 Create a new XML Signature Signer object, which can be used to hold configuration information and sign multiple
@@ -80,13 +101,12 @@ def sign(
80101 key = None ,
81102 passphrase : Optional [bytes ] = None ,
82103 cert = None ,
83- reference_uri : Optional [Union [str , List [str ]]] = None ,
104+ reference_uri : Optional [Union [str , List [str ], List [ XMLSignatureReference ] ]] = None ,
84105 key_name : Optional [str ] = None ,
85106 key_info : Optional [_Element ] = None ,
86107 id_attribute : Optional [str ] = None ,
87108 always_add_key_value : bool = False ,
88- payload_inclusive_ns_prefixes : Optional [List [str ]] = None ,
89- signature_inclusive_ns_prefixes : Optional [List [str ]] = None ,
109+ inclusive_ns_prefixes : Optional [List [str ]] = None ,
90110 signature_properties = None ,
91111 ):
92112 """
@@ -113,7 +133,8 @@ def sign(
113133 :param reference_uri:
114134 Custom reference URI or list of reference URIs to incorporate into the signature. When ``method`` is set to
115135 ``detached`` or ``enveloped``, reference URIs are set to this value and only the referenced elements are
116- signed.
136+ signed. To specify extra options specific to each reference URI, pass a list of one or more
137+ XMLSignatureReference objects.
117138 :param key_name: Add a KeyName element in the KeyInfo element that may be used by the signer to communicate a
118139 key identifier to the recipient. Typically, KeyName contains an identifier related to the key pair used to
119140 sign the message.
@@ -129,13 +150,13 @@ def sign(
129150 document is already encoded in the certificate (which is in X509Data), so the verifier must either ignore
130151 KeyValue or make sure it matches what's in the certificate. This parameter is provided for compatibility
131152 purposes only.
132- :param payload_inclusive_ns_prefixes:
133- Provide a list of XML namespace prefixes whose declarations should be preserved when canonicalizing the
134- content referenced by the signature (**InclusiveNamespaces PrefixList**).
135- :param signature_inclusive_ns_prefixes:
153+ :param inclusive_ns_prefixes:
136154 Provide a list of XML namespace prefixes whose declarations should be preserved when canonicalizing the
137- signature itself (**InclusiveNamespaces PrefixList**).
138- :type signature_inclusive_ns_prefixes: list
155+ signature (**InclusiveNamespaces PrefixList**).
156+
157+ To specify this value separately for reference canonicalizaition, pass a list of one or more
158+ XMLSignatureReference objects as the ``reference_uri`` keyword argument, and set the
159+ ``inclusive_ns_prefixes`` attribute on those objects.
139160 :param signature_properties:
140161 One or more Elements that are to be included in the SignatureProperies section when using the detached
141162 method.
@@ -158,10 +179,7 @@ def sign(
158179 else :
159180 cert_chain = cert
160181
161- if isinstance (reference_uri , (str , bytes )):
162- input_reference_uris = [reference_uri ]
163- else :
164- input_reference_uris = reference_uri # type: ignore
182+ input_references = self ._preprocess_reference_uri (reference_uri )
165183
166184 signing_settings = SigningSettings (
167185 key = None ,
@@ -179,28 +197,27 @@ def sign(
179197 else :
180198 signing_settings .key = key
181199
182- sig_root , doc_root , c14n_inputs , reference_uris = self ._unpack (data , input_reference_uris )
200+ sig_root , doc_root , c14n_inputs , references = self ._unpack (data , input_references )
183201
184202 if self .signature_type == SignatureType .detached and signature_properties is not None :
185- reference_uris .append ("#prop" )
203+ references .append (XMLSignatureReference ( URI = "#prop" ) )
186204 if signature_properties is not None and not isinstance (signature_properties , list ):
187205 signature_properties = [signature_properties ]
188206 signature_properties_el = self ._build_signature_properties (signature_properties )
189207 c14n_inputs .append (signature_properties_el )
190208
191209 signed_info_node , signature_value_node = self ._build_sig (
192210 sig_root ,
193- reference_uris ,
194- c14n_inputs ,
195- sig_insp = signature_inclusive_ns_prefixes ,
196- payload_insp = payload_inclusive_ns_prefixes ,
211+ references = references ,
212+ c14n_inputs = c14n_inputs ,
213+ inclusive_ns_prefixes = inclusive_ns_prefixes ,
197214 )
198215
199216 for signature_annotator in self .signature_annotators :
200217 signature_annotator (sig_root , signing_settings = signing_settings )
201218
202219 signed_info_c14n = self ._c14n (
203- signed_info_node , algorithm = self .c14n_alg , inclusive_ns_prefixes = signature_inclusive_ns_prefixes
220+ signed_info_node , algorithm = self .c14n_alg , inclusive_ns_prefixes = inclusive_ns_prefixes
204221 )
205222 if self .sign_alg .name .startswith ("HMAC_" ):
206223 signer = HMAC (key = key , algorithm = digest_algorithm_implementations [self .sign_alg ]())
@@ -238,6 +255,16 @@ def sign(
238255
239256 return doc_root if self .signature_type == SignatureType .enveloped else sig_root
240257
258+ def _preprocess_reference_uri (self , reference_uris ):
259+ if reference_uris is None :
260+ return None
261+ if isinstance (reference_uris , (str , bytes )):
262+ reference_uris = [reference_uris ]
263+ references = list (
264+ ref if isinstance (ref , XMLSignatureReference ) else XMLSignatureReference (URI = ref ) for ref in reference_uris
265+ )
266+ return references
267+
241268 def _add_key_info (self , sig_root , signing_settings : SigningSettings ):
242269 if self .sign_alg .name .startswith ("HMAC_" ):
243270 return
@@ -261,25 +288,24 @@ def _add_key_info(self, sig_root, signing_settings: SigningSettings):
261288 else :
262289 sig_root .append (signing_settings .key_info )
263290
264- def _get_c14n_inputs_from_reference_uris (self , doc_root , reference_uris ):
265- c14n_inputs , new_reference_uris = [], []
266- for reference_uri in reference_uris :
267- if not reference_uri .startswith ("#" ):
268- reference_uri = "#" + reference_uri
269- c14n_inputs .append (self .get_root (self ._resolve_reference (doc_root , {"URI" : reference_uri })))
270- new_reference_uris .append (reference_uri )
271- return c14n_inputs , new_reference_uris
291+ def _get_c14n_inputs_from_references (self , doc_root , references : List [XMLSignatureReference ]):
292+ c14n_inputs , new_references = [], []
293+ for reference in references :
294+ uri = reference .URI if reference .URI .startswith ("#" ) else "#" + reference .URI
295+ c14n_inputs .append (self .get_root (self ._resolve_reference (doc_root , {"URI" : uri })))
296+ new_references .append (XMLSignatureReference (URI = uri , c14n_method = reference .c14n_method ))
297+ return c14n_inputs , new_references
272298
273- def _unpack (self , data , reference_uris ):
299+ def _unpack (self , data , references : List [ XMLSignatureReference ] ):
274300 sig_root = Element (ds_tag ("Signature" ), nsmap = self .namespaces )
275301 if self .signature_type == SignatureType .enveloped :
276302 if isinstance (data , (str , bytes )):
277303 raise InvalidInput ("When using enveloped signature, **data** must be an XML element" )
278304 doc_root = self .get_root (data )
279305 c14n_inputs = [self .get_root (data )]
280- if reference_uris is not None :
306+ if references is not None :
281307 # Only sign the referenced element(s)
282- c14n_inputs , reference_uris = self ._get_c14n_inputs_from_reference_uris (doc_root , reference_uris )
308+ c14n_inputs , references = self ._get_c14n_inputs_from_references (doc_root , references )
283309
284310 signature_placeholders = self ._findall (doc_root , "Signature[@Id='placeholder']" , anywhere = True )
285311 if len (signature_placeholders ) == 0 :
@@ -295,19 +321,21 @@ def _unpack(self, data, reference_uris):
295321 else :
296322 raise InvalidInput ("Enveloped signature input contains more than one placeholder" )
297323
298- if reference_uris is None :
324+ if references is None :
299325 # Set default reference URIs based on signed data ID attribute values
300- reference_uris = []
326+ references = []
301327 for c14n_input in c14n_inputs :
302328 payload_id = c14n_input .get ("Id" , c14n_input .get ("ID" ))
303- reference_uris .append ("#{}" .format (payload_id ) if payload_id is not None else "" )
329+ uri = "#{}" .format (payload_id ) if payload_id is not None else ""
330+ references .append (XMLSignatureReference (URI = uri ))
304331 elif self .signature_type == SignatureType .detached :
305332 doc_root = self .get_root (data )
306- if reference_uris is None :
307- reference_uris = ["#{}" .format (data .get ("Id" , data .get ("ID" , "object" )))]
333+ if references is None :
334+ uri = "#{}" .format (data .get ("Id" , data .get ("ID" , "object" )))
335+ references = [XMLSignatureReference (URI = uri )]
308336 c14n_inputs = [self .get_root (data )]
309337 try :
310- c14n_inputs , reference_uris = self ._get_c14n_inputs_from_reference_uris (doc_root , reference_uris )
338+ c14n_inputs , references = self ._get_c14n_inputs_from_references (doc_root , references )
311339 except InvalidInput : # Dummy reference URI
312340 c14n_inputs = [self .get_root (data )]
313341 elif self .signature_type == SignatureType .enveloping :
@@ -317,30 +345,38 @@ def _unpack(self, data, reference_uris):
317345 c14n_inputs [0 ].text = data
318346 else :
319347 c14n_inputs [0 ].append (self .get_root (data ))
320- reference_uris = ["#object" ]
321- return sig_root , doc_root , c14n_inputs , reference_uris
348+ references = [XMLSignatureReference ( URI = "#object" ) ]
349+ return sig_root , doc_root , c14n_inputs , references
322350
323- def _build_sig (self , sig_root , reference_uris , c14n_inputs , sig_insp , payload_insp ):
351+ def _build_sig (self , sig_root , references , c14n_inputs , inclusive_ns_prefixes ):
324352 signed_info = SubElement (sig_root , ds_tag ("SignedInfo" ), nsmap = self .namespaces )
325353 sig_c14n_method = SubElement (signed_info , ds_tag ("CanonicalizationMethod" ), Algorithm = self .c14n_alg .value )
326- if sig_insp :
327- SubElement (sig_c14n_method , ec_tag ("InclusiveNamespaces" ), PrefixList = " " .join (sig_insp ))
354+ if inclusive_ns_prefixes :
355+ SubElement (sig_c14n_method , ec_tag ("InclusiveNamespaces" ), PrefixList = " " .join (inclusive_ns_prefixes ))
328356
329357 SubElement (signed_info , ds_tag ("SignatureMethod" ), Algorithm = self .sign_alg .value )
330- for i , reference_uri in enumerate (reference_uris ):
331- reference = SubElement (signed_info , ds_tag ("Reference" ), URI = reference_uri )
332- transforms = SubElement (reference , ds_tag ("Transforms" ))
358+ for i , reference in enumerate (references ):
359+ if reference .c14n_method is None :
360+ reference .c14n_method = self .c14n_alg
361+ if reference .inclusive_ns_prefixes is None :
362+ reference .inclusive_ns_prefixes = inclusive_ns_prefixes
363+ reference_node = SubElement (signed_info , ds_tag ("Reference" ), URI = reference .URI )
364+ transforms = SubElement (reference_node , ds_tag ("Transforms" ))
333365 if self .signature_type == SignatureType .enveloped :
334366 SubElement (transforms , ds_tag ("Transform" ), Algorithm = namespaces .ds + "enveloped-signature" )
335- SubElement (transforms , ds_tag ("Transform" ), Algorithm = self . c14n_alg .value )
367+ SubElement (transforms , ds_tag ("Transform" ), Algorithm = reference . c14n_method .value )
336368 else :
337- c14n_xform = SubElement (transforms , ds_tag ("Transform" ), Algorithm = self .c14n_alg .value )
338- if payload_insp :
339- SubElement (c14n_xform , ec_tag ("InclusiveNamespaces" ), PrefixList = " " .join (payload_insp ))
340-
341- SubElement (reference , ds_tag ("DigestMethod" ), Algorithm = self .digest_alg .value )
342- digest_value = SubElement (reference , ds_tag ("DigestValue" ))
343- payload_c14n = self ._c14n (c14n_inputs [i ], algorithm = self .c14n_alg , inclusive_ns_prefixes = payload_insp )
369+ c14n_xform = SubElement (transforms , ds_tag ("Transform" ), Algorithm = reference .c14n_method .value )
370+ if reference .inclusive_ns_prefixes :
371+ SubElement (
372+ c14n_xform , ec_tag ("InclusiveNamespaces" ), PrefixList = " " .join (reference .inclusive_ns_prefixes )
373+ )
374+
375+ SubElement (reference_node , ds_tag ("DigestMethod" ), Algorithm = self .digest_alg .value )
376+ digest_value = SubElement (reference_node , ds_tag ("DigestValue" ))
377+ payload_c14n = self ._c14n (
378+ c14n_inputs [i ], algorithm = reference .c14n_method , inclusive_ns_prefixes = reference .inclusive_ns_prefixes
379+ )
344380 digest = self ._get_digest (payload_c14n , algorithm = self .digest_alg )
345381 digest_value .text = b64encode (digest ).decode ()
346382 signature_value = SubElement (sig_root , ds_tag ("SignatureValue" ))
0 commit comments