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.
- cert_utils/__init__.py +8 -0
- cert_utils/compat/__init__.py +0 -0
- cert_utils/compat/cryptography.py +172 -0
- cert_utils/compat/openssl.py +860 -0
- cert_utils/compat/pyopenssl.py +207 -0
- cert_utils/compat/signatures.py +114 -0
- cert_utils/conditionals.py +121 -0
- cert_utils/convert.py +307 -0
- cert_utils/core.py +3075 -0
- cert_utils/errors.py +54 -0
- cert_utils/letsencrypt-certs/deprecated/e5-cross.pem +26 -0
- cert_utils/letsencrypt-certs/deprecated/e5.pem +17 -0
- cert_utils/letsencrypt-certs/deprecated/e6-cross.pem +26 -0
- cert_utils/letsencrypt-certs/deprecated/e6.pem +17 -0
- cert_utils/letsencrypt-certs/deprecated/e7-cross.pem +26 -0
- cert_utils/letsencrypt-certs/deprecated/e7.pem +17 -0
- cert_utils/letsencrypt-certs/deprecated/e8-cross.pem +26 -0
- cert_utils/letsencrypt-certs/deprecated/e8.pem +17 -0
- cert_utils/letsencrypt-certs/deprecated/e9-cross.pem +26 -0
- cert_utils/letsencrypt-certs/deprecated/e9.pem +17 -0
- cert_utils/letsencrypt-certs/deprecated/fakeleintermediatex1.pem +28 -0
- cert_utils/letsencrypt-certs/deprecated/fakelerootx1.pem +29 -0
- cert_utils/letsencrypt-certs/deprecated/isrg-root-ocsp-x1.pem +28 -0
- cert_utils/letsencrypt-certs/deprecated/isrg-root-x1-cross-signed.pem +31 -0
- cert_utils/letsencrypt-certs/deprecated/isrg-root-x2-cross-signed.pem +26 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-e1.pem +17 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-e2.pem +17 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-r3-cross-signed.pem +26 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-r3.pem +30 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-r4-cross-signed.pem +26 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-r4.pem +30 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-x1-cross-signed.pem +27 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-x2-cross-signed.pem +27 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-x3-cross-signed.pem +27 -0
- cert_utils/letsencrypt-certs/deprecated/lets-encrypt-x4-cross-signed.pem +27 -0
- cert_utils/letsencrypt-certs/deprecated/letsencrypt-stg-root-x2-signed-by-x1.pem +27 -0
- cert_utils/letsencrypt-certs/deprecated/letsencryptauthorityx1.pem +32 -0
- cert_utils/letsencrypt-certs/deprecated/letsencryptauthorityx2.pem +32 -0
- cert_utils/letsencrypt-certs/deprecated/letsencryptauthorityx3.pem +32 -0
- cert_utils/letsencrypt-certs/deprecated/letsencryptauthorityx4.pem +32 -0
- cert_utils/letsencrypt-certs/deprecated/r10.pem +29 -0
- cert_utils/letsencrypt-certs/deprecated/r11.pem +29 -0
- cert_utils/letsencrypt-certs/deprecated/r12.pem +29 -0
- cert_utils/letsencrypt-certs/deprecated/r13.pem +29 -0
- cert_utils/letsencrypt-certs/deprecated/r14.pem +29 -0
- cert_utils/letsencrypt-certs/deprecated/trustid-x3-root.p7c +0 -0
- cert_utils/letsencrypt-certs/deprecated/trustid-x3-root.pem +20 -0
- cert_utils/letsencrypt-certs/int-ye1.pem +16 -0
- cert_utils/letsencrypt-certs/int-ye2.pem +16 -0
- cert_utils/letsencrypt-certs/int-ye3.pem +16 -0
- cert_utils/letsencrypt-certs/int-yr1.pem +28 -0
- cert_utils/letsencrypt-certs/int-yr2.pem +28 -0
- cert_utils/letsencrypt-certs/int-yr3.pem +28 -0
- cert_utils/letsencrypt-certs/isrg-root-x2.pem +14 -0
- cert_utils/letsencrypt-certs/isrgrootx1.pem +31 -0
- cert_utils/letsencrypt-certs/isrgrootx1.pkix +0 -0
- cert_utils/letsencrypt-certs/letsencrypt-stg-root-x1.pem +32 -0
- cert_utils/letsencrypt-certs/letsencrypt-stg-root-x2.pem +15 -0
- cert_utils/letsencrypt-certs/root-x2-by-x1.pem +26 -0
- cert_utils/letsencrypt-certs/root-ye-by-x2.pem +17 -0
- cert_utils/letsencrypt-certs/root-ye.pem +12 -0
- cert_utils/letsencrypt-certs/root-yr-by-x1.pem +34 -0
- cert_utils/letsencrypt-certs/root-yr.pem +30 -0
- cert_utils/letsencrypt_info/__init__.py +6 -0
- cert_utils/letsencrypt_info/certificates.py +1074 -0
- cert_utils/letsencrypt_info/rate_limits.py +414 -0
- cert_utils/letsencrypt_info/types.py +120 -0
- cert_utils/model.py +181 -0
- cert_utils/network.py +28 -0
- cert_utils/py.typed +0 -0
- cert_utils/scripts/__init__.py +0 -0
- cert_utils/scripts/cert_info.py +332 -0
- cert_utils/utils.py +388 -0
- cert_utils-1.2.0.dist-info/METADATA +259 -0
- cert_utils-1.2.0.dist-info/RECORD +79 -0
- cert_utils-1.2.0.dist-info/WHEEL +5 -0
- cert_utils-1.2.0.dist-info/entry_points.txt +2 -0
- cert_utils-1.2.0.dist-info/licenses/LICENSE.txt +224 -0
- 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
|