選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

server.rb 8.4KB

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