uv-pkcs11
An unofficial fork of uv — the extremely fast Python package and project manager — with PKCS#11 client-certificate (mTLS) support, so uv can authenticate to package indexes with keys held in PKCS#11 providers that never expose the private key.
The fork never logs in to a token, so it works with providers whose
certificate and key are usable without a PIN: software HSMs, network HSMs
unlocked out of band, and p11-kit-proxied providers configured for loginless
use. Ordinary PIN-protected smart cards and tokens that require C_Login
before private-key use are not supported. The PKCS#11 support itself is
new (beta): tested end to end against SoftHSM, with limited real-world
provider mileage so far.
This project is not affiliated with or endorsed by Astral. The fork lives at github.com/dtrodrigues/uv-pkcs11; for everything except the PKCS#11 additions, see the upstream documentation.
Usage
Client-certificate behavior is controlled entirely by SSL_CLIENT_CERT:
-
A
pkcs11:URI (an RFC 7512 subset) selects a PKCS#11 identity:$ export SSL_CLIENT_CERT='pkcs11:?module-path=/path/to/pkcs11-module.so' $ uv pip install --index-url https://my-mtls-index.example.com/simple/ some-package
The path attributes
token,serial,id(percent-encodedCKA_ID), andobject(certificate label) narrow the match when tokens hold more than one identity, for examplepkcs11:id=%01orpkcs11:token=MyToken;type=certis accepted, other attributes are rejected. Themodule-pathquery attribute must be an absolute path, and the named module is native code loaded into the process — only point it at a module you trust. Without amodule-pathquery attribute, the p11-kit proxy (p11-kit-proxy.so;p11-kit-proxy.dylibon macOS) is loaded, picking up any module registered with p11-kit. Exactly one identity must match, otherwise uv reports an error naming the candidates. -
A file path is a PEM client certificate and key, exactly as in upstream uv.
-
Unset means no client certificate — stock uv behavior. PKCS#11 is never activated implicitly.
Notes:
- No PIN is presented and no login is performed: the certificate/key pair
must be visible in a public session (providers unlocked out of band work
as-is; PIN-protected devices requiring login will not).
pin-value/pin-sourceURI attributes are rejected. - RSA only (PKCS#1 v1.5 and PSS with SHA-256/384/512); the identity applies
only to verified HTTPS connections, never to hosts marked
--allow-insecure-host. - Hash-and-sign mechanisms (
CKM_SHA*_RSA_PKCS,CKM_SHA*_RSA_PKCS_PSS) are preferred; providers that only expose the rawCKM_RSA_PKCSandCKM_RSA_PKCS_PSSmechanisms work too, with the handshake transcript hashed by uv and the token padding and signing the digest. A provider withoutCKM_RSA_PKCS_PSScan only authenticate over TLS 1.2, since TLS 1.3 requires RSA-PSS for client certificates.CKM_RSA_X_509is not used. - Only the leaf certificate is sent; intermediates must be known to the server.
- A certificate is only considered when it can sign as a TLS client: its
keyUsage, if present, must includedigitalSignature(akeyEncipherment-only RSA key-exchange certificate is passed over), and itsextendedKeyUsage, if present, must includeclientAuth. Selecting such a certificate explicitly reports the reason;rustls-pkcs11-inspectlists it next to the certificate.
Known limitations (kept simple on purpose; both surface as TLS handshake failures rather than discovery-time errors):
- Pairing trusts the provider's
CKA_IDconvention: the certificate's public key is not compared against the private key, so a stale or mispaired certificate sharing the key'sCKA_IDis selected and fails when the server verifies the handshake signature. Re-provision the token so certificate and key match. - Signature schemes are offered based on
C_GetMechanismListalone: per-mechanismCKF_SIGNflags and key-size ranges fromC_GetMechanismInfoare not checked, and a key'sCKA_ALLOWED_MECHANISMSis not read (the cryptoki crate decodes that attribute with an out-of-bounds read), so a token that lists an RSA mechanism it cannot use with the selected key (for example a key outside the mechanism's supported size range, or one restricted to other mechanisms) fails inC_Signduring the handshake.
Installation
$ pip install uv-pkcs11
Wheels are built for Linux (x86_64 and aarch64) and macOS (Apple Silicon);
other platforms build from the sdist. The distribution installs the uv and
uvx commands and therefore must not be installed alongside the official
uv distribution in the same environment — install one or the other.
Versioning
Wheel versions mirror the upstream uv release the fork is built from (e.g.
0.12.9); a fourth component (0.12.9.1, 0.12.9.2, ...) marks a fork-side
re-release on the same upstream base. Note that PEP 440 treats 0.12.9.0 as
equal to 0.12.9, so re-releases start at .1.
uv self update support is intentionally not built in — it would replace
this fork with official uv binaries.
The binary identifies itself as the fork: uv --version and uv self version
end with a [uv-pkcs11 <fork version>] marker naming the exact fork release (the
version itself stays the upstream version so required-version checks keep
working), and uv self version --output-format json
reports "package_name": "uv-pkcs11" and "fork_version", so you can confirm which build is
installed.
$ uv --version
uv 0.12.9 (1a2b3c4d5 2026-09-01 aarch64-apple-darwin) [uv-pkcs11 0.12.9.1]
Release files for uv-pkcs11 0.12.9.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| uv_pkcs11-0.12.9.1.tar.gz | 7.2 MB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uv_pkcs11-0.12.9.1-py3-none-manylinux_2_28_aarch64.whl | Python 3 | none | Linux glibc 2.28+ ARM64 | Details |
| uv_pkcs11-0.12.9.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| uv_pkcs11-0.12.9.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 74.4 MB
Release files / uv_pkcs11-0.12.9.1.tar.gz
| Download URL | uv_pkcs11-0.12.9.1.tar.gz |
|---|---|
| Size | 7.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bb4789f52d873582cad4b51ba0f29913a75082b47477709cb4a5b246156f384d
|
|
BLAKE2b-256 checksum How to use checksums |
8104ad05d27d578bf2fe40e4aeece13c431b98be0744b367aa33a462e1d0dba2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / uv_pkcs11-0.12.9.1-py3-none-manylinux_2_28_aarch64.whl
| Download URL | uv_pkcs11-0.12.9.1-py3-none-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 23.1 MB |
| Tags | Linux glibc 2.28+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
4875a2723983cd198def1a16ff9147d35c2ace5c398922263a2d3d51e344ebb2
|
|
BLAKE2b-256 checksum How to use checksums |
1cdfab1f2161e0e2398024b4847865be8d5ebd4bb89bc9bce5e87daa88a843aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / uv_pkcs11-0.12.9.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | uv_pkcs11-0.12.9.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 24.0 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
5718b8c00a5615e8f4530d0f43cb1c87d18e18e1259e45bd03f52e39e7ba0e00
|
|
BLAKE2b-256 checksum How to use checksums |
0822619dca189e194005a0a9714707370af937a274fea8bbb08be309a047228b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / uv_pkcs11-0.12.9.1-py3-none-macosx_11_0_arm64.whl
| Download URL | uv_pkcs11-0.12.9.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 20.2 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
f93d1d6ab649d37a6fc41d6b0cd7a12020cce662b7fdb51e7f4abb495a7efd69
|
|
BLAKE2b-256 checksum How to use checksums |
fe2680c51c8fcae4bb5c9e1816bff069c9ca8b743ddd1fc23c83ff46724960d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|