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
cert_utils/convert.py ADDED
@@ -0,0 +1,307 @@
1
+ # stdlib
2
+ import base64
3
+ import binascii
4
+ import json
5
+ import logging
6
+ import subprocess
7
+ import textwrap
8
+ from typing import List
9
+ from typing import TYPE_CHECKING
10
+
11
+ # pypi
12
+ import psutil
13
+
14
+ # locals
15
+ from . import conditionals
16
+ from .core import check_openssl_version
17
+ from .core import openssl_path
18
+ from .core import openssl_version
19
+ from .core import validate_key
20
+ from .errors import OpenSslError_InvalidCertificate
21
+ from .utils import cleanup_pem_text
22
+ from .utils import new_der_tempfile
23
+ from .utils import new_pem_tempfile
24
+ from .utils import split_pem_chain
25
+
26
+ # ==============================================================================
27
+
28
+ log = logging.getLogger("cert_utils")
29
+ log.setLevel(logging.INFO)
30
+
31
+ # ------------------------------------------------------------------------------
32
+
33
+
34
+ def convert_der_to_pem(der_data: bytes) -> str:
35
+ """
36
+ :param der_data: DER encoded string
37
+ :type der_data: str
38
+ :returns: PEM encoded version of the DER Certificate
39
+ :rtype: str
40
+ """
41
+ # PEM is just a b64 encoded DER Certificate with the header/footer
42
+ as_pem = """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format(
43
+ "\n".join(textwrap.wrap(base64.b64encode(der_data).decode("utf8"), 64))
44
+ )
45
+ return as_pem
46
+
47
+
48
+ def convert_der_to_pem__csr(der_data: bytes) -> str:
49
+ """
50
+ :param der_data: CSR in DER encoding
51
+ :type der_data: str
52
+ :returns: PEM encoded version of the DER encoded CSR
53
+ :rtype: str
54
+ """
55
+ # PEM is just a b64 encoded DER CertificateRequest with the header/footer
56
+ as_pem = """-----BEGIN CERTIFICATE REQUEST-----\n{0}\n-----END CERTIFICATE REQUEST-----\n""".format(
57
+ "\n".join(textwrap.wrap(base64.b64encode(der_data).decode("utf8"), 64))
58
+ )
59
+ return as_pem
60
+
61
+
62
+ def convert_der_to_pem__rsakey(der_data: bytes) -> str:
63
+ """
64
+ :param der_data: RSA Key in DER encoding
65
+ :type der_data: str
66
+ :returns: PEM encoded version of the RSA Key
67
+ :rtype: str
68
+
69
+ The OpenSSL Equivalent / Fallback is::
70
+
71
+ openssl rsa -in {FILEPATH} -inform der -outform pem
72
+ """
73
+ # PEM is just a b64 encoded DER RSA KEY with the header/footer
74
+ as_pem = """-----BEGIN RSA PRIVATE KEY-----\n{0}\n-----END RSA PRIVATE KEY-----\n""".format(
75
+ "\n".join(textwrap.wrap(base64.b64encode(der_data).decode("utf8"), 64))
76
+ )
77
+ return as_pem
78
+
79
+
80
+ def convert_pem_to_der(pem_data: str) -> bytes:
81
+ """
82
+ :param pem_data: PEM encoded data
83
+ :type pem_data: str
84
+ :returns: DER encoded version of the PEM data
85
+ :rtype: str
86
+
87
+ The OpenSSL Equivalent / Fallback is::
88
+
89
+ openssl req -in {FILEPATH} -outform DER
90
+
91
+ The RFC requires the PEM header/footer to start/end with 5 dashes
92
+ This function is a bit lazy and does not check that.
93
+ """
94
+ # PEM is just a b64 encoded DER data with the appropiate header/footer
95
+ lines = [_l.strip() for _l in pem_data.strip().split("\n")]
96
+ # remove the BEGIN CERT
97
+ if (
98
+ ("BEGIN CERTIFICATE" in lines[0])
99
+ or ("BEGIN RSA PRIVATE KEY" in lines[0])
100
+ or ("BEGIN PRIVATE KEY" in lines[0])
101
+ or ("BEGIN CERTIFICATE REQUEST" in lines[0])
102
+ ):
103
+ lines = lines[1:]
104
+ if (
105
+ ("END CERTIFICATE" in lines[-1])
106
+ or ("END RSA PRIVATE KEY" in lines[-1])
107
+ or ("END PRIVATE KEY" in lines[-1])
108
+ or ("END CERTIFICATE REQUEST" in lines[-1])
109
+ ):
110
+ lines = lines[:-1]
111
+ stringed = "".join(lines)
112
+ result = base64.b64decode(stringed)
113
+ return result
114
+
115
+
116
+ def convert_pkcs7_to_pems(pkcs7_data: bytes) -> List[str]:
117
+ """
118
+ :param pkcs7_data: pkcs7 encoded Certifcate Chain
119
+ :type pem_data: str
120
+ :returns: list of PEM encoded Certificates in the pkcs7_data
121
+ :rtype: list
122
+
123
+ The OpenSSL Equivalent / Fallback is::
124
+
125
+ openssl pkcs7 -inform DER -in {FILEPATH} -print_certs -outform PEM
126
+ """
127
+ # TODO: accept a pkcs7 filepath; FallbackError_FilepathRequired
128
+ log.info("convert_pkcs7_to_pems >")
129
+ if conditionals.cryptography:
130
+ if TYPE_CHECKING:
131
+ assert conditionals.crypto_pkcs7 is not None
132
+ assert conditionals.crypto_serialization is not None
133
+ certs_loaded = conditionals.crypto_pkcs7.load_der_pkcs7_certificates(pkcs7_data)
134
+ certs_bytes = [
135
+ cert.public_bytes(conditionals.crypto_serialization.Encoding.PEM)
136
+ for cert in certs_loaded
137
+ ]
138
+ certs_string = [cert.decode("utf8") for cert in certs_bytes]
139
+ certs_string = [cleanup_pem_text(cert) for cert in certs_string]
140
+ return certs_string
141
+
142
+ log.debug(".convert_pkcs7_to_pems > openssl fallback")
143
+ if openssl_version is None:
144
+ check_openssl_version()
145
+
146
+ _tmpfile_der = new_der_tempfile(pkcs7_data)
147
+ try:
148
+ cert_der_filepath = _tmpfile_der.name
149
+ with psutil.Popen(
150
+ [
151
+ openssl_path,
152
+ "pkcs7",
153
+ "-inform",
154
+ "DER",
155
+ "-in",
156
+ cert_der_filepath,
157
+ "-print_certs",
158
+ "-outform",
159
+ "PEM",
160
+ ],
161
+ stdout=subprocess.PIPE,
162
+ stderr=subprocess.PIPE,
163
+ ) as proc:
164
+ data_bytes, err = proc.communicate()
165
+ if not data_bytes:
166
+ raise OpenSslError_InvalidCertificate(err)
167
+ data_str = data_bytes.decode()
168
+ # OpenSSL might return extra info
169
+ # for example: "subject=/O=Digital Signature Trust Co./CN=DST Root CA X3\nissuer=/O=Digital Signature Trust Co./CN=DST Root CA X3\n-----BEGIN CERTIFICATE---[...]"
170
+ # split_pem_chain works perfectly with this payload!
171
+ certs = split_pem_chain(data_str)
172
+ return certs
173
+ except Exception as exc: # noqa: F841
174
+ raise
175
+ finally:
176
+ _tmpfile_der.close()
177
+
178
+
179
+ def convert_jwk_to_ans1(pkey_jsons: str) -> str:
180
+ """
181
+ input is a json string
182
+
183
+ adapted from https://github.com/JonLundy
184
+ who shared this gist under the MIT license:
185
+ https://gist.github.com/JonLundy/f25c99ee0770e19dc595
186
+
187
+ :param pkey_jsons: JWK Key
188
+ :type pkey_jsons: str
189
+ :returns: Key in ANS1 Format
190
+ :rtype: str
191
+ """
192
+ pkey = json.loads(pkey_jsons)
193
+
194
+ def enc(data_bytes: bytes) -> str:
195
+ missing_padding = 4 - len(data_bytes) % 4
196
+ if missing_padding:
197
+ data_bytes += b"=" * missing_padding
198
+ data_bytes = binascii.hexlify(base64.b64decode(data_bytes, b"-_")).upper()
199
+ data_str = data_bytes.decode("utf8")
200
+ return "0x" + data_str
201
+
202
+ for k, v in list(pkey.items()):
203
+ if k == "kty":
204
+ continue
205
+ pkey[k] = enc(v.encode())
206
+
207
+ converted = []
208
+ converted.append("asn1=SEQUENCE:private_key\n[private_key]\nversion=INTEGER:0")
209
+ converted.append("n=INTEGER:{}".format(pkey["n"]))
210
+ converted.append("e=INTEGER:{}".format(pkey["e"]))
211
+ converted.append("d=INTEGER:{}".format(pkey["d"]))
212
+ converted.append("p=INTEGER:{}".format(pkey["p"]))
213
+ converted.append("q=INTEGER:{}".format(pkey["q"]))
214
+ converted.append("dp=INTEGER:{}".format(pkey["dp"]))
215
+ converted.append("dq=INTEGER:{}".format(pkey["dq"]))
216
+ converted.append("qi=INTEGER:{}".format(pkey["qi"]))
217
+ converted.append("") # trailing newline
218
+ converted_ = "\n".join(converted)
219
+ return converted_
220
+
221
+
222
+ def convert_lejson_to_pem(pkey_jsons: str) -> str:
223
+ """
224
+ This routine will use crypto/certbot if available.
225
+ If not, openssl is used via subprocesses
226
+
227
+ input is a json string
228
+
229
+ adapted from https://github.com/JonLundy
230
+ who shared this gist under the MIT license:
231
+ https://gist.github.com/JonLundy/f25c99ee0770e19dc595
232
+
233
+ openssl asn1parse -noout -out private_key.der -genconf <(python conv.py private_key.json)
234
+ openssl rsa -in private_key.der -inform der > private_key.pem
235
+ openssl rsa -in private_key.pem
236
+
237
+ :param pkey_jsons: LetsEncrypt JSON formatted Key
238
+ :type pkey_jsons: str
239
+ :returns: Key in PEM Encoding
240
+ :rtype: str
241
+ """
242
+ log.info("convert_lejson_to_pem >")
243
+
244
+ if conditionals.cryptography and conditionals.josepy:
245
+ if TYPE_CHECKING:
246
+ assert conditionals.crypto_serialization is not None
247
+ pkey = conditionals.josepy.JWKRSA.json_loads(pkey_jsons)
248
+ as_pem = pkey.key.private_bytes(
249
+ encoding=conditionals.crypto_serialization.Encoding.PEM,
250
+ format=conditionals.crypto_serialization.PrivateFormat.TraditionalOpenSSL,
251
+ encryption_algorithm=conditionals.crypto_serialization.NoEncryption(),
252
+ )
253
+ as_pem = as_pem.decode("utf8")
254
+ as_pem = cleanup_pem_text(as_pem)
255
+
256
+ # note: we don't need to provide key_pem_filepath because we already rely on openssl
257
+ key_technology = validate_key(key_pem=as_pem)
258
+ return as_pem
259
+
260
+ log.debug(".convert_lejson_to_pem > openssl fallback")
261
+ if openssl_version is None:
262
+ check_openssl_version()
263
+
264
+ pkey_ans1 = convert_jwk_to_ans1(pkey_jsons)
265
+ as_pem = None
266
+ tmpfiles = []
267
+ try:
268
+ tmpfile_ans1 = new_pem_tempfile(pkey_ans1)
269
+ tmpfiles.append(tmpfile_ans1)
270
+
271
+ tmpfile_der = new_pem_tempfile("")
272
+ tmpfiles.append(tmpfile_der)
273
+
274
+ with psutil.Popen(
275
+ [
276
+ openssl_path,
277
+ "asn1parse",
278
+ "-noout",
279
+ "-out",
280
+ tmpfile_der.name,
281
+ "-genconf",
282
+ tmpfile_ans1.name,
283
+ ],
284
+ stdout=subprocess.PIPE,
285
+ stderr=subprocess.PIPE,
286
+ ) as proc:
287
+ generated, err = proc.communicate()
288
+ if err:
289
+ raise ValueError(err)
290
+ # convert to pem
291
+ as_pem = convert_der_to_pem__rsakey(tmpfile_der.read())
292
+
293
+ # we need a tmpfile to validate it
294
+ tmpfile_pem = new_pem_tempfile(as_pem)
295
+ tmpfiles.append(tmpfile_pem)
296
+
297
+ # validate it
298
+ key_technology = validate_key( # noqa: F841
299
+ key_pem=as_pem, key_pem_filepath=tmpfile_pem.name
300
+ )
301
+ return as_pem
302
+
303
+ except Exception as exc: # noqa: F841
304
+ raise
305
+ finally:
306
+ for t in tmpfiles:
307
+ t.close()