Repository: abhinavsingh/proxy.py
Pull request #1446 (2024-08-10 05:33:02)

TLS intercept self-signed servers using `--insecure-tls-interception`
- Based upon https://github.com/abhinavsingh/proxy.py/pull/1401
- Fixes https://github.com/abhinavsingh/proxy.py/issues/1292
- Fixes https://github.com/abhinavsingh/proxy.py/issues/536

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) Report
Attention: Patch coverage is `86.20690%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 83.94%. Comparing base [(`39854e1`)](https://app.codecov.io/gh/abhinavsingh/proxy.py/commit/39854e1d799e198c393d3721e8b75602f72554b0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) to head [(`615ceb3`)](https://app.codecov.io/gh/abhinavsingh/proxy.py/commit/615ceb3ef67f1557a11635330445098b6e78454a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).

| [Files](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | Patch % | Lines |
|---|---|---|
| [proxy/common/utils.py](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446?src=pr&el=tree&filepath=proxy%2Fcommon%2Futils.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29tbW9uL3V0aWxzLnB5) | 81.81% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) |
| [proxy/core/connection/server.py](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446?src=pr&el=tree&filepath=proxy%2Fcore%2Fconnection%2Fserver.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#diff-cHJveHkvY29yZS9jb25uZWN0aW9uL3NlcnZlci5weQ==) | 33.33% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) |

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop    #1446      +/-   ##
===========================================
- Coverage    84.57%   83.94%   -0.63%     
===========================================
  Files          177      178       +1     
  Lines         8103     8129      +26     
  Branches      1239     1241       +2     
===========================================
- Hits          6853     6824      -29     
- Misses        1052     1097      +45     
- Partials       198      208      +10     
```

| [Flag](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | Coverage Δ | |
|---|---|---|
| [GHA](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `83.94% <86.20%> (-0.47%)` | :arrow_down: |
| [Linux](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `83.94% <86.20%> (-0.09%)` | :arrow_down: |
| [Python 3.10](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `85.14% <82.60%> (-0.01%)` | :arrow_down: |
| [Python 3.11](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `79.35% <86.20%> (-4.96%)` | :arrow_down: |
| [Python 3.12](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `?` | |
| [Python 3.6](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `?` | |
| [Python 3.7](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `?` | |
| [Python 3.8](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `?` | |
| [Python 3.9](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `?` | |
| [Windows](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `?` | |
| [macOS](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `?` | |
| [pytest](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh) | `83.94% <86.20%> (-0.47%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/abhinavsingh/proxy.py/pull/1446?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Abhinav+Singh).


Patch:
diff --git a/README.md b/README.md
index d38bc4e..bf3964d 100644
--- a/README.md
+++ b/README.md
@@ -71,6 +71,7 @@
   - [Plugin Ordering](#plugin-ordering)
 - [End-to-End Encryption](#end-to-end-encryption)
 - [TLS Interception](#tls-interception)
+  - [Insecure TLS Interception](#insecure-tls-interception)
   - [TLS Interception With Docker](#tls-interception-with-docker)
 - [GROUT (NGROK Alternative)](#grout-ngrok-alternative)
   - [Grout Usage](#grout-usage)
@@ -1241,6 +1242,13 @@ cached file instead of plain text.
 Now use CA flags with other
 [plugin examples](#plugin-examples) to see them work with `https` traffic.
 
+## Insecure TLS Interception
+
+To intercept TLS traffic from a server using a self-signed certificate
+add the `--insecure-tls-interception` flag to disable mandatory TLS certificate validation.
+
+NOTE: This flag disables certificate check for all servers.
+
 ## TLS Interception With Docker
 
 Important notes about TLS Interception with Docker container:
@@ -2510,17 +2518,16 @@ To run standalone benchmark for `proxy.py`, use the following command from repo
 
 ```console
 ❯ proxy -h
-usage: -m [-h] [--enable-proxy-protocol] [--threadless] [--threaded]
-          [--num-workers NUM_WORKERS] [--enable-events] [--enable-conn-pool]
-          [--key-file KEY_FILE] [--cert-file CERT_FILE]
-          [--client-recvbuf-size CLIENT_RECVBUF_SIZE]
-          [--server-recvbuf-size SERVER_RECVBUF_SIZE]
-          [--max-sendbuf-size MAX_SENDBUF_SIZE] [--timeout TIMEOUT]
-          [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
+usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
           [--tunnel-username TUNNEL_USERNAME]
           [--tunnel-ssh-key TUNNEL_SSH_KEY]
           [--tunnel-ssh-key-passphrase TUNNEL_SSH_KEY_PASSPHRASE]
-          [--tunnel-remote-port TUNNEL_REMOTE_PORT]
+          [--tunnel-remote-port TUNNEL_REMOTE_PORT] [--threadless]
+          [--threaded] [--num-workers NUM_WORKERS] [--enable-events]
+          [--enable-proxy-protocol] [--enable-conn-pool] [--key-file KEY_FILE]
+          [--cert-file CERT_FILE] [--client-recvbuf-size CLIENT_RECVBUF_SIZE]
+          [--server-recvbuf-size SERVER_RECVBUF_SIZE]
+          [--max-sendbuf-size MAX_SENDBUF_SIZE] [--timeout TIMEOUT]
           [--local-executor LOCAL_EXECUTOR] [--backlog BACKLOG]
           [--hostname HOSTNAME] [--hostnames HOSTNAMES [HOSTNAMES ...]]
           [--port PORT] [--ports PORTS [PORTS ...]] [--port-file PORT_FILE]
@@ -2533,9 +2540,9 @@ usage: -m [-h] [--enable-proxy-protocol] [--threadless] [--threaded]
           [--work-klass WORK_KLASS] [--pid-file PID_FILE] [--openssl OPENSSL]
           [--data-dir DATA_DIR] [--ssh-listener-klass SSH_LISTENER_KLASS]
           [--disable-http-proxy] [--disable-headers DISABLE_HEADERS]
-          [--ca-key-file CA_KEY_FILE] [--ca-cert-dir CA_CERT_DIR]
-          [--ca-cert-file CA_CERT_FILE] [--ca-file CA_FILE]
-          [--ca-signing-key-file CA_SIGNING_KEY_FILE]
+          [--ca-key-file CA_KEY_FILE] [--insecure-tls-interception]
+          [--ca-cert-dir CA_CERT_DIR] [--ca-cert-file CA_CERT_FILE]
+          [--ca-file CA_FILE] [--ca-signing-key-file CA_SIGNING_KEY_FILE]
           [--auth-plugin AUTH_PLUGIN] [--cache-requests]
           [--cache-by-content-type] [--cache-dir CACHE_DIR]
           [--proxy-pool PROXY_POOL] [--enable-web-server]
@@ -2549,13 +2556,25 @@ usage: -m [-h] [--enable-proxy-protocol] [--threadless] [--threaded]
           [--filtered-client-ips FILTERED_CLIENT_IPS]
           [--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG]
 
-proxy.py v2.4.4rc6.dev191+gef5a8922
+proxy.py v2.4.5
 
 options:
   -h, --help            show this help message and exit
-  --enable-proxy-protocol
-                        Default: False. If used, will enable proxy protocol.
-                        Only version 1 is currently supported.
+  --tunnel-hostname TUNNEL_HOSTNAME
+                        Default: None. Remote hostname or IP address to which
+                        SSH tunnel will be established.
+  --tunnel-port TUNNEL_PORT
+                        Default: 22. SSH port of the remote host.
+  --tunnel-username TUNNEL_USERNAME
+                        Default: None. Username to use for establishing SSH
+                        tunnel.
+  --tunnel-ssh-key TUNNEL_SSH_KEY
+                        Default: None. Private key path in pem format
+  --tunnel-ssh-key-passphrase TUNNEL_SSH_KEY_PASSPHRASE
+                        Default: None. Private key passphrase
+  --tunnel-remote-port TUNNEL_REMOTE_PORT
+                        Default: 8899. Remote port which will be forwarded
+                        locally for proxy.
   --threadless          Default: True. Enabled by default on Python 3.8+ (mac,
                         linux). When disabled a new thread is spawned to
                         handle each client connection.
@@ -2567,6 +2586,9 @@ options:
   --enable-events       Default: False. Enables core to dispatch lifecycle
                         events. Plugins can be used to subscribe for core
                         events.
+  --enable-proxy-protocol
+                        Default: False. If used, will enable proxy protocol.
+                        Only version 1 is currently supported.
   --enable-conn-pool    Default: False. (WIP) Enable upstream connection
                         pooling.
   --key-file KEY_FILE   Default: None. Server key file to enable end-to-end
@@ -2588,21 +2610,6 @@ options:
   --timeout TIMEOUT     Default: 10.0. Number of seconds after which an
                         inactive connection must be dropped. Inactivity is
                         defined by no data sent or received by the client.
-  --tunnel-hostname TUNNEL_HOSTNAME
-                        Default: None. Remote hostname or IP address to which
-                        SSH tunnel will be established.
-  --tunnel-port TUNNEL_PORT
-                        Default: 22. SSH port of the remote host.
-  --tunnel-username TUNNEL_USERNAME
-                        Default: None. Username to use for establishing SSH
-                        tunnel.
-  --tunnel-ssh-key TUNNEL_SSH_KEY
-                        Default: None. Private key path in pem format
-  --tunnel-ssh-key-passphrase TUNNEL_SSH_KEY_PASSPHRASE
-                        Default: None. Private key passphrase
-  --tunnel-remote-port TUNNEL_REMOTE_PORT
-                        Default: 8899. Remote port which will be forwarded
-                        locally for proxy.
   --local-executor LOCAL_EXECUTOR
                         Default: 1. Enabled by default. Use 0 to disable. When
                         enabled acceptors will make use of local (same
@@ -2668,6 +2675,8 @@ options:
                         Default: None. CA key to use for signing dynamically
                         generated HTTPS certificates. If used, must also pass
                         --ca-cert-file and --ca-signing-key-file
+  --insecure-tls-interception
+                        Default: False. Disables certificate verification
   --ca-cert-dir CA_CERT_DIR
                         Default: ~/.proxy/certificates. Directory to store
                         dynamically generated certificates. Also see --ca-key-
@@ -2676,9 +2685,9 @@ options:
                         Default: None. Signing certificate to use for signing
                         dynamically generated HTTPS certificates. If used,
                         must also pass --ca-key-file and --ca-signing-key-file
-  --ca-file CA_FILE     Default: /Users/abhinavsingh/Dev/proxy.py/.venv31013/l
-                        ib/python3.10/site-packages/certifi/cacert.pem.
-                        Provide path to custom CA bundle for peer certificate
+  --ca-file CA_FILE     Default: /Users/abhinavsingh/Dev/proxy.py/.venv3118/li
+                        b/python3.11/site-packages/certifi/cacert.pem. Provide
+                        path to custom CA bundle for peer certificate
                         verification
   --ca-signing-key-file CA_SIGNING_KEY_FILE
                         Default: None. CA signing key to use for dynamic
diff --git a/proxy/common/constants.py b/proxy/common/constants.py
index 4655822..ca47abd 100644
--- a/proxy/common/constants.py
+++ b/proxy/common/constants.py
@@ -91,6 +91,7 @@ DEFAULT_BASIC_AUTH = None
 DEFAULT_MAX_SEND_SIZE = 64 * 1024
 DEFAULT_BUFFER_SIZE = 128 * 1024
 DEFAULT_CA_CERT_DIR = None
+DEFAULT_INSECURE_TLS_INTERCEPTION = False
 DEFAULT_CA_CERT_FILE = None
 DEFAULT_CA_KEY_FILE = None
 DEFAULT_CA_SIGNING_KEY_FILE = None
diff --git a/proxy/common/utils.py b/proxy/common/utils.py
index 9ac8883..84f3484 100644
--- a/proxy/common/utils.py
+++ b/proxy/common/utils.py
@@ -12,17 +12,21 @@
 
        utils
 """
+import os
 import ssl
 import sys
 import socket
 import logging
 import argparse
+import tempfile
 import functools
 import ipaddress
 import contextlib
 from types import TracebackType
 from typing import Any, Dict, List, Type, Tuple, Callable, Optional
 
+import _ssl  # noqa: WPS436
+
 from .types import HostPort
 from .constants import (
     CRLF, COLON, HTTP_1_1, IS_WINDOWS, WHITESPACE, DEFAULT_TIMEOUT,
@@ -36,6 +40,23 @@ if not IS_WINDOWS:  # pragma: no cover
 logger = logging.getLogger(__name__)
 
 
+def cert_der_to_dict(der: Optional[bytes]) -> Dict[str, Any]:
+    """Parse a DER formatted certificate to a python dict"""
+    if not der:
+        return {}
+    with tempfile.NamedTemporaryFile(delete=False) as cert_file:
+        pem = ssl.DER_cert_to_PEM_cert(der)
+        cert_file.write(pem.encode())
+        cert_file.flush()
+        cert_file.seek(0)
+    try:
+        certificate = _ssl._test_decode_cert(cert_file.name)
+    finally:
+        cert_file.close()
+        os.remove(cert_file.name)
+    return certificate or {}
+
+
 def tls_interception_enabled(flags: argparse.Namespace) -> bool:
     return flags.ca_key_file is not None and \
         flags.ca_cert_dir is not None and \
diff --git a/proxy/core/connection/server.py b/proxy/core/connection/server.py
index 3123304..619ada4 100644
--- a/proxy/core/connection/server.py
+++ b/proxy/core/connection/server.py
@@ -15,6 +15,7 @@ from .types import tcpConnectionTypes
 from .connection import TcpConnection, TcpConnectionUninitializedException
 from ...common.types import HostPort, TcpOrTlsSocket
 from ...common.utils import new_socket_connection
+from ...common.constants import DEFAULT_SSL_CONTEXT_OPTIONS
 
 
 class TcpServerConnection(TcpConnection):
@@ -51,12 +52,12 @@ class TcpServerConnection(TcpConnection):
             # Ref https://github.com/PyCQA/pylint/issues/3691
             verify_mode: ssl.VerifyMode = ssl.VerifyMode.CERT_REQUIRED,     # pylint: disable=E1101
     ) -> None:
-        ctx = ssl.create_default_context(
-            ssl.Purpose.SERVER_AUTH,
-            cafile=ca_file,
+        ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=ca_file)
+        ctx.options |= DEFAULT_SSL_CONTEXT_OPTIONS
+        # pylint: disable=E1101
+        ctx.check_hostname = (
+            False if verify_mode == ssl.VerifyMode.CERT_NONE else hostname is not None
         )
-        ctx.options |= ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
-        ctx.check_hostname = hostname is not None
         ctx.verify_mode = verify_mode
         self.connection.setblocking(True)
         self._conn = ctx.wrap_socket(
diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py
index 4e2f44a..80af168 100644
--- a/proxy/http/proxy/server.py
+++ b/proxy/http/proxy/server.py
@@ -21,7 +21,7 @@ import socket
 import logging
 import threading
 import subprocess
-from typing import Any, Dict, List, Union, Optional, cast
+from typing import Any, Dict, List, Union, Optional
 
 from .plugin import HttpProxyBasePlugin
 from ..parser import HttpParser, httpParserTypes, httpParserStates
@@ -35,7 +35,7 @@ from ...common.pki import gen_csr, sign_csr, gen_public_key
 from ...core.event import eventNames
 from ...common.flag import flags
 from ...common.types import Readables, Writables, Descriptors
-from ...common.utils import text_
+from ...common.utils import text_, cert_der_to_dict
 from ...core.connection import (
     TcpServerConnection, TcpConnectionUninitializedException,
 )
@@ -43,7 +43,8 @@ from ...common.constants import (
     COMMA, DEFAULT_CA_FILE, PLUGIN_PROXY_AUTH, DEFAULT_CA_CERT_DIR,
     DEFAULT_CA_KEY_FILE, DEFAULT_CA_CERT_FILE, DEFAULT_DISABLE_HEADERS,
     PROXY_AGENT_HEADER_VALUE, DEFAULT_DISABLE_HTTP_PROXY,
-    DEFAULT_CA_SIGNING_KEY_FILE, DEFAULT_HTTP_PROXY_ACCESS_LOG_FORMAT,
+    DEFAULT_CA_SIGNING_KEY_FILE, DEFAULT_INSECURE_TLS_INTERCEPTION,
+    DEFAULT_HTTP_PROXY_ACCESS_LOG_FORMAT,
     DEFAULT_HTTPS_PROXY_ACCESS_LOG_FORMAT,
 )
 
@@ -74,6 +75,13 @@ flags.add_argument(
     'HTTPS certificates.  If used, must also pass --ca-cert-file and --ca-signing-key-file',
 )
 
+flags.add_argument(
+    '--insecure-tls-interception',
+    action='store_true',
+    default=DEFAULT_INSECURE_TLS_INTERCEPTION,
+    help='Default: False. Disables certificate verification',
+)
+
 flags.add_argument(
     '--ca-cert-dir',
     type=str,
@@ -760,10 +768,17 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
         assert isinstance(self.upstream.connection, socket.socket)
         do_close = False
         try:
+            # pylint: disable=E1101
+            verify_mode = (
+                ssl.VerifyMode.CERT_NONE
+                if self.flags.insecure_tls_interception
+                else ssl.VerifyMode.CERT_REQUIRED
+            )
             self.upstream.wrap(
                 text_(self.request.host),
                 self.flags.ca_file,
                 as_non_blocking=True,
+                verify_mode=verify_mode,
             )
         except ssl.SSLCertVerificationError:    # Server raised certificate verification error
             # When --disable-interception-on-ssl-cert-verification-error flag is on,
@@ -802,7 +817,7 @@ class HttpProxyPlugin(HttpProtocolHandlerPlugin):
         try:
             # TODO: Perform async certificate generation
             generated_cert = self.generate_upstream_certificate(
-                cast(Dict[str, Any], self.upstream.connection.getpeercert()),
+                cert_der_to_dict(self.upstream.connection.getpeercert(True)),
             )
             self.client.wrap(self.flags.ca_signing_key_file, generated_cert)
         except subprocess.TimeoutExpired as e:  # Popen communicate timeout


Test patch:
diff --git a/tests/certificates/test_cert_data.py b/tests/certificates/test_cert_data.py
new file mode 100644
index 0000000..612429b
--- /dev/null
+++ b/tests/certificates/test_cert_data.py
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+"""
+    proxy.py
+    ~~~~~~~~
+    ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
+    Network monitoring, controls & Application development, testing, debugging.
+
+    :copyright: (c) 2013-present by Abhinav Singh and contributors.
+    :license: BSD, see LICENSE for more details.
+"""
+from typing import Any
+
+
+# pylint: disable=line-too-long
+test_cert_bytes = b"0\x82\x03\xa30\x82\x02\x8b\xa0\x03\x02\x01\x02\x02\x14PE\x01\x8c\xa6\xea\xd8#\xcf\x90\xb0D\xc7\x04\xde\x9b9Y\xf3 0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x000a1\x0b0\t\x06\x03U\x04\x06\x13\x02as1\x0b0\t\x06\x03U\x04\x08\x0c\x02as1\x0b0\t\x06\x03U\x04\x07\x0c\x02as1\x0b0\t\x06\x03U\x04\n\x0c\x02as1\x0b0\t\x06\x03U\x04\x0b\x0c\x02as1\x0b0\t\x06\x03U\x04\x03\x0c\x02as1\x110\x0f\x06\t*\x86H\x86\xf7\r\x01\t\x01\x16\x02as0\x1e\x17\r240429125057Z\x17\r250429125057Z0a1\x0b0\t\x06\x03U\x04\x06\x13\x02as1\x0b0\t\x06\x03U\x04\x08\x0c\x02as1\x0b0\t\x06\x03U\x04\x07\x0c\x02as1\x0b0\t\x06\x03U\x04\n\x0c\x02as1\x0b0\t\x06\x03U\x04\x0b\x0c\x02as1\x0b0\t\x06\x03U\x04\x03\x0c\x02as1\x110\x0f\x06\t*\x86H\x86\xf7\r\x01\t\x01\x16\x02as0\x82\x01\"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\xee\xcbU\xe3\xc4]\x83\xb9\x9d\xb1(v0\x18\x18\xc3\x00\x96\xc0\x0f\xc29\x84\xe7/W\xc7\x0b\xec\xdf\x9d-\xec\xd9\x876\xe5m\xda\x96\xea\xb0\xc6\x00\x7f\xb6\x93;\xd6\x1bK`\xd4Hc<\xa0g\xe5Q[\xe3\xe1\xd1DD5\x9b\x12\xdf\xd0\xd0\xc6X\xc9\x98\xc9\xb1\x81\xf5\xa2\x12\xaa\xc1\xb0\x80\xe8)R\xa7\xed\xe3P6\x82\x05\xbcA4\x91\xbcs?\xc2\xf2\xfd-\xe65'};\xa7E\xb2yN\x0fiO7\x82-`CX\xdb\xe0\x9c\xd7\x8e\x00N\nAu\xac/\xb3o\xcaG;\xa4\x8d\xca\x92\xe3F\x96\xe5\xbd\x1dq\xf6\xa5\x9f\xc5@I=\xfc\x1cl\x81\xb3y\x93FaPa^\x08\x0f\x80t\xb8J\xfd\xb8]\xd52\xf5\x9bE\xe8J:\x08\x8c\x98m0\xba\x85\x1b\xb6\x97\xe5\xba4\xe3nU\xa5\xc7\xeb\xde_z\x1a(j\xa7\xeb\x8a\xb4\xe1'?\x91\x80MhG=y\xc7\xf1|\xcaJ@\xae\xc4'\xd6\xd6}L\xf4\x91NV`\x98\x80\xef%\xa2hq\x05s\x02\x03\x01\x00\x01\xa3S0Q0\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14\xc6\xa4,\xe5\xe3\x15j\x18\x15@Xw!\xdd\xbf\xc6\xe5\xf0vG0\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\xc6\xa4,\xe5\xe3\x15j\x18\x15@Xw!\xdd\xbf\xc6\xe5\xf0vG0\x0f\x06\x03U\x1d\x13\x01\x01\xff\x04\x050\x03\x01\x01\xff0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\xacx\xeb\x02\x8a\xd3\x966\xb73\xfb\n\x1eb\ng\xda\x84\x18\x97P\xb4\x7f\x8a\xbd\x82\xf3\x1b\xe8k%\xcc\x0f\xbd\x7fB\xb9\x1df|-k\x01\xf3\x89\x08r\xb9\x93\xf5?Z\x16\xff\x0f\x97\x91b#\xef$I\x11\x9e\x16\xb2J\x97\xd1\x0e\xd6\xabD\xca@\xe7\xb3\xbe\x84S\x1e\xdb;\x9b\xc4\xf4\x18\xf4\x9a\x1b\xcej\xe0qmx\xe4N?K\n.p\xa8\xa6\xfa\xb0\xf7y\xe8\x0f\xbd\x0c216\xb0\xa1d\x1f\x7f3\xa1l?\xbe\x9a\x06\xed]\x1a\x00\xab\xb4e\x13:\x17\x1b\x88\x8e\xcaqp\"\x8f\xa6\xf7\x06J?`\xe0\xf7\xce\xf8K\x08\x15\x18\xa1\xc4\xb5\xd9hB\xb0\xc6\\\xae?\xa9\x83FL\x8cm\xd1\xad^\xf0\xa5:\x8e\x97\x07\xd2\xd0l\x0e\x9d\x01\xa00c)\xae\xd0@\xefr\xe7,\xb7[\xd3H\xfe1\xfb\xa9|\xd0\xac\xc6i\x98\xe5\xd5\xd1\xf2\x97<\xf9\xe1?=\x93\xfaM\x86\xa2\x9dy\xdeZj\x93&\xa6\x84d\x07a\xbf\xd6\xdde\xaa)\t\xd6\x0e\x99\x85K"  # noqa: WPS342, E501
+
+
+def mock_cert(_: Any) -> Any:
+    return test_cert_bytes
+
+
+cert_dict = {
+    'subject': (
+        (('countryName', 'as'),),
+        (('stateOrProvinceName', 'as'),),
+        (('localityName', 'as'),),
+        (('organizationName', 'as'),),
+        (('organizationalUnitName', 'as'),),
+        (('commonName', 'as'),),
+        (('emailAddress', 'as'),),
+    ),
+    'issuer': (
+        (('countryName', 'as'),),
+        (('stateOrProvinceName', 'as'),),
+        (('localityName', 'as'),),
+        (('organizationName', 'as'),),
+        (('organizationalUnitName', 'as'),),
+        (('commonName', 'as'),),
+        (('emailAddress', 'as'),),
+    ),
+    'version': 3,
+    'serialNumber': '5045018CA6EAD823CF90B044C704DE9B3959F320',
+    'notBefore': 'Apr 29 12:50:57 2024 GMT',
+    'notAfter': 'Apr 29 12:50:57 2025 GMT',
+}
diff --git a/tests/http/proxy/test_http_proxy_tls_interception.py b/tests/http/proxy/test_http_proxy_tls_interception.py
index 2fbdaef..fad6eff 100644
--- a/tests/http/proxy/test_http_proxy_tls_interception.py
+++ b/tests/http/proxy/test_http_proxy_tls_interception.py
@@ -30,6 +30,7 @@ from proxy.http.responses import PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT
 from proxy.core.connection import TcpServerConnection
 from proxy.common.constants import DEFAULT_CA_FILE
 from ...test_assertions import Assertions
+from ...certificates.test_cert_data import mock_cert
 
 
 class TestHttpProxyTlsInterception(Assertions):
@@ -59,6 +60,7 @@ class TestHttpProxyTlsInterception(Assertions):
         # Used for server side wrapping
         self.mock_ssl_context = mocker.patch('ssl.create_default_context')
         upstream_tls_sock = mock.MagicMock(spec=ssl.SSLSocket)
+        upstream_tls_sock.getpeercert = mock_cert
         self.mock_ssl_context.return_value.wrap_socket.return_value = upstream_tls_sock
 
         # Used for client wrapping
@@ -75,8 +77,8 @@ class TestHttpProxyTlsInterception(Assertions):
 
         # Do not mock the original wrap method
         self.mock_server_conn.return_value.wrap.side_effect = \
-            lambda x, y, as_non_blocking: TcpServerConnection.wrap(
-                self.mock_server_conn.return_value, x, y, as_non_blocking=as_non_blocking,
+            lambda x, y, as_non_blocking, verify_mode: TcpServerConnection.wrap(
+                self.mock_server_conn.return_value, x, y, as_non_blocking=as_non_blocking, verify_mode=verify_mode,
             )
 
         type(self.mock_server_conn.return_value).connection = \
diff --git a/tests/plugin/test_http_proxy_plugins_with_tls_interception.py b/tests/plugin/test_http_proxy_plugins_with_tls_interception.py
index a0a05b6..d07ab64 100644
--- a/tests/plugin/test_http_proxy_plugins_with_tls_interception.py
+++ b/tests/plugin/test_http_proxy_plugins_with_tls_interception.py
@@ -29,6 +29,7 @@ from proxy.http.responses import (
 from proxy.core.connection import TcpServerConnection
 from .utils import get_plugin_by_test_name
 from ..test_assertions import Assertions
+from ..certificates.test_cert_data import mock_cert
 
 
 class TestHttpProxyPluginExamplesWithTlsInterception(Assertions):
@@ -78,8 +79,8 @@ class TestHttpProxyPluginExamplesWithTlsInterception(Assertions):
         self.protocol_handler.initialize()
 
         self.server = self.mock_server_conn.return_value
-
         self.server_ssl_connection = mocker.MagicMock(spec=ssl.SSLSocket)
+        self.server_ssl_connection.getpeercert = mock_cert
         self.mock_ssl_context.return_value.wrap_socket.return_value = self.server_ssl_connection
         self.client_ssl_connection = mocker.MagicMock(spec=ssl.SSLSocket)
         self.mock_ssl_wrap.return_value.wrap_socket.return_value = self.client_ssl_connection
@@ -97,8 +98,8 @@ class TestHttpProxyPluginExamplesWithTlsInterception(Assertions):
 
         # Do not mock the original wrap method
         self.server.wrap.side_effect = \
-            lambda x, y, as_non_blocking: TcpServerConnection.wrap(
-                self.server, x, y, as_non_blocking=as_non_blocking,
+            lambda x, y, as_non_blocking, verify_mode: TcpServerConnection.wrap(
+                self.server, x, y, as_non_blocking=as_non_blocking, verify_mode=verify_mode,
             )
 
         self.server.has_buffer.side_effect = has_buffer