-
Notifications
You must be signed in to change notification settings - Fork 4
Replace hardcoded CA cert with trust-on-first-use certificate management #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,27 +1,96 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """The "direkt" module provides a best-practice connection mode to your Intinor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Direkt unit's API. The first connection attempt for each request will be done | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with the "requests" library requiring a valid certificate. If this does not | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| succeed the second connection attempt will be done without strict hostname | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checking using an Intinor issued HTTPS certificate. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| While we recommend using the "direkt" module, alternatives are available. An | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alternative is connecting to Direkt units through ISS explicitly using the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "requests" library. If you wish instead to directly connect to your unit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| without using the "direkt" module (and not connect through ISS), e.g. under use | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| of a third-party certificate, please contact Intinor support. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Contact Intinor support for more information on Direkt unit usage and how to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secure your API infrastructure. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """The "direkt" module provides a best-practice connection mode to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| your Intinor Direkt unit's API. The first connection attempt for each | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request will be done with the "requests" library requiring a valid | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| certificate. If this does not succeed the second attempt uses a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| trust-on-first-use (TOFU) model: the server's TLS certificate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fingerprint is compared against a local known-hosts store. On first | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| contact the user is prompted to accept the fingerprint; on subsequent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| connections it is verified automatically. If a fingerprint mismatch is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detected the user is prompted to accept the new fingerprint. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| While we recommend using the "direkt" module or something similar, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alternatives are available. An alternative is connecting to Direkt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| units through ISS explicitly using the "requests" library. If you wish | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| instead to directly connect to your unit without using the "direkt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| module (and not connect through ISS), e.g. under use of a third-party | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| certificate, please contact Intinor support. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Contact Intinor support for more information on Direkt unit usage and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| how to secure your API infrastructure. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import hashlib | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ssl | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import warnings | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import urllib3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from urllib3.poolmanager import PoolManager | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Path to the known-hosts fingerprint store (next to this file). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _KNOWN_HOSTS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'known_hosts.json') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _load_known_hosts(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Load the known-hosts fingerprint store from disk.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if os.path.isfile(_KNOWN_HOSTS_PATH): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with open(_KNOWN_HOSTS_PATH, 'r') as f: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return json.load(f) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _save_known_hosts(known_hosts): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Persist the known-hosts fingerprint store to disk.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with open(_KNOWN_HOSTS_PATH, 'w') as f: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| json.dump(known_hosts, f, indent=2, sort_keys=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+47
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Path to the known-hosts fingerprint store (next to this file). | |
| _KNOWN_HOSTS_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), | |
| 'known_hosts.json') | |
| def _load_known_hosts(): | |
| """Load the known-hosts fingerprint store from disk.""" | |
| if os.path.isfile(_KNOWN_HOSTS_PATH): | |
| with open(_KNOWN_HOSTS_PATH, 'r') as f: | |
| return json.load(f) | |
| return {} | |
| def _save_known_hosts(known_hosts): | |
| """Persist the known-hosts fingerprint store to disk.""" | |
| with open(_KNOWN_HOSTS_PATH, 'w') as f: | |
| json.dump(known_hosts, f, indent=2, sort_keys=True) | |
| # Path to the known-hosts fingerprint store, stored in a per-user | |
| # configuration directory instead of next to the module file. | |
| if os.name == 'nt': | |
| # On Windows, prefer %APPDATA%, fall back to the user's home directory. | |
| _base_config_dir = os.environ.get('APPDATA', os.path.expanduser('~')) | |
| _KNOWN_HOSTS_PATH = os.path.join( | |
| _base_config_dir, | |
| 'Intinor', | |
| 'direkt_known_hosts.json', | |
| ) | |
| else: | |
| # On POSIX, prefer $XDG_CONFIG_HOME, fall back to ~/.config. | |
| _base_config_dir = os.environ.get( | |
| 'XDG_CONFIG_HOME', | |
| os.path.join(os.path.expanduser('~'), '.config'), | |
| ) | |
| _KNOWN_HOSTS_PATH = os.path.join( | |
| _base_config_dir, | |
| 'intinor', | |
| 'direkt_known_hosts.json', | |
| ) | |
| def _load_known_hosts(): | |
| """Load the known-hosts fingerprint store from disk.""" | |
| path = _KNOWN_HOSTS_PATH | |
| if os.path.isfile(path): | |
| try: | |
| with open(path, 'r') as f: | |
| return json.load(f) | |
| except (json.JSONDecodeError, ValueError): | |
| # Corrupted JSON – warn and recover by returning an empty store. | |
| warnings.warn( | |
| f"Known-hosts store at '{path}' is corrupted; resetting.", | |
| RuntimeWarning, | |
| ) | |
| return {} | |
| except OSError as exc: | |
| # I/O problem (e.g. permission error) – warn and continue | |
| # without known hosts rather than crashing all requests. | |
| warnings.warn( | |
| f"Could not read known-hosts store at '{path}': {exc}", | |
| RuntimeWarning, | |
| ) | |
| return {} | |
| return {} | |
| def _save_known_hosts(known_hosts): | |
| """Persist the known-hosts fingerprint store to disk atomically.""" | |
| path = _KNOWN_HOSTS_PATH | |
| directory = os.path.dirname(path) | |
| try: | |
| if directory: | |
| os.makedirs(directory, exist_ok=True) | |
| tmp_path = path + '.tmp' | |
| # Open the temporary file with restrictive permissions (0600). | |
| fd = os.open( | |
| tmp_path, | |
| os.O_WRONLY | os.O_CREAT | os.O_TRUNC, | |
| 0o600, | |
| ) | |
| try: | |
| with os.fdopen(fd, 'w') as f: | |
| json.dump(known_hosts, f, indent=2, sort_keys=True) | |
| finally: | |
| # If json.dump failed before fd was wrapped, ensure the fd is | |
| # closed. If it was wrapped, the context manager above handles it. | |
| if not f.closed: | |
| try: | |
| os.close(fd) | |
| except OSError: | |
| pass | |
| os.replace(tmp_path, path) | |
| except OSError as exc: | |
| # Do not crash the application if we cannot persist the store. | |
| warnings.warn( | |
| f"Could not write known-hosts store to '{path}': {exc}", | |
| RuntimeWarning, | |
| ) |
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using input() inside the request path can raise EOFError / block indefinitely in non-interactive contexts (CI, services, piped stdin). Expose a non-interactive control (e.g. callback/flag/env var) and fail with a clear exception when prompting isn’t possible, so callers can handle first-trust and mismatch cases programmatically.
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_verify_tofu_fingerprint raises RuntimeError on rejection/change. Since this module is a thin wrapper around requests, callers are likely to expect requests.exceptions.* on connection/validation failures (as in the first attempt). Consider raising a Requests/urllib3 SSLError (or a dedicated, documented exception type from this module) so error handling stays consistent and actionable.
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This relies on the private attribute response.raw._connection.sock to access the underlying socket. That attribute is not part of Requests/urllib3’s public API and can be None or renamed across versions (e.g. urllib3 2.x), leading to runtime AttributeError. Prefer a supported mechanism for fingerprint verification (such as urllib3’s built-in fingerprint assertion during connect) or obtain the peer cert through a public connection hook rather than introspecting private fields.
| # Extract the peer certificate from the underlying socket. | |
| sock = response.raw._connection.sock | |
| # Extract the peer certificate from the underlying socket using | |
| # public attributes only. Fall back with a clear error if the | |
| # socket is not available, rather than relying on private fields. | |
| conn = getattr(response.raw, "connection", None) | |
| sock = getattr(conn, "sock", None) if conn is not None else None | |
| if sock is None: | |
| raise RuntimeError( | |
| "Cannot access underlying TLS socket to verify certificate " | |
| "fingerprint; urllib3 connection has no active socket." | |
| ) |
Copilot
AI
Feb 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TOFU fingerprint check happens after super().send(...) returns, which means the HTTP request (including any Authorization headers / credentials) may already have been sent over a TLS connection that has not yet been trusted. This defeats the purpose of TOFU and can leak secrets to a MITM on first contact or after a fingerprint change, and it also won’t protect intermediate redirect hops. Restructure so the certificate is fetched and the fingerprint is validated (and the user decision recorded) before any request bytes are transmitted, e.g. by performing a preflight TLS connect to obtain the cert/fingerprint and then enforcing it during the actual request (or by using urllib3’s fingerprint assertion during connect for the request once the fingerprint is known).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
README now only mentions “certificate trust handling” but doesn’t describe the new TOFU behavior (first-contact prompt, fingerprint mismatch prompt) or where the fingerprint store is kept. This doesn’t fully reflect the new trust model described in the PR; please document the TOFU workflow and the
known_hosts.jsonlocation/behavior so users understand the prompts and how to reset trust.