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.

cryptcheck.rb 4.4KB

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