@@ -0,0 +1 @@ | |||
runner |
@@ -0,0 +1,17 @@ | |||
#!/usr/bin/env ruby | |||
$:.unshift File.expand_path File.join File.dirname(__FILE__), '../lib' | |||
require 'rubygems' | |||
require 'bundler/setup' | |||
require 'cryptcheck' | |||
name, port, level = case ARGV.length | |||
when 1 then [ARGV[0], 22, :info] | |||
when 2 then [ARGV[0], ARGV[1], :info] | |||
when 3 then [ARGV[0], ARGV[1], ARGV[3]] | |||
end | |||
::CryptCheck::Logger.level = level | |||
server = ::CryptCheck::Ssh::Server.new name, port | |||
#grade = ::CryptCheck::Ssh::Grade.new server | |||
#::CryptCheck::Logger.info { '' } | |||
#grade.display |
@@ -35,4 +35,5 @@ Gem::Specification.new do |spec| | |||
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' | |||
spec.add_dependency 'net-ssh', '~> 2.9', '>= 2.9.2' | |||
end |
@@ -0,0 +1,142 @@ | |||
module CryptCheck | |||
module Tls | |||
class TlsNotSupportedGrade | |||
attr_reader :server, :score, :grade | |||
def initialize(server) | |||
@server, @score, @grade = server, -1, 'X' | |||
end | |||
end | |||
class Grade | |||
attr_reader :server, :protocol_score, :key_exchange_score, :cipher_strengths_score, :score, :grade, :error, :warning, :success | |||
def initialize(server) | |||
@server = server | |||
calculate_protocol_score | |||
calculate_key_exchange_score | |||
calculate_cipher_strengths_score | |||
@score = @protocol_score*0.3 + @key_exchange_score*0.3 + @cipher_strengths_score*0.4 | |||
calculate_error | |||
calculate_warning | |||
calculate_success | |||
calculate_grade | |||
end | |||
def display | |||
color = case self.grade | |||
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 }" } | |||
Logger.info { '' } | |||
Logger.info { "Protocole : #{self.protocol_score} / 100" } | |||
Logger.info { "Key exchange : #{self.key_exchange_score} / 100" } | |||
Logger.info { "Ciphers strength : #{self.cipher_strengths_score} / 100" } | |||
Logger.info { "Overall score : #{self.score} / 100" } | |||
Logger.info { '' } | |||
Logger.info { "Errors : #{self.error.join(' ').colorize :red }" } unless self.error.empty? | |||
Logger.info { "Warnings : #{self.warning.join(' ').colorize :yellow }" } unless self.warning.empty? | |||
Logger.info { "Best practices : #{self.success.join(' ').colorize :green }" } unless self.success.empty? | |||
end | |||
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' | |||
end | |||
@grade = [@grade, 'B'].max if !@server.tlsv1_2? or @server.key_size < 2048 | |||
@grade = [@grade, 'C'].max if @server.des3? | |||
@grade = [@grade, 'F'].max unless @error.empty? | |||
@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 | |||
@error = [] | |||
@error << :md5_sig if @server.md5_sig? | |||
@error << :sslv2 if @server.sslv2? | |||
@error << :sslv3 if @server.sslv3? | |||
@error << :md5 if @server.md5? | |||
@error << :anonymous if @server.anonymous? | |||
@error << :dss if @server.dss? | |||
@error << :null if @server.null? | |||
@error << :export if @server.export? | |||
@error << :des if @server.des? | |||
@error << :rc4 if @server.rc4? | |||
end | |||
def calculate_warning | |||
@warning = [] | |||
@warning << :sha1_sig if @server.sha1_sig? | |||
#@warning << :sha1 if @server.sha1? | |||
@warning << :des3 if @server.des3? | |||
end | |||
def calculate_success | |||
@success = [] | |||
@success << :pfs if @server.pfs_only? | |||
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 | |||
METHODS_SCORES = { SSLv2: 0, SSLv3: 20, TLSv1: 60, TLSv1_1: 80, TLSv1_2: 100 } | |||
def calculate_protocol_score | |||
@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 10 | |||
when 512...1024 then 20 | |||
when 1024...2048 then 50 | |||
when 2048...4096 then 90 | |||
else 100 | |||
end | |||
end | |||
def calculate_cipher_strengths_score | |||
@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 | |||
end |
@@ -0,0 +1,117 @@ | |||
require 'net/ssh' | |||
module CryptCheck | |||
module Ssh | |||
class SshNotSupportedServer | |||
attr_reader :hostname, :port | |||
def initialize(hostname, port) | |||
@hostname, @port = hostname, port | |||
end | |||
end | |||
class Server | |||
TCP_TIMEOUT = 10 | |||
class SshNotAvailableException < Exception | |||
end | |||
attr_reader :hostname, :port, :kex, :encryption, :hmac, :compression, :key | |||
KEX = { | |||
'curve25519-sha256@libssh.org' => :green, | |||
'diffie-hellman-group1-sha1' => :yellow, | |||
'diffie-hellman-group14-sha1' => :yellow, | |||
'diffie-hellman-group-exchange-sha1' => :yellow, | |||
'diffie-hellman-group-exchange-sha256' => :green, | |||
'ecdh-sha2-nistp256' => :yellow, | |||
'ecdh-sha2-nistp384' => :yellow, | |||
'ecdh-sha2-nistp521' => :yellow | |||
} | |||
ENCRYPTION = { | |||
'3des-cbc' => :red, | |||
'aes128-cbc' => :yellow, | |||
'aes192-cbc' => :yellow, | |||
'aes256-cbc' => :yellow, | |||
'aes128-ctr' => :yellow, | |||
'aes192-ctr' => :yellow, | |||
'aes256-ctr' => :yellow, | |||
'aes128-gcm@openssh.com' => :green, | |||
'aes256-gcm@openssh.com' => :green, | |||
'arcfour' => :red, | |||
'arcfour128' => :red, | |||
'arcfour256' => :red, | |||
'blowfish-cbc' => :yellow, | |||
'cast128-cbc' => nil, | |||
'chacha20-poly1305@openssh.com' => :green | |||
} | |||
HMAC = { | |||
'hmac-md5' => :red, | |||
'hmac-md5-96' => :red, | |||
'hmac-ripemd160' => :green, | |||
'hmac-sha1' => :yellow, | |||
'hmac-sha1-96' => :red, | |||
'hmac-sha2-256' => :green, | |||
'hmac-sha2-512' => :green, | |||
'umac-64@openssh.com' => :red, | |||
'umac-128@openssh.com' => nil, | |||
'hmac-md5-etm@openssh.com' => :red, | |||
'hmac-md5-96-etm@openssh.com' => :red, | |||
'hmac-ripemd160-etm@openssh.com' => :green, | |||
'hmac-sha1-etm@openssh.com' => :yellow, | |||
'hmac-sha1-96-etm@openssh.com' => :red, | |||
'hmac-sha2-256-etm@openssh.com' => :green, | |||
'hmac-sha2-512-etm@openssh.com' => :green, | |||
'umac-64-etm@openssh.com' => :red, | |||
'umac-128-etm@openssh.com' => nil | |||
} | |||
COMPRESSION = { | |||
'none' => nil, | |||
'zlib@openssh.com' => nil | |||
} | |||
KEY = { | |||
'ecdsa-sha2-nistp256-cert-v01@openssh.com' => :yellow, | |||
'ecdsa-sha2-nistp384-cert-v01@openssh.com' => :yellow, | |||
'ecdsa-sha2-nistp521-cert-v01@openssh.com' => :yellow, | |||
'ssh-ed25519-cert-v01@openssh.com' => :green, | |||
'ssh-rsa-cert-v01@openssh.com' => :yellow, | |||
'ssh-dss-cert-v01@openssh.com' => :red, | |||
'ssh-rsa-cert-v00@openssh.com' => :yellow, | |||
'ssh-dss-cert-v00@openssh.com' => :red, | |||
'ecdsa-sha2-nistp256' => :yellow, | |||
'ecdsa-sha2-nistp384' => :yellow, | |||
'ecdsa-sha2-nistp521' => :yellow, | |||
'ssh-ed25519' => :green, | |||
'ssh-rsa' => :yellow, | |||
'ssh-dss' => :red | |||
} | |||
def initialize(hostname, port) | |||
@hostname, @port = hostname, port | |||
Logger.info { "#{hostname}:#{port}".colorize :blue } | |||
session = ::Net::SSH::Transport::Session.new @hostname, port: @port, timeout: TCP_TIMEOUT | |||
data = session.algorithms.instance_variable_get :@server_data | |||
@kex, @encryption, @hmac, @compression, @key = data[:kex], data[:encryption_server], data[:hmac_server], data[:compression_server], data[:host_key] | |||
Logger.info { '' } | |||
@kex.each { |k| Logger.info { "Key exchange : #{k.colorize KEX[k]}" } } | |||
Logger.info { '' } | |||
@encryption.each { |e| Logger.info { "Encryption : #{e.colorize ENCRYPTION[e]}" } } | |||
Logger.info { '' } | |||
@hmac.each { |h| Logger.info { "HMAC : #{h.colorize HMAC[h]}" } } | |||
Logger.info { '' } | |||
@compression.each { |c| Logger.info { "Compression : #{c}" } } | |||
Logger.info { '' } | |||
@key.each { |k| Logger.info { "Key type : #{k.colorize KEY[k]}" } } | |||
session.close | |||
rescue => e | |||
Logger.debug { "SSH not supported : #{e}" } | |||
raise SshNotAvailableException, e | |||
end | |||
end | |||
end | |||
end |