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.

283 lines
5.7KB

  1. module CryptCheck
  2. module Tls
  3. class Cipher
  4. TYPES = {
  5. md5: %w(MD5),
  6. sha1: %w(SHA),
  7. sha256: %w(SHA256),
  8. sha384: %w(SHA384),
  9. poly1305: %w(POLY1305),
  10. psk: %w(PSK),
  11. srp: %w(SRP),
  12. anonymous: %w(ADH AECDH),
  13. dss: %w(DSS),
  14. rsa: %w(RSA),
  15. ecdsa: %w(ECDSA),
  16. dh: %w(DH ADH),
  17. ecdh: %w(ECDH AECDH),
  18. dhe: %w(DHE EDH ADH),
  19. ecdhe: %w(ECDHE AECDH),
  20. null: %w(NULL),
  21. export: %w(EXP),
  22. rc2: %w(RC2),
  23. rc4: %w(RC4),
  24. des: %w(DES-CBC),
  25. des3: %w(3DES DES-CBC3),
  26. aes: %w(AES(128|256) AES-(128|256)),
  27. camellia: %w(CAMELLIA(128|256)),
  28. seed: %w(SEED),
  29. idea: %w(IDEA),
  30. chacha20: %w(CHACHA20),
  31. #cbc: %w(CBC),
  32. gcm: %w(GCM),
  33. ccm: %w(CCM)
  34. }
  35. attr_reader :method, :name, :states, :status
  36. def initialize(method, name)
  37. @method, @name = method, name
  38. fetch_states
  39. end
  40. extend Enumerable
  41. def self.each(&block)
  42. SUPPORTED.each &block
  43. end
  44. def self.[](method)
  45. SUPPORTED[method]
  46. end
  47. TYPES.each do |name, ciphers|
  48. class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
  49. def self.#{name}?(cipher)
  50. #{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ cipher }
  51. end
  52. def #{name}?
  53. #{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ @name }
  54. end
  55. RUBY_EVAL
  56. end
  57. def self.cbc?(cipher)
  58. !aead? cipher
  59. end
  60. def cbc?
  61. !aead?
  62. end
  63. def self.aead?(cipher)
  64. gcm?(cipher) or ccm?(cipher)
  65. end
  66. def aead?
  67. gcm? or ccm?
  68. end
  69. def ssl?
  70. sslv2? or sslv3?
  71. end
  72. def tls?
  73. tlsv1? or tlsv1_1? or tlsv1_2?
  74. end
  75. def pfs?
  76. dhe? or ecdhe?
  77. end
  78. def ecc?
  79. ecdsa? or ecdhe? or ecdh?
  80. end
  81. def sweet32?
  82. size = self.block_size
  83. return false unless size # Not block encryption
  84. size <= 64
  85. end
  86. CHECKS = [
  87. [:psk, Proc.new { |s| s.psk? }, :critical],
  88. [:srp, Proc.new { |s| s.srp? }, :critical],
  89. [:dss, Proc.new { |s| s.dss? }, :critical],
  90. [:anonymous, Proc.new { |s| s.anonymous? }, :critical],
  91. [:null, Proc.new { |s| s.null? }, :critical],
  92. [:export, Proc.new { |s| s.export? }, :critical],
  93. [:des, Proc.new { |s| s.des? }, :critical],
  94. [:md5, Proc.new { |s| s.md5? }, :critical],
  95. [:rc4, Proc.new { |s| s.rc4? }, :error],
  96. [:sweet32, Proc.new { |s| s.sweet32? }, :error],
  97. #[:cbc, Proc.new { |s| s.cbc? }, :warning],
  98. [:dhe, Proc.new { |s| s.dhe? }, :warning],
  99. [:no_pfs, Proc.new { |s| not s.pfs? }, :warning],
  100. [:pfs, Proc.new { |s| s.pfs? }, :good],
  101. [:ecdhe, Proc.new { |s| s.ecdhe? }, :good],
  102. [:aead, Proc.new { |s| s.aead? }, :good],
  103. ]
  104. def fetch_states
  105. @states = Status.collect { |s| [s, []] }.to_h
  106. CHECKS.each do |name, check, status|
  107. result = check.call self
  108. @states[status ? status : result] << name if result
  109. end
  110. statuses = @states.reject { |_, v| v.empty? }.keys
  111. @status = Status[statuses]
  112. end
  113. def to_s(type = :long)
  114. case type
  115. when :long
  116. states = @states.collect { |k, vs| vs.collect { |v| v.to_s.colorize k } }.flatten.join ' '
  117. "#{@method} #{@name.colorize @status} [#{states}]"
  118. when :short
  119. @name.colorize @status
  120. end
  121. end
  122. PRIORITY = { good: 1, none: 2, warning: 3, error: 4, critical: 5 }
  123. def self.sort(ciphers)
  124. ciphers.sort do |a, b|
  125. error_a, error_b = PRIORITY[a.score], PRIORITY[b.score]
  126. compare = error_a <=> error_b
  127. next compare unless compare == 0
  128. size_a, size_b = a.size, b.size
  129. compare = size_b <=> size_a
  130. next compare unless compare == 0
  131. dh_a, dh_b = a.dh, b.dh
  132. next -1 if not dh_a and dh_b
  133. next 1 if dh_a and not dh_b
  134. next a.name <=> b.name if not dh_a and not dh_b
  135. compare = b.dh.size <=> a.dh.size
  136. next compare unless compare == 0
  137. a.name <=> b.name
  138. end
  139. end
  140. def self.list(cipher_suite = 'ALL:COMPLEMENTOFALL', method: :TLSv1_2)
  141. context = OpenSSL::SSL::SSLContext.new method
  142. context.ciphers = cipher_suite
  143. ciphers = context.ciphers.collect { |c| self.new method, c }
  144. self.sort ciphers
  145. end
  146. def kex
  147. case
  148. when ecdhe? || ecdh?
  149. :ecdh
  150. when dhe? || dh?
  151. :dh
  152. when dss?
  153. :dss
  154. else
  155. :rsa
  156. end
  157. end
  158. def auth
  159. case
  160. when ecdsa?
  161. :ecdsa
  162. when rsa?
  163. :rsa
  164. when dss?
  165. :dss
  166. when anonymous?
  167. nil
  168. else
  169. :rsa
  170. end
  171. end
  172. def encryption
  173. case
  174. when chacha20?
  175. :chacha20
  176. when aes?
  177. :aes
  178. when camellia?
  179. :camellia
  180. when seed?
  181. :seed
  182. when idea?
  183. :idea
  184. when des3?
  185. :'3des'
  186. when des?
  187. :des
  188. when rc4?
  189. :rc4
  190. when rc2?
  191. :rc2
  192. end
  193. end
  194. def mode
  195. case
  196. when gcm?
  197. :gcm
  198. when ccm?
  199. :ccm
  200. when rc4? || chacha20?
  201. nil
  202. else
  203. :cbc
  204. end
  205. end
  206. def block_size
  207. case self.encryption
  208. when :'3des', :idea, :rc2
  209. 64
  210. when :aes, :camellia, :seed
  211. 128
  212. else
  213. nil
  214. end
  215. end
  216. def hmac
  217. case
  218. when poly1305?
  219. [:poly1305, 128]
  220. when sha384?
  221. [:sha384, 384]
  222. when sha256?
  223. [:sha256, 256]
  224. when sha1?
  225. [:sha1, 160]
  226. when md5?
  227. [:md5, 128]
  228. end
  229. end
  230. def <=>(other)
  231. status = Status.compare self, other
  232. return status if status != 0
  233. @name <=> other.name
  234. end
  235. ALL = 'ALL:COMPLEMENTOFALL'
  236. SUPPORTED = Method.collect do |m|
  237. context = ::OpenSSL::SSL::SSLContext.new m.name
  238. context.ciphers = ALL
  239. [m, context.ciphers.collect { |c| Cipher.new m, c.first }.sort ]
  240. end.to_h.freeze
  241. end
  242. end
  243. end