4
4
import unittest
5
5
import unittest .mock
6
6
from test import support
7
+ import re
7
8
import socket
8
9
import select
10
+ import struct
9
11
import time
10
12
import datetime
11
13
import gc
14
+ import http .client
12
15
import os
13
16
import errno
14
17
import pprint
@@ -4815,6 +4818,218 @@ def sni_cb(sock, servername, ctx):
4815
4818
s .connect ((HOST , server .port ))
4816
4819
4817
4820
4821
+ def set_socket_so_linger_on_with_zero_timeout (sock ):
4822
+ sock .setsockopt (socket .SOL_SOCKET , socket .SO_LINGER , struct .pack ('ii' , 1 , 0 ))
4823
+
4824
+
4825
+ class TestPreHandshakeClose (unittest .TestCase ):
4826
+ """Verify behavior of close sockets with received data before to the handshake.
4827
+ """
4828
+
4829
+ class SingleConnectionTestServerThread (threading .Thread ):
4830
+
4831
+ def __init__ (self , * , name , call_after_accept ):
4832
+ self .call_after_accept = call_after_accept
4833
+ self .received_data = b'' # set by .run()
4834
+ self .wrap_error = None # set by .run()
4835
+ self .listener = None # set by .start()
4836
+ self .port = None # set by .start()
4837
+ super ().__init__ (name = name )
4838
+
4839
+ def __enter__ (self ):
4840
+ self .start ()
4841
+ return self
4842
+
4843
+ def __exit__ (self , * args ):
4844
+ try :
4845
+ if self .listener :
4846
+ self .listener .close ()
4847
+ except OSError :
4848
+ pass
4849
+ self .join ()
4850
+ self .wrap_error = None # avoid dangling references
4851
+
4852
+ def start (self ):
4853
+ self .ssl_ctx = ssl .create_default_context (ssl .Purpose .CLIENT_AUTH )
4854
+ self .ssl_ctx .verify_mode = ssl .CERT_REQUIRED
4855
+ self .ssl_ctx .load_verify_locations (cafile = ONLYCERT )
4856
+ self .ssl_ctx .load_cert_chain (certfile = ONLYCERT , keyfile = ONLYKEY )
4857
+ self .listener = socket .socket ()
4858
+ self .port = support .bind_port (self .listener )
4859
+ self .listener .settimeout (2.0 )
4860
+ self .listener .listen (1 )
4861
+ super ().start ()
4862
+
4863
+ def run (self ):
4864
+ conn , address = self .listener .accept ()
4865
+ self .listener .close ()
4866
+ with conn :
4867
+ if self .call_after_accept (conn ):
4868
+ return
4869
+ try :
4870
+ tls_socket = self .ssl_ctx .wrap_socket (conn , server_side = True )
4871
+ except OSError as err : # ssl.SSLError inherits from OSError
4872
+ self .wrap_error = err
4873
+ else :
4874
+ try :
4875
+ self .received_data = tls_socket .recv (400 )
4876
+ except OSError :
4877
+ pass # closed, protocol error, etc.
4878
+
4879
+ def non_linux_skip_if_other_okay_error (self , err ):
4880
+ if sys .platform == "linux" :
4881
+ return # Expect the full test setup to always work on Linux.
4882
+ if (isinstance (err , ConnectionResetError ) or
4883
+ (isinstance (err , OSError ) and err .errno == errno .EINVAL ) or
4884
+ re .search ('wrong.version.number' , getattr (err , "reason" , "" ), re .I )):
4885
+ # On Windows the TCP RST leads to a ConnectionResetError
4886
+ # (ECONNRESET) which Linux doesn't appear to surface to userspace.
4887
+ # If wrap_socket() winds up on the "if connected:" path and doing
4888
+ # the actual wrapping... we get an SSLError from OpenSSL. Typically
4889
+ # WRONG_VERSION_NUMBER. While appropriate, neither is the scenario
4890
+ # we're specifically trying to test. The way this test is written
4891
+ # is known to work on Linux. We'll skip it anywhere else that it
4892
+ # does not present as doing so.
4893
+ self .skipTest (f"Could not recreate conditions on { sys .platform } :"
4894
+ f" { err = } " )
4895
+ # If maintaining this conditional winds up being a problem.
4896
+ # just turn this into an unconditional skip anything but Linux.
4897
+ # The important thing is that our CI has the logic covered.
4898
+
4899
+ def test_preauth_data_to_tls_server (self ):
4900
+ server_accept_called = threading .Event ()
4901
+ ready_for_server_wrap_socket = threading .Event ()
4902
+
4903
+ def call_after_accept (unused ):
4904
+ server_accept_called .set ()
4905
+ if not ready_for_server_wrap_socket .wait (2.0 ):
4906
+ raise RuntimeError ("wrap_socket event never set, test may fail." )
4907
+ return False # Tell the server thread to continue.
4908
+
4909
+ server = self .SingleConnectionTestServerThread (
4910
+ call_after_accept = call_after_accept ,
4911
+ name = "preauth_data_to_tls_server" )
4912
+ server .__enter__ () # starts it
4913
+ self .addCleanup (server .__exit__ ) # ... & unittest.TestCase stops it.
4914
+
4915
+ with socket .socket () as client :
4916
+ client .connect (server .listener .getsockname ())
4917
+ # This forces an immediate connection close via RST on .close().
4918
+ set_socket_so_linger_on_with_zero_timeout (client )
4919
+ client .setblocking (False )
4920
+
4921
+ server_accept_called .wait ()
4922
+ client .send (b"DELETE /data HTTP/1.0\r \n \r \n " )
4923
+ client .close () # RST
4924
+
4925
+ ready_for_server_wrap_socket .set ()
4926
+ server .join ()
4927
+ wrap_error = server .wrap_error
4928
+ self .assertEqual (b"" , server .received_data )
4929
+ self .assertIsInstance (wrap_error , OSError ) # All platforms.
4930
+ self .non_linux_skip_if_other_okay_error (wrap_error )
4931
+ self .assertIsInstance (wrap_error , ssl .SSLError )
4932
+ self .assertIn ("before TLS handshake with data" , wrap_error .args [1 ])
4933
+ self .assertIn ("before TLS handshake with data" , wrap_error .reason )
4934
+ self .assertNotEqual (0 , wrap_error .args [0 ])
4935
+ self .assertIsNone (wrap_error .library , msg = "attr must exist" )
4936
+
4937
+ def test_preauth_data_to_tls_client (self ):
4938
+ client_can_continue_with_wrap_socket = threading .Event ()
4939
+
4940
+ def call_after_accept (conn_to_client ):
4941
+ # This forces an immediate connection close via RST on .close().
4942
+ set_socket_so_linger_on_with_zero_timeout (conn_to_client )
4943
+ conn_to_client .send (
4944
+ b"HTTP/1.0 307 Temporary Redirect\r \n "
4945
+ b"Location: https://example.com/someone-elses-server\r \n "
4946
+ b"\r \n " )
4947
+ conn_to_client .close () # RST
4948
+ client_can_continue_with_wrap_socket .set ()
4949
+ return True # Tell the server to stop.
4950
+
4951
+ server = self .SingleConnectionTestServerThread (
4952
+ call_after_accept = call_after_accept ,
4953
+ name = "preauth_data_to_tls_client" )
4954
+ server .__enter__ () # starts it
4955
+ self .addCleanup (server .__exit__ ) # ... & unittest.TestCase stops it.
4956
+
4957
+ # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
4958
+ set_socket_so_linger_on_with_zero_timeout (server .listener )
4959
+
4960
+ with socket .socket () as client :
4961
+ client .connect (server .listener .getsockname ())
4962
+ if not client_can_continue_with_wrap_socket .wait (2.0 ):
4963
+ self .fail ("test server took too long." )
4964
+ ssl_ctx = ssl .create_default_context ()
4965
+ try :
4966
+ tls_client = ssl_ctx .wrap_socket (
4967
+ client , server_hostname = "localhost" )
4968
+ except OSError as err : # SSLError inherits from OSError
4969
+ wrap_error = err
4970
+ received_data = b""
4971
+ else :
4972
+ wrap_error = None
4973
+ received_data = tls_client .recv (400 )
4974
+ tls_client .close ()
4975
+
4976
+ server .join ()
4977
+ self .assertEqual (b"" , received_data )
4978
+ self .assertIsInstance (wrap_error , OSError ) # All platforms.
4979
+ self .non_linux_skip_if_other_okay_error (wrap_error )
4980
+ self .assertIsInstance (wrap_error , ssl .SSLError )
4981
+ self .assertIn ("before TLS handshake with data" , wrap_error .args [1 ])
4982
+ self .assertIn ("before TLS handshake with data" , wrap_error .reason )
4983
+ self .assertNotEqual (0 , wrap_error .args [0 ])
4984
+ self .assertIsNone (wrap_error .library , msg = "attr must exist" )
4985
+
4986
+ def test_https_client_non_tls_response_ignored (self ):
4987
+
4988
+ server_responding = threading .Event ()
4989
+
4990
+ class SynchronizedHTTPSConnection (http .client .HTTPSConnection ):
4991
+ def connect (self ):
4992
+ http .client .HTTPConnection .connect (self )
4993
+ # Wait for our fault injection server to have done its thing.
4994
+ if not server_responding .wait (1.0 ) and support .verbose :
4995
+ sys .stdout .write ("server_responding event never set." )
4996
+ self .sock = self ._context .wrap_socket (
4997
+ self .sock , server_hostname = self .host )
4998
+
4999
+ def call_after_accept (conn_to_client ):
5000
+ # This forces an immediate connection close via RST on .close().
5001
+ set_socket_so_linger_on_with_zero_timeout (conn_to_client )
5002
+ conn_to_client .send (
5003
+ b"HTTP/1.0 402 Payment Required\r \n "
5004
+ b"\r \n " )
5005
+ conn_to_client .close () # RST
5006
+ server_responding .set ()
5007
+ return True # Tell the server to stop.
5008
+
5009
+ server = self .SingleConnectionTestServerThread (
5010
+ call_after_accept = call_after_accept ,
5011
+ name = "non_tls_http_RST_responder" )
5012
+ server .__enter__ () # starts it
5013
+ self .addCleanup (server .__exit__ ) # ... & unittest.TestCase stops it.
5014
+ # Redundant; call_after_accept sets SO_LINGER on the accepted conn.
5015
+ set_socket_so_linger_on_with_zero_timeout (server .listener )
5016
+
5017
+ connection = SynchronizedHTTPSConnection (
5018
+ f"localhost" ,
5019
+ port = server .port ,
5020
+ context = ssl .create_default_context (),
5021
+ timeout = 2.0 ,
5022
+ )
5023
+ # There are lots of reasons this raises as desired, long before this
5024
+ # test was added. Sending the request requires a successful TLS wrapped
5025
+ # socket; that fails if the connection is broken. It may seem pointless
5026
+ # to test this. It serves as an illustration of something that we never
5027
+ # want to happen... properly not happening.
5028
+ with self .assertRaises (OSError ) as err_ctx :
5029
+ connection .request ("HEAD" , "/test" , headers = {"Host" : "localhost" })
5030
+ response = connection .getresponse ()
5031
+
5032
+
4818
5033
def test_main (verbose = False ):
4819
5034
if support .verbose :
4820
5035
plats = {
0 commit comments