Class: Vines::Store

Inherits:
Object
  • Object
show all
Includes:
Log
Defined in:
lib/vines/store.rb

Overview

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

Constant Summary collapse

@@sources =
nil

Instance Method Summary collapse

Methods included from Log

#log

Constructor Details

#initialize(dir) ⇒ Store

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



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/vines/store.rb', line 15

def initialize(dir)
  @dir = File.expand_path(dir)
  @store = OpenSSL::X509::Store.new
  certs.each {|c|
    begin
      @store.add_cert(c)
    rescue
      # do nothing cert is already known
      log.warn("WARNING! There are duplicate certificates")
    end
  }
end

Instance Method Details

#certsObject

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.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/vines/store.rb', line 51

def certs
  unless @@sources
    pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
    files = Dir[File.join(@dir, '*.crt')]
    files << AppConfig.environment.certificate_authorities if defined?(AppConfig)
    pairs = files.map do |name|
      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
    end
    @@sources = Hash[pairs]
  end
  @@sources.values.flatten
end

#domain?(pem, domain) ⇒ Boolean

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?

Returns:

  • (Boolean)


42
43
44
45
46
# File 'lib/vines/store.rb', line 42

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) ⇒ Object

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, else

  • 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 and 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 and conf/certs/wonderland.lit.key. These same two files would be returned for the subdomains of tea.wonderland.lit, crumpets.wonderland.lit, etc.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/vines/store.rb', line 88

def files_for_domain(domain)
  crt = File.expand_path("#{domain}.crt", @dir)
  key = File.expand_path("#{domain}.key", @dir)
  # lygneo keys will be prioritized
  if defined?(AppConfig)
    crt = AppConfig.server.chat.certificate 
    key = AppConfig.server.chat.key
  end
  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
  nil
end

#trusted?(pem) ⇒ Boolean

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.

Returns:

  • (Boolean)


31
32
33
34
35
36
37
# File 'lib/vines/store.rb', line 31

def trusted?(pem)
  if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
    @store.verify(cert).tap do |trusted|
      @store.add_cert(cert) if trusted rescue nil
    end
  end
end