Compare commits

...

6 Commits

Author SHA1 Message Date
coletdjnz
d47f6f3c60
Merge d3ae5688b4 into b83ca24eb7 2024-11-10 09:28:03 +05:30
coletdjnz
d3ae5688b4
change to --proxy-no-check-certificates 2024-09-08 17:31:24 +12:00
coletdjnz
fc8b4a3be9
Expose CLI args 2024-09-08 17:27:33 +12:00
coletdjnz
a3cf32ad5b
Add proxy nocheckcertificate and client_certificate options to YoutubeDL 2024-09-08 17:23:20 +12:00
coletdjnz
40ab38b660
add legacy_ssl_support tests 2024-09-08 17:00:12 +12:00
coletdjnz
4accb0befe
[networking] Add proxy_client_cert, proxy_verify and legacy_proxy_ssl_support options 2024-09-08 15:55:55 +12:00
10 changed files with 294 additions and 19 deletions

View File

@ -772,6 +772,7 @@ If you fork the project on GitHub, you can run your fork's [build workflow](.git
that do not support RFC 5746 secure
renegotiation
--no-check-certificates Suppress HTTPS certificate validation
--proxy-no-check-certificates Suppress HTTPS Proxy certificate validation
--prefer-insecure Use an unencrypted connection to retrieve
information about the video (Currently
supported only for YouTube)
@ -879,6 +880,18 @@ If you fork the project on GitHub, you can run your fork's [build workflow](.git
Password for client certificate private key,
if encrypted. If not provided, and the key
is encrypted, yt-dlp will ask interactively
--proxy-client-certificate CERTFILE
Path to client certificate file in PEM
format for HTTPS proxy. May include the
private key
--proxy-client-certificate-key KEYFILE
Path to private key file for client
certificate for HTTPS proxy
--proxy-client-certificate-password PASSWORD
Password for client certificate private key,
if encrypted, for HTTPS proxy. If not
provided, and the key is encrypted, yt-dlp
will ask interactively
## Post-Processing Options:
-x, --extract-audio Convert video files to audio-only files

View File

@ -19,6 +19,8 @@ from yt_dlp.dependencies import urllib3
from yt_dlp.networking import Request
from yt_dlp.networking.exceptions import HTTPError, ProxyError, SSLError
MTLS_CERT_DIR = os.path.join(TEST_DIR, 'testdata', 'certificate')
class HTTPProxyAuthMixin:
@ -135,6 +137,35 @@ class HTTPSProxyHandler(HTTPProxyHandler):
super().__init__(request, *args, **kwargs)
class MTLSHTTPSProxyHandler(HTTPProxyHandler):
def __init__(self, request, *args, **kwargs):
certfn = os.path.join(TEST_DIR, 'testcert.pem')
cacertfn = os.path.join(MTLS_CERT_DIR, 'ca.crt')
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
sslctx.verify_mode = ssl.CERT_REQUIRED
sslctx.load_verify_locations(cafile=cacertfn)
sslctx.load_cert_chain(certfn, None)
if isinstance(request, ssl.SSLSocket):
request = SSLTransport(request, ssl_context=sslctx, server_side=True)
else:
request = sslctx.wrap_socket(request, server_side=True)
super().__init__(request, *args, **kwargs)
class LegacyHTTPSProxyHandler(HTTPProxyHandler):
def __init__(self, request, *args, **kwargs):
certfn = os.path.join(TEST_DIR, 'testcert.pem')
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
sslctx.maximum_version = ssl.TLSVersion.TLSv1_2
sslctx.set_ciphers('SHA1:AESCCM:aDSS:eNULL:aNULL')
sslctx.load_cert_chain(certfn, None)
if isinstance(request, ssl.SSLSocket):
request = SSLTransport(request, ssl_context=sslctx, server_side=True)
else:
request = sslctx.wrap_socket(request, server_side=True)
super().__init__(request, *args, **kwargs)
class HTTPConnectProxyHandler(BaseHTTPRequestHandler, HTTPProxyAuthMixin):
protocol_version = 'HTTP/1.1'
default_request_version = 'HTTP/1.1'
@ -178,6 +209,39 @@ class HTTPSConnectProxyHandler(HTTPConnectProxyHandler):
self.server.close_request(self._original_request)
class MTLSHTTPSConnectProxyHandler(HTTPConnectProxyHandler):
def __init__(self, request, *args, **kwargs):
certfn = os.path.join(TEST_DIR, 'testcert.pem')
cacertfn = os.path.join(MTLS_CERT_DIR, 'ca.crt')
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
sslctx.verify_mode = ssl.CERT_REQUIRED
sslctx.load_verify_locations(cafile=cacertfn)
sslctx.load_cert_chain(certfn, None)
request = sslctx.wrap_socket(request, server_side=True)
self._original_request = request
super().__init__(request, *args, **kwargs)
def do_CONNECT(self):
super().do_CONNECT()
self.server.close_request(self._original_request)
class LegacyHTTPSConnectProxyHandler(HTTPConnectProxyHandler):
def __init__(self, request, *args, **kwargs):
certfn = os.path.join(TEST_DIR, 'testcert.pem')
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
sslctx.maximum_version = ssl.TLSVersion.TLSv1_2
sslctx.set_ciphers('SHA1:AESCCM:aDSS:eNULL:aNULL')
sslctx.load_cert_chain(certfn, None)
request = sslctx.wrap_socket(request, server_side=True)
self._original_request = request
super().__init__(request, *args, **kwargs)
def do_CONNECT(self):
super().do_CONNECT()
self.server.close_request(self._original_request)
@contextlib.contextmanager
def proxy_server(proxy_server_class, request_handler, bind_ip=None, **proxy_server_kwargs):
server = server_thread = None
@ -285,7 +349,7 @@ class TestHTTPProxy:
@pytest.mark.skip_handler('Urllib', 'urllib does not support https proxies')
def test_https(self, handler, ctx):
with ctx.http_server(HTTPSProxyHandler) as server_address:
with handler(verify=False, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
with handler(proxy_verify=False, verify=False, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
proxy_info = ctx.proxy_info_request(rh)
assert proxy_info['proxy'] == server_address
assert proxy_info['connect'] is False
@ -294,10 +358,64 @@ class TestHTTPProxy:
@pytest.mark.skip_handler('Urllib', 'urllib does not support https proxies')
def test_https_verify_failed(self, handler, ctx):
with ctx.http_server(HTTPSProxyHandler) as server_address:
with handler(verify=True, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
# Accept SSLError as may not be feasible to tell if it is proxy or request error.
# note: if request proto also does ssl verification, this may also be the error of the request.
# Until we can support passing custom cacerts to handlers, we cannot properly test this for all cases.
with handler(proxy_verify=True, verify=False, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
# Accept both ProxyError and SSLError as may not be feasible to tell if it is proxy or request error.
with pytest.raises((ProxyError, SSLError)):
ctx.proxy_info_request(rh)
@pytest.mark.skip_handler('Urllib', 'urllib does not support https proxies')
@pytest.mark.skip_handler('CurlCFFI', 'legacy_ssl ignored by CurlCFFI')
def test_https_legacy_ssl_support(self, handler, ctx):
with ctx.http_server(LegacyHTTPSProxyHandler) as server_address:
with handler(proxy_verify=False, verify=False, proxy_legacy_ssl_support=True, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
proxy_info = ctx.proxy_info_request(rh)
assert proxy_info['proxy'] == server_address
assert proxy_info['connect'] is False
assert 'Proxy-Authorization' not in proxy_info['headers']
with handler(proxy_verify=False, verify=False, proxy_legacy_ssl_support=False, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
with pytest.raises((ProxyError, SSLError)):
ctx.proxy_info_request(rh)
@pytest.mark.skip_handler('Urllib', 'urllib does not support https proxies')
@pytest.mark.parametrize('proxy_client_cert', [
{'client_certificate': os.path.join(MTLS_CERT_DIR, 'clientwithkey.crt')},
{
'client_certificate': os.path.join(MTLS_CERT_DIR, 'client.crt'),
'client_certificate_key': os.path.join(MTLS_CERT_DIR, 'client.key'),
},
{
'client_certificate': os.path.join(MTLS_CERT_DIR, 'clientwithencryptedkey.crt'),
'client_certificate_password': 'foobar',
},
{
'client_certificate': os.path.join(MTLS_CERT_DIR, 'client.crt'),
'client_certificate_key': os.path.join(MTLS_CERT_DIR, 'clientencrypted.key'),
'client_certificate_password': 'foobar',
},
], ids=['combined_nopass', 'nocombined_nopass', 'combined_pass', 'nocombined_pass'])
def test_https_mtls(self, handler, ctx, proxy_client_cert):
with ctx.http_server(MTLSHTTPSProxyHandler) as server_address:
with handler(
proxy_verify=False,
verify=False,
proxy_client_cert=proxy_client_cert,
proxies={ctx.REQUEST_PROTO: f'https://{server_address}'},
) as rh:
proxy_info = ctx.proxy_info_request(rh)
assert proxy_info['proxy'] == server_address
assert proxy_info['connect'] is False
assert 'Proxy-Authorization' not in proxy_info['headers']
@pytest.mark.skip_handler('Urllib', 'urllib does not support https proxies')
def test_https_mtls_error(self, handler, ctx):
with ctx.http_server(MTLSHTTPSProxyHandler) as server_address:
with handler(
proxy_verify=False,
verify=False,
proxy_client_cert=None,
proxies={ctx.REQUEST_PROTO: f'https://{server_address}'},
) as rh:
with pytest.raises((ProxyError, SSLError)):
ctx.proxy_info_request(rh)
@ -331,10 +449,6 @@ class TestHTTPConnectProxy:
assert proxy_info['proxy'] == server_address
assert 'Proxy-Authorization' in proxy_info['headers']
@pytest.mark.skip_handler(
'Requests',
'bug in urllib3 causes unclosed socket: https://github.com/urllib3/urllib3/issues/3374',
)
def test_http_connect_bad_auth(self, handler, ctx):
with ctx.http_server(HTTPConnectProxyHandler, username='test', password='test') as server_address:
with handler(verify=False, proxies={ctx.REQUEST_PROTO: f'http://test:bad@{server_address}'}) as rh:
@ -355,7 +469,7 @@ class TestHTTPConnectProxy:
@pytest.mark.skipif(urllib3 is None, reason='requires urllib3 to test')
def test_https_connect_proxy(self, handler, ctx):
with ctx.http_server(HTTPSConnectProxyHandler) as server_address:
with handler(verify=False, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
with handler(proxy_verify=False, verify=False, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
proxy_info = ctx.proxy_info_request(rh)
assert proxy_info['proxy'] == server_address
assert proxy_info['connect'] is True
@ -364,17 +478,71 @@ class TestHTTPConnectProxy:
@pytest.mark.skipif(urllib3 is None, reason='requires urllib3 to test')
def test_https_connect_verify_failed(self, handler, ctx):
with ctx.http_server(HTTPSConnectProxyHandler) as server_address:
with handler(verify=True, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
# Accept SSLError as may not be feasible to tell if it is proxy or request error.
# note: if request proto also does ssl verification, this may also be the error of the request.
# Until we can support passing custom cacerts to handlers, we cannot properly test this for all cases.
with handler(proxy_verify=True, verify=False, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
# Accept both ProxyError and SSLError as may not be feasible to tell if it is proxy or request error.
with pytest.raises((ProxyError, SSLError)):
ctx.proxy_info_request(rh)
@pytest.mark.skipif(urllib3 is None, reason='requires urllib3 to test')
def test_https_connect_proxy_auth(self, handler, ctx):
with ctx.http_server(HTTPSConnectProxyHandler, username='test', password='test') as server_address:
with handler(verify=False, proxies={ctx.REQUEST_PROTO: f'https://test:test@{server_address}'}) as rh:
with handler(proxy_verify=False, verify=False, proxies={ctx.REQUEST_PROTO: f'https://test:test@{server_address}'}) as rh:
proxy_info = ctx.proxy_info_request(rh)
assert proxy_info['proxy'] == server_address
assert 'Proxy-Authorization' in proxy_info['headers']
@pytest.mark.skipif(urllib3 is None, reason='requires urllib3 to test')
@pytest.mark.parametrize('proxy_client_cert', [
{'client_certificate': os.path.join(MTLS_CERT_DIR, 'clientwithkey.crt')},
{
'client_certificate': os.path.join(MTLS_CERT_DIR, 'client.crt'),
'client_certificate_key': os.path.join(MTLS_CERT_DIR, 'client.key'),
},
{
'client_certificate': os.path.join(MTLS_CERT_DIR, 'clientwithencryptedkey.crt'),
'client_certificate_password': 'foobar',
},
{
'client_certificate': os.path.join(MTLS_CERT_DIR, 'client.crt'),
'client_certificate_key': os.path.join(MTLS_CERT_DIR, 'clientencrypted.key'),
'client_certificate_password': 'foobar',
},
], ids=['combined_nopass', 'nocombined_nopass', 'combined_pass', 'nocombined_pass'])
def test_https_connect_mtls(self, handler, ctx, proxy_client_cert):
with ctx.http_server(MTLSHTTPSConnectProxyHandler) as server_address:
with handler(
proxy_verify=False,
verify=False,
proxy_client_cert=proxy_client_cert,
proxies={ctx.REQUEST_PROTO: f'https://{server_address}'},
) as rh:
proxy_info = ctx.proxy_info_request(rh)
assert proxy_info['proxy'] == server_address
assert proxy_info['connect'] is True
assert 'Proxy-Authorization' not in proxy_info['headers']
@pytest.mark.skipif(urllib3 is None, reason='requires urllib3 to test')
def test_https_connect_mtls_error(self, handler, ctx):
with ctx.http_server(MTLSHTTPSConnectProxyHandler) as server_address:
with handler(
proxy_verify=False,
verify=False,
proxy_client_cert=None,
proxies={ctx.REQUEST_PROTO: f'https://{server_address}'},
) as rh:
with pytest.raises((ProxyError, SSLError)):
ctx.proxy_info_request(rh)
@pytest.mark.skipif(urllib3 is None, reason='requires urllib3 to test')
@pytest.mark.skip_handler('CurlCFFI', 'legacy_ssl ignored by CurlCFFI')
def test_https_connect_legacy_ssl_support(self, handler, ctx):
with ctx.http_server(LegacyHTTPSConnectProxyHandler) as server_address:
with handler(proxy_verify=False, verify=False, proxy_legacy_ssl_support=True, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
proxy_info = ctx.proxy_info_request(rh)
assert proxy_info['proxy'] == server_address
assert proxy_info['connect'] is True
assert 'Proxy-Authorization' not in proxy_info['headers']
with handler(proxy_verify=False, verify=False, proxy_legacy_ssl_support=False, proxies={ctx.REQUEST_PROTO: f'https://{server_address}'}) as rh:
with pytest.raises((ProxyError, SSLError)):
ctx.proxy_info_request(rh)

View File

@ -61,7 +61,7 @@ from yt_dlp.networking.impersonate import (
ImpersonateRequestHandler,
ImpersonateTarget,
)
from yt_dlp.utils import YoutubeDLError
from yt_dlp.utils import YoutubeDLError, traverse_obj
from yt_dlp.utils._utils import _YDLLogger as FakeLogger
from yt_dlp.utils.networking import HTTPHeaderDict, std_headers
@ -772,6 +772,10 @@ class TestClientCertificate:
'client_certificate_password': 'foobar',
})
def test_mtls_required(self, handler):
with pytest.raises(SSLError):
self._run_test(handler)
@pytest.mark.parametrize('handler', ['CurlCFFI'], indirect=True)
class TestHTTPImpersonateRequestHandler(TestRequestHandlerBase):
@ -1776,6 +1780,7 @@ class TestYoutubeDLNetworking:
'compat_opts': ['no-certifi'],
'nocheckcertificate': True,
'legacyserverconnect': True,
'proxy_nocheckcertificate': True,
}) as ydl:
rh = self.build_handler(ydl)
assert rh.headers.get('test') == 'testtest'
@ -1787,6 +1792,7 @@ class TestYoutubeDLNetworking:
assert rh.prefer_system_certs is True
assert rh.verify is False
assert rh.legacy_ssl_support is True
assert rh.proxy_verify is False
@pytest.mark.parametrize('ydl_params', [
{'client_certificate': 'fakecert.crt'},
@ -1799,6 +1805,22 @@ class TestYoutubeDLNetworking:
rh = self.build_handler(ydl)
assert rh._client_cert == ydl_params # XXX: Too bound to implementation
@pytest.mark.parametrize('client_cert', [
{'client_certificate': 'fakecert.crt'},
{'client_certificate': 'fakecert.crt', 'client_certificate_key': 'fakekey.key'},
{'client_certificate': 'fakecert.crt', 'client_certificate_key': 'fakekey.key', 'client_certificate_password': 'foobar'},
{'client_certificate_key': 'fakekey.key', 'client_certificate_password': 'foobar'},
])
def test_proxy_client_certificate(self, client_cert):
ydl_params = traverse_obj(client_cert, {
'proxy_client_certificate': 'client_certificate',
'proxy_client_certificate_key': 'client_certificate_key',
'proxy_client_certificate_password': 'client_certificate_password',
})
with FakeYDL(ydl_params) as ydl:
rh = self.build_handler(ydl)
assert rh._proxy_client_cert == client_cert
def test_urllib_file_urls(self):
with FakeYDL({'enable_file_urls': False}) as ydl:
rh = self.build_handler(ydl, UrllibRH)

View File

@ -356,7 +356,7 @@ class TestWebsSocketRequestHandlerConformance:
'client_certificate_key': os.path.join(MTLS_CERT_DIR, 'clientencrypted.key'),
'client_certificate_password': 'foobar',
},
))
), ids=['combined_nopass', 'nocombined_nopass', 'combined_pass', 'nocombined_pass'])
def test_mtls(self, handler, client_cert):
with handler(
# Disable client-side validation of unacceptable self-signed testcert.pem
@ -366,6 +366,15 @@ class TestWebsSocketRequestHandlerConformance:
) as rh:
ws_validate_and_send(rh, Request(self.mtls_wss_base_url)).close()
def test_mtls_required(self, handler):
with handler(
# Disable client-side validation of unacceptable self-signed testcert.pem
# The test is of a check on the server side, so unaffected
verify=False,
) as rh:
with pytest.raises(SSLError):
ws_validate_and_send(rh, Request(self.mtls_wss_base_url))
def test_request_disable_proxy(self, handler):
for proxy_proto in handler._SUPPORTED_PROXY_SCHEMES or ['ws']:
# Given handler is configured with a proxy

View File

@ -339,10 +339,15 @@ class YoutubeDL:
legacyserverconnect: Explicitly allow HTTPS connection to servers that do not
support RFC 5746 secure renegotiation
nocheckcertificate: Do not verify SSL certificates
proxy_nocheckcertificate: Do not verify SSL certificates for HTTPS proxy
client_certificate: Path to client certificate file in PEM format. May include the private key
client_certificate_key: Path to private key file for client certificate
client_certificate_password: Password for client certificate private key, if encrypted.
If not provided and the key is encrypted, yt-dlp will ask interactively
proxy_client_certificate: Path to client certificate file in PEM format for HTTPS proxy. May include the private key
proxy_client_certificate_key: Path to private key file for client certificate for HTTPS proxy.
proxy_client_certificate_password: Password for client certificate private key, if encrypted, for HTTPS proxy.
If not provided and the key is encrypted, yt-dlp will ask interactively
prefer_insecure: Use HTTP instead of HTTPS to retrieve information.
(Only supported by some extractors)
enable_file_urls: Enable file:// URLs. This is disabled by default for security reasons.
@ -4218,6 +4223,7 @@ class YoutubeDL:
proxies=proxies,
prefer_system_certs='no-certifi' in self.params['compat_opts'],
verify=not self.params.get('nocheckcertificate'),
proxy_verify=not self.params.get('proxy_nocheckcertificate'),
**traverse_obj(self.params, {
'verbose': 'debug_printtraffic',
'source_address': 'source_address',
@ -4230,6 +4236,11 @@ class YoutubeDL:
'client_certificate_key': 'client_certificate_key',
'client_certificate_password': 'client_certificate_password',
},
'proxy_client_cert': {
'client_certificate': 'proxy_client_certificate',
'client_certificate_key': 'proxy_client_certificate_key',
'client_certificate_password': 'proxy_client_certificate_password',
},
}),
))
director.preferences.update(preferences or [])

View File

@ -799,6 +799,9 @@ def parse_options(argv=None):
'client_certificate': opts.client_certificate,
'client_certificate_key': opts.client_certificate_key,
'client_certificate_password': opts.client_certificate_password,
'proxy_client_certificate': opts.proxy_client_certificate,
'proxy_client_certificate_key': opts.proxy_client_certificate_key,
'proxy_client_certificate_password': opts.proxy_client_certificate_password,
'quiet': opts.quiet,
'no_warnings': opts.no_warnings,
'forceurl': opts.geturl,
@ -911,6 +914,7 @@ def parse_options(argv=None):
'cookiesfrombrowser': opts.cookiesfrombrowser,
'legacyserverconnect': opts.legacy_server_connect,
'nocheckcertificate': opts.no_check_certificate,
'proxy_nocheckcertificate': opts.proxy_no_check_certificate,
'prefer_insecure': opts.prefer_insecure,
'enable_file_urls': opts.enable_file_urls,
'http_headers': opts.headers,

View File

@ -187,7 +187,7 @@ class CurlCFFIRH(ImpersonateRequestHandler, InstanceStoreMixin):
# curl_cffi does not currently set these for proxies
session.curl.setopt(CurlOpt.PROXY_CAINFO, certifi.where())
if not self.verify:
if not self.proxy_verify:
session.curl.setopt(CurlOpt.PROXY_SSL_VERIFYPEER, 0)
session.curl.setopt(CurlOpt.PROXY_SSL_VERIFYHOST, 0)
@ -202,6 +202,15 @@ class CurlCFFIRH(ImpersonateRequestHandler, InstanceStoreMixin):
if client_certificate_password:
session.curl.setopt(CurlOpt.KEYPASSWD, client_certificate_password)
if self._proxy_client_cert:
session.curl.setopt(CurlOpt.PROXY_SSLCERT, self._proxy_client_cert['client_certificate'])
proxy_client_certificate_key = self._proxy_client_cert.get('client_certificate_key')
proxy_client_certificate_password = self._proxy_client_cert.get('client_certificate_password')
if proxy_client_certificate_key:
session.curl.setopt(CurlOpt.PROXY_SSLKEY, proxy_client_certificate_key)
if proxy_client_certificate_password:
session.curl.setopt(CurlOpt.PROXY_KEYPASSWD, proxy_client_certificate_password)
timeout = self._calculate_timeout(request)
# set CURLOPT_LOW_SPEED_LIMIT and CURLOPT_LOW_SPEED_TIME to act as a read timeout. [1]
@ -243,6 +252,8 @@ class CurlCFFIRH(ImpersonateRequestHandler, InstanceStoreMixin):
or (e.code == CurlECode.RECV_ERROR and 'CONNECT' in str(e))
):
raise ProxyError(cause=e) from e
elif e.code == CurlECode.RECV_ERROR and 'SSL' in str(e):
raise SSLError(cause=e) from e
else:
raise TransportError(cause=e) from e

View File

@ -301,6 +301,7 @@ class RequestsRH(RequestHandler, InstanceStoreMixin):
session = RequestsSession()
http_adapter = RequestsHTTPAdapter(
ssl_context=self._make_sslcontext(legacy_ssl_support=legacy_ssl_support),
proxy_ssl_context=self._make_proxy_sslcontext(),
source_address=self.source_address,
max_retries=urllib3.util.retry.Retry(False),
)

View File

@ -187,10 +187,14 @@ class RequestHandler(abc.ABC):
@param source_address: Client-side IP address to bind to for requests.
@param verbose: Print debug request and traffic information to stdout.
@param prefer_system_certs: Whether to prefer system certificates over other means (e.g. certifi).
@param client_cert: SSL client certificate configuration.
@param client_cert: SSL client certificate configuration.z
dict with {client_certificate, client_certificate_key, client_certificate_password}
@param proxy_client_cert: SSL client certificate configuration for proxy connections.
dict with {client_certificate, client_certificate_key, client_certificate_password}
@param verify: Verify SSL certificates
@param proxy_verify: Verify SSL certificates of proxy connections
@param legacy_ssl_support: Enable legacy SSL options such as legacy server connect and older cipher support.
@param proxy_legacy_ssl_support: Enable legacy SSL options such as legacy server connect and older cipher support for proxy connections.
Some configuration options may be available for individual Requests too. In this case,
either the Request configuration option takes precedence or they are merged.
@ -230,8 +234,11 @@ class RequestHandler(abc.ABC):
verbose: bool = False,
prefer_system_certs: bool = False,
client_cert: dict[str, str | None] | None = None,
proxy_client_cert: dict[str, str | None] | None = None,
verify: bool = True,
proxy_verify: bool = True,
legacy_ssl_support: bool = False,
proxy_legacy_ssl_support: bool = False,
**_,
):
@ -244,8 +251,11 @@ class RequestHandler(abc.ABC):
self.verbose = verbose
self.prefer_system_certs = prefer_system_certs
self._client_cert = client_cert or {}
self._proxy_client_cert = proxy_client_cert or {}
self.verify = verify
self.proxy_verify = proxy_verify
self.legacy_ssl_support = legacy_ssl_support
self.proxy_legacy_ssl_support = proxy_legacy_ssl_support
super().__init__()
def _make_sslcontext(self, legacy_ssl_support=None):
@ -256,6 +266,14 @@ class RequestHandler(abc.ABC):
**self._client_cert,
)
def _make_proxy_sslcontext(self, legacy_ssl_support=None):
return make_ssl_context(
verify=self.proxy_verify,
legacy_support=legacy_ssl_support if legacy_ssl_support is not None else self.proxy_legacy_ssl_support,
use_certifi=not self.prefer_system_certs,
**self._proxy_client_cert,
)
def _merge_headers(self, request_headers):
return HTTPHeaderDict(self.headers, request_headers)

View File

@ -790,6 +790,20 @@ def create_parser():
help='Password for client certificate private key, if encrypted. '
'If not provided, and the key is encrypted, yt-dlp will ask interactively')
authentication.add_option(
'--proxy-client-certificate',
dest='proxy_client_certificate', metavar='CERTFILE',
help='Path to client certificate file in PEM format for HTTPS proxy. May include the private key')
authentication.add_option(
'--proxy-client-certificate-key',
dest='proxy_client_certificate_key', metavar='KEYFILE',
help='Path to private key file for client certificate for HTTPS proxy')
authentication.add_option(
'--proxy-client-certificate-password',
dest='proxy_client_certificate_password', metavar='PASSWORD',
help='Password for client certificate private key, if encrypted, for HTTPS proxy. '
'If not provided, and the key is encrypted, yt-dlp will ask interactively')
video_format = optparse.OptionGroup(parser, 'Video Format Options')
video_format.add_option(
'-f', '--format',
@ -1094,6 +1108,10 @@ def create_parser():
'--no-check-certificates',
action='store_true', dest='no_check_certificate', default=False,
help='Suppress HTTPS certificate validation')
workarounds.add_option(
'--proxy-no-check-certificates',
action='store_true', dest='proxy_no_check_certificate', default=False,
help='Suppress HTTPS Proxy certificate validation')
workarounds.add_option(
'--prefer-insecure', '--prefer-unsecure',
action='store_true', dest='prefer_insecure',