A Python library for managing Public Key Infrastructures with a unified API across X.509 and Nebula certificate backends. Issue, revoke, export, and persist certificates programmatically or from the CLI.
Setting up mutual TLS or a Nebula overlay network typically means juggling openssl, nebula-cert, and ad-hoc scripts. pki-kit replaces all of that with a single, typed Python API:
- Dual backend -- X.509 and Nebula certificates through one interface
- Pluggable storage -- in-memory (tests/CI) or filesystem with optional SOPS encryption for private keys
- Idempotent operations -- re-issuing the same request + serial returns the existing certificate
- Full lifecycle -- issue, revoke, filter, export (PEM, DER, PKCS#12)
- Type-safe -- Pydantic models, strict mypy, generics throughout
- CLI included --
pki-kitcommand for scripting and quick operations
pip install pki-kit
# with CLI support
pip install pki-kit[cli]Requires Python 3.12+.
For Nebula backend: install nebula-cert and make sure it's on your PATH.
from datetime import UTC, datetime, timedelta
from pki import (
ExtendedKeyUsage,
KeyAlgorithm,
KeyUsage,
Subject,
X509CertificateRequest,
X509Pki,
)
pki = X509Pki()
now = datetime.now(tz=UTC)
# Self-signed CA
ca = pki.issue_certificate(
X509CertificateRequest(
subject=Subject(common_name="My Root CA"),
not_before=now,
not_after=now + timedelta(days=3650),
key_algorithm=KeyAlgorithm.EC_P256,
is_ca=True,
key_usages=[KeyUsage.CERT_SIGN, KeyUsage.CRL_SIGN],
)
)
# Leaf certificate signed by the CA
leaf = pki.issue_certificate(
X509CertificateRequest(
subject=Subject(common_name="api.example.com"),
not_before=now,
not_after=now + timedelta(days=365),
key_algorithm=KeyAlgorithm.EC_P256,
san_dns_names=["api.example.com", "*.api.example.com"],
extended_key_usages=[ExtendedKeyUsage.SERVER_AUTH],
),
ca_serial_number=ca.serial_number,
)
# Export PEM
cert_pem = pki.export_certificate_pem(leaf.serial_number)
key_pem = pki.export_private_key_pem(leaf.serial_number)
# Or grab a PKCS#12 bundle
p12 = pki.export_pkcs12_bytes(leaf.serial_number, password=b"changeit")from datetime import timedelta
from pki import KeyAlgorithm, NebulaCertificateRequest, NebulaPki, Subject
pki = NebulaPki()
ca = pki.issue_certificate(
NebulaCertificateRequest(
subject=Subject(common_name="Nebula CA"),
duration=timedelta(days=3650),
is_ca=True,
key_algorithm=KeyAlgorithm.ED25519,
)
)
host = pki.issue_certificate(
NebulaCertificateRequest(
subject=Subject(common_name="lighthouse-1"),
duration=timedelta(days=365),
ip="10.0.0.1/24",
groups=["lighthouses", "servers"],
key_algorithm=KeyAlgorithm.ED25519,
),
ca_serial_number=ca.serial_number,
)from pathlib import Path
from pki import X509Pki
from pki.stores import (
FilesystemCertificateStore,
FilesystemKeyStore,
FilesystemStateStore,
SoftwareCryptoProvider,
)
base = Path("./my-pki")
name = "production"
state_store = FilesystemStateStore(base_dir=base, pki_name=name)
cert_store = FilesystemCertificateStore(base_dir=base, pki_name=name)
key_store = FilesystemKeyStore(base_dir=base, pki_name=name) # SOPS-encrypted
pki = X509Pki(
pki_state=state_store.load(),
certificate_store=cert_store,
crypto_provider=SoftwareCryptoProvider(key_store=key_store),
)
# ... issue certificates ...
# Persist state
state_store.save(pki.export_state())# X.509
pki-kit x509 issue-ca --common-name "My CA" --days 3650 --algorithm ec-p256
pki-kit x509 issue --common-name "server.local" --days 365 --san-dns "server.local"
pki-kit x509 list
pki-kit x509 export-cert <serial>
pki-kit x509 revoke <serial> --reason key-compromise
# Nebula
pki-kit nebula issue-ca --common-name "Nebula CA" --days 3650
pki-kit nebula issue --common-name "host1" --ip "10.0.0.1/24" --groups web,servers
pki-kit nebula listSet defaults via environment variables:
| Variable | Description |
|---|---|
PKI_KIT_BASE_PATH |
Root directory for filesystem stores |
PKI_KIT_PKI_NAME |
PKI instance name (default: default) |
PKI_KIT_SOPS_ARGS |
Extra arguments passed to SOPS |
PKI_KIT_OUTPUT_FORMAT |
Output format: TABLE, JSON, YAML |
| Algorithm | Backend |
|---|---|
RSA_2048 |
X.509 |
RSA_4096 |
X.509 |
EC_P256 |
X.509, Nebula |
EC_P384 |
X.509 |
ED25519 |
X.509, Nebula |
Pki (abstract)
├── X509Pki ─── CryptoProvider ─── KeyStore
└── NebulaPki ├── InMemoryKeyStore
└── FilesystemKeyStore (SOPS)
CertificateStore (generic)
├── InMemoryCertificateStore
└── FilesystemCertificateStore (YAML)
StateStore
├── InMemoryStateStore
└── FilesystemStateStore (YAML)
All stores default to in-memory implementations -- swap in filesystem stores when you need persistence, with no changes to your certificate logic.
uv sync --all-extras
uv run pytest
uv run mypy src
uv run ruff check src testsSee LICENSE for details.