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

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