main
1# frozen_string_literal: true
2
3module Saml
4 module Kit
5 # This class is responsible for
6 # validating an xml digital signature
7 # in an xml document.
8 class Signature
9 include Validatable
10 include Translatable
11
12 validate :validate_signature
13 validate :validate_certificate
14
15 attr_reader :name
16
17 def initialize(node)
18 @name = 'Signature'
19 @node = node
20 end
21
22 # Returns the embedded X509 Certificate
23 def certificate
24 xpath = './ds:KeyInfo/ds:X509Data/ds:X509Certificate'
25 value = at_xpath(xpath).try(:text)
26 return if value.nil?
27
28 ::Xml::Kit::Certificate.new(value, use: :signing)
29 end
30
31 # Returns true when the fingerprint of the certificate matches one of
32 # the certificates registered in the metadata.
33 def trusted?(metadata)
34 return false if metadata.nil?
35
36 metadata.matches?(certificate.fingerprint, use: :signing).present?
37 end
38
39 def digest_value
40 at_xpath('./ds:SignedInfo/ds:Reference/ds:DigestValue').try(:text)
41 end
42
43 def expected_digest_value
44 digests = dsignature.references.map do |xxx|
45 Base64.encode64(xxx.calculate_digest_value).chomp
46 end
47 digests.count > 1 ? digests : digests[0]
48 end
49
50 def digest_method
51 xpath = './ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm'
52 at_xpath(xpath).try(:value)
53 end
54
55 def signature_value
56 at_xpath('./ds:SignatureValue').try(:text)
57 end
58
59 def signature_method
60 at_xpath('./ds:SignedInfo/ds:SignatureMethod/@Algorithm').try(:value)
61 end
62
63 def canonicalization_method
64 xpath = './ds:SignedInfo/ds:CanonicalizationMethod/@Algorithm'
65 at_xpath(xpath).try(:value)
66 end
67
68 def transforms
69 xpath = xpath_for([
70 '.',
71 'ds:SignedInfo',
72 'ds:Reference',
73 'ds:Transforms',
74 'ds:Transform',
75 '@Algorithm',
76 ])
77 node.search(xpath, Saml::Kit::Document::NAMESPACES).try(:map, &:value)
78 end
79
80 # Returns the XML Hash.
81 def to_h
82 @to_h ||= present? ? Hash.from_xml(to_xml)['Signature'] : {}
83 end
84
85 def present?
86 node.present?
87 end
88
89 def to_xml(pretty: nil)
90 pretty ? node.to_xml(indent: 2) : to_s
91 end
92
93 def to_s
94 node.to_s
95 end
96
97 private
98
99 attr_reader :node
100
101 def validate_signature
102 return errors.add(:base, error_message(:empty)) if certificate.nil?
103 return if dsignature.valid?(certificate.x509)
104
105 dsignature.errors.each do |attribute|
106 errors.add(attribute, error_message(attribute))
107 end
108 rescue StandardError => error
109 errors.add(:base, error.message)
110 end
111
112 def validate_certificate(now = Time.now.utc)
113 return unless certificate.present?
114 return if certificate.active?(now)
115
116 message = error_message(
117 :certificate,
118 not_before: certificate.not_before,
119 not_after: certificate.not_after
120 )
121 errors.add(:certificate, message)
122 end
123
124 def at_xpath(xpath)
125 return nil unless node
126
127 node.at_xpath(xpath, Saml::Kit::Document::NAMESPACES)
128 end
129
130 def dsignature
131 @dsignature ||= Xmldsig::Signature.new(node, 'ID=$uri or @Id')
132 end
133
134 def xpath_for(segments)
135 segments.join('/')
136 end
137 end
138 end
139end