1
1
from base64 import b64decode
2
2
from dataclasses import dataclass , replace
3
- from typing import Callable , FrozenSet , List , Optional , Union
3
+ from typing import Callable , FrozenSet , List , Optional , Union , Tuple
4
4
from warnings import warn
5
5
6
6
import cryptography .exceptions
7
7
from cryptography import x509
8
8
from cryptography .hazmat .primitives .asymmetric import dsa , ec , rsa , utils
9
9
from cryptography .hazmat .primitives .asymmetric .padding import MGF1 , PSS , AsymmetricPadding , PKCS1v15
10
10
from cryptography .hazmat .primitives .hmac import HMAC
11
- from cryptography .hazmat .primitives .serialization import load_der_public_key
11
+ from cryptography .hazmat .primitives .serialization import load_der_public_key , Encoding , PublicFormat
12
+
12
13
from lxml import etree
13
14
14
15
from .algorithms import (
27
28
bits_to_bytes_unit ,
28
29
bytes_to_long ,
29
30
ds_tag ,
30
- ensure_bytes ,
31
31
namespaces ,
32
32
)
33
33
@@ -42,6 +42,11 @@ class SignatureConfiguration:
42
42
"""
43
43
If ``True``, a valid X.509 certificate-based signature with an established chain of trust is required to
44
44
pass validation. If ``False``, other types of valid signatures (e.g. HMAC or RSA public key) are accepted.
45
+
46
+ Ensure you establish trust: to specify an HMAC shared secret that will be required to be used by the signature, use
47
+ the ``hmac_key`` parameter. If ``require_x509`` is ``False`` and no ``hmac_key`` is set, any valid XML signature
48
+ satisfying the other SignatureConfiguration parameters will be accepted, regardless of the key used. You must then
49
+ establish trust by examining the ``VerifyResult`` contents, in particular asserting on the value of ``signature_key``.
45
50
"""
46
51
47
52
location : str = ".//"
@@ -62,13 +67,15 @@ class SignatureConfiguration:
62
67
signature_methods : FrozenSet [SignatureMethod ] = frozenset (sm for sm in SignatureMethod if "SHA1" not in sm .name )
63
68
"""
64
69
Set of acceptable signature methods (signature algorithms). Any signature generated using an algorithm not listed
65
- here will fail verification.
70
+ here will fail verification. It is recommended that you restrict this set to only methods expected to be used in
71
+ signatures handled by your application.
66
72
"""
67
73
68
74
digest_algorithms : FrozenSet [DigestAlgorithm ] = frozenset (da for da in DigestAlgorithm if "SHA1" not in da .name )
69
75
"""
70
76
Set of acceptable digest algorithms. Any signature or reference transform generated using an algorithm not listed
71
- here will cause verification to fail.
77
+ here will cause verification to fail. It is recommended that you restrict this set to only methods expected to be
78
+ used in signatures handled by your application.
72
79
"""
73
80
74
81
ignore_ambiguous_key_info : bool = False
@@ -86,7 +93,8 @@ class SignatureConfiguration:
86
93
class VerifyResult :
87
94
"""
88
95
This is a dataclass representing structured data returned by :func:`signxml.XMLVerifier.verify`. The results of a
89
- verification contain the signed bytes, the parsed signed XML, and the parsed signature XML. Example usage:
96
+ verification contain the signed bytes, the parsed signed XML, the parsed signature XML, and the key that was used
97
+ to verify the signature. Example usage:
90
98
91
99
verified_data = signxml.XMLVerifier().verify(input_data).signed_xml
92
100
"""
@@ -100,6 +108,9 @@ class VerifyResult:
100
108
signature_xml : etree ._Element
101
109
"The signature element parsed as XML"
102
110
111
+ signature_key : bytes
112
+ "The cryptographic key that was used to verify the signature, in PEM format (for asymmetric keys) or raw secret bytes (for HMAC keys)"
113
+
103
114
104
115
class XMLVerifier (XMLSignatureProcessor ):
105
116
"""
@@ -123,7 +134,7 @@ def _verify_signature_with_pubkey(
123
134
key_value : Optional [etree ._Element ] = None ,
124
135
der_encoded_key_value : Optional [etree ._Element ] = None ,
125
136
signing_certificate : Optional [x509 .Certificate ] = None ,
126
- ) -> bytes :
137
+ ) -> Tuple [ bytes , bytes | dsa . DSAPublicKey | rsa . RSAPublicKey | ec . EllipticCurvePublicKey ] :
127
138
if der_encoded_key_value is not None :
128
139
assert der_encoded_key_value .text is not None
129
140
key = load_der_public_key (b64decode (der_encoded_key_value .text ))
@@ -177,7 +188,7 @@ def _verify_signature_with_pubkey(
177
188
key .verify (raw_signature , data = signed_info_c14n , padding = padding , algorithm = digest_alg_impl )
178
189
else :
179
190
raise InvalidInput (f"Unsupported signature algorithm { signature_alg } " )
180
- return signed_info_c14n
191
+ return signed_info_c14n , key
181
192
182
193
def _encode_dss_signature (self , raw_signature : bytes , key_size_bits : int ) -> bytes :
183
194
want_raw_signature_len = bits_to_bytes_unit (key_size_bits ) * 2
@@ -279,7 +290,7 @@ def verify(
279
290
cert_subject_name : Optional [str ] = None ,
280
291
cert_resolver : Optional [Callable ] = None ,
281
292
ca_pem_file : Optional [Union [str , bytes ]] = None ,
282
- hmac_key : Optional [str ] = None ,
293
+ hmac_key : Optional [bytes ] = None ,
283
294
validate_schema : bool = True ,
284
295
parser = None ,
285
296
uri_resolver : Optional [Callable ] = None ,
@@ -366,6 +377,15 @@ def verify(
366
377
:raises: :class:`signxml.exceptions.InvalidSignature`
367
378
"""
368
379
self .hmac_key = hmac_key
380
+ if hmac_key is not None :
381
+ if expect_config .signature_methods == SignatureConfiguration ().signature_methods : # default value
382
+ expect_config = replace (
383
+ expect_config ,
384
+ signature_methods = frozenset (sm for sm in SignatureMethod if sm .name .startswith ("HMAC" )),
385
+ )
386
+ elif any (not sm .name .startswith ("HMAC" ) for sm in expect_config .signature_methods ):
387
+ raise InvalidInput ("When hmac_key is set, all expected signature methods must use HMAC" )
388
+
369
389
self .config = expect_config
370
390
if deprecated_kwargs :
371
391
self .config = replace (expect_config , ** deprecated_kwargs )
@@ -452,7 +472,7 @@ def verify(
452
472
raise InvalidSignature ("Certificate subject common name mismatch" )
453
473
454
474
try :
455
- verified_signed_info_c14n = self ._verify_signature_with_pubkey (
475
+ verified_signed_info_c14n , key_used = self ._verify_signature_with_pubkey (
456
476
signed_info_c14n = signed_info_c14n ,
457
477
raw_signature = raw_signature ,
458
478
signing_certificate = signing_cert ,
@@ -471,18 +491,19 @@ def verify(
471
491
if self .hmac_key is None :
472
492
raise InvalidInput ('Parameter "hmac_key" is required when verifying a HMAC signature' )
473
493
474
- signer = HMAC (key = ensure_bytes ( self .hmac_key ) , algorithm = digest_algorithm_implementations [signature_alg ]())
494
+ signer = HMAC (key = self .hmac_key , algorithm = digest_algorithm_implementations [signature_alg ]())
475
495
signer .update (signed_info_c14n )
476
496
try :
477
497
signer .verify (raw_signature )
478
498
verified_signed_info_c14n = signed_info_c14n
479
499
except cryptography .exceptions .InvalidSignature :
480
500
raise InvalidSignature ("Signature mismatch (HMAC)" )
501
+ key_used = self .hmac_key
481
502
else :
482
503
if key_value is None and der_encoded_key_value is None :
483
504
raise InvalidInput ("Expected to find either KeyValue or X509Data XML element in KeyInfo" )
484
505
485
- verified_signed_info_c14n = self ._verify_signature_with_pubkey (
506
+ verified_signed_info_c14n , key_used = self ._verify_signature_with_pubkey (
486
507
signed_info_c14n = signed_info_c14n ,
487
508
raw_signature = raw_signature ,
488
509
key_value = key_value ,
@@ -493,15 +514,17 @@ def verify(
493
514
verified_signed_info = self ._fromstring (verified_signed_info_c14n )
494
515
verify_results : List [VerifyResult ] = []
495
516
for idx , reference in enumerate (self ._findall (verified_signed_info , "Reference" )):
496
- verify_results .append (self ._verify_reference (reference , idx , root , uri_resolver , c14n_algorithm , signature ))
517
+ verify_results .append (
518
+ self ._verify_reference (reference , idx , root , uri_resolver , c14n_algorithm , signature , key_used )
519
+ )
497
520
498
521
if type (self .config .expect_references ) is int and len (verify_results ) != self .config .expect_references :
499
522
msg = "Expected to find {} references, but found {}"
500
523
raise InvalidSignature (msg .format (self .config .expect_references , len (verify_results )))
501
524
502
525
return verify_results if self .config .expect_references > 1 else verify_results [0 ]
503
526
504
- def _verify_reference (self , reference , index , root , uri_resolver , c14n_algorithm , signature ):
527
+ def _verify_reference (self , reference , index , root , uri_resolver , c14n_algorithm , signature , signature_key_used ):
505
528
copied_root = self ._fromstring (self ._tostring (root ))
506
529
copied_signature_ref = self ._get_signature (copied_root )
507
530
transforms = self ._find (reference , "Transforms" , require = False )
@@ -522,7 +545,12 @@ def _verify_reference(self, reference, index, root, uri_resolver, c14n_algorithm
522
545
payload_c14n_xml = self ._fromstring (payload_c14n )
523
546
except etree .XMLSyntaxError :
524
547
payload_c14n_xml = None
525
- return VerifyResult (payload_c14n , payload_c14n_xml , signature )
548
+
549
+ if isinstance (signature_key_used , bytes ):
550
+ signature_key = signature_key_used
551
+ else :
552
+ signature_key = signature_key_used .public_bytes (Encoding .PEM , PublicFormat .SubjectPublicKeyInfo )
553
+ return VerifyResult (payload_c14n , payload_c14n_xml , signature , signature_key = signature_key )
526
554
527
555
def validate_schema (self , signature ):
528
556
last_exception = None
0 commit comments