main
1# frozen_string_literal: true
2
3module Saml
4 module Kit
5 # This class represents the main configuration that is use for generating
6 # SAML documents.
7 #
8 # Saml::Kit::Configuration.new do |config|
9 # config.entity_id = "com:saml:kit"
10 # config.signature_method = :SHA256
11 # config.digest_method = :SHA256
12 # config.registry = Saml::Kit::DefaultRegistry.new
13 # config.session_timeout = 30.minutes
14 # config.logger = Rails.logger
15 # end
16 #
17 # To specify global configuration it is best to do this in an initializer
18 # that runs at the start of the program.
19 #
20 # Saml::Kit.configure do |configuration|
21 # configuration.entity_id = "https://www.example.com/saml/metadata"
22 # configuration.generate_key_pair_for(use: :signing)
23 # configuration.add_key_pair(
24 # ENV["X509_CERTIFICATE"],
25 # ENV["PRIVATE_KEY"],
26 # passphrase: ENV['PRIVATE_KEY_PASSPHRASE'],
27 # use: :encryption
28 # )
29 # end
30 class Configuration
31 USES = %i[signing encryption].freeze
32 # The issuer to use in requests or responses from this entity to use.
33 attr_accessor :entity_id
34 # The signature method to use when generating signatures
35 # (See {Saml::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
36 attr_accessor :signature_method
37 # The digest method to use when generating signatures
38 # (See {Saml::Kit::Builders::XmlSignature::DIGEST_METHODS})
39 attr_accessor :digest_method
40 # The metadata registry to use for searching for metadata associated
41 # with an issuer.
42 attr_accessor :registry
43 # The session timeout to use when generating an Assertion.
44 attr_accessor :session_timeout
45 # The logger to write log messages to.
46 attr_accessor :logger
47 # The total allowable clock drift for session timeout validation.
48 attr_accessor :clock_drift
49
50 def initialize
51 @clock_drift = 30.seconds
52 @digest_method = :SHA256
53 @key_pairs = []
54 @logger = Logger.new(STDOUT)
55 @registry = DefaultRegistry.new
56 @session_timeout = 3.hours
57 @signature_method = :SHA256
58 yield self if block_given?
59 end
60
61 # Add a key pair that can be used for either signing or encryption.
62 #
63 # @param certificate [String] the x509 certificate with public key.
64 # @param private_key [String] the plain text private key.
65 # @param passphrase [String] the password to decrypt the private key.
66 # @param use [Symbol] the type of key pair, `:signing` or `:encryption`
67 def add_key_pair(certificate, private_key, passphrase: nil, use: :signing)
68 ensure_proper_use(use)
69 @key_pairs.push(
70 ::Xml::Kit::KeyPair.new(
71 certificate, private_key, passphrase, use.to_sym
72 )
73 )
74 end
75
76 # Generates a unique key pair that can be used for signing or encryption.
77 #
78 # @param use [Symbol] the type of key pair, `:signing` or `:encryption`
79 # @param passphrase [String] the private key passphrase to use.
80 def generate_key_pair_for(use:, passphrase: SecureRandom.uuid)
81 ensure_proper_use(use)
82 certificate, private_key = ::Xml::Kit::SelfSignedCertificate.new.create(
83 passphrase: passphrase
84 )
85 add_key_pair(certificate, private_key, passphrase: passphrase, use: use)
86 end
87
88 # Return each key pair for a specific use.
89 #
90 # @param use [Symbol] the type of key pair to return
91 # `nil`, `:signing` or `:encryption`
92 def key_pairs(use: nil)
93 use.present? ? active_key_pairs.find_all { |xxx| xxx.for?(use) } : active_key_pairs
94 end
95
96 # Return each certificate for a specific use.
97 #
98 # @param use [Symbol] the type of key pair to return
99 # `nil`, `:signing` or `:encryption`
100 def certificates(use: nil)
101 key_pairs(use: use).flat_map(&:certificate)
102 end
103
104 # Return each private for a specific use.
105 #
106 # @param use [Symbol] the type of key pair to return
107 # `nil`, `:signing` or `:encryption`
108 def private_keys(use: nil)
109 key_pairs(use: use).flat_map(&:private_key)
110 end
111
112 # Returns true if there is at least one signing certificate registered.
113 def sign?
114 @sign ||= certificates(use: :signing).any?
115 end
116
117 private
118
119 def ensure_proper_use(use)
120 return if USES.include?(use)
121
122 error_message = 'Use must be either :signing or :encryption'
123 raise ArgumentError, error_message
124 end
125
126 def active_key_pairs
127 @key_pairs.find_all { |x| active?(x) }.sort_by { |x| x.certificate.not_after }.reverse
128 end
129
130 def active?(key_pair)
131 key_pair.certificate.active?
132 rescue OpenSSL::X509::CertificateError => error
133 Saml::Kit.logger.error(error)
134 false
135 end
136 end
137 end
138end