lib/rails/auth/x509/certificate.rb
# frozen_string_literal: true
module Rails
module Auth
module X509
# X.509 client certificates obtained from HTTP requests
class Certificate
attr_reader :certificate
def initialize(certificate)
unless certificate.is_a?(OpenSSL::X509::Certificate)
raise TypeError, "expecting OpenSSL::X509::Certificate, got #{certificate.class}"
end
@certificate = certificate.freeze
@subject = {}
@certificate.subject.to_a.each do |name, data, _type|
@subject[name.freeze] = data.freeze
end
@subject_alt_names = SubjectAltNameExtension.new(certificate)
@subject_alt_names.freeze
@subject.freeze
end
def [](component)
@subject[component.to_s.upcase]
end
def cn
@subject["CN"]
end
alias common_name cn
def dns_names
@subject_alt_names.dns_names
end
def ips
@subject_alt_names.ips
end
def ou
@subject["OU"]
end
alias organizational_unit ou
def uris
@subject_alt_names.uris
end
# According to the SPIFFE standard only one SPIFFE ID can exist in the URI
# SAN:
# (https://github.com/spiffe/spiffe/blob/master/standards/X509-SVID.md#2-spiffe-id)
#
# @return [String, nil] string containing SPIFFE ID if one is present
# in the certificate
def spiffe_id
uris.detect { |uri| uri.start_with?("spiffe://") }
end
# Generates inspectable attributes for debugging
#
# @return [Hash] hash containing parts of the certificate subject (cn, ou)
# and subject alternative name extension (uris, dns_names) as well
# as SPIFFE ID (spiffe_id), which is just a convenience since those
# are already included in the uris
def attributes
{
cn: cn,
dns_names: dns_names,
ips: ips,
ou: ou,
spiffe_id: spiffe_id,
uris: uris
}.reject { |_, v| v.nil? || v.empty? }
end
# Compare ourself to another object by ensuring that it has the same type
# and that its certificate pem is the same as ours
def ==(other)
other.is_a?(self.class) && other.certificate.to_der == certificate.to_der
end
alias eql? ==
end
end
end
end