Skip to content

Instantly share code, notes, and snippets.

@rdp
Created December 8, 2019 07:02
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/35bec8cd7e8349985bd560f243d93dca to your computer and use it in GitHub Desktop.
Save rdp/35bec8cd7e8349985bd560f243d93dca to your computer and use it in GitHub Desktop.
iff --git a/spec/std/openssl/ssl/socket_spec.cr b/spec/std/openssl/ssl/socket_spec.cr
index 1b6675327..9ba587f1f 100644
--- a/spec/std/openssl/ssl/socket_spec.cr
+++ b/spec/std/openssl/ssl/socket_spec.cr
@@ -20,6 +20,27 @@ describe OpenSSL::SSL::Socket do
end
end
+ it "accepts clients that don't read anything and close the connection" do
+ tcp_server = TCPServer.new(0)
+ server_context, client_context = ssl_context_pair
+ # in tls 1.3, if clients don't read anything and close the connection
+ # the server might still try and send it a ticket, resulting in a pipe failure
+ server_context.disable_session_resume_tickets
+
+ OpenSSL::SSL::Server.open(tcp_server, server_context) do |server|
+ spawn do
+ # the :sync_close portion, as implemented, effects a socket close from the client
+ OpenSSL::SSL::Socket::Client.open(TCPSocket.new(tcp_server.local_address.address, tcp_server.local_address.port), client_context, hostname: "example.com", sync_close: true) do |socket|
+ # doesn't read anything, just write and close connection immediately
+ socket.puts "hello"
+ end
+ end
+
+ client = server.accept # shouldn't raise
+ client.close
+ end
+ end
+
it "returns the TLS version" do
tcp_server = TCPServer.new(0)
server_context, client_context = ssl_context_pair
@@ -39,6 +60,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
client_successfully_closed_socket = Channel(Nil).new
spawn do
diff --git a/src/openssl/lib_ssl.cr b/src/openssl/lib_ssl.cr
index f663c3ea4..455e83c41 100644
--- a/src/openssl/lib_ssl.cr
+++ b/src/openssl/lib_ssl.cr
@@ -206,6 +206,12 @@ 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*)
+ # control TLS 1.3 session ticket generation
+ {% if compare_versions(OPENSSL_VERSION, "1.1.1") >= 0 %}
+ fun ssl_ctx_set_num_tickets = SSL_CTX_set_num_tickets(ctx : SSLContext, larg : LibC::SizeT) : Int
+ fun ssl_set_num_tickets = SSL_set_num_tickets(ctx : SSL, larg : LibC::SizeT) : Int
+ {% end %}
+
{% if compare_versions(OPENSSL_VERSION, "1.1.0") >= 0 %}
fun tls_method = TLS_method : SSLMethod
{% else %}
diff --git a/src/openssl/ssl/context.cr b/src/openssl/ssl/context.cr
index cad1934b6..e0eea9e6f 100644
--- a/src/openssl/ssl/context.cr
+++ b/src/openssl/ssl/context.cr
@@ -191,6 +191,20 @@ abstract class OpenSSL::SSL::Context
def self.from_hash(params) : self
super(params)
end
+
+ # Disables all session ticket generation for this context.
+ # Tickets are used to resume earlier sessions more quickly,
+ # but in TLS 1.3 if the client connects, sends data, and closes the connection
+ # unidirectionally, the server connects, then sends a ticket
+ # after the connect handshake, the ticket send can fail with Broken Pipe.
+ # So if you have that kind of behavior (clients that never read) call this method.
+ def disable_session_resume_tickets
+ add_options(OpenSSL::SSL::Options::NO_TICKET) # TLS v1.2 and below
+ {% if compare_versions(LibSSL::OPENSSL_VERSION, "1.1.1") >= 0 %}
+ out = LibSSL.ssl_ctx_set_num_tickets(self, 0) # TLS v1.3
+ raise OpenSSL::Error.new("SSL_CTX_set_num_tickets") if out != 1
+ {% end %}
+ end
end
protected def initialize(method : LibSSL::SSLMethod)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment