Detect ECC curves

aeris 7 months ago
parent da0d83357d
commit fc63f58e0c
  1. 94
      lib/cryptcheck/tls/curve.rb
  2. 204
      lib/cryptcheck/tls/engine.rb
  3. 2
      lib/fixtures/01_openssl/ec.rb

@ -1,52 +1,54 @@
module CryptCheck
module Tls
class Curve
attr_reader :name
module Tls
class Curve
attr_reader :name
def initialize(name)
name = name.to_sym if name.is_a? String
@name = name
end
def initialize(name)
name = name.to_sym if name.is_a? String
@name = name
end
SUPPORTED = %i(secp256k1 sect283k1 sect283r1 secp384r1
SUPPORTED = (%i(secp256k1 sect283k1 sect283r1 secp384r1
sect409k1 sect409r1 secp521r1 sect571k1 sect571r1
prime192v1 prime256v1
brainpoolP256r1 brainpoolP384r1 brainpoolP512r1 x25519).collect { |c| self.new c }.freeze
extend Enumerable
def self.each(&block)
SUPPORTED.each &block
end
def to_s
@name
end
def to_h
{ name: @name, states: self.states }
end
def ==(other)
case other
when String
@name == other.to_sym
when Symbol
@name == other
else
@name == other.name
end
end
protected
include State
CHECKS = [].freeze
protected
def available_checks
CHECKS
end
end
end
brainpoolP256r1 brainpoolP384r1 brainpoolP512r1 x25519) & OpenSSL::PKey::EC::builtin_curves.collect { |c| c.first.to_sym }).collect { |c| self.new c }.freeze
extend Enumerable
def self.each(&block)
SUPPORTED.each &block
end
def to_s
@name
end
def to_h
{ name: @name, states: self.states }
end
def ==(other)
case other
when String
@name == other.to_sym
when Symbol
@name == other
else
@name == other.name
end
end
protected
include State
CHECKS = [].freeze
protected
def available_checks
CHECKS
end
end
end
end

@ -62,7 +62,6 @@ module CryptCheck
fetch_supported_ciphers
fetch_dh
fetch_ciphers_preferences
fetch_ecdsa_certs
fetch_supported_curves
fetch_curves_preference
@ -150,133 +149,99 @@ module CryptCheck
end.flatten.uniq &:fingerprint
end
def fetch_ecdsa_certs
@ecdsa_certs = {}
@supported_ciphers.each do |method, ciphers|
ecdsa = ciphers.keys.detect &:ecdsa?
next unless ecdsa
ecdsa_curve = Curve.new ciphers[ecdsa].tmp_key.curve
@ecdsa_certs = Curve.collect do |curve|
begin
connection = ssl_client method, ecdsa, curves: [curve, ecdsa_curve]
[curve, connection]
rescue TLSException
nil
end
end.compact.to_h
break
end
end
def fetch_supported_curves
Logger.info { '' }
Logger.info { 'Supported elliptic curves' }
@supported_curves = []
ecdsa_curve = @ecdsa_certs.keys.first
if ecdsa_curve
# If we have an ECDSA cipher, we need at least the certificate curve to do handshake,
# but with lowest priority to check for ECHDE and not just ECDSA
@supported_ciphers.each do |method, ciphers|
ecdsa = ciphers.keys.detect &:ecdsa?
next unless ecdsa
@supported_curves = Curve.select do |curve|
if curve == ecdsa_curve
# ECDSA curve is always supported
Logger.info { " ECC curve #{curve.name}" }
next true
end
begin
connection = ssl_client method, ecdsa, curves: [curve, ecdsa_curve]
# Not too fast !!!
# Handshake will **always** succeed, because ECDSA
# curve is always supported.
# So, we need to test for the real curve!
# Treaky case : if server preference is enforced,
# ECDSA curve can be prefered over ECDHE one and so
# really supported curve can be detected as not supported :(
dh = connection.tmp_key
negociated_curve = dh.curve
supported = ecdsa_curve != negociated_curve
if supported
Logger.info { " ECC curve #{curve.name}" }
else
Logger.debug { " ECC curve #{curve.name} : not supported" }
end
supported
rescue TLSException
false
end
end
break
end
else
# If we have no ECDSA ciphers, ECC supported are only ECDH ones
# So peak an ECDH cipher and test all curves
@supported_ciphers.each do |method, ciphers|
ecdh = ciphers.keys.detect { |c| c.ecdh? or c.ecdhe? }
next unless ecdh
@supported_curves = Curve.select do |curve|
begin
ssl_client method, ecdh, curves: curve
Logger.info { " ECC curve #{curve.name}" }
true
rescue TLSException
Logger.debug { " ECC curve #{curve.name} : not supported" }
false
end
end
break
ecdsa = @supported_ciphers.find do |method, ciphers|
cipher, connection = ciphers.find { |c, _| c.ecdsa? }
break [method, cipher, connection] if cipher
end
ecdh = @supported_ciphers.find do |method, ciphers|
cipher, connection = ciphers.find { |c, _| c.ecdh? or c.ecdhe? }
break [method, cipher, connection] if cipher
end
cipher, curves = if ecdsa
# If we have an ECDSA cipher, we need at least the
# certificate curve to do handshake, but with lowest
# priority to check for ECHDE and not just ECDSA
_, _, connection = ecdsa
key = connection.peer_cert.public_key
ecdsa_curve = Curve.new key.group.curve_name
curves = Curve.collect { |c| [c, ecdsa_curve] }
[ecdsa, curves]
else
# If we have no ECDSA ciphers, ECC supported are
# only ECDH ones, so peak an ECDH cipher and test
# all curves
curves = Curve.collect { |c| [c] }
[ecdh, curves]
end
method, cipher, _ = cipher
supported_curves = curves.collect do |curve|
begin
ssl_client method, cipher, curves: curve
connection = ssl_client method, cipher, curves: curve
connection.tmp_key.curve
rescue TLSException
nil
end
end.compact.uniq
@supported_curves = supported_curves.collect do |curve|
Logger.info { " ECC curve #{curve}" }
Curve.new curve
end
end
def fetch_curves_preference
@curves_preference = if @supported_curves.size < 2
Logger.info { 'Curves preference : ' + 'not applicable'.colorize(:unknown) }
nil
else
method, cipher = @supported_ciphers.collect do |method, ciphers|
cipher = ciphers.keys.detect { |c| c.ecdh? or c.ecdhe? }
[method, cipher]
end.detect { |n| !n.nil? }
a, b, _ = @supported_curves
ab, ba = [a, b], [b, a]
if cipher.ecdsa?
# In case of ECDSA, add the cert key at the end
# Or no negociation possible
ecdsa_curve = @ecdsa_certs.keys.first
ab << ecdsa_curve
ba << ecdsa_curve
end
ab = ssl_client(method, cipher, curves: ab).tmp_key.curve
ba = ssl_client(method, cipher, curves: ba).tmp_key.curve
if ab != ba
Logger.info { 'Curves preference : ' + 'client preference'.colorize(:warning) }
:client
else
sort = lambda do |a, b|
curves = [a, b]
if cipher.ecdsa?
# In case of ECDSA, add the cert key at the end
# Or no negociation possible
curves << ecdsa_curve
end
connection = ssl_client method, cipher, curves: curves
curve = connection.tmp_key.curve
a == curve ? -1 : 1
end
preferences = @supported_curves.sort &sort
Logger.info { 'Curves preference : ' + preferences.collect { |c| c.name }.join(', ') }
preferences
end
end
@curves_preference = nil
if @supported_curves.size < 2
Logger.info { 'Curves preference : ' + 'not applicable'.colorize(:unknown) }
return
end
method, cipher, connection = @supported_ciphers.find do |method, ciphers|
cipher, connection = ciphers.find { |c, _| c.ecdh? or c.ecdhe? }
break [method, cipher, connection] if cipher
end
a, b, _ = @supported_curves
ab, ba = [a, b], [b, a]
if cipher.ecdsa?
# In case of ECDSA, add the cert key at the end
# Or no negociation possible
ecdsa_curve = Curve.new connection.peer_cert.public_key.group.curve_name
ab << ecdsa_curve
ba << ecdsa_curve
end
ab = ssl_client(method, cipher, curves: ab).tmp_key.curve
ba = ssl_client(method, cipher, curves: ba).tmp_key.curve
if ab != ba
Logger.info { 'Curves preference: ' + 'client preference'.colorize(:warning) }
@curves_preference = :client
return
end
sort = lambda do |a, b|
curves = [a, b]
if cipher.ecdsa?
# In case of ECDSA, add the cert key at the end
# Or no negociation possible
ecdsa_curve = Curve.new connection.tmp_key.curve
curves << ecdsa_curve
end
connection = ssl_client method, cipher, curves: curves
curve = connection.tmp_key.curve
a == curve ? -1 : 1
end
@curves_preference = @supported_curves.sort &sort
Logger.info { 'Curves preference : ' + @curves_preference.collect { |c| c.name }.join(', ') }
end
def check_fallback_scsv
@ -440,9 +405,6 @@ module CryptCheck
# First, collect "standard" connections
# { method => { cipher => connection, ... }, ... }
certs = @supported_ciphers.values.collect(&:values).flatten 1
# Then, collect "ecdsa" connections
# { curve => connection, ... }
certs += @ecdsa_certs.values
# For anonymous cipher, there is no certificate at all
certs = certs.reject { |c| c.peer_cert.nil? }
# Then, fetch cert

@ -14,7 +14,7 @@ module Fixture
end
def to_s
"ECC #{self.size} bits"
"ECC #{self.size} bits (#{self.curve})"
end
def to_h

Loading…
Cancel
Save