Skip to main content

pki-kit

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.

Why pki-kit?

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-kit command for scripting and quick operations

Installation

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.

Quick Start

X.509: Create a CA and issue a leaf certificate

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")

Nebula: Create a CA and issue host certificates

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,
)

Persistent storage with filesystem stores

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())

CLI

# 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 list

Set 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

Key Algorithms

Algorithm Backend
RSA_2048 X.509
RSA_4096 X.509
EC_P256 X.509, Nebula
EC_P384 X.509
ED25519 X.509, Nebula

Architecture

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.

Development

uv sync --all-extras
uv run pytest
uv run mypy src
uv run ruff check src tests

License

See LICENSE for details.

Metadata

Release files for pki-kit 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pki-kit 0.0.2
File Size Uploaded
pki_kit-0.0.2.tar.gz 20.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pki-kit 0.0.2
File Interpreter ABI Platform
pki_kit-0.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 54.0 kB

Release files / pki_kit-0.0.2.tar.gz

Download URL pki_kit-0.0.2.tar.gz
Size 20.7 kB
Tags Source
SHA-256 checksum
How to use checksums
b20c167ff0fdaf6d9795a7576c688ebcdba177449797118131a7d1d435abfa4c
BLAKE2b-256 checksum
How to use checksums
785744f13bb0d494d4b1a1e6eedfda29f9b6b4000b86e4aecf7cf23b1870ddd5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.16

Release files / pki_kit-0.0.2-py3-none-any.whl

Download URL pki_kit-0.0.2-py3-none-any.whl
Size 33.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5cb32cd43b5e29eec880b45842dc198e3b40e27d79f6157ef70cc047d098126b
BLAKE2b-256 checksum
How to use checksums
a9613087cdbea352b5cbb4528a1595f3a2a120fcd63f43aa6a6a11fe305053c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.8.16

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page