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.

183 lines
4.4KB

  1. require 'colorize'
  2. require 'ipaddr'
  3. require 'timeout'
  4. require 'yaml'
  5. require 'cryptcheck/tls/fixture'
  6. module CryptCheck
  7. MAX_ANALYSIS_DURATION = 120
  8. PARALLEL_ANALYSIS = 10
  9. class AnalysisFailure
  10. attr_reader :error
  11. def initialize(error)
  12. @error = error
  13. end
  14. def to_s
  15. @error.to_s
  16. end
  17. end
  18. class NoTLSAvailableServer
  19. attr_reader :server
  20. def initialize(server)
  21. @server = OpenStruct.new hostname: server
  22. end
  23. def grade
  24. 'X'
  25. end
  26. end
  27. autoload :Status, 'cryptcheck/status'
  28. autoload :Logger, 'cryptcheck/logger'
  29. autoload :Tls, 'cryptcheck/tls'
  30. module Tls
  31. autoload :Method, 'cryptcheck/tls/method'
  32. autoload :Cipher, 'cryptcheck/tls/cipher'
  33. autoload :Curve, 'cryptcheck/tls/curve'
  34. autoload :Cert, 'cryptcheck/tls/cert'
  35. autoload :Server, 'cryptcheck/tls/server'
  36. autoload :TcpServer, 'cryptcheck/tls/server'
  37. autoload :UdpServer, 'cryptcheck/tls/server'
  38. autoload :Grade, 'cryptcheck/tls/grade'
  39. autoload :Https, 'cryptcheck/tls/https'
  40. module Https
  41. autoload :Server, 'cryptcheck/tls/https/server'
  42. autoload :Grade, 'cryptcheck/tls/https/grade'
  43. end
  44. autoload :Xmpp, 'cryptcheck/tls/xmpp'
  45. module Xmpp
  46. autoload :Server, 'cryptcheck/tls/xmpp/server'
  47. autoload :Grade, 'cryptcheck/tls/xmpp/grade'
  48. end
  49. autoload :Smtp, 'cryptcheck/tls/smtp'
  50. module Smtp
  51. autoload :Server, 'cryptcheck/tls/smtp/server'
  52. autoload :Grade, 'cryptcheck/tls/smtp/grade'
  53. end
  54. end
  55. autoload :Ssh, 'cryptcheck/ssh'
  56. module Ssh
  57. autoload :Packet, 'cryptcheck/ssh/packet'
  58. autoload :Server, 'cryptcheck/ssh/server'
  59. autoload :SshNotSupportedServer, 'cryptcheck/ssh/server'
  60. autoload :Grade, 'cryptcheck/ssh/grade'
  61. end
  62. private
  63. def self.addresses(host)
  64. begin
  65. ip = IPAddr.new host
  66. return [[ip.family, ip.to_s, nil]]
  67. rescue IPAddr::InvalidAddressError
  68. end
  69. ::Addrinfo.getaddrinfo(host, nil, nil, :STREAM)
  70. .collect { |a| [a.afamily, a.ip_address, host] }
  71. end
  72. def self.analyze_addresses(host, addresses, port, server, grade, *args, **kargs)
  73. first = true
  74. addresses.collect do |family, ip|
  75. first ? (first = false) : Logger.info { '' }
  76. key = [host, ip, port]
  77. a = [host, family, ip, port, *args]
  78. begin
  79. ::Timeout::timeout MAX_ANALYSIS_DURATION do
  80. s = if kargs.empty?
  81. server.new *a
  82. else
  83. server.new *a, **kargs
  84. end
  85. exit
  86. if grade
  87. g = grade.new s
  88. Logger.info { '' }
  89. g.display
  90. [key, g]
  91. else
  92. [key, s]
  93. end
  94. end
  95. rescue => e
  96. e = Tls::Server::TLSException.new "Too long analysis (max #{MAX_ANALYSIS_DURATION.humanize})" \
  97. if e.message == 'execution expired'
  98. raise unless e.is_a? Tls::Server::TLSException
  99. Logger.error e
  100. [key, AnalysisFailure.new(e)]
  101. end
  102. end.to_h
  103. end
  104. def self.analyze(host, port, server, grade, *args, **kargs)
  105. addresses = begin
  106. addresses host
  107. rescue ::SocketError => e
  108. Logger::error e
  109. key = [host, nil, port]
  110. error = AnalysisFailure.new "Unable to resolve #{host}"
  111. return { key => error }
  112. end
  113. analyze_addresses host, addresses, port, server, grade, *args, **kargs
  114. end
  115. def self.analyze_hosts(hosts, template, output, groups: nil, &block)
  116. results = {}
  117. semaphore = ::Mutex.new
  118. ::Parallel.each hosts, progress: 'Analysing', in_threads: PARALLEL_ANALYSIS, finish: lambda { |item, _, _| puts item[1] } do |description, host|
  119. #hosts.each do |description, host|
  120. result = block.call host.strip
  121. result = result.values.first
  122. result = NoTLSAvailableServer.new(host) if result.is_a? AnalysisFailure
  123. semaphore.synchronize do
  124. if results.include? description
  125. results[description] << result
  126. else
  127. results[description] = [result]
  128. end
  129. end
  130. end
  131. results = ::Hash[groups.collect { |g| [g, results[g]] }] if groups
  132. results.each do |d, _|
  133. results[d].sort! do |a, b|
  134. cmp = score(a) <=> score(b)
  135. if cmp == 0
  136. cmp = a.server.hostname <=> b.server.hostname
  137. end
  138. cmp
  139. end
  140. end
  141. ::File.write output, ::ERB.new(::File.read template).result(binding)
  142. end
  143. def self.analyze_file(input, template, output, &block)
  144. config = ::YAML.load_file input
  145. hosts = []
  146. groups = []
  147. config.each do |c|
  148. d, hs = c['description'], c['hostnames']
  149. groups << d
  150. hs.each { |host| hosts << [d, host] }
  151. end
  152. self.analyze_hosts hosts, template, output, groups: groups, &block
  153. end
  154. private
  155. SCORES = %w(A+ A B+ B C+ C D E F G M T X)
  156. def self.score(a)
  157. SCORES.index a.grade
  158. end
  159. end