pki-core 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. pki_core-0.3.1/LICENSE +28 -0
  2. pki_core-0.3.1/PKG-INFO +358 -0
  3. pki_core-0.3.1/README.md +324 -0
  4. pki_core-0.3.1/pyproject.toml +89 -0
  5. pki_core-0.3.1/setup.cfg +4 -0
  6. pki_core-0.3.1/src/pki/core/__init__.py +36 -0
  7. pki_core-0.3.1/src/pki/core/algorithms.py +123 -0
  8. pki_core-0.3.1/src/pki/core/certificate.py +120 -0
  9. pki_core-0.3.1/src/pki/core/crl.py +374 -0
  10. pki_core-0.3.1/src/pki/core/identity.py +124 -0
  11. pki_core-0.3.1/src/pki/core/providers.py +123 -0
  12. pki_core-0.3.1/src/pki/core/revocation.py +313 -0
  13. pki_core-0.3.1/src/pki/core/selectors.py +41 -0
  14. pki_core-0.3.1/src/pki/core/trust_store.py +224 -0
  15. pki_core-0.3.1/src/pki/core/validation.py +257 -0
  16. pki_core-0.3.1/src/pki_core.egg-info/PKG-INFO +358 -0
  17. pki_core-0.3.1/src/pki_core.egg-info/SOURCES.txt +35 -0
  18. pki_core-0.3.1/src/pki_core.egg-info/dependency_links.txt +1 -0
  19. pki_core-0.3.1/src/pki_core.egg-info/requires.txt +12 -0
  20. pki_core-0.3.1/src/pki_core.egg-info/top_level.txt +1 -0
  21. pki_core-0.3.1/tests/test_algorithms.py +210 -0
  22. pki_core-0.3.1/tests/test_certificate.py +136 -0
  23. pki_core-0.3.1/tests/test_chain_validation.py +95 -0
  24. pki_core-0.3.1/tests/test_crl.py +117 -0
  25. pki_core-0.3.1/tests/test_fuzz_algorithms.py +170 -0
  26. pki_core-0.3.1/tests/test_fuzz_certificate.py +230 -0
  27. pki_core-0.3.1/tests/test_fuzz_crl.py +185 -0
  28. pki_core-0.3.1/tests/test_fuzz_identity.py +234 -0
  29. pki_core-0.3.1/tests/test_fuzz_providers.py +213 -0
  30. pki_core-0.3.1/tests/test_fuzz_revocation.py +201 -0
  31. pki_core-0.3.1/tests/test_fuzz_trust_store.py +148 -0
  32. pki_core-0.3.1/tests/test_identity.py +52 -0
  33. pki_core-0.3.1/tests/test_providers.py +88 -0
  34. pki_core-0.3.1/tests/test_revocation.py +242 -0
  35. pki_core-0.3.1/tests/test_selectors.py +50 -0
  36. pki_core-0.3.1/tests/test_trust_store.py +85 -0
  37. pki_core-0.3.1/tests/test_validation.py +43 -0
pki_core-0.3.1/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, MeV Technology Consulting, LLC
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,358 @@
1
+ Metadata-Version: 2.4
2
+ Name: pki-core
3
+ Version: 0.3.1
4
+ Summary: Generic X.509 certificate utilities: parsing, CRL caching, identity extraction, provider registry, and validation pipeline.
5
+ Author: MeV Technology Consulting, LLC
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://oss.mevtc.com
8
+ Project-URL: Repository, https://github.com/mevtc/pki-core
9
+ Project-URL: Issues, https://github.com/mevtc/pki-core/issues
10
+ Project-URL: Changelog, https://github.com/mevtc/pki-core/blob/main/CHANGELOG.md
11
+ Keywords: pki,x509,certificate,crl,identity,validation
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Security :: Cryptography
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: cryptography>=44.0
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: platformdirs>=4.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8.0; extra == "dev"
27
+ Requires-Dist: pytest-cov>=6.0; extra == "dev"
28
+ Requires-Dist: hypothesis>=6.100; extra == "dev"
29
+ Requires-Dist: ruff>=0.9.0; extra == "dev"
30
+ Requires-Dist: mypy>=1.14; extra == "dev"
31
+ Requires-Dist: bandit>=1.8; extra == "dev"
32
+ Requires-Dist: pre-commit>=4.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # pki-core
36
+
37
+ Generic X.509 certificate utilities for PKI-enabled applications.
38
+
39
+ ## Features
40
+
41
+ - **Certificate parsing** — load PEM/DER x509 certificates, extract policy OIDs, emails, SAN URIs/UUIDs, FASC-N, and fingerprints
42
+ - **Identity extraction** — parse certificate identity using a pluggable provider registry with callable CN parsers and ID selectors
43
+ - **Chain validation** — RFC 5280 certificate path validation via `verify_chain()`
44
+ - **Revocation checking** — pluggable strategies (`CRL`, `OCSP`) with configurable order and fallback
45
+ - **Algorithm enforcement** — `AlgorithmPolicy` validates key type/size and signature hash
46
+ - **Trust store management** — download, merge, and deduplicate CA bundles from provider-defined sources
47
+ - **Validation pipeline** — `validate_certificate()` composes chain validation, algorithm checking, identity extraction, expiry, and revocation into a single call
48
+ - **Provider registry** — pluggable authentication provider definitions with OID matching and heuristic detection
49
+
50
+ ## Architecture
51
+
52
+ pki-core is a PKI framework with a pluggable provider system. **Provider packs** supply ecosystem-specific definitions (OIDs, CN parsers, trust store sources) while pki-core handles the generic infrastructure.
53
+
54
+ ```
55
+ pki.core ← framework (this package)
56
+ pki.federal ← DoD CAC / Federal PIV / ECA provider pack
57
+ pki.mycorp ← your organization's provider pack
58
+ ```
59
+
60
+ All packages share the `pki` namespace via Python's implicit namespace packages (PEP 420). Each is installed independently.
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install pki-core
66
+ ```
67
+
68
+ ## Examples
69
+
70
+ ### Minimal — parse and validate a certificate
71
+
72
+ The simplest usage: load a certificate, check expiry, and extract identity.
73
+
74
+ ```python
75
+ from pki.core.certificate import load_certificate
76
+ from pki.core.validation import CertificatePolicy, ValidationStatus, validate_certificate
77
+
78
+ cert = load_certificate(pem_bytes)
79
+ result = validate_certificate(cert)
80
+
81
+ if result.status == ValidationStatus.VALID:
82
+ print(f"Valid: {result.identity.cn}")
83
+ else:
84
+ print(f"Failed: {result.status} — {result.error}")
85
+ ```
86
+
87
+ ### Full — chain validation, algorithm policy, CRL + OCSP, Federal PKI
88
+
89
+ A production configuration using pki-federal with all validation steps enabled.
90
+
91
+ ```python
92
+ from pki.core.certificate import load_certificate
93
+ from pki.core.crl import CRLConfig, load_ca_certs_from_pem
94
+ from pki.core.revocation import CRL, OCSP, RevocationPolicy
95
+ from pki.core.validation import CertificatePolicy, ValidationStatus, validate_certificate
96
+ from pki.federal import SP800_78_ALGORITHM_POLICY, default_registry
97
+
98
+ # Load the certificate
99
+ cert = load_certificate(pem_bytes)
100
+
101
+ # Load CA bundle (built from build_ca_bundle_for_providers() or downloaded)
102
+ ca_certs = load_ca_certs_from_pem(open("/etc/pki/ca-bundle.pem", "rb").read())
103
+
104
+ # Configure the full validation pipeline
105
+ policy = CertificatePolicy(
106
+ # Step 0: RFC 5280 chain validation
107
+ check_chain=True,
108
+ trust_store=ca_certs,
109
+
110
+ # Step 0b: SP 800-78 algorithm enforcement
111
+ algorithm_policy=SP800_78_ALGORITHM_POLICY,
112
+
113
+ # Step 1: Identity extraction with Federal PKI providers (CAC + PIV)
114
+ registry=default_registry(),
115
+
116
+ # Step 2: Validity period (enabled by default)
117
+ check_validity_period=True,
118
+
119
+ # Step 3: Revocation — try CRL first, fall back to OCSP
120
+ revocation=RevocationPolicy(
121
+ checks=(CRL, OCSP),
122
+ issuer_certs=ca_certs,
123
+ crl_config=CRLConfig(cache_dir="/var/cache/pki/crls", cache_ttl=3600),
124
+ strict=True,
125
+ ),
126
+ )
127
+
128
+ result = validate_certificate(cert, policy)
129
+
130
+ match result.status:
131
+ case ValidationStatus.VALID:
132
+ print(f"Identity: {result.identity.primary_id}")
133
+ print(f"Credential: {result.identity.credential_type}")
134
+ print(f"Name: {result.identity.firstname} {result.identity.lastname}")
135
+ case ValidationStatus.CHAIN_UNTRUSTED:
136
+ print(f"Untrusted chain: {result.error}")
137
+ case ValidationStatus.ALGORITHM_NONCOMPLIANT:
138
+ print(f"Algorithm rejected: {result.error}")
139
+ case ValidationStatus.EXPIRED:
140
+ print(f"Expired: {result.error}")
141
+ case ValidationStatus.REVOKED:
142
+ print(f"Revoked: {result.error}")
143
+ case _:
144
+ print(f"Error: {result.error}")
145
+ ```
146
+
147
+ ### Custom revocation strategy
148
+
149
+ Implement `RevocationCheck` to add your own revocation source:
150
+
151
+ ```python
152
+ from pki.core.revocation import CRL, OCSP, RevocationCheck, RevocationPolicy, RevocationResult
153
+
154
+ class InternalCRLDatabase(RevocationCheck):
155
+ """Check revocation against an internal database."""
156
+
157
+ def check(self, cert, policy):
158
+ serial = format(cert.serial_number, "x")
159
+ if self._is_revoked(serial):
160
+ return RevocationResult.REVOKED, f"Revoked in internal DB (serial {serial})"
161
+ return RevocationResult.GOOD, "Not revoked in internal DB"
162
+
163
+ def _is_revoked(self, serial):
164
+ ... # query your database
165
+
166
+ # Internal DB first, then CRL, then OCSP
167
+ policy = CertificatePolicy(
168
+ revocation=RevocationPolicy(checks=(InternalCRLDatabase(), CRL, OCSP)),
169
+ )
170
+ ```
171
+
172
+ ## Configuration architecture
173
+
174
+ The validation pipeline is configured through three nested dataclasses,
175
+ each owning a distinct concern:
176
+
177
+ ```
178
+ CertificatePolicy ← top-level pipeline config
179
+ ├── check_chain: bool ← chain validation on/off
180
+ ├── trust_store: list[Certificate] ← root CAs for chain validation
181
+ ├── algorithm_policy: AlgorithmPolicy ← key/hash requirements (opt-in)
182
+ ├── registry: ProviderRegistry ← identity extraction providers
183
+ │
184
+ └── revocation: RevocationPolicy ← revocation checking (or None to skip)
185
+ ├── checks: Sequence[RevocationCheck] ← ordered strategies (CRL, OCSP, custom)
186
+ ├── issuer_certs: list[Certificate] ← CAs for CRL sig verification + OCSP requests
187
+ ├── strict: bool ← fail-closed when all checks unavailable
188
+ │
189
+ └── crl_config: CRLConfig ← cache mechanics
190
+ ├── cache_dir: str ← where to store cached CRLs
191
+ ├── cache_ttl: int ← seconds before stale (triggers background refresh)
192
+ ├── max_crl_bytes: int ← reject CRLs larger than this
193
+ ├── max_acceptable_age: int ← force-refresh if older than this
194
+ └── fetch_timeout: int ← HTTP timeout
195
+ ```
196
+
197
+ **`CertificatePolicy`** owns the pipeline — which steps to run and in what order.
198
+
199
+ **`RevocationPolicy`** owns the security decisions — which strategies to try, what CA certs to use, and whether to fail open or closed. It is separate from `CertificatePolicy` so it can be shared across multiple validation calls or constructed independently.
200
+
201
+ **`CRLConfig`** owns the cache mechanics — where files are stored, how long they're fresh, size limits. Provider packs like `pki-federal` subclass it to set deployment-specific defaults (e.g., 20 MB max for DoD CRLs, 18-hour max age per FIPS 201-3).
202
+
203
+ ## Validation pipeline
204
+
205
+ `validate_certificate()` runs these steps in order, short-circuiting on first failure:
206
+
207
+ | Step | Check | Controlled by | Status on failure |
208
+ |------|-------|---------------|-------------------|
209
+ | 0 | Chain validation | `check_chain`, `trust_store` | `CHAIN_UNTRUSTED` |
210
+ | 0b | Algorithm compliance | `algorithm_policy` | `ALGORITHM_NONCOMPLIANT` |
211
+ | 1 | Identity extraction | `registry` | `ERROR` |
212
+ | 2 | Validity period | `check_validity_period` | `NOT_YET_VALID` / `EXPIRED` |
213
+ | 3 | Revocation | `revocation` | `REVOKED` / `ERROR` |
214
+
215
+ Identity is populated even on failure (when possible) so callers can log who the failed certificate belonged to.
216
+
217
+ ### Validation boundary
218
+
219
+ pki-core handles chain validation, identity extraction, algorithm enforcement, and revocation checking. The following must be handled by the TLS terminator (nginx, AWS ALB) or the application:
220
+
221
+ - **TLS challenge-response** — proof of private key possession (FIPS 201-3 §6.2.3.1 steps 3-6)
222
+ - **Certificate policy OID constraints in path building** — `cryptography`'s verifier does not support RFC 5280 §6.1.1 `initial-policy-set`. Use scoped trust stores and `get_policy_oids()` for post-validation policy checking.
223
+ - **Certificate Transparency** — SCT verification is not performed.
224
+
225
+ ## Security
226
+
227
+ ### FIPS 140 cryptographic module status
228
+
229
+ pki-core does not implement cryptographic primitives directly. All
230
+ cryptographic operations (signature verification, hash computation, CRL/OCSP
231
+ validation) are performed by the
232
+ [cryptography](https://cryptography.io/) library, which uses OpenSSL as its
233
+ backend.
234
+
235
+ **pki-core itself is not FIPS 140 validated.** FIPS 140 validation applies
236
+ to the underlying cryptographic module (OpenSSL), not to libraries that
237
+ call it. To deploy pki-core in a FIPS 140 compliant environment:
238
+
239
+ 1. Use an OpenSSL build that has a FIPS 140 validation certificate
240
+ (e.g., the OpenSSL FIPS Object Module or a vendor-validated build).
241
+ 2. Ensure the FIPS provider is active in the OpenSSL configuration
242
+ (`openssl list -providers` should show `fips`).
243
+ 3. The `cryptography` library will automatically use the FIPS provider
244
+ when OpenSSL is configured for FIPS mode.
245
+
246
+ The `AlgorithmPolicy` class (and `SP800_78_ALGORITHM_POLICY` in pki-federal)
247
+ enforces that certificates use approved algorithms (RSA 2048+, P-256/P-384,
248
+ SHA-256+), but this is an application-level check — it does not replace
249
+ FIPS 140 validation of the cryptographic module itself.
250
+
251
+ ### SBOM
252
+
253
+ CycloneDX Software Bills of Materials are generated in CI on every pipeline
254
+ run and published as artifacts. SBOMs list all direct and transitive Python
255
+ dependencies with version numbers, enabling vulnerability tracking and
256
+ supply chain risk management per OMB M-22-18.
257
+
258
+ ### NIST SP 800-53 controls
259
+
260
+ See [SP800-53-CONTROLS.md](SP800-53-CONTROLS.md) for a full mapping of
261
+ 26 security controls to implementation evidence across the pki ecosystem.
262
+
263
+ pki-core directly implements:
264
+
265
+ - **IA-2(12)** — chain validation, FASC-N/UUID extraction, algorithm enforcement
266
+ - **IA-5 / IA-5(2)** — certificate lifecycle (expiration, revocation, path validation)
267
+ - **SC-12** — trust store management, secure CRL caching
268
+ - **SC-13** — cryptographic operations via FIPS-capable OpenSSL backend
269
+ - **SC-17** — certificate validation pipeline
270
+ - **SI-10** — input validation (certificate parsing, CRL size limits, algorithm checks)
271
+ - **SA-11 / SA-11(1) / SA-11(8)** — fuzz testing, static analysis, dependency scanning
272
+ - **SR-4 / CM-8** — CycloneDX SBOMs
273
+
274
+ ### Security testing and static analysis
275
+
276
+ See [SECURITY.md](SECURITY.md) for vulnerability reporting, fuzz testing
277
+ coverage, and a full list of static analysis suppressions with
278
+ justifications.
279
+
280
+ ## Provider packs
281
+
282
+ A provider pack defines `AuthProvider` instances for a specific PKI ecosystem. Each provider specifies:
283
+
284
+ - **auth_oids** — certificate policy OIDs that identify this credential type
285
+ - **cn_parser** — callable that extracts name fields from the certificate CN
286
+ - **primary_id_selector** — callable that picks the primary identifier (EDIPI, UUID, email, etc.)
287
+ - **heuristics** — fallback rules for matching certificates without recognized OIDs
288
+ - **trust_store_sources** — URLs and formats for downloading CA bundles
289
+
290
+ ### Defining a custom provider
291
+
292
+ ```python
293
+ from pki.core.providers import AuthProvider, ProviderRegistry, HeuristicRule
294
+ from pki.core.selectors import select_email_first
295
+
296
+ def parse_company_cn(identity):
297
+ parts = identity.cn.split(" ")
298
+ identity.firstname = parts[0]
299
+ identity.lastname = parts[-1]
300
+
301
+ provider = AuthProvider(
302
+ name="ACME",
303
+ display_name="ACME Corp",
304
+ auth_oids=frozenset({"1.2.3.4.5.6"}),
305
+ cn_parser=parse_company_cn,
306
+ primary_id_selector=select_email_first,
307
+ heuristics=(HeuristicRule(field="org", pattern="acme"),),
308
+ )
309
+
310
+ registry = ProviderRegistry()
311
+ registry.register(provider)
312
+
313
+ policy = CertificatePolicy(registry=registry)
314
+ result = validate_certificate(cert, policy)
315
+ ```
316
+
317
+ ### Combining provider packs
318
+
319
+ ```python
320
+ from pki.core.providers import ProviderRegistry
321
+ from pki.core.validation import CertificatePolicy, validate_certificate
322
+ from pki.federal import CAC_PROVIDER, PIV_PROVIDER
323
+ from pki.mycorp import MYCORP_PROVIDER
324
+
325
+ registry = ProviderRegistry()
326
+ registry.register(CAC_PROVIDER)
327
+ registry.register(PIV_PROVIDER)
328
+ registry.register(MYCORP_PROVIDER)
329
+
330
+ policy = CertificatePolicy(registry=registry)
331
+ result = validate_certificate(cert, policy)
332
+ ```
333
+
334
+ ### Creating a reusable provider pack
335
+
336
+ To distribute providers as a package, follow the same layout as [pki-federal](https://github.com/mevtc/pki-federal):
337
+
338
+ ```
339
+ pki-mycorp/
340
+ ├── src/pki/mycorp/
341
+ │ ├── __init__.py # export provider instances
342
+ │ ├── oids.py # policy OID constants
343
+ │ ├── cn_parsers.py # CN parsing functions
344
+ │ ├── providers.py # AuthProvider instances and registry factories
345
+ │ └── trust_store.py # CA bundle fetchers (if applicable)
346
+ ├── pyproject.toml # depends on pki-core
347
+ └── tests/
348
+ ```
349
+
350
+ No `pki/__init__.py` — use Python's implicit namespace packages so `pki.core`, `pki.federal`, and `pki.mycorp` coexist.
351
+
352
+ ## Available provider packs
353
+
354
+ - [pki-federal](https://github.com/mevtc/pki-federal) — DoD CAC, Federal PIV, and ECA
355
+
356
+ ## License
357
+
358
+ BSD-3-Clause — see [LICENSE](LICENSE).