require "openssl" lib LibCrypto fun evp_ripemd160 = EVP_sha1 : EVP_MD fun sk_free = sk_free(st : Void*) fun sk_num = sk_num(x0 : Void*) : Int fun sk_pop_free = sk_pop_free(st : Void*, callback : (Void*) ->) fun sk_value = sk_value(x0 : Void*, x1 : Int) : Void* fun openssl_add_all_algorithms = rand : LibC::Int fun err_load_crypto_strings = rand : LibC::Int end lib LibSSL {% ssl_version = "1.1.0" %} fun ssl_set_tlsext_host_name = SSL_set_tlsext_host_name(handle : SSL, name : Char*) : Long fun ssl_ctx_set_tmp_ecdh = SSL_CTX_set_tmp_ecdh(ctx : SSLContext, parg : Void*) : ULong fun ssl_ctx_get_mode = SSL_CTX_get_mode(ctx : SSLContext) : ULong fun ssl_ctx_set_mode = SSL_CTX_set_mode(ctx : SSLContext, mode : ULong) : ULong fun ssl_ctx_clear_mode = SSL_CTX_clear_mode(ctx : SSLContext, mode : ULong) : ULong 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 fun ssl_library_init = rand : LibC::Int fun ssl_load_error_strings = rand : LibC::Int end struct OpenSSL::BIO CRYSTAL_BIO_BORING = begin bwrite = LibCrypto::BioMethodWriteOld.new do |bio, data, len| io = Box(IO).unbox(BIO.get_data(bio)) io.write Slice.new(data, len) len end bwrite_ex = LibCrypto::BioMethodWrite.new do |bio, data, len, writep| count = len > Int32::MAX ? Int32::MAX : len.to_i io = Box(IO).unbox(BIO.get_data(bio)) io.write Slice.new(data, count) writep.value = LibC::SizeT.new(count) 1 end bread = LibCrypto::BioMethodReadOld.new do |bio, buffer, len| io = Box(IO).unbox(BIO.get_data(bio)) io.flush io.read(Slice.new(buffer, len)).to_i end bread_ex = LibCrypto::BioMethodWrite.new do |bio, buffer, len, readp| count = len > Int32::MAX ? Int32::MAX : len.to_i io = Box(IO).unbox(BIO.get_data(bio)) io.flush ret = io.read Slice.new(buffer, count) readp.value = LibC::SizeT.new(ret) 1 end ctrl = LibCrypto::BioMethodCtrl.new do |bio, cmd, num, ptr| io = Box(IO).unbox(BIO.get_data(bio)) val = case cmd when LibCrypto::CTRL_FLUSH io.flush 1 when LibCrypto::CTRL_PUSH, LibCrypto::CTRL_POP 0 else STDERR.puts "WARNING: Unsupported BIO ctrl call (#{cmd})" 0 end LibCrypto::Long.new(val) end create = LibCrypto::BioMethodCreate.new do |bio| {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %} LibCrypto.BIO_set_shutdown(bio, 1) LibCrypto.BIO_set_init(bio, 1) # bio.value.num = -1 {% else %} bio.value.shutdown = 1 bio.value.init = 1 bio.value.num = -1 {% end %} 1 end destroy = LibCrypto::BioMethodDestroy.new do |bio| BIO.set_data(bio, Pointer(Void).null) 1 end {% if compare_versions(LibCrypto::OPENSSL_VERSION, "1.1.0") >= 0 %} biom = LibCrypto.BIO_meth_new(Int32::MAX, "Crystal BIO") LibCrypto.BIO_meth_set_write(biom, bwrite) LibCrypto.BIO_meth_set_read(biom, bread) LibCrypto.BIO_meth_set_ctrl(biom, ctrl) LibCrypto.BIO_meth_set_create(biom, create) LibCrypto.BIO_meth_set_destroy(biom, destroy) biom {% else %} biom = Pointer(LibCrypto::BioMethod).malloc(1) biom.value.type_id = Int32::MAX biom.value.name = "Crystal BIO" biom.value.bwrite = bwrite biom.value.bread = bread biom.value.ctrl = ctrl biom.value.create = create biom.value.destroy = destroy biom {% end %} end def initialize(@io : IO) @bio = LibCrypto.BIO_new(CRYSTAL_BIO_BORING) # We need to store a reference to the box because it's # stored in `@bio.value.ptr`, but that lives in C-land, # not in Crystal-land. @boxed_io = Box(IO).box(io) BIO.set_data(@bio, @boxed_io) end end abstract class OpenSSL::SSL::Context def set_tmp_ecdh_key(curve = LibCrypto::NID_X9_62_prime256v1) key = LibCrypto.ec_key_new_by_curve_name(curve) raise OpenSSL::Error.new("ec_key_new_by_curve_name") if key.null? LibSSL.ssl_ctx_set_tmp_ecdh(@handle, key) LibCrypto.ec_key_free(key) end # Returns the current modes set on the TLS context. def modes OpenSSL::SSL::Modes.new LibSSL.ssl_ctx_get_mode(@handle) end # Adds modes to the TLS context. def add_modes(mode : OpenSSL::SSL::Modes) OpenSSL::SSL::Modes.new LibSSL.ssl_ctx_set_mode(@handle, mode) end # Removes modes from the TLS context. def remove_modes(mode : OpenSSL::SSL::Modes) OpenSSL::SSL::Modes.new LibSSL.ssl_ctx_clear_mode(@handle, mode) end # Returns the current options set on the TLS context. def options OpenSSL::SSL::Options.new LibSSL.ssl_ctx_get_options(@handle) end def add_options(options : OpenSSL::SSL::Options) OpenSSL::SSL::Options.new LibSSL.ssl_ctx_set_options(@handle, options) end def remove_options(options : OpenSSL::SSL::Options) OpenSSL::SSL::Options.new LibSSL.ssl_ctx_clear_options(@handle, options) end end abstract class OpenSSL::SSL::Socket < IO class Client < Socket def initialize(io, context : Context::Client = Context::Client.new, sync_close : Bool = false, hostname : String? = nil) super(io, context, sync_close) begin if hostname LibSSL.ssl_set_tlsext_host_name(@ssl, hostname) {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.0.2") >= 0 %} param = LibSSL.ssl_get0_param(@ssl) if ::Socket.ip?(hostname) unless LibCrypto.x509_verify_param_set1_ip_asc(param, hostname) == 1 raise OpenSSL::Error.new("X509_VERIFY_PARAM_set1_ip_asc") end else unless LibCrypto.x509_verify_param_set1_host(param, hostname, hostname.bytesize) == 1 raise OpenSSL::Error.new("X509_VERIFY_PARAM_set1_host") end end {% else %} context.set_cert_verify_callback(hostname) {% end %} end ret = LibSSL.ssl_connect(@ssl) unless ret == 1 raise OpenSSL::SSL::Error.new(@ssl, ret, "SSL_connect") end rescue ex LibSSL.ssl_free(@ssl) # GC never calls finalize, avoid mem leak raise ex end end end end