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 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. require 'socket'
  2. require 'openssl'
  3. require 'httparty'
  4. module CryptCheck
  5. module Tls
  6. class TlsNotSupportedServer
  7. attr_reader :hostname, :port
  8. def initialize(hostname, port)
  9. @hostname, @port = hostname, port
  10. end
  11. end
  12. class Server
  13. TCP_TIMEOUT = 10
  14. SSL_TIMEOUT = 2*TCP_TIMEOUT
  15. EXISTING_METHODS = %i(TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2)
  16. SUPPORTED_METHODS = ::OpenSSL::SSL::SSLContext::METHODS
  17. class TLSException < ::Exception
  18. end
  19. class TLSNotAvailableException < TLSException
  20. end
  21. class MethodNotAvailable < TLSException
  22. end
  23. class CipherNotAvailable < TLSException
  24. end
  25. class Timeout < TLSException
  26. end
  27. class TLSTimeout < TLSException
  28. end
  29. class ConnectionError < TLSException
  30. end
  31. attr_reader :hostname, :port, :prefered_ciphers, :cert, :cert_valid, :cert_trusted, :dh
  32. def initialize(hostname, port)
  33. @hostname, @port = hostname, port
  34. @dh = []
  35. Logger.info { "#{hostname}:#{port}".colorize :blue }
  36. extract_cert
  37. Logger.info { '' }
  38. Logger.info { "Key : #{Tls.key_to_s self.key}" }
  39. fetch_prefered_ciphers
  40. check_supported_cipher
  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 }.sort.last
  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. {
  66. md2: %w(md2WithRSAEncryption),
  67. md5: %w(md5WithRSAEncryption md5WithRSA),
  68. sha1: %w(sha1WithRSAEncryption sha1WithRSA dsaWithSHA1 dsaWithSHA1_2 ecdsa_with_SHA1)
  69. }.each do |name, signature|
  70. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  71. def #{name}_sig?
  72. #{signature}.include? @cert.signature_algorithm
  73. end
  74. RUBY_EVAL
  75. end
  76. Cipher::TYPES.each do |type, _|
  77. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  78. def #{type}?
  79. supported_ciphers.any? { |c| c.#{type}? }
  80. end
  81. RUBY_EVAL
  82. end
  83. def key_size
  84. @cert.public_key.rsa_equivalent_size
  85. end
  86. def ssl?
  87. sslv2? or sslv3?
  88. end
  89. def tls?
  90. tlsv1? or tlsv1_1? or tlsv1_2?
  91. end
  92. def tls_only?
  93. tls? and !ssl?
  94. end
  95. def pfs?
  96. supported_ciphers.any? { |c| c.pfs? }
  97. end
  98. def pfs_only?
  99. supported_ciphers.all? { |c| c.pfs? }
  100. end
  101. private
  102. def connect(family, host, port, &block)
  103. socket = ::Socket.new family, sock_type
  104. sockaddr = ::Socket.sockaddr_in port, host
  105. Logger.trace { "Connecting to #{host}:#{port}" }
  106. begin
  107. status = socket.connect_nonblock sockaddr
  108. Logger.trace { "Connecting to #{host}:#{port} status : #{status}" }
  109. raise ConnectionError, status unless status == 0
  110. Logger.trace { "Connected to #{host}:#{port}" }
  111. block_given? ? block.call(socket) : nil
  112. rescue ::IO::WaitReadable
  113. Logger.trace { "Waiting for read to #{host}:#{port}" }
  114. raise Timeout unless IO.select [socket], nil, nil, TCP_TIMEOUT
  115. retry
  116. rescue ::IO::WaitWritable
  117. Logger.trace { "Waiting for write to #{host}:#{port}" }
  118. raise Timeout unless IO.select nil, [socket], nil, TCP_TIMEOUT
  119. retry
  120. rescue => e
  121. case e.message
  122. when /^Connection refused/
  123. raise TLSNotAvailableException, e
  124. end
  125. raise
  126. ensure
  127. socket.close
  128. end
  129. end
  130. def ssl_connect(socket, context, method, &block)
  131. ssl_socket = ::OpenSSL::SSL::SSLSocket.new socket, context
  132. ssl_socket.hostname = @hostname unless method == :SSLv2
  133. Logger.trace { "SSL connecting to #{@hostname}:#{@port}" }
  134. begin
  135. ssl_socket.connect_nonblock
  136. Logger.trace { "SSL connected to #{@hostname}:#{@port}" }
  137. return block_given? ? block.call(ssl_socket) : nil
  138. rescue ::IO::WaitReadable
  139. Logger.trace { "Waiting for SSL read to #{@hostname}:#{@port}" }
  140. raise TLSTimeout unless IO.select [socket], nil, nil, SSL_TIMEOUT
  141. retry
  142. rescue ::IO::WaitWritable
  143. Logger.trace { "Waiting for SSL write to #{@hostname}:#{@port}" }
  144. raise TLSTimeout unless IO.select nil, [socket], nil, SSL_TIMEOUT
  145. retry
  146. rescue ::OpenSSL::SSL::SSLError => e
  147. case e.message
  148. when /state=SSLv2 read server hello A$/,
  149. /state=SSLv3 read server hello A: wrong version number$/
  150. raise MethodNotAvailable, e
  151. when /state=error: no ciphers available$/,
  152. /state=SSLv3 read server hello A: sslv3 alert handshake failure$/
  153. raise CipherNotAvailable, e
  154. end
  155. raise TLSException, e
  156. rescue => e
  157. case e.message
  158. when /^Connection reset by peer$/
  159. raise MethodNotAvailable, e
  160. end
  161. raise TLSException, e
  162. ensure
  163. ssl_socket.close
  164. end
  165. end
  166. def ssl_client(method, ciphers = nil, &block)
  167. ssl_context = ::OpenSSL::SSL::SSLContext.new method
  168. ssl_context.ciphers = ciphers if ciphers
  169. Logger.trace { "Try #{method} connection with #{ciphers}" }
  170. [::Socket::AF_INET, ::Socket::AF_INET6].each do |family|
  171. Logger.trace { "Try connection for family #{family}" }
  172. addrs = begin
  173. ::Socket.getaddrinfo @hostname, nil, family, :STREAM
  174. rescue ::SocketError => e
  175. Logger.error { "Unable to resolv #{@hostname} : #{e}" }
  176. next
  177. end
  178. addrs.each do |addr|
  179. connect family, addr[3], @port do |socket|
  180. ssl_connect socket, ssl_context, method do |ssl_socket|
  181. return block_given? ? block.call(ssl_socket) : nil
  182. end
  183. end
  184. end
  185. end
  186. Logger.debug { "No SSL available on #{@hostname}" }
  187. raise CipherNotAvailable
  188. end
  189. def extract_cert
  190. EXISTING_METHODS.each do |method|
  191. next unless SUPPORTED_METHODS.include? method
  192. begin
  193. @cert, @chain = ssl_client(method) { |s| [s.peer_cert, s.peer_cert_chain] }
  194. Logger.debug { "Certificate #{@cert.subject}" }
  195. break
  196. rescue TLSException
  197. end
  198. end
  199. raise TLSNotAvailableException unless @cert
  200. @cert_valid = ::OpenSSL::SSL.verify_certificate_identity @cert, @hostname
  201. @cert_trusted = verify_trust @chain, @cert
  202. end
  203. def prefered_cipher(method)
  204. cipher = ssl_client(method, 'ALL:COMPLEMENTOFALL') { |s| Cipher.new method, s.cipher, s.tmp_key }
  205. Logger.info { "Prefered cipher for #{Tls.colorize method} : #{cipher.colorize}" }
  206. cipher
  207. rescue TLSException => e
  208. Logger.debug { "Method #{Tls.colorize method} not supported : #{e}" }
  209. nil
  210. end
  211. def fetch_prefered_ciphers
  212. @prefered_ciphers = {}
  213. EXISTING_METHODS.each do |method|
  214. next unless SUPPORTED_METHODS.include? method
  215. @prefered_ciphers[method] = prefered_cipher method
  216. end
  217. raise TLSNotAvailableException unless @prefered_ciphers.any? { |_, c| !c.nil? }
  218. end
  219. def available_ciphers(method)
  220. context = ::OpenSSL::SSL::SSLContext.new method
  221. context.ciphers = 'ALL:COMPLEMENTOFALL'
  222. context.ciphers
  223. end
  224. def supported_cipher?(method, cipher)
  225. dh = ssl_client method, [cipher] { |s| s.tmp_key }
  226. @dh << dh if dh
  227. cipher = Cipher.new method, cipher, dh
  228. dh = dh ? " (#{'DH'.colorize :green} : #{Tls.key_to_s dh})" : ''
  229. Logger.info { "#{Tls.colorize method} / #{cipher.colorize} : Supported#{dh}" }
  230. cipher
  231. rescue TLSException => e
  232. Logger.debug { "#{Tls.colorize method} / #{cipher.colorize} : Not supported (#{e})" }
  233. nil
  234. end
  235. def check_supported_cipher
  236. Logger.info { '' }
  237. @supported_ciphers = {}
  238. EXISTING_METHODS.each do |method|
  239. next unless SUPPORTED_METHODS.include? method and @prefered_ciphers[method]
  240. supported_ciphers = available_ciphers(method).collect { |c| supported_cipher? method, c }.reject { |c| c.nil? }
  241. Logger.info { '' } unless supported_ciphers.empty?
  242. @supported_ciphers[method] = supported_ciphers
  243. end
  244. end
  245. def verify_trust(chain, cert)
  246. store = ::OpenSSL::X509::Store.new
  247. store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
  248. store.set_default_paths
  249. %w(cacert mozilla).each do |directory|
  250. ::Dir.glob(::File.join '/usr/share/ca-certificates', directory, '*').each do |file|
  251. cert = ::OpenSSL::X509::Certificate.new ::File.read file
  252. begin
  253. store.add_cert cert
  254. rescue ::OpenSSL::X509::StoreError
  255. end
  256. end
  257. end
  258. chain.each do |cert|
  259. begin
  260. store.add_cert cert
  261. rescue ::OpenSSL::X509::StoreError
  262. end
  263. end
  264. trusted = store.verify cert
  265. p store.error_string unless trusted
  266. trusted
  267. end
  268. def uniq_dh
  269. dh, find = [], []
  270. @dh.each do |k|
  271. f = [k.type, k.size]
  272. unless find.include? f
  273. dh << k
  274. find << f
  275. end
  276. end
  277. @dh = dh
  278. end
  279. end
  280. class TcpServer < Server
  281. private
  282. def sock_type
  283. ::Socket::SOCK_STREAM
  284. end
  285. end
  286. class UdpServer < Server
  287. private
  288. def sock_type
  289. ::Socket::SOCK_DGRAM
  290. end
  291. end
  292. end
  293. end