You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

server.rb 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. require 'socket'
  2. require 'openssl'
  3. require 'httparty'
  4. module CryptCheck
  5. module Tls
  6. class Server
  7. TCP_TIMEOUT = 10
  8. SSL_TIMEOUT = 2*TCP_TIMEOUT
  9. EXISTING_METHODS = %i(TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2)
  10. SUPPORTED_METHODS = ::OpenSSL::SSL::SSLContext::METHODS
  11. class TLSException < ::StandardError
  12. end
  13. class TLSNotAvailableException < TLSException
  14. def to_s
  15. 'TLS seems not supported on this server'
  16. end
  17. end
  18. class MethodNotAvailable < TLSException
  19. end
  20. class CipherNotAvailable < TLSException
  21. end
  22. class InappropriateFallback < TLSException
  23. end
  24. class Timeout < ::StandardError
  25. end
  26. class TLSTimeout < Timeout
  27. end
  28. class ConnectionError < ::StandardError
  29. end
  30. attr_reader :family, :ip, :port, :hostname, :prefered_ciphers, :cert, :cert_valid, :cert_trusted, :dh
  31. def initialize(hostname, family, ip, port)
  32. @hostname, @family, @ip, @port = hostname, family, ip, port
  33. @dh = []
  34. Logger.info { name.colorize :perfect }
  35. extract_cert
  36. Logger.info { '' }
  37. Logger.info { "Key : #{Tls.key_to_s self.key}" }
  38. fetch_prefered_ciphers
  39. check_supported_cipher
  40. check_fallback_scsv
  41. uniq_dh
  42. end
  43. def key
  44. @cert.public_key
  45. end
  46. def cipher_size
  47. supported_ciphers.collect { |c| c.size }.min
  48. end
  49. def supported_protocols
  50. @supported_ciphers.keys
  51. end
  52. def supported_ciphers
  53. @supported_ciphers.values.flatten 1
  54. end
  55. def supported_ciphers_by_protocol(protocol)
  56. @supported_ciphers[protocol]
  57. end
  58. EXISTING_METHODS.each do |method|
  59. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  60. def #{method.to_s.downcase}?
  61. !prefered_ciphers[:#{method}].nil?
  62. end
  63. RUBY_EVAL
  64. end
  65. SIGNATURE_ALGORITHMS = {
  66. 'dsaWithSHA' => %i(sha1 dss),
  67. 'dsaWithSHA1' => %i(sha1 dss),
  68. 'dsaWithSHA1_2' => %i(sha1 dss),
  69. 'dsa_with_SHA224' => %i(sha2 dss),
  70. 'dsa_with_SHA256' => %i(sha2 dss),
  71. 'mdc2WithRSA' => %i(mdc2 rsa),
  72. 'md2WithRSAEncryption' => %i(md2 rsa),
  73. 'md4WithRSAEncryption' => %i(md4, rsa),
  74. 'md5WithRSA' => %i(md5 rsa),
  75. 'md5WithRSAEncryption' => %i(md5 rsa),
  76. 'shaWithRSAEncryption' => %i(sha rsa),
  77. 'sha1WithRSA' => %i(sha1 rsa),
  78. 'sha1WithRSAEncryption' => %i(sha1 rsa),
  79. 'sha224WithRSAEncryption' => %i(sha2 rsa),
  80. 'sha256WithRSAEncryption' => %i(sha2 rsa),
  81. 'sha384WithRSAEncryption' => %i(sha2 rsa),
  82. 'sha512WithRSAEncryption' => %i(sha2 rsa),
  83. 'ripemd160WithRSA' => %i(ripemd160 rsa),
  84. 'ecdsa-with-SHA1' => %i(sha1 ecc),
  85. 'ecdsa-with-SHA224' => %i(sha2 ecc),
  86. 'ecdsa-with-SHA256' => %i(sha2 ecc),
  87. 'ecdsa-with-SHA384' => %i(sha2 ecc),
  88. 'ecdsa-with-SHA512' => %i(sha2 ecc),
  89. 'id_GostR3411_94_with_GostR3410_2001' => %i(ghost),
  90. 'id_GostR3411_94_with_GostR3410_94' => %i(ghost),
  91. 'id_GostR3411_94_with_GostR3410_94_cc' => %i(ghost),
  92. 'id_GostR3411_94_with_GostR3410_2001_cc' => %i(ghost)
  93. }
  94. %i(md2 mdc2 md4 md5 ripemd160 sha sha1 sha2 rsa dss ecc ghost).each do |name|
  95. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  96. def #{name}_sig?
  97. SIGNATURE_ALGORITHMS[@cert.signature_algorithm].include? :#{name}
  98. end
  99. RUBY_EVAL
  100. end
  101. Cipher::TYPES.each do |type, _|
  102. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  103. def #{type}?
  104. supported_ciphers.any? { |c| c.#{type}? }
  105. end
  106. RUBY_EVAL
  107. end
  108. def key_size
  109. @cert.public_key.size
  110. end
  111. def ssl?
  112. sslv2? or sslv3?
  113. end
  114. def tls?
  115. tlsv1? or tlsv1_1? or tlsv1_2?
  116. end
  117. def tls_only?
  118. tls? and !ssl?
  119. end
  120. def tlsv1_2_only?
  121. tlsv1_2? and not ssl? and not tlsv1? and not tlsv1_1?
  122. end
  123. def pfs?
  124. supported_ciphers.any? { |c| c.pfs? }
  125. end
  126. def pfs_only?
  127. supported_ciphers.all? { |c| c.pfs? }
  128. end
  129. def ecdhe?
  130. supported_ciphers.any? { |c| c.ecdhe? }
  131. end
  132. def ecdhe_only?
  133. supported_ciphers.all? { |c| c.ecdhe? }
  134. end
  135. def aead?
  136. supported_ciphers.any? { |c| c.aead? }
  137. end
  138. def aead_only?
  139. supported_ciphers.all? { |c| c.aead? }
  140. end
  141. def sweet32?
  142. supported_ciphers.any? { |c| c.sweet32? }
  143. end
  144. def fallback_scsv?
  145. @fallback_scsv
  146. end
  147. def must_staple?
  148. @cert.extensions.any? { |e| e.oid == '1.3.6.1.5.5.7.1.24' }
  149. end
  150. private
  151. def name
  152. name = "#@ip:#@port"
  153. name += " [#@hostname]" if @hostname
  154. name
  155. end
  156. def connect(&block)
  157. socket = ::Socket.new @family, sock_type
  158. sockaddr = ::Socket.sockaddr_in @port, @ip
  159. #Logger.trace { "Connecting to #{@ip}:#{@port}" }
  160. begin
  161. status = socket.connect_nonblock sockaddr
  162. #Logger.trace { "Connecting to #{@ip}:#{@port} status : #{status}" }
  163. raise ConnectionError, status unless status == 0
  164. #Logger.trace { "Connected to #{@ip}:#{@port}" }
  165. block_given? ? block.call(socket) : nil
  166. rescue ::IO::WaitReadable
  167. #Logger.trace { "Waiting for read to #{@ip}:#{@port}" }
  168. raise Timeout, "Timeout when connect to #{@ip}:#{@port} (max #{TCP_TIMEOUT.humanize})" unless IO.select [socket], nil, nil, TCP_TIMEOUT
  169. retry
  170. rescue ::IO::WaitWritable
  171. #Logger.trace { "Waiting for write to #{@ip}:#{@port}" }
  172. raise Timeout, "Timeout when connect to #{@ip}:#{@port} (max #{TCP_TIMEOUT.humanize})" unless IO.select nil, [socket], nil, TCP_TIMEOUT
  173. retry
  174. ensure
  175. socket.close
  176. end
  177. end
  178. def ssl_connect(socket, context, method, &block)
  179. ssl_socket = ::OpenSSL::SSL::SSLSocket.new socket, context
  180. ssl_socket.hostname = @hostname if @hostname and method != :SSLv2
  181. #Logger.trace { "SSL connecting to #{name}" }
  182. begin
  183. ssl_socket.connect_nonblock
  184. #Logger.trace { "SSL connected to #{name}" }
  185. return block_given? ? block.call(ssl_socket) : nil
  186. rescue ::OpenSSL::SSL::SSLErrorWaitReadable
  187. #Logger.trace { "Waiting for SSL read to #{name}" }
  188. raise TLSTimeout, "Timeout when TLS connect to #{@ip}:#{@port} (max #{SSL_TIMEOUT.humanize})" unless IO.select [ssl_socket], nil, nil, SSL_TIMEOUT
  189. retry
  190. rescue ::OpenSSL::SSL::SSLErrorWaitWritable
  191. #Logger.trace { "Waiting for SSL write to #{name}" }
  192. raise TLSTimeout, "Timeout when TLS connect to #{@ip}:#{@port} (max #{SSL_TIMEOUT.humanize})" unless IO.select nil, [ssl_socket], nil, SSL_TIMEOUT
  193. retry
  194. rescue ::OpenSSL::SSL::SSLError => e
  195. case e.message
  196. when /state=SSLv.* read server hello A$/
  197. raise TLSNotAvailableException, e
  198. when /state=SSLv.* read server hello A: wrong version number$/
  199. raise MethodNotAvailable, e
  200. when /state=error: no ciphers available$/,
  201. /state=SSLv.* read server hello A: sslv.* alert handshake failure$/
  202. raise CipherNotAvailable, e
  203. when /state=SSLv.* read server hello A: tlsv.* alert inappropriate fallback$/
  204. raise InappropriateFallback, e
  205. end
  206. raise
  207. rescue ::SystemCallError => e
  208. case e.message
  209. when /^Connection reset by peer - SSL_connect$/
  210. raise TLSNotAvailableException, e
  211. end
  212. raise
  213. ensure
  214. ssl_socket.close
  215. end
  216. end
  217. # secp192r1 secp256r1
  218. SUPPORTED_CURVES = %w(secp160k1 secp160r1 secp160r2 sect163k1
  219. sect163r1 sect163r2 secp192k1 sect193r1 sect193r2 secp224k1
  220. secp224r1 sect233k1 sect233r1 sect239k1 secp256k1 sect283k1
  221. sect283r1 secp384r1 sect409k1 sect409r1 secp521r1 sect571k1
  222. sect571r1 X25519)
  223. def ssl_client(method, ciphers = %w(ALL COMPLEMENTOFALL), curves = nil, fallback: false, &block)
  224. ssl_context = ::OpenSSL::SSL::SSLContext.new method, fallback_scsv: fallback
  225. ssl_context.ciphers = ciphers.join ':'
  226. ssl_context.ecdh_curves = curves.join ':' if curves
  227. #ssl_context.ecdh_auto = false
  228. #ecdh = OpenSSL::PKey::EC.new('sect163r1').generate_key
  229. #ssl_context.tmp_ecdh_callback = proc { ecdh }
  230. Logger.trace { "Try method=#{method} / ciphers=#{ciphers} / curves=#{curves} / scsv=#{fallback}" }
  231. connect do |socket|
  232. ssl_connect socket, ssl_context, method do |ssl_socket|
  233. return block_given? ? block.call(ssl_socket) : nil
  234. end
  235. end
  236. end
  237. def extract_cert
  238. EXISTING_METHODS.each do |method|
  239. next unless SUPPORTED_METHODS.include? method
  240. begin
  241. @cert, @chain = ssl_client(method) { |s| [s.peer_cert, s.peer_cert_chain] }
  242. Logger.debug { "Certificate #{@cert.subject}" }
  243. break
  244. rescue Timeout, TLSTimeout, ConnectionError, ::SystemCallError
  245. raise
  246. rescue
  247. end
  248. end
  249. raise TLSNotAvailableException unless @cert
  250. @cert_valid = ::OpenSSL::SSL.verify_certificate_identity @cert, (@hostname || @ip)
  251. @cert_trusted = verify_trust @chain, @cert
  252. end
  253. def prefered_cipher(method)
  254. cipher = ssl_client(method) { |s| Cipher.new method, s.cipher, s.tmp_key }
  255. Logger.info { "Prefered cipher for #{Tls.colorize method} : #{cipher.colorize}" }
  256. cipher
  257. rescue => e
  258. Logger.debug { "Method #{Tls.colorize method} not supported : #{e}" }
  259. nil
  260. end
  261. def fetch_prefered_ciphers
  262. @prefered_ciphers = {}
  263. EXISTING_METHODS.each do |method|
  264. next unless SUPPORTED_METHODS.include? method
  265. @prefered_ciphers[method] = prefered_cipher method
  266. end
  267. raise TLSNotAvailableException unless @prefered_ciphers.any? { |_, c| !c.nil? }
  268. end
  269. def available_ciphers(method)
  270. context = ::OpenSSL::SSL::SSLContext.new method
  271. context.ciphers = %w(ALL COMPLEMENTOFALL)
  272. context.ciphers
  273. end
  274. def supported_cipher?(method, cipher, curves = nil)
  275. dh = ssl_client(method, [cipher], curves) { |s| s.tmp_key }
  276. @dh << dh if dh
  277. cipher = Cipher.new method, cipher, dh
  278. dh = dh ? " (#{'PFS'.colorize :good} : #{Tls.key_to_s dh})" : ''
  279. states = cipher.states
  280. text = %i(critical error warning good perfect best).collect do |s|
  281. states[s].collect { |t| t.to_s.colorize s }.join ' '
  282. end.reject &:empty?
  283. text = text.join ' '
  284. Logger.info { "#{Tls.colorize method} / #{cipher.colorize}#{dh} [#{text}]" }
  285. cipher
  286. rescue => e
  287. cipher = Cipher.new method, cipher
  288. Logger.debug { "#{Tls.colorize method} / #{cipher.colorize} : Not supported (#{e})" }
  289. nil
  290. end
  291. def check_supported_cipher
  292. Logger.info { '' }
  293. @supported_ciphers = {}
  294. EXISTING_METHODS.each do |method|
  295. next unless SUPPORTED_METHODS.include? method and @prefered_ciphers[method]
  296. available_ciphers = available_ciphers method
  297. available_ciphers = available_ciphers.inject [] do |cs, c|
  298. cipher = Cipher.new method, c
  299. if cipher.ecdhe?
  300. c = SUPPORTED_CURVES.collect { |ec| [method, c.first, [ec]] }
  301. else
  302. c = [[method, c.first]]
  303. end
  304. cs + c
  305. end
  306. supported_ciphers = available_ciphers.collect { |c| supported_cipher? *c }.reject { |c| c.nil? }
  307. Logger.info { '' } unless supported_ciphers.empty?
  308. @supported_ciphers[method] = supported_ciphers
  309. end
  310. end
  311. def check_fallback_scsv
  312. @fallback_scsv = false
  313. methods = @supported_ciphers.keys
  314. if methods.size > 1
  315. # We will try to connect to the not better supported method
  316. method = methods[1]
  317. begin
  318. ssl_client method, fallback: true
  319. rescue InappropriateFallback
  320. @fallback_scsv = true
  321. end
  322. else
  323. @fallback_scsv = nil
  324. end
  325. text, color = case @fallback_scsv
  326. when true
  327. ['Supported', :good]
  328. when false
  329. ['Not supported', :error]
  330. when nil
  331. ['Not applicable', :unknown]
  332. end
  333. Logger.info { "Fallback SCSV : #{text.colorize color}" }
  334. end
  335. def verify_trust(chain, cert)
  336. store = ::OpenSSL::X509::Store.new
  337. store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
  338. store.set_default_paths
  339. %w(/etc/ssl/certs).each do |directory|
  340. ::Dir.glob(::File.join directory, '*.pem').each do |file|
  341. cert = ::OpenSSL::X509::Certificate.new ::File.read file
  342. begin
  343. store.add_cert cert
  344. rescue ::OpenSSL::X509::StoreError
  345. end
  346. end
  347. end
  348. chain.each do |cert|
  349. begin
  350. store.add_cert cert
  351. rescue ::OpenSSL::X509::StoreError
  352. end
  353. end
  354. trusted = store.verify cert
  355. p store.error_string unless trusted
  356. trusted
  357. end
  358. def uniq_dh
  359. dh, find = [], []
  360. @dh.each do |k|
  361. f = [k.type, k.size]
  362. unless find.include? f
  363. dh << k
  364. find << f
  365. end
  366. end
  367. @dh = dh
  368. end
  369. end
  370. class TcpServer < Server
  371. private
  372. def sock_type
  373. ::Socket::SOCK_STREAM
  374. end
  375. end
  376. class UdpServer < Server
  377. private
  378. def sock_type
  379. ::Socket::SOCK_DGRAM
  380. end
  381. end
  382. end
  383. end