Skip to main content

A low-level TLS 1.3 client toolkit for Python, built on Facebook's Fizz

Project description

fizzpy

A small TLS 1.3 HTTP client for Python, built on Facebook's Fizz (C++) via pybind11.

By default it offers a post-quantum key exchange — the standardized hybrid X25519MLKEM768 group (codepoint 4588). An ordinary GET negotiates a hybrid ML-KEM handshake against servers that support it (Cloudflare, Google) and falls back to classical X25519 against those that don't.

TLS 1.3 only, HTTP/1.1 only. This is a focused toolkit / learning project, not a drop-in requests replacement. See Status.

A fizzpy ClientHello in Wireshark

A fizzpy request captured in Wireshark: the TLS 1.3 ClientHello offers the post-quantum X25519MLKEM768 group (0x11ec), and a custom marker extension (0xFE5A, see below) makes it unmistakably ours — generated by examples/pq_marker.py.

Install

pip install fizzpy

The Linux wheels are self-contained: folly, Fizz, and liboqs are statically linked into the extension, so a fresh pip install has working post-quantum TLS with no system packages to add. certifi (pulled in automatically) supplies the default trust store.

Wheels are produced by CI (manylinux_2_28, x86_64, CPython 3.10–3.14); if one isn't available for your platform yet, build from source — see BUILDING.md.

Usage

Synchronous:

import fizzpy

r = fizzpy.get("https://www.cloudflare.com")
print(r.status_code)               # 200
print(r.headers["content-type"])   # text/html; charset=UTF-8
print(r.text[:64])

# The negotiated TLS 1.3 parameters are attached to every response:
print(r.tls)
# {'version': 'TLSv1.3', 'cipher': 'TLS_AES_128_GCM_SHA256',
#  'group': 'X25519MLKEM768', 'group_code': 4588,
#  'alpn': 'http/1.1', 'sni': 'www.cloudflare.com', 'peer_cert': '...'}

Asynchronous (the same C++ core, resolved on the running event loop):

import asyncio
from fizzpy.aio import AsyncClient

async def main():
    async with AsyncClient() as client:
        r = await client.get("https://www.google.com")
        print(r.status_code, r.tls["group"], r.tls["group_code"])

asyncio.run(main())

Choosing the key exchange

The default offers [x25519_mlkem768, x25519], mirroring how Chrome and Firefox send both key shares. Override it per client:

import fizzpy
from fizzpy import NamedGroup

# Force post-quantum only (handshake fails if the server lacks ML-KEM):
pq = fizzpy.Client(groups=[NamedGroup.x25519_mlkem768])

# Classical only:
classical = fizzpy.Client(groups=[NamedGroup.x25519])

Custom ClientHello extensions

fizzpy surfaces Fizz's low-level extension hook, so you can append arbitrary extensions to the TLS 1.3 ClientHello. Each is an opaque (type, data) pair; fizzpy rejects types it manages itself (key_share, supported_versions, …) and out-of-range or duplicate types.

import fizzpy

client = fizzpy.Client(
    extensions=[fizzpy.Extension(0xFE5A, b"hello from fizzpy")],
)
r = client.get("https://blog.cloudflare.com")
print(r.tls["group"])   # still negotiates X25519MLKEM768

The extension is sent verbatim in the ClientHello — see it on the wire with examples/pq_marker.py and the screenshot above.

Certificate verification

Certificate chains are verified against the certifi CA bundle and the hostname is checked against the certificate's SAN by default. Point at a custom CA, or disable verification entirely:

fizzpy.Client(cafile="/path/to/ca.pem")   # trust a specific CA
fizzpy.Client(verify=False)               # accept any certificate (insecure)

Status

What works today:

  • TLS 1.3 handshake with classical or post-quantum (ML-KEM) key exchange
  • GET/POST/HEAD/PUT/DELETE, sync and async
  • HTTP/1.1 response framing (content-length, chunked, gzip/deflate)
  • Connection keep-alive with a per-host pool; automatic redirect following
  • Chain + hostname certificate verification, certifi default + custom CA trust

Current limitations:

  • TLS 1.3 only (a Fizz constraint) — it cannot talk to TLS 1.2-only servers
  • HTTP/1.1 only (no HTTP/2)

Building

The post-quantum handshake requires a Fizz built against liboqs. The CI wheels build the whole folly + Fizz + liboqs tree from source inside manylinux_2_28; see BUILDING.md for that recipe and for local development builds.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fizzpy-0.3.0.tar.gz (431.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fizzpy-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (5.5 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

fizzpy-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (5.5 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

fizzpy-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (5.5 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

fizzpy-0.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (5.5 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

fizzpy-0.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (5.5 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file fizzpy-0.3.0.tar.gz.

File metadata

  • Download URL: fizzpy-0.3.0.tar.gz
  • Upload date:
  • Size: 431.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fizzpy-0.3.0.tar.gz
Algorithm Hash digest
SHA256 92073ed3af79bac3a220f5c70c7bac7168e45b339de09df297cd4d0d030fef04
MD5 ff039e9e332c08a881bbe114338c1280
BLAKE2b-256 d94f1378168b55dcf961c5a4df51e3ddbc397bd22a61a5d3ceb3af61cda2ea36

See more details on using hashes here.

Provenance

The following attestation bundles were made for fizzpy-0.3.0.tar.gz:

Publisher: publish.yml on Xevion/fizz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fizzpy-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fizzpy-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 29ee199f0cb7314e82b01b73f502b686b12f0ce4367789c8db9a96ab5e47b878
MD5 ea6afaa69b8a9b6ca8b59339022573b6
BLAKE2b-256 46abc498315c4bdb3d2357bc809714bbd2fd90f7a2431b1306d831d6f2852b12

See more details on using hashes here.

Provenance

The following attestation bundles were made for fizzpy-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on Xevion/fizz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fizzpy-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fizzpy-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 842b7e1c39c6b27957ee658c7767d0c99e503497d2e52769be18c44d6d6ff022
MD5 b529e282cf80faff9446d58abd45a15f
BLAKE2b-256 472dd9aac3a55e48264425cda6dd1088a3dc1c853d363f6a1a51e38831d13be0

See more details on using hashes here.

Provenance

The following attestation bundles were made for fizzpy-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on Xevion/fizz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fizzpy-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fizzpy-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 42b0ae9f9701c9d349e4f2d87a1a63efad6ff5040ef4bbda6c969a22f18e88f9
MD5 3e8c6acbf399730439e7507af1833544
BLAKE2b-256 99b2fda8ab877ae52987d7ef6c5700df7749c6f9b28923a47249406ce7c68116

See more details on using hashes here.

Provenance

The following attestation bundles were made for fizzpy-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on Xevion/fizz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fizzpy-0.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fizzpy-0.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 140608577df4b6b7de680597e8711e95cac3c5b3ac4cb5cad29974226e7e6741
MD5 047cb09b270652c46a3462e7db5e31e1
BLAKE2b-256 c1bb39068e97b8db459a4fdd9c5b4bfd25155f66e2c4970c7a8fbd4201053651

See more details on using hashes here.

Provenance

The following attestation bundles were made for fizzpy-0.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on Xevion/fizz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fizzpy-0.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for fizzpy-0.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d98dd8a33a4e10bc62ad2495a90030dfdbe7c3b9c3317630309362a79cc81b85
MD5 f08a12624aa73815772c37821f31b311
BLAKE2b-256 dec492b85a263c251373a1d566e8622d39b9f76252a43283ffed4bde6ae4293a

See more details on using hashes here.

Provenance

The following attestation bundles were made for fizzpy-0.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on Xevion/fizz-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page