From f668edee5317d788e8604f9b8de9d76947515c94 Mon Sep 17 00:00:00 2001 From: TAHRI Ahmed R Date: Sat, 11 Nov 2023 08:26:10 +0100 Subject: [PATCH] :bookmark: Release 1.0.2 (#2) ### Added - Function `register_ca` so that user may register their own custom CA (PEM, and DER accepted) in addition to the system trust store. ### Fixed - Overrule `SSL_CERT_FILE` environment variable so that system CA is always returned. ### Changed - Function `create_default_ssl_context` now instantiate an `SSLContext` with the Mozilla Recommended Cipher Suite, instead of your system default. --- CHANGELOG.md | 11 ++++++++++ README.md | 11 ++++++++++ wassima/__init__.py | 49 ++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9b278..6d4a176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to wassima will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 1.0.2 (2023-11-11) + +### Added +- Function `register_ca` so that user may register their own custom CA (PEM, and DER accepted) in addition to the system trust store. + +### Fixed +- Overrule `SSL_CERT_FILE` environment variable so that system CA is always returned. + +### Changed +- Function `create_default_ssl_context` now instantiate an `SSLContext` with the Mozilla Recommended Cipher Suite, instead of your system default. + ## 1.0.1 (2023-09-26) ### Added diff --git a/README.md b/README.md index a7dc2c4..53b9c00 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,14 @@ bundle = wassima.generate_ca_bundle() # ... It contains a string with all of your root CAs! # It is not a path but the file content itself. ``` + +*C) Register your own CA in addition to the system's* + +```python +import wassima + +wassima.register_ca(open("./myrootca.pem", "r").read()) +bundle = wassima.generate_ca_bundle() +# ... It contains a string with all of your root CAs, PLUS your own 'myrootca.pem'. +# It is not a path but the file content itself. +``` diff --git a/wassima/__init__.py b/wassima/__init__.py index 6ff1fd5..d5de65f 100644 --- a/wassima/__init__.py +++ b/wassima/__init__.py @@ -6,15 +6,38 @@ import ssl from functools import lru_cache +from os import environ from ssl import DER_cert_to_PEM_cert +from threading import RLock from ._version import VERSION, __version__ #: Determine if we could load correctly the non-native rust module. RUSTLS_LOADED: bool +# Mozilla TLS recommendations for ciphers +# General-purpose servers with a variety of clients, recommended for almost all systems. +MOZ_INTERMEDIATE_CIPHERS: str = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305" +#: Contain user custom CAs +_MANUALLY_REGISTERED_CA: list[bytes] = [] +#: Lock for shared register-ca +_USER_APPEND_CA_LOCK = RLock() try: - from ._rustls import root_der_certificates + from ._rustls import root_der_certificates as _root_der_certificates + + @lru_cache() + def root_der_certificates() -> list[bytes]: + try: + bck = environ.pop("SSL_CERT_FILE") + except KeyError: + bck = None + + try: + with _USER_APPEND_CA_LOCK: + return _root_der_certificates() + _MANUALLY_REGISTERED_CA + finally: + if bck is not None: + environ["SSL_CERT_FILE"] = bck RUSTLS_LOADED = True except ImportError: @@ -33,9 +56,9 @@ warnings.warn( f"""Unable to access your system root CAs. Your particular interpreter and/or operating system ({platform.python_implementation()}, {platform.uname()}, {platform.python_version()}) - is not be supported. While it is not ideal, you may circumvent that warning by having certifi + is not supported. While it is not ideal, you may circumvent that warning by having certifi installed in your environment. Run `python -m pip install certifi`. - You may also open an issue at https://github.com/jawah/wassima/issues to get your platform compatible.""", + You may also open an issue at https://github.com/jawah/wassima/issues to get your platform supported.""", RuntimeWarning, ) @@ -75,18 +98,33 @@ def generate_ca_bundle() -> str: return "\n\n".join(root_pem_certificates()) +def register_ca(pem_or_der_certificate: bytes | str) -> None: + """ + You may register your own CA certificate in addition to your system trust store. + """ + with _USER_APPEND_CA_LOCK: + if isinstance(pem_or_der_certificate, str): + pem_or_der_certificate = PEM_cert_to_DER_cert(pem_or_der_certificate) + + if pem_or_der_certificate not in _MANUALLY_REGISTERED_CA: + _MANUALLY_REGISTERED_CA.append(pem_or_der_certificate) + + root_pem_certificates.cache_clear() + root_der_certificates.cache_clear() + + def create_default_ssl_context() -> ssl.SSLContext: """ Instantiate a native SSLContext (client purposes) that ships with your system root CAs. In addition to that, assign it the default OpenSSL ciphers suite and set TLS 1.2 as the minimum supported version. Also disable commonName check and enforce - hostname altName verification. + hostname altName verification. The Mozilla Recommended Cipher Suite is used instead of system default. """ ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.load_verify_locations(cadata=generate_ca_bundle()) ctx.minimum_version = ssl.TLSVersion.TLSv1_2 - ctx.set_ciphers("DEFAULT") + ctx.set_ciphers(MOZ_INTERMEDIATE_CIPHERS) ctx.verify_mode = ssl.CERT_REQUIRED try: @@ -107,6 +145,7 @@ def create_default_ssl_context() -> ssl.SSLContext: "root_pem_certificates", "generate_ca_bundle", "create_default_ssl_context", + "register_ca", "__version__", "VERSION", "RUSTLS_LOADED",