ソースを参照

Refactoring for usage on RoR application

master
Nicolas Vinot 4年前
コミット
67b2654e22

+ 1
- 0
.gitignore ファイルの表示

@@ -1,4 +1,5 @@
*.iml
*.gem
Gemfile.lock
/.idea/
/html/

+ 1
- 20
Gemfile ファイルの表示

@@ -1,21 +1,2 @@
source 'https://rubygems.org'

gem 'rake'
gem 'httparty'
gem 'nokogiri'
gem 'net-ssh', '>= 2.9.2.beta'
gem 'net-scp'
gem 'tcp_timeout'
gem 'parallel'
gem 'ruby-progressbar'
gem 'logging'
#gem 'activerecord'
#gem 'sqlite3'
gem 'colorize'

group :test do
gem 'rspec'
gem 'webmock'
end

gem 'debase'
gemspec

+ 0
- 1
bin/check_https_alexa.rb ファイルの表示

@@ -2,7 +2,6 @@
$:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib'
require 'rubygems'
require 'bundler/setup'
require 'logging'
require 'cryptcheck'

GROUP_NAME = 'Top 100 Alexa'

+ 0
- 1
bin/check_smtp.rb ファイルの表示

@@ -2,7 +2,6 @@
$:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib'
require 'rubygems'
require 'bundler/setup'
require 'logging'
require 'cryptcheck'

name = ARGV[0]

+ 0
- 1
bin/check_xmpp.rb ファイルの表示

@@ -2,7 +2,6 @@
$:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib'
require 'rubygems'
require 'bundler/setup'
require 'logging'
require 'cryptcheck'

name = ARGV[0]

+ 38
- 0
cryptcheck.gemspec ファイルの表示

@@ -0,0 +1,38 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)

Gem::Specification.new do |spec|
spec.name = 'cryptcheck'
spec.version = '1.0.0'
spec.authors = ['Aeris']
spec.email = ['aeris+tls@imirhil.fr']

spec.summary = %q{Check best practices on crypto-stack implementation}
spec.description = %q{Verify if best practices are well implemented on current crypto-stack (TLS & SSH) protocol (HTTPS, SMTP, XMPP, SSH & VPN)}
spec.homepage = 'https://tls.imirhil.fr'
spec.license = 'AGPLv3+'

if spec.respond_to?(:metadata)
spec.metadata['allowed_push_host'] = 'TODO: Set to "http://mygemserver.com"'
else
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
end

spec.files = { '*.rb' => %w(lib) }
.collect_concat { |e, ds| ds.collect_concat { |d| Dir[File.join d, '**', e] } }
# spec.bindir = 'bin'
# spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
# spec.test_files = spec.files.grep(%r{^spec/})
spec.require_paths = %w(lib)

spec.add_development_dependency 'bundler', '~> 1.9', '>= 1.9.8'
spec.add_development_dependency 'rake', '~> 10.4', '>= 10.4.2'
spec.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'

spec.add_dependency 'httparty', '~> 0.13', '>= 0.13.3'
spec.add_dependency 'nokogiri', '~> 1.6', '>= 1.6.6'
spec.add_dependency 'parallel', '~> 1.3', '>= 1.3.4'
spec.add_dependency 'ruby-progressbar', '~> 1.7', '>= 1.7.1'
spec.add_dependency 'colorize', '~> 0.7', '>= 0.7.7'
end

+ 1
- 0
lib/cryptcheck.rb ファイルの表示

@@ -5,6 +5,7 @@ module CryptCheck
autoload :Logger, 'cryptcheck/logger'
autoload :Tls, 'cryptcheck/tls'
module Tls
autoload :Cipher, 'cryptcheck/tls/cipher'
autoload :Server, 'cryptcheck/tls/server'
autoload :TcpServer, 'cryptcheck/tls/server'
autoload :UdpServer, 'cryptcheck/tls/server'

+ 7
- 52
lib/cryptcheck/tls.rb ファイルの表示

@@ -1,5 +1,4 @@
require 'erb'
require 'logging'
require 'parallel'

module CryptCheck
@@ -7,33 +6,6 @@ module CryptCheck
MAX_ANALYSIS_DURATION = 600
PARALLEL_ANALYSIS = 10

TYPES = {
md5: %w(MD5),
sha1: %w(SHA),

psk: %w(PSK),
srp: %w(SRP),
anonymous: %w(ADH AECDH),

dss: %w(DSS),

null: %w(NULL),
export: %w(EXP),
des: %w(DES-CBC),
rc4: %w(RC4),
des3: %w(3DES DES-CBC3),

pfs: %w(DHE EDH ECDHE ECDH)
}

TYPES.each do |name, ciphers|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def self.#{name}?(cipher)
#{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ cipher }
end
RUBY_EVAL
end

def self.grade(hostname, port, server_class:, grade_class:)
timeout MAX_ANALYSIS_DURATION do
grade_class.new server_class.new hostname, port
@@ -89,20 +61,8 @@ module CryptCheck

def self.colorize(cipher)
colors = case
when /^SSL/ =~ cipher,
dss?(cipher),
anonymous?(cipher),
null?(cipher),
export?(cipher),
md5?(cipher),
des?(cipher),
rc4?(cipher)
{ color: :white, background: :red }
when des3?(cipher)
{ color: :yellow }
when :TLSv1_2 == cipher,
pfs?(cipher)
{ color: :green }
when /^SSL/ =~ cipher then { color: :white, background: :red }
when :TLSv1_2 == cipher then { color: :green }
end
cipher.to_s.colorize colors
end
@@ -110,18 +70,13 @@ module CryptCheck
def self.key_to_s(key)
size = key.rsa_equivalent_size
type_color = case key.type
when :ecc
{ color: :green }
when :dsa
{ color: :yellow }
when :ecc then { color: :green }
when :dsa then { color: :yellow }
end
size_color = case size
when 0...1024
{ color: :white, background: :red }
when 1024...2048
{ color: :yellow }
when 4096...::Float::INFINITY
{ color: :green }
when 0...1024 then { color: :white, background: :red }
when 1024...2048 then { color: :yellow }
when 4096...::Float::INFINITY then { color: :green }
end
"#{key.type.to_s.upcase.colorize type_color} #{key.size.to_s.colorize size_color} bits"
end

+ 69
- 0
lib/cryptcheck/tls/cipher.rb ファイルの表示

@@ -0,0 +1,69 @@
module CryptCheck
module Tls
class Cipher
TYPES = {
md5: %w(MD5),
sha1: %w(SHA),

psk: %w(PSK),
srp: %w(SRP),
anonymous: %w(ADH AECDH),

dss: %w(DSS),

null: %w(NULL),
export: %w(EXP),
des: %w(DES-CBC),
rc2: %w(RC2),
rc4: %w(RC4),
des3: %w(3DES DES-CBC3),

pfs: %w(DHE EDH ECDHE ECDH)
}

attr_reader :protocol, :name, :size, :dh

def initialize(protocol, cipher, dh)
@protocol, @dh = protocol, dh
@name, _, @size = cipher
end

TYPES.each do |name, ciphers|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def self.#{name}?(cipher)
#{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ cipher }
end
def #{name}?
#{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ @name }
end
RUBY_EVAL
end

def ssl?
sslv2? or sslv3?
end

def tls?
tlsv1? or tlsv1_1? or tlsv1_2?
end

def colorize
colors = case
when dss?,
anonymous?,
null?,
export?,
md5?,
des?,
rc4?
{ color: :white, background: :red }
when des3?
{ color: :yellow }
when pfs?
{ color: :green }
end
@name.colorize colors
end
end
end
end

+ 3
- 0
lib/cryptcheck/tls/fixture.rb ファイルの表示

@@ -1,3 +1,5 @@
require 'openssl'

class ::OpenSSL::PKey::EC
def type
:ecc
@@ -14,6 +16,7 @@ class ::OpenSSL::PKey::EC
when 256 then 3072
when 384 then 7680
when 521 then 15360
when 571 then 21000
end
end


+ 26
- 55
lib/cryptcheck/tls/grade.rb ファイルの表示

@@ -21,21 +21,15 @@ module CryptCheck
calculate_warning
calculate_success
calculate_grade
calculate_perfect
end

def display
color = case self.grade
when 'A+'
:blue
when 'A'
:green
when 'B', 'C'
:yellow
when 'E', 'F'
:red
when 'M', 'T'
{ color: :white, background: :red }
when 'A+' then :blue
when 'A' then :green
when 'B', 'C' then :yellow
when 'E', 'F' then :red
when 'M', 'T' then { color: :white, background: :red }
end

Logger.info { "Grade : #{self.grade.colorize color }" }
@@ -53,18 +47,12 @@ module CryptCheck
private
def calculate_grade
@grade = case @score
when 0...20 then
'F'
when 20...35 then
'E'
when 35...50 then
'D'
when 50...65 then
'C'
when 65...80 then
'B'
else
'A'
when 0...20 then 'F'
when 20...35 then 'E'
when 35...50 then 'D'
when 50...65 then 'C'
when 65...80 then 'B'
else 'A'
end

@grade = [@grade, 'B'].max if !@server.tlsv1_2? or @server.key_size < 2048
@@ -73,6 +61,8 @@ module CryptCheck

@grade = 'M' unless @server.cert_valid
@grade = 'T' unless @server.cert_trusted

@grade = 'A+' if @grade == 'A' and @error.empty? and @warning.empty? and (all_success & @success) == all_success
end

def calculate_error
@@ -108,63 +98,44 @@ module CryptCheck
end

ALL_ERROR = %i(md5_sig md5 anonymous dss null export des rc4)

def all_error
ALL_ERROR
end

ALL_WARNING = %i(sha1_sig des3)

def all_warning
ALL_WARNING
end

ALL_SUCCESS = %i(pfs)

def all_success
ALL_SUCCESS
end

def calculate_perfect
@grade = 'A+' if @grade == 'A' and @error.empty? and @warning.empty? and (ALL_SUCCESS & @success) == ALL_SUCCESS
end

METHODS_SCORES = { SSLv2: 0, SSLv3: 10, TLSv1: 50, TLSv1_1: 75, TLSv1_2: 100 }

METHODS_SCORES = { SSLv2: 0, SSLv3: 20, TLSv1: 60, TLSv1_1: 80, TLSv1_2: 100 }
def calculate_protocol_score
methods = @server.supported_methods
worst, best = methods.last, methods.first
@protocol_score = (METHODS_SCORES[worst] + METHODS_SCORES[best]) / 2
@protocol_score = @server.supported_protocols.collect { |p| METHODS_SCORES[p] }.min
end

def calculate_key_exchange_score
@key_exchange_score = case @server.key_size
when 0 then 0
when 0...512 then 20
when 512...1024 then 40
when 1024...2048 then 80
when 0...512 then 10
when 512...1024 then 20
when 1024...2048 then 50
when 2048...4096 then 90
when 4096...::Float::INFINITY then 100
else 100
end
end

def calculate_cipher_strength_score(cipher_strength)
case cipher_strength
when 0 then
0
when 0...128 then
20
when 128...256 then
80
else
100
end
end

def calculate_cipher_strengths_score
strength = @server.cipher_size
worst, best = strength[:min], strength[:max]
@cipher_strengths_score = (calculate_cipher_strength_score(worst) + calculate_cipher_strength_score(best)) / 2
@cipher_strengths_score = case @server.cipher_size
when 0 then 0
when 0...112 then 10
when 112...128 then 50
when 128...256 then 90
else 100
end
end
end
end

+ 10
- 7
lib/cryptcheck/tls/https/server.rb ファイルの表示

@@ -14,14 +14,17 @@ module CryptCheck
def fetch_hsts
port = @port == 443 ? '' : ":#{@port}"

response = ::HTTParty.head "https://#{@hostname}#{port}/", { follow_redirects: false, verify: false, timeout: SSL_TIMEOUT }
if header = response.headers['strict-transport-security']
name, value = header.split '='
if name == 'max-age'
@hsts = value.to_i
Logger.info { "HSTS : #{@hsts.to_s.colorize hsts_long? ? :green : nil}" }
return
begin
response = ::HTTParty.head "https://#{@hostname}#{port}/", { follow_redirects: false, verify: false, timeout: SSL_TIMEOUT }
if header = response.headers['strict-transport-security']
name, value = header.split '='
if name == 'max-age'
@hsts = value.to_i
Logger.info { "HSTS : #{@hsts.to_s.colorize hsts_long? ? :green : nil}" }
return
end
end
rescue ::Net::OpenTimeout
end

Logger.info { 'No HSTS'.colorize :yellow }

+ 46
- 25
lib/cryptcheck/tls/server.rb ファイルの表示

@@ -32,7 +32,7 @@ module CryptCheck
class ConnectionError < TLSException
end

attr_reader :hostname, :port, :prefered_ciphers, :cert, :cert_valid, :cert_trusted
attr_reader :hostname, :port, :prefered_ciphers, :cert, :cert_valid, :cert_trusted, :dh

def initialize(hostname, port)
@hostname, @port = hostname, port
@@ -40,19 +40,30 @@ module CryptCheck
Logger.info { "#{hostname}:#{port}".colorize :blue }
extract_cert
Logger.info { '' }
Logger.info { "Key : #{Tls.key_to_s @cert.public_key}" }
Logger.info { "Key : #{Tls.key_to_s self.key}" }
fetch_prefered_ciphers
check_supported_cipher
uniq_dh
end

def supported_methods
EXISTING_METHODS.select { |m| !@prefered_ciphers[m].nil? }
def key
@cert.public_key
end

def cipher_size
cipher_strengths = supported_ciphers.collect { |c| c[2] }.uniq.sort
worst, best = cipher_strengths.first, cipher_strengths.last
{ worst: worst, best: best }
supported_ciphers.collect { |c| c.size }.sort.last
end

def supported_protocols
@supported_ciphers.keys
end

def supported_ciphers
@supported_ciphers.values.flatten 1
end

def supported_ciphers_by_protocol(protocol)
@supported_ciphers[protocol]
end

EXISTING_METHODS.each do |method|
@@ -75,10 +86,10 @@ module CryptCheck
RUBY_EVAL
end

Tls::TYPES.each do |type, _|
Cipher::TYPES.each do |type, _|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{type}?
supported_ciphers.any? { |s| Tls.#{type}? s.first }
supported_ciphers.any? { |c| c.#{type}? }
end
RUBY_EVAL
end
@@ -100,15 +111,11 @@ module CryptCheck
end

def pfs?
supported_ciphers.any? { |c| Tls.pfs? c.first }
supported_ciphers.any? { |c| c.pfs? }
end

def pfs_only?
supported_ciphers.all? { |c| Tls.pfs? c.first }
end

def supported_ciphers
@supported_ciphers.values.flatten(1).uniq
supported_ciphers.all? { |c| c.pfs? }
end

private
@@ -221,8 +228,8 @@ module CryptCheck
end

def prefered_cipher(method)
cipher = ssl_client(method, 'ALL:COMPLEMENTOFALL') { |s| s.cipher }
Logger.info { "Prefered cipher for #{Tls.colorize method} : #{Tls.colorize cipher.first}" }
cipher = ssl_client(method, 'ALL:COMPLEMENTOFALL') { |s| Cipher.new method, s.cipher, s.tmp_key }
Logger.info { "Prefered cipher for #{Tls.colorize method} : #{cipher.colorize}" }
cipher
rescue TLSException => e
Logger.debug { "Method #{Tls.colorize method} not supported : #{e}" }
@@ -246,12 +253,14 @@ module CryptCheck

def supported_cipher?(method, cipher)
dh = ssl_client method, [cipher] { |s| s.tmp_key }
@dh << dh if dh
cipher = Cipher.new method, cipher, dh
dh = dh ? " (#{'DH'.colorize :green} : #{Tls.key_to_s dh})" : ''
Logger.info { "#{Tls.colorize method} / #{Tls.colorize cipher[0]} : Supported#{dh}" }
true
Logger.info { "#{Tls.colorize method} / #{cipher.colorize} : Supported#{dh}" }
cipher
rescue TLSException => e
Logger.debug { "#{Tls.colorize method} / #{Tls.colorize cipher[0]} : Not supported (#{e})" }
false
Logger.debug { "#{Tls.colorize method} / #{cipher.colorize} : Not supported (#{e})" }
nil
end

def check_supported_cipher
@@ -259,9 +268,9 @@ module CryptCheck
@supported_ciphers = {}
EXISTING_METHODS.each do |method|
next unless SUPPORTED_METHODS.include? method and @prefered_ciphers[method]
ciphers = available_ciphers(method).select { |cipher| supported_cipher? method, cipher }
@supported_ciphers[method] = ciphers
Logger.info { '' } unless ciphers.empty?
supported_ciphers = available_ciphers(method).collect { |c| supported_cipher? method, c }.reject { |c| c.nil? }
Logger.info { '' } unless supported_ciphers.empty?
@supported_ciphers[method] = supported_ciphers
end
end

@@ -270,7 +279,7 @@ module CryptCheck
store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT
store.set_default_paths

%w(cacert).each do |directory|
%w(cacert mozilla).each do |directory|
::Dir.glob(::File.join '/usr/share/ca-certificates', directory, '*').each do |file|
cert = ::OpenSSL::X509::Certificate.new ::File.read file
begin
@@ -289,6 +298,18 @@ module CryptCheck
p store.error_string unless trusted
trusted
end

def uniq_dh
dh, find = [], []
@dh.each do |k|
f = [k.type, k.size]
unless find.include? f
dh << k
find << f
end
end
@dh = dh
end
end

class TcpServer < Server

+ 0
- 2
lib/cryptcheck/tls/xmpp.rb ファイルの表示

@@ -1,5 +1,4 @@
require 'erb'
require 'logging'
require 'parallel'

module CryptCheck
@@ -7,7 +6,6 @@ module CryptCheck
module Xmpp
MAX_ANALYSIS_DURATION = 600
PARALLEL_ANALYSIS = 10
Logger = ::Logging.logger[Xmpp]

def self.grade(hostname, type=:s2s)
timeout MAX_ANALYSIS_DURATION do

+ 7
- 8
output/https.erb ファイルの表示

@@ -97,10 +97,10 @@
<%= n.grade %>
</td>

<td class="<%= s.key_size < 2048 ? :danger : s.key_size < 4096 ? :warning : :success %>">
<% type, size = s.key %>
<%= "#{size} (#{type.to_s.upcase})" %>
<span class="sr-only">(<%= s.key_size < 2048 ? '☹' : '☺' %>)</span>
<% key = s.key %>
<td class="<%= key.rsa_equivalent_size < 2048 ? :danger : key.rsa_equivalent_size < 4096 ? :warning : :success %>">
<%= "#{key.size} (#{key.type.to_s.upcase})" %>
<span class="sr-only">(<%= key.size < 2048 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sha1_sig? ? :warning : :success %>">
<%= s.sha1_sig? ? '✓' : '✗' %>
@@ -124,10 +124,9 @@
<span class="sr-only">(<%= s.tls? ? '☺' : '☹' %>)</span>
</td>

<% cipher_size = s.cipher_size[:worst] %>
<td class="<%= cipher_size < 112 ? :danger : cipher_size < 128 ? :warning : :success %>">
<%= cipher_size %>
<span class="sr-only">(<%= cipher_size < 128 ? '☹' : '☺' %>)</span>
<td class="<%= s.cipher_size < 112 ? :danger : s.cipher_size < 128 ? :warning : :success %>">
<%= s.cipher_size %>
<span class="sr-only">(<%= s.cipher_size < 128 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.md5? ? :critical : :success %>">
<%= s.md5? ? '✓' : '✗' %>

+ 1
- 0
output/porn.yml ファイルの表示

@@ -623,6 +623,7 @@
- www.xartmodels.com
- www.yourdirtymind.com
- your-daily-girl.com
- blog.onahole.eu
- description: Erotic Stories
hostnames:
- literotica.com

+ 7
- 8
output/smtp.erb ファイルの表示

@@ -97,10 +97,10 @@
<%= n.grade %>
</td>

<td class="<%= s.key_size < 2048 ? :danger : s.key_size < 4096 ? :warning : :success %>">
<% type, size = s.key %>
<%= "#{size} (#{type.to_s.upcase})" %>
<span class="sr-only">(<%= s.key_size < 2048 ? '☹' : '☺' %>)</span>
<% key = s.key %>
<td class="<%= key.rsa_equivalent_size < 2048 ? :danger : key.rsa_equivalent_size < 4096 ? :warning : :success %>">
<%= "#{key.size} (#{key.type.to_s.upcase})" %>
<span class="sr-only">(<%= key.size < 2048 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sha1_sig? ? :warning : :success %>">
<%= s.sha1_sig? ? '✓' : '✗' %>
@@ -124,10 +124,9 @@
<span class="sr-only">(<%= s.tls? ? '☺' : '☹' %>)</span>
</td>

<% cipher_size = s.cipher_size[:worst] %>
<td class="<%= cipher_size < 112 ? :danger : cipher_size < 128 ? :warning : :success %>">
<%= cipher_size %>
<span class="sr-only">(<%= cipher_size < 128 ? '☹' : '☺' %>)</span>
<td class="<%= s.cipher_size < 112 ? :danger : s.cipher_size < 128 ? :warning : :success %>">
<%= s.cipher_size %>
<span class="sr-only">(<%= s.cipher_size < 128 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.md5? ? :critical : :success %>">
<%= s.md5? ? '✓' : '✗' %>

+ 7
- 8
output/xmpp.erb ファイルの表示

@@ -110,10 +110,10 @@
<%= n.grade %>
</td>

<td class="<%= s.key_size < 2048 ? :danger : s.key_size < 4096 ? :warning : :success %>">
<% type, size = s.key %>
<%= "#{size} (#{type.to_s.upcase})" %>
<span class="sr-only">(<%= s.key_size < 2048 ? '☹' : '☺' %>)</span>
<% key = s.key %>
<td class="<%= key.rsa_equivalent_size < 2048 ? :danger : key.rsa_equivalent_size < 4096 ? :warning : :success %>">
<%= "#{key.size} (#{key.type.to_s.upcase})" %>
<span class="sr-only">(<%= key.size < 2048 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.sha1_sig? ? :warning : :success %>">
<%= s.sha1_sig? ? '✓' : '✗' %>
@@ -137,10 +137,9 @@
<span class="sr-only">(<%= s.tls? ? '☺' : '☹' %>)</span>
</td>

<% cipher_size = s.cipher_size[:worst] %>
<td class="<%= cipher_size < 112 ? :danger : cipher_size < 128 ? :warning : :success %>">
<%= cipher_size %>
<span class="sr-only">(<%= cipher_size < 128 ? '☹' : '☺' %>)</span>
<td class="<%= s.cipher_size < 112 ? :danger : s.cipher_size < 128 ? :warning : :success %>">
<%= s.cipher_size %>
<span class="sr-only">(<%= s.cipher_size < 128 ? '☹' : '☺' %>)</span>
</td>
<td class="<%= s.md5? ? :critical : :success %>">
<%= s.md5? ? '✓' : '✗' %>

読み込み中…
キャンセル
保存