main
 1# frozen_string_literal: true
 2
 3module Minbox
 4  class Server
 5    SUBJECT = "/C=CA/ST=AB/L=Calgary/O=minbox/OU=development/CN=minbox"
 6    attr_reader :host, :logger, :key, :server
 7
 8    def initialize(host: "localhost", port: 25, tls: false, logger: Minbox.logger, thread_pool: Concurrent::CachedThreadPool.new)
 9      @host = host
10      @logger = logger
11      @tls = tls
12      @key = OpenSSL::PKey::RSA.new(2048)
13      logger.debug("Starting server on port #{port}...")
14      @server = TCPServer.new(port.to_i)
15      @thread_pool = thread_pool
16    end
17
18    def tls?
19      @tls
20    end
21
22    def listen!(&block)
23      @server = upgrade(@server) if tls?
24      logger.debug("Server started!")
25
26      loop do
27        handle(server.accept, &block)
28      rescue StandardError => error
29        logger.error(error)
30      end
31    end
32
33    def handle(socket, &block)
34      @thread_pool.post do
35        logger.debug("client connected: #{socket.inspect}")
36        Client.new(self, socket, logger).handle(&block)
37      end
38    end
39
40    def shutdown!
41      server&.close
42    end
43
44    def ssl_context
45      @ssl_context ||=
46        begin
47          ssl_context = OpenSSL::SSL::SSLContext.new
48          ssl_context.cert = certificate_for(key)
49          ssl_context.key = key
50          ssl_context.ssl_version = :TLSv1_2
51          ssl_context
52        end
53    end
54
55    private
56
57    def upgrade(tcp_server)
58      server = OpenSSL::SSL::SSLServer.new(tcp_server, ssl_context)
59      server.start_immediately = true
60      server
61    end
62
63    def certificate_for(private_key, subject = SUBJECT)
64      certificate = OpenSSL::X509::Certificate.new
65      certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse(subject)
66      certificate.not_before = Time.now
67      certificate.not_after = certificate.not_before + 30 * 24 * 60 * 60 # 30 days
68      certificate.public_key = private_key.public_key
69      certificate.serial = 1
70      certificate.version = 2
71      apply_ski_extension_to(certificate)
72      certificate.sign(private_key, OpenSSL::Digest::SHA256.new)
73      certificate
74    end
75
76    def apply_ski_extension_to(certificate)
77      extensions = OpenSSL::X509::ExtensionFactory.new
78      extensions.subject_certificate =
79        extensions.issuer_certificate = certificate
80      [
81        ["subjectKeyIdentifier", "hash", false],
82        ["keyUsage", "keyEncipherment,digitalSignature", true],
83      ].each do |x|
84        certificate.add_extension(extensions.create_extension(x[0], x[1], x[2]))
85      end
86    end
87  end
88end