main
1# frozen_string_literal: true
2
3module Xml
4 module Kit
5 # {include:file:spec/xml/kit/document_spec.rb}
6 class Document
7 include ActiveModel::Validations
8 NAMESPACES = { "ds": ::Xml::Kit::Namespaces::XMLDSIG }.freeze
9
10 validate :validate_signatures
11 validate :validate_certificates
12
13 def initialize(raw_xml, namespaces: NAMESPACES)
14 @raw_xml = raw_xml
15 @namespaces = namespaces
16 @document = ::Nokogiri::XML(raw_xml)
17 end
18
19 # Returns the first XML node found by searching the document with the provided XPath.
20 #
21 # @param xpath [String] the XPath to use to search the document
22 def find_by(xpath)
23 document.at_xpath(xpath, namespaces)
24 end
25
26 # Returns all XML nodes found by searching the document with the provided XPath.
27 #
28 # @param xpath [String] the XPath to use to search the document
29 def find_all(xpath)
30 document.search(xpath, namespaces)
31 end
32
33 # Return the XML document as a [String].
34 #
35 # @param pretty [Boolean] return the XML string in a human readable format if true.
36 def to_xml(pretty: true)
37 pretty ? document.to_xml(indent: 2) : raw_xml
38 end
39
40 private
41
42 attr_reader :raw_xml, :document, :namespaces
43
44 def validate_signatures
45 invalid_signatures.flat_map(&:errors).uniq.each do |error|
46 errors.add(error, 'is invalid')
47 end
48 end
49
50 def invalid_signatures(id_attr: 'ID=$uri or @Id')
51 Xmldsig::SignedDocument
52 .new(document, id_attr: id_attr)
53 .signatures.find_all do |signature|
54 x509_certificates.all? do |certificate|
55 !signature.valid?(certificate)
56 end
57 end
58 end
59
60 def validate_certificates(now = Time.current)
61 return if find_by('//ds:Signature').nil?
62
63 x509_certificates.each do |certificate|
64 errors.add(:certificate, "Not valid before #{certificate.not_before}") if now < certificate.not_before
65
66 errors.add(:certificate, "Not valid after #{certificate.not_after}") if now > certificate.not_after
67 end
68 end
69
70 def x509_certificates
71 find_all('//ds:KeyInfo/ds:X509Data/ds:X509Certificate').map do |item|
72 Certificate.to_x509(item.text)
73 end
74 end
75 end
76 end
77end