From fcf16e715ae1a7f51375a8acbb6934d0d9ce8bd6 Mon Sep 17 00:00:00 2001 From: Omar Roth Date: Fri, 15 Nov 2019 21:22:20 -0500 Subject: [PATCH] Add patch for BoringSSL --- src/lsquic.cr | 3 +- src/lsquic/liblsquic.cr | 4 +- src/lsquic/patch.cr | 185 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 src/lsquic/patch.cr diff --git a/src/lsquic.cr b/src/lsquic.cr index 565615d..2678513 100644 --- a/src/lsquic.cr +++ b/src/lsquic.cr @@ -1,5 +1,4 @@ require "./lsquic/*" -# require "http/headers" require "http" require "socket" @@ -17,6 +16,8 @@ HEADERS = HTTP::Headers{ # "content-length" => "0", } +pp HTTP::Client.get("https://www.youtube.com#{PATH}").status_code + # logger_if = LibLsquic::LoggerIf.new # logger_if.log_buf = ->(logger_ctx : Void*, msg_buf : LibC::Char*, msg_size : LibC::SizeT) { puts String.new(msg_buf); 0 } # LibLsquic.logger_init(pointerof(logger_if), nil, LibLsquic::LoggerTimestampStyle::LltsHhmmssms) diff --git a/src/lsquic/liblsquic.cr b/src/lsquic/liblsquic.cr index 8d86248..efd5e55 100644 --- a/src/lsquic/liblsquic.cr +++ b/src/lsquic/liblsquic.cr @@ -1,4 +1,4 @@ -@[Link(ldflags: "#{__DIR__}/ext/liblsquic.a #{__DIR__}/ext/libssl.a #{__DIR__}/ext/libcrypto.a -lz")] +@[Link(ldflags: "#{__DIR__}/ext/liblsquic.a")] lib LibLsquic MAX_CID_LEN = 20 QQUIC_CID_LEN = 8 @@ -375,3 +375,5 @@ lib LibLsquic end $ver2str : LibC::Char*[6] end + +require "./patch" diff --git a/src/lsquic/patch.cr b/src/lsquic/patch.cr new file mode 100644 index 0000000..4e4628c --- /dev/null +++ b/src/lsquic/patch.cr @@ -0,0 +1,185 @@ +require "openssl/lib_ssl" +require "openssl/lib_crypto" +require "openssl/bio" + +@[Link(ldflags: "#{__DIR__}/ext/liblsquic.a #{__DIR__}/ext/libcrypto.a")] +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* +end + +@[Link(ldflags: "#{__DIR__}/ext/libssl.a")] +lib LibSSL + 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 +end + +require "openssl/ssl/context" + +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 +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 + +require "openssl/ssl/socket" + +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