cert-utils 1.2.0__py3-none-any.whl

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 (79) hide show
  1. cert_utils/__init__.py +8 -0
  2. cert_utils/compat/__init__.py +0 -0
  3. cert_utils/compat/cryptography.py +172 -0
  4. cert_utils/compat/openssl.py +860 -0
  5. cert_utils/compat/pyopenssl.py +207 -0
  6. cert_utils/compat/signatures.py +114 -0
  7. cert_utils/conditionals.py +121 -0
  8. cert_utils/convert.py +307 -0
  9. cert_utils/core.py +3075 -0
  10. cert_utils/errors.py +54 -0
  11. cert_utils/letsencrypt-certs/deprecated/e5-cross.pem +26 -0
  12. cert_utils/letsencrypt-certs/deprecated/e5.pem +17 -0
  13. cert_utils/letsencrypt-certs/deprecated/e6-cross.pem +26 -0
  14. cert_utils/letsencrypt-certs/deprecated/e6.pem +17 -0
  15. cert_utils/letsencrypt-certs/deprecated/e7-cross.pem +26 -0
  16. cert_utils/letsencrypt-certs/deprecated/e7.pem +17 -0
  17. cert_utils/letsencrypt-certs/deprecated/e8-cross.pem +26 -0
  18. cert_utils/letsencrypt-certs/deprecated/e8.pem +17 -0
  19. cert_utils/letsencrypt-certs/deprecated/e9-cross.pem +26 -0
  20. cert_utils/letsencrypt-certs/deprecated/e9.pem +17 -0
  21. cert_utils/letsencrypt-certs/deprecated/fakeleintermediatex1.pem +28 -0
  22. cert_utils/letsencrypt-certs/deprecated/fakelerootx1.pem +29 -0
  23. cert_utils/letsencrypt-certs/deprecated/isrg-root-ocsp-x1.pem +28 -0
  24. cert_utils/letsencrypt-certs/deprecated/isrg-root-x1-cross-signed.pem +31 -0
  25. cert_utils/letsencrypt-certs/deprecated/isrg-root-x2-cross-signed.pem +26 -0
  26. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-e1.pem +17 -0
  27. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-e2.pem +17 -0
  28. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-r3-cross-signed.pem +26 -0
  29. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-r3.pem +30 -0
  30. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-r4-cross-signed.pem +26 -0
  31. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-r4.pem +30 -0
  32. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-x1-cross-signed.pem +27 -0
  33. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-x2-cross-signed.pem +27 -0
  34. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-x3-cross-signed.pem +27 -0
  35. cert_utils/letsencrypt-certs/deprecated/lets-encrypt-x4-cross-signed.pem +27 -0
  36. cert_utils/letsencrypt-certs/deprecated/letsencrypt-stg-root-x2-signed-by-x1.pem +27 -0
  37. cert_utils/letsencrypt-certs/deprecated/letsencryptauthorityx1.pem +32 -0
  38. cert_utils/letsencrypt-certs/deprecated/letsencryptauthorityx2.pem +32 -0
  39. cert_utils/letsencrypt-certs/deprecated/letsencryptauthorityx3.pem +32 -0
  40. cert_utils/letsencrypt-certs/deprecated/letsencryptauthorityx4.pem +32 -0
  41. cert_utils/letsencrypt-certs/deprecated/r10.pem +29 -0
  42. cert_utils/letsencrypt-certs/deprecated/r11.pem +29 -0
  43. cert_utils/letsencrypt-certs/deprecated/r12.pem +29 -0
  44. cert_utils/letsencrypt-certs/deprecated/r13.pem +29 -0
  45. cert_utils/letsencrypt-certs/deprecated/r14.pem +29 -0
  46. cert_utils/letsencrypt-certs/deprecated/trustid-x3-root.p7c +0 -0
  47. cert_utils/letsencrypt-certs/deprecated/trustid-x3-root.pem +20 -0
  48. cert_utils/letsencrypt-certs/int-ye1.pem +16 -0
  49. cert_utils/letsencrypt-certs/int-ye2.pem +16 -0
  50. cert_utils/letsencrypt-certs/int-ye3.pem +16 -0
  51. cert_utils/letsencrypt-certs/int-yr1.pem +28 -0
  52. cert_utils/letsencrypt-certs/int-yr2.pem +28 -0
  53. cert_utils/letsencrypt-certs/int-yr3.pem +28 -0
  54. cert_utils/letsencrypt-certs/isrg-root-x2.pem +14 -0
  55. cert_utils/letsencrypt-certs/isrgrootx1.pem +31 -0
  56. cert_utils/letsencrypt-certs/isrgrootx1.pkix +0 -0
  57. cert_utils/letsencrypt-certs/letsencrypt-stg-root-x1.pem +32 -0
  58. cert_utils/letsencrypt-certs/letsencrypt-stg-root-x2.pem +15 -0
  59. cert_utils/letsencrypt-certs/root-x2-by-x1.pem +26 -0
  60. cert_utils/letsencrypt-certs/root-ye-by-x2.pem +17 -0
  61. cert_utils/letsencrypt-certs/root-ye.pem +12 -0
  62. cert_utils/letsencrypt-certs/root-yr-by-x1.pem +34 -0
  63. cert_utils/letsencrypt-certs/root-yr.pem +30 -0
  64. cert_utils/letsencrypt_info/__init__.py +6 -0
  65. cert_utils/letsencrypt_info/certificates.py +1074 -0
  66. cert_utils/letsencrypt_info/rate_limits.py +414 -0
  67. cert_utils/letsencrypt_info/types.py +120 -0
  68. cert_utils/model.py +181 -0
  69. cert_utils/network.py +28 -0
  70. cert_utils/py.typed +0 -0
  71. cert_utils/scripts/__init__.py +0 -0
  72. cert_utils/scripts/cert_info.py +332 -0
  73. cert_utils/utils.py +388 -0
  74. cert_utils-1.2.0.dist-info/METADATA +259 -0
  75. cert_utils-1.2.0.dist-info/RECORD +79 -0
  76. cert_utils-1.2.0.dist-info/WHEEL +5 -0
  77. cert_utils-1.2.0.dist-info/entry_points.txt +2 -0
  78. cert_utils-1.2.0.dist-info/licenses/LICENSE.txt +224 -0
  79. cert_utils-1.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,207 @@
1
+ """
2
+ Please don't use this.
3
+
4
+ THIS ENTIRE FILE IS DEPRECATED, UNTESTED AND UNSUPPORTED.
5
+
6
+ This is provided solely for legacy information as these operations are a bit
7
+ more esoteric and not well documented elsewhere.
8
+
9
+ Please use the other interfaces.
10
+ """
11
+
12
+ import logging
13
+ from types import ModuleType
14
+ from typing import List
15
+ from typing import Optional
16
+
17
+ # locals
18
+ from ..errors import OpenSslError
19
+ from ..utils import split_pem_chain
20
+
21
+ # conditional imports
22
+ openssl_crypto: Optional[ModuleType]
23
+ try:
24
+ from OpenSSL import crypto as openssl_crypto
25
+ except ImportError:
26
+ openssl_crypto = None
27
+
28
+ # logging
29
+ log = logging.getLogger("cert_utils")
30
+ log.setLevel(logging.INFO)
31
+
32
+ # ==============================================================================
33
+
34
+
35
+ def ensure_chain(
36
+ root_pem: str,
37
+ fullchain_pem: Optional[str] = None,
38
+ cert_pem: Optional[str] = None,
39
+ chain_pem: Optional[str] = None,
40
+ root_pems_other: Optional[List[str]] = None,
41
+ ) -> bool:
42
+ """
43
+ validates from a root down to a chain
44
+ if chain is a fullchain (with endentity), cert_pem can be None
45
+
46
+ THIS WILL RAISE ERRORS, NOT RETURN VALUES
47
+
48
+ submit EITHER fullchain_pem or chain_pem+cert_pem
49
+
50
+ :param root_pem: The PEM Encoded Root Certificate. Required.
51
+ :type root_pem: str
52
+ :param fullchain_pem: A full Certificate chain in PEM encoding, which
53
+ consists of a Leaf Certificate, and optionally multiple upstream certs in
54
+ a single string.
55
+ If provided:
56
+ * `:param:cert_pem` MUST NOT be provided
57
+ * `:param:chain_pem` MUST NOT be provided.
58
+ :type fullchain_pem: str
59
+ :param cert_pem: the EndEntity or Leaf Certificate.
60
+ If provided:
61
+ * `:param:chain_pem` MUST be provided
62
+ * `:param:fullchain_pem` MUST NOT be provided.
63
+ :type cert_pem: str
64
+ :param chain_pem: A Certificate chain in PEM format, which is multiple
65
+ upstream certs in a single string.
66
+ If provided:
67
+ * `:param:cert_pem` MUST be provided
68
+ * `:param:fullchain_pem` MUST NOT be provided.
69
+ :param root_pems_other: an iterable list of trusted roots certificates, in
70
+ PEM encoding; currently unused.
71
+ :returns: True
72
+ :rtype: bool
73
+
74
+
75
+ The OpenSSL Equivalent / Fallback is::
76
+
77
+ Modern versions of openssl accept multiple `-untrusted` arguments::
78
+
79
+ openssl verify -purpose sslserver -CAfile root.pem [[-untrusted intermediate.pem],[-untrusted intermediate.pem],] cert.pem
80
+
81
+ However older ones only want to see a single `-untrusted`::
82
+
83
+ openssl verify -purpose sslserver -CAfile root.pem -untrusted intermediate.pem cert.pem
84
+
85
+ To get around this, put all the intermediates into a single file.
86
+
87
+ This is a stopgap solution and needs to be refactored.
88
+
89
+ NOTE:
90
+ openssl does not care about the order of intermediates, so this should
91
+ be iteratively built up like the pure-python example
92
+ """
93
+ log.debug("ensure_chain >")
94
+ if fullchain_pem:
95
+ if chain_pem or cert_pem:
96
+ raise ValueError(
97
+ "If `ensure_chain` is invoked with `fullchain_pem`, do not pass in `chain_pem` or `cert_pem`."
98
+ )
99
+ else:
100
+ if not chain_pem or not cert_pem:
101
+ raise ValueError(
102
+ "If `ensure_chain` is not invoked with `fullchain_pem`, you must pass in `chain_pem` and `cert_pem`."
103
+ )
104
+
105
+ if fullchain_pem:
106
+ intermediates = split_pem_chain(fullchain_pem)
107
+ cert_pem = intermediates.pop(0)
108
+ else:
109
+ assert cert_pem
110
+ assert chain_pem
111
+ intermediates = split_pem_chain(chain_pem)
112
+ cert_pem = cert_pem.strip() # needed to match regex results in above situation
113
+
114
+ # sometimes people submit things they should not
115
+ if intermediates[-1] == cert_pem:
116
+ intermediates = intermediates[:-1]
117
+
118
+ if not openssl_crypto:
119
+ raise ValueError("Requires pyopenssl")
120
+ # build a root storage
121
+ store = openssl_crypto.X509Store()
122
+ root_parsed = openssl_crypto.load_certificate(
123
+ openssl_crypto.FILETYPE_PEM, root_pem.encode()
124
+ )
125
+ store.add_cert(root_parsed)
126
+
127
+ for _intermediate_pem in reversed(intermediates):
128
+ _intermediate_parsed = openssl_crypto.load_certificate(
129
+ openssl_crypto.FILETYPE_PEM, _intermediate_pem.encode()
130
+ )
131
+ # Check the chain certificate before adding it to the store.
132
+ _store_ctx = openssl_crypto.X509StoreContext(store, _intermediate_parsed)
133
+ _store_ctx.verify_certificate()
134
+ store.add_cert(_intermediate_parsed)
135
+
136
+ cert_parsed = openssl_crypto.load_certificate(
137
+ openssl_crypto.FILETYPE_PEM, cert_pem.encode()
138
+ )
139
+ _store_ctx = openssl_crypto.X509StoreContext(store, cert_parsed)
140
+ _store_ctx.verify_certificate()
141
+ return True
142
+
143
+
144
+ def ensure_chain_order(
145
+ chain_certs: List[str],
146
+ cert_pem: Optional[str] = None,
147
+ ) -> bool:
148
+ """
149
+ :param chain_certs: A list of PEM encoded Certificates. Required.
150
+ :type chain_certs: list
151
+ :param cert_pem: A PEM Encoded Certificate to test against the `chain_certs`.
152
+ Optional
153
+ :type cert_pem: str
154
+ :returns: bool
155
+ :rtype: None
156
+
157
+ The OpenSSL Equivalent / Fallback is::
158
+
159
+ /usr/local/bin/openssl verify -purpose sslserver -partial_chain -trusted {ROOT.pem} {CHAINREVERSED.pem}
160
+ """
161
+ log.debug("ensure_chain_order >")
162
+ if cert_pem:
163
+ chain_certs.append(cert_pem)
164
+ if len(chain_certs) < 2:
165
+ raise ValueError("must submit 2 or more chain certificates")
166
+ # reverse the cert list
167
+ # we're going to pretend the last item is a root
168
+ r_chain_certs = chain_certs[::-1]
169
+ if not openssl_crypto:
170
+ raise ValueError("Requires pyopenssl")
171
+ # TODO: openssl crypto does not seem to support partial chains yet
172
+ # as a stopgap, just look to ensure the issuer/subject match
173
+ """
174
+ # build a root storage
175
+ # pretend the first item is a root
176
+ store = openssl_crypto.X509Store()
177
+ root_pem = r_chain_certs.pop(0)
178
+ root_parsed = openssl_crypto.load_certificate(openssl_crypto.FILETYPE_PEM, root_pem)
179
+ store.add_cert(root_parsed)
180
+
181
+ for (idx, cert_pem) in enumerate(r_chain_certs):
182
+ # Check the chain certificate before adding it to the store.
183
+ try:
184
+ cert_parsed = openssl_crypto.load_certificate(openssl_crypto.FILETYPE_PEM, cert_pem)
185
+ _store_ctx = openssl_crypto.X509StoreContext(store, cert_parsed)
186
+ _store_ctx.verify_certificate()
187
+ store.add_cert(cert_parsed)
188
+ except openssl_crypto.X509StoreContextError as exc:
189
+ raise OpenSslError("could not verify: crypto")
190
+ """
191
+ # stash our data in here
192
+ parsed_certs = {}
193
+
194
+ # loop the certs
195
+ for idx, cert_pem in enumerate(r_chain_certs):
196
+ # everyone generates data
197
+ cert = openssl_crypto.load_certificate(
198
+ openssl_crypto.FILETYPE_PEM, cert_pem.encode()
199
+ )
200
+ parsed_certs[idx] = cert
201
+ if idx == 0:
202
+ continue
203
+ # only after the first cert do we need to check the last cert
204
+ upchain = parsed_certs[idx - 1]
205
+ if upchain.get_subject() != cert.get_issuer():
206
+ raise OpenSslError("could not verify: upchain does not match issuer")
207
+ return True
@@ -0,0 +1,114 @@
1
+ """Small DER codecs for account signatures and OpenSSL public-key metadata.
2
+
3
+ No Cryptography/josepy dependency. This is not a general ASN.1 implementation.
4
+ """
5
+
6
+ import subprocess
7
+ from typing import Optional
8
+ from typing import Tuple
9
+
10
+ import psutil
11
+
12
+ from ..errors import OpenSslError_InvalidKey
13
+
14
+
15
+ def _read(data: bytes, offset: int, tag: int) -> Tuple[bytes, int]:
16
+ if offset + 2 > len(data) or data[offset] != tag:
17
+ raise ValueError("invalid DER tag or truncated value")
18
+ length = data[offset + 1]
19
+ offset += 2
20
+ if length & 128:
21
+ count = length & 127
22
+ if count == 0 or count > 2 or offset + count > len(data):
23
+ raise ValueError("invalid DER length")
24
+ length_end = offset + count
25
+ encoded = data[offset:length_end]
26
+ length = int.from_bytes(encoded, "big")
27
+ if encoded[0] == 0 or length < 128:
28
+ raise ValueError("non-minimal DER length")
29
+ offset += count
30
+ end = offset + length
31
+ if end > len(data):
32
+ raise ValueError("truncated DER value")
33
+ return data[offset:end], end
34
+
35
+
36
+ def _encode(tag: int, value: bytes) -> bytes:
37
+ length = len(value)
38
+ size = (length.bit_length() + 7) // 8
39
+ prefix = (
40
+ bytes([length])
41
+ if length < 128
42
+ else bytes([128 | size]) + length.to_bytes(size, "big")
43
+ )
44
+ return bytes([tag]) + prefix + value
45
+
46
+
47
+ def raw_to_der(signature: bytes, width: int) -> bytes:
48
+ """Encode a fixed-width unsigned r||s signature as canonical DER."""
49
+ if width not in (32, 48, 66) or len(signature) != width * 2:
50
+ raise ValueError("invalid standardized EC signature length")
51
+ integers = []
52
+ for part in (signature[:width], signature[width:]):
53
+ part = part.lstrip(b"\x00") or b"\x00"
54
+ if part[0] & 128:
55
+ part = b"\x00" + part
56
+ integers.append(_encode(2, part))
57
+ return _encode(48, b"".join(integers))
58
+
59
+
60
+ def der_to_raw(signature: bytes, width: int) -> bytes:
61
+ """Decode two canonical nonnegative DER integers, rejecting trailing data."""
62
+ if width not in (32, 48, 66) or len(signature) > 2 * width + 10:
63
+ raise ValueError("invalid EC signature size")
64
+ sequence, end = _read(signature, 0, 48)
65
+ if end != len(signature):
66
+ raise ValueError("trailing DER signature data")
67
+ offset = 0
68
+ parts = []
69
+ for _ in range(2):
70
+ part, offset = _read(sequence, offset, 2)
71
+ if not part or part[0] & 128:
72
+ raise ValueError("invalid DER signature integer")
73
+ if len(part) > 1 and part[0] == 0 and not part[1] & 128:
74
+ raise ValueError("non-minimal DER signature integer")
75
+ number = int.from_bytes(part, "big")
76
+ if number.bit_length() > width * 8:
77
+ raise ValueError("EC signature integer exceeds curve width")
78
+ parts.append(number.to_bytes(width, "big"))
79
+ if offset != len(sequence):
80
+ raise ValueError("extra DER signature components")
81
+ return b"".join(parts)
82
+
83
+
84
+ def openssl_key_width(openssl: str, filepath: str) -> Optional[int]:
85
+ """Return None for RSA or EC component width, using only public metadata."""
86
+ with psutil.Popen(
87
+ [openssl, "pkey", "-in", filepath, "-pubout", "-outform", "DER"],
88
+ stdin=subprocess.DEVNULL,
89
+ stdout=subprocess.PIPE,
90
+ stderr=subprocess.PIPE,
91
+ ) as proc:
92
+ public, error = proc.communicate()
93
+ if proc.returncode:
94
+ raise OpenSslError_InvalidKey(error.decode("utf8", errors="replace"))
95
+ sequence, end = _read(public, 0, 48)
96
+ if end != len(public):
97
+ raise ValueError("trailing public key data")
98
+ algorithm, _ = _read(sequence, 0, 48)
99
+ oid, offset = _read(algorithm, 0, 6)
100
+ if oid == bytes.fromhex("2a864886f70d010101"):
101
+ return None
102
+ if oid != bytes.fromhex("2a8648ce3d0201"):
103
+ raise ValueError("unsupported private key type")
104
+ curve, end = _read(algorithm, offset, 6)
105
+ if end != len(algorithm):
106
+ raise ValueError("unsupported EC parameters")
107
+ widths = {
108
+ bytes.fromhex("2a8648ce3d030107"): 32,
109
+ bytes.fromhex("2b81040022"): 48,
110
+ bytes.fromhex("2b81040023"): 66,
111
+ }
112
+ if curve not in widths:
113
+ raise ValueError("unsupported EC curve")
114
+ return widths[curve]
@@ -0,0 +1,121 @@
1
+ # stdlib
2
+ import logging
3
+ import os
4
+ import sys
5
+ from types import ModuleType
6
+ from typing import Any
7
+ from typing import Callable
8
+ from typing import Optional
9
+
10
+ log = logging.getLogger("cert_utils")
11
+
12
+ # ------------------------------------------------------------------------------
13
+
14
+ """
15
+ Conditional Imports
16
+
17
+ This package contains our conditional imports.
18
+
19
+ DO NOT IMPORT FROM THIS PACKAGE.
20
+ Instead, import this package and access the imports through it.
21
+
22
+ The unit tests need to unset these packages to `None` to ensure fallback
23
+ operations work correctly. If packages are imported from here, they may not
24
+ be property unset in the unit_tests.
25
+
26
+ BAD:
27
+ from .conditionals import cryptography
28
+ cert = cryptography.x509.load_pem_x509_certificate(
29
+ cert_pem.encode()
30
+ )
31
+
32
+ GOOD:
33
+ from . import conditionals
34
+ cert = conditionals.cryptography.x509.load_pem_x509_certificate(
35
+ cert_pem.encode()
36
+ )
37
+
38
+
39
+ """
40
+ cryptography: Optional[ModuleType]
41
+ crypto_x509: Optional[ModuleType]
42
+ crypto_hashes: Optional[ModuleType]
43
+ crypto_padding: Optional[ModuleType]
44
+ crypto_serialization: Optional[ModuleType]
45
+ crypto_dsa: Optional[ModuleType]
46
+ crypto_ec: Optional[ModuleType]
47
+ crypto_ed25519: Optional[ModuleType]
48
+ crypto_ed448: Optional[ModuleType]
49
+ crypto_rsa: Optional[ModuleType]
50
+ crypto_utils: Optional[ModuleType]
51
+ EllipticCurvePublicKey: Optional[Any]
52
+ RSAPublicKey: Optional[Any]
53
+ crypto_pkcs7: Optional[ModuleType]
54
+ josepy: Optional[ModuleType]
55
+
56
+ # first cryptography
57
+ try:
58
+ import cryptography
59
+ import cryptography.x509 as crypto_x509
60
+ from cryptography.hazmat.primitives import hashes as crypto_hashes
61
+ from cryptography.hazmat.primitives import padding as crypto_padding
62
+ from cryptography.hazmat.primitives import serialization as crypto_serialization
63
+ from cryptography.hazmat.primitives.asymmetric import dsa as crypto_dsa
64
+ from cryptography.hazmat.primitives.asymmetric import ec as crypto_ec
65
+ from cryptography.hazmat.primitives.asymmetric import ed25519 as crypto_ed25519
66
+ from cryptography.hazmat.primitives.asymmetric import ed448 as crypto_ed448
67
+ from cryptography.hazmat.primitives.asymmetric import rsa as crypto_rsa
68
+ from cryptography.hazmat.primitives.asymmetric import utils as crypto_utils
69
+ from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
70
+ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
71
+ from cryptography.hazmat.primitives.serialization import pkcs7 as crypto_pkcs7
72
+ except ImportError:
73
+ cryptography = None
74
+ crypto_x509 = None
75
+ crypto_hashes = None
76
+ crypto_padding = None
77
+ crypto_serialization = None
78
+ crypto_dsa = None
79
+ crypto_ec = None
80
+ crypto_ed25519 = None
81
+ crypto_ed448 = None
82
+ crypto_rsa = None
83
+ crypto_utils = None
84
+ EllipticCurvePublicKey = None
85
+ RSAPublicKey = None
86
+ crypto_pkcs7 = None
87
+
88
+
89
+ def is_josepy_compatible() -> bool:
90
+ # this code works with josepy 1 & 2
91
+ f_version: Callable
92
+ if sys.version_info < (3, 8):
93
+ # catch 3.7 and below
94
+ import importlib_metadata
95
+
96
+ f_version = importlib_metadata.version
97
+ else:
98
+ import importlib
99
+ import importlib.metadata
100
+
101
+ f_version = importlib.metadata.version
102
+
103
+ _v = f_version("josepy")
104
+ if int(_v.split(".")[0]) not in (1, 2):
105
+ _force = bool(int(os.environ.get("CERT_UTILS_FORCE_JOSEPY", "0")))
106
+ if not _force:
107
+ return False
108
+ return True
109
+
110
+
111
+ # then josepy
112
+ try:
113
+ import josepy
114
+
115
+ if not is_josepy_compatible():
116
+ log.critical("josepy might not be compatible; disabling")
117
+ log.critical("set env `CERT_UTILS_FORCE_JOSEPY=1` to bypass")
118
+ josepy = None # noqa: F811
119
+ except ImportError as exc:
120
+ log.critical("josepy ImportError %s", exc)
121
+ josepy = None