Hello Team,
Great work on this project. I have encountered an issue starting from V7.5.2 and still persistent in latest V7.6.1
When configured to have both cert and key in the same file, it raises a value error in client_functions.py in get_ssl_ctx method.
Before i used this configuration with no problem at all.
Configuration
config["COT_URL"] = "ssl://tak_server_url:8089"
config["PYTAK_TLS_CLIENT_CERT"] = "./cert_key.pem" ## both cert and key from user.p12 here
config["PYTAK_TLS_DONT_CHECK_HOSTNAME"] = "1"
config["PYTAK_TLS_DONT_VERIFY"] = "1"
Error Traceback
Traceback (most recent call last):
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/site-packages/pytak/client_functions.py", line 1012, in get_ssl_ctx
if not os.path.exists(client_cert) or not os.path.exists(client_key):
~~~~~~~~~~~~~~^^^^^^^^^^^^
File "<frozen genericpath>", line 19, in exists
TypeError: stat: path should be string, bytes, os.PathLike or integer, not NoneType
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/henri.berisha/pytak_test/quick_start.py", line 44, in <module>
asyncio.run(main())
~~~~~~~~~~~^^^^^^^^
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/asyncio/runners.py", line 205, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/asyncio/runners.py", line 128, in run
return self._loop.run_until_complete(task)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/asyncio/base_events.py", line 720, in run_until_complete
return future.result()
~~~~~~~~~~~~~^^
File "/home/henri.berisha/pytak_test/quick_start.py", line 39, in main
await clitool.setup()
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/site-packages/pytak/classes.py", line 929, in setup
write_worker, read_worker = await _make_workers(
^^^^^^^^^^^^^^^^^^^^
self.tx_queue, self.rx_queue, self.config
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/site-packages/pytak/classes.py", line 848, in _make_workers
reader, writer = await pytak.protocol_factory(config)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/site-packages/pytak/client_functions.py", line 607, in protocol_factory
reader, writer = await create_tls_client(config, cot_url)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/site-packages/pytak/client_functions.py", line 934, in create_tls_client
ssl_ctx = get_ssl_ctx(tls_config)
File "/home/henri.berisha/anaconda3/envs/pytak-v-7-6-1/lib/python3.14/site-packages/pytak/client_functions.py", line 1020, in get_ssl_ctx
raise ValueError(
...<3 lines>...
) from exc
ValueError: Error opening resource. Using: PYTAK_TLS_CLIENT_CERT=./cert_key.pem [PYTAK_TLS_CLIENT_KEY=None] Using Password: False?
Solution Fix - This mod in get_ssl_ctx fixes the issue when both cert and key are in the same file
def get_ssl_ctx(tls_config: SectionProxy) -> ssl.SSLContext:
"""Configure a TLS socket context."""
client_cert = tls_config.get("PYTAK_TLS_CLIENT_CERT")
client_key = tls_config.get("PYTAK_TLS_CLIENT_KEY", fallback=None)
client_cafile = tls_config.get("PYTAK_TLS_CLIENT_CAFILE", fallback=None)
client_password = tls_config.get(
"PYTAK_TLS_CERT_ENROLLMENT_PASSPHRASE",
fallback=tls_config.get("PYTAK_TLS_CLIENT_PASSWORD", fallback=None),
)
client_ciphers = tls_config.get("PYTAK_TLS_CLIENT_CIPHERS") or "ALL"
# Do not verify CA against our trust store.
dont_verify = tls_config.getboolean(
"PYTAK_TLS_DONT_VERIFY",
fallback=False,
)
dont_check_hostname = dont_verify or tls_config.getboolean(
"PYTAK_TLS_DONT_CHECK_HOSTNAME",
fallback=False,
)
# Client certificate is always required.
if client_cert:
if not os.path.exists(client_cert):
raise SyntaxError(
f"Resource not found: PYTAK_TLS_CLIENT_CERT={client_cert}"
)
else:
raise SyntaxError("Missing value: PYTAK_TLS_CLIENT_CERT")
# Client key is optional because the certificate PEM may contain
# both the certificate and the private key.
if client_key:
if not os.path.exists(client_key):
raise SyntaxError(
f"Resource not found: PYTAK_TLS_CLIENT_KEY={client_key}"
)
# SSL Context
ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ssl_ctx.options |= ssl.OP_NO_TLSv1
ssl_ctx.options |= ssl.OP_NO_TLSv1_1
ssl_ctx.set_ciphers(client_ciphers)
# Checks & Verifications
ssl_ctx.check_hostname = True
ssl_ctx.verify_mode = ssl.VerifyMode.CERT_REQUIRED
# PKCS#12.
#
# OpenSSL's load_cert_chain() requires filesystem paths, but the
# extracted PEMs are needed only for that call. Keeping them in /tmp
# for the life of a gateway leaks three files on every reconnect and
# can fill a RAM-backed tmpfs during a long TAK Server outage.
temporary_cert_paths = []
if client_cert.endswith(".p12"):
cert_paths = convert_cert(client_cert, client_password)
temporary_cert_paths = [
path for path in cert_paths.values()
if path
]
client_cert = cert_paths["cert_pem_path"]
client_key = cert_paths["pk_pem_path"]
try:
# The client certificate must exist.
# Here the client _cert can contain both the cert and key if provided in one file:
# PYTAK_TLS_CLIENT_CERT = /some/path_to/client_cert_and_key.pem
if not os.path.exists(client_cert):
raise SystemError(
f"Missing client certificate: {client_cert}"
)
# If client_key is provided separately, verify that it exists.
if client_key and not os.path.exists(client_key):
raise SystemError(
f"Missing client private key: {client_key}"
)
# keyfile=None is intentional.
#
# When client_key is None, OpenSSL/Python will look for the
# private key in the same PEM file as client_cert.
ssl_ctx.load_cert_chain(
client_cert,
keyfile=client_key,
password=client_password,
)
except Exception as exc:
raise ValueError(
f"Error opening resource. "
f"Using: PYTAK_TLS_CLIENT_CERT={client_cert} "
f"[PYTAK_TLS_CLIENT_KEY={client_key}] "
f"Using Password: {bool(client_password)}?"
) from exc
finally:
# Remove temporary PEM files generated from PKCS#12.
for path in temporary_cert_paths:
try:
os.remove(path)
except FileNotFoundError:
pass
# CA File
if client_cafile:
ssl_ctx.load_verify_locations(
cafile=client_cafile
)
# Disables TLS Server Common Name Verification
if dont_check_hostname:
warnings.warn(
"Disabled TLS Server Common Name Verification"
)
ssl_ctx.check_hostname = False
# Disables TLS Server Certificate Verification
if dont_verify:
warnings.warn(
"Disabled TLS Server Certificate Verification"
)
ssl_ctx.verify_mode = ssl.CERT_NONE
return ssl_ctx
Hello Team,
Great work on this project. I have encountered an issue starting from V7.5.2 and still persistent in latest V7.6.1
When configured to have both cert and key in the same file, it raises a value error in
client_functions.pyinget_ssl_ctxmethod.Before i used this configuration with no problem at all.
Configuration
Error Traceback
Solution Fix - This mod in
get_ssl_ctxfixes the issue when both cert and key are in the same file