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, 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) 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.10
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.10.tar.gz | 7.2 MB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uv_pkcs11-0.12.10-py3-none-manylinux_2_28_aarch64.whl | Python 3 | none | Linux glibc 2.28+ ARM64 | Details |
| uv_pkcs11-0.12.10-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.10-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 74.5 MB
Release files / uv_pkcs11-0.12.10.tar.gz
| Download URL | uv_pkcs11-0.12.10.tar.gz |
|---|---|
| Size | 7.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e367805598284be379a1551459e86653f80ddabee40003425e2af9c6b1795baa
|
|
BLAKE2b-256 checksum How to use checksums |
1a052ef0b0f6cddea80b28a92a949997732a3eadd1fa7871305c67caf3624e24
|
| 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.10-py3-none-manylinux_2_28_aarch64.whl
| Download URL | uv_pkcs11-0.12.10-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 |
eddbcdbcc7f6b2d538d4a4e442c43774b84f812fb6cb1b809cc652dbe2aa0f66
|
|
BLAKE2b-256 checksum How to use checksums |
0c97deaedab8a9cffb9c8fcee717de7d6bfcc71484d7f9b0a18977c2d29b1d50
|
| 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.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | uv_pkcs11-0.12.10-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 |
33a964dfa45de21e2edcaba4123e7e2bfc5dfb82452ed4f3268e92a0873e6deb
|
|
BLAKE2b-256 checksum How to use checksums |
b98bf6b9486f142ae77c4d0a465068a75a3e406dc4a9ef709c4ffbeafd9b763f
|
| 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.10-py3-none-macosx_11_0_arm64.whl
| Download URL | uv_pkcs11-0.12.10-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 |
8a52dd9e493581e39df3ed6d4a2b7bdd23c9fe6c72976574db692d0bb5c777d9
|
|
BLAKE2b-256 checksum How to use checksums |
07d8713ca9575b1481789e75a24d725384d70b9749d4ffb0000d45f9c9de4bc7
|
| 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}
|