midi-smtp-server 2.3.3 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +112 -0
- data/MIT-LICENSE.txt +1 -1
- data/README.md +35 -950
- data/lib/midi-smtp-server/logger.rb +37 -0
- data/lib/midi-smtp-server/tls-transport.rb +54 -33
- data/lib/midi-smtp-server/version.rb +2 -2
- data/lib/midi-smtp-server.rb +178 -121
- metadata +5 -3
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
# A small and highly customizable ruby SMTP-Server.
|
6
|
+
module MidiSmtpServer
|
7
|
+
|
8
|
+
# class for Logging and support of on_logging_event
|
9
|
+
class ForwardingLogger
|
10
|
+
|
11
|
+
def initialize(on_logging_event)
|
12
|
+
@on_logging_event = on_logging_event
|
13
|
+
end
|
14
|
+
|
15
|
+
def info(msg)
|
16
|
+
@on_logging_event.call(nil, Logger::INFO, msg)
|
17
|
+
end
|
18
|
+
|
19
|
+
def warn(msg)
|
20
|
+
@on_logging_event.call(nil, Logger::WARN, msg)
|
21
|
+
end
|
22
|
+
|
23
|
+
def error(msg)
|
24
|
+
@on_logging_event.call(nil, Logger::ERROR, msg)
|
25
|
+
end
|
26
|
+
|
27
|
+
def fatal(msg)
|
28
|
+
@on_logging_event.call(nil, Logger::FATAL, msg)
|
29
|
+
end
|
30
|
+
|
31
|
+
def debug(msg)
|
32
|
+
@on_logging_event.call(nil, Logger::DEBUG, msg)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -12,16 +12,19 @@ module MidiSmtpServer
|
|
12
12
|
# Encryption ciphers and methods
|
13
13
|
# check https://www.owasp.org/index.php/TLS_Cipher_String_Cheat_Sheet
|
14
14
|
TLS_CIPHERS_ADVANCED_PLUS = 'DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256'
|
15
|
-
TLS_CIPHERS_ADVANCED =
|
16
|
-
TLS_CIPHERS_BROAD_COMP =
|
17
|
-
TLS_CIPHERS_WIDEST_COMP =
|
18
|
-
TLS_CIPHERS_LEGACY =
|
15
|
+
TLS_CIPHERS_ADVANCED = "#{TLS_CIPHERS_ADVANCED_PLUS}:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256"
|
16
|
+
TLS_CIPHERS_BROAD_COMP = "#{TLS_CIPHERS_ADVANCED}:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA"
|
17
|
+
TLS_CIPHERS_WIDEST_COMP = "#{TLS_CIPHERS_ADVANCED}:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA"
|
18
|
+
TLS_CIPHERS_LEGACY = "#{TLS_CIPHERS_ADVANCED}:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA"
|
19
19
|
TLS_METHODS_ADVANCED = 'TLSv1_2'
|
20
20
|
TLS_METHODS_LEGACY = 'TLSv1_1'
|
21
21
|
|
22
22
|
# class for TlsTransport
|
23
23
|
class TlsTransport
|
24
24
|
|
25
|
+
# current TLS OpenSSL::SSL::SSLContext
|
26
|
+
attr_reader :ssl_context
|
27
|
+
|
25
28
|
def initialize(cert_path, key_path, ciphers, methods, cert_cn, cert_san, logger)
|
26
29
|
# if need to debug something while working with openssl
|
27
30
|
# OpenSSL::debug = true
|
@@ -31,18 +34,11 @@ module MidiSmtpServer
|
|
31
34
|
@cert_path = cert_path.to_s == '' ? nil : cert_path.strip
|
32
35
|
@key_path = key_path.to_s == '' ? nil : key_path.strip
|
33
36
|
# create SSL context
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@
|
37
|
+
@ssl_context = OpenSSL::SSL::SSLContext.new
|
38
|
+
@ssl_context.ciphers = ciphers.to_s == '' ? TLS_CIPHERS_ADVANCED_PLUS : ciphers
|
39
|
+
@ssl_context.ssl_version = methods.to_s == '' ? TLS_METHODS_ADVANCED : methods
|
37
40
|
# check cert_path and key_path
|
38
|
-
if
|
39
|
-
# if any is set, test the pathes
|
40
|
-
raise "File \”#{@cert_path}\" does not exist or is not a regular file. Could not load certificate." unless File.file?(@cert_path.to_s)
|
41
|
-
raise "File \”#{@key_path}\" does not exist or is not a regular file. Could not load private key." unless File.file?(@key_path.to_s)
|
42
|
-
# try to load certificate and key
|
43
|
-
@ctx.cert = OpenSSL::X509::Certificate.new(File.open(@cert_path.to_s))
|
44
|
-
@ctx.key = OpenSSL::PKey::RSA.new(File.open(@key_path.to_s))
|
45
|
-
else
|
41
|
+
if @cert_path.nil?
|
46
42
|
# if none cert_path was set, create a self signed test certificate
|
47
43
|
# and try to setup common subject and subject alt name(s) for cert
|
48
44
|
@cert_cn = cert_cn.to_s.strip
|
@@ -52,33 +48,58 @@ module MidiSmtpServer
|
|
52
48
|
@cert_san.each { |san| @cert_san_ip << san if san =~ Resolv::IPv4::Regex || san =~ Resolv::IPv6::Regex }
|
53
49
|
# initialize self certificate and key
|
54
50
|
logger.debug("SSL: using self generated test certificate! CN=#{@cert_cn} SAN=[#{@cert_san.join(',')}]")
|
55
|
-
@
|
56
|
-
@
|
57
|
-
@
|
58
|
-
@
|
51
|
+
@ssl_context.key = OpenSSL::PKey::RSA.new 4096
|
52
|
+
@ssl_context.cert = OpenSSL::X509::Certificate.new
|
53
|
+
@ssl_context.cert.version = 2
|
54
|
+
@ssl_context.cert.serial = 1
|
59
55
|
# the subject and the issuer are identical only for test certificate
|
60
|
-
@
|
61
|
-
@
|
62
|
-
@
|
56
|
+
@ssl_context.cert.subject = OpenSSL::X509::Name.new [['CN', @cert_cn]]
|
57
|
+
@ssl_context.cert.issuer = @ssl_context.cert.subject
|
58
|
+
@ssl_context.cert.public_key = @ssl_context.key
|
63
59
|
# valid for 90 days
|
64
|
-
@
|
65
|
-
@
|
60
|
+
@ssl_context.cert.not_before = Time.now
|
61
|
+
@ssl_context.cert.not_after = Time.now + (60 * 60 * 24 * 90)
|
66
62
|
# setup some cert extensions
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
@
|
71
|
-
@
|
72
|
-
@
|
73
|
-
@
|
74
|
-
logger.debug("SSL: generated test certificate\r\n#{@
|
63
|
+
x509_extension_factory = OpenSSL::X509::ExtensionFactory.new
|
64
|
+
x509_extension_factory.subject_certificate = @ssl_context.cert
|
65
|
+
x509_extension_factory.issuer_certificate = @ssl_context.cert
|
66
|
+
@ssl_context.cert.add_extension(x509_extension_factory.create_extension('basicConstraints', 'CA:FALSE', false))
|
67
|
+
@ssl_context.cert.add_extension(x509_extension_factory.create_extension('keyUsage', 'digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment', false))
|
68
|
+
@ssl_context.cert.add_extension(x509_extension_factory.create_extension('subjectAltName', (@cert_san.map { |san| "DNS:#{san}" } + @cert_san_ip.map { |ip| "IP:#{ip}" }).join(', '), false))
|
69
|
+
@ssl_context.cert.sign @ssl_context.key, OpenSSL::Digest.new('SHA256')
|
70
|
+
logger.debug("SSL: generated test certificate\r\n#{@ssl_context.cert.to_text}")
|
71
|
+
else
|
72
|
+
# if any is set, test the pathes
|
73
|
+
raise "File \”#{@cert_path}\" does not exist or is not a regular file. Could not load certificate." unless File.file?(@cert_path.to_s)
|
74
|
+
raise "File \”#{@key_path}\" does not exist or is not a regular file. Could not load private key." unless @key_path.nil? || File.file?(@key_path.to_s)
|
75
|
+
# try to load certificate and key
|
76
|
+
cert_lines = File.read(@cert_path.to_s).lines
|
77
|
+
# check if the cert file contains a chain of certs
|
78
|
+
cert_indexes = cert_lines.each_with_index.map { |line, index| index if line.downcase.include?('-begin certificate-') }.compact
|
79
|
+
# create each cert in the chain
|
80
|
+
certs = []
|
81
|
+
cert_indexes.each_with_index do |cert_index, current_index|
|
82
|
+
end_index = current_index + 1 < cert_indexes.length ? cert_indexes[current_index + 1] : -1
|
83
|
+
certs << OpenSSL::X509::Certificate.new(cert_lines[cert_index..end_index].join)
|
84
|
+
end
|
85
|
+
# add the cert and optional found chain to context
|
86
|
+
@ssl_context.cert = certs.first
|
87
|
+
@ssl_context.extra_chain_cert = certs[1..]
|
88
|
+
# check if key was given by separate file or should be included in cert
|
89
|
+
if @key_path.nil?
|
90
|
+
key_index = cert_lines.index { |line| line =~ /-begin[^-]+key-/i }
|
91
|
+
end_index = cert_lines.index { |line| line =~ /-end[^-]+key-/i }
|
92
|
+
@ssl_context.key = OpenSSL::PKey::RSA.new(cert_lines[key_index..end_index].join)
|
93
|
+
else
|
94
|
+
@ssl_context.key = OpenSSL::PKey::RSA.new(File.open(@key_path.to_s))
|
95
|
+
end
|
75
96
|
end
|
76
97
|
end
|
77
98
|
|
78
99
|
# start ssl connection over existing tcpserver socket
|
79
100
|
def start(io)
|
80
101
|
# start SSL negotiation
|
81
|
-
ssl = OpenSSL::SSL::SSLSocket.new(io, @
|
102
|
+
ssl = OpenSSL::SSL::SSLSocket.new(io, @ssl_context)
|
82
103
|
# connect to server socket
|
83
104
|
ssl.accept
|
84
105
|
# make sure to close also the underlying io
|