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.

tls.rb 2.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. require 'erb'
  2. require 'logging'
  3. require 'parallel'
  4. module CryptCheck
  5. module Tls
  6. MAX_ANALYSIS_DURATION = 600
  7. PARALLEL_ANALYSIS = 10
  8. TYPES = {
  9. md5: %w(MD5),
  10. sha1: %w(SHA),
  11. psk: %w(PSK),
  12. srp: %w(SRP),
  13. anonymous: %w(ADH AECDH),
  14. dss: %w(DSS),
  15. null: %w(NULL),
  16. export: %w(EXP),
  17. des: %w(DES-CBC),
  18. rc4: %w(RC4),
  19. des3: %w(3DES DES-CBC3),
  20. pfs: %w(DHE EDH ECDHE ECDH)
  21. }
  22. TYPES.each do |name, ciphers|
  23. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  24. def self.#{name}?(cipher)
  25. #{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ cipher }
  26. end
  27. RUBY_EVAL
  28. end
  29. def self.grade(hostname, port, server_class:, grade_class:)
  30. timeout MAX_ANALYSIS_DURATION do
  31. grade_class.new server_class.new hostname, port
  32. end
  33. rescue ::Exception => e
  34. @Logger.error { "Error during #{hostname}:#{port} analysis : #{e}" }
  35. TlsNotSupportedGrade.new TlsNotSupportedServer.new hostname, port
  36. end
  37. def self.analyze(hosts, template, output, groups = nil, port:, server_class:, grade_class:)
  38. results = {}
  39. semaphore = ::Mutex.new
  40. ::Parallel.each hosts, progress: 'Analysing', in_threads: PARALLEL_ANALYSIS, finish: lambda { |item, _, _| puts item[1] } do |description, host|
  41. result = grade host.strip, port, server_class: server_class, grade_class: grade_class
  42. semaphore.synchronize do
  43. if results.include? description
  44. results[description] << result
  45. else
  46. results[description] = [result]
  47. end
  48. end
  49. end
  50. results = ::Hash[groups.collect { |g| [g, results[g]] }] if groups
  51. results.each do |d, _|
  52. results[d].sort! do |a, b|
  53. cmp = score(a) <=> score(b)
  54. if cmp == 0
  55. cmp = b.score <=> a.score
  56. if cmp == 0
  57. cmp = a.server.hostname <=> b.server.hostname
  58. end
  59. end
  60. cmp
  61. end
  62. end
  63. ::File.write output, ::ERB.new(::File.read(template)).result(binding)
  64. end
  65. def self.analyze_from_file(file, template, output, port:, server_class:, grade_class:)
  66. config = ::YAML.load_file file
  67. hosts = []
  68. groups = []
  69. config.each do |c|
  70. d, hs = c['description'], c['hostnames']
  71. groups << d
  72. hs.each { |host| hosts << [d, host] }
  73. end
  74. self.analyze hosts, template, output, groups, port: port, server_class: server_class, grade_class: grade_class
  75. end
  76. def self.colorize(cipher)
  77. colors = case
  78. when /^SSL/ =~ cipher,
  79. dss?(cipher),
  80. anonymous?(cipher),
  81. null?(cipher),
  82. export?(cipher),
  83. md5?(cipher),
  84. des?(cipher),
  85. rc4?(cipher)
  86. { color: :white, background: :red }
  87. when des3?(cipher)
  88. { color: :yellow }
  89. when :TLSv1_2 == cipher,
  90. pfs?(cipher)
  91. { color: :green }
  92. end
  93. cipher.to_s.colorize colors
  94. end
  95. private
  96. SCORES = %w(A+ A A- B C D E F T M X)
  97. def self.score(a)
  98. SCORES.index a.grade
  99. end
  100. end
  101. end