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