cert-utils 1.0.3__tar.gz → 1.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cert_utils-1.0.3 → cert_utils-1.0.4}/CHANGES.txt +14 -0
- cert_utils-1.0.4/PKG-INFO +234 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/__init__.py +1 -1
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/utils.py +153 -14
- cert_utils-1.0.4/src/cert_utils.egg-info/PKG-INFO +234 -0
- cert_utils-1.0.4/src/cert_utils.egg-info/entry_points.txt +2 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/requires.txt +0 -1
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_unit.py +248 -43
- cert_utils-1.0.3/PKG-INFO +0 -209
- cert_utils-1.0.3/src/cert_utils.egg-info/PKG-INFO +0 -209
- cert_utils-1.0.3/src/cert_utils.egg-info/entry_points.txt +0 -3
- {cert_utils-1.0.3 → cert_utils-1.0.4}/LICENSE.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/MANIFEST.in +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/README.md +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/pyproject.toml +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/setup.cfg +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/setup.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/compat/cryptography.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/compat/deprecated.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/compat/openssl.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/compat/pyopenssl.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/conditionals.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/convert.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/core.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/errors.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e5-cross.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e5.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e6-cross.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e6.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e7-cross.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e7.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e8-cross.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e8.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e9-cross.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e9.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/fakeleintermediatex1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/fakelerootx1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrg-root-ocsp-x1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrg-root-x1-cross-signed.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrg-root-x2-cross-signed.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrg-root-x2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrgrootx1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrgrootx1.pkix +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-e1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-e2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-r3-cross-signed.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-r3.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-r4-cross-signed.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-r4.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-x1-cross-signed.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-x2-cross-signed.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-x3-cross-signed.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-x4-cross-signed.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/letsencryptauthorityx1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/letsencryptauthorityx2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/letsencryptauthorityx3.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/letsencryptauthorityx4.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r10.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r11.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r12.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r13.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r14.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/trustid-x3-root.p7c +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/trustid-x3-root.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt_info.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/model.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/py.typed +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/scripts/cert_info.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/SOURCES.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/dependency_links.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/not-zip-safe +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/top_level.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/__init__.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/_utils.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/__README__.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/__README__.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/alternate_chains/1/chain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/alternate_chains/1/fullchain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/alternate_chains/2/chain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/alternate_chains/2/fullchain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/cert.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/chain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/fullchain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/privkey.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/appendix_a-cert.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/cert--all.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/cert--issuer+serial.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/cert--key_id.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/edge_case_generate.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-ec/ec384-1-key.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-ec/ec384-1.csr +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-ec/ec384-2-key.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-3.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-4.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-5.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-account_daily.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-account_weekly.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-global_daily.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-global_weekly.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-single_certificate.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_1.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_2.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_3.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_4.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_5.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_1.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_2.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_3.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_4.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_5.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_1-server.crt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_1-server.csr +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_1-server.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_2-server.crt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_2-server.csr +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_2-server.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_3-server.crt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_3-server.csr +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_3-server.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_4-server.crt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_4-server.csr +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_4-server.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_5-server.crt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_5-server.csr +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_5-server.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/__README__.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/_data.json +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/_demo.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/cert.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/chain_0.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/chain_0_bad.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/chain_1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/chain_2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/root_0.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/root_1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/root_2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/__README__.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert3.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert4.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert5.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert6.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain3.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain4.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain5.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain6.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain3.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain4.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain5.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain6.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey1.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey2.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey3.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey4.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey5.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey6.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/_support/account.key +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/_support/info.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/info.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/meta.json +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/private_key.json +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/private_key.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/regr.json +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_002/private_key.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_003/private_key.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/cert.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/chain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/csr.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/fullchain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/info.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/privkey.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/test.py +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_002/csr.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_002/info.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_002/privkey.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/cert.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/csr.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/fullchain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/info.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/privkey.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/cert.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/csr.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/fullchain.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/info.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/privkey.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_005/csr.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_005/info.txt +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_005/privkey.pem +0 -0
- {cert_utils-1.0.3 → cert_utils-1.0.4}/tox.ini +0 -0
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
1.0.4
|
|
2
|
+
The following functions now accept new kwargs:
|
|
3
|
+
* kwargs:
|
|
4
|
+
allow_hostname: bool = True
|
|
5
|
+
allow_ipv4: bool = False
|
|
6
|
+
allow_ipv6: bool = False
|
|
7
|
+
* functions
|
|
8
|
+
- domains_from_list
|
|
9
|
+
- domains_from_string
|
|
10
|
+
- validate_domains
|
|
11
|
+
* new functions
|
|
12
|
+
utils.identify_san_type
|
|
13
|
+
* updated repo to not allow admins to bypass rules
|
|
14
|
+
|
|
1
15
|
1.0.3
|
|
2
16
|
* exception did not properly interpolate variable
|
|
3
17
|
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: cert_utils
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: TLS/SSL Certiicate Utilities
|
|
5
|
+
Home-page: https://github.com/aptise/cert_utils
|
|
6
|
+
Author: Jonathan Vanasco
|
|
7
|
+
Author-email: jonathan@findmeon.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: web
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
21
|
+
Requires-Python: >=3.7
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE.txt
|
|
24
|
+
Requires-Dist: python-dateutil
|
|
25
|
+
Requires-Dist: psutil>=4.4.0
|
|
26
|
+
Requires-Dist: requests
|
|
27
|
+
Requires-Dist: typing_extensions
|
|
28
|
+
Provides-Extra: testing
|
|
29
|
+
Requires-Dist: certbot; extra == "testing"
|
|
30
|
+
Requires-Dist: cryptography>=42.0.0; extra == "testing"
|
|
31
|
+
Requires-Dist: josepy<3; extra == "testing"
|
|
32
|
+
Requires-Dist: mypy; extra == "testing"
|
|
33
|
+
Requires-Dist: pytest; extra == "testing"
|
|
34
|
+
Requires-Dist: types-psutil; extra == "testing"
|
|
35
|
+
Requires-Dist: types-python-dateutil; extra == "testing"
|
|
36
|
+
Requires-Dist: types-requests; extra == "testing"
|
|
37
|
+
Provides-Extra: josepy
|
|
38
|
+
Requires-Dist: josepy<3; extra == "josepy"
|
|
39
|
+
Dynamic: author
|
|
40
|
+
Dynamic: author-email
|
|
41
|
+
Dynamic: classifier
|
|
42
|
+
Dynamic: description
|
|
43
|
+
Dynamic: description-content-type
|
|
44
|
+
Dynamic: home-page
|
|
45
|
+
Dynamic: keywords
|
|
46
|
+
Dynamic: license
|
|
47
|
+
Dynamic: provides-extra
|
|
48
|
+
Dynamic: requires-dist
|
|
49
|
+
Dynamic: requires-python
|
|
50
|
+
Dynamic: summary
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
cert_utils
|
|
55
|
+
==========
|
|
56
|
+
|
|
57
|
+
**cert_utils** offers support for common operations when dealing with SSL
|
|
58
|
+
Certificates, specifically within the LetsEncrypt ecosystem.
|
|
59
|
+
|
|
60
|
+
This library was originally developed as a toolkit for bugfixing and
|
|
61
|
+
troubleshooting large ACME installations.
|
|
62
|
+
|
|
63
|
+
**cert_utils** will attempt to process operations with Python via the modern
|
|
64
|
+
[Cryptography](https://cryptography.io/en/latest/) package when possible.
|
|
65
|
+
If the required Python libraries are not installed, it will fallback to using
|
|
66
|
+
OpenSSL commandline via subprocesses. **cert_utils** does a bit of work to
|
|
67
|
+
standardize certificate operations across versions of Python and OpenSSL that
|
|
68
|
+
do not share the same inputs, outputs or invocations.
|
|
69
|
+
|
|
70
|
+
**cert_utils** was formerly part of the
|
|
71
|
+
**[peter_sslers](https://github.com/aptise/peter_sslers)** ACME Client and
|
|
72
|
+
Certificate Management System, and has been descoped into it's own library.
|
|
73
|
+
|
|
74
|
+
This library *does not* process Certificates and Certificate Data itself.
|
|
75
|
+
Instead, it offers a simplified API to invoke other libraries and extract data
|
|
76
|
+
from Certificates. It was designed for developers and system administrators to
|
|
77
|
+
more easily use the various libraries to accomplish specific tasks on the
|
|
78
|
+
commandline or as part of other projects.
|
|
79
|
+
|
|
80
|
+
This library now includes a utility, `cert_info`:
|
|
81
|
+
|
|
82
|
+
Examples CLI:
|
|
83
|
+
-------------
|
|
84
|
+
|
|
85
|
+
The CLI tool `cert_info` can parse a cert and pull the active ARI info.
|
|
86
|
+
|
|
87
|
+
The tool accepts a filepath to a local file OR a https URL to be inspected live:
|
|
88
|
+
|
|
89
|
+
> cert_info https://letsencrypt.org
|
|
90
|
+
|
|
91
|
+
This will gnerate the output:
|
|
92
|
+
|
|
93
|
+
********************************************************************************
|
|
94
|
+
********************************************************************************
|
|
95
|
+
Process URL:
|
|
96
|
+
url: https://letsencrypt.org
|
|
97
|
+
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
98
|
+
Connection Data:
|
|
99
|
+
{'cert_dict': {'OCSP': ('http://e6.o.lencr.org',),
|
|
100
|
+
'caIssuers': ('http://e6.i.lencr.org/',),
|
|
101
|
+
'issuer': ((('countryName', 'US'),),
|
|
102
|
+
(('organizationName', "Let's Encrypt"),),
|
|
103
|
+
(('commonName', 'E6'),)),
|
|
104
|
+
'notAfter': 'Mar 7 14:44:04 2025 GMT',
|
|
105
|
+
'notBefore': 'Dec 7 14:44:05 2024 GMT',
|
|
106
|
+
'serialNumber': '04C7A73A361AC02B67BDBD6AF210285B1CC7',
|
|
107
|
+
'subject': ((('commonName', 'letsencrypt.org'),),),
|
|
108
|
+
'subjectAltName': (('DNS', 'lencr.org'),
|
|
109
|
+
('DNS', 'letsencrypt.com'),
|
|
110
|
+
('DNS', 'letsencrypt.org'),
|
|
111
|
+
('DNS', 'www.lencr.org'),
|
|
112
|
+
('DNS', 'www.letsencrypt.com'),
|
|
113
|
+
('DNS', 'www.letsencrypt.org')),
|
|
114
|
+
'version': 3},
|
|
115
|
+
'cert_pem': '-----BEGIN CERTIFICATE-----\n'
|
|
116
|
+
'MIID1DCCA1qgAwIBAgISBMenOjYawCtnvb1q8hAoWxzHMAoGCCqGSM49BAMDMDIx\n'
|
|
117
|
+
'CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF\n'
|
|
118
|
+
'NjAeFw0yNDEyMDcxNDQ0MDVaFw0yNTAzMDcxNDQ0MDRaMBoxGDAWBgNVBAMTD2xl\n'
|
|
119
|
+
'dHNlbmNyeXB0Lm9yZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDgHHaIeMwYz\n'
|
|
120
|
+
'APO9ZgfeGansUeFRN0sW4K23nrVppRXfjHvN+83vtH170hshCrIAZRJCL+CPcA2N\n'
|
|
121
|
+
'UqmLiSdM/M+jggJmMIICYjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYB\n'
|
|
122
|
+
'BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFD2MLtcnwBWM\n'
|
|
123
|
+
'FukcX4wMfYCQUCMAMB8GA1UdIwQYMBaAFJMnRpgDqVFojpjWxEJI2yO/WJTSMFUG\n'
|
|
124
|
+
'CCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2U2Lm8ubGVuY3Iub3Jn\n'
|
|
125
|
+
'MCIGCCsGAQUFBzAChhZodHRwOi8vZTYuaS5sZW5jci5vcmcvMG8GA1UdEQRoMGaC\n'
|
|
126
|
+
'CWxlbmNyLm9yZ4IPbGV0c2VuY3J5cHQuY29tgg9sZXRzZW5jcnlwdC5vcmeCDXd3\n'
|
|
127
|
+
'dy5sZW5jci5vcmeCE3d3dy5sZXRzZW5jcnlwdC5jb22CE3d3dy5sZXRzZW5jcnlw\n'
|
|
128
|
+
'dC5vcmcwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEEBgorBgEEAdZ5AgQCBIH1BIHy\n'
|
|
129
|
+
'APAAdgB9WR4S4XgqexxhZ3xe/fjQh1wUoE6VnrkDL9kOjC55uAAAAZOhyXh9AAAE\n'
|
|
130
|
+
'AwBHMEUCIAXoVc0BRK6ELL0Q1lHv+QbrobnBiPthcr2E7g0IulkHAiEAlVkiVkF9\n'
|
|
131
|
+
'7KeR/At3XdJNRYbJda0iKaKtqKy0KH7igZgAdgATSt8atZhCCXgMb+9MepGkFrcj\n'
|
|
132
|
+
'Sc5YV2rfrtqnwqvgIgAAAZOhyXk0AAAEAwBHMEUCIQD+yhm/6lwMMWyxBaB15Mkh\n'
|
|
133
|
+
'g3tsQCtr0DOD4uvAzc63FAIgHgjXduGCbB/3VTtRCGWfmkWSS9z+CjfO1O6hkzGW\n'
|
|
134
|
+
'XBowCgYIKoZIzj0EAwMDaAAwZQIxAKVLzxW03Kk8qJMBtWQCw5dgSHNWOGG9Oq03\n'
|
|
135
|
+
'XOGr8092sx9Ezz3XsXWeq8JvokFjhAIwCZOX/UjxdHfgV/O6gkiNARQ1FokDyfS0\n'
|
|
136
|
+
'ClynPk9psMTPVl5QWgYJzjVrJagAjtJm\n'
|
|
137
|
+
'-----END CERTIFICATE-----\n',
|
|
138
|
+
'peername': ('100.28.201.155', 443)}
|
|
139
|
+
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
140
|
+
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
141
|
+
Cert Data:
|
|
142
|
+
ARI ID: kydGmAOpUWiOmNbEQkjbI79YlNI.BMenOjYawCtnvb1q8hAoWxzH
|
|
143
|
+
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
|
|
144
|
+
Requesting ARI
|
|
145
|
+
ari_url: https://acme-v02.api.letsencrypt.org/draft-ietf-acme-ari-03/renewalInfo/kydGmAOpUWiOmNbEQkjbI79YlNI.BMenOjYawCtnvb1q8hAoWxzH
|
|
146
|
+
{'Server': 'nginx', 'Date': 'Tue, 10 Dec 2024 18:29:04 GMT', 'Content-Type': 'application/json', 'Content-Length': '101', 'Connection': 'keep-alive', 'Cache-Control': 'public, max-age=0, no-cache', 'Link': '<https://acme-v02.api.letsencrypt.org/directory>;rel="index"', 'Retry-After': '21600', 'X-Frame-Options': 'DENY', 'Strict-Transport-Security': 'max-age=604800'}
|
|
147
|
+
- - - - - - - - - -
|
|
148
|
+
{'suggestedWindow': {'end': '2025-02-06T15:03:34Z',
|
|
149
|
+
'start': '2025-02-04T15:03:34Z'}}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
Examples API:
|
|
153
|
+
-------------
|
|
154
|
+
|
|
155
|
+
For example, `cert_utils.parse_cert` returns a Python dict of key fields in a
|
|
156
|
+
certificate. This can make writing a script to analyze large directories of
|
|
157
|
+
certificates fairly simple.
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
### Parse a Leaf/End-Entity
|
|
161
|
+
|
|
162
|
+
Example Script:
|
|
163
|
+
|
|
164
|
+
```!python
|
|
165
|
+
import cert_utils
|
|
166
|
+
import pprint
|
|
167
|
+
|
|
168
|
+
cert_path = "./tests/test_data/unit_tests/cert_001/cert.pem"
|
|
169
|
+
cert_pem = open(cert_path, 'r').read()
|
|
170
|
+
data = cert_utils.parse_cert(cert_pem)
|
|
171
|
+
pprint.pprint(data)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Result:
|
|
175
|
+
|
|
176
|
+
{'SubjectAlternativeName': ['a.example.com',
|
|
177
|
+
'b.example.com',
|
|
178
|
+
'c.example.com',
|
|
179
|
+
'd.example.com'],
|
|
180
|
+
'authority_key_identifier': 'D159010094B0A62ADBABE54B2321CA1B6EBA93E7',
|
|
181
|
+
'enddate': datetime.datetime(2025, 6, 16, 20, 19, 30),
|
|
182
|
+
'fingerprint_sha1': 'F63C5C66B52551EEDADF7CE44301D646680B8F5D',
|
|
183
|
+
'issuer': 'CN=Pebble Intermediate CA 601ea1',
|
|
184
|
+
'issuer_uri': None,
|
|
185
|
+
'key_technology': 'RSA',
|
|
186
|
+
'spki_sha256': '34E67CC615761CBADAF430B2E02E0EC39C99EEFC73CCE469B18AE54A37EF6942',
|
|
187
|
+
'startdate': datetime.datetime(2020, 6, 16, 20, 19, 30),
|
|
188
|
+
'subject': 'CN=a.example.com'}
|
|
189
|
+
|
|
190
|
+
The payload contains `SubjectAlternativeName` listing all the domains, along
|
|
191
|
+
with `enddate` and `startdate` in Python datetime objects for easy comparison.
|
|
192
|
+
|
|
193
|
+
### Parse a Trusted Root
|
|
194
|
+
|
|
195
|
+
Example Script:
|
|
196
|
+
|
|
197
|
+
```!python
|
|
198
|
+
import cert_utils
|
|
199
|
+
import pprint
|
|
200
|
+
|
|
201
|
+
cert_path = "./src/cert_utils/letsencrypt-certs/isrgrootx1.pem"
|
|
202
|
+
cert_pem = open(cert_path, 'r').read()
|
|
203
|
+
data = cert_utils.parse_cert(cert_pem)
|
|
204
|
+
pprint.pprint(data)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Result:
|
|
208
|
+
|
|
209
|
+
{'SubjectAlternativeName': None,
|
|
210
|
+
'authority_key_identifier': None,
|
|
211
|
+
'enddate': datetime.datetime(2035, 6, 4, 11, 4, 38),
|
|
212
|
+
'fingerprint_sha1': 'CABD2A79A1076A31F21D253635CB039D4329A5E8',
|
|
213
|
+
'issuer': 'C=US\nO=Internet Security Research Group\nCN=ISRG Root X1',
|
|
214
|
+
'issuer_uri': None,
|
|
215
|
+
'key_technology': 'RSA',
|
|
216
|
+
'spki_sha256': '0B9FA5A59EED715C26C1020C711B4F6EC42D58B0015E14337A39DAD301C5AFC3',
|
|
217
|
+
'startdate': datetime.datetime(2015, 6, 4, 11, 4, 38),
|
|
218
|
+
'subject': 'C=US\nO=Internet Security Research Group\nCN=ISRG Root X1'}
|
|
219
|
+
|
|
220
|
+
The payload on Trusted Roots is identical.
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
Why does this exist?
|
|
224
|
+
--------------------
|
|
225
|
+
|
|
226
|
+
The [peter_sslers](https://github.com/aptise/peter_sslers) project was designed
|
|
227
|
+
to deploy on a wide variety of production servers that did not share common
|
|
228
|
+
Python and OpenSSL installations. Earlier versions of the library
|
|
229
|
+
(within peter_sslers) supported both Python2.7 and Python3, as it was common to
|
|
230
|
+
encounter a machine that did not have Python3 installed. Although it is still
|
|
231
|
+
common to find these machines, Python2.7 was dropped to take advantage of
|
|
232
|
+
typing. Depending on the version of OpenSSL installed on a system,
|
|
233
|
+
**cert_utils** will invoke the binary or regex the output to bridge support
|
|
234
|
+
through a unified interface.
|
|
@@ -11,6 +11,9 @@ from typing import List
|
|
|
11
11
|
from typing import Tuple
|
|
12
12
|
from typing import Union
|
|
13
13
|
|
|
14
|
+
# pypi
|
|
15
|
+
from typing_extensions import Literal
|
|
16
|
+
|
|
14
17
|
# ==============================================================================
|
|
15
18
|
|
|
16
19
|
_RE_rn = re.compile(r"\r\n")
|
|
@@ -34,11 +37,67 @@ CERT_PEM_REGEX = re.compile(
|
|
|
34
37
|
|
|
35
38
|
|
|
36
39
|
# technically we could end in a dot (\.?)
|
|
37
|
-
|
|
38
|
-
r"""
|
|
39
|
-
|
|
40
|
+
RE_cert_domain = re.compile(
|
|
41
|
+
r"""
|
|
42
|
+
^
|
|
43
|
+
(?:\*\.)? # allow a leading "*.", to denote wildcards
|
|
44
|
+
(?:
|
|
45
|
+
[A-Z0-9] # the first char MUST be a letter or number
|
|
46
|
+
(?:[A-Z0-9-]{0,61} # inner chars can have a dash
|
|
47
|
+
[A-Z0-9] # the last char must be a leter or number
|
|
48
|
+
\.
|
|
49
|
+
)?
|
|
50
|
+
)+ # one or more components
|
|
51
|
+
(?:
|
|
52
|
+
[A-Z]{2,6}
|
|
53
|
+
|
|
|
54
|
+
[A-Z0-9-]{2,}
|
|
55
|
+
(?<!-) # doesn't end with a dash
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
$
|
|
59
|
+
""",
|
|
60
|
+
re.I | re.X,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses
|
|
65
|
+
|
|
66
|
+
# IPv6 RegEx
|
|
67
|
+
RE_ipv6 = re.compile(
|
|
68
|
+
r"""
|
|
69
|
+
(
|
|
70
|
+
([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}| # 1:2:3:4:5:6:7:8
|
|
71
|
+
([0-9a-fA-F]{1,4}:){1,7}:| # 1:: 1:2:3:4:5:6:7::
|
|
72
|
+
([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}| # 1::8 1:2:3:4:5:6::8 1:2:3:4:5:6::8
|
|
73
|
+
([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}| # 1::7:8 1:2:3:4:5::7:8 1:2:3:4:5::8
|
|
74
|
+
([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}| # 1::6:7:8 1:2:3:4::6:7:8 1:2:3:4::8
|
|
75
|
+
([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}| # 1::5:6:7:8 1:2:3::5:6:7:8 1:2:3::8
|
|
76
|
+
([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}| # 1::4:5:6:7:8 1:2::4:5:6:7:8 1:2::8
|
|
77
|
+
[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})| # 1::3:4:5:6:7:8 1::3:4:5:6:7:8 1::8
|
|
78
|
+
:((:[0-9a-fA-F]{1,4}){1,7}|:)| # ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::
|
|
79
|
+
fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}| # fe80::7:8%eth0 fe80::7:8%1 (link-local IPv6 addresses with zone index)
|
|
80
|
+
::(ffff(:0{1,4}){0,1}:){0,1}
|
|
81
|
+
((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}
|
|
82
|
+
(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])| # ::255.255.255.255 ::ffff:255.255.255.255 ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
|
|
83
|
+
([0-9a-fA-F]{1,4}:){1,4}:
|
|
84
|
+
((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}
|
|
85
|
+
(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]) # 2001:db8:3:4::192.0.2.33 64:ff9b::192.0.2.33 (IPv4-Embedded IPv6 Address)
|
|
86
|
+
)
|
|
87
|
+
""",
|
|
88
|
+
re.I | re.X,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# IPv4 RegEx
|
|
93
|
+
RE_ipv4 = re.compile(
|
|
94
|
+
r"""
|
|
95
|
+
((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])
|
|
96
|
+
""",
|
|
97
|
+
re.I | re.X,
|
|
40
98
|
)
|
|
41
99
|
|
|
100
|
+
|
|
42
101
|
# (RSA, (4096,))
|
|
43
102
|
# (EC, ("P-384",))
|
|
44
103
|
TECHNOLOGY_RETURN_VALUES = Tuple[str, Tuple[Union[str, int]]]
|
|
@@ -100,7 +159,12 @@ def curve_to_nist(curve_name: str) -> str:
|
|
|
100
159
|
raise ValueError("Unknown curve: %s" % curve_name)
|
|
101
160
|
|
|
102
161
|
|
|
103
|
-
def domains_from_list(
|
|
162
|
+
def domains_from_list(
|
|
163
|
+
domain_names: Iterable[str],
|
|
164
|
+
allow_hostname: bool = True,
|
|
165
|
+
allow_ipv4: bool = False,
|
|
166
|
+
allow_ipv6: bool = False,
|
|
167
|
+
) -> List[str]:
|
|
104
168
|
"""
|
|
105
169
|
Turns a list of strings into a standardized list of domain names.
|
|
106
170
|
|
|
@@ -109,26 +173,53 @@ def domains_from_list(domain_names: Iterable[str]) -> List[str]:
|
|
|
109
173
|
This invokes `validate_domains`, which uses a simple regex to validate each domain in the list.
|
|
110
174
|
|
|
111
175
|
:param domain_names: (required) An iterable list of strings
|
|
176
|
+
:param allow_hostname: bool, default True
|
|
177
|
+
added in 1.0.4
|
|
178
|
+
:param allow_ipv4: bool, default False
|
|
179
|
+
added in 1.0.4
|
|
180
|
+
:param allow_ipv6: bool, default False
|
|
181
|
+
added in 1.0.4
|
|
112
182
|
"""
|
|
113
183
|
domain_names = [d for d in [d.strip().lower() for d in domain_names] if d]
|
|
114
184
|
# make the list unique
|
|
115
|
-
domain_names = list(set(domain_names))
|
|
185
|
+
domain_names = sorted(list(set(domain_names)))
|
|
116
186
|
# validate the list
|
|
117
|
-
validate_domains(
|
|
187
|
+
validate_domains(
|
|
188
|
+
domain_names,
|
|
189
|
+
allow_hostname=allow_hostname,
|
|
190
|
+
allow_ipv4=allow_ipv4,
|
|
191
|
+
allow_ipv6=allow_ipv6,
|
|
192
|
+
)
|
|
118
193
|
return domain_names
|
|
119
194
|
|
|
120
195
|
|
|
121
|
-
def domains_from_string(
|
|
196
|
+
def domains_from_string(
|
|
197
|
+
text: str,
|
|
198
|
+
allow_hostname: bool = True,
|
|
199
|
+
allow_ipv4: bool = False,
|
|
200
|
+
allow_ipv6: bool = False,
|
|
201
|
+
) -> List[str]:
|
|
122
202
|
"""
|
|
123
|
-
:param text: (required) Turns a comma-separated-list of domain names into a list
|
|
124
|
-
|
|
125
203
|
This invokes `domains_from_list` which invokes `validate_domains`, which uses a simple regex to validate each domain in the list.
|
|
126
204
|
|
|
127
205
|
This will raise a `ValueError("invalid domain")` on the first invalid domain
|
|
206
|
+
|
|
207
|
+
:param text: (required) Turns a comma-separated-list of domain names into a list
|
|
208
|
+
:param allow_hostname: bool, default True
|
|
209
|
+
added in 1.0.4
|
|
210
|
+
:param allow_ipv4: bool, default False
|
|
211
|
+
added in 1.0.4
|
|
212
|
+
:param allow_ipv6: bool, default False
|
|
213
|
+
added in 1.0.4
|
|
128
214
|
"""
|
|
129
215
|
# generate list
|
|
130
216
|
domain_names = text.split(",")
|
|
131
|
-
return domains_from_list(
|
|
217
|
+
return domains_from_list(
|
|
218
|
+
domain_names,
|
|
219
|
+
allow_hostname=allow_hostname,
|
|
220
|
+
allow_ipv4=allow_ipv4,
|
|
221
|
+
allow_ipv6=allow_ipv6,
|
|
222
|
+
)
|
|
132
223
|
|
|
133
224
|
|
|
134
225
|
def hex_with_colons(as_hex: str) -> str:
|
|
@@ -140,6 +231,27 @@ def hex_with_colons(as_hex: str) -> str:
|
|
|
140
231
|
return output
|
|
141
232
|
|
|
142
233
|
|
|
234
|
+
def identify_san_type(
|
|
235
|
+
san: str,
|
|
236
|
+
) -> Literal["hostname", "ipv4", "ipv6"]:
|
|
237
|
+
"""
|
|
238
|
+
This does a rudimentary check against hostnames (default) and optionally
|
|
239
|
+
ipv4 and ipv6.
|
|
240
|
+
|
|
241
|
+
see also: validate_domains
|
|
242
|
+
added in 1.0.4
|
|
243
|
+
"""
|
|
244
|
+
if RE_cert_domain.match(san):
|
|
245
|
+
if RE_ipv4.match(san):
|
|
246
|
+
return "ipv4"
|
|
247
|
+
return "hostname"
|
|
248
|
+
if RE_ipv4.match(san):
|
|
249
|
+
return "ipv4"
|
|
250
|
+
if RE_ipv6.match(san):
|
|
251
|
+
return "ipv6"
|
|
252
|
+
raise ValueError("invalid san: `%s`" % san)
|
|
253
|
+
|
|
254
|
+
|
|
143
255
|
def jose_b64(b: bytes) -> str:
|
|
144
256
|
# helper function base64 encode for jose spec
|
|
145
257
|
return base64.urlsafe_b64encode(b).decode("utf8").replace("=", "")
|
|
@@ -197,15 +309,42 @@ def split_pem_chain(pem_text: str) -> List[str]:
|
|
|
197
309
|
return certs
|
|
198
310
|
|
|
199
311
|
|
|
200
|
-
def validate_domains(
|
|
312
|
+
def validate_domains(
|
|
313
|
+
domain_names: Iterable[str],
|
|
314
|
+
allow_hostname: bool = True,
|
|
315
|
+
allow_ipv4: bool = False,
|
|
316
|
+
allow_ipv6: bool = False,
|
|
317
|
+
) -> bool:
|
|
201
318
|
"""
|
|
202
|
-
Ensures each items of the iterable `domain_names`
|
|
319
|
+
Ensures each items of the iterable `domain_names` is qualified for inclusion
|
|
320
|
+
in a x509 certificate.
|
|
321
|
+
|
|
322
|
+
This does a rudimentary check against hostnames (default) and optionally
|
|
323
|
+
ipv4 and ipv6.
|
|
324
|
+
|
|
325
|
+
see also: identify_san_type
|
|
203
326
|
|
|
204
327
|
:param domain_names: (required) An iterable list of strings
|
|
328
|
+
:param allow_hostname: bool, default True
|
|
329
|
+
added in 1.0.4
|
|
330
|
+
:param allow_ipv4: bool, default False
|
|
331
|
+
added in 1.0.4
|
|
332
|
+
:param allow_ipv6: bool, default False
|
|
333
|
+
added in 1.0.4
|
|
205
334
|
"""
|
|
206
335
|
for d in domain_names:
|
|
207
|
-
if
|
|
208
|
-
|
|
336
|
+
if allow_hostname and RE_cert_domain.match(d):
|
|
337
|
+
if RE_ipv4.match(d):
|
|
338
|
+
if not allow_ipv4:
|
|
339
|
+
raise ValueError("invalid domain: `%s`" % d)
|
|
340
|
+
continue
|
|
341
|
+
if allow_ipv4 and RE_ipv4.match(d):
|
|
342
|
+
continue
|
|
343
|
+
if allow_ipv6 and RE_ipv6.match(d):
|
|
344
|
+
# TODO: drop out special prefixes
|
|
345
|
+
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
|
346
|
+
continue
|
|
347
|
+
raise ValueError("invalid domain: `%s`" % d)
|
|
209
348
|
return True
|
|
210
349
|
|
|
211
350
|
|