def test_assert_invalid_fingerprint(self): https_pool = HTTPSConnectionPool("127.0.0.1", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = ("AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:" "AA:AA:AA:AA:AA:AA:AA:AA:AA") def _test_request(pool): with pytest.raises(MaxRetryError) as cm: pool.request("GET", "/", retries=0) assert isinstance(cm.value.reason, SSLError) _test_request(https_pool) https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = "AA:A" _test_request(https_pool) https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = "AA" _test_request(https_pool)
def test_assert_invalid_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = 'AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:' \ 'AA:AA:AA:AA:AA:AA:AA:AA:AA' def _test_request(pool): with self.assertRaises(MaxRetryError) as cm: pool.request('GET', '/', retries=0) self.assertIsInstance(cm.exception.reason, SSLError) _test_request(https_pool) https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = 'AA:A' _test_request(https_pool) https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = 'AA' _test_request(https_pool)
def test_assert_invalid_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = 'AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:' \ 'AA:AA:AA:AA:AA:AA:AA:AA:AA' def _test_request(pool): with self.assertRaises(MaxRetryError) as cm: pool.request('GET', '/', retries=0) self.assertIsInstance(cm.exception.reason, SSLError) _test_request(https_pool) https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = 'AA:A' _test_request(https_pool) https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = 'AA' _test_request(https_pool)
def test_make_request_read_tmout(self): # Test pool._make_request() directly & indirectly - ensure ReadTimeoutError is raised. block_event = Event() self.set_block_response(block_event) timeout = Timeout(read=SHORT_TIMEOUT, connect=2) # Ensure connect with a very long timeout pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, retries=False) # Test pool._make_request directly conn = pool._get_conn() self.assertIsNone( conn.sock) # Ensure we didn't get an existing connection with self.assertRaises(ReadTimeoutError) as cmgr: pool._make_request(conn, 'GET', '/') self.assertEqual(cmgr.exception.args[0].split()[-1], 'timeout=%s)' % timeout.read_timeout) block_event.set() # Test pool._make_request indirectly (through pool.request) with self.assertRaises(ReadTimeoutError) as cmgr: pool.request('GET', '/', timeout=timeout) self.assertEqual(cmgr.exception.args[0].split()[-1], 'timeout=%s)' % timeout.read_timeout) block_event.set()
def test_make_request_con_tmout(self): # Test pool._make_request() directly & indirectly - ensure ConnectTimeoutError is raised. block_event = Event() self.set_block_response(block_event) timeout = Timeout(connect=SHORT_TIMEOUT) pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, retries=False) exc = ReadTimeoutError # Until we fix it to be ConnectTimeoutError pos = 0 # Will be 1 once we fix ConnectTimeoutError # Test pool._make_request directly conn = pool._get_conn() self.assertIsNone( conn.sock) # Ensure we didn't get an existing connection with self.assertRaises(exc) as cmgr: pool._make_request(conn, 'GET', '/') self.assertEqual(cmgr.exception.args[pos].split()[-1], 'timeout=%s)' % timeout.connect_timeout) block_event.set() # Test pool._make_request indirectly (through pool.request) with self.assertRaises(exc) as cmgr: pool.request('GET', '/') self.assertEqual(cmgr.exception.args[pos].split()[-1], 'timeout=%s)' % timeout.connect_timeout) block_event.set()
def test_assert_invalid_fingerprint(self): https_pool = HTTPSConnectionPool("127.0.0.1", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) https_pool.assert_fingerprint = "AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:" "AA:AA:AA:AA:AA:AA:AA:AA:AA" self.assertRaises(SSLError, https_pool.request, "GET", "/") https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = "AA:A" self.assertRaises(SSLError, https_pool.request, "GET", "/") https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = "AA" self.assertRaises(SSLError, https_pool.request, "GET", "/")
def test_reuse_conn(self): # Test pool.request() connection reusal: # 1. Create a new connection. # 2. Perform a request which will succeed. # 3. Reuse the connection - delay the response with original pool settings. # 4. Reuse same connection for a successful read request with delay in response. # 5. Reuse same connection for a timeout read request. # Create the connection pool with default timeouts long enough to connect and read. self.set_block_response(None) timeout = Timeout(connect=1., read=4) headers = make_headers(keep_alive=True) pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, headers=headers, retries=False) # First request - direct with pool._make_request() with delay=conn_timeout+read_timeout-0.5 delay = timeout.connect_timeout + timeout.read_timeout - 0.5 self.set_block_response(delay) conn = pool._get_conn() self.assertIsNone(conn.sock) pool._make_request(conn, 'GET', '/') self.set_block_response(None) # Make a request - it must succeed pool.request('GET', '/') # Reuse the connection - successful read request with delayed response. # * Ensure that new connection is not created by using a short connect timeout with # pool._make_request. # * Use a read timeout which will be larger than the pool's connect timeout but shorter # than the pool's read timeout timeout = Timeout(connect=SHORT_TIMEOUT, read=2.5) delay = 1.1 # Check that the timeouts are as intended self.assertLess(timeout.connect_timeout, pool.timeout.connect_timeout) self.assertLess(pool.timeout.connect_timeout, delay) self.assertLess(delay, timeout.read_timeout) self.assertLess(timeout.read_timeout, pool.timeout.read_timeout) # Make the request self.set_block_response(delay) pool.request('GET', '/', timeout=timeout) # Reuse the connection - timeout read request delay = timeout.read_timeout + 1 self.set_block_response(delay) now = time.time() with self.assertRaises(ReadTimeoutError) as cmgr: pool.request('GET', '/', timeout=timeout) delta = time.time() - now self.assertEqual(cmgr.exception.args[0].split()[-1], 'timeout=%s)' % timeout.read_timeout) self.assertAlmostEqual(delta, timeout.read_timeout, places=1) print('delta={}'.format(delta))
def test_assert_invalid_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) https_pool.assert_fingerprint = 'AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:' \ 'AA:AA:AA:AA:AA:AA:AA:AA:AA' self.assertRaises(SSLError, https_pool.request, 'GET', '/') https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = 'AA:A' self.assertRaises(SSLError, https_pool.request, 'GET', '/') https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = 'AA' self.assertRaises(SSLError, https_pool.request, 'GET', '/')
def test_assert_invalid_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) https_pool.assert_fingerprint = 'AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:' \ 'AA:AA:AA:AA:AA:AA:AA:AA:AA' self.assertRaises(SSLError, https_pool.request, 'GET', '/') https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = 'AA:A' self.assertRaises(SSLError, https_pool.request, 'GET', '/') https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = 'AA' self.assertRaises(SSLError, https_pool.request, 'GET', '/')
def test_https_connect_timeout(self): url = '/' host, port = self.host, self.port timeout = Timeout(connect=SHORT_TIMEOUT) # Pool-global timeout pool = HTTPSConnectionPool(host, port, timeout=timeout) conn = pool._get_conn() exc = ReadTimeoutError # Until we fix it to be ConnectTimeoutError pos = 0 # Will be 1 once we fix ConnectTimeoutError with self.assertRaises(exc) as cmgr: pool._make_request(conn, 'GET', url) self.assertEqual(cmgr.exception.args[pos].split()[-1], 'timeout=%s)' % timeout.connect_timeout) # Retries retries = Retry(connect=0) self.assertRaises(MaxRetryError, pool.request, 'GET', url, retries=retries, timeout=timeout) # Request-specific connection timeouts timeout2 = Timeout(read=LONG_TIMEOUT, connect=SHORT_TIMEOUT / 100) pool = HTTPSConnectionPool(host, port, timeout=timeout2, retries=False) conn = pool._get_conn() with self.assertRaises(exc) as cmgr: pool._make_request(conn, 'GET', url, timeout=timeout2) self.assertEqual(cmgr.exception.args[pos].split()[-1], 'timeout=%s)' % timeout2.connect_timeout) pool._put_conn(conn) timeout = Timeout(connect=SHORT_TIMEOUT) with self.assertRaises(exc) as cmgr: pool.request('GET', url, timeout=timeout) self.assertEqual(cmgr.exception.args[pos].split()[-1], 'timeout=%s)' % timeout.connect_timeout)
class TestHTTPS(HTTPSDummyServerTestCase): tls_protocol_name = None def setUp(self): self._pool = HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA) self.addCleanup(self._pool.close) def test_simple(self): r = self._pool.request('GET', '/') self.assertEqual(r.status, 200, r.data) @fails_on_travis_gce def test_dotted_fqdn(self): pool = HTTPSConnectionPool(self.host + '.', self.port, ca_certs=DEFAULT_CA) r = pool.request('GET', '/') self.assertEqual(r.status, 200, r.data) def test_client_intermediate(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS['certfile'], DEFAULT_CLIENT_CERTS['keyfile'], ) https_pool = HTTPSConnectionPool(self.host, self.port, key_file=client_key, cert_file=client_cert, ca_certs=DEFAULT_CA) r = https_pool.request('GET', '/certificate') subject = json.loads(r.data.decode('utf-8')) assert subject['organizationalUnitName'].startswith( 'Testing server cert') def test_client_no_intermediate(self): client_cert, client_key = ( DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS['certfile'], DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS['keyfile']) https_pool = HTTPSConnectionPool(self.host, self.port, cert_file=client_cert, key_file=client_key, ca_certs=DEFAULT_CA) try: https_pool.request('GET', '/certificate', retries=False) except SSLError as e: if not ('alert unknown ca' in str(e) or 'invalid certificate chain' in str(e) or 'unknown Cert Authority' in str(e) or # https://github.com/urllib3/urllib3/issues/1422 'connection closed via error' in str(e) or 'WSAECONNRESET' in str(e)): raise except ProtocolError as e: # https://github.com/urllib3/urllib3/issues/1422 if not ('An existing connection was forcibly closed by the remote host' in str(e)): raise @requires_ssl_context_keyfile_password def test_client_key_password(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS['certfile'], PASSWORD_CLIENT_KEYFILE, ) https_pool = HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA, key_file=client_key, cert_file=client_cert, key_password="******") r = https_pool.request('GET', '/certificate') subject = json.loads(r.data.decode('utf-8')) assert subject['organizationalUnitName'].startswith( 'Testing server cert') @requires_ssl_context_keyfile_password def test_client_encrypted_key_requires_password(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS['certfile'], PASSWORD_CLIENT_KEYFILE, ) https_pool = HTTPSConnectionPool(self.host, self.port, key_file=client_key, cert_file=client_cert, key_password=None) with pytest.raises(MaxRetryError) as e: https_pool.request('GET', '/certificate') assert 'password is required' in str(e.value) assert isinstance(e.value.reason, SSLError) def test_verified(self): https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.assertEqual(conn.__class__, VerifiedHTTPSConnection) with mock.patch('warnings.warn') as warn: r = https_pool.request('GET', '/') self.assertEqual(r.status, 200) # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL \ or util.IS_SECURETRANSPORT: self.assertFalse(warn.called, warn.call_args_list) else: self.assertTrue(warn.called) if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] self.assertEqual(error, InsecurePlatformWarning) def test_verified_with_context(self): ctx = util.ssl_.create_urllib3_context(cert_reqs=ssl.CERT_REQUIRED) ctx.load_verify_locations(cafile=DEFAULT_CA) https_pool = HTTPSConnectionPool(self.host, self.port, ssl_context=ctx) self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.assertEqual(conn.__class__, VerifiedHTTPSConnection) with mock.patch('warnings.warn') as warn: r = https_pool.request('GET', '/') self.assertEqual(r.status, 200) # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL \ or util.IS_SECURETRANSPORT: self.assertFalse(warn.called, warn.call_args_list) else: self.assertTrue(warn.called) if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] self.assertEqual(error, InsecurePlatformWarning) def test_context_combines_with_ca_certs(self): ctx = util.ssl_.create_urllib3_context(cert_reqs=ssl.CERT_REQUIRED) https_pool = HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA, ssl_context=ctx) self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.assertEqual(conn.__class__, VerifiedHTTPSConnection) with mock.patch('warnings.warn') as warn: r = https_pool.request('GET', '/') self.assertEqual(r.status, 200) # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL \ or util.IS_SECURETRANSPORT: self.assertFalse(warn.called, warn.call_args_list) else: self.assertTrue(warn.called) if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] self.assertEqual(error, InsecurePlatformWarning) @onlyPy279OrNewer @notSecureTransport # SecureTransport does not support cert directories @notOpenSSL098 # OpenSSL 0.9.8 does not support cert directories def test_ca_dir_verified(self): https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED', ca_cert_dir=DEFAULT_CA_DIR) self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.assertEqual(conn.__class__, VerifiedHTTPSConnection) with mock.patch('warnings.warn') as warn: r = https_pool.request('GET', '/') self.assertEqual(r.status, 200) self.assertFalse(warn.called, warn.call_args_list) def test_invalid_common_name(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) try: https_pool.request('GET', '/') self.fail("Didn't raise SSL invalid common name") except MaxRetryError as e: self.assertIsInstance(e.reason, SSLError) self.assertTrue("doesn't match" in str(e.reason) or "certificate verify failed" in str(e.reason)) def test_verified_with_bad_ca_certs(self): https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA_BAD) self.addCleanup(https_pool.close) try: https_pool.request('GET', '/') self.fail("Didn't raise SSL error with bad CA certs") except MaxRetryError as e: self.assertIsInstance(e.reason, SSLError) self.assertIn( 'certificate verify failed', str(e.reason), "Expected 'certificate verify failed'," "instead got: %r" % e.reason) def test_verified_without_ca_certs(self): # default is cert_reqs=None which is ssl.CERT_NONE https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED') self.addCleanup(https_pool.close) try: https_pool.request('GET', '/') self.fail("Didn't raise SSL error with no CA certs when" "CERT_REQUIRED is set") except MaxRetryError as e: self.assertIsInstance(e.reason, SSLError) # there is a different error message depending on whether or # not pyopenssl is injected self.assertTrue( 'No root certificates specified' in str(e.reason) or 'certificate verify failed' in str(e.reason) or 'invalid certificate chain' in str(e.reason), "Expected 'No root certificates specified', " "'certificate verify failed', or " "'invalid certificate chain', " "instead got: %r" % e.reason) def test_no_ssl(self): pool = HTTPSConnectionPool(self.host, self.port) pool.ConnectionCls = None self.addCleanup(pool.close) self.assertRaises(SSLError, pool._new_conn) with self.assertRaises(MaxRetryError) as cm: pool.request('GET', '/', retries=0) self.assertIsInstance(cm.exception.reason, SSLError) def test_unverified_ssl(self): """ Test that bare HTTPSConnection can connect, make requests """ pool = HTTPSConnectionPool(self.host, self.port, cert_reqs=ssl.CERT_NONE) self.addCleanup(pool.close) with mock.patch('warnings.warn') as warn: r = pool.request('GET', '/') self.assertEqual(r.status, 200) self.assertTrue(warn.called) # Modern versions of Python, or systems using PyOpenSSL, only emit # the unverified warning. Older systems may also emit other # warnings, which we want to ignore here. calls = warn.call_args_list self.assertIn(InsecureRequestWarning, [x[0][1] for x in calls]) def test_ssl_unverified_with_ca_certs(self): pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_NONE', ca_certs=DEFAULT_CA_BAD) self.addCleanup(pool.close) with mock.patch('warnings.warn') as warn: r = pool.request('GET', '/') self.assertEqual(r.status, 200) self.assertTrue(warn.called) # Modern versions of Python, or systems using PyOpenSSL, only emit # the unverified warning. Older systems may also emit other # warnings, which we want to ignore here. calls = warn.call_args_list if sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL \ or util.IS_SECURETRANSPORT: category = calls[0][0][1] elif util.HAS_SNI: category = calls[1][0][1] else: category = calls[2][0][1] self.assertEqual(category, InsecureRequestWarning) def test_assert_hostname_false(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_hostname = False https_pool.request('GET', '/') def test_assert_specific_hostname(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_hostname = 'localhost' https_pool.request('GET', '/') def test_server_hostname(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA, server_hostname='localhost') self.addCleanup(https_pool.close) conn = https_pool._new_conn() conn.request('GET', '/') # Assert the wrapping socket is using the passed-through SNI name. # pyopenssl doesn't let you pull the server_hostname back off the # socket, so only add this assertion if the attribute is there (i.e. # the python ssl module). if hasattr(conn.sock, 'server_hostname'): self.assertEqual(conn.sock.server_hostname, "localhost") def test_assert_fingerprint_md5(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = 'F2:06:5A:42:10:3F:45:1C:17:FE:E6:' \ '07:1E:8A:86:E5' https_pool.request('GET', '/') def test_assert_fingerprint_sha1(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:' \ 'BF:93:CF:F9:71:CC:07:7D:0A' https_pool.request('GET', '/') def test_assert_fingerprint_sha256(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = ('C5:4D:0B:83:84:89:2E:AE:B4:58:BB:12:' 'F7:A6:C4:76:05:03:88:D8:57:65:51:F3:' '1E:60:B0:8B:70:18:64:E6') https_pool.request('GET', '/') def test_assert_invalid_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = 'AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:' \ 'AA:AA:AA:AA:AA:AA:AA:AA:AA' def _test_request(pool): with self.assertRaises(MaxRetryError) as cm: pool.request('GET', '/', retries=0) self.assertIsInstance(cm.exception.reason, SSLError) _test_request(https_pool) https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = 'AA:A' _test_request(https_pool) https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = 'AA' _test_request(https_pool) def test_verify_none_and_bad_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_NONE', ca_certs=DEFAULT_CA_BAD) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = 'AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:' \ 'AA:AA:AA:AA:AA:AA:AA:AA:AA' with self.assertRaises(MaxRetryError) as cm: https_pool.request('GET', '/', retries=0) self.assertIsInstance(cm.exception.reason, SSLError) def test_verify_none_and_good_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_NONE', ca_certs=DEFAULT_CA_BAD) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:' \ 'BF:93:CF:F9:71:CC:07:7D:0A' https_pool.request('GET', '/') @notSecureTransport def test_good_fingerprint_and_hostname_mismatch(self): # This test doesn't run with SecureTransport because we don't turn off # hostname validation without turning off all validation, which this # test doesn't do (deliberately). We should revisit this if we make # new decisions. https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:' \ 'BF:93:CF:F9:71:CC:07:7D:0A' https_pool.request('GET', '/') @requires_network def test_https_timeout(self): timeout = Timeout(connect=0.001) https_pool = HTTPSConnectionPool(TARPIT_HOST, self.port, timeout=timeout, retries=False, cert_reqs='CERT_REQUIRED') self.addCleanup(https_pool.close) timeout = Timeout(total=None, connect=0.001) https_pool = HTTPSConnectionPool(TARPIT_HOST, self.port, timeout=timeout, retries=False, cert_reqs='CERT_REQUIRED') self.addCleanup(https_pool.close) self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/') timeout = Timeout(read=0.001) https_pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, retries=False, cert_reqs='CERT_REQUIRED') self.addCleanup(https_pool.close) https_pool.ca_certs = DEFAULT_CA https_pool.assert_fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:' \ 'BF:93:CF:F9:71:CC:07:7D:0A' timeout = Timeout(total=None) https_pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, cert_reqs='CERT_NONE') self.addCleanup(https_pool.close) https_pool.request('GET', '/') def test_tunnel(self): """ test the _tunnel behavior """ timeout = Timeout(total=None) https_pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, cert_reqs='CERT_NONE') self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.addCleanup(conn.close) conn.set_tunnel(self.host, self.port) conn._tunnel = mock.Mock() https_pool._make_request(conn, 'GET', '/') conn._tunnel.assert_called_once_with() @requires_network def test_enhanced_timeout(self): def new_pool(timeout, cert_reqs='CERT_REQUIRED'): https_pool = HTTPSConnectionPool(TARPIT_HOST, self.port, timeout=timeout, retries=False, cert_reqs=cert_reqs) self.addCleanup(https_pool.close) return https_pool https_pool = new_pool(Timeout(connect=0.001)) conn = https_pool._new_conn() self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/') self.assertRaises(ConnectTimeoutError, https_pool._make_request, conn, 'GET', '/') https_pool = new_pool(Timeout(connect=5)) self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/', timeout=Timeout(connect=0.001)) t = Timeout(total=None) https_pool = new_pool(t) conn = https_pool._new_conn() self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/', timeout=Timeout(total=None, connect=0.001)) def test_enhanced_ssl_connection(self): fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:BF:93:CF:F9:71:CC:07:7D:0A' https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA, assert_fingerprint=fingerprint) self.addCleanup(https_pool.close) r = https_pool.request('GET', '/') assert r.status == 200 @onlyPy279OrNewer def test_ssl_correct_system_time(self): self._pool.cert_reqs = 'CERT_REQUIRED' self._pool.ca_certs = DEFAULT_CA w = self._request_without_resource_warnings('GET', '/') self.assertEqual([], w) @onlyPy279OrNewer def test_ssl_wrong_system_time(self): self._pool.cert_reqs = 'CERT_REQUIRED' self._pool.ca_certs = DEFAULT_CA with mock.patch('urllib3.connection.datetime') as mock_date: mock_date.date.today.return_value = datetime.date(1970, 1, 1) w = self._request_without_resource_warnings('GET', '/') self.assertEqual(len(w), 1) warning = w[0] self.assertEqual(SystemTimeWarning, warning.category) self.assertIn(str(RECENT_DATE), warning.message.args[0]) def _request_without_resource_warnings(self, method, url): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') self._pool.request(method, url) return [x for x in w if not isinstance(x.message, ResourceWarning)] def test_set_ssl_version_to_tls_version(self): if self.tls_protocol_name is None: pytest.skip("Skipping base test class") self._pool.ssl_version = self.certs['ssl_version'] r = self._pool.request('GET', '/') self.assertEqual(r.status, 200, r.data) def test_set_cert_default_cert_required(self): conn = VerifiedHTTPSConnection(self.host, self.port) conn.set_cert() self.assertEqual(conn.cert_reqs, ssl.CERT_REQUIRED) def test_tls_protocol_name_of_socket(self): if self.tls_protocol_name is None: pytest.skip("Skipping base test class") conn = self._pool._get_conn() conn.connect() if not hasattr(conn.sock, 'version'): pytest.skip('SSLSocket.version() not available') self.assertEqual(conn.sock.version(), self.tls_protocol_name)
class TestHTTPS(HTTPSDummyServerTestCase): tls_protocol_name = None def setUp(self): self._pool = HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA) self.addCleanup(self._pool.close) def test_simple(self): r = self._pool.request('GET', '/') self.assertEqual(r.status, 200, r.data) @fails_on_travis_gce def test_dotted_fqdn(self): pool = HTTPSConnectionPool(self.host + '.', self.port, ca_certs=DEFAULT_CA) r = pool.request('GET', '/') self.assertEqual(r.status, 200, r.data) def test_client_intermediate(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS['certfile'], DEFAULT_CLIENT_CERTS['keyfile'], ) https_pool = HTTPSConnectionPool(self.host, self.port, key_file=client_key, cert_file=client_cert, ca_certs=DEFAULT_CA) r = https_pool.request('GET', '/certificate') subject = json.loads(r.data.decode('utf-8')) assert subject['organizationalUnitName'].startswith( 'Testing server cert') def test_client_no_intermediate(self): client_cert, client_key = ( DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS['certfile'], DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS['keyfile'] ) https_pool = HTTPSConnectionPool(self.host, self.port, cert_file=client_cert, key_file=client_key, ca_certs=DEFAULT_CA) try: https_pool.request('GET', '/certificate', retries=False) except SSLError as e: if not ('alert unknown ca' in str(e) or 'invalid certificate chain' in str(e) or 'unknown Cert Authority' in str(e) or # https://github.com/urllib3/urllib3/issues/1422 'connection closed via error' in str(e) or 'WSAECONNRESET' in str(e)): raise except ProtocolError as e: # https://github.com/urllib3/urllib3/issues/1422 if not ('An existing connection was forcibly closed by the remote host' in str(e)): raise @requires_ssl_context_keyfile_password def test_client_key_password(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS['certfile'], PASSWORD_CLIENT_KEYFILE, ) https_pool = HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA, key_file=client_key, cert_file=client_cert, key_password="******") r = https_pool.request('GET', '/certificate') subject = json.loads(r.data.decode('utf-8')) assert subject['organizationalUnitName'].startswith( 'Testing server cert') @requires_ssl_context_keyfile_password def test_client_encrypted_key_requires_password(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS['certfile'], PASSWORD_CLIENT_KEYFILE, ) https_pool = HTTPSConnectionPool(self.host, self.port, key_file=client_key, cert_file=client_cert, key_password=None) with pytest.raises(MaxRetryError) as e: https_pool.request('GET', '/certificate') assert 'password is required' in str(e.value) assert isinstance(e.value.reason, SSLError) def test_verified(self): https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.assertEqual(conn.__class__, VerifiedHTTPSConnection) with mock.patch('warnings.warn') as warn: r = https_pool.request('GET', '/') self.assertEqual(r.status, 200) # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL \ or util.IS_SECURETRANSPORT: self.assertFalse(warn.called, warn.call_args_list) else: self.assertTrue(warn.called) if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] self.assertEqual(error, InsecurePlatformWarning) def test_verified_with_context(self): ctx = util.ssl_.create_urllib3_context(cert_reqs=ssl.CERT_REQUIRED) ctx.load_verify_locations(cafile=DEFAULT_CA) https_pool = HTTPSConnectionPool(self.host, self.port, ssl_context=ctx) self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.assertEqual(conn.__class__, VerifiedHTTPSConnection) with mock.patch('warnings.warn') as warn: r = https_pool.request('GET', '/') self.assertEqual(r.status, 200) # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL \ or util.IS_SECURETRANSPORT: self.assertFalse(warn.called, warn.call_args_list) else: self.assertTrue(warn.called) if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] self.assertEqual(error, InsecurePlatformWarning) def test_context_combines_with_ca_certs(self): ctx = util.ssl_.create_urllib3_context(cert_reqs=ssl.CERT_REQUIRED) https_pool = HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA, ssl_context=ctx) self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.assertEqual(conn.__class__, VerifiedHTTPSConnection) with mock.patch('warnings.warn') as warn: r = https_pool.request('GET', '/') self.assertEqual(r.status, 200) # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL \ or util.IS_SECURETRANSPORT: self.assertFalse(warn.called, warn.call_args_list) else: self.assertTrue(warn.called) if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] self.assertEqual(error, InsecurePlatformWarning) @onlyPy279OrNewer @notSecureTransport # SecureTransport does not support cert directories @notOpenSSL098 # OpenSSL 0.9.8 does not support cert directories def test_ca_dir_verified(self): https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED', ca_cert_dir=DEFAULT_CA_DIR) self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.assertEqual(conn.__class__, VerifiedHTTPSConnection) with mock.patch('warnings.warn') as warn: r = https_pool.request('GET', '/') self.assertEqual(r.status, 200) self.assertFalse(warn.called, warn.call_args_list) def test_invalid_common_name(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) try: https_pool.request('GET', '/') self.fail("Didn't raise SSL invalid common name") except MaxRetryError as e: self.assertIsInstance(e.reason, SSLError) self.assertTrue( "doesn't match" in str(e.reason) or "certificate verify failed" in str(e.reason) ) def test_verified_with_bad_ca_certs(self): https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA_BAD) self.addCleanup(https_pool.close) try: https_pool.request('GET', '/') self.fail("Didn't raise SSL error with bad CA certs") except MaxRetryError as e: self.assertIsInstance(e.reason, SSLError) self.assertIn('certificate verify failed', str(e.reason), "Expected 'certificate verify failed'," "instead got: %r" % e.reason) def test_verified_without_ca_certs(self): # default is cert_reqs=None which is ssl.CERT_NONE https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED') self.addCleanup(https_pool.close) try: https_pool.request('GET', '/') self.fail("Didn't raise SSL error with no CA certs when" "CERT_REQUIRED is set") except MaxRetryError as e: self.assertIsInstance(e.reason, SSLError) # there is a different error message depending on whether or # not pyopenssl is injected self.assertTrue('No root certificates specified' in str(e.reason) or 'certificate verify failed' in str(e.reason) or 'invalid certificate chain' in str(e.reason), "Expected 'No root certificates specified', " "'certificate verify failed', or " "'invalid certificate chain', " "instead got: %r" % e.reason) def test_no_ssl(self): pool = HTTPSConnectionPool(self.host, self.port) pool.ConnectionCls = None self.addCleanup(pool.close) self.assertRaises(SSLError, pool._new_conn) with self.assertRaises(MaxRetryError) as cm: pool.request('GET', '/', retries=0) self.assertIsInstance(cm.exception.reason, SSLError) def test_unverified_ssl(self): """ Test that bare HTTPSConnection can connect, make requests """ pool = HTTPSConnectionPool(self.host, self.port, cert_reqs=ssl.CERT_NONE) self.addCleanup(pool.close) with mock.patch('warnings.warn') as warn: r = pool.request('GET', '/') self.assertEqual(r.status, 200) self.assertTrue(warn.called) # Modern versions of Python, or systems using PyOpenSSL, only emit # the unverified warning. Older systems may also emit other # warnings, which we want to ignore here. calls = warn.call_args_list self.assertIn(InsecureRequestWarning, [x[0][1] for x in calls]) def test_ssl_unverified_with_ca_certs(self): pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_NONE', ca_certs=DEFAULT_CA_BAD) self.addCleanup(pool.close) with mock.patch('warnings.warn') as warn: r = pool.request('GET', '/') self.assertEqual(r.status, 200) self.assertTrue(warn.called) # Modern versions of Python, or systems using PyOpenSSL, only emit # the unverified warning. Older systems may also emit other # warnings, which we want to ignore here. calls = warn.call_args_list if sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL \ or util.IS_SECURETRANSPORT: category = calls[0][0][1] elif util.HAS_SNI: category = calls[1][0][1] else: category = calls[2][0][1] self.assertEqual(category, InsecureRequestWarning) def test_assert_hostname_false(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_hostname = False https_pool.request('GET', '/') def test_assert_specific_hostname(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_hostname = 'localhost' https_pool.request('GET', '/') def test_server_hostname(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA, server_hostname='localhost') self.addCleanup(https_pool.close) conn = https_pool._new_conn() conn.request('GET', '/') # Assert the wrapping socket is using the passed-through SNI name. # pyopenssl doesn't let you pull the server_hostname back off the # socket, so only add this assertion if the attribute is there (i.e. # the python ssl module). if hasattr(conn.sock, 'server_hostname'): self.assertEqual(conn.sock.server_hostname, "localhost") def test_assert_fingerprint_md5(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = 'F2:06:5A:42:10:3F:45:1C:17:FE:E6:' \ '07:1E:8A:86:E5' https_pool.request('GET', '/') def test_assert_fingerprint_sha1(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:' \ 'BF:93:CF:F9:71:CC:07:7D:0A' https_pool.request('GET', '/') def test_assert_fingerprint_sha256(self): https_pool = HTTPSConnectionPool('localhost', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = ('C5:4D:0B:83:84:89:2E:AE:B4:58:BB:12:' 'F7:A6:C4:76:05:03:88:D8:57:65:51:F3:' '1E:60:B0:8B:70:18:64:E6') https_pool.request('GET', '/') def test_assert_invalid_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = 'AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:' \ 'AA:AA:AA:AA:AA:AA:AA:AA:AA' def _test_request(pool): with self.assertRaises(MaxRetryError) as cm: pool.request('GET', '/', retries=0) self.assertIsInstance(cm.exception.reason, SSLError) _test_request(https_pool) https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = 'AA:A' _test_request(https_pool) https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = 'AA' _test_request(https_pool) def test_verify_none_and_bad_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_NONE', ca_certs=DEFAULT_CA_BAD) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = 'AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:' \ 'AA:AA:AA:AA:AA:AA:AA:AA:AA' with self.assertRaises(MaxRetryError) as cm: https_pool.request('GET', '/', retries=0) self.assertIsInstance(cm.exception.reason, SSLError) def test_verify_none_and_good_fingerprint(self): https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_NONE', ca_certs=DEFAULT_CA_BAD) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:' \ 'BF:93:CF:F9:71:CC:07:7D:0A' https_pool.request('GET', '/') @notSecureTransport def test_good_fingerprint_and_hostname_mismatch(self): # This test doesn't run with SecureTransport because we don't turn off # hostname validation without turning off all validation, which this # test doesn't do (deliberately). We should revisit this if we make # new decisions. https_pool = HTTPSConnectionPool('127.0.0.1', self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA) self.addCleanup(https_pool.close) https_pool.assert_fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:' \ 'BF:93:CF:F9:71:CC:07:7D:0A' https_pool.request('GET', '/') @requires_network def test_https_timeout(self): timeout = Timeout(connect=0.001) https_pool = HTTPSConnectionPool(TARPIT_HOST, self.port, timeout=timeout, retries=False, cert_reqs='CERT_REQUIRED') self.addCleanup(https_pool.close) timeout = Timeout(total=None, connect=0.001) https_pool = HTTPSConnectionPool(TARPIT_HOST, self.port, timeout=timeout, retries=False, cert_reqs='CERT_REQUIRED') self.addCleanup(https_pool.close) self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/') timeout = Timeout(read=0.001) https_pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, retries=False, cert_reqs='CERT_REQUIRED') self.addCleanup(https_pool.close) https_pool.ca_certs = DEFAULT_CA https_pool.assert_fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:' \ 'BF:93:CF:F9:71:CC:07:7D:0A' timeout = Timeout(total=None) https_pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, cert_reqs='CERT_NONE') self.addCleanup(https_pool.close) https_pool.request('GET', '/') def test_tunnel(self): """ test the _tunnel behavior """ timeout = Timeout(total=None) https_pool = HTTPSConnectionPool(self.host, self.port, timeout=timeout, cert_reqs='CERT_NONE') self.addCleanup(https_pool.close) conn = https_pool._new_conn() self.addCleanup(conn.close) conn.set_tunnel(self.host, self.port) conn._tunnel = mock.Mock() https_pool._make_request(conn, 'GET', '/') conn._tunnel.assert_called_once_with() @requires_network def test_enhanced_timeout(self): def new_pool(timeout, cert_reqs='CERT_REQUIRED'): https_pool = HTTPSConnectionPool(TARPIT_HOST, self.port, timeout=timeout, retries=False, cert_reqs=cert_reqs) self.addCleanup(https_pool.close) return https_pool https_pool = new_pool(Timeout(connect=0.001)) conn = https_pool._new_conn() self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/') self.assertRaises(ConnectTimeoutError, https_pool._make_request, conn, 'GET', '/') https_pool = new_pool(Timeout(connect=5)) self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/', timeout=Timeout(connect=0.001)) t = Timeout(total=None) https_pool = new_pool(t) conn = https_pool._new_conn() self.assertRaises(ConnectTimeoutError, https_pool.request, 'GET', '/', timeout=Timeout(total=None, connect=0.001)) def test_enhanced_ssl_connection(self): fingerprint = '92:81:FE:85:F7:0C:26:60:EC:D6:B3:BF:93:CF:F9:71:CC:07:7D:0A' https_pool = HTTPSConnectionPool(self.host, self.port, cert_reqs='CERT_REQUIRED', ca_certs=DEFAULT_CA, assert_fingerprint=fingerprint) self.addCleanup(https_pool.close) r = https_pool.request('GET', '/') assert r.status == 200 @onlyPy279OrNewer def test_ssl_correct_system_time(self): self._pool.cert_reqs = 'CERT_REQUIRED' self._pool.ca_certs = DEFAULT_CA w = self._request_without_resource_warnings('GET', '/') self.assertEqual([], w) @onlyPy279OrNewer def test_ssl_wrong_system_time(self): self._pool.cert_reqs = 'CERT_REQUIRED' self._pool.ca_certs = DEFAULT_CA with mock.patch('urllib3.connection.datetime') as mock_date: mock_date.date.today.return_value = datetime.date(1970, 1, 1) w = self._request_without_resource_warnings('GET', '/') self.assertEqual(len(w), 1) warning = w[0] self.assertEqual(SystemTimeWarning, warning.category) self.assertIn(str(RECENT_DATE), warning.message.args[0]) def _request_without_resource_warnings(self, method, url): with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') self._pool.request(method, url) return [x for x in w if not isinstance(x.message, ResourceWarning)] def test_set_ssl_version_to_tls_version(self): if self.tls_protocol_name is None: pytest.skip("Skipping base test class") self._pool.ssl_version = self.certs['ssl_version'] r = self._pool.request('GET', '/') self.assertEqual(r.status, 200, r.data) def test_set_cert_default_cert_required(self): conn = VerifiedHTTPSConnection(self.host, self.port) conn.set_cert() self.assertEqual(conn.cert_reqs, ssl.CERT_REQUIRED) def test_tls_protocol_name_of_socket(self): if self.tls_protocol_name is None: pytest.skip("Skipping base test class") conn = self._pool._get_conn() conn.connect() if not hasattr(conn.sock, 'version'): pytest.skip('SSLSocket.version() not available') self.assertEqual(conn.sock.version(), self.tls_protocol_name)
class TestHTTPS(HTTPSDummyServerTestCase): tls_protocol_name = None def setup_method(self, method): self._pool = HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA) def teardown_method(self, method): self._pool.close() def test_simple(self): r = self._pool.request("GET", "/") assert r.status == 200, r.data @fails_on_travis_gce def test_dotted_fqdn(self): pool = HTTPSConnectionPool(self.host + ".", self.port, ca_certs=DEFAULT_CA) r = pool.request("GET", "/") assert r.status == 200, r.data def test_client_intermediate(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS["certfile"], DEFAULT_CLIENT_CERTS["keyfile"], ) https_pool = HTTPSConnectionPool( self.host, self.port, key_file=client_key, cert_file=client_cert, ca_certs=DEFAULT_CA, ) r = https_pool.request("GET", "/certificate") subject = json.loads(r.data.decode("utf-8")) assert subject["organizationalUnitName"].startswith( "Testing server cert") def test_client_no_intermediate(self): client_cert, client_key = ( DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS["certfile"], DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS["keyfile"], ) https_pool = HTTPSConnectionPool( self.host, self.port, cert_file=client_cert, key_file=client_key, ca_certs=DEFAULT_CA, ) try: https_pool.request("GET", "/certificate", retries=False) except SSLError as e: if not ("alert unknown ca" in str(e) or "invalid certificate chain" in str(e) or "unknown Cert Authority" in str(e) or # https://github.com/urllib3/urllib3/issues/1422 "connection closed via error" in str(e) or "WSAECONNRESET" in str(e)): raise except ProtocolError as e: if not ("An existing connection was forcibly closed by the remote host" in str(e) # Python 3.7.4+ or "WSAECONNRESET" in str(e) # Windows or "EPIPE" in str(e) # macOS ): raise @requires_ssl_context_keyfile_password def test_client_key_password(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS["certfile"], PASSWORD_CLIENT_KEYFILE, ) https_pool = HTTPSConnectionPool( self.host, self.port, ca_certs=DEFAULT_CA, key_file=client_key, cert_file=client_cert, key_password="******", ) r = https_pool.request("GET", "/certificate") subject = json.loads(r.data.decode("utf-8")) assert subject["organizationalUnitName"].startswith( "Testing server cert") @requires_ssl_context_keyfile_password def test_client_encrypted_key_requires_password(self): client_cert, client_key = ( DEFAULT_CLIENT_CERTS["certfile"], PASSWORD_CLIENT_KEYFILE, ) with pytest.raises(SSLError) as e: HTTPSConnectionPool( self.host, self.port, key_file=client_key, cert_file=client_cert, key_password=None, ) assert "password is required" in str(e.value) def test_verified(self): with HTTPSConnectionPool(self.host, self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: with mock.patch("warnings.warn") as warn: r = https_pool.request("GET", "/") assert r.status == 200 # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if (sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL or util.IS_SECURETRANSPORT): assert not warn.called, warn.call_args_list else: assert warn.called if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] assert error == InsecurePlatformWarning def test_verified_with_context(self): ctx = util.ssl_.create_urllib3_context(cert_reqs=ssl.CERT_REQUIRED) ctx.load_verify_locations(cafile=DEFAULT_CA) with HTTPSConnectionPool(self.host, self.port, ssl_context=ctx) as https_pool: with mock.patch("warnings.warn") as warn: r = https_pool.request("GET", "/") assert r.status == 200 # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if (sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL or util.IS_SECURETRANSPORT): assert not warn.called, warn.call_args_list else: assert warn.called if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] assert error == InsecurePlatformWarning def test_context_combines_with_ca_certs(self): ctx = util.ssl_.create_urllib3_context(cert_reqs=ssl.CERT_REQUIRED) with HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA, ssl_context=ctx) as https_pool: with mock.patch("warnings.warn") as warn: r = https_pool.request("GET", "/") assert r.status == 200 # Modern versions of Python, or systems using PyOpenSSL, don't # emit warnings. if (sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL or util.IS_SECURETRANSPORT): assert not warn.called, warn.call_args_list else: assert warn.called if util.HAS_SNI: call = warn.call_args_list[0] else: call = warn.call_args_list[1] error = call[0][1] assert error == InsecurePlatformWarning @onlyPy279OrNewer @notSecureTransport # SecureTransport does not support cert directories @notOpenSSL098 # OpenSSL 0.9.8 does not support cert directories def test_ca_dir_verified(self): with HTTPSConnectionPool(self.host, self.port, cert_reqs="CERT_REQUIRED", ca_cert_dir=DEFAULT_CA_DIR) as https_pool: with mock.patch("warnings.warn") as warn: r = https_pool.request("GET", "/") assert r.status == 200 assert not warn.called, warn.call_args_list def test_invalid_common_name(self): with HTTPSConnectionPool("127.0.0.1", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: try: https_pool.request("GET", "/") self.fail("Didn't raise SSL invalid common name") except MaxRetryError as e: assert isinstance(e.reason, SSLError) assert "doesn't match" in str( e.reason) or "certificate verify failed" in str(e.reason) def test_verified_with_bad_ca_certs(self): with HTTPSConnectionPool(self.host, self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA_BAD) as https_pool: try: https_pool.request("GET", "/") self.fail("Didn't raise SSL error with bad CA certs") except MaxRetryError as e: assert isinstance(e.reason, SSLError) assert "certificate verify failed" in str(e.reason), ( "Expected 'certificate verify failed', instead got: %r" % e.reason) def test_verified_without_ca_certs(self): # default is cert_reqs=None which is ssl.CERT_NONE with HTTPSConnectionPool(self.host, self.port, cert_reqs="CERT_REQUIRED") as https_pool: try: https_pool.request("GET", "/") self.fail("Didn't raise SSL error with no CA certs when" "CERT_REQUIRED is set") except MaxRetryError as e: assert isinstance(e.reason, SSLError) # there is a different error message depending on whether or # not pyopenssl is injected assert ("No root certificates specified" in str(e.reason) # PyPy sometimes uses all-caps here or "certificate verify failed" in str(e.reason).lower() or "invalid certificate chain" in str(e.reason)), ( "Expected 'No root certificates specified', " "'certificate verify failed', or " "'invalid certificate chain', " "instead got: %r" % e.reason) def test_unverified_ssl(self): """ Test that bare HTTPSConnection can connect, make requests """ with HTTPSConnectionPool(self.host, self.port, cert_reqs=ssl.CERT_NONE) as pool: with mock.patch("warnings.warn") as warn: r = pool.request("GET", "/") assert r.status == 200 assert warn.called # Modern versions of Python, or systems using PyOpenSSL, only emit # the unverified warning. Older systems may also emit other # warnings, which we want to ignore here. calls = warn.call_args_list assert InsecureRequestWarning in [x[0][1] for x in calls] def test_ssl_unverified_with_ca_certs(self): with HTTPSConnectionPool(self.host, self.port, cert_reqs="CERT_NONE", ca_certs=DEFAULT_CA_BAD) as pool: with mock.patch("warnings.warn") as warn: r = pool.request("GET", "/") assert r.status == 200 assert warn.called # Modern versions of Python, or systems using PyOpenSSL, only emit # the unverified warning. Older systems may also emit other # warnings, which we want to ignore here. calls = warn.call_args_list if (sys.version_info >= (2, 7, 9) or util.IS_PYOPENSSL or util.IS_SECURETRANSPORT): category = calls[0][0][1] elif util.HAS_SNI: category = calls[1][0][1] else: category = calls[2][0][1] assert category == InsecureRequestWarning def test_assert_hostname_false(self): with HTTPSConnectionPool("localhost", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: https_pool.assert_hostname = False https_pool.request("GET", "/") def test_assert_specific_hostname(self): with HTTPSConnectionPool("localhost", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: https_pool.assert_hostname = "localhost" https_pool.request("GET", "/") def test_server_hostname(self): with HTTPSConnectionPool( "127.0.0.1", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA, server_hostname="localhost", ) as https_pool: conn = https_pool._new_conn() https_pool._start_conn(conn, connect_timeout=None) # Assert the wrapping socket is using the passed-through SNI name. # pyopenssl doesn't let you pull the server_hostname back off the # socket, so only add this assertion if the attribute is there (i.e. # the python ssl module). # XXX This is highly-specific to SyncBackend # See https://github.com/python-trio/urllib3/pull/54#discussion_r241683895 # for potential solutions sock = conn._sock._sock if hasattr(sock, "server_hostname"): assert sock.server_hostname == "localhost" def test_assert_fingerprint_md5(self): with HTTPSConnectionPool("localhost", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: https_pool.assert_fingerprint = ( "F2:06:5A:42:10:3F:45:1C:17:FE:E6:07:1E:8A:86:E5") https_pool.request("GET", "/") def test_assert_fingerprint_sha1(self): with HTTPSConnectionPool("localhost", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: https_pool.assert_fingerprint = ( "92:81:FE:85:F7:0C:26:60:EC:D6:B3:BF:93:CF:F9:71:CC:07:7D:0A") https_pool.request("GET", "/") def test_assert_fingerprint_sha256(self): with HTTPSConnectionPool("localhost", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: https_pool.assert_fingerprint = ( "C5:4D:0B:83:84:89:2E:AE:B4:58:BB:12:" "F7:A6:C4:76:05:03:88:D8:57:65:51:F3:" "1E:60:B0:8B:70:18:64:E6") https_pool.request("GET", "/") def test_assert_invalid_fingerprint(self): with HTTPSConnectionPool("127.0.0.1", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: https_pool.assert_fingerprint = ( "AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA") def _test_request(pool): with pytest.raises(MaxRetryError) as cm: pool.request("GET", "/", retries=0) assert isinstance(cm.value.reason, SSLError) _test_request(https_pool) https_pool._get_conn() # Uneven length https_pool.assert_fingerprint = "AA:A" _test_request(https_pool) https_pool._get_conn() # Invalid length https_pool.assert_fingerprint = "AA" _test_request(https_pool) def test_verify_none_and_bad_fingerprint(self): with HTTPSConnectionPool("127.0.0.1", self.port, cert_reqs="CERT_NONE", ca_certs=DEFAULT_CA_BAD) as https_pool: https_pool.assert_fingerprint = ( "AA:AA:AA:AA:AA:AAAA:AA:AAAA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA") with pytest.raises(MaxRetryError) as cm: https_pool.request("GET", "/", retries=0) assert isinstance(cm.value.reason, SSLError) def test_verify_none_and_good_fingerprint(self): with HTTPSConnectionPool("127.0.0.1", self.port, cert_reqs="CERT_NONE", ca_certs=DEFAULT_CA_BAD) as https_pool: https_pool.assert_fingerprint = ( "92:81:FE:85:F7:0C:26:60:EC:D6:B3:BF:93:CF:F9:71:CC:07:7D:0A") https_pool.request("GET", "/") @notSecureTransport def test_good_fingerprint_and_hostname_mismatch(self): # This test doesn't run with SecureTransport because we don't turn off # hostname validation without turning off all validation, which this # test doesn't do (deliberately). We should revisit this if we make # new decisions. with HTTPSConnectionPool("127.0.0.1", self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) as https_pool: https_pool.assert_fingerprint = ( "92:81:FE:85:F7:0C:26:60:EC:D6:B3:BF:93:CF:F9:71:CC:07:7D:0A") https_pool.request("GET", "/") @requires_network def test_https_timeout(self): timeout = Timeout(total=None, connect=0.001) with HTTPSConnectionPool( TARPIT_HOST, self.port, timeout=timeout, retries=False, cert_reqs="CERT_REQUIRED", ) as https_pool: with pytest.raises(ConnectTimeoutError): https_pool.request("GET", "/") timeout = Timeout(read=0.01) with HTTPSConnectionPool( self.host, self.port, timeout=timeout, retries=False, cert_reqs="CERT_REQUIRED", ) as https_pool: https_pool.assert_fingerprint = ( "92:81:FE:85:F7:0C:26:60:EC:D6:B3:BF:93:CF:F9:71:CC:07:7D:0A") timeout = Timeout(total=None) with HTTPSConnectionPool(self.host, self.port, timeout=timeout, cert_reqs="CERT_NONE") as https_pool: https_pool.request("GET", "/") @requires_network def test_enhanced_timeout(self): with HTTPSConnectionPool( TARPIT_HOST, self.port, timeout=Timeout(connect=0.001), retries=False, cert_reqs="CERT_REQUIRED", ) as https_pool: conn = https_pool._new_conn() with pytest.raises(ConnectTimeoutError): https_pool.request("GET", "/") with pytest.raises(ConnectTimeoutError): https_pool._make_request(conn, "GET", "/") with HTTPSConnectionPool( TARPIT_HOST, self.port, timeout=Timeout(connect=5), retries=False, cert_reqs="CERT_REQUIRED", ) as https_pool: with pytest.raises(ConnectTimeoutError): https_pool.request("GET", "/", timeout=Timeout(connect=0.001)) with HTTPSConnectionPool( TARPIT_HOST, self.port, timeout=Timeout(total=None), retries=False, cert_reqs="CERT_REQUIRED", ) as https_pool: conn = https_pool._new_conn() with pytest.raises(ConnectTimeoutError): https_pool.request("GET", "/", timeout=Timeout(total=None, connect=0.001)) def test_enhanced_ssl_connection(self): fingerprint = "92:81:FE:85:F7:0C:26:60:EC:D6:B3:BF:93:CF:F9:71:CC:07:7D:0A" with HTTPSConnectionPool( self.host, self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA, assert_fingerprint=fingerprint, ) as https_pool: r = https_pool.urlopen("GET", "/") assert r.status == 200 @onlyPy279OrNewer def test_ssl_correct_system_time(self): w = self._request_without_resource_warnings("GET", "/") assert [] == w @onlyPy279OrNewer def test_ssl_wrong_system_time(self): with mock.patch("urllib3._sync.connection.datetime") as mock_date: mock_date.date.today.return_value = datetime.date(1970, 1, 1) w = self._request_without_resource_warnings("GET", "/") assert len(w) == 1 warning = w[0] assert SystemTimeWarning == warning.category assert str(RECENT_DATE) in warning.message.args[0] def _request_without_resource_warnings(self, method, url): pool = HTTPSConnectionPool(self.host, self.port, cert_reqs="CERT_REQUIRED", ca_certs=DEFAULT_CA) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") pool.request(method, url) return [x for x in w if not isinstance(x.message, ResourceWarning)] def test_set_ssl_version_to_tls_version(self): if self.tls_protocol_name is None: pytest.skip("Skipping base test class") self._pool.ssl_version = self.certs["ssl_version"] r = self._pool.request("GET", "/") assert r.status == 200, r.data def test_set_cert_default_cert_required(self): pool = HTTPSConnectionPool(self.host, self.port, ca_certs=DEFAULT_CA) assert pool.ssl_context.verify_mode == ssl.CERT_REQUIRED def test_tls_protocol_name_of_socket(self): if self.tls_protocol_name is None: pytest.skip("Skipping base test class") conn = self._pool._get_conn() conn.connect(self._pool.ssl_context) if not hasattr(conn._sock, "_version"): pytest.skip("_version() not available in backend") assert conn._sock._version() == self.tls_protocol_name