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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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.enable_fallback_scsv if fallback
  226. ssl_context.ciphers = ciphers.join ':'
  227. ssl_context.ecdh_curves = curves.join ':' if curves
  228. #ssl_context.ecdh_auto = false
  229. #ecdh = OpenSSL::PKey::EC.new('sect163r1').generate_key
  230. #ssl_context.tmp_ecdh_callback = proc { ecdh }
  231. Logger.trace { "Try method=#{method} / ciphers=#{ciphers} / curves=#{curves} / scsv=#{fallback}" }
  232. connect do |socket|
  233. ssl_connect socket, ssl_context, method do |ssl_socket|
  234. return block_given? ? block.call(ssl_socket) : nil
  235. end
  236. end
  237. end
  238. def extract_cert
  239. EXISTING_METHODS.each do |method|
  240. next unless SUPPORTED_METHODS.include? method
  241. begin
  242. @cert, @chain = ssl_client(method) { |s| [s.peer_cert, s.peer_cert_chain] }
  243. Logger.debug { "Certificate #{@cert.subject}" }
  244. break
  245. rescue Timeout, TLSTimeout, ConnectionError, ::SystemCallError
  246. raise
  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. supported_ciphers = []
  297. available_ciphers = available_ciphers method
  298. available_ciphers.each do |c|
  299. cipher = Cipher.new method, c
  300. supported = supported_cipher? method, c.first
  301. if supported
  302. if cipher.ecdhe?
  303. SUPPORTED_CURVES.each do |curve|
  304. supported = supported_cipher? method, c.first, [curve]
  305. supported_ciphers << supported if supported
  306. end
  307. else
  308. supported_ciphers << supported
  309. end
  310. end
  311. end
  312. Logger.info { '' } unless supported_ciphers.empty?
  313. @supported_ciphers[method] = supported_ciphers
  314. end
  315. end
  316. def check_fallback_scsv
  317. @fallback_scsv = false
  318. methods = @prefered_ciphers.reject { |_, v| v.nil? }.keys
  319. if methods.size > 1
  320. # We will try to connect to the not better supported method
  321. method = methods[1]
  322. begin
  323. ssl_client method, fallback: true
  324. rescue InappropriateFallback
  325. @fallback_scsv = true
  326. end
  327. else
  328. @fallback_scsv = nil
  329. end
  330. text, color = case @fallback_scsv
  331. when true
  332. ['Supported', :good]
  333. when false
  334. ['Not supported', :error]
  335. when nil
  336. ['Not applicable', :unknown]
  337. end
  338. Logger.info { "Fallback SCSV : #{text.colorize color}" }
  339. end
  340. def verify_trust(chain, cert)
  341. store = ::OpenSSL::X509::Store.new
  342. store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
  343. store.set_default_paths
  344. %w(/etc/ssl/certs).each do |directory|
  345. ::Dir.glob(::File.join directory, '*.pem').each do |file|
  346. cert = ::OpenSSL::X509::Certificate.new ::File.read file
  347. begin
  348. store.add_cert cert
  349. rescue ::OpenSSL::X509::StoreError
  350. end
  351. end
  352. end
  353. chain.each do |cert|
  354. begin
  355. store.add_cert cert
  356. rescue ::OpenSSL::X509::StoreError
  357. end
  358. end
  359. trusted = store.verify cert
  360. p store.error_string unless trusted
  361. trusted
  362. end
  363. def uniq_dh
  364. dh, find = [], []
  365. @dh.each do |k|
  366. f = [k.type, k.size]
  367. unless find.include? f
  368. dh << k
  369. find << f
  370. end
  371. end
  372. @dh = dh
  373. end
  374. end
  375. class TcpServer < Server
  376. private
  377. def sock_type
  378. ::Socket::SOCK_STREAM
  379. end
  380. end
  381. class UdpServer < Server
  382. private
  383. def sock_type
  384. ::Socket::SOCK_DGRAM
  385. end
  386. end
  387. end
  388. end