11from base64 import b64decode
22from dataclasses import dataclass , replace
3- from typing import Callable , FrozenSet , List , Optional , Union
3+ from typing import Callable , FrozenSet , List , Optional , Union , Tuple
44from warnings import warn
55
66import cryptography .exceptions
77from cryptography import x509
88from cryptography .hazmat .primitives .asymmetric import dsa , ec , rsa , utils
99from cryptography .hazmat .primitives .asymmetric .padding import MGF1 , PSS , AsymmetricPadding , PKCS1v15
1010from 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+
1213from lxml import etree
1314
1415from .algorithms import (
2728 bits_to_bytes_unit ,
2829 bytes_to_long ,
2930 ds_tag ,
30- ensure_bytes ,
3131 namespaces ,
3232)
3333
@@ -42,6 +42,11 @@ class SignatureConfiguration:
4242 """
4343 If ``True``, a valid X.509 certificate-based signature with an established chain of trust is required to
4444 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``.
4550 """
4651
4752 location : str = ".//"
@@ -62,13 +67,15 @@ class SignatureConfiguration:
6267 signature_methods : FrozenSet [SignatureMethod ] = frozenset (sm for sm in SignatureMethod if "SHA1" not in sm .name )
6368 """
6469 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.
6672 """
6773
6874 digest_algorithms : FrozenSet [DigestAlgorithm ] = frozenset (da for da in DigestAlgorithm if "SHA1" not in da .name )
6975 """
7076 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.
7279 """
7380
7481 ignore_ambiguous_key_info : bool = False
@@ -86,7 +93,8 @@ class SignatureConfiguration:
8693class VerifyResult :
8794 """
8895 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:
9098
9199 verified_data = signxml.XMLVerifier().verify(input_data).signed_xml
92100 """
@@ -100,6 +108,9 @@ class VerifyResult:
100108 signature_xml : etree ._Element
101109 "The signature element parsed as XML"
102110
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+
103114
104115class XMLVerifier (XMLSignatureProcessor ):
105116 """
@@ -123,7 +134,7 @@ def _verify_signature_with_pubkey(
123134 key_value : Optional [etree ._Element ] = None ,
124135 der_encoded_key_value : Optional [etree ._Element ] = None ,
125136 signing_certificate : Optional [x509 .Certificate ] = None ,
126- ) -> bytes :
137+ ) -> Tuple [ bytes , bytes | dsa . DSAPublicKey | rsa . RSAPublicKey | ec . EllipticCurvePublicKey ] :
127138 if der_encoded_key_value is not None :
128139 assert der_encoded_key_value .text is not None
129140 key = load_der_public_key (b64decode (der_encoded_key_value .text ))
@@ -177,7 +188,7 @@ def _verify_signature_with_pubkey(
177188 key .verify (raw_signature , data = signed_info_c14n , padding = padding , algorithm = digest_alg_impl )
178189 else :
179190 raise InvalidInput (f"Unsupported signature algorithm { signature_alg } " )
180- return signed_info_c14n
191+ return signed_info_c14n , key
181192
182193 def _encode_dss_signature (self , raw_signature : bytes , key_size_bits : int ) -> bytes :
183194 want_raw_signature_len = bits_to_bytes_unit (key_size_bits ) * 2
@@ -279,7 +290,7 @@ def verify(
279290 cert_subject_name : Optional [str ] = None ,
280291 cert_resolver : Optional [Callable ] = None ,
281292 ca_pem_file : Optional [Union [str , bytes ]] = None ,
282- hmac_key : Optional [str ] = None ,
293+ hmac_key : Optional [bytes ] = None ,
283294 validate_schema : bool = True ,
284295 parser = None ,
285296 uri_resolver : Optional [Callable ] = None ,
@@ -366,6 +377,15 @@ def verify(
366377 :raises: :class:`signxml.exceptions.InvalidSignature`
367378 """
368379 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+
369389 self .config = expect_config
370390 if deprecated_kwargs :
371391 self .config = replace (expect_config , ** deprecated_kwargs )
@@ -452,7 +472,7 @@ def verify(
452472 raise InvalidSignature ("Certificate subject common name mismatch" )
453473
454474 try :
455- verified_signed_info_c14n = self ._verify_signature_with_pubkey (
475+ verified_signed_info_c14n , key_used = self ._verify_signature_with_pubkey (
456476 signed_info_c14n = signed_info_c14n ,
457477 raw_signature = raw_signature ,
458478 signing_certificate = signing_cert ,
@@ -471,18 +491,19 @@ def verify(
471491 if self .hmac_key is None :
472492 raise InvalidInput ('Parameter "hmac_key" is required when verifying a HMAC signature' )
473493
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 ]())
475495 signer .update (signed_info_c14n )
476496 try :
477497 signer .verify (raw_signature )
478498 verified_signed_info_c14n = signed_info_c14n
479499 except cryptography .exceptions .InvalidSignature :
480500 raise InvalidSignature ("Signature mismatch (HMAC)" )
501+ key_used = self .hmac_key
481502 else :
482503 if key_value is None and der_encoded_key_value is None :
483504 raise InvalidInput ("Expected to find either KeyValue or X509Data XML element in KeyInfo" )
484505
485- verified_signed_info_c14n = self ._verify_signature_with_pubkey (
506+ verified_signed_info_c14n , key_used = self ._verify_signature_with_pubkey (
486507 signed_info_c14n = signed_info_c14n ,
487508 raw_signature = raw_signature ,
488509 key_value = key_value ,
@@ -493,15 +514,17 @@ def verify(
493514 verified_signed_info = self ._fromstring (verified_signed_info_c14n )
494515 verify_results : List [VerifyResult ] = []
495516 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+ )
497520
498521 if type (self .config .expect_references ) is int and len (verify_results ) != self .config .expect_references :
499522 msg = "Expected to find {} references, but found {}"
500523 raise InvalidSignature (msg .format (self .config .expect_references , len (verify_results )))
501524
502525 return verify_results if self .config .expect_references > 1 else verify_results [0 ]
503526
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 ):
505528 copied_root = self ._fromstring (self ._tostring (root ))
506529 copied_signature_ref = self ._get_signature (copied_root )
507530 transforms = self ._find (reference , "Transforms" , require = False )
@@ -522,7 +545,12 @@ def _verify_reference(self, reference, index, root, uri_resolver, c14n_algorithm
522545 payload_c14n_xml = self ._fromstring (payload_c14n )
523546 except etree .XMLSyntaxError :
524547 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 )
526554
527555 def validate_schema (self , signature ):
528556 last_exception = None
0 commit comments