|
|
|
module CryptCheck
|
|
|
|
module Tls
|
|
|
|
class Cert
|
|
|
|
DEFAULT_CA_DIRECTORIES = [
|
|
|
|
'/usr/share/ca-certificates/mozilla'
|
|
|
|
]
|
|
|
|
|
|
|
|
SIGNATURE_ALGORITHMS = %i(md2 mdc2 md4 md5 ripemd160 sha sha1 sha2 rsa dss ecc ghost).freeze
|
|
|
|
SIGNATURE_ALGORITHMS_X509 = {
|
|
|
|
'dsaWithSHA' => %i(sha1 dss),
|
|
|
|
'dsaWithSHA1' => %i(sha1 dss),
|
|
|
|
'dsaWithSHA1_2' => %i(sha1 dss),
|
|
|
|
'dsa_with_SHA224' => %i(sha2 dss),
|
|
|
|
'dsa_with_SHA256' => %i(sha2 dss),
|
|
|
|
|
|
|
|
'mdc2WithRSA' => %i(mdc2 rsa),
|
|
|
|
|
|
|
|
'md2WithRSAEncryption' => %i(md2 rsa),
|
|
|
|
|
|
|
|
'md4WithRSAEncryption' => %i(md4, rsa),
|
|
|
|
|
|
|
|
'md5WithRSA' => %i(md5 rsa),
|
|
|
|
'md5WithRSAEncryption' => %i(md5 rsa),
|
|
|
|
|
|
|
|
'shaWithRSAEncryption' => %i(sha rsa),
|
|
|
|
'sha1WithRSA' => %i(sha1 rsa),
|
|
|
|
'sha1WithRSAEncryption' => %i(sha1 rsa),
|
|
|
|
'sha224WithRSAEncryption' => %i(sha2 rsa),
|
|
|
|
'sha256WithRSAEncryption' => %i(sha2 rsa),
|
|
|
|
'sha384WithRSAEncryption' => %i(sha2 rsa),
|
|
|
|
'sha512WithRSAEncryption' => %i(sha2 rsa),
|
|
|
|
|
|
|
|
'ripemd160WithRSA' => %i(ripemd160 rsa),
|
|
|
|
|
|
|
|
'ecdsa-with-SHA1' => %i(sha1 ecc),
|
|
|
|
'ecdsa-with-SHA224' => %i(sha2 ecc),
|
|
|
|
'ecdsa-with-SHA256' => %i(sha2 ecc),
|
|
|
|
'ecdsa-with-SHA384' => %i(sha2 ecc),
|
|
|
|
'ecdsa-with-SHA512' => %i(sha2 ecc),
|
|
|
|
|
|
|
|
'id_GostR3411_94_with_GostR3410_2001' => %i(ghost),
|
|
|
|
'id_GostR3411_94_with_GostR3410_94' => %i(ghost),
|
|
|
|
'id_GostR3411_94_with_GostR3410_94_cc' => %i(ghost),
|
|
|
|
'id_GostR3411_94_with_GostR3410_2001_cc' => %i(ghost)
|
|
|
|
}.freeze
|
|
|
|
WEAK_SIGN = {
|
|
|
|
critical: %i(mdc2 md2 md4 md5 sha sha1)
|
|
|
|
}.freeze
|
|
|
|
|
|
|
|
SIGNATURE_ALGORITHMS.each do |name|
|
|
|
|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
|
|
|
def #{name}?
|
|
|
|
SIGNATURE_ALGORITHMS_X509[@cert.signature_algorithm].include? :#{name}
|
|
|
|
end
|
|
|
|
RUBY_EVAL
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(cert, chain=[])
|
|
|
|
@cert, @chain = case cert
|
|
|
|
when ::OpenSSL::X509::Certificate
|
|
|
|
[cert, chain]
|
|
|
|
when ::OpenSSL::SSL::SSLSocket
|
|
|
|
[cert.peer_cert, cert.peer_cert_chain]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.trusted?(cert, chain, roots: DEFAULT_CA_DIRECTORIES)
|
|
|
|
store = ::OpenSSL::X509::Store.new
|
|
|
|
store.purpose = ::OpenSSL::X509::PURPOSE_SSL_SERVER
|
|
|
|
store.add_chains roots
|
|
|
|
chain.each do |cert|
|
|
|
|
# Never add other self signed certificates than system CA !
|
|
|
|
next if cert.subject == cert.issuer
|
|
|
|
store.add_cert cert rescue nil
|
|
|
|
end if chain
|
|
|
|
|
|
|
|
trusted = store.verify cert
|
|
|
|
return :trusted if trusted
|
|
|
|
store.error_string
|
|
|
|
end
|
|
|
|
|
|
|
|
def trusted?(roots: DEFAULT_CA_DIRECTORIES)
|
|
|
|
Cert.trusted? @cert, @chain, roots: roots
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid?(host)
|
|
|
|
::OpenSSL::SSL.verify_certificate_identity @cert, host
|
|
|
|
end
|
|
|
|
|
|
|
|
def fingerprint
|
|
|
|
@cert.fingerprint
|
|
|
|
end
|
|
|
|
|
|
|
|
def key
|
|
|
|
@cert.public_key
|
|
|
|
end
|
|
|
|
|
|
|
|
def subject
|
|
|
|
@cert.subject
|
|
|
|
end
|
|
|
|
|
|
|
|
def serial
|
|
|
|
@cert.serial
|
|
|
|
end
|
|
|
|
|
|
|
|
def issuer
|
|
|
|
@cert.issuer
|
|
|
|
end
|
|
|
|
|
|
|
|
def lifetime
|
|
|
|
{ not_before: @cert.not_before, not_after: @cert.not_after }
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_h
|
|
|
|
{
|
|
|
|
subject: self.subject.to_s,
|
|
|
|
serial: self.serial.to_s,
|
|
|
|
issuer: self.issuer.to_s,
|
|
|
|
lifetime: self.lifetime,
|
|
|
|
fingerprint: self.fingerprint,
|
|
|
|
chain: @chain.collect do |cert|
|
|
|
|
{
|
|
|
|
subject: cert.subject.to_s,
|
|
|
|
serial: cert.serial.to_s,
|
|
|
|
issuer: cert.issuer.to_s,
|
|
|
|
fingerprint: cert.fingerprint,
|
|
|
|
lifetime: { not_before: cert.not_before, not_after: cert.not_after }
|
|
|
|
}
|
|
|
|
end,
|
|
|
|
key: self.key.to_h,
|
|
|
|
states: self.states
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
protected
|
|
|
|
include State
|
|
|
|
|
|
|
|
CHECKS = WEAK_SIGN.collect do |level, hashes|
|
|
|
|
hashes.collect do |hash|
|
|
|
|
["#{hash}_sign".to_sym, level, -> (s) { s.send "#{hash}?" }]
|
|
|
|
end
|
|
|
|
end.flatten(1).freeze
|
|
|
|
|
|
|
|
def available_checks
|
|
|
|
CHECKS
|
|
|
|
end
|
|
|
|
|
|
|
|
def children
|
|
|
|
[self.key]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|