main
  1# frozen_string_literal: true
  2
  3require 'xml/kit/templatable'
  4
  5module Xml
  6  module Kit
  7    # {include:file:spec/xml/kit/certificate_spec.rb}
  8    class Certificate
  9      include Templatable
 10      BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z).freeze
 11      BEGIN_CERT = /-----BEGIN CERTIFICATE-----/.freeze
 12      END_CERT = /-----END CERTIFICATE-----/.freeze
 13      # The use can be `:signing` or `:encryption`. Use `nil` for both.
 14      attr_reader :use
 15
 16      # The raw certificate value. This can be a Base64 encoded PEM or just a PEM format.
 17      attr_reader :value
 18
 19      def initialize(value, use: nil)
 20        @value = value
 21        @use = use.nil? ? use : use.downcase.to_sym
 22      end
 23
 24      # @return [Xml::Kit::Fingerprint] the certificate fingerprint.
 25      def fingerprint
 26        Fingerprint.new(value)
 27      end
 28
 29      # Returns true if this certificate is for the specified use.
 30      #
 31      # @param use [Symbol] `:signing` or `:encryption`.
 32      # @return [Boolean] true or false.
 33      def for?(use)
 34        return true if self.use.nil?
 35
 36        self.use == use.to_sym
 37      end
 38
 39      # Returns true if this certificate is used for encryption.
 40      #
 41      # return [Boolean] true or false.
 42      def encryption?
 43        for?(:encryption)
 44      end
 45
 46      # Returns true if this certificate is used for signing.
 47      #
 48      # return [Boolean] true or false.
 49      def signing?
 50        for?(:signing)
 51      end
 52
 53      # Returns the x509 form.
 54      #
 55      # return [OpenSSL::X509::Certificate] the OpenSSL equivalent.
 56      def x509
 57        @x509 ||= self.class.to_x509(value)
 58      end
 59
 60      # Returns the public key.
 61      #
 62      # @return [OpenSSL::PKey::RSA] the RSA public key.
 63      def public_key
 64        x509.public_key
 65      end
 66
 67      def ==(other)
 68        fingerprint == other.fingerprint
 69      end
 70
 71      def eql?(other)
 72        self == other
 73      end
 74
 75      def hash
 76        value.hash
 77      end
 78
 79      def to_s
 80        value
 81      end
 82
 83      def to_h
 84        { use: @use, fingerprint: fingerprint.to_s }
 85      end
 86
 87      def inspect
 88        to_h.inspect
 89      end
 90
 91      def stripped
 92        self.class.strip(x509.to_pem)
 93      end
 94
 95      def to_key_pair(private_key, passphrase: nil, use: nil)
 96        KeyPair.new(x509.to_pem, private_key.to_s, passphrase, use)
 97      end
 98
 99      def expired?(time = Time.now)
100        x509.not_after <= time
101      end
102
103      def active?(time = Time.now)
104        x509.not_before <= time && !expired?(time)
105      end
106
107      def not_after
108        x509.not_after
109      end
110
111      def not_before
112        x509.not_before
113      end
114
115      def key_info
116        @key_info ||= KeyInfo.new(x509: x509)
117      end
118
119      class << self
120        def to_x509(value)
121          return value if value.is_a?(OpenSSL::X509::Certificate)
122
123          value = Base64.decode64(strip(value)) if base64?(value)
124          OpenSSL::X509::Certificate.new(value)
125        end
126
127        def base64?(value)
128          return unless value.is_a?(String)
129
130          sanitized_value = strip(value)
131          !!sanitized_value.match(BASE64_FORMAT)
132        end
133
134        def strip(value)
135          value
136            .gsub(BEGIN_CERT, '')
137            .gsub(END_CERT, '')
138            .gsub(/[\r\n]|\\r|\\n|\s/, '')
139        end
140      end
141    end
142  end
143end