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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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 CipherNotAvailable < TLSException
  22. end
  23. class Timeout < TLSException
  24. end
  25. class TLSTimeout < TLSException
  26. end
  27. class ConnectionError < TLSException
  28. end
  29. attr_reader :hostname, :port, :prefered_ciphers, :cert, :cert_valid, :cert_trusted
  30. def initialize(hostname, port)
  31. @hostname = hostname
  32. @port = port
  33. Logger.info { "#{hostname}:#{port}".colorize :blue }
  34. extract_cert
  35. #@prefered_ciphers = @supported_ciphers = Hash[SUPPORTED_METHODS.collect { |m| [m, []]}]
  36. fetch_prefered_ciphers
  37. check_supported_cipher
  38. end
  39. def supported_methods
  40. worst = EXISTING_METHODS.find { |method| !@prefered_ciphers[method].nil? }
  41. best = EXISTING_METHODS.reverse.find { |method| !@prefered_ciphers[method].nil? }
  42. { worst: worst, best: best }
  43. end
  44. def key
  45. key = @cert.public_key
  46. case key
  47. when ::OpenSSL::PKey::RSA then
  48. [:rsa, key.n.num_bits]
  49. when ::OpenSSL::PKey::DSA then
  50. [:dsa, key.p.num_bits]
  51. when ::OpenSSL::PKey::EC then
  52. [:ecc, key.group.degree]
  53. end
  54. end
  55. def key_size
  56. type, size = self.key
  57. if type == :ecc
  58. size = case size
  59. when 160 then
  60. 1024
  61. when 224 then
  62. 2048
  63. when 256 then
  64. 3072
  65. when 384 then
  66. 7680
  67. when 521 then
  68. 15360
  69. end
  70. end
  71. size
  72. end
  73. def cipher_size
  74. cipher_strengths = supported_ciphers.collect { |c| c[2] }.uniq.sort
  75. worst, best = cipher_strengths.first, cipher_strengths.last
  76. { worst: worst, best: best }
  77. end
  78. EXISTING_METHODS.each do |method|
  79. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  80. def #{method.to_s.downcase}?
  81. !prefered_ciphers[:#{method}].nil?
  82. end
  83. RUBY_EVAL
  84. end
  85. {
  86. md2: %w(md2WithRSAEncryption),
  87. md5: %w(md5WithRSAEncryption md5WithRSA),
  88. sha1: %w(sha1WithRSAEncryption sha1WithRSA dsaWithSHA1 dsaWithSHA1_2 ecdsa_with_SHA1)
  89. }.each do |name, signature|
  90. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  91. def #{name}_sig?
  92. #{signature}.include? @cert.signature_algorithm
  93. end
  94. RUBY_EVAL
  95. end
  96. Tls::TYPES.each do |type, _|
  97. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  98. def #{type}?
  99. supported_ciphers.any? { |s| Tls.#{type}? s.first }
  100. end
  101. RUBY_EVAL
  102. end
  103. def ssl?
  104. sslv2? or sslv3?
  105. end
  106. def tls?
  107. tlsv1? or tlsv1_1? or tlsv1_2?
  108. end
  109. def tls_only?
  110. tls? and !ssl?
  111. end
  112. def pfs?
  113. supported_ciphers.any? { |c| Tls.pfs? c.first }
  114. end
  115. def pfs_only?
  116. supported_ciphers.all? { |c| Tls.pfs? c.first }
  117. end
  118. def supported_ciphers
  119. @supported_ciphers.values.flatten(1).uniq
  120. end
  121. def supported_ciphers_by_method
  122. @supported_ciphers
  123. end
  124. private
  125. def connect(family, host, port, &block)
  126. socket = ::Socket.new family, sock_type
  127. sockaddr = ::Socket.sockaddr_in port, host
  128. Logger.trace { "Connecting to #{host}:#{port}" }
  129. begin
  130. status = socket.connect_nonblock sockaddr
  131. Logger.trace { "Connecting to #{host}:#{port} status : #{status}" }
  132. raise ConnectionError, status unless status == 0
  133. Logger.trace { "Connected to #{host}:#{port}" }
  134. block_given? ? block.call(socket) : nil
  135. rescue ::IO::WaitReadable
  136. Logger.trace { "Waiting for read to #{host}:#{port}" }
  137. raise Timeout unless IO.select [socket], nil, nil, TCP_TIMEOUT
  138. retry
  139. rescue ::IO::WaitWritable
  140. Logger.trace { "Waiting for write to #{host}:#{port}" }
  141. raise Timeout unless IO.select nil, [socket], nil, TCP_TIMEOUT
  142. retry
  143. ensure
  144. socket.close
  145. end
  146. end
  147. def ssl_connect(socket, context, method, &block)
  148. ssl_socket = ::OpenSSL::SSL::SSLSocket.new socket, context
  149. ssl_socket.hostname = @hostname unless method == :SSLv2
  150. Logger.trace { "SSL connecting to #{@hostname}:#{@port}" }
  151. begin
  152. ssl_socket.connect_nonblock
  153. Logger.trace { "SSL connected to #{@hostname}:#{@port}" }
  154. return block_given? ? block.call(ssl_socket) : nil
  155. rescue ::IO::WaitReadable
  156. Logger.trace { "Waiting for SSL read to #{@hostname}:#{@port}" }
  157. raise TLSTimeout unless IO.select [socket], nil, nil, SSL_TIMEOUT
  158. retry
  159. rescue ::IO::WaitWritable
  160. Logger.trace { "Waiting for SSL write to #{@hostname}:#{@port}" }
  161. raise TLSTimeout unless IO.select nil, [socket], nil, SSL_TIMEOUT
  162. retry
  163. rescue => e
  164. raise TLSNotAvailableException, e
  165. ensure
  166. ssl_socket.close
  167. end
  168. end
  169. def ssl_client(method, ciphers = nil, &block)
  170. ssl_context = ::OpenSSL::SSL::SSLContext.new method
  171. ssl_context.ciphers = ciphers if ciphers
  172. Logger.trace { "Try #{method} connection with #{ciphers}" }
  173. [::Socket::AF_INET, ::Socket::AF_INET6].each do |family|
  174. Logger.trace { "Try connection for family #{family}" }
  175. addrs = begin
  176. ::Socket.getaddrinfo @hostname, nil, family, :STREAM
  177. rescue ::SocketError => e
  178. Logger.error { "Unable to resolv #{@hostname} : #{e}" }
  179. next
  180. end
  181. addrs.each do |addr|
  182. connect family, addr[3], @port do |socket|
  183. ssl_connect socket, ssl_context, method do |ssl_socket|
  184. return block_given? ? block.call(ssl_socket) : nil
  185. end
  186. end
  187. end
  188. end
  189. Logger.debug { "No SSL available on #{@hostname}" }
  190. raise CipherNotAvailable
  191. end
  192. def extract_cert
  193. EXISTING_METHODS.each do |method|
  194. next unless SUPPORTED_METHODS.include? method
  195. begin
  196. @cert, @chain = ssl_client(method) { |s| [s.peer_cert, s.peer_cert_chain] }
  197. Logger.debug { "Certificate #{@cert.subject}" }
  198. break
  199. rescue TLSException => e
  200. Logger.trace { "Method #{Tls.colorize method} not supported : #{e}" }
  201. end
  202. end
  203. raise TLSNotAvailableException unless @cert
  204. @cert_valid = ::OpenSSL::SSL.verify_certificate_identity @cert, @hostname
  205. @cert_trusted = verify_trust @chain, @cert
  206. end
  207. def prefered_cipher(method)
  208. cipher = ssl_client(method, 'ALL:COMPLEMENTOFALL') { |s| s.cipher }
  209. Logger.info { "Prefered cipher for #{Tls.colorize method} : #{Tls.colorize cipher.first}" }
  210. cipher
  211. rescue Exception
  212. Logger.debug { "Method #{Tls.colorize method} not supported" }
  213. nil
  214. end
  215. def fetch_prefered_ciphers
  216. Logger.info { '' }
  217. @prefered_ciphers = {}
  218. EXISTING_METHODS.each do |method|
  219. next unless SUPPORTED_METHODS.include? method
  220. @prefered_ciphers[method] = prefered_cipher method
  221. end
  222. raise TLSNotAvailableException.new unless @prefered_ciphers.any? { |_, c| !c.nil? }
  223. end
  224. def available_ciphers(method)
  225. context = ::OpenSSL::SSL::SSLContext.new method
  226. context.ciphers = 'ALL:COMPLEMENTOFALL'
  227. context.ciphers
  228. end
  229. def supported_cipher?(method, cipher)
  230. ssl_client method, [cipher]
  231. Logger.info { "#{Tls.colorize method} / #{Tls.colorize cipher[0]} : Supported" }
  232. true
  233. rescue TLSException => e
  234. Logger.debug { "#{Tls.colorize method} / #{Tls.colorize cipher[0]} : Not supported" }
  235. false
  236. end
  237. def check_supported_cipher
  238. Logger.info { '' }
  239. @supported_ciphers = {}
  240. EXISTING_METHODS.each do |method|
  241. next unless SUPPORTED_METHODS.include? method and @prefered_ciphers[method]
  242. ciphers = available_ciphers(method).select { |cipher| supported_cipher? method, cipher }
  243. @supported_ciphers[method] = ciphers
  244. Logger.info { '' } unless ciphers.empty?
  245. end
  246. end
  247. def verify_trust(chain, cert)
  248. store = ::OpenSSL::X509::Store.new
  249. store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
  250. store.set_default_paths
  251. %w(cacert).each do |directory|
  252. ::Dir.glob(::File.join '/usr/share/ca-certificates', directory, '*').each do |file|
  253. cert = ::OpenSSL::X509::Certificate.new ::File.read file
  254. begin
  255. store.add_cert cert
  256. rescue ::OpenSSL::X509::StoreError
  257. end
  258. end
  259. end
  260. chain.each do |cert|
  261. begin
  262. store.add_cert cert
  263. rescue ::OpenSSL::X509::StoreError
  264. end
  265. end
  266. trusted = store.verify cert
  267. p store.error_string unless trusted
  268. trusted
  269. end
  270. end
  271. class TcpServer < Server
  272. private
  273. def sock_type
  274. ::Socket::SOCK_STREAM
  275. end
  276. end
  277. class UdpServer < Server
  278. private
  279. def sock_type
  280. ::Socket::SOCK_DGRAM
  281. end
  282. end
  283. end
  284. end