Created
December 13, 2019 14:48
-
-
Save rdp/2b867a5b9e3c0a56368833f2b95878bd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/spec/std/openssl/ssl/socket_spec.cr b/spec/std/openssl/ssl/socket_spec.cr | |
index df3a34a61..b1d931c34 100644 | |
--- a/spec/std/openssl/ssl/socket_spec.cr | |
+++ b/spec/std/openssl/ssl/socket_spec.cr | |
@@ -42,7 +42,7 @@ describe OpenSSL::SSL::Socket do | |
# in tls 1.3, if clients don't read anything and close the connection | |
# the server still try and write to it a ticket, resulting in a "pipe failure" | |
# this context method disables the tickets which allows the behavior: | |
- server_context.disable_session_resume_tickets | |
+ #server_context.disable_session_resume_tickets | |
OpenSSL::SSL::Server.open(tcp_server, server_context) do |server| | |
spawn do | |
@@ -61,7 +61,7 @@ describe OpenSSL::SSL::Socket do | |
it "closes connection to server that doesn't properly terminate SSL session" do | |
tcp_server = TCPServer.new(0) | |
server_context, client_context = ssl_context_pair | |
- server_context.disable_session_resume_tickets # avoid Broken pipe | |
+ #server_context.disable_session_resume_tickets # avoid Broken pipe | |
client_successfully_closed_socket = Channel(Nil).new | |
spawn do | |
@@ -77,4 +77,24 @@ describe OpenSSL::SSL::Socket do | |
socket.close | |
client_successfully_closed_socket.send(nil) | |
end | |
+ | |
+ it "interprets graceful EOF of underlying socket as SSL termination" do | |
+ tcp_server = TCPServer.new(0) | |
+ server_context, client_context = ssl_context_pair | |
+ | |
+ server_finished_reading = Channel(String).new | |
+ spawn do | |
+ OpenSSL::SSL::Server.open(tcp_server, server_context, sync_close: true) do |server| | |
+ server_socket = server.accept | |
+ received = server_socket.gets_to_end # interprets underlying socket close as a graceful EOF | |
+ server_finished_reading.send(received) | |
+ end | |
+ end | |
+ socket = TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port) | |
+ socket_ssl = OpenSSL::SSL::Socket::Client.new(socket, client_context, hostname: "example.com", sync_close: true) | |
+ socket_ssl.print "hello" | |
+ socket_ssl.flush # needed today see #5375 | |
+ socket.close # close underlying socket without gracefully shutting down SSL at all | |
+ server_finished_reading.receive().should eq("hello") | |
+ end | |
end | |
diff --git a/src/openssl.cr b/src/openssl.cr | |
index 93b676f6a..307f7ce4c 100644 | |
--- a/src/openssl.cr | |
+++ b/src/openssl.cr | |
@@ -96,6 +96,7 @@ module OpenSSL | |
class Error < OpenSSL::Error | |
getter error : ErrorType | |
+ getter? underlying_eof : Bool = false | |
def initialize(ssl : LibSSL::SSL, return_code : LibSSL::Int, func = nil) | |
@error = LibSSL.ssl_get_error(ssl, return_code) | |
@@ -109,6 +110,7 @@ module OpenSSL | |
case return_code | |
when 0 | |
message = "Unexpected EOF" | |
+ @underlying_eof = true | |
when -1 | |
cause = Errno.new(func || "OpenSSL") | |
message = "I/O error" | |
diff --git a/src/openssl/ssl/socket.cr b/src/openssl/ssl/socket.cr | |
index f3557f2a3..3ab50a8db 100644 | |
--- a/src/openssl/ssl/socket.cr | |
+++ b/src/openssl/ssl/socket.cr | |
@@ -115,7 +115,13 @@ abstract class OpenSSL::SSL::Socket < IO | |
LibSSL.ssl_read(@ssl, slice.to_unsafe, count).tap do |bytes| | |
if bytes <= 0 && !LibSSL.ssl_get_error(@ssl, bytes).zero_return? | |
- raise OpenSSL::SSL::Error.new(@ssl, bytes, "SSL_read") | |
+ ex = OpenSSL::SSL::Error.new(@ssl, bytes, "SSL_read") | |
+ if ex.underlying_eof? | |
+ # underlying BIO termianted gracefully, without terminating SSL aspect gracefully first | |
+ # some misbehaving servers "do this" so treat as EOF even though it's a protocol error | |
+ return 0 | |
+ end | |
+ raise ex | |
end | |
end | |
end | |
@@ -155,7 +161,10 @@ abstract class OpenSSL::SSL::Socket < IO | |
begin | |
ret = LibSSL.ssl_shutdown(@ssl) | |
break if ret == 1 # done bidirectional | |
- break if ret == 0 && sync_close? # done unidirectional, "this first successful call to SSL_shutdown() is sufficient" | |
+ if ret == 0 && sync_close? # done unidirectional, "this first successful call to SSL_shutdown() is sufficient" | |
+ #unbuffered_read(Bytes.new(0)) | |
+ break | |
+ end | |
raise OpenSSL::SSL::Error.new(@ssl, ret, "SSL_shutdown") if ret < 0 | |
rescue e : OpenSSL::SSL::Error | |
case e.error |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
comment on wrong gist?