From b908acfd37d1bd50272915309f4600ab0c4ae491 Mon Sep 17 00:00:00 2001 From: Sving1024 Date: Mon, 25 Aug 2025 23:55:56 +0800 Subject: [PATCH 1/7] fix init order & add cli args --- accesser/utils/certmanager.py | 47 +------------------------------- accesser/utils/importca.py | 3 +-- accesser/utils/setting.py | 51 ++++++++++++++++++++++++++++++++--- 3 files changed, 49 insertions(+), 52 deletions(-) diff --git a/accesser/utils/certmanager.py b/accesser/utils/certmanager.py index 37df450..60daf22 100644 --- a/accesser/utils/certmanager.py +++ b/accesser/utils/certmanager.py @@ -36,52 +36,7 @@ from .log import logger logger = logger.getChild("certmanager") from . import setting -from .setting import basepath - - -def decide_state_path_legacy(): - if setting.config["importca"]: - return Path(basepath) - else: - return Path() - - -def decide_state_path_unix_like(): - if os.geteuid() == 0: - logger.warn("Running Accesser as the root user carries certain risks; see pull #245") - return Path("/var/lib") / "accesser" - - state_path = os.getenv("XDG_STATE_HOME", None) - if state_path is not None: - state_path = Path(state_path) / "accesser" - else: - state_path = Path.home() / ".local/state" / "accesser" - return state_path - - -def decide_certpath(): - certpath = None - # 人为指定最优先 - #if setting.config["state_dir"]: - #return Path(setting.config["state_dir"]) / "cert" - match platform.system(): - case 'Linux' | 'FreeBSD': - deprecated_path = decide_state_path_legacy() / "CERT" - # 暂仅在 *nix 上视为已废弃 - if deprecated_path.exists(): - logger.warn("deprecated path, see pull #245") - return deprecated_path - certpath = decide_state_path_unix_like() / "cert" - case _: - # windows,mac,android ... - certpath = decide_state_path_legacy() / "CERT" - return certpath - - -certpath = decide_certpath() -if not certpath.exists(): - os.makedirs(certpath, exist_ok=True) - +from .setting import certpath def create_root_ca(): key = rsa.generate_private_key( diff --git a/accesser/utils/importca.py b/accesser/utils/importca.py index 6b7380c..e8e6074 100644 --- a/accesser/utils/importca.py +++ b/accesser/utils/importca.py @@ -17,7 +17,6 @@ # along with this program. If not, see . import os, sys -from pathlib import Path import subprocess import locale @@ -27,9 +26,9 @@ from . import setting from . import certmanager as cm from .log import logger +from .setting import certpath logger = logger.getChild('importca') -certpath = cm.certpath def logandrun(cmd): if hasattr(subprocess, 'STARTUPINFO'): diff --git a/accesser/utils/setting.py b/accesser/utils/setting.py index 6b871ff..c1b6d73 100644 --- a/accesser/utils/setting.py +++ b/accesser/utils/setting.py @@ -6,8 +6,11 @@ except ModuleNotFoundError: import tomli as tomllib import argparse +import platform +import logging basepath = Path(__file__).parent.parent +certpath = None def deep_merge(config_a: dict, config_b: dict): @@ -59,7 +62,47 @@ def deep_merge(config_a: dict, config_b: dict): config = deep_merge(_config, _rules) +def decide_state_path_legacy(): + if config["importca"]: + return Path(basepath) + return Path() + + +def decide_state_path_unix_like(): + if os.geteuid() == 0: + logging.warning( + "Running Accesser as the root user carries certain risks. Do not use it in production." + ) + return Path("/var/lib") / "accesser" + + state_path = os.getenv("XDG_STATE_HOME", None) + if state_path is not None: + state_path = Path(state_path) / "accesser" + else: + state_path = Path.home() / ".local/state" / "accesser" + return state_path + + +def decide_certpath(): + # 人为指定最优先 + if config["state_dir"]: + return Path(config["state_dir"]) / "CERT" + match platform.system(): + case "Linux" | "FreeBSD": + deprecated_path = decide_state_path_legacy() / "CERT" + # 暂仅在 *nix 上视为已废弃 + if deprecated_path.exists(): + logging.warning("deprecated path, see pull #245") + return deprecated_path + return decide_state_path_unix_like() / "CERT" + case _: + # windows,mac,android ... + return decide_state_path_legacy() / "CERT" + return + + def parse_args(): + global certpath parser = argparse.ArgumentParser() parser.add_argument( "--notsetproxy", @@ -80,9 +123,9 @@ def parse_args(): args = parser.parse_args() if args.notsetproxy: config["setproxy"] = False - return - # FIXME Wrong initialization sequence - # see pull #245 if args.notimportca: config["importca"] = False - config["state_dir"] = args.state_dir + if args.state_dir is not None: + config["state_dir"] = args.state_dir + certpath = decide_certpath() + return From 84ca93b98c08da39020537214374d3f70bbbc599 Mon Sep 17 00:00:00 2001 From: Sving1024 Date: Tue, 26 Aug 2025 00:24:59 +0800 Subject: [PATCH 2/7] replace certpath with setting.certpath --- accesser/__init__.py | 4 ++-- accesser/utils/certmanager.py | 15 ++++++++------- accesser/utils/importca.py | 21 ++++++++++----------- accesser/utils/setting.py | 4 +++- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/accesser/__init__.py b/accesser/__init__.py index 7e96d14..e65e360 100644 --- a/accesser/__init__.py +++ b/accesser/__init__.py @@ -51,7 +51,7 @@ async def update_cert(server_name): async with cert_lock: if not server_name in cert_store: cm.create_certificate(server_name) - context.load_cert_chain(os.path.join(cm.certpath, "{}.crt".format(server_name))) + context.load_cert_chain(setting.certpath.joinpath("{}.crt".format(server_name))) cert_store.add(server_name) async def send_pac(writer: asyncio.StreamWriter): @@ -64,7 +64,7 @@ async def send_pac(writer: asyncio.StreamWriter): await writer.wait_closed() async def send_crt(writer: asyncio.StreamWriter, path: str): - with open(os.path.join(cm.certpath, path.rsplit(sep = '/',maxsplit = 1)[-1]), 'rb') as f: + with open(setting.certpath, (path.rsplit(sep = '/',maxsplit = 1)[-1]), 'rb') as f: crt = f.read() writer.write(f'HTTP/1.1 200 OK\r\nContent-Type: application/x-x509-ca-cert\r\nContent-Length: {len(crt)}\r\n\r\n'.encode('iso-8859-1')) writer.write(crt) diff --git a/accesser/utils/certmanager.py b/accesser/utils/certmanager.py index 60daf22..e4e695a 100644 --- a/accesser/utils/certmanager.py +++ b/accesser/utils/certmanager.py @@ -34,9 +34,10 @@ from cryptography.x509.oid import ExtendedKeyUsageOID from .log import logger + logger = logger.getChild("certmanager") from . import setting -from .setting import certpath + def create_root_ca(): key = rsa.generate_private_key( @@ -86,11 +87,11 @@ def create_root_ca(): .sign(key, hashes.SHA256()) ) - (certpath / "root.crt").write_bytes( + setting.certpath.joinpath("root.crt").write_bytes( cert.public_bytes(serialization.Encoding.PEM) ) - (certpath / "root.key").write_bytes( + setting.certpath.joinpath("root.key").write_bytes( key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, @@ -98,7 +99,7 @@ def create_root_ca(): ) ) - (certpath / "root.pfx").write_bytes( + setting.certpath.joinpath("root.pfx").write_bytes( serialization.pkcs12.serialize_key_and_certificates( b"Accesser", key, cert, None, serialization.NoEncryption() ) @@ -106,8 +107,8 @@ def create_root_ca(): def create_certificate(server_name): - rootpem = (certpath / "root.crt").read_bytes() - rootkey = (certpath / "root.key").read_bytes() + rootpem = (setting.certpath / "root.crt").read_bytes() + rootkey = (setting.certpath / "root.key").read_bytes() ca_cert = x509.load_pem_x509_certificate(rootpem) pkey = serialization.load_pem_private_key(rootkey, password=None) @@ -174,7 +175,7 @@ def create_certificate(server_name): .sign(pkey, hashes.SHA256()) ) - (certpath / f"{server_name}.crt").write_bytes( + (setting.certpath / f"{server_name}.crt").write_bytes( cert.public_bytes(serialization.Encoding.PEM) + pkey.private_bytes( encoding=serialization.Encoding.PEM, diff --git a/accesser/utils/importca.py b/accesser/utils/importca.py index e8e6074..3b04dd8 100644 --- a/accesser/utils/importca.py +++ b/accesser/utils/importca.py @@ -26,7 +26,6 @@ from . import setting from . import certmanager as cm from .log import logger -from .setting import certpath logger = logger.getChild('importca') @@ -41,28 +40,28 @@ def logandrun(cmd): def import_windows_ca(): try: - logandrun('certutil -f -user -p "" -exportPFX My Accesser '+os.path.join(certpath, 'root.pfx')) + logandrun('certutil -f -user -p "" -exportPFX My Accesser '+str(setting.certpath.joinpath('root.pfx'))) except subprocess.CalledProcessError: logger.debug("Export Failed") - if not os.path.exists(os.path.join(certpath ,"root.pfx")): + if not setting.certpath.joinpath("root.pfx").exists(): cm.create_root_ca() try: logger.info("Importing new certificate") - logandrun('CertUtil -f -user -p "" -importPFX My '+os.path.join(certpath, 'root.pfx')) + logandrun('CertUtil -f -user -p "" -importPFX My '+str(setting.certpath.joinpath("root.pfx"))) except subprocess.CalledProcessError: logger.error("Import Failed") logandrun('CertUtil -user -delstore My Accesser') - # os.remove(os.path.join(certpath ,"root.pfx")) - # os.remove(os.path.join(certpath ,"root.crt")) - # os.remove(os.path.join(certpath ,"root.key")) + # os.remove(os.path.join(setting.certpath ,"root.pfx")) + # os.remove(os.path.join(setting.certpath ,"root.crt")) + # os.remove(os.path.join(setting.certpath ,"root.key")) # sys.exit(5) logger.warning('Try to manually import the certificate') else: - with open(os.path.join(certpath ,"root.pfx"), 'rb') as pfxfile: + with setting.certpath.joinpath("root.pfx").open("wb") as pfxfile: private_key, certificate, _ = pkcs12.load_key_and_certificates(pfxfile.read(), password=None) - with open(os.path.join(certpath ,"root.crt"), "wb") as certfile: + with setting.certpath.joinpath("root.crt").open("wb") as certfile: certfile.write(certificate.public_bytes(serialization.Encoding.PEM)) - with open(os.path.join(certpath ,"root.key"), "wb") as pkeyfile: + with setting.certpath.joinpath("root.key").open("wb") as pkeyfile: pkeyfile.write(private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, @@ -98,7 +97,7 @@ def get_exist_ca_sha1(): os.system(cmd) def import_ca(): - if not(os.path.exists(os.path.join(certpath ,"root.crt")) and os.path.exists(os.path.join(certpath ,"root.key"))): + if not(setting.certpath.joinpath("root.crt").exists() and setting.certpath.joinpath("root.key").exists()): if setting.config['importca']: if sys.platform.startswith('win'): import_windows_ca() diff --git a/accesser/utils/setting.py b/accesser/utils/setting.py index c1b6d73..32cca2c 100644 --- a/accesser/utils/setting.py +++ b/accesser/utils/setting.py @@ -85,7 +85,7 @@ def decide_state_path_unix_like(): def decide_certpath(): # 人为指定最优先 - if config["state_dir"]: + if "state_dir" in config and config["state_dir"] is not None: return Path(config["state_dir"]) / "CERT" match platform.system(): case "Linux" | "FreeBSD": @@ -128,4 +128,6 @@ def parse_args(): if args.state_dir is not None: config["state_dir"] = args.state_dir certpath = decide_certpath() + if not certpath.exists() or certpath.is_file(): + certpath.mkdir(parents=True) return From 9737fb4f0fbbbf74ae579c43b6b653859fe61f54 Mon Sep 17 00:00:00 2001 From: Sving1024 Date: Tue, 26 Aug 2025 00:40:24 +0800 Subject: [PATCH 3/7] move warning msg to a better place --- accesser/__init__.py | 5 ++++- accesser/utils/setting.py | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/accesser/__init__.py b/accesser/__init__.py index e65e360..e2cc7c9 100644 --- a/accesser/__init__.py +++ b/accesser/__init__.py @@ -209,7 +209,10 @@ async def main(): global context, cert_store, cert_lock, DNSresolver print(f"Accesser v{__version__} Copyright (C) 2018-2024 URenko") setting.parse_args() - + if os.geteuid() == 0: + logger.warning( + "Running Accesser as the root user carries certain risks. Do not use it in production." + ) if setting.rules_update_case in ('old', 'missing'): logger.warning("Updated rules.toml because it is %s.", setting.rules_update_case) elif setting.rules_update_case == 'modified': diff --git a/accesser/utils/setting.py b/accesser/utils/setting.py index 32cca2c..9740551 100644 --- a/accesser/utils/setting.py +++ b/accesser/utils/setting.py @@ -70,9 +70,6 @@ def decide_state_path_legacy(): def decide_state_path_unix_like(): if os.geteuid() == 0: - logging.warning( - "Running Accesser as the root user carries certain risks. Do not use it in production." - ) return Path("/var/lib") / "accesser" state_path = os.getenv("XDG_STATE_HOME", None) From 0486f32a7a60ac1089a438e0066ed77e55f568d9 Mon Sep 17 00:00:00 2001 From: Sving1024 Date: Tue, 26 Aug 2025 00:44:24 +0800 Subject: [PATCH 4/7] replace operator / with joinpath --- accesser/utils/certmanager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/accesser/utils/certmanager.py b/accesser/utils/certmanager.py index e4e695a..958db56 100644 --- a/accesser/utils/certmanager.py +++ b/accesser/utils/certmanager.py @@ -107,8 +107,8 @@ def create_root_ca(): def create_certificate(server_name): - rootpem = (setting.certpath / "root.crt").read_bytes() - rootkey = (setting.certpath / "root.key").read_bytes() + rootpem = setting.certpath.joinpath("root.crt").read_bytes() + rootkey = setting.certpath.joinpath("root.key").read_bytes() ca_cert = x509.load_pem_x509_certificate(rootpem) pkey = serialization.load_pem_private_key(rootkey, password=None) @@ -175,7 +175,7 @@ def create_certificate(server_name): .sign(pkey, hashes.SHA256()) ) - (setting.certpath / f"{server_name}.crt").write_bytes( + setting.certpath.joinpath(f"{server_name}.crt").write_bytes( cert.public_bytes(serialization.Encoding.PEM) + pkey.private_bytes( encoding=serialization.Encoding.PEM, From f7d455cc30eac46fc0f8e531f4c3795faf109eea Mon Sep 17 00:00:00 2001 From: Sving1024 Date: Tue, 26 Aug 2025 00:49:27 +0800 Subject: [PATCH 5/7] remove unused modules --- accesser/utils/certmanager.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/accesser/utils/certmanager.py b/accesser/utils/certmanager.py index 958db56..29f57d6 100644 --- a/accesser/utils/certmanager.py +++ b/accesser/utils/certmanager.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os, platform import datetime -from pathlib import Path from cryptography import x509 from cryptography.x509.oid import NameOID From d4eaee7d7ec165af69e197981d2285e83548278e Mon Sep 17 00:00:00 2001 From: Sving1024 Date: Tue, 26 Aug 2025 00:57:21 +0800 Subject: [PATCH 6/7] add platfrom check before root check --- accesser/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/accesser/__init__.py b/accesser/__init__.py index e2cc7c9..b77e053 100644 --- a/accesser/__init__.py +++ b/accesser/__init__.py @@ -30,6 +30,7 @@ from packaging.version import Version from tld import get_tld, is_tld import dns, dns.asyncresolver, dns.nameserver +import platform from .utils import certmanager as cm from .utils import importca @@ -209,10 +210,11 @@ async def main(): global context, cert_store, cert_lock, DNSresolver print(f"Accesser v{__version__} Copyright (C) 2018-2024 URenko") setting.parse_args() - if os.geteuid() == 0: - logger.warning( - "Running Accesser as the root user carries certain risks. Do not use it in production." - ) + if platform.system() == "Linux" or platform.system() == "FreeBSD": + if os.geteuid() == 0: + logger.warning( + "Running Accesser as the root user carries certain risks. Do not use it in production." + ) if setting.rules_update_case in ('old', 'missing'): logger.warning("Updated rules.toml because it is %s.", setting.rules_update_case) elif setting.rules_update_case == 'modified': From 167e446ad439fdbd467f07f89baa6caef8ddfb9e Mon Sep 17 00:00:00 2001 From: Sving1024 Date: Tue, 26 Aug 2025 13:21:27 +0800 Subject: [PATCH 7/7] fix wrong path; replace open with read_bytes; add clear notifications; --- accesser/__init__.py | 8 ++++---- accesser/utils/importca.py | 13 ++++++------- accesser/utils/setting.py | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/accesser/__init__.py b/accesser/__init__.py index b77e053..da72686 100644 --- a/accesser/__init__.py +++ b/accesser/__init__.py @@ -31,6 +31,7 @@ from tld import get_tld, is_tld import dns, dns.asyncresolver, dns.nameserver import platform +from pathlib import Path from .utils import certmanager as cm from .utils import importca @@ -56,8 +57,8 @@ async def update_cert(server_name): cert_store.add(server_name) async def send_pac(writer: asyncio.StreamWriter): - with open('pac' if os.path.exists('pac') else os.path.join(basepath, 'pac'), 'rb') as f: - pac = f.read().replace(b'{{port}}', str(setting.config['server']['port']).encode('iso-8859-1')).replace(b'{{host}}', setting.config['server'].get('pac_host', '127.0.0.1').encode('iso-8859-1')) + pac_file = Path('pac') if Path('pac').exists() else Path(basepath).joinpath('pac') + pac = pac_file.read_bytes().replace(b'{{port}}', str(setting.config['server']['port']).encode('iso-8859-1')).replace(b'{{host}}', setting.config['server'].get('pac_host', '127.0.0.1').encode('iso-8859-1')) writer.write(f'HTTP/1.1 200 OK\r\nContent-Type: application/x-ns-proxy-autoconfig\r\nContent-Length: {len(pac)}\r\n\r\n'.encode('iso-8859-1')) writer.write(pac) await writer.drain() @@ -65,8 +66,7 @@ async def send_pac(writer: asyncio.StreamWriter): await writer.wait_closed() async def send_crt(writer: asyncio.StreamWriter, path: str): - with open(setting.certpath, (path.rsplit(sep = '/',maxsplit = 1)[-1]), 'rb') as f: - crt = f.read() + crt = setting.certpath.joinpath(path.rsplit(sep = '/',maxsplit = 1)[-1]).read_bytes() writer.write(f'HTTP/1.1 200 OK\r\nContent-Type: application/x-x509-ca-cert\r\nContent-Length: {len(crt)}\r\n\r\n'.encode('iso-8859-1')) writer.write(crt) await writer.drain() diff --git a/accesser/utils/importca.py b/accesser/utils/importca.py index 3b04dd8..a354850 100644 --- a/accesser/utils/importca.py +++ b/accesser/utils/importca.py @@ -57,16 +57,15 @@ def import_windows_ca(): # sys.exit(5) logger.warning('Try to manually import the certificate') else: - with setting.certpath.joinpath("root.pfx").open("wb") as pfxfile: - private_key, certificate, _ = pkcs12.load_key_and_certificates(pfxfile.read(), password=None) - with setting.certpath.joinpath("root.crt").open("wb") as certfile: - certfile.write(certificate.public_bytes(serialization.Encoding.PEM)) - with setting.certpath.joinpath("root.key").open("wb") as pkeyfile: - pkeyfile.write(private_key.private_bytes( + private_key, certificate, _ = pkcs12.load_key_and_certificates(setting.certpath.joinpath("root.pfx").read_bytes(), password=None) + setting.certpath.joinpath("root.crt").write_bytes(certificate.public_bytes(serialization.Encoding.PEM)) + setting.certpath.joinpath("root.key").write_bytes( + private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption(), - )) + ) + ) def import_mac_ca(): ca_hash = CertUtil.ca_thumbprint.replace(':', '') diff --git a/accesser/utils/setting.py b/accesser/utils/setting.py index 9740551..91b3af0 100644 --- a/accesser/utils/setting.py +++ b/accesser/utils/setting.py @@ -87,9 +87,9 @@ def decide_certpath(): match platform.system(): case "Linux" | "FreeBSD": deprecated_path = decide_state_path_legacy() / "CERT" - # 暂仅在 *nix 上视为已废弃 if deprecated_path.exists(): - logging.warning("deprecated path, see pull #245") + logging.warning("cert path %s is deprecated.", str(deprecated_path)) + logging.warning("Please check https://github.com/URenko/Accesser/pull/245 for migration.") return deprecated_path return decide_state_path_unix_like() / "CERT" case _: