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.
Files changed (196) hide show
  1. {cert_utils-1.0.3 → cert_utils-1.0.4}/CHANGES.txt +14 -0
  2. cert_utils-1.0.4/PKG-INFO +234 -0
  3. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/__init__.py +1 -1
  4. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/utils.py +153 -14
  5. cert_utils-1.0.4/src/cert_utils.egg-info/PKG-INFO +234 -0
  6. cert_utils-1.0.4/src/cert_utils.egg-info/entry_points.txt +2 -0
  7. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/requires.txt +0 -1
  8. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_unit.py +248 -43
  9. cert_utils-1.0.3/PKG-INFO +0 -209
  10. cert_utils-1.0.3/src/cert_utils.egg-info/PKG-INFO +0 -209
  11. cert_utils-1.0.3/src/cert_utils.egg-info/entry_points.txt +0 -3
  12. {cert_utils-1.0.3 → cert_utils-1.0.4}/LICENSE.txt +0 -0
  13. {cert_utils-1.0.3 → cert_utils-1.0.4}/MANIFEST.in +0 -0
  14. {cert_utils-1.0.3 → cert_utils-1.0.4}/README.md +0 -0
  15. {cert_utils-1.0.3 → cert_utils-1.0.4}/pyproject.toml +0 -0
  16. {cert_utils-1.0.3 → cert_utils-1.0.4}/setup.cfg +0 -0
  17. {cert_utils-1.0.3 → cert_utils-1.0.4}/setup.py +0 -0
  18. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/compat/cryptography.py +0 -0
  19. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/compat/deprecated.py +0 -0
  20. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/compat/openssl.py +0 -0
  21. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/compat/pyopenssl.py +0 -0
  22. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/conditionals.py +0 -0
  23. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/convert.py +0 -0
  24. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/core.py +0 -0
  25. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/errors.py +0 -0
  26. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e5-cross.pem +0 -0
  27. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e5.pem +0 -0
  28. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e6-cross.pem +0 -0
  29. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e6.pem +0 -0
  30. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e7-cross.pem +0 -0
  31. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e7.pem +0 -0
  32. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e8-cross.pem +0 -0
  33. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e8.pem +0 -0
  34. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e9-cross.pem +0 -0
  35. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/e9.pem +0 -0
  36. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/fakeleintermediatex1.pem +0 -0
  37. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/fakelerootx1.pem +0 -0
  38. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrg-root-ocsp-x1.pem +0 -0
  39. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrg-root-x1-cross-signed.pem +0 -0
  40. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrg-root-x2-cross-signed.pem +0 -0
  41. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrg-root-x2.pem +0 -0
  42. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrgrootx1.pem +0 -0
  43. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/isrgrootx1.pkix +0 -0
  44. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-e1.pem +0 -0
  45. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-e2.pem +0 -0
  46. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-r3-cross-signed.pem +0 -0
  47. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-r3.pem +0 -0
  48. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-r4-cross-signed.pem +0 -0
  49. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-r4.pem +0 -0
  50. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-x1-cross-signed.pem +0 -0
  51. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-x2-cross-signed.pem +0 -0
  52. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-x3-cross-signed.pem +0 -0
  53. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/lets-encrypt-x4-cross-signed.pem +0 -0
  54. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/letsencryptauthorityx1.pem +0 -0
  55. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/letsencryptauthorityx2.pem +0 -0
  56. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/letsencryptauthorityx3.pem +0 -0
  57. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/letsencryptauthorityx4.pem +0 -0
  58. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r10.pem +0 -0
  59. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r11.pem +0 -0
  60. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r12.pem +0 -0
  61. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r13.pem +0 -0
  62. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/r14.pem +0 -0
  63. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/trustid-x3-root.p7c +0 -0
  64. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt-certs/trustid-x3-root.pem +0 -0
  65. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/letsencrypt_info.py +0 -0
  66. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/model.py +0 -0
  67. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/py.typed +0 -0
  68. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils/scripts/cert_info.py +0 -0
  69. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/SOURCES.txt +0 -0
  70. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/dependency_links.txt +0 -0
  71. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/not-zip-safe +0 -0
  72. {cert_utils-1.0.3 → cert_utils-1.0.4}/src/cert_utils.egg-info/top_level.txt +0 -0
  73. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/__init__.py +0 -0
  74. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/_utils.py +0 -0
  75. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/__README__.txt +0 -0
  76. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/__README__.txt +0 -0
  77. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/alternate_chains/1/chain.pem +0 -0
  78. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/alternate_chains/1/fullchain.pem +0 -0
  79. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/alternate_chains/2/chain.pem +0 -0
  80. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/alternate_chains/2/fullchain.pem +0 -0
  81. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/cert.pem +0 -0
  82. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/chain.pem +0 -0
  83. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/fullchain.pem +0 -0
  84. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/alternate_chains/1/privkey.pem +0 -0
  85. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/appendix_a-cert.pem +0 -0
  86. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/cert--all.pem +0 -0
  87. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/cert--issuer+serial.pem +0 -0
  88. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/cert--key_id.pem +0 -0
  89. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/draft-acme-ari/edge_case_generate.py +0 -0
  90. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-ec/ec384-1-key.pem +0 -0
  91. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-ec/ec384-1.csr +0 -0
  92. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-ec/ec384-2-key.pem +0 -0
  93. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-1.pem +0 -0
  94. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-2.pem +0 -0
  95. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-3.pem +0 -0
  96. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-4.pem +0 -0
  97. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-5.pem +0 -0
  98. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-account_daily.pem +0 -0
  99. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-account_weekly.pem +0 -0
  100. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-global_daily.pem +0 -0
  101. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-global_weekly.pem +0 -0
  102. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/AcmeAccountKey-cycle-single_certificate.pem +0 -0
  103. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_1.key +0 -0
  104. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_2.key +0 -0
  105. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_3.key +0 -0
  106. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_4.key +0 -0
  107. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/acme_account_5.key +0 -0
  108. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_1.key +0 -0
  109. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_2.key +0 -0
  110. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_3.key +0 -0
  111. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_4.key +0 -0
  112. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/private_5.key +0 -0
  113. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_1-server.crt +0 -0
  114. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_1-server.csr +0 -0
  115. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_1-server.key +0 -0
  116. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_2-server.crt +0 -0
  117. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_2-server.csr +0 -0
  118. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_2-server.key +0 -0
  119. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_3-server.crt +0 -0
  120. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_3-server.csr +0 -0
  121. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_3-server.key +0 -0
  122. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_4-server.crt +0 -0
  123. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_4-server.csr +0 -0
  124. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_4-server.key +0 -0
  125. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_5-server.crt +0 -0
  126. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_5-server.csr +0 -0
  127. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/key_technology-rsa/selfsigned_5-server.key +0 -0
  128. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/__README__.txt +0 -0
  129. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/_data.json +0 -0
  130. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/_demo.py +0 -0
  131. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/cert.pem +0 -0
  132. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/chain_0.pem +0 -0
  133. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/chain_0_bad.pem +0 -0
  134. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/chain_1.pem +0 -0
  135. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/chain_2.pem +0 -0
  136. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/root_0.pem +0 -0
  137. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/root_1.pem +0 -0
  138. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/TestA/root_2.pem +0 -0
  139. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/long_chains/__README__.txt +0 -0
  140. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert1.pem +0 -0
  141. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert2.pem +0 -0
  142. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert3.pem +0 -0
  143. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert4.pem +0 -0
  144. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert5.pem +0 -0
  145. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/cert6.pem +0 -0
  146. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain1.pem +0 -0
  147. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain2.pem +0 -0
  148. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain3.pem +0 -0
  149. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain4.pem +0 -0
  150. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain5.pem +0 -0
  151. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/chain6.pem +0 -0
  152. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain1.pem +0 -0
  153. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain2.pem +0 -0
  154. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain3.pem +0 -0
  155. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain4.pem +0 -0
  156. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain5.pem +0 -0
  157. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/fullchain6.pem +0 -0
  158. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey1.pem +0 -0
  159. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey2.pem +0 -0
  160. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey3.pem +0 -0
  161. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey4.pem +0 -0
  162. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey5.pem +0 -0
  163. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/pebble-certs/privkey6.pem +0 -0
  164. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/_support/account.key +0 -0
  165. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/_support/info.txt +0 -0
  166. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/info.txt +0 -0
  167. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/meta.json +0 -0
  168. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/private_key.json +0 -0
  169. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/private_key.pem +0 -0
  170. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_001/regr.json +0 -0
  171. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_002/private_key.pem +0 -0
  172. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/account_003/private_key.pem +0 -0
  173. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/cert.pem +0 -0
  174. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/chain.pem +0 -0
  175. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/csr.pem +0 -0
  176. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/fullchain.pem +0 -0
  177. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/info.txt +0 -0
  178. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/privkey.pem +0 -0
  179. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_001/test.py +0 -0
  180. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_002/csr.pem +0 -0
  181. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_002/info.txt +0 -0
  182. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_002/privkey.pem +0 -0
  183. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/cert.pem +0 -0
  184. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/csr.pem +0 -0
  185. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/fullchain.pem +0 -0
  186. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/info.txt +0 -0
  187. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_003/privkey.pem +0 -0
  188. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/cert.pem +0 -0
  189. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/csr.pem +0 -0
  190. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/fullchain.pem +0 -0
  191. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/info.txt +0 -0
  192. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_004/privkey.pem +0 -0
  193. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_005/csr.pem +0 -0
  194. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_005/info.txt +0 -0
  195. {cert_utils-1.0.3 → cert_utils-1.0.4}/tests/test_data/unit_tests/cert_005/privkey.pem +0 -0
  196. {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
+ ![Python package](https://github.com/jvanasco/cert_utils/workflows/Python%20package/badge.svg)
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.
@@ -5,4 +5,4 @@ from .utils import * # noqa: F401, F403
5
5
 
6
6
  # ==============================================================================
7
7
 
8
- __VERSION__ = "1.0.3"
8
+ __VERSION__ = "1.0.4"
@@ -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
- RE_domain = re.compile(
38
- r"""^(?:\*\.)?(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))$""",
39
- re.I,
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(domain_names: Iterable[str]) -> List[str]:
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(domain_names)
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(text: str) -> List[str]:
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(domain_names)
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(domain_names: Iterable[str]) -> bool:
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` matches a regular expression.
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 not RE_domain.match(d):
208
- raise ValueError("invalid domain: `%s`" % d)
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