Browse Source

Support OpenSSL 1.1 and so TLSv1.3

master
aeris 3 months ago
parent
commit
7c48e9e150
31 changed files with 895 additions and 506 deletions
  1. +22
    -15
      Dockerfile
  2. +92
    -67
      Makefile
  3. +114
    -4
      bin/bundle
  4. +7
    -0
      bin/console
  5. +29
    -2
      bin/rspec
  6. +0
    -1
      bin/test
  7. +5
    -0
      bin/test
  8. +92
    -82
      bin/tls_server.rb
  9. +38
    -30
      cryptcheck.gemspec
  10. +3
    -2
      lib/cryptcheck.rb
  11. +0
    -52
      lib/cryptcheck/fixture.rb
  12. +0
    -1
      lib/cryptcheck/state.rb
  13. +2
    -0
      lib/cryptcheck/tls.rb
  14. +40
    -24
      lib/cryptcheck/tls/cipher.rb
  15. +1
    -8
      lib/cryptcheck/tls/curve.rb
  16. +32
    -25
      lib/cryptcheck/tls/engine.rb
  17. +0
    -185
      lib/cryptcheck/tls/fixture.rb
  18. +5
    -4
      lib/cryptcheck/tls/method.rb
  19. +23
    -0
      lib/fixtures/00_openssl.rb
  20. +11
    -0
      lib/fixtures/01_openssl/certificate.rb
  21. +71
    -0
      lib/fixtures/01_openssl/context.rb
  22. +46
    -0
      lib/fixtures/01_openssl/dh.rb
  23. +35
    -0
      lib/fixtures/01_openssl/dsa.rb
  24. +50
    -0
      lib/fixtures/01_openssl/ec.rb
  25. +47
    -0
      lib/fixtures/01_openssl/pkey.rb
  26. +44
    -0
      lib/fixtures/01_openssl/rsa.rb
  27. +27
    -0
      lib/fixtures/01_openssl/store.rb
  28. +20
    -0
      lib/fixtures/integer.rb
  29. +20
    -0
      lib/fixtures/string.rb
  30. +0
    -0
      lib/fixtures/tls.rb
  31. +19
    -4
      set-env

+ 22
- 15
Dockerfile View File

@@ -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/


+ 92
- 67
Makefile View File

@@ -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

+ 114
- 4
bin/bundle View File

@@ -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

+ 7
- 0
bin/console View File

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

Pry.start

+ 29
- 2
bin/rspec View File

@@ -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")

+ 0
- 1
bin/test View File

@@ -1 +0,0 @@
runner

+ 5
- 0
bin/test View File

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

+ 92
- 82
bin/tls_server.rb View File

@@ -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

+ 38
- 30
cryptcheck.gemspec View File

@@ -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

+ 3
- 2
lib/cryptcheck.rb View File

@@ -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 }

+ 0
- 52
lib/cryptcheck/fixture.rb View File

@@ -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

+ 0
- 1
lib/cryptcheck/state.rb View File

@@ -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


+ 2
- 0
lib/cryptcheck/tls.rb View File

@@ -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


+ 40
- 24
lib/cryptcheck/tls/cipher.rb View File

@@ -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


+ 1
- 8
lib/cryptcheck/tls/curve.rb View File

@@ -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



+ 32
- 25
lib/cryptcheck/tls/engine.rb View File

@@ -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


+ 0
- 185
lib/cryptcheck/tls/fixture.rb View File

@@ -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

+ 5
- 4
lib/cryptcheck/tls/method.rb View File

@@ -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


+ 23
- 0
lib/fixtures/00_openssl.rb View File

@@ -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

+ 11
- 0
lib/fixtures/01_openssl/certificate.rb View File

@@ -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

+ 71
- 0
lib/fixtures/01_openssl/context.rb View File

@@ -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,
TLSv1_2: ::OpenSSL::SSL::OP_NO_TLSv1_2,
TLSv1_1: ::OpenSSL::SSL::OP_NO_TLSv1_1,
TLSv1: ::OpenSSL::SSL::OP_NO_TLSv1,
SSL_3: ::OpenSSL::SSL::OP_NO_SSLv3,
SSL_2: ::OpenSSL::SSL::OP_NO_SSLv2
}.yield_self do |e|
all = e.values
e.collect do |m, o|
excludes = all - [o]
options = excludes.reduce :|
[m, options]
end.to_h
end.freeze

module Prepend
def initialize(method = nil)
super()
if method
self.options = EXCLUDES[method]
self.min_version = self.max_version = METHODS[method]
end
end
end

module ClassMethods
def supported?(method)
return false if %i[SSLv2 SSLv3].include? method
self.new method
true
rescue => e
ap e
false
end
end

def self.included(base)
base.extend ClassMethods
base.prepend Prepend
end
end
else
module Context
module ClassMethods
def supported?(method)
::OpenSSL::SSL::SSLContext::METHODS.include? method
end
end

def self.included(base)
base.extend ClassMethods
end
end
end
end
end

::OpenSSL::SSL::SSLContext.include Fixture::OpenSSL::Context

+ 46
- 0
lib/fixtures/01_openssl/dh.rb View File

@@ -0,0 +1,46 @@
module Fixture
module OpenSSL
module 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
end
end

::OpenSSL::PKey::DH.prepend Fixture::OpenSSL::DH

+ 35
- 0
lib/fixtures/01_openssl/dsa.rb View File

@@ -0,0 +1,35 @@
module Fixture
module OpenSSL
module 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
end
end

::OpenSSL::PKey::DSA.include Fixture::OpenSSL::DSA

+ 50
- 0
lib/fixtures/01_openssl/ec.rb View File

@@ -0,0 +1,50 @@
module Fixture
module OpenSSL
module 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
end
end

::OpenSSL::PKey::EC.include Fixture::OpenSSL::EC

+ 47
- 0
lib/fixtures/01_openssl/pkey.rb View File

@@ -0,0 +1,47 @@
require 'ostruct'

module Fixture
module OpenSSL
module PKey
def fingerprint
::OpenSSL::Digest::SHA256.hexdigest self.to_der
end

# Currently, Ruby doesn't support curve other than NIST ECC
# For X25519, we got a plain `PKey` instead of an `EC`
# We need to wait for https://github.com/ruby/openssl/pull/329 &
# https://github.com/ruby/openssl/pull/364 for more generic curve support
# So we supposed we have X25519 in case we catch a `PKey`

def type
:x25519
end

def size
128
end

def curve
:x25519
end

def to_s
"#{self.size} bits"
end

def to_h
{ type: :ecc, curve: self.curve, size: self.size, fingerprint: self.fingerprint, states: self.states }
end

include ::CryptCheck::State

CHECKS = [].freeze

def available_checks
CHECKS
end
end
end
end

::OpenSSL::PKey::PKey.include Fixture::OpenSSL::PKey

+ 44
- 0
lib/fixtures/01_openssl/rsa.rb View File

@@ -0,0 +1,44 @@
module Fixture
module OpenSSL
module 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
end
end

::OpenSSL::PKey::RSA.include Fixture::OpenSSL::RSA

+ 27
- 0
lib/fixtures/01_openssl/store.rb View File

@@ -0,0 +1,27 @@
module Fixture
module OpenSSL
module 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
end
end

::OpenSSL::X509::Store.include Fixture::OpenSSL::Store

+ 20
- 0
lib/fixtures/integer.rb View File

@@ -0,0 +1,20 @@
module Fixture
module 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
end

::Integer.include Fixture::Integer

+ 20
- 0
lib/fixtures/string.rb View File

@@ -0,0 +1,20 @@
module Fixture
module String
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
super color
end
end
end

String.prepend Fixture::String

+ 0
- 0
lib/fixtures/tls.rb View File


+ 19
- 4
set-env View File

@@ -1,5 +1,20 @@
DIR="$(dirname "$(readlink -e "${BASH_SOURCE:-$0}")")"
export LIBRARY_PATH="$DIR/lib"
export C_INCLUDE_PATH="$DIR/build/openssl/include"
export CPLUS_INCLUDE_PATH="$DIR/build/openssl/include"
export LD_LIBRARY_PATH="$DIR/lib"

case "$1" in
1.0)
OPENSSL_VERSION=1.0.2j
export RBENV_VERSION=2.3.8-cryptcheck
;;
1.1)
OPENSSL_VERSION=1.1.1g
export RBENV_VERSION=2.6.6-cryptcheck
;;
*)
echo "You must provide OpenSSL version to use: 1.0 or 1.1"
exit -1
;;
esac

OPENSSL_PATH="$DIR/build/openssl-$OPENSSL_VERSION"
export LIBRARY_PATH="$OPENSSL_PATH" C_INCLUDE_PATH="$OPENSSL_PATH/include"
export LD_LIBRARY_PATH="$LIBRARY_PATH"

Loading…
Cancel
Save