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 and is published on PyPI as 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;uv-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.
Diagnosing a token
The wheel installs uv-pkcs11-inspect next to uv and uvx. It lists the
tokens, certificates, and private keys a module exposes without login and
applies the same selection rules as uv, so its verdict is what uv will do
with the same URI. Pass the pkcs11: URI you intend to put in
SSL_CLIENT_CERT, or just a module path; with no argument, the p11-kit proxy
is inspected. The exit status is 0 when exactly one identity would be
selected.
$ uv-pkcs11-inspect 'pkcs11:?module-path=/path/to/pkcs11-module.so'
Module: /path/to/pkcs11-module.so
Token `MyToken` (slot 1)
Signing: RSA_PSS_SHA512, RSA_PSS_SHA384, RSA_PSS_SHA256, RSA_PKCS1_SHA512, ...
Certificates: 1
CKA_ID 3f9a… label `My Certificate`
Private keys: 1
CKA_ID 3f9a… label `My Key` RSA_PSS_SHA512, RSA_PSS_SHA384, ...
OK: exactly one usable identity across all tokens: token `MyToken`, CKA_ID 3f9a….
With several identities it prints AMBIGUOUS and their CKA_IDs, so you
can add an id=, token=, or object= attribute to the URI; with none it
prints NOT USABLE and a hint (for example that nothing is visible without
login). uv-pkcs11-inspect --version reports the fork build it came from.
Installation
$ pip install uv-pkcs11
Releases are published to PyPI at
pypi.org/project/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.13
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.13.tar.gz | 7.2 MB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uv_pkcs11-0.12.13-py3-none-manylinux_2_28_aarch64.whl | Python 3 | none | Linux glibc 2.28+ ARM64 | Details |
| uv_pkcs11-0.12.13-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.13-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 75.8 MB
Release files / uv_pkcs11-0.12.13.tar.gz
| Download URL | uv_pkcs11-0.12.13.tar.gz |
|---|---|
| Size | 7.2 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a829978cf74495858fe1f61e7a0ce845de9cc6f3ee4c9bd4650374f19a91da20
|
|
BLAKE2b-256 checksum How to use checksums |
58533791dd5923f90ae07e3ced8d86c4a29b34d89a378c9b450407a7a5df6964
|
| 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.13-py3-none-manylinux_2_28_aarch64.whl
| Download URL | uv_pkcs11-0.12.13-py3-none-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 23.4 MB |
| Tags | Linux glibc 2.28+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
359691f0a571670a065fffda67c674a91ff51a914c2a39b9a6e1435fd38df15f
|
|
BLAKE2b-256 checksum How to use checksums |
9c0d7cdc6c9abd868a09b0212d5f5aad59a127769366907af69199583d70d31d
|
| 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.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | uv_pkcs11-0.12.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 24.4 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
646a56cf798d07b10b41b03e884f0d3d57fb732d73cf0bf17e0f0f3c91552d49
|
|
BLAKE2b-256 checksum How to use checksums |
6809e87392b9f00ea0ff759d6e74cdcc35c118a2e855ea4d68962aee8c927169
|
| 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.13-py3-none-macosx_11_0_arm64.whl
| Download URL | uv_pkcs11-0.12.13-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 20.8 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
2fd5c86dde96669abb0364bbd2caa958b3195ed09b3d0f55b095b1212b43157f
|
|
BLAKE2b-256 checksum How to use checksums |
bda9e5b872b4fd4ed597fda25711792b28503902d5f270a8cbc334b4207f4c86
|
| 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}
|