diff --git a/lib/xml_security.rb b/lib/xml_security.rb index 1b1b3228..f731d464 100644 --- a/lib/xml_security.rb +++ b/lib/xml_security.rb @@ -310,17 +310,29 @@ def validate_signature(base64_cert, soft = true) canon_string = noko_signed_info_element.canonicalize(canon_algorithm) noko_sig_element.remove + # get signed info + signed_info_element = REXML::XPath.first( + sig_element, + "./ds:SignedInfo", + { "ds" => DSIG } + ) # get inclusive namespaces inclusive_namespaces = extract_inclusive_namespaces # check digests - ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG}) + ref = REXML::XPath.first(signed_info_element, "./ds:Reference", {"ds"=>DSIG}) - hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id }) + reference_nodes = document.xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id }) + + if reference_nodes.length > 1 # ensures no elements with same ID to prevent signature wrapping attack. + return append_error("Digest Mismatch", soft) + end + + hashed_element = reference_nodes[0] canon_algorithm = canon_algorithm REXML::XPath.first( - ref, - '//ds:CanonicalizationMethod', + signed_info_element, + './ds:CanonicalizationMethod', { "ds" => DSIG } ) @@ -330,13 +342,13 @@ def validate_signature(base64_cert, soft = true) digest_algorithm = algorithm(REXML::XPath.first( ref, - "//ds:DigestMethod", + "./ds:DigestMethod", { "ds" => DSIG } )) hash = digest_algorithm.digest(canon_hashed_element) encoded_digest_value = REXML::XPath.first( ref, - "//ds:DigestValue", + "./ds:DigestValue", { "ds" => DSIG } ) digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value)) @@ -362,7 +374,7 @@ def validate_signature(base64_cert, soft = true) def process_transforms(ref, canon_algorithm) transforms = REXML::XPath.match( ref, - "//ds:Transforms/ds:Transform", + "./ds:Transforms/ds:Transform", { "ds" => DSIG } )