1
1
from base64 import b64encode
2
+ from dataclasses import dataclass
2
3
from typing import List , Optional , Union
3
4
4
5
from cryptography .hazmat .primitives .asymmetric import ec , utils
32
33
)
33
34
34
35
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
+
35
56
class XMLSigner (XMLSignatureProcessor ):
36
57
"""
37
58
Create a new XML Signature Signer object, which can be used to hold configuration information and sign multiple
@@ -80,13 +101,12 @@ def sign(
80
101
key = None ,
81
102
passphrase : Optional [bytes ] = None ,
82
103
cert = None ,
83
- reference_uri : Optional [Union [str , List [str ]]] = None ,
104
+ reference_uri : Optional [Union [str , List [str ], List [ XMLSignatureReference ] ]] = None ,
84
105
key_name : Optional [str ] = None ,
85
106
key_info : Optional [_Element ] = None ,
86
107
id_attribute : Optional [str ] = None ,
87
108
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 ,
90
110
signature_properties = None ,
91
111
):
92
112
"""
@@ -113,7 +133,8 @@ def sign(
113
133
:param reference_uri:
114
134
Custom reference URI or list of reference URIs to incorporate into the signature. When ``method`` is set to
115
135
``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.
117
138
:param key_name: Add a KeyName element in the KeyInfo element that may be used by the signer to communicate a
118
139
key identifier to the recipient. Typically, KeyName contains an identifier related to the key pair used to
119
140
sign the message.
@@ -129,13 +150,13 @@ def sign(
129
150
document is already encoded in the certificate (which is in X509Data), so the verifier must either ignore
130
151
KeyValue or make sure it matches what's in the certificate. This parameter is provided for compatibility
131
152
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:
136
154
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.
139
160
:param signature_properties:
140
161
One or more Elements that are to be included in the SignatureProperies section when using the detached
141
162
method.
@@ -158,10 +179,7 @@ def sign(
158
179
else :
159
180
cert_chain = cert
160
181
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 )
165
183
166
184
signing_settings = SigningSettings (
167
185
key = None ,
@@ -179,28 +197,27 @@ def sign(
179
197
else :
180
198
signing_settings .key = key
181
199
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 )
183
201
184
202
if self .signature_type == SignatureType .detached and signature_properties is not None :
185
- reference_uris .append ("#prop" )
203
+ references .append (XMLSignatureReference ( URI = "#prop" ) )
186
204
if signature_properties is not None and not isinstance (signature_properties , list ):
187
205
signature_properties = [signature_properties ]
188
206
signature_properties_el = self ._build_signature_properties (signature_properties )
189
207
c14n_inputs .append (signature_properties_el )
190
208
191
209
signed_info_node , signature_value_node = self ._build_sig (
192
210
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 ,
197
214
)
198
215
199
216
for signature_annotator in self .signature_annotators :
200
217
signature_annotator (sig_root , signing_settings = signing_settings )
201
218
202
219
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
204
221
)
205
222
if self .sign_alg .name .startswith ("HMAC_" ):
206
223
signer = HMAC (key = key , algorithm = digest_algorithm_implementations [self .sign_alg ]())
@@ -238,6 +255,16 @@ def sign(
238
255
239
256
return doc_root if self .signature_type == SignatureType .enveloped else sig_root
240
257
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
+
241
268
def _add_key_info (self , sig_root , signing_settings : SigningSettings ):
242
269
if self .sign_alg .name .startswith ("HMAC_" ):
243
270
return
@@ -261,25 +288,24 @@ def _add_key_info(self, sig_root, signing_settings: SigningSettings):
261
288
else :
262
289
sig_root .append (signing_settings .key_info )
263
290
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
272
298
273
- def _unpack (self , data , reference_uris ):
299
+ def _unpack (self , data , references : List [ XMLSignatureReference ] ):
274
300
sig_root = Element (ds_tag ("Signature" ), nsmap = self .namespaces )
275
301
if self .signature_type == SignatureType .enveloped :
276
302
if isinstance (data , (str , bytes )):
277
303
raise InvalidInput ("When using enveloped signature, **data** must be an XML element" )
278
304
doc_root = self .get_root (data )
279
305
c14n_inputs = [self .get_root (data )]
280
- if reference_uris is not None :
306
+ if references is not None :
281
307
# 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 )
283
309
284
310
signature_placeholders = self ._findall (doc_root , "Signature[@Id='placeholder']" , anywhere = True )
285
311
if len (signature_placeholders ) == 0 :
@@ -295,19 +321,21 @@ def _unpack(self, data, reference_uris):
295
321
else :
296
322
raise InvalidInput ("Enveloped signature input contains more than one placeholder" )
297
323
298
- if reference_uris is None :
324
+ if references is None :
299
325
# Set default reference URIs based on signed data ID attribute values
300
- reference_uris = []
326
+ references = []
301
327
for c14n_input in c14n_inputs :
302
328
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 ))
304
331
elif self .signature_type == SignatureType .detached :
305
332
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 )]
308
336
c14n_inputs = [self .get_root (data )]
309
337
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 )
311
339
except InvalidInput : # Dummy reference URI
312
340
c14n_inputs = [self .get_root (data )]
313
341
elif self .signature_type == SignatureType .enveloping :
@@ -317,30 +345,38 @@ def _unpack(self, data, reference_uris):
317
345
c14n_inputs [0 ].text = data
318
346
else :
319
347
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
322
350
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 ):
324
352
signed_info = SubElement (sig_root , ds_tag ("SignedInfo" ), nsmap = self .namespaces )
325
353
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 ))
328
356
329
357
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" ))
333
365
if self .signature_type == SignatureType .enveloped :
334
366
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 )
336
368
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
+ )
344
380
digest = self ._get_digest (payload_c14n , algorithm = self .digest_alg )
345
381
digest_value .text = b64encode (digest ).decode ()
346
382
signature_value = SubElement (sig_root , ds_tag ("SignatureValue" ))
0 commit comments