Skip to content

Commit

Permalink
Fix: detect OpenSSL/LibreSSL from official C headers
Browse files Browse the repository at this point in the history
Detecting the OpenSSL version using pkg-config is prone to bugs when
a pkg-config definition for libssl or libcrypto isn't available.
Using a C preprocessor to extract the official
OPENSSL_VERSION_NUMBER and LIBRESSL_VERSION_NUMBER constants from
the openssl/opensslv.h header.

Version numbers are less descriptive than their text counterparts,
but they should always be correct.
  • Loading branch information
ysbaddaden committed Jan 1, 2019
1 parent 954c960 commit 150c501
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 55 deletions.
6 changes: 3 additions & 3 deletions spec/std/openssl/ssl/context_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ describe OpenSSL::SSL::Context do
context.should be_a(OpenSSL::SSL::Context::Client)
context.verify_mode.should eq(OpenSSL::SSL::VerifyMode::NONE)
context.options.no_ssl_v3?.should_not be_true
{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.1") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1010100f %} # openssl 1.1.1
context.modes.should eq(OpenSSL::SSL::Modes::AUTO_RETRY)
{% else %}
context.modes.should eq(OpenSSL::SSL::Modes::None)
Expand All @@ -67,7 +67,7 @@ describe OpenSSL::SSL::Context do
context.should be_a(OpenSSL::SSL::Context::Server)
context.verify_mode.should eq(OpenSSL::SSL::VerifyMode::NONE)
context.options.no_ssl_v3?.should_not be_true
{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.1") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1010100f %} # openssl 1.1.1
context.modes.should eq(OpenSSL::SSL::Modes::AUTO_RETRY)
{% else %}
context.modes.should eq(OpenSSL::SSL::Modes::None)
Expand Down Expand Up @@ -162,7 +162,7 @@ describe OpenSSL::SSL::Context do
context.verify_mode.should eq(OpenSSL::SSL::VerifyMode::PEER)
end

{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1000200f || LibSSL::LIBRESSL_VERSION_NUMBER >= 0x2050000f %} # openssl 1.0.2 || libressl 2.5.0
it "alpn_protocol=" do
context = OpenSSL::SSL::Context::Client.insecure
context.alpn_protocol = "h2"
Expand Down
2 changes: 1 addition & 1 deletion src/openssl.cr
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module OpenSSL
alias Options = LibSSL::Options
alias VerifyMode = LibSSL::VerifyMode
alias ErrorType = LibSSL::SSLError
{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1000200f %} # openssl 1.0.2
alias X509VerifyFlags = LibCrypto::X509VerifyFlags
{% end %}

Expand Down
10 changes: 5 additions & 5 deletions src/openssl/bio.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ require "./lib_crypto"
# :nodoc:
struct OpenSSL::BIO
def self.get_data(bio) : Void*
{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if LibCrypto::OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
LibCrypto.BIO_get_data(bio)
{% else %}
bio.value.ptr
{% end %}
end

def self.set_data(bio, data : Void*)
{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if LibCrypto::OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
LibCrypto.BIO_set_data(bio, data)
{% else %}
bio.value.ptr = data
Expand Down Expand Up @@ -65,7 +65,7 @@ struct OpenSSL::BIO
end

create = LibCrypto::BioMethodCreate.new do |bio|
{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if LibCrypto::OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
LibCrypto.BIO_set_shutdown(bio, 1)
LibCrypto.BIO_set_init(bio, 1)
# bio.value.num = -1
Expand All @@ -82,10 +82,10 @@ struct OpenSSL::BIO
1
end

{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if LibCrypto::OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
biom = LibCrypto.BIO_meth_new(Int32::MAX, "Crystal BIO")

{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.1") >= 0 %}
{% if LibCrypto::OPENSSL_VERSION_NUMBER >= 0x1010100f %} # openssl 1.1.1
LibCrypto.BIO_meth_set_write_ex(biom, bwrite_ex)
LibCrypto.BIO_meth_set_read_ex(biom, bread_ex)
{% else %}
Expand Down
27 changes: 8 additions & 19 deletions src/openssl/lib_crypto.cr
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
{% begin %}
lib LibCrypto
{% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") &&
(`test -f $(pkg-config --silence-errors --variable=includedir libcrypto)/openssl/opensslv.h || printf %s false` != "false") &&
(`printf "#include <openssl/opensslv.h>\nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libcrypto || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
{% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libcrypto || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %}

{% if from_libressl %}
LIBRESSL_VERSION = {{ ssl_version }}
OPENSSL_VERSION = "0.0.0"
{% else %}
LIBRESSL_VERSION = "0.0.0"
OPENSSL_VERSION = {{ ssl_version }}
{% end %}
{{ `#{__DIR__ }/version.sh libcrypto` }}
end
{% end %}

Expand Down Expand Up @@ -66,7 +55,7 @@ lib LibCrypto
alias BioMethodDestroy = Bio* -> Int
alias BioMethodCallbackCtrl = (Bio*, Int, Void*) -> Long

{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
type BioMethod = Void
{% else %}
struct BioMethod
Expand All @@ -90,7 +79,7 @@ lib LibCrypto
fun BIO_set_init(Bio*, Int)
fun BIO_set_shutdown(Bio*, Int)

{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
fun BIO_meth_new(Int, Char*) : BioMethod*
fun BIO_meth_set_read(BioMethod*, BioMethodReadOld)
fun BIO_meth_set_write(BioMethod*, BioMethodWriteOld)
Expand All @@ -101,7 +90,7 @@ lib LibCrypto
fun BIO_meth_set_destroy(BioMethod*, BioMethodDestroy)
fun BIO_meth_set_callback_ctrl(BioMethod*, BioMethodCallbackCtrl)
{% end %}
{% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.1") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1010100f %} # openssl 1.1.1
fun BIO_meth_set_read_ex(BioMethod*, BioMethodRead)
fun BIO_meth_set_write_ex(BioMethod*, BioMethodWrite)
{% end %}
Expand Down Expand Up @@ -174,7 +163,7 @@ lib LibCrypto
fun evp_md_block_size = EVP_MD_block_size(md : EVP_MD) : LibC::Int
fun evp_digestfinal_ex = EVP_DigestFinal_ex(ctx : EVP_MD_CTX, md : UInt8*, size : UInt32*) : Int32

{% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
fun evp_md_ctx_new = EVP_MD_CTX_new : EVP_MD_CTX
fun evp_md_ctx_free = EVP_MD_CTX_free(ctx : EVP_MD_CTX)
{% else %}
Expand Down Expand Up @@ -242,7 +231,7 @@ lib LibCrypto
NID_commonName = 13
NID_subject_alt_name = 85

{% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
fun sk_free = OPENSSL_sk_free(st : Void*)
fun sk_num = OPENSSL_sk_num(x0 : Void*) : Int
fun sk_pop_free = OPENSSL_sk_pop_free(st : Void*, callback : (Void*) ->)
Expand Down Expand Up @@ -287,12 +276,12 @@ lib LibCrypto
fun x509v3_ext_nconf_nid = X509V3_EXT_nconf_nid(conf : Void*, ctx : Void*, ext_nid : Int, value : Char*) : X509_EXTENSION
fun x509v3_ext_print = X509V3_EXT_print(out : Bio*, ext : X509_EXTENSION, flag : Int, indent : Int) : Int

{% unless compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER < 0x1010000f %} # openssl < 1.1.0
fun err_load_crypto_strings = ERR_load_crypto_strings
fun openssl_add_all_algorithms = OPENSSL_add_all_algorithms_noconf
{% end %}

{% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1000200f %} # openssl 1.0.2
type X509VerifyParam = Void*

@[Flags]
Expand Down
25 changes: 7 additions & 18 deletions src/openssl/lib_ssl.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,7 @@ require "./lib_crypto"

{% begin %}
lib LibSSL
{% from_libressl = (`hash pkg-config 2> /dev/null || printf %s false` != "false") &&
(`test -f $(pkg-config --silence-errors --variable=includedir libssl)/openssl/opensslv.h || printf %s false` != "false") &&
(`printf "#include <openssl/opensslv.h>\nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $(pkg-config --cflags --silence-errors libssl || true) -E -`.chomp.split('\n').last != "LIBRESSL_VERSION_NUMBER") %}
{% ssl_version = `hash pkg-config 2> /dev/null && pkg-config --silence-errors --modversion libssl || printf %s 0.0.0`.split.last.gsub(/[^0-9.]/, "") %}

{% if from_libressl %}
LIBRESSL_VERSION = {{ ssl_version }}
OPENSSL_VERSION = "0.0.0"
{% else %}
LIBRESSL_VERSION = "0.0.0"
OPENSSL_VERSION = {{ ssl_version }}
{% end %}
{{ `#{__DIR__ }/version.sh libssl` }}
end
{% end %}

Expand Down Expand Up @@ -102,7 +91,7 @@ lib LibSSL
NETSCAPE_DEMO_CIPHER_CHANGE_BUG = 0x40000000
CRYPTOPRO_TLSEXT_BUG = 0x80000000

{% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
MICROSOFT_SESS_ID_BUG = 0x00000000
NETSCAPE_CHALLENGE_BUG = 0x00000000
NETSCAPE_REUSE_CIPHER_CHANGE_BUG = 0x00000000
Expand Down Expand Up @@ -188,7 +177,7 @@ lib LibSSL
fun ssl_ctx_set_default_verify_paths = SSL_CTX_set_default_verify_paths(ctx : SSLContext) : Int
fun ssl_ctx_ctrl = SSL_CTX_ctrl(ctx : SSLContext, cmd : Int, larg : ULong, parg : Void*) : ULong

{% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
fun ssl_ctx_get_options = SSL_CTX_get_options(ctx : SSLContext) : ULong
fun ssl_ctx_set_options = SSL_CTX_set_options(ctx : SSLContext, larg : ULong) : ULong
fun ssl_ctx_clear_options = SSL_CTX_clear_options(ctx : SSLContext, larg : ULong) : ULong
Expand All @@ -200,22 +189,22 @@ lib LibSSL
# Hostname validation for OpenSSL <= 1.0.1
fun ssl_ctx_set_cert_verify_callback = SSL_CTX_set_cert_verify_callback(ctx : SSLContext, callback : CertVerifyCallback, arg : Void*)

{% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >=0x1010000f %} # openssl 1.1.0
fun tls_method = TLS_method : SSLMethod
{% else %}
fun ssl_library_init = SSL_library_init
fun ssl_load_error_strings = SSL_load_error_strings
fun sslv23_method = SSLv23_method : SSLMethod
{% end %}

{% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 || compare_versions(LIBRESSL_VERSION, "2.5.0") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1000200f || LIBRESSL_VERSION_NUMBER >= 0x2050000f %} # openssl 1.0.2 || libressl 2.5.0
alias ALPNCallback = (SSL, Char**, Char*, Char*, Int, Void*) -> Int

fun ssl_get0_alpn_selected = SSL_get0_alpn_selected(handle : SSL, data : Char**, len : LibC::UInt*) : Void
fun ssl_ctx_set_alpn_select_cb = SSL_CTX_set_alpn_select_cb(ctx : SSLContext, cb : ALPNCallback, arg : Void*) : Void
{% end %}

{% if compare_versions(OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if OPENSSL_VERSION_NUMBER >= 0x1000200f %} # openssl 1.0.2
alias X509VerifyParam = LibCrypto::X509VerifyParam

fun ssl_get0_param = SSL_get0_param(handle : SSL) : X509VerifyParam
Expand All @@ -224,7 +213,7 @@ lib LibSSL
{% end %}
end

{% unless compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER < 0x1010000f %}
LibSSL.ssl_library_init
LibSSL.ssl_load_error_strings
LibCrypto.openssl_add_all_algorithms
Expand Down
14 changes: 7 additions & 7 deletions src/openssl/ssl/context.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ require "uri/punycode"
abstract class OpenSSL::SSL::Context
# :nodoc:
def self.default_method
{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
LibSSL.tls_method
{% else %}
LibSSL.sslv23_method
Expand Down Expand Up @@ -81,7 +81,7 @@ abstract class OpenSSL::SSL::Context
super(method)

self.verify_mode = OpenSSL::SSL::VerifyMode::PEER
{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1000200f %} # openssl 1.0.2
self.default_verify_param = "ssl_server"
{% end %}
end
Expand Down Expand Up @@ -157,7 +157,7 @@ abstract class OpenSSL::SSL::Context
super(method)

add_options(OpenSSL::SSL::Options::CIPHER_SERVER_PREFERENCE)
{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1000200f %} # openssl 1.0.2
self.default_verify_param = "ssl_client"
{% end %}

Expand Down Expand Up @@ -301,7 +301,7 @@ abstract class OpenSSL::SSL::Context

# Returns the current options set on the TLS context.
def options
opts = {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %}
opts = {% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
LibSSL.ssl_ctx_get_options(@handle)
{% else %}
LibSSL.ssl_ctx_ctrl(@handle, LibSSL::SSL_CTRL_OPTIONS, 0, nil)
Expand All @@ -320,7 +320,7 @@ abstract class OpenSSL::SSL::Context
# )
# ```
def add_options(options : OpenSSL::SSL::Options)
opts = {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %}
opts = {% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
LibSSL.ssl_ctx_set_options(@handle, options)
{% else %}
LibSSL.ssl_ctx_ctrl(@handle, LibSSL::SSL_CTRL_OPTIONS, options, nil)
Expand All @@ -335,7 +335,7 @@ abstract class OpenSSL::SSL::Context
# context.remove_options(OpenSSL::SSL::Options::NO_SSL_V3)
# ```
def remove_options(options : OpenSSL::SSL::Options)
opts = {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.0") >= 0 %}
opts = {% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1010000f %} # openssl 1.1.0
LibSSL.ssl_ctx_clear_options(@handle, options)
{% else %}
LibSSL.ssl_ctx_ctrl(@handle, LibSSL::SSL_CTRL_CLEAR_OPTIONS, options, nil)
Expand All @@ -353,7 +353,7 @@ abstract class OpenSSL::SSL::Context
LibSSL.ssl_ctx_set_verify(@handle, mode, nil)
end

{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1000200f %} # openssl 1.0.2

@alpn_protocol : Pointer(Void)?

Expand Down
4 changes: 2 additions & 2 deletions src/openssl/ssl/socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ abstract class OpenSSL::SSL::Socket < IO
hostname.to_unsafe.as(Pointer(Void))
)

{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1000200f %} # openssl 1.0.2
param = LibSSL.ssl_get0_param(@ssl)

if ::Socket.ip?(hostname)
Expand Down Expand Up @@ -129,7 +129,7 @@ abstract class OpenSSL::SSL::Socket < IO
@bio.io.flush
end

{% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %}
{% if LibSSL::OPENSSL_VERSION_NUMBER >= 0x1000200f %} # openssl 1.0.2
# Returns the negotiated ALPN protocol (eg: `"h2"`) of `nil` if no protocol was
# negotiated.
def alpn_protocol
Expand Down
13 changes: 13 additions & 0 deletions src/openssl/version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#! /bin/sh

CFLAGS=`command -v pkg-config > /dev/null && pkg-config --cflags --silence-errors $1 || true`
LIBRESSL_VERSION_NUMBER=$(printf "#include <openssl/opensslv.h>\nLIBRESSL_VERSION_NUMBER" | ${CC:-cc} $CFLAGS -E - | tail -n 1)
OPENSSL_VERSION_NUMBER=$(printf "#include <openssl/opensslv.h>\nOPENSSL_VERSION_NUMBER" | ${CC:-cc} $CFLAGS -E - | tail -n 1)

if [ $LIBRESSL_VERSION_NUMBER = LIBRESSL_VERSION_NUMBER ]; then
echo LIBRESSL_VERSION_NUMBER = 0
echo OPENSSL_VERSION_NUMBER = $(echo $OPENSSL_VERSION_NUMBER | sed 's/L//')
else
echo LIBRESSL_VERSION_NUMBER = $(echo $LIBRESSL_VERSION_NUMBER | sed 's/L//')
echo OPENSSL_VERSION_NUMBER = 0
fi

0 comments on commit 150c501

Please sign in to comment.