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