Skip to content

Instantly share code, notes, and snippets.

@rdp
Created December 13, 2019 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rdp/2b867a5b9e3c0a56368833f2b95878bd to your computer and use it in GitHub Desktop.
Save rdp/2b867a5b9e3c0a56368833f2b95878bd to your computer and use it in GitHub Desktop.
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
@rdp
Copy link
Author

rdp commented Dec 13, 2019

comment on wrong gist?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment