25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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