class Vines::Store

An X509 certificate store that validates certificate trust chains. This uses the conf/certs/*.crt files as the list of trusted root CA certificates.

Public Class Methods

new(dir) click to toggle source

Create a certificate store to read certificate files from the given directory.

dir - The String directory name (absolute or relative).

# File lib/vines/store.rb, line 14
def initialize(dir)
  @dir = File.expand_path(dir)
  @store = OpenSSL::X509::Store.new
  certs.each {|cert| append(cert) }
end

Public Instance Methods

certs() click to toggle source

Return the trusted root CA certificates installed in conf/certs. These certificates are used to start the trust chain needed to validate certs we receive from clients and servers.

Returns an Array of OpenSSL::X509::Certificate objects.

# File lib/vines/store.rb, line 54
def certs
  @@sources ||= begin
    pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
    files = Dir[File.join(@dir, '*.crt')]
    if defined?(AppConfig)
      chain = AppConfig.environment.certificate_authorities.get
      files << chain unless chain.nil?
    end
    pairs = files.map do |name|
      begin
        File.open(name, "r:UTF-8") do |f|
          pems = f.read.scan(pattern)
          certs = pems.map {|pem| OpenSSL::X509::Certificate.new(pem) }
          certs.reject! {|cert| cert.not_after < Time.now }
          [name, certs]
        end
      rescue ArgumentError => e
        puts "Skipping '#{name}' cause of '#{e.message.to_s}'! "+
             "Checkout https://wiki.diasporafoundation.org/Vines#FAQ "+
             "for further instructions."
      end
    end
    Hash[pairs.compact]
  end
  @@sources.values.flatten
end
domain?(pem, domain) click to toggle source

Return true if the domain name matches one of the names in the certificate. In other words, is the certificate provided to us really for the domain to which we think we're connected?

pem - The PEM encoded certificate String. domain - The domain name String.

Returns true if the certificate was issued for the domain.

# File lib/vines/store.rb, line 43
def domain?(pem, domain)
  if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
    OpenSSL::SSL.verify_certificate_identity(cert, domain) rescue false
  end
end
files_for_domain(domain) click to toggle source

Returns a pair of file names containing the public key certificate and matching private key for the given domain. This supports using wildcard certificate files to serve several subdomains.

Finding the certificate and private key file for a domain follows these steps:

  • Look for <domain>.crt and <domain>.key files in the conf/certs directory. If found, return those file names, otherwise . . .

  • Inspect all conf/certs/*.crt files for certificates that contain the domain name either as the subject common name (CN) or as a DNS subjectAltName. The corresponding private key must be in a file of the same name as the certificate's, but with a .key extension.

So in the simplest configuration, the tea.wonderland.lit encryption files would be named:

  • conf/certs/tea.wonderland.lit.crt

  • conf/certs/tea.wonderland.lit.key

However, in the case of a wildcard certificate for *.wonderland.lit, the files would be:

  • conf/certs/wonderland.lit.crt

  • conf/certs/wonderland.lit.key

These same two files would be returned for the subdomains of:

  • tea.wonderland.lit

  • crumpets.wonderland.lit

  • etc.

domain - The String domain name.

Returns a two element String array for the certificate and private key

file names or nil if not found.
# File lib/vines/store.rb, line 117
def files_for_domain(domain)
  crt = File.expand_path("#{domain}.crt", @dir)
  key = File.expand_path("#{domain}.key", @dir)
  return [crt, key] if File.exists?(crt) && File.exists?(key)

  # Might be a wildcard cert file.
  @@sources.each do |file, certs|
    certs.each do |cert|
      if OpenSSL::SSL.verify_certificate_identity(cert, domain)
        key = file.chomp(File.extname(file)) + '.key'
        return [file, key] if File.exists?(file) && File.exists?(key)
      end
    end
  end
  puts "Your're using vines without a certificate! "+
       "Checkout https://wiki.diasporafoundation.org/Vines#Certificates "+
       "for further instructions."
  nil
end
trusted?(pem) click to toggle source

Return true if the certificate is signed by a CA certificate in the store. If the certificate can be trusted, it's added to the store so it can be used to trust other certs.

pem - The PEM encoded certificate String.

Returns true if the certificate is trusted.

# File lib/vines/store.rb, line 27
def trusted?(pem)
  if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
    @store.verify(cert).tap do |trusted|
      append(cert) if trusted
    end
  end
end

Private Instance Methods

append(cert) click to toggle source

Add a trusted certificate to the store, suppressing any OpenSSL errors caused by the certificate already being stored.

cert - The OpenSSL::X509::Certificate to add.

Returns nothing.

# File lib/vines/store.rb, line 145
def append(cert)
  @store.add_cert(cert)
rescue OpenSSL::X509::StoreError
  # Already added to store.
end