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. - A key's
CKA_ALLOWED_MECHANISMS, where the provider reports it, narrows the mechanisms offered for that key, so a key restricted to part of the token's mechanism list advertises only the schemes it can actually produce. - 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_GetMechanismListand the key'sCKA_ALLOWED_MECHANISMS: 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.15
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.15.tar.gz | 7.0 MB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| uv_pkcs11-0.12.15-py3-none-manylinux_2_28_aarch64.whl | Python 3 | none | Linux glibc 2.28+ ARM64 | Details |
| uv_pkcs11-0.12.15-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.15-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 75.6 MB
Release files / uv_pkcs11-0.12.15.tar.gz
| Download URL | uv_pkcs11-0.12.15.tar.gz |
|---|---|
| Size | 7.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f225af5a5044a1c9e0a9a1fd4896255aeb7b61a1dd7499aa71a3adb5b3c1c90b
|
|
BLAKE2b-256 checksum How to use checksums |
1ae8e5cb57ade09b6004318654f6e9531f8144238f30b889ba5b1fe0cee4176e
|
| 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.15-py3-none-manylinux_2_28_aarch64.whl
| Download URL | uv_pkcs11-0.12.15-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 |
5a7dc82333a835676c5e5dddbecfd6d5506d7e9539bdcbdb76f37324695a1bcc
|
|
BLAKE2b-256 checksum How to use checksums |
e50bbae9b6bdfe9d15f84dd2c683251869c324453dfedb92e05a66db32871cba
|
| 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.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | uv_pkcs11-0.12.15-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 |
d32adacb93eeaed54a286fdc87b711d2f2b5de71b68fd1b04ae14044610a740b
|
|
BLAKE2b-256 checksum How to use checksums |
c2851cdb80ec91e8ccfea87fbd862fe621dfbaf09c21bb165122d68fecc7ac6b
|
| 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.15-py3-none-macosx_11_0_arm64.whl
| Download URL | uv_pkcs11-0.12.15-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 |
bf5f44e937cd0bf91ad1c207a24df44fba338fc989d822935dc7f98da1cea2bb
|
|
BLAKE2b-256 checksum How to use checksums |
792fe67d2d033c75de393d1df4eea88479442b79d5d720154a8ab3eacb58a024
|
| 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}
|