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.

309 lines
8.6KB

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