Support OpenSSL 1.1 and so TLSv1.3

master
aeris 3 years ago
parent 72d554769a
commit 7c48e9e150
  1. 37
      Dockerfile
  2. 159
      Makefile
  3. 118
      bin/bundle
  4. 7
      bin/console
  5. 31
      bin/rspec
  6. 1
      bin/test
  7. 5
      bin/test
  8. 174
      bin/tls_server.rb
  9. 68
      cryptcheck.gemspec
  10. 5
      lib/cryptcheck.rb
  11. 52
      lib/cryptcheck/fixture.rb
  12. 1
      lib/cryptcheck/state.rb
  13. 2
      lib/cryptcheck/tls.rb
  14. 64
      lib/cryptcheck/tls/cipher.rb
  15. 9
      lib/cryptcheck/tls/curve.rb
  16. 57
      lib/cryptcheck/tls/engine.rb
  17. 185
      lib/cryptcheck/tls/fixture.rb
  18. 9
      lib/cryptcheck/tls/method.rb
  19. 23
      lib/fixtures/00_openssl.rb
  20. 11
      lib/fixtures/01_openssl/certificate.rb
  21. 71
      lib/fixtures/01_openssl/context.rb
  22. 46
      lib/fixtures/01_openssl/dh.rb
  23. 35
      lib/fixtures/01_openssl/dsa.rb
  24. 50
      lib/fixtures/01_openssl/ec.rb
  25. 47
      lib/fixtures/01_openssl/pkey.rb
  26. 44
      lib/fixtures/01_openssl/rsa.rb
  27. 27
      lib/fixtures/01_openssl/store.rb
  28. 20
      lib/fixtures/integer.rb
  29. 20
      lib/fixtures/string.rb
  30. 0
      lib/fixtures/tls.rb
  31. 23
      set-env

@ -1,6 +1,11 @@
FROM alpine:3.10 AS builder
FROM alpine:3.11 AS builder
MAINTAINER aeris <aeris@imirhil.fr>
ARG OPENSSL_VERSION=1.0.2j
ARG OPENSSL_BINDING=1.0
ARG OPENSSL_LIB_VERSION=1.0.0
ARG RUBY_VERSION=2.3.8-cryptcheck
RUN apk add --update make gcc \
linux-headers readline-dev libxml2-dev yaml-dev zlib-dev libffi-dev gdbm-dev ncurses-dev \
ca-certificates wget patch perl musl-dev bash coreutils git
@ -8,35 +13,37 @@ RUN apk add --update make gcc \
ENV PATH /usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH
ENV RBENV_ROOT /usr/local/rbenv
ENV RUBY_CONFIGURE_OPTS --disable-install-doc
ENV C_INCLUDE_PATH /cryptcheck/build/openssl/include
ENV CPLUS_INCLUDE_PATH /cryptcheck/build/openssl/include
ENV LIBRARY_PATH /cryptcheck/lib
ENV LD_LIBRARY_PATH /cryptcheck/lib
RUN git clone https://github.com/rbenv/rbenv "$RBENV_ROOT" -b v1.1.2 --depth 1
RUN git clone https://github.com/sstephenson/ruby-build "$RBENV_ROOT/plugins/ruby-build"
WORKDIR /cryptcheck/
COPY . /cryptcheck/
RUN make libs
RUN make rbenv
RUN echo "gem: --no-test --no-document" > /etc/gemrc && \
RUN make openssl-$OPENSSL_BINDING rbenv ruby-$OPENSSL_BINDING && \
cp build/openssl-$OPENSSL_VERSION/libssl.so \
build/openssl-$OPENSSL_VERSION/libssl.so.$OPENSSL_LIB_VERSION \
build/openssl-$OPENSSL_VERSION/libcrypto.so \
build/openssl-$OPENSSL_VERSION/libcrypto.so.$OPENSSL_LIB_VERSION \
lib && \
make clean
RUN echo "gem: --no-test --no-document" > /root/.gemrc && \
rbenv local $RUBY_VERSION && \
gem install bundler && \
bundle install --deployment --without development test
rm -f Gemfile.lock && bundle update && \
bundle config set deployment true && \
bundle config set without 'development test' && \
bundle install
FROM alpine:3.10 AS engine
FROM alpine:3.11 AS engine
MAINTAINER aeris <aeris@imirhil.fr>
WORKDIR /cryptcheck/
RUN apk add --update tini bash ca-certificates libxml2 yaml zlib libffi gdbm ncurses
ENV PATH /usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH
ENV LD_LIBRARY_PATH /cryptcheck/lib
ENV RBENV_ROOT /usr/local/rbenv
ENTRYPOINT ["/sbin/tini", "--", "/cryptcheck/bin/cryptcheck"]
COPY --from=builder /etc/gemrc /etc/gemrc
COPY --from=builder /root/.gemrc /root/.gemrc
COPY --from=builder /usr/local/rbenv/ /usr/local/rbenv/
COPY --from=builder /cryptcheck/ /cryptcheck/

@ -1,29 +1,33 @@
PWD = $(shell pwd)
OPENSSL_LIB_VERSION = 1.0.0
OPENSSL_VERSION = 1.0.2j
OPENSSL_NAME = openssl-$(OPENSSL_VERSION)
OPENSSL_DIR = build/$(OPENSSL_NAME)
RUBY_MAJOR_VERSION = 2.3
RUBY_VERSION = $(RUBY_MAJOR_VERSION).8
RBENV_DIR = $(RBENV_ROOT)/versions/$(RUBY_VERSION)-cryptcheck
RBENV_ROOT ?= ~/.rbenv
export LIBRARY_PATH ?= $(PWD)/lib
export C_INCLUDE_PATH ?= $(PWD)/build/openssl/include
export LD_LIBRARY_PATH ?= $(PWD)/lib
RBENV__VERSION := v1.1.2
RUBY_BUILD_VERSION = v20200401
OPENSSL_1_0_VERSION = 1.0.2j
OPENSSL_1_1_VERSION = 1.1.1g
RUBY_1_0_VERSION = 2.3.8
RUBY_1_1_VERSION = 2.6.6
ROOT_DIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
BUILD_DIR = $(ROOT_DIR)/build
LIBRARY_PATH_1_0 = $(BUILD_DIR)/openssl-$(OPENSSL_1_0_VERSION)
C_INCLUDE_PATH_1_0 = $(LIBRARY_PATH_1_0)/include
LIBRARY_PATH_1_1 = $(BUILD_DIR)/openssl-$(OPENSSL_1_1_VERSION)
C_INCLUDE_PATH_1_1 = $(LIBRARY_PATH_1_1)/include
MAKE_OPTS ?= -j $(shell nproc)
.SECONDARY:
.SUFFIXES:
all: libs rbenv
clean:
rm -rf build/
clean: clean-libs
clean-libs:
[ -d "build/openssl/" ] \
&& find "build/openssl/" \( -name "*.o" -o -name "*.so" \) -delete \
|| true
rm -f lib/libcrypto.so* lib/libssl.so* "build/openssl//Makefile"
mr-proper:
rm -rf lib/libcrypto.so* lib/libssl.so* lib/openssl.so build
$(RBENV_ROOT)/:
git clone https://github.com/rbenv/rbenv/ "$@" -b "$(RBENV__VERSION)" --depth 1
$(RBENV_ROOT)/plugins/ruby-build/: | $(RBENV_ROOT)/
git clone https://github.com/rbenv/ruby-build/ "$@" -b "$(RUBY_BUILD_VERSION)" --depth 1
rbenv: | $(RBENV_ROOT)/plugins/ruby-build/
build/:
mkdir "$@"
@ -31,63 +35,84 @@ build/:
build/chacha-poly.patch: | build/
wget -q https://github.com/cloudflare/sslconfig/raw/master/patches/openssl__chacha20_poly1305_draft_and_rfc_ossl102j.patch -O "$@"
build/$(OPENSSL_NAME).tar.gz: | build/
wget -q "https://www.openssl.org/source/old/1.0.2/$(OPENSSL_NAME).tar.gz" -O "$@"
build/openssl/: | $(OPENSSL_DIR)/
ln -s "$(OPENSSL_NAME)" "build/openssl"
build/openssl-%.tar.gz: | build/
wget -q "https://www.openssl.org/source/$(notdir $@)" -O "$@"
$(OPENSSL_DIR)/: build/$(OPENSSL_NAME).tar.gz build/chacha-poly.patch
tar -C build -xf "build/$(OPENSSL_NAME).tar.gz"
patch -d "$(OPENSSL_DIR)" -p1 < build/chacha-poly.patch
build/openssl-$(OPENSSL_1_0_VERSION)/: build/openssl-$(OPENSSL_1_0_VERSION).tar.gz build/chacha-poly.patch
tar -C build -xf "$<"
patch -d "$@" -p1 < build/chacha-poly.patch
for p in patches/openssl/*.patch; do patch -d "$@" -p1 < "$$p"; done
build/openssl/Makefile: | build/openssl/
cd build/openssl/ && ./config enable-ssl2 enable-ssl3 enable-ssl3-method enable-md2 enable-rc5 enable-weak-ssl-ciphers enable-shared
build/openssl-$(OPENSSL_1_1_VERSION)/: build/openssl-$(OPENSSL_1_1_VERSION).tar.gz build/chacha-poly.patch
tar -C build -xf "$<"
build/openssl/libssl.so \
build/openssl/libcrypto.so: build/openssl/Makefile
$(MAKE) -C build/openssl/
.ONESHELL:
build/openssl-%/Makefile: | build/openssl-%/
cd "$(dir $@)"
./config --prefix=/usr --openssldir=/etc/ssl \
enable-ssl2 enable-ssl3 enable-ssl3-method \
enable-md2 enable-rc5 enable-weak-ssl-ciphers enable-shared
$(MAKE) $(MAKE_OPTS) depend
LIBS = lib/libssl.so lib/libcrypto.so lib/libssl.so.$(OPENSSL_LIB_VERSION) lib/libcrypto.so.$(OPENSSL_LIB_VERSION)
lib/%.so: build/openssl/%.so
cp "$<" "$@"
lib/%.so.$(OPENSSL_LIB_VERSION): lib/%.so
ln -fs "$(notdir $(subst .$(OPENSSL_LIB_VERSION),,$@))" "$@"
libs: $(LIBS)
$(RBENV_ROOT)/:
git clone https://github.com/rbenv/rbenv/ $@ -b v1.1.1 --depth 1
$(RBENV_ROOT)/plugins/ruby-build/: | $(RBENV_ROOT)/
git clone https://github.com/rbenv/ruby-build/ $@ -b v20171215 --depth 1
build/openssl-%/libssl.so build/openssl-%/libcrypto.so: build/openssl-%/Makefile
$(MAKE) -C "$(dir $<)" $(MAKE_OPTS)
$(RBENV_ROOT)/plugins/ruby-build/share/ruby-build/$(RUBY_VERSION): | $(RBENV_ROOT)/plugins/ruby-build/
openssl-1.0: build/openssl-$(OPENSSL_1_0_VERSION)/libssl.so build/openssl-$(OPENSSL_1_0_VERSION)/libcrypto.so
openssl-1.1: build/openssl-$(OPENSSL_1_1_VERSION)/libssl.so build/openssl-$(OPENSSL_1_1_VERSION)/libcrypto.so
openssl: openssl-1.0 openssl-1.1
build/$(RUBY_VERSION)-cryptcheck: $(RBENV_ROOT)/plugins/ruby-build/share/ruby-build/$(RUBY_VERSION)
cp $< $@
build/$(RUBY_1_0_VERSION)-cryptcheck: $(RBENV_ROOT)/plugins/ruby-build/share/ruby-build/$(RUBY_1_0_VERSION)
cp "$<" "$@"
build/$(RUBY_1_1_VERSION)-cryptcheck: $(RBENV_ROOT)/plugins/ruby-build/share/ruby-build/$(RUBY_1_1_VERSION)
cp "$<" "$@"
rbenv: build/$(RUBY_VERSION)-cryptcheck $(LIBS) | $(OPENSSL_DIR)/
$(RBENV_ROOT)/versions/$(RUBY_1_0_VERSION)-cryptcheck: build/$(RUBY_1_0_VERSION)-cryptcheck openssl-1.0
cat patches/ruby/*.patch | \
RUBY_BUILD_CACHE_PATH=$(PWD)/build \
RUBY_BUILD_DEFINITIONS=$(PWD)/build \
MAKE_OPTS="-j $(shell nproc)" rbenv install -fp $(RUBY_VERSION)-cryptcheck
# rbenv sequester $(RUBY_VERSION)-cryptcheck
rbenv local $(RUBY_VERSION)-cryptcheck
gem install bundler
bundle install
spec/faketime/libfaketime.so: spec/faketime/faketime.c spec/faketime/faketime.h
$(CC) $^ -o $@ -shared -fPIC -ldl -std=c99 -Werror -Wall
lib/libfaketime.so: spec/faketime/libfaketime.so
ln -fs ../$< $@
faketime: lib/libfaketime.so
LIBRARY_PATH="$(LIBRARY_PATH_1_0)" \
C_INCLUDE_PATH="$(C_INCLUDE_PATH_1_0)" \
LD_LIBRARY_PATH="$(LIBRARY_PATH_1_0)" \
RUBY_BUILD_CACHE_PATH="$(BUILD_DIR)" \
RUBY_BUILD_DEFINITIONS="$(BUILD_DIR)" \
MAKE_OPTS="$(MAKE_OPTS)" rbenv install -fp "$(notdir $@)"
$(RBENV_ROOT)/versions/$(RUBY_1_1_VERSION)-cryptcheck: build/$(RUBY_1_1_VERSION)-cryptcheck openssl-1.1
cat patches/ciphersuites.patch | \
LIBRARY_PATH="$(LIBRARY_PATH_1_1)" \
C_INCLUDE_PATH="$(C_INCLUDE_PATH_1_1)" \
LD_LIBRARY_PATH="$(LIBRARY_PATH_1_1)" \
RUBY_BUILD_CACHE_PATH="$(BUILD_DIR)" \
RUBY_BUILD_DEFINITIONS="$(BUILD_DIR)" \
MAKE_OPTS="$(MAKE_OPTS)" rbenv install -fp "$(notdir $@)"
ruby-1.0: $(RBENV_ROOT)/versions/$(RUBY_1_0_VERSION)-cryptcheck
ruby-1.1: $(RBENV_ROOT)/versions/$(RUBY_1_1_VERSION)-cryptcheck
ruby: ruby-1.0 ruby-1.1
build/libfaketime.so: spec/faketime/faketime.c spec/faketime/faketime.h
$(CC) $^ -o "$@" -shared -fPIC -ldl -std=c99 -Werror -Wall
faketime: build/libfaketime.so
.PHONY: faketime
test-material:
bin/generate-test-material.rb
test: spec/faketime/libfaketime.so
bin/rspec
docker:
docker build . -t aeris22/cryptcheck:v2 -t aeris22/cryptcheck:v2.1 -t aeris22/cryptcheck:latest
LD_LIBRARY_PATH="$(LIBRARY_PATH_1_0):$(BUILD_DIR)" bin/rspec
.PHONY: test
docker-1.0:
docker build . --target engine \
-t aeris22/cryptcheck:v2-1.0 \
-t aeris22/cryptcheck:v2.2-1.0 \
-t aeris22/cryptcheck:latest-1.0 \
-t aeris22/cryptcheck:v2 \
-t aeris22/cryptcheck:v2.2 \
-t aeris22/cryptcheck:latest
docker-1.1:
docker build . --target engine \
--build-arg OPENSSL_VERSION=1.1.1g \
--build-arg OPENSSL_BINDING=1.1 \
--build-arg OPENSSL_LIB_VERSION=1.1 \
--build-arg RUBY_VERSION=2.6.6-cryptcheck \
-t aeris22/cryptcheck:v2-1.1 \
-t aeris22/cryptcheck:v2.2-1.1 \
-t aeris22/cryptcheck:latest-1.1
docker: docker-1.0 docker-1.1

@ -1,4 +1,114 @@
#!/bin/bash
: ${RBENV_ROOT:=$HOME/.rbenv}
DIR="$(readlink -m "$(dirname "$0")")"
LD_LIBRARY_PATH="${DIR}/../lib" "${RBENV_ROOT}/shims/bundle" $*
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'bundle' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "rubygems"
m = Module.new do
module_function
def invoked_as_script?
File.expand_path($0) == File.expand_path(__FILE__)
end
def env_var_version
ENV["BUNDLER_VERSION"]
end
def cli_arg_version
return unless invoked_as_script? # don't want to hijack other binstubs
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
bundler_version = nil
update_index = nil
ARGV.each_with_index do |a, i|
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
bundler_version = a
end
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
bundler_version = $1
update_index = i
end
bundler_version
end
def gemfile
gemfile = ENV["BUNDLE_GEMFILE"]
return gemfile if gemfile && !gemfile.empty?
File.expand_path("../../Gemfile", __FILE__)
end
def lockfile
lockfile =
case File.basename(gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
else "#{gemfile}.lock"
end
File.expand_path(lockfile)
end
def lockfile_version
return unless File.file?(lockfile)
lockfile_contents = File.read(lockfile)
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
Regexp.last_match(1)
end
def bundler_version
@bundler_version ||=
env_var_version || cli_arg_version ||
lockfile_version
end
def bundler_requirement
return "#{Gem::Requirement.default}.a" unless bundler_version
bundler_gem_version = Gem::Version.new(bundler_version)
requirement = bundler_gem_version.approximate_recommendation
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
requirement += ".a" if bundler_gem_version.prerelease?
requirement
end
def load_bundler!
ENV["BUNDLE_GEMFILE"] ||= gemfile
activate_bundler
end
def activate_bundler
gem_error = activation_error_handling do
gem "bundler", bundler_requirement
end
return if gem_error.nil?
require_error = activation_error_handling do
require "bundler/version"
end
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
exit 42
end
def activation_error_handling
yield
nil
rescue StandardError, LoadError => e
e
end
end
m.load_bundler!
if m.invoked_as_script?
load Gem.bin_path("bundler", "bundle")
end

@ -0,0 +1,7 @@
#!/usr/bin/env ruby
require 'rubygems'
require 'bundler/setup'
require 'pry'
require 'cryptcheck'
Pry.start

@ -1,2 +1,29 @@
#!/bin/bash
LD_PRELOAD=${PWD}/lib/libfaketime.so LD_LIBRARY_PATH=${PWD}/lib bundle exec rspec $@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'rspec' is installed as part of a gem, and
# this file is here to facilitate running it.
#
require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
Pathname.new(__FILE__).realpath)
bundle_binstub = File.expand_path("../bundle", __FILE__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("rspec-core", "rspec")

@ -1 +0,0 @@
runner

@ -0,0 +1,5 @@
#!/usr/bin/env ruby
require 'bundler/setup'
require 'cryptcheck'
require 'pry-byebug'
CryptCheck::Tls::Https.analyze 'localhost', 443

@ -28,102 +28,112 @@ OpenSSL::PKey::EC.send :alias_method, :private?, :private_key?
# exit
def certificate(key)
CryptCheck::Logger.info 'Generating certificate'
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = rand 2**(20*8-1) .. 2**(20*8)
cert.not_before = Time.now
cert.not_after = Time.now + 365*24*60*60
cert.public_key = case key
when OpenSSL::PKey::EC
curve = key.group.curve_name
public = OpenSSL::PKey::EC.new curve
public.public_key = key.public_key
public
else
key.public_key
end
name = OpenSSL::X509::Name.parse 'CN=localhost'
cert.subject = name
cert.issuer = name
extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert
extension_factory.subject_certificate = cert
extension_factory.issuer_certificate = cert
cert.add_extension extension_factory.create_extension 'basicConstraints', 'CA:TRUE', true
cert.add_extension extension_factory.create_extension 'keyUsage', 'keyEncipherment, dataEncipherment, digitalSignature,nonRepudiation,keyCertSign'
cert.add_extension extension_factory.create_extension 'extendedKeyUsage', 'serverAuth, clientAuth'
cert.add_extension extension_factory.create_extension 'subjectKeyIdentifier', 'hash'
cert.add_extension extension_factory.create_extension 'authorityKeyIdentifier', 'keyid:always'
cert.add_extension extension_factory.create_extension 'subjectAltName', 'DNS:localhost'
cert.add_extension OpenSSL::X509::Extension.new '1.3.6.1.5.5.7.1.24', '0', true
cert.sign key, OpenSSL::Digest::SHA512.new
CryptCheck::Logger.info 'Certificate generated'
cert
CryptCheck::Logger.info 'Generating certificate'
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = rand 2 ** (20 * 8 - 1)..2 ** (20 * 8)
cert.not_before = Time.now
cert.not_after = Time.now + 365 * 24 * 60 * 60
cert.public_key = case key
when OpenSSL::PKey::EC
curve = key.group.curve_name
public = OpenSSL::PKey::EC.new curve
public.public_key = key.public_key
public
else
key.public_key
end
name = OpenSSL::X509::Name.parse 'CN=localhost'
cert.subject = name
cert.issuer = name
extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert
extension_factory.subject_certificate = cert
extension_factory.issuer_certificate = cert
cert.add_extension extension_factory.create_extension 'basicConstraints', 'CA:TRUE', true
cert.add_extension extension_factory.create_extension 'keyUsage', 'keyEncipherment, dataEncipherment, digitalSignature,nonRepudiation,keyCertSign'
cert.add_extension extension_factory.create_extension 'extendedKeyUsage', 'serverAuth, clientAuth'
cert.add_extension extension_factory.create_extension 'subjectKeyIdentifier', 'hash'
cert.add_extension extension_factory.create_extension 'authorityKeyIdentifier', 'keyid:always'
cert.add_extension extension_factory.create_extension 'subjectAltName', 'DNS:localhost'
cert.add_extension OpenSSL::X509::Extension.new '1.3.6.1.5.5.7.1.24', '0', true
cert.sign key, OpenSSL::Digest::SHA512.new
CryptCheck::Logger.info 'Certificate generated'
cert
end
key = OpenSSL::PKey::RSA.new File.read 'config/rsa-2048.pem'
# key = OpenSSL::PKey::EC.new('prime256v1').generate_key
cert = certificate key
rsa_key = OpenSSL::PKey::RSA.new File.read 'config/rsa-2048.pem'
rsa_cert = certificate rsa_key
ec_key = OpenSSL::PKey::EC.new('prime256v1').generate_key
ec_cert = certificate ec_key
CryptCheck::Logger.info 'Starting server'
context = OpenSSL::SSL::SSLContext.new
#context = OpenSSL::SSL::SSLContext.new :SSLv3
#context = OpenSSL::SSL::SSLContext.new :TLSv1_1
context.cert = cert
context.key = key
context.ciphers = ARGV[0] || 'EECDH+AESGCM'
#dh = OpenSSL::PKey::DH.new File.read 'config/dh-4096.pem'
#context.tmp_dh_callback = proc { dh }
#context.ecdh_curves = CryptCheck::Tls::Server::SUPPORTED_CURVES.join ':'
#context.ecdh_curves = 'secp384r1:secp521r1:sect571r1'
#context.ecdh_curves = 'prime256v1'
#ecdh = OpenSSL::PKey::EC.new('secp384r1').generate_key
#context.tmp_ecdh_callback = proc { ecdh }
if context.respond_to? :add_certificate
context.add_certificate ec_cert, ec_key
context.add_certificate rsa_cert, rsa_key
else
context.certs = [ec_cert, rsa_cert]
context.keys = [ec_key, rsa_key]
end
ciphers = ARGV[0] || 'EECDH+AESGCM'
puts ciphers
context.ciphers = ciphers
dh = OpenSSL::PKey::DH.new File.read 'config/dh-2048.pem'
# context.tmp_dh_callback = proc { dh }
# context.ecdh_curves = CryptCheck::Tls::Server::SUPPORTED_CURVES.join ':'
# context.ecdh_curves = 'prime256v1:secp384r1:secp521r1:sect571r1'
# context.ecdh_curves = 'prime256v1'
# ecdh = OpenSSL::PKey::EC.new('prime256v1').generate_key
# context.tmp_ecdh_callback = proc { ecdh }
host, port = '::', 5000
tcp_server = TCPServer.new host, port
tls_server = OpenSSL::SSL::SSLServer.new tcp_server, context
tcp_server = TCPServer.new host, port
tls_server = OpenSSL::SSL::SSLServer.new tcp_server, context
::CryptCheck::Logger.info "Server started on #{host}:#{port}"
# ::CryptCheck::Logger.info "Supported ciphers:"
# context.ciphers.each { |c| ::CryptCheck::Logger.info c.first }
loop do
begin
connection = tls_server.accept
method = connection.ssl_version
dh = connection.tmp_key
cipher = connection.cipher
cipher = CryptCheck::Tls::Cipher.new method, cipher.first
states = cipher.states
# p states
# text = %i(critical error warning good perfect best).collect do |s|
# states[s].collect { |t| t.to_s.colorize s }.join ' '
# end.reject &:empty?
# text = []
# text = text.join ' '
# text = ''
dh = dh ? " (#{'PFS'.colorize :good}ย : #{CryptCheck::Tls.key_to_s dh})" : ''
CryptCheck::Logger.info { "#{cipher}#{dh}" }
data = connection.gets
if data
CryptCheck::Logger.info data
end
connection.puts 'HTTP/1.1 200 OK'
connection.puts 'Strict-Transport-Security: max-age=31536000'
connection.close
rescue OpenSSL::SSL::SSLError, SystemCallError
end
begin
connection = tls_server.accept
method = connection.ssl_version
dh = connection.tmp_key
cipher = connection.cipher
cipher = CryptCheck::Tls::Cipher.new method, cipher.first
states = cipher.states
# p states
# text = %i(critical error warning good perfect best).collect do |s|
# states[s].collect { |t| t.to_s.colorize s }.join ' '
# end.reject &:empty?
# text = []
# text = text.join ' '
# text = ''
dh = dh ? " (#{'PFS'.colorize :good}ย : #{CryptCheck::Tls.key_to_s dh})" : ''
CryptCheck::Logger.info { "#{cipher}#{dh}" }
data = connection.gets
if data
CryptCheck::Logger.info data
end
connection.puts 'HTTP/1.1 200 OK'
connection.puts 'Strict-Transport-Security: max-age=31536000'
connection.close
rescue OpenSSL::SSL::SSLError, SystemCallError
end
end

@ -3,42 +3,50 @@ 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 = '2.0.0'
spec.authors = ['Aeris']
spec.email = ['aeris+tls@imirhil.fr']
spec.name = 'cryptcheck'
spec.version = '2.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 = 'AGPL-3.0+'
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 = 'AGPL-3.0+'
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
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.files = { '*.rb' => %w(lib) }.collect_concat do |e, ds|
ds.collect_concat do |d|
Dir[File.join d, '**', e]
end
end
# 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.require_paths = %w(lib)
spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'rspec'
spec.add_development_dependency 'ffi'
spec.add_development_dependency 'pry-byebug'
spec.add_development_dependency 'pry-rescue'
spec.add_development_dependency 'pry-stack_explorer'
spec.add_development_dependency 'bundler'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'rspec'
spec.add_development_dependency 'ffi'
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.4')
# pry-byebug supports 2.3 only up to 3.6.x
spec.add_development_dependency 'pry-byebug', '~> 3.6.0'
# and there is a bug if using more than pry 0.12.x
spec.add_development_dependency 'pry', '~> 0.12.2'
else
spec.add_development_dependency 'pry-byebug'
end
spec.add_dependency 'httparty'
spec.add_dependency 'nokogiri'
spec.add_dependency 'parallel'
spec.add_dependency 'ruby-progressbar'
spec.add_dependency 'colorize'
spec.add_dependency 'awesome_print'
spec.add_dependency 'thor'
spec.add_dependency 'httparty'
spec.add_dependency 'nokogiri'
spec.add_dependency 'parallel'
spec.add_dependency 'ruby-progressbar'
spec.add_dependency 'colorize'
spec.add_dependency 'awesome_print'
spec.add_dependency 'thor'
end

@ -2,6 +2,7 @@ require 'colorize'
require 'ipaddr'
require 'timeout'
require 'yaml'
require 'openssl'
module CryptCheck
autoload :State, 'cryptcheck/state'
@ -55,5 +56,5 @@ module CryptCheck
end
end
require 'cryptcheck/fixture'
require 'cryptcheck/tls/fixture'
fixtures = File.join __dir__, 'fixtures', '**', '*.rb'
Dir[fixtures].sort.each { |f| require f }

@ -1,52 +0,0 @@
class String
alias :colorize_old :colorize
COLORS = {
critical: { color: :white, background: :red },
error: :red,
warning: :light_red,
good: :green,
great: :blue,
best: :magenta,
unknown: { background: :black }
}
def colorize(state)
color = COLORS[state] || state
self.colorize_old color
end
end
class Exception
BACKTRACE_REGEXP = /^(.*):(\d+):in `(.*)'$/
def colorize
$stderr.puts self.message.colorize(:red)
self.backtrace.each do |line|
line = BACKTRACE_REGEXP.match line
line = '%s:%s:in `%s\'' % [
line[1].colorize(:yellow),
line[2].colorize(:blue),
line[3].colorize(:magenta)
]
$stderr.puts line
end
end
end
class Integer
def humanize
secs = self
[[60, :second],
[60, :minute],
[24, :hour],
[30, :day],
[12, :month]].map do |count, name|
if secs > 0
secs, n = secs.divmod count
n = n.to_i
n > 0 ? "#{n} #{name}#{n > 1 ? 's' : ''}" : nil
end
end.compact.reverse.join ' '
end
end

@ -99,7 +99,6 @@ module CryptCheck
b <=> a
end
protected
def checks
@checks ||= self.available_checks.collect { |c| perform_check c }.flatten(1) + children.collect(&:checks).flatten(1)
end

@ -15,6 +15,8 @@ module CryptCheck
def self.key_to_s(key)
size, color = case key.type
when :x25519
["#{key.curve} #{key.size}", :good]
when :ecc
["#{key.group.curve_name} #{key.size}", :good]
when :rsa

@ -25,17 +25,21 @@ module CryptCheck
rc4: %w(RC4),
des: %w(DES-CBC),
des3: %w(3DES DES-CBC3),
aes: %w(AES(128|256) AES-(128|256)),
aes128: %w(AES128 AES-128),
aes256: %w(AES256 AES-256),
aes: %w(AES(128|256) AES-(128|256) AES_(128|256)),
aes128: %w(AES128 AES-128 AES_128),
aes256: %w(AES256 AES-256 AES_256),
camellia: %w(CAMELLIA(128|256)),
seed: %w(SEED),
idea: %w(IDEA),
chacha20: %w(CHACHA20),
aria: %w(ARIA(128|256) ARIA-(128|256) ARIA_(128|256)),
aria128: %w(ARIA128 ARIA-128 ARIA_128),
aria256: %w(ARIA256 ARIA-256 ARIA_256),
# cbc: %w(CBC),
gcm: %w(GCM),
ccm: %w(CCM)
gcm: %w(GCM),
ccm: %w(CCM CCM8),
ccm8: %w(CCM8)
}.freeze
attr_reader :method, :name
@ -59,23 +63,14 @@ module CryptCheck
TYPES.each do |name, ciphers|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def self.#{name}?(cipher)
#{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ cipher }
#{ciphers}.any? { |c| /(^|[-_])#\{c\}([-_]|$)/ =~ cipher }
end
def #{name}?
#{ciphers}.any? { |c| /(^|-)#\{c\}(-|$)/ =~ @name }
#{ciphers}.any? { |c| /(^|[-_])#\{c\}([-_]|$)/ =~ @name }
end
RUBY_EVAL
end
def self.aes?(cipher)
aes?(cipher) or aes?(cipher)
end
def aes?
aes128? or aes256?
end
def self.cbc?(cipher)
!aead? cipher
end
@ -101,6 +96,7 @@ module CryptCheck
end
def pfs?
return true if self.method == :TLSv1_3
dhe? or ecdhe?
end
@ -194,7 +190,11 @@ module CryptCheck
when aes128?
[:aes, 128, 128, self.mode]
when aes256?
[:aes, 256, 256, self.mode]
[:aes, 256, 128, self.mode]
when aria128?
[:aria, 128, 128, self.mode]
when aria256?
[:aria, 256, 128, self.mode]
when camellia?
[:camellia, 128, 128, self.mode]
when seed?
@ -211,6 +211,8 @@ module CryptCheck
[:rc2, 64, 64, self.mode]
when null?
[nil, 0, 0, nil]
else
raise "Unknown encryption #{@method} #{@name}"
end
end
@ -218,8 +220,6 @@ module CryptCheck
case
when gcm?
:gcm
when ccm?
:ccm
when chacha20?
:aead
when rc4?
@ -233,6 +233,10 @@ module CryptCheck
case
when poly1305?
[:poly1305, 128]
when ccm8?
[:ccm, 64]
when ccm?
[:ccm, 128]
when sha384?
[:sha384, 384]
when sha256?
@ -275,11 +279,23 @@ module CryptCheck
@name <=> other.name
end
ALL = 'ALL:COMPLEMENTOFALL'.freeze
SUPPORTED = Method.collect do |m|
context = ::OpenSSL::SSL::SSLContext.new m.to_sym
context.ciphers = ALL
ciphers = context.ciphers.collect { |c| Cipher.new m, c.first }
ALL = 'ALL:COMPLEMENTOFALL'.freeze
ALL_TLSv1_3 = %w[
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
TLS_AES_128_CCM_SHA256
TLS_AES_128_CCM_8_SHA256
].freeze
SUPPORTED = Method.collect do |m|
sym = m.to_sym
ciphers = if sym == :TLSv1_3
ALL_TLSv1_3.collect { |c| Cipher.new m, c }
else
context = ::OpenSSL::SSL::SSLContext.new sym
context.ciphers = ALL
context.ciphers.collect { |c| Cipher.new m, c.first }
end
[m, ciphers.sort]
end.to_h.freeze
end

@ -8,17 +8,10 @@ module CryptCheck
@name = name
end
# SUPPORTED = %i(sect163k1 sect163r1 sect163r2 sect193r1
# sect193r2 sect233k1 sect233r1 sect239k1 sect283k1 sect283r1
# sect409k1 sect409r1 sect571k1 sect571r1 secp160k1 secp160r1
# secp160r2 secp192k1 secp192r1 secp224k1 secp224r1 secp256k1
# secp256r1 secp384r1 secp521r1
# prime256v1
# brainpoolP256r1 brainpoolP384r1 brainpoolP512r1)
SUPPORTED = %i(secp256k1 sect283k1 sect283r1 secp384r1
sect409k1 sect409r1 secp521r1 sect571k1 sect571r1
prime192v1 prime256v1
brainpoolP256r1 brainpoolP384r1 brainpoolP512r1).collect { |c| self.new c }.freeze
brainpoolP256r1 brainpoolP384r1 brainpoolP512r1 x25519).collect { |c| self.new c }.freeze
extend Enumerable

@ -10,17 +10,22 @@ module CryptCheck
class TLSException < ::StandardError
end
class TLSNotAvailableException < TLSException
def to_s
'TLSย seems not supported on this server'
end
end
class MethodNotAvailable < TLSException
end
class CipherNotAvailable < TLSException
end
class InappropriateFallback < TLSException
end
class Timeout < ::StandardError
def initialize(ip, port)
@message = "Timeout when connecting to #{ip}:#{port} (max #{TCP_TIMEOUT.humanize})"
@ -30,11 +35,13 @@ module CryptCheck
@message
end
end
class TLSTimeout < Timeout
def initialize(ip, port)
@message = "Timeout when TLS connecting to #{ip}:#{port} (max #{TLS_TIMEOUT.humanize})"
end
end
class ConnectionError < ::StandardError
end
@ -86,9 +93,7 @@ module CryptCheck
connection = ssl_client method, cipher
Logger.info { " Cipher #{cipher}" }
dh = connection.tmp_key
if dh
Logger.info { " PFSย : #{dh}" }
end
Logger.info { " PFSย : #{dh}" } if dh
connection
rescue TLSException
Logger.debug { " Cipher #{cipher} : not supported" }
@ -285,7 +290,7 @@ module CryptCheck
ssl_client method, fallback: true
rescue InappropriateFallback,
CipherNotAvailable, # Seems some servers reply with "sslv3 alert handshake failure"โ€ฆ
MethodNotAvailable, # Seems some servers reply with "wrong version number"โ€ฆ
MethodNotAvailable # Seems some servers reply with "wrong version number"โ€ฆ
@fallback_scsv = true
end
else
@ -349,17 +354,13 @@ module CryptCheck
def ssl_connect(socket, context, method, &block)
ssl_socket = ::OpenSSL::SSL::SSLSocket.new socket, context
ssl_socket.hostname = @hostname if @hostname and method != :SSLv2
#Logger.trace { "SSL connecting to #{name}" }
begin
ssl_socket.connect_nonblock
#Logger.trace { "SSL connected to #{name}" }
return block_given? ? block.call(ssl_socket) : nil
rescue ::OpenSSL::SSL::SSLErrorWaitReadable
#Logger.trace { "Waiting for SSL read to #{name}" }
raise TLSTimeout.new(@ip, @port) unless IO.select [ssl_socket], nil, nil, TLS_TIMEOUT
retry
rescue ::OpenSSL::SSL::SSLErrorWaitWritable
#Logger.trace { "Waiting for SSL write to #{name}" }
raise TLSTimeout.new(@ip, @port) unless IO.select nil, [ssl_socket], nil, TLS_TIMEOUT
retry
rescue ::OpenSSL::SSL::SSLError => e
@ -368,15 +369,18 @@ module CryptCheck
/state=SSLv3 read server hello A$/,
/state=SSLv3 read server hello A: wrong version number$/,
/state=SSLv3 read server hello A: tlsv1 alert protocol version$/,
/state=SSLv3 read server key exchange A: sslv3 alert handshake failure$/
/state=SSLv3 read server key exchange A: sslv3 alert handshake failure$/,
/state=error: tlsv1 alert protocol version$/
raise MethodNotAvailable, e
when /state=SSLv2 read server hello A: peer error no cipher$/,
/state=error: no ciphers available$/,
/state=SSLv3 read server hello A: sslv3 alert handshake failure$/,
/state=error: missing export tmp dh key$/,
/state=error: wrong curve$/
/state=error: wrong curve$/,
/error: sslv3 alert handshake failure$/
raise CipherNotAvailable, e
when /state=SSLv3 read server hello A: tlsv1 alert inappropriate fallback$/
when /state=SSLv3 read server hello A: tlsv1 alert inappropriate fallback$/,
/state=error: tlsv1 alert inappropriate fallback$/
raise InappropriateFallback, e
end
raise
@ -393,16 +397,18 @@ module CryptCheck
def ssl_client(method, ciphers = nil, curves: nil, fallback: false, &block)
sleep SLOW_DOWN if SLOW_DOWN > 0
ssl_context = ::OpenSSL::SSL::SSLContext.new method.to_sym
method = method.to_sym
ssl_context = ::OpenSSL::SSL::SSLContext.new method
ssl_context.enable_fallback_scsv if fallback
if ciphers
ciphers = [ciphers] unless ciphers.is_a? Enumerable
ciphers = ciphers.collect(&:name).join ':'
ciphers = Array(ciphers).collect(&:name).join ':' if ciphers
if method == :TLSv1_3
ssl_context.ciphersuites = ciphers if ciphers
else
ciphers = Cipher::ALL
ciphers ||= Cipher::ALL
ssl_context.ciphers = ciphers
end
ssl_context.ciphers = ciphers
if curves
curves = [curves] unless curves.is_a? Enumerable
@ -413,16 +419,17 @@ module CryptCheck
end
Logger.trace { "Try method=#{method} / ciphers=#{ciphers} / curves=#{curves} / scsv=#{fallback}" }
begin
connect do |socket|
ssl_connect socket, ssl_context, method do |ssl_socket|
return block_given? ? block.call(ssl_socket) : ssl_socket
end
connect do |socket|
ssl_connect socket, ssl_context, method do |ssl_socket|
return block_given? ? block.call(ssl_socket) : ssl_socket
end
rescue => e
Logger.trace { "Error occurs : #{e}" }
raise
end
rescue TLSException => e
Logger.trace { "Error occurs : #{e}" }
raise
rescue => e
Logger.trace { "Error occurs : #{e}" }
raise TLSException.new e
end
def verify_certs

@ -1,185 +0,0 @@
require 'openssl'
class ::OpenSSL::PKey::PKey
def fingerprint
::OpenSSL::Digest::SHA256.hexdigest self.to_der
end
end
class ::OpenSSL::PKey::EC
def type
:ecc
end
def size
self.group.degree
end
def curve
self.group.curve_name
end
def to_s
"ECC #{self.size} bits"
end
def to_h
{ type: :ecc, curve: self.curve, size: self.size, fingerprint: self.fingerprint, states: self.states }
end
protected
include ::CryptCheck::State
CHECKS = [
[:ecc, %i(critical error warning), -> (s) do
case s.size
when 0...160
:critical
when 160...192
:error
when 192...256
:warning
else
false
end
end]
].freeze
def available_checks
CHECKS
end
end
class ::OpenSSL::PKey::RSA
def type
:rsa
end
def size
self.n.num_bits
end
def to_s
"RSA #{self.size} bits"
end
def to_h
{ type: :rsa, size: self.size, fingerprint: self.fingerprint, states: self.states }
end
protected
include ::CryptCheck::State
CHECKS = [
[:rsa, %i(critical error), ->(s) do
case s.size
when 0...1024
:critical
when 1024...2048
:error
else
false
end
end]
].freeze
def available_checks
CHECKS
end
end
class ::OpenSSL::PKey::DSA
def type
:dsa
end
def size
self.p.num_bits
end
def to_s
"DSA #{self.size} bits"
end
def to_h
{ type: :dsa, size: self.size, fingerprint: self.fingerprint, states: self.states }
end
include ::CryptCheck::State
CHECKS = [
[:dsa, :critical, -> (_) { true }]
].freeze
protected
def available_checks
CHECKS
end
end
class ::OpenSSL::PKey::DH
def type
:dh
end
def size
self.p.num_bits
end
def to_s
"DH #{self.size} bits"
end
def to_h
{ size: self.size, fingerprint: self.fingerprint, states: self.states }
end
protected
include ::CryptCheck::State
CHECKS = [
[:dh, %i(critical error), -> (s) do
case s.size
when 0...1024
:critical
when 1024...2048
:error
else
false
end
end]
].freeze
protected
def available_checks
CHECKS
end
end
class ::OpenSSL::X509::Certificate
def fingerprint
::OpenSSL::Digest::SHA256.hexdigest self.to_der
end
end
class ::OpenSSL::X509::Store
def add_chains(chains)
chains = [chains] unless chains.is_a? Enumerable
chains.each do |chain|
case chain
when ::OpenSSL::X509::Certificate
self.add_cert chain
else
next unless File.exists? chain
if File.directory?(chain)
Dir.entries(chain)
.collect { |e| File.join chain, e }
.select { |e| File.file? e }
.each { |f| self.add_file f }
else
self.add_file chain
end
end
end
end
end

@ -3,8 +3,9 @@ require 'delegate'
module CryptCheck
module Tls
class Method < SimpleDelegator
EXISTING = %i(TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2).freeze
SUPPORTED = (EXISTING & ::OpenSSL::SSL::SSLContext::METHODS)
EXISTING = %i(TLSv1_3 TLSv1_2 TLSv1_1 TLSv1 SSLv3 SSLv2).freeze
# EXISTING = %i(TLSv1_3)
SUPPORTED = EXISTING.select { |m| ::OpenSSL::SSL::SSLContext.supported? m }
.collect { |m| [m, self.new(m)] }.to_h.freeze
def self.[](method)
@ -19,9 +20,9 @@ module CryptCheck
def to_s
colors = case self.to_sym
when *%i(SSLv3 SSLv2)
when :SSLv3, :SSLv2
:critical
when :TLSv1_2
when :TLSv1_2, :TLSv1_3
:good
end
super.colorize colors

@ -0,0 +1,23 @@
module Fixture
module OpenSSL
module ClassMethods
def version
Gem::Version.new ::OpenSSL::VERSION
end
def ge?(version)
self.version >= Gem::Version.new(version)
end
def ge_2_1_2?
self.ge? '2.1.2'
end
end
def self.included(base)
base.extend ClassMethods
end
end
end
::OpenSSL.include Fixture::OpenSSL

@ -0,0 +1,11 @@
module Fixture
module OpenSSL
module Certificate
def fingerprint
::OpenSSL::Digest::SHA256.hexdigest self.to_der
end
end
end
end
::OpenSSL::X509::Certificate.include Fixture::OpenSSL::Certificate

@ -0,0 +1,71 @@
module Fixture
module OpenSSL
if ::OpenSSL.ge_2_1_2?
module Context
METHODS = {
TLSv1_3: ::OpenSSL::SSL::TLS1_3_VERSION,
TLSv1_2: ::OpenSSL::SSL::TLS1_2_VERSION,
TLSv1_1: ::OpenSSL::SSL::TLS1_1_VERSION,
TLSv1: ::OpenSSL::SSL::TLS1_VERSION,
SSL_3: ::OpenSSL::SSL::SSL3_VERSION,
SSL_2: ::OpenSSL::SSL::SSL2_VERSION
}.freeze
EXCLUDES = {
TLSv1_3: ::OpenSSL::SSL::OP_NO_TLSv1_3,