From b3878cac7766080bde084a6db91238355c27d8fa Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 13 May 2024 22:33:10 +0200 Subject: [PATCH 1/6] Start Python database tooling --- scripts/check_db_certificates.py | 27 +++ scripts/poetry.lock | 338 +++++++++++++++++++++++++++++++ scripts/pyproject.toml | 18 ++ 3 files changed, 383 insertions(+) create mode 100644 scripts/check_db_certificates.py create mode 100644 scripts/poetry.lock create mode 100644 scripts/pyproject.toml diff --git a/scripts/check_db_certificates.py b/scripts/check_db_certificates.py new file mode 100644 index 0000000..208e354 --- /dev/null +++ b/scripts/check_db_certificates.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from sqlalchemy import create_engine, select, MetaData, Table +from pprint import pprint +import os + +db_user = os.getenv("db_user", default="cacert") +db_password = os.getenv("db_password", default="cacert") +db_host = os.getenv("db_host", "localhost") +db_port = os.getenv("db_port", "3306") +db_name = os.getenv("db_name", "cacert") + +dsn = ( + f"mariadb+mariadbconnector://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" +) +engine = create_engine(dsn) + +metadata_obj = MetaData() + +for table in ("emailcerts", "domaincerts", "orgemailcerts", "orgdomaincerts"): + certs_table = Table(table, metadata_obj, autoload_with=engine) + + stmt = select(certs_table) + + with engine.connect() as conn: + for row in conn.execute(stmt): + pprint(row) diff --git a/scripts/poetry.lock b/scripts/poetry.lock new file mode 100644 index 0000000..4318ddc --- /dev/null +++ b/scripts/poetry.lock @@ -0,0 +1,338 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cryptography" +version = "42.0.7" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"}, + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"}, + {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"}, + {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"}, + {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"}, + {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"}, + {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"}, + {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "greenlet" +version = "3.0.3" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, + {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, + {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, + {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, + {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, + {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, + {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "mariadb" +version = "1.1.10" +description = "Python MariaDB extension" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mariadb-1.1.10-cp310-cp310-win32.whl", hash = "sha256:1d81b22efbaaf4c5bc5e4cc4e2ef3c459538c1a939371089d8c5591d6f26a62e"}, + {file = "mariadb-1.1.10-cp310-cp310-win_amd64.whl", hash = "sha256:dff8b28ce4044574870d7bdd2d9f9f5da8e5f95a7ff6d226185db733060d1a93"}, + {file = "mariadb-1.1.10-cp311-cp311-win32.whl", hash = "sha256:1ce87971c02375236ff8933e6c593c748e7b2f2950b86eabfab4289fd250ea63"}, + {file = "mariadb-1.1.10-cp311-cp311-win_amd64.whl", hash = "sha256:4521aa721f926946bd71491f872e8babc78fa97755ed2114f5684b77363107cb"}, + {file = "mariadb-1.1.10-cp312-cp312-win32.whl", hash = "sha256:03d6284ef713d1cad40146576a4cc2d6cbc1662060f2a0e59b174e1694521698"}, + {file = "mariadb-1.1.10-cp312-cp312-win_amd64.whl", hash = "sha256:8c8c6b27486b0e1772a23002c702b5fd244eecf9f05633dd6cb345fc26755a20"}, + {file = "mariadb-1.1.10-cp38-cp38-win32.whl", hash = "sha256:5d652117e2fdf12b9723e7452a05fce9e6ccbae6ea48871b21a3a8fde259dc48"}, + {file = "mariadb-1.1.10-cp38-cp38-win_amd64.whl", hash = "sha256:49200378c614984f5ec875481662a49d7c97c2be27970b01b32fa4b7520d4e22"}, + {file = "mariadb-1.1.10-cp39-cp39-win32.whl", hash = "sha256:d7b09ec4abd02ed235257feb769f90cd4066e8f536b55b92f5166103d5b66a63"}, + {file = "mariadb-1.1.10-cp39-cp39-win_amd64.whl", hash = "sha256:29040e426f877ddc45f337c6eb381b6bbab63cc6bf8431a28effe30162142513"}, + {file = "mariadb-1.1.10.tar.gz", hash = "sha256:a332893e3ef7ceb7970ab4bd7c844bcb4bd68a051ca51313566f9808d7411f2d"}, +] + +[package.dependencies] +packaging = "*" + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.30" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a8e3b0a7e09e94be7510d1661339d6b52daf202ed2f5b1f9f48ea34ee6f2d57"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b60203c63e8f984df92035610c5fb76d941254cf5d19751faab7d33b21e5ddc0"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1dc3eabd8c0232ee8387fbe03e0a62220a6f089e278b1f0aaf5e2d6210741ad"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40ad017c672c00b9b663fcfcd5f0864a0a97828e2ee7ab0c140dc84058d194cf"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e42203d8d20dc704604862977b1470a122e4892791fe3ed165f041e4bf447a1b"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-win32.whl", hash = "sha256:2a4f4da89c74435f2bc61878cd08f3646b699e7d2eba97144030d1be44e27584"}, + {file = "SQLAlchemy-2.0.30-cp37-cp37m-win_amd64.whl", hash = "sha256:b6bf767d14b77f6a18b6982cbbf29d71bede087edae495d11ab358280f304d8e"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"}, + {file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"}, + {file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "typing-extensions" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "fcfe7b47013b4982c972ffd2f1731ff823c626aa825c9907a8b63de1519e78ca" diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml new file mode 100644 index 0000000..dd20875 --- /dev/null +++ b/scripts/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "cacert-webdb-utils" +version = "0.1.0" +description = "CAcert webdb database Python utilities" +authors = ["Jan Dittberner "] +license = "GPL-2" +package-mode = false + +[tool.poetry.dependencies] +python = "^3.11" +sqlalchemy = "^2.0.30" +mariadb = "^1.1.10" +cryptography = "^42.0.7" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" -- 2.30.2 From ef68be8b60cf26bb3aad92c8267c62ab1d15d94a Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sat, 25 May 2024 20:36:31 +0200 Subject: [PATCH 2/6] Implement basic analyzer script --- scripts/check_db_certificates.py | 197 ++++++++++++++++++++++++++++--- scripts/poetry.lock | 6 +- 2 files changed, 182 insertions(+), 21 deletions(-) diff --git a/scripts/check_db_certificates.py b/scripts/check_db_certificates.py index 208e354..e5ba23f 100644 --- a/scripts/check_db_certificates.py +++ b/scripts/check_db_certificates.py @@ -1,27 +1,188 @@ #!/usr/bin/env python3 +import os +import re +import typing +from datetime import datetime, timezone +from typing import NamedTuple +from cryptography import x509 +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.primitives.asymmetric import rsa, ec from sqlalchemy import create_engine, select, MetaData, Table -from pprint import pprint -import os -db_user = os.getenv("db_user", default="cacert") -db_password = os.getenv("db_password", default="cacert") -db_host = os.getenv("db_host", "localhost") -db_port = os.getenv("db_port", "3306") -db_name = os.getenv("db_name", "cacert") -dsn = ( - f"mariadb+mariadbconnector://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" -) -engine = create_engine(dsn) +class CheckResult(NamedTuple): + name: str + code: int + + def __str__(self): + return f"[{self.code:04d}: {self.name}]" + + +CODE_OK = CheckResult("OK", 0) +CODE_FILE_MISSING = CheckResult("file missing", 1000) +CODE_UNSUPPORTED_FORMAT = CheckResult("unsupported format", 1001) +CODE_EMPTY = CheckResult("empty", 1002) +CODE_DEPRECATED_SPKAC = CheckResult("deprecated SPKAC", 1003) +CODE_INVALID_SIGNATURE = CheckResult("invalid signature", 1004) +CODE_UNSUPPORTED_SIGNATURE_ALGORITHM = CheckResult("unsupported signature algorithm", 1005) +CODE_PUBLIC_KEY_TOO_WEAK = CheckResult("public key too weak", 1006) +CODE_UNSUPPORTED_PUBLIC_KEY = CheckResult("unsupported public key", 1007) +CODE_CSR_AND_CRT_PUBLIC_KEY_MISMATCH = CheckResult("CSR and CRT public key mismatch", 1008) +CODE_CERTIFICATE_FOR_INVALID_CSR = CheckResult("certificate for invalid CSR", 1009) +CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE = CheckResult("not signed by expected CA", 1010) +CODE_CERTIFICATE_IS_EXPIRED = CheckResult("certificate is expired", 1011) + +SUPPORTED_SIGNATURE_ALGORITHMS = [ + x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA256, x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA384, + x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA512, x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA256, + x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA384, x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA512 +] + + +def check_csr(csr_name: str) -> [CheckResult, typing.Any]: + if not csr_name: + return CODE_EMPTY, None + + if not os.path.isfile(csr_name): + return CODE_FILE_MISSING, None + + with open(csr_name, "rb") as f: + csr_data = f.read() + + if re.search(r"SPKAC = ", csr_data.decode("iso-8859-1")): + return CODE_DEPRECATED_SPKAC, None + + try: + csr = x509.load_pem_x509_csr(csr_data) + except Exception as e: + print(e, csr_data) + return CODE_UNSUPPORTED_FORMAT, None + + if csr.signature_algorithm_oid not in SUPPORTED_SIGNATURE_ALGORITHMS: + return CODE_UNSUPPORTED_SIGNATURE_ALGORITHM, None + + try: + if not csr.is_signature_valid: + return CODE_INVALID_SIGNATURE, None + except Exception as e: + print(e, csr_data) + return CODE_INVALID_SIGNATURE, None + + public_key = csr.public_key() + + if isinstance(public_key, rsa.RSAPublicKey): + if public_key.key_size < 2048: + return CODE_PUBLIC_KEY_TOO_WEAK, None + elif isinstance(public_key, ec.EllipticCurvePublicKey): + if public_key.key_size < 256: + return CODE_PUBLIC_KEY_TOO_WEAK, None + else: + return CODE_UNSUPPORTED_PUBLIC_KEY, None + + return CODE_OK, public_key.public_numbers() + + +def check_crt(crt_name: str, ca_certificate: x509.Certificate, public_numbers: typing.Any) -> CheckResult: + if not crt_name: + return CODE_EMPTY + + if public_numbers is None: + return CODE_CERTIFICATE_FOR_INVALID_CSR + + if not os.path.isfile(crt_name): + return CODE_FILE_MISSING + + with open(crt_name, "rb") as f: + crt_data = f.read() + + try: + crt = x509.load_pem_x509_certificate(crt_data) + except ValueError: + return CODE_UNSUPPORTED_FORMAT + + if crt.signature_algorithm_oid not in SUPPORTED_SIGNATURE_ALGORITHMS: + return CODE_UNSUPPORTED_SIGNATURE_ALGORITHM + + try: + public_key = crt.public_key() + except UnsupportedAlgorithm: + return CODE_UNSUPPORTED_PUBLIC_KEY + + if isinstance(public_key, rsa.RSAPublicKey): + if public_key.key_size < 2048: + return CODE_PUBLIC_KEY_TOO_WEAK + elif isinstance(public_key, ec.EllipticCurvePublicKey): + if public_key.key_size < 256: + return CODE_PUBLIC_KEY_TOO_WEAK + else: + return CODE_UNSUPPORTED_PUBLIC_KEY + + if public_key.public_numbers() != public_numbers: + return CODE_CSR_AND_CRT_PUBLIC_KEY_MISMATCH + + if crt.not_valid_after_utc < datetime.now(timezone.utc): + return CODE_CERTIFICATE_IS_EXPIRED + + try: + crt.verify_directly_issued_by(ca_certificate) + except Exception as e: + print(e, crt.issuer, ca_certificate.subject) + return CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE + + return CODE_OK + + +def load_ca_certificates() -> dict[int, x509.Certificate]: + with open("../certs/root_X0F.crt", "rb") as f: + root_cert = x509.load_pem_x509_certificate(f.read()) + + with open("../certs/CAcert_Class3Root_x14E228.crt", "rb") as f: + class3_cert = x509.load_pem_x509_certificate(f.read()) + + return { + 1: root_cert, + 2: class3_cert, + } + + +def main(): + db_user = os.getenv("DB_USER", default="cacert") + db_password = os.getenv("DB_PASSWORD", default="cacert") + db_host = os.getenv("DB_HOST", "localhost") + db_port = os.getenv("DB_PORT", "3306") + db_name = os.getenv("DB_NAME", "cacert") + + ca_certificates = load_ca_certificates() + + dsn = ( + f"mariadb+mariadbconnector://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" + ) + engine = create_engine(dsn) + + metadata_obj = MetaData() + fail_counter = 0 + good_counter = 0 + + for table in ("emailcerts", "domaincerts", "orgemailcerts", "orgdomaincerts"): + certs_table = Table(table, metadata_obj, autoload_with=engine) + + stmt = select(certs_table) + + with engine.connect() as conn: + for row in conn.execute(stmt): + csr_code, public_numbers = check_csr(row.csr_name) + ca_cert = ca_certificates[row.rootcert] + crt_code = check_crt(row.crt_name, ca_cert, public_numbers) -metadata_obj = MetaData() + if csr_code != CODE_OK or crt_code != CODE_OK: + fail_counter += 1 + print(f"{fail_counter:03d} {table}: {row.id:06d} -> csr_code: {csr_code}, crt_code: {crt_code}") + else: + good_counter += 1 -for table in ("emailcerts", "domaincerts", "orgemailcerts", "orgdomaincerts"): - certs_table = Table(table, metadata_obj, autoload_with=engine) + print(f"Good: {good_counter}, Failed: {fail_counter}") - stmt = select(certs_table) - with engine.connect() as conn: - for row in conn.execute(stmt): - pprint(row) +if __name__ == "__main__": + main() diff --git a/scripts/poetry.lock b/scripts/poetry.lock index 4318ddc..ce0d81d 100644 --- a/scripts/poetry.lock +++ b/scripts/poetry.lock @@ -323,13 +323,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [metadata] -- 2.30.2 From 52992aad46c73623e6648aea973f771a504e0ffa Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 26 May 2024 10:43:09 +0200 Subject: [PATCH 3/6] Refactor and improve statistic script --- scripts/check_db_certificates.py | 308 ++++++++++++++++++++----------- 1 file changed, 205 insertions(+), 103 deletions(-) diff --git a/scripts/check_db_certificates.py b/scripts/check_db_certificates.py index e5ba23f..edd3cf7 100644 --- a/scripts/check_db_certificates.py +++ b/scripts/check_db_certificates.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import logging import os import re import typing @@ -18,6 +19,12 @@ class CheckResult(NamedTuple): def __str__(self): return f"[{self.code:04d}: {self.name}]" + def __lt__(self, other): + return self.code < other.code + + def __gt__(self, other): + return self.code > other.code + CODE_OK = CheckResult("OK", 0) CODE_FILE_MISSING = CheckResult("file missing", 1000) @@ -40,110 +47,216 @@ SUPPORTED_SIGNATURE_ALGORITHMS = [ ] -def check_csr(csr_name: str) -> [CheckResult, typing.Any]: - if not csr_name: - return CODE_EMPTY, None +def load_ca_certificates(root_ca_cert, sub_ca_cert) -> dict[int, x509.Certificate]: + with open(root_ca_cert, "rb") as f: + root_cert = x509.load_pem_x509_certificate(f.read()) - if not os.path.isfile(csr_name): - return CODE_FILE_MISSING, None + with open(sub_ca_cert, "rb") as f: + class3_cert = x509.load_pem_x509_certificate(f.read()) - with open(csr_name, "rb") as f: - csr_data = f.read() + return { + 1: root_cert, + 2: class3_cert, + } - if re.search(r"SPKAC = ", csr_data.decode("iso-8859-1")): - return CODE_DEPRECATED_SPKAC, None - try: - csr = x509.load_pem_x509_csr(csr_data) - except Exception as e: - print(e, csr_data) - return CODE_UNSUPPORTED_FORMAT, None +class Counters: + fail = 0 + good = 0 + good_csr = 0 + good_crt = 0 + missing_crt = 0 + expired_crt = 0 - if csr.signature_algorithm_oid not in SUPPORTED_SIGNATURE_ALGORITHMS: - return CODE_UNSUPPORTED_SIGNATURE_ALGORITHM, None + csr_codes: dict[CheckResult, int] = {} + crt_codes: dict[CheckResult, int] = {} - try: - if not csr.is_signature_valid: - return CODE_INVALID_SIGNATURE, None - except Exception as e: - print(e, csr_data) - return CODE_INVALID_SIGNATURE, None + def count_fail(self): + self.fail += 1 - public_key = csr.public_key() + def count_good(self): + self.good += 1 - if isinstance(public_key, rsa.RSAPublicKey): - if public_key.key_size < 2048: - return CODE_PUBLIC_KEY_TOO_WEAK, None - elif isinstance(public_key, ec.EllipticCurvePublicKey): - if public_key.key_size < 256: - return CODE_PUBLIC_KEY_TOO_WEAK, None - else: - return CODE_UNSUPPORTED_PUBLIC_KEY, None + def count_good_csr(self): + self.good_csr += 1 - return CODE_OK, public_key.public_numbers() + def count_good_crt(self): + self.good_crt += 1 + def count_missing_crt(self): + self.missing_crt += 1 -def check_crt(crt_name: str, ca_certificate: x509.Certificate, public_numbers: typing.Any) -> CheckResult: - if not crt_name: - return CODE_EMPTY + def count_expired_crt(self): + self.expired_crt += 1 - if public_numbers is None: - return CODE_CERTIFICATE_FOR_INVALID_CSR + def count_csr(self, csr_code: CheckResult): + self.csr_codes.setdefault(csr_code, 0) + self.csr_codes[csr_code] += 1 - if not os.path.isfile(crt_name): - return CODE_FILE_MISSING + def count_crt(self, crt_code: CheckResult): + self.crt_codes.setdefault(crt_code, 0) + self.crt_codes[crt_code] += 1 - with open(crt_name, "rb") as f: - crt_data = f.read() + def __str__(self): + return ( + f"good CSR and certificate: {self.good}\n" + f"good CSR, issue with certificate: {self.good_csr}\n" + f"good certificate, issue with CSR: {self.good_crt}\n" + f"failed CSR and certificate: {self.fail}\n" + f"missing certificate: {self.missing_crt}\n" + f"expired certificate: {self.expired_crt}\n\nCSR results:\n" + ) + "\n".join([f"{code}: {count:d}" for code, count in sorted(self.csr_codes.items())]) + ( + "\n\nCertificate results:\n" + ) + ( + "\n".join([f"{code}: {count:d}" for code, count in sorted(self.crt_codes.items())]) + ) + + +class Analyzer: + def __init__(self, logger: logging.Logger, dsn: str, ca_certificates: dict[int, x509.Certificate]): + self.logger = logger + self.engine = create_engine(dsn) + self.ca_certificates = ca_certificates + self.c = Counters() + + def analyze(self): + metadata_obj = MetaData() + + for table in ("emailcerts", "domaincerts", "orgemailcerts", "orgdomaincerts"): + certs_table = Table(table, metadata_obj, autoload_with=self.engine) + + stmt = select(certs_table) + + with self.engine.connect() as conn: + for row in conn.execute(stmt): + self.analyze_row(table, row) + + def analyze_row(self, table: str, row) -> None: + ca_cert = self.ca_certificates[row.rootcert] + csr_code, public_numbers = self.check_csr(row.csr_name) + self.c.count_csr(csr_code) + crt_code = self.check_crt(row.crt_name, ca_cert, public_numbers) + self.c.count_crt(crt_code) + + if csr_code != CODE_OK and crt_code not in (CODE_OK, CODE_CERTIFICATE_IS_EXPIRED): + self.logger.debug( + "%06d %s: %06d -> csr_code: %s, crt_code: %s", + self.c.fail, table, row.id, csr_code, crt_code + ) + self.c.count_fail() + return + + if csr_code != CODE_OK: + self.c.count_good_crt() + if crt_code == CODE_CERTIFICATE_IS_EXPIRED: + self.c.count_expired_crt() + return + + if crt_code == CODE_EMPTY: + self.c.count_missing_crt() + return + + if csr_code == CODE_OK and crt_code not in (CODE_OK, CODE_CERTIFICATE_IS_EXPIRED): + self.c.count_good_csr() + return + + if crt_code == CODE_CERTIFICATE_IS_EXPIRED: + self.c.count_expired_crt() + + self.c.count_good() + + def check_csr(self, csr_name: str) -> [CheckResult, typing.Any]: + if not csr_name: + return CODE_EMPTY, None + + if not os.path.isfile(csr_name): + return CODE_FILE_MISSING, None + + with open(csr_name, "rb") as f: + csr_data = f.read() + + if re.search(r"SPKAC = ", csr_data.decode("iso-8859-1")): + return CODE_DEPRECATED_SPKAC, None + + try: + csr = x509.load_pem_x509_csr(csr_data) + except Exception as e: + self.logger.debug("unsupported CSR format: %s for\n%s", e, csr_data) + return CODE_UNSUPPORTED_FORMAT, None + + if csr.signature_algorithm_oid not in SUPPORTED_SIGNATURE_ALGORITHMS: + return CODE_UNSUPPORTED_SIGNATURE_ALGORITHM, None + + try: + if not csr.is_signature_valid: + return CODE_INVALID_SIGNATURE, None + except Exception as e: + self.logger.debug("CSR signature check failed: %s for \n%s", e, csr_data) + return CODE_INVALID_SIGNATURE, None - try: - crt = x509.load_pem_x509_certificate(crt_data) - except ValueError: - return CODE_UNSUPPORTED_FORMAT + public_key = csr.public_key() - if crt.signature_algorithm_oid not in SUPPORTED_SIGNATURE_ALGORITHMS: - return CODE_UNSUPPORTED_SIGNATURE_ALGORITHM + if isinstance(public_key, rsa.RSAPublicKey): + if public_key.key_size < 2048: + return CODE_PUBLIC_KEY_TOO_WEAK, None + elif isinstance(public_key, ec.EllipticCurvePublicKey): + if public_key.key_size < 256: + return CODE_PUBLIC_KEY_TOO_WEAK, None + else: + return CODE_UNSUPPORTED_PUBLIC_KEY, None - try: - public_key = crt.public_key() - except UnsupportedAlgorithm: - return CODE_UNSUPPORTED_PUBLIC_KEY + return CODE_OK, public_key.public_numbers() - if isinstance(public_key, rsa.RSAPublicKey): - if public_key.key_size < 2048: - return CODE_PUBLIC_KEY_TOO_WEAK - elif isinstance(public_key, ec.EllipticCurvePublicKey): - if public_key.key_size < 256: - return CODE_PUBLIC_KEY_TOO_WEAK - else: - return CODE_UNSUPPORTED_PUBLIC_KEY + def check_crt(self, crt_name: str, ca_certificate: x509.Certificate, public_numbers: typing.Any) -> CheckResult: + if not crt_name: + return CODE_EMPTY - if public_key.public_numbers() != public_numbers: - return CODE_CSR_AND_CRT_PUBLIC_KEY_MISMATCH + if not os.path.isfile(crt_name): + return CODE_FILE_MISSING - if crt.not_valid_after_utc < datetime.now(timezone.utc): - return CODE_CERTIFICATE_IS_EXPIRED + with open(crt_name, "rb") as f: + crt_data = f.read() - try: - crt.verify_directly_issued_by(ca_certificate) - except Exception as e: - print(e, crt.issuer, ca_certificate.subject) - return CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE + try: + crt = x509.load_pem_x509_certificate(crt_data) + except ValueError: + return CODE_UNSUPPORTED_FORMAT - return CODE_OK + if crt.signature_algorithm_oid not in SUPPORTED_SIGNATURE_ALGORITHMS: + return CODE_UNSUPPORTED_SIGNATURE_ALGORITHM + try: + public_key = crt.public_key() + except UnsupportedAlgorithm: + return CODE_UNSUPPORTED_PUBLIC_KEY -def load_ca_certificates() -> dict[int, x509.Certificate]: - with open("../certs/root_X0F.crt", "rb") as f: - root_cert = x509.load_pem_x509_certificate(f.read()) + if isinstance(public_key, rsa.RSAPublicKey): + if public_key.key_size < 2048: + return CODE_PUBLIC_KEY_TOO_WEAK + elif isinstance(public_key, ec.EllipticCurvePublicKey): + if public_key.key_size < 256: + return CODE_PUBLIC_KEY_TOO_WEAK + else: + return CODE_UNSUPPORTED_PUBLIC_KEY - with open("../certs/CAcert_Class3Root_x14E228.crt", "rb") as f: - class3_cert = x509.load_pem_x509_certificate(f.read()) + if public_numbers and public_key.public_numbers() != public_numbers: + return CODE_CSR_AND_CRT_PUBLIC_KEY_MISMATCH - return { - 1: root_cert, - 2: class3_cert, - } + if crt.not_valid_after_utc < datetime.now(timezone.utc): + return CODE_CERTIFICATE_IS_EXPIRED + + try: + crt.verify_directly_issued_by(ca_certificate) + except Exception as e: + self.logger.debug("certificate verification failed: %s\n issuer of certificate: %s\n" + " CA certificate: %s", e, crt.issuer, + ca_certificate.subject) + return CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE + + return CODE_OK + + def get_statistics(self): + self.logger.info("Statistics:\n%s", self.c) def main(): @@ -152,36 +265,25 @@ def main(): db_host = os.getenv("DB_HOST", "localhost") db_port = os.getenv("DB_PORT", "3306") db_name = os.getenv("DB_NAME", "cacert") + root_ca_cert: str = os.getenv("ROOT_CA_CERTIFICATE", "../www/certs/root_X0F.crt") + sub_ca_cert = os.getenv("SUB_CA_CERTIFICATE", "../www/certs/CAcert_Class3Root_x14E228.crt") + debug = bool(os.getenv("DEBUG", "false")) - ca_certificates = load_ca_certificates() - - dsn = ( - f"mariadb+mariadbconnector://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" - ) - engine = create_engine(dsn) - - metadata_obj = MetaData() - fail_counter = 0 - good_counter = 0 - - for table in ("emailcerts", "domaincerts", "orgemailcerts", "orgdomaincerts"): - certs_table = Table(table, metadata_obj, autoload_with=engine) + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") + logger = logging.getLogger(__name__) - stmt = select(certs_table) + if debug: + logger.level = logging.DEBUG - with engine.connect() as conn: - for row in conn.execute(stmt): - csr_code, public_numbers = check_csr(row.csr_name) - ca_cert = ca_certificates[row.rootcert] - crt_code = check_crt(row.crt_name, ca_cert, public_numbers) + analyzer = Analyzer( + logger=logger, + dsn=f"mariadb+mariadbconnector://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}", + ca_certificates=load_ca_certificates(root_ca_cert, sub_ca_cert), + ) - if csr_code != CODE_OK or crt_code != CODE_OK: - fail_counter += 1 - print(f"{fail_counter:03d} {table}: {row.id:06d} -> csr_code: {csr_code}, crt_code: {crt_code}") - else: - good_counter += 1 + analyzer.analyze() - print(f"Good: {good_counter}, Failed: {fail_counter}") + analyzer.get_statistics() if __name__ == "__main__": -- 2.30.2 From fe02217028240d1691e243fac37fa766e23f935a Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 26 May 2024 11:29:20 +0200 Subject: [PATCH 4/6] Format using isort and black --- scripts/check_db_certificates.py | 108 ++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/scripts/check_db_certificates.py b/scripts/check_db_certificates.py index edd3cf7..c38d775 100644 --- a/scripts/check_db_certificates.py +++ b/scripts/check_db_certificates.py @@ -3,13 +3,12 @@ import logging import os import re import typing -from datetime import datetime, timezone -from typing import NamedTuple - from cryptography import x509 from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.primitives.asymmetric import rsa, ec -from sqlalchemy import create_engine, select, MetaData, Table +from cryptography.hazmat.primitives.asymmetric import ec, rsa +from datetime import datetime, timezone +from sqlalchemy import MetaData, Table, create_engine, select +from typing import NamedTuple class CheckResult(NamedTuple): @@ -32,18 +31,27 @@ CODE_UNSUPPORTED_FORMAT = CheckResult("unsupported format", 1001) CODE_EMPTY = CheckResult("empty", 1002) CODE_DEPRECATED_SPKAC = CheckResult("deprecated SPKAC", 1003) CODE_INVALID_SIGNATURE = CheckResult("invalid signature", 1004) -CODE_UNSUPPORTED_SIGNATURE_ALGORITHM = CheckResult("unsupported signature algorithm", 1005) +CODE_UNSUPPORTED_SIGNATURE_ALGORITHM = CheckResult( + "unsupported signature algorithm", 1005 +) CODE_PUBLIC_KEY_TOO_WEAK = CheckResult("public key too weak", 1006) CODE_UNSUPPORTED_PUBLIC_KEY = CheckResult("unsupported public key", 1007) -CODE_CSR_AND_CRT_PUBLIC_KEY_MISMATCH = CheckResult("CSR and CRT public key mismatch", 1008) +CODE_CSR_AND_CRT_PUBLIC_KEY_MISMATCH = CheckResult( + "CSR and CRT public key mismatch", 1008 +) CODE_CERTIFICATE_FOR_INVALID_CSR = CheckResult("certificate for invalid CSR", 1009) -CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE = CheckResult("not signed by expected CA", 1010) +CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE = CheckResult( + "not signed by expected CA", 1010 +) CODE_CERTIFICATE_IS_EXPIRED = CheckResult("certificate is expired", 1011) SUPPORTED_SIGNATURE_ALGORITHMS = [ - x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA256, x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA384, - x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA512, x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA256, - x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA384, x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA512 + x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA256, + x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA384, + x509.oid.SignatureAlgorithmOID.RSA_WITH_SHA512, + x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA256, + x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA384, + x509.oid.SignatureAlgorithmOID.ECDSA_WITH_SHA512, ] @@ -99,21 +107,36 @@ class Counters: def __str__(self): return ( - f"good CSR and certificate: {self.good}\n" - f"good CSR, issue with certificate: {self.good_csr}\n" - f"good certificate, issue with CSR: {self.good_crt}\n" - f"failed CSR and certificate: {self.fail}\n" - f"missing certificate: {self.missing_crt}\n" - f"expired certificate: {self.expired_crt}\n\nCSR results:\n" - ) + "\n".join([f"{code}: {count:d}" for code, count in sorted(self.csr_codes.items())]) + ( - "\n\nCertificate results:\n" - ) + ( - "\n".join([f"{code}: {count:d}" for code, count in sorted(self.crt_codes.items())]) + ( + f"good CSR and certificate: {self.good}\n" + f"good CSR, issue with certificate: {self.good_csr}\n" + f"good certificate, issue with CSR: {self.good_crt}\n" + f"failed CSR and certificate: {self.fail}\n" + f"missing certificate: {self.missing_crt}\n" + f"expired certificate: {self.expired_crt}\n\nCSR results:\n" + ) + + "\n".join( + [f"{code}: {count:d}" for code, count in sorted(self.csr_codes.items())] + ) + + ("\n\nCertificate results:\n") + + ( + "\n".join( + [ + f"{code}: {count:d}" + for code, count in sorted(self.crt_codes.items()) + ] + ) + ) ) class Analyzer: - def __init__(self, logger: logging.Logger, dsn: str, ca_certificates: dict[int, x509.Certificate]): + def __init__( + self, + logger: logging.Logger, + dsn: str, + ca_certificates: dict[int, x509.Certificate], + ): self.logger = logger self.engine = create_engine(dsn) self.ca_certificates = ca_certificates @@ -138,10 +161,17 @@ class Analyzer: crt_code = self.check_crt(row.crt_name, ca_cert, public_numbers) self.c.count_crt(crt_code) - if csr_code != CODE_OK and crt_code not in (CODE_OK, CODE_CERTIFICATE_IS_EXPIRED): + if csr_code != CODE_OK and crt_code not in ( + CODE_OK, + CODE_CERTIFICATE_IS_EXPIRED, + ): self.logger.debug( "%06d %s: %06d -> csr_code: %s, crt_code: %s", - self.c.fail, table, row.id, csr_code, crt_code + self.c.fail, + table, + row.id, + csr_code, + crt_code, ) self.c.count_fail() return @@ -156,7 +186,10 @@ class Analyzer: self.c.count_missing_crt() return - if csr_code == CODE_OK and crt_code not in (CODE_OK, CODE_CERTIFICATE_IS_EXPIRED): + if csr_code == CODE_OK and crt_code not in ( + CODE_OK, + CODE_CERTIFICATE_IS_EXPIRED, + ): self.c.count_good_csr() return @@ -207,7 +240,12 @@ class Analyzer: return CODE_OK, public_key.public_numbers() - def check_crt(self, crt_name: str, ca_certificate: x509.Certificate, public_numbers: typing.Any) -> CheckResult: + def check_crt( + self, + crt_name: str, + ca_certificate: x509.Certificate, + public_numbers: typing.Any, + ) -> CheckResult: if not crt_name: return CODE_EMPTY @@ -248,9 +286,13 @@ class Analyzer: try: crt.verify_directly_issued_by(ca_certificate) except Exception as e: - self.logger.debug("certificate verification failed: %s\n issuer of certificate: %s\n" - " CA certificate: %s", e, crt.issuer, - ca_certificate.subject) + self.logger.debug( + "certificate verification failed: %s\n issuer of certificate: %s\n" + " CA certificate: %s", + e, + crt.issuer, + ca_certificate.subject, + ) return CODE_NOT_SIGNED_BY_EXPECTED_CA_CERTIFICATE return CODE_OK @@ -266,10 +308,14 @@ def main(): db_port = os.getenv("DB_PORT", "3306") db_name = os.getenv("DB_NAME", "cacert") root_ca_cert: str = os.getenv("ROOT_CA_CERTIFICATE", "../www/certs/root_X0F.crt") - sub_ca_cert = os.getenv("SUB_CA_CERTIFICATE", "../www/certs/CAcert_Class3Root_x14E228.crt") + sub_ca_cert = os.getenv( + "SUB_CA_CERTIFICATE", "../www/certs/CAcert_Class3Root_x14E228.crt" + ) debug = bool(os.getenv("DEBUG", "false")) - logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") + logging.basicConfig( + level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s" + ) logger = logging.getLogger(__name__) if debug: -- 2.30.2 From c18f78741bbee8936e3ae38a7a3b6a02797a9372 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 2 Jun 2024 13:07:59 +0200 Subject: [PATCH 5/6] Switch to Python 3.9 for Debian 11 compatibilty --- scripts/poetry.lock | 10 +++++----- scripts/pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/poetry.lock b/scripts/poetry.lock index ce0d81d..ae165f0 100644 --- a/scripts/poetry.lock +++ b/scripts/poetry.lock @@ -323,16 +323,16 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "typing-extensions" -version = "4.12.0" +version = "4.12.1" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, - {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, + {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, + {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, ] [metadata] lock-version = "2.0" -python-versions = "^3.11" -content-hash = "fcfe7b47013b4982c972ffd2f1731ff823c626aa825c9907a8b63de1519e78ca" +python-versions = "^3.9" +content-hash = "2575a37b9796f99ea57b78531c90a5f3eb2741b910c6dcfbb8c6018440aab42a" diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml index dd20875..8f67ac8 100644 --- a/scripts/pyproject.toml +++ b/scripts/pyproject.toml @@ -7,7 +7,7 @@ license = "GPL-2" package-mode = false [tool.poetry.dependencies] -python = "^3.11" +python = "^3.9" sqlalchemy = "^2.0.30" mariadb = "^1.1.10" cryptography = "^42.0.7" -- 2.30.2 From 46db965846bc40c23a27fe19c861fda1ad079f27 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 2 Jun 2024 11:18:04 +0000 Subject: [PATCH 6/6] Use mariadb version compatible with Debian 11 webdb is currently deployed on a Debian 11 system that comes with mariadb client library version 10.5.23. The mariadb driver version broke compatibilty in release 1.1.x. This commit ensures that versions below 1.1.x are used. This should be changed when upgrading the webdb systems to Debian 12 or later. --- scripts/poetry.lock | 40 ++++++++++++---------------------------- scripts/pyproject.toml | 2 +- 2 files changed, 13 insertions(+), 29 deletions(-) diff --git a/scripts/poetry.lock b/scripts/poetry.lock index ae165f0..c457d2c 100644 --- a/scripts/poetry.lock +++ b/scripts/poetry.lock @@ -191,36 +191,20 @@ test = ["objgraph", "psutil"] [[package]] name = "mariadb" -version = "1.1.10" +version = "1.0.11" description = "Python MariaDB extension" optional = false -python-versions = ">=3.8" -files = [ - {file = "mariadb-1.1.10-cp310-cp310-win32.whl", hash = "sha256:1d81b22efbaaf4c5bc5e4cc4e2ef3c459538c1a939371089d8c5591d6f26a62e"}, - {file = "mariadb-1.1.10-cp310-cp310-win_amd64.whl", hash = "sha256:dff8b28ce4044574870d7bdd2d9f9f5da8e5f95a7ff6d226185db733060d1a93"}, - {file = "mariadb-1.1.10-cp311-cp311-win32.whl", hash = "sha256:1ce87971c02375236ff8933e6c593c748e7b2f2950b86eabfab4289fd250ea63"}, - {file = "mariadb-1.1.10-cp311-cp311-win_amd64.whl", hash = "sha256:4521aa721f926946bd71491f872e8babc78fa97755ed2114f5684b77363107cb"}, - {file = "mariadb-1.1.10-cp312-cp312-win32.whl", hash = "sha256:03d6284ef713d1cad40146576a4cc2d6cbc1662060f2a0e59b174e1694521698"}, - {file = "mariadb-1.1.10-cp312-cp312-win_amd64.whl", hash = "sha256:8c8c6b27486b0e1772a23002c702b5fd244eecf9f05633dd6cb345fc26755a20"}, - {file = "mariadb-1.1.10-cp38-cp38-win32.whl", hash = "sha256:5d652117e2fdf12b9723e7452a05fce9e6ccbae6ea48871b21a3a8fde259dc48"}, - {file = "mariadb-1.1.10-cp38-cp38-win_amd64.whl", hash = "sha256:49200378c614984f5ec875481662a49d7c97c2be27970b01b32fa4b7520d4e22"}, - {file = "mariadb-1.1.10-cp39-cp39-win32.whl", hash = "sha256:d7b09ec4abd02ed235257feb769f90cd4066e8f536b55b92f5166103d5b66a63"}, - {file = "mariadb-1.1.10-cp39-cp39-win_amd64.whl", hash = "sha256:29040e426f877ddc45f337c6eb381b6bbab63cc6bf8431a28effe30162142513"}, - {file = "mariadb-1.1.10.tar.gz", hash = "sha256:a332893e3ef7ceb7970ab4bd7c844bcb4bd68a051ca51313566f9808d7411f2d"}, -] - -[package.dependencies] -packaging = "*" - -[[package]] -name = "packaging" -version = "24.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" +python-versions = ">=3.6" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "mariadb-1.0.11-cp310-cp310-win32.whl", hash = "sha256:4a583a80059d11f1895a2c93b1b7110948e87f0256da3e3222939a2530f0518e"}, + {file = "mariadb-1.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:63c5c7cf99335e5c961e1d65a323576c9cb834e1a6a8084a6a8b4ffd85ca6213"}, + {file = "mariadb-1.0.11-cp37-cp37m-win32.whl", hash = "sha256:db509f8142e1bf55973bbe5f46ba33d9832b4031d1e249d657cbe263119b3b17"}, + {file = "mariadb-1.0.11-cp37-cp37m-win_amd64.whl", hash = "sha256:61fd04f555de0a58d6519be6f1e5a70daaef2334ca8946a7c25c8a9a2d96c6e2"}, + {file = "mariadb-1.0.11-cp38-cp38-win32.whl", hash = "sha256:9afc253b9edbec95637465c01fe5c730dd549f18752cdc5cdfac995adfe7c8d3"}, + {file = "mariadb-1.0.11-cp38-cp38-win_amd64.whl", hash = "sha256:166973d6cd7da5d4fe84fc9d63f8d219a660ed2c82ebf6acadc9b3dd811f51bc"}, + {file = "mariadb-1.0.11-cp39-cp39-win32.whl", hash = "sha256:173f23f9a01b2043523168e73e110ade417d38f325abdb2271d4ebedd3abea25"}, + {file = "mariadb-1.0.11-cp39-cp39-win_amd64.whl", hash = "sha256:4c8a1e5ed4c242b7310876219e1efb364f03394db140513e6f7541de3c803d04"}, + {file = "mariadb-1.0.11.zip", hash = "sha256:76916c892bc936c5b0f36e25a1411f651a7b7ce978992ae3a8de7e283efdacbf"}, ] [[package]] @@ -335,4 +319,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "2575a37b9796f99ea57b78531c90a5f3eb2741b910c6dcfbb8c6018440aab42a" +content-hash = "f47eac9ec53924848bb7e3bf3f36b8bbb1039c193514e341a5093ac15ea95e7b" diff --git a/scripts/pyproject.toml b/scripts/pyproject.toml index 8f67ac8..ab2670a 100644 --- a/scripts/pyproject.toml +++ b/scripts/pyproject.toml @@ -9,7 +9,7 @@ package-mode = false [tool.poetry.dependencies] python = "^3.9" sqlalchemy = "^2.0.30" -mariadb = "^1.1.10" +mariadb = "~1.0.0" cryptography = "^42.0.7" -- 2.30.2