Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

308 Zeilen
8.6KB

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