@@ -11,11 +11,17 @@ class VerifySignature
1111
1212 class InvalidDigest < RuntimeError ; end
1313 class InvalidSignedValue < RuntimeError ; end
14+ class MissingDecryptedAttachment < RuntimeError ; end
1415
1516 attr_reader :document
1617
17- def initialize ( xml )
18+ # @param xml [String] The XML document to verify
19+ # @param decrypted_attachments [Hash] A hash of decrypted attachments: { 'id' => 'decrypted_string' }
20+ # For example: the decrypted_attachments of a gzipped xml is the gzipped base64 string, the result of the decryption
21+ # { 'phase4-att-1f34-4d68a..' => 'kZ\xB4\xCD}\xCB..' }
22+ def initialize ( xml , decrypted_attachments : { } )
1823 @document = Nokogiri ::XML ( xml . to_s , &:noblanks )
24+ @decrypted_attachments = decrypted_attachments
1925 end
2026
2127 # Returns XML namespaces that are used internally for document querying.
@@ -51,14 +57,14 @@ def certificate
5157 # Validates document signature, returns +true+ on success, +false+ otherwise.
5258 def valid?
5359 verify
54- rescue InvalidDigest , InvalidSignedValue
60+ rescue InvalidDigest , InvalidSignedValue , MissingDecryptedAttachment
5561 return false
5662 end
5763
5864 # Validates document signature and digests and raises if anything mismatches.
5965 def verify!
6066 verify
61- rescue InvalidDigest , InvalidSignedValue => e
67+ rescue InvalidDigest , InvalidSignedValue , MissingDecryptedAttachment => e
6268 raise InvalidSignature , e . message
6369 end
6470
@@ -78,15 +84,23 @@ def digesters
7884
7985 def verify
8086 document . xpath ( '//wse:Security/ds:Signature/ds:SignedInfo/ds:Reference' , namespaces ) . each do |ref |
81- next unless ref . attributes [ 'URI' ] . value . start_with? ( '#' )
82-
8387 digest_algorithm = ref . at_xpath ( '//ds:DigestMethod' , namespaces ) [ 'Algorithm' ]
8488
8589 transform_inclusive_ns = inclusive_namespaces ( ref , './/ds:Transforms/ds:Transform/ec:InclusiveNamespaces' )
8690
87- element_id = ref . attributes [ 'URI' ] . value [ 1 ..-1 ] # strip leading '#'
88- element = document . at_xpath ( %(//*[@wsu:Id="#{ element_id } "]) , namespaces )
89- unless supplied_digest ( element ) == generate_digest ( element , digest_algorithm , transform_inclusive_ns )
91+ ref_uri = ref . attributes [ 'URI' ] . value
92+ if ref_uri . start_with? ( "#" )
93+ element_id = ref_uri . sub ( /^#/ , '' )
94+ element = document . at_xpath ( %(//*[@wsu:Id="#{ element_id } "]) , namespaces )
95+ generated_digest = generate_digest ( element , digest_algorithm , transform_inclusive_ns )
96+ else
97+ element_id = ref_uri . sub ( /^cid:/ , '' )
98+ element = @decrypted_attachments [ element_id ]
99+ raise MissingDecryptedAttachment , "Missing decrypted attachment for #{ element_id } " if element . nil?
100+ generated_digest = digest ( element , digest_algorithm ) . strip
101+ end
102+
103+ unless supplied_digest ( ref ) == generated_digest
90104 raise InvalidDigest , "Invalid Digest for #{ element_id } "
91105 end
92106 end
@@ -118,19 +132,14 @@ def generate_digest(element, algorithm, inclusive_namespaces = nil)
118132 end
119133
120134 def supplied_digest ( element )
121- element = document . at_xpath ( element , namespaces ) if element . is_a? String
122- find_digest_value element . attributes [ 'Id' ] . value
135+ element . at_xpath ( './/ds:DigestValue' , namespaces ) . text
123136 end
124137
125138 def signature_value
126139 element = document . at_xpath ( '//wse:Security/ds:Signature/ds:SignatureValue' , namespaces )
127140 element ? element . text : ""
128141 end
129142
130- def find_digest_value ( id )
131- document . at_xpath ( %(//wse:Security/ds:Signature/ds:SignedInfo/ds:Reference[@URI="##{ id } "]/ds:DigestValue) , namespaces ) . text
132- end
133-
134143 # Calculate digest for string with given algorithm URL and Base64 encodes it.
135144 def digest ( string , algorithm )
136145 Base64 . encode64 digester ( algorithm ) . digest ( string )
0 commit comments