main
1# frozen_string_literal: true
2
3module Saml
4 module Kit
5 # This module is responsible for
6 # validating the trustworthiness
7 # of a saml document.
8 module Trustable
9 extend ActiveSupport::Concern
10
11 included do
12 validate :must_have_valid_signature, unless: :signature_verified
13 validate :must_be_registered
14 validate :must_be_signed, if: :signature_required?
15 validate :must_be_trusted
16 end
17
18 # Returns true when this document must carry a signature to be valid.
19 def signature_required?
20 configuration.signature_required && signature_required_by_type?
21 end
22
23 # Returns true when the document has an embedded XML Signature or has
24 # been verified externally.
25 def signed?
26 signature_verified || signature.present?
27 end
28
29 # @!visibility private
30 def signature
31 @signature ||= Signature.new(at_xpath("/samlp:#{name}/ds:Signature"))
32 end
33
34 # Returns true when documents is signed and the signing certificate
35 # belongs to a known service entity.
36 def trusted?
37 return true if signature_verified
38 return false unless signed?
39
40 signature.trusted?(provider)
41 end
42
43 # @!visibility private
44 def provider
45 registry.metadata_for(issuer)
46 end
47
48 # @!visibility private
49 def signature_verified!
50 @signature_verified = true
51 end
52
53 private
54
55 attr_reader :signature_verified
56
57 def must_have_valid_signature
58 return if to_xml.blank?
59 return unless signature.present?
60
61 signature.valid?
62 signature.each_error do |attribute, error|
63 errors.add(attribute, error)
64 end
65 end
66
67 def must_be_registered
68 return unless expected_type?
69 return if provider.present?
70
71 errors.add(:provider, error_message(:unregistered))
72 end
73
74 # Whether a document of this type is worthless unless signed.
75 #
76 # A request may legitimately arrive unsigned, which is what
77 # `WantAuthnRequestsSigned="false"` means, so the default is false. A
78 # document that asserts an identity carries no authenticity guarantee at
79 # all when unsigned, so Response and Assertion override this.
80 def signature_required_by_type?
81 false
82 end
83
84 def must_be_signed
85 return unless expected_type?
86 return if signature_covers_document?
87
88 errors.add(:base, error_message(:unsigned))
89 end
90
91 # Whether a signature exists over the content this document asserts.
92 # Response overrides this, because a signature on either the Response or
93 # the Assertion covers the identity being asserted.
94 def signature_covers_document?
95 signed?
96 end
97
98 # The trust counterpart of `signature_covers_document?`. Kept separate
99 # from the public `trusted?`, which answers the narrower question of
100 # whether *this* element's own signature is trusted.
101 def signature_trusted?
102 trusted?
103 end
104
105 def must_be_trusted
106 return if signature_trusted?
107 return if !signature_required? && provider.present? && !signed?
108
109 errors.add(:fingerprint, error_message(:invalid_fingerprint))
110 end
111 end
112 end
113end