x509kit 0.3.1__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 (37) hide show
  1. x509kit-0.3.1/CHANGELOG.md +120 -0
  2. x509kit-0.3.1/LICENSE +21 -0
  3. x509kit-0.3.1/MANIFEST.in +13 -0
  4. x509kit-0.3.1/PKG-INFO +304 -0
  5. x509kit-0.3.1/README.md +268 -0
  6. x509kit-0.3.1/SECURITY.md +158 -0
  7. x509kit-0.3.1/check_dispatcher.py +141 -0
  8. x509kit-0.3.1/check_docs.py +256 -0
  9. x509kit-0.3.1/check_no_hidden.py +124 -0
  10. x509kit-0.3.1/compat_test.py +176 -0
  11. x509kit-0.3.1/examples/01_root_ca.py +29 -0
  12. x509kit-0.3.1/examples/02_issue_from_csr.py +42 -0
  13. x509kit-0.3.1/examples/03_self_signed_server.py +63 -0
  14. x509kit-0.3.1/examples/04_mtls_server.py +102 -0
  15. x509kit-0.3.1/examples/05_crl_revocation.py +50 -0
  16. x509kit-0.3.1/examples/06_ocsp_responder.py +48 -0
  17. x509kit-0.3.1/examples/07_pkcs12_export.py +49 -0
  18. x509kit-0.3.1/examples/08_parse_real_cert.py +42 -0
  19. x509kit-0.3.1/examples/09_encrypted_key.py +37 -0
  20. x509kit-0.3.1/examples/10_verify_chain.py +47 -0
  21. x509kit-0.3.1/examples/11_name_canonicalization.py +27 -0
  22. x509kit-0.3.1/examples/12_extensions.py +40 -0
  23. x509kit-0.3.1/examples/README.md +37 -0
  24. x509kit-0.3.1/flask/test_live_ca.py +407 -0
  25. x509kit-0.3.1/flask/test_mtls.py +362 -0
  26. x509kit-0.3.1/fuzz_x509.py +298 -0
  27. x509kit-0.3.1/pyproject.toml +78 -0
  28. x509kit-0.3.1/setup.cfg +4 -0
  29. x509kit-0.3.1/src/x509kit/__init__.py +338 -0
  30. x509kit-0.3.1/src/x509kit/py.typed +0 -0
  31. x509kit-0.3.1/src/x509kit/x509.py +6383 -0
  32. x509kit-0.3.1/src/x509kit.egg-info/PKG-INFO +304 -0
  33. x509kit-0.3.1/src/x509kit.egg-info/SOURCES.txt +35 -0
  34. x509kit-0.3.1/src/x509kit.egg-info/dependency_links.txt +1 -0
  35. x509kit-0.3.1/src/x509kit.egg-info/requires.txt +6 -0
  36. x509kit-0.3.1/src/x509kit.egg-info/top_level.txt +1 -0
  37. x509kit-0.3.1/test_all.py +1025 -0
@@ -0,0 +1,120 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project.
4
+ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+ Versions follow [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Planned
10
+ - Full type annotations on public API
11
+ - `Name.from_string("CN=x, O=y, C=XX")` parser
12
+ - `Cert.from_url("https://...")` convenience
13
+ - `X25519Keys` for ECDH
14
+ - ACME (RFC 8555) compatibility
15
+ - Coverage reporting in CI
16
+
17
+ ## [0.3.0] - 2026-10-10
18
+
19
+ First public release.
20
+
21
+ ### Added
22
+
23
+ **Cryptography:**
24
+ - RSA PKCS#1 v1.5 + RSASSA-PSS with blinding and CRT
25
+ - Ed25519 (RFC 8032)
26
+ - ECDSA (P-256, P-384, P-521, secp256k1) with RFC 6979
27
+ - AES-256 (FIPS 197) and AES-256-GCM (NIST SP 800-38D), pure Python
28
+ - PKCS#8 EncryptedPrivateKeyInfo (PBES2 + PBKDF2 + AES-256-CBC/GCM)
29
+
30
+ **X.509 v3:**
31
+ - Full DER parser and encoder (strict, minimal-length, depth-limited)
32
+ - PEM codec
33
+ - All standard extensions: SAN, BasicConstraints, KeyUsage,
34
+ Extended Key Usage, Name Constraints (with intersection), AKI/SKI,
35
+ CRL Distribution Points, AIA
36
+ - RFC 4518 DN canonicalization
37
+ - RFC 6125 hostname validation
38
+
39
+ **Protocols:**
40
+ - PKCS#10 CertificationRequest (create, parse, verify)
41
+ - CRL (RFC 5280): builder, parser, verification
42
+ - OCSP (RFC 6960): request, response, nonce
43
+ - PKCS#12 (RFC 7292): export, import, MAC verification
44
+
45
+ **API:**
46
+ - Fluent builders: `CertificateBuilder`, `CSRBuilder`, `CRLBuilder`,
47
+ `OCSPResponseBuilder`, `PKCS12Builder`
48
+ - `X509Error` with fix hints, `X509Warning` for non-fatal issues
49
+ - Crypto dispatcher: single point for all key-type logic
50
+ - SAN convenience accessors on `Cert` and `CSR`: `dns_names`,
51
+ `ip_addresses`, `emails`, `uris`
52
+ - `Cert.crl_distribution_points` reader
53
+ - `__eq__` / `__hash__` for `Name`, `Cert`, `RSAKeys`, `Ed25519Keys`,
54
+ `ECDSAKeys`, `CertBundle`, `CRL`, `CSR`
55
+
56
+ **Safety:**
57
+ - DoS limits: `_MAX_DER_SIZE = 512 KB`, `_MAX_PEM_SIZE = 1 MB`,
58
+ `_MAX_DEPTH = 32`
59
+ - `_safe_parse` decorator wraps all parser constructors; malformed
60
+ input raises `X509ParseError` instead of leaking `ValueError`,
61
+ `TypeError`, `IndexError`
62
+ - `check_no_hidden.py` — AST-based proof of zero third-party imports
63
+
64
+ **Tests and tools:**
65
+ - `test_all.py` — ~210 test cases
66
+ - `fuzz_x509.py` — random input fuzzing
67
+ - `stress_x509kit.py` — heavy stress test (parser fuzz, mutation,
68
+ crypto ops, GC churn)
69
+ - `compat_test.py` — cross-verify with `cryptography` (12/12)
70
+ - `test_live_ca.py` — full PKI cycle through OpenSSL CLI
71
+ - `examples/` — 12 runnable example scripts
72
+ - `check_docs.py`, `check_dispatcher.py`
73
+
74
+ **CI:**
75
+ - GitHub Actions: tests on Python 3.9–3.13
76
+ - Doc and dispatcher checks
77
+
78
+ ### Cross-verified against
79
+ - `cryptography` (RSA, Ed25519, ECDSA, X.509 parsing)
80
+ - OpenSSL 3.6.5 CLI (`verify`, `s_client`, `crl_check`, `pkey`,
81
+ `pkcs12`)
82
+ - RFC 8032 Ed25519 test vectors (byte-exact)
83
+ - RFC 6979 ECDSA test vectors (byte-exact)
84
+ - FIPS 197 AES-256 test vector
85
+ - NIST SP 800-38D GCM test vectors
86
+
87
+ ### Known limitations
88
+ - Not constant-time (see `SECURITY.md`)
89
+ - No formal audit
90
+ - No production hardening
91
+ - Not for adversarial environments
92
+
93
+ ## [0.2.0] - development
94
+
95
+ ### Added
96
+ - Ed25519 keys
97
+ - ECDSA keys (P-256, P-384, P-521, secp256k1) with RFC 6979
98
+ - RSA blinding and CRT
99
+ - AES-256 (FIPS 197) and AES-256-GCM (NIST SP 800-38D)
100
+ - PKCS#8 EncryptedPrivateKeyInfo
101
+ - X.509 v3: SAN, BasicConstraints, KeyUsage, EKU, AKI/SKI
102
+ - CRL (RFC 5280)
103
+ - Fuzzing tests
104
+ - Live CA test with OpenSSL CLI
105
+ - mTLS test with PKCS#12
106
+
107
+ ### Changed
108
+ - RSA minimum key size raised to 2048 bits
109
+ - `weak_hash` and `weak` flags added
110
+
111
+ ## [0.1.0] - initial
112
+
113
+ ### Added
114
+ - ASN.1 DER parser and encoder
115
+ - PEM codec
116
+ - X.509 v3 parsing and verification
117
+ - PKCS#10 CSR
118
+ - Basic chain validation with path_len
119
+ - RFC 6125 hostname validation
120
+ - Mini HTTP CA server
x509kit-0.3.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ include SECURITY.md
5
+ recursive-include examples *.py *.md
6
+ recursive-include flask *.py
7
+ include test_all.py
8
+ include compat_test.py
9
+ include check_docs.py
10
+ include check_dispatcher.py
11
+ include check_no_hidden.py
12
+ include fuzz_x509.py
13
+ include stress_x509kit.py
x509kit-0.3.1/PKG-INFO ADDED
@@ -0,0 +1,304 @@
1
+ Metadata-Version: 2.4
2
+ Name: x509kit
3
+ Version: 0.3.1
4
+ Summary: Pure-Python X.509 / PKCS#10 / CRL / OCSP / PKCS#12 toolkit — stdlib only
5
+ Author-email: ArtProsto <artemkamirn@mail.ru>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/artem122py/x509kit
8
+ Project-URL: Documentation, https://github.com/artem122py/x509kit#readme
9
+ Project-URL: Source, https://github.com/artem122py/x509kit
10
+ Project-URL: Issues, https://github.com/artem122py/x509kit/issues
11
+ Project-URL: Changelog, https://github.com/artem122py/x509kit/blob/main/CHANGELOG.md
12
+ Keywords: x509,certificate,pki,csr,crl,ocsp,pkcs12,asn1,der,pem,rsa,ed25519,ecdsa,tls,mtls,cryptography,stdlib
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Education
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Security :: Cryptography
25
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7; extra == "dev"
32
+ Requires-Dist: build; extra == "dev"
33
+ Requires-Dist: twine; extra == "dev"
34
+ Requires-Dist: ruff; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # x509kit
38
+
39
+ ![tests](https://github.com/artem122py/x509kit/actions/workflows/tests.yml/badge.svg)
40
+ ![python](https://img.shields.io/badge/python-3.9+-blue)
41
+ ![license](https://img.shields.io/badge/license-MIT-green)
42
+ ![deps](https://img.shields.io/badge/third--party%20deps-0-success)
43
+
44
+
45
+ Pure-Python X.509 / PKCS#10 / CRL / OCSP / PKCS#12 toolkit.
46
+ **Standard library only.**
47
+
48
+ No third-party packages. No C extensions. No vendored code.
49
+
50
+ ## Zero third-party dependencies
51
+
52
+ `x509kit` uses only the Python standard library. No `cryptography`,
53
+ no `pyOpenSSL`, no `asn1crypto`, no `pycryptodome`, no C extensions.
54
+
55
+ $ pip show x509kit | grep "^Requires"
56
+ Requires:
57
+
58
+ $ grep -rhE "^\s*(import|from) " src/x509kit/ \
59
+ | awk '{print $2}' | cut -d. -f1 | sort -u
60
+ base64
61
+ datetime
62
+ hashlib
63
+ hmac
64
+ math
65
+ os
66
+ re
67
+ secrets
68
+ socket
69
+ unicodedata
70
+ warnings
71
+
72
+ All of these are in the Python standard library. Nothing else.
73
+
74
+ ### Why stdlib only
75
+
76
+ - **No transitive CVEs.** Fewer packages — fewer places for something
77
+ to go wrong.
78
+ - **Embedded / restricted environments** where you cannot install
79
+ third-party packages.
80
+ - **Air-gapped systems** — drop a single package and run.
81
+ - **Readable.** The whole implementation is in one place.
82
+ - **Auditable.** The attack surface is exactly one package.
83
+
84
+ ### What is implemented from scratch
85
+
86
+ Every protocol and primitive is our own Python code:
87
+
88
+ - ASN.1 DER parser and encoder (strict, minimal-length)
89
+ - RSA PKCS#1 v1.5 and RSASSA-PSS (with blinding and CRT)
90
+ - Ed25519 (RFC 8032)
91
+ - ECDSA (RFC 6979 deterministic nonces)
92
+ - AES-256 block cipher (FIPS 197) and GCM mode (NIST SP 800-38D)
93
+ - PBKDF2 and PKCS#12 KDF
94
+ - Every X.509 v3, PKCS#10, PKCS#12, CRL, OCSP structure
95
+ - RFC 4518 DN canonicalization
96
+
97
+ Hashing (`hashlib`), MAC (`hmac`), and randomness (`secrets`) come from
98
+ the standard library — writing a custom SHA-256 in Python would be
99
+ slower and less tested than what the interpreter already ships.
100
+
101
+ `cryptography` and the `openssl` binary appear only in tests, as
102
+ independent verifiers of our output.
103
+
104
+ ## Verify it is really stdlib-only
105
+
106
+ Do not grep for the word "cryptography" — it appears in documentation
107
+ comments saying "we do not use it". Instead, run this:
108
+
109
+ python check_no_hidden.py
110
+
111
+ This script uses Python's `ast` module to extract every import statement
112
+ from every `.py` file under `src/`. It compares the result against
113
+ `sys.stdlib_module_names` and a small blacklist of crypto libraries.
114
+
115
+ Expected output ends with:
116
+
117
+ RESULT: OK - stdlib only
118
+
119
+ If it prints anything else, that is a bug — please open an issue.
120
+
121
+ ## Verify in 30 seconds
122
+
123
+ Not asking you to trust me — asking you to run this:
124
+
125
+ git clone https://github.com/artem122py/x509kit
126
+ cd x509kit
127
+ pip install -e .
128
+ python flask/test_live_ca.py
129
+
130
+ In one go, this script:
131
+
132
+ - Generates Root CA + Intermediate in memory (nothing on disk)
133
+ - Issues a server leaf certificate for a Flask server
134
+ - Starts Flask on HTTPS with the leaf + intermediate chain
135
+ - Calls the real OpenSSL CLI to generate a key and a CSR
136
+ - POSTs the CSR to the server, gets a leaf back
137
+ - Runs `openssl verify -CAfile root -untrusted inter leaf.pem`
138
+ → expected output: `leaf.pem: OK`
139
+ - Revokes the leaf via `/revoke`
140
+ - Runs `openssl verify -CRLfile crl.pem -crl_check`
141
+ → expected output: `error 23 at 0 depth lookup: certificate revoked`
142
+
143
+ The verification step is done by OpenSSL 3.x, called via subprocess.
144
+ It is not our own verify() method. If our DER encoding were wrong,
145
+ OpenSSL would reject it — and it doesn't.
146
+
147
+ ## Cross-verify matrix
148
+
149
+ The library is not self-tested. It is tested against external
150
+ implementations, in both directions:
151
+
152
+ | Component | Independent verifier | Result |
153
+ |-----------|---------------------|--------|
154
+ | RSA (PKCS#1 v1.5, PSS) | cryptography + openssl pkeyutl | both ways |
155
+ | Ed25519 | RFC 8032 test vectors (byte-exact), cryptography | both ways |
156
+ | ECDSA (P-256/384/521/secp256k1) | RFC 6979 test vectors (byte-exact), cryptography | both ways |
157
+ | AES-256-CBC | FIPS 197 test vector | byte-exact |
158
+ | AES-256-GCM | NIST SP 800-38D test vectors | byte-exact |
159
+ | X.509 v3 certificates | openssl x509 -text, openssl verify | OK |
160
+ | Chains (root → intermediate → leaf) | Python ssl TLS handshake | OK |
161
+ | CRL | openssl crl -text, openssl verify -crl_check | error 23 |
162
+ | PKCS#12 (.p12) | openssl pkcs12 -info -nodes | 3 blocks extracted |
163
+ | PKCS#8 EncryptedPrivateKeyInfo | openssl pkey | OK |
164
+ | mTLS | openssl s_client -cert -key | handshake OK |
165
+
166
+ Run it yourself:
167
+
168
+ python test_all.py
169
+
170
+ ## Compatibility with cryptography
171
+
172
+ Run this yourself — it verifies that x509kit and `cryptography` agree
173
+ in **both directions**, for every primitive:
174
+
175
+ python compat_test.py
176
+
177
+ Expected output:
178
+
179
+ ============================================================
180
+ x509kit ↔ cryptography
181
+ ============================================================
182
+
183
+ 1. RSA PKCS#1 v1.5
184
+ ✓ наш RSA sign → их verify
185
+ ✓ их RSA sign → наш verify
186
+
187
+ 2. Ed25519 (RFC 8032)
188
+ ✓ seed → тот же pubkey
189
+ ✓ наш Ed sign → их verify
190
+ ✓ их Ed sign → наш verify
191
+
192
+ 3. ECDSA P-256
193
+ ✓ наш EC sign → их verify
194
+ ✓ их EC sign → наш verify
195
+
196
+ 4. X.509 сертификат
197
+ ✓ наш PEM → их x509 парсер
198
+ ✓ серийник совпадает
199
+ ✓ их PEM → наш Cert парсер
200
+
201
+ 5. Полная цепочка
202
+ ✓ их SAN читает наш leaf
203
+ ✓ их CA парсится нашим CACert
204
+
205
+ ============================================================
206
+ 12/12 passed
207
+ ============================================================
208
+
209
+ This is not a self-test. Every check calls into `cryptography` (which
210
+ links against OpenSSL) and asks it to verify our output, or vice versa.
211
+ If any row fails, our implementation is wrong.
212
+
213
+ Requires: `pip install cryptography`
214
+
215
+ ## Examples
216
+
217
+ See [`examples/`](examples/) — 12 runnable scripts covering the whole
218
+ library:
219
+
220
+ - `01_root_ca.py` — self-signed CA
221
+ - `02_issue_from_csr.py` — issue leaf from CSR
222
+ - `03_self_signed_server.py` — HTTPS with self-signed cert
223
+ - `04_mtls_server.py` — mutual TLS
224
+ - `05_crl_revocation.py` — revoke and generate CRL
225
+ - `06_ocsp_responder.py` — OCSP response
226
+ - `07_pkcs12_export.py` — export to `.p12`
227
+ - `08_parse_real_cert.py` — fetch and parse a live HTTPS cert
228
+ - `09_encrypted_key.py` — encrypted PKCS#8 keys
229
+ - `10_verify_chain.py` — chain verification scenarios
230
+ - `11_name_canonicalization.py` — RFC 4518
231
+ - `12_extensions.py` — all extensions in one cert
232
+
233
+ Each is self-contained, prints what it does, and shows the OpenSSL
234
+ command to verify the result.
235
+
236
+ ## What this is
237
+
238
+ A learning project that turned into a working implementation. It exists
239
+ to answer the question: what actually happens inside an X.509
240
+ certificate — by writing it from scratch, on the standard library, and
241
+ checking the result against OpenSSL until they agree.
242
+
243
+ ## What this is not
244
+
245
+ Not a replacement for OpenSSL or cryptography in production. See
246
+ [SECURITY.md](SECURITY.md) for the full list of what is covered and
247
+ what is not. Short version: no constant-time guarantees, no formal
248
+ audit, no production hardening.
249
+
250
+ If you need production-grade crypto: use cryptography.
251
+ If you want to understand X.509 by reading Python that OpenSSL agrees
252
+ with: this is for you.
253
+
254
+ ## Install
255
+
256
+ pip install x509kit
257
+
258
+ ## Quick example
259
+
260
+ from x509kit import CertificateBuilder, Ed25519Keys, RSAKeys
261
+
262
+ ca_key = Ed25519Keys.generate()
263
+ ca = (CertificateBuilder(is_ca=True)
264
+ .add_subject({"C": "XX", "O": "MyOrg", "CN": "MyOrg Root CA"})
265
+ .add_public_key(ca_key)
266
+ .add_serial_random().add_validity(days=3650)
267
+ .add_basic_constraints(path_len=0)
268
+ .add_key_usage("keyCertSign", "cRLSign")
269
+ .self_signed().sign(ca_key))
270
+
271
+ leaf_key = RSAKeys.generate(2048)
272
+ leaf = (CertificateBuilder()
273
+ .add_subject({"C": "XX", "O": "MyOrg", "CN": "example.com"})
274
+ .add_issuer(ca).add_public_key(leaf_key)
275
+ .add_serial_random().add_validity(days=365)
276
+ .add_san_dns("example.com", "www.example.com")
277
+ .add_extended_key_usage("serverAuth")
278
+ .sign(ca_key))
279
+
280
+ assert ca.verify_chain([leaf])
281
+
282
+ ## Features
283
+
284
+ - ASN.1 DER parser and encoder (strict, minimal-length)
285
+ - PEM codec
286
+ - RSA PKCS#1 v1.5 + RSASSA-PSS (blinding + CRT)
287
+ - Ed25519 (RFC 8032), ECDSA (P-256/384/521/secp256k1, RFC 6979)
288
+ - X.509 v3: SAN, BasicConstraints, KeyUsage, Extended Key Usage,
289
+ Name Constraints (with intersection), AKI/SKI, CRL Distribution Points, AIA
290
+ - PKCS#10 CSR, CRL (RFC 5280), OCSP (RFC 6960), PKCS#12 (RFC 7292)
291
+ - PKCS#8 EncryptedPrivateKeyInfo (PBES2 + PBKDF2 + AES-256-CBC/GCM)
292
+ - RFC 4518 DN canonicalization
293
+ - Fluent builders: CertificateBuilder, CSRBuilder, CRLBuilder,
294
+ OCSPResponseBuilder, PKCS12Builder
295
+ - X509Error with fix hints, X509Warning for non-fatal issues
296
+
297
+ ## Status
298
+
299
+ Alpha (0.3.0). Educational. Not for production. See
300
+ [SECURITY.md](SECURITY.md).
301
+
302
+ ## License
303
+
304
+ MIT