docs/documentation/platform/pki/code-signing/pkcs11-module.mdx
The Infisical PKCS#11 module is a small native library (.so, .dylib, or .dll) that exposes your Infisical Signers to any tool that supports the PKCS#11 v2.40 standard. Tools like jarsigner, osslsigncode, cosign, apksigner, openssl, and gpg work without modification. They make their usual PKCS#11 calls; the module forwards them to Infisical and returns the signature.
Grab the pre-built binary for your platform from the releases page:
| Platform | File |
|---|---|
| Linux x86_64 | libinfisical-pkcs11.so |
| Linux ARM64 | libinfisical-pkcs11.so |
| macOS x86_64 | libinfisical-pkcs11.dylib |
| macOS ARM64 | libinfisical-pkcs11.dylib |
| Windows x86_64 | libinfisical-pkcs11.dll |
Drop the binary in a known location:
# Linux
sudo cp libinfisical-pkcs11.so /usr/local/lib/
sudo chmod 755 /usr/local/lib/libinfisical-pkcs11.so
# macOS
sudo cp libinfisical-pkcs11.dylib /usr/local/lib/
sudo chmod 755 /usr/local/lib/libinfisical-pkcs11.dylib
If you want to build instead of downloading, you need Go 1.24+ and a C compiler:
git clone https://github.com/Infisical/infisical-pkcs-11.git
cd infisical-pkcs-11
make build
The output binary lands in the current directory.
The module reads its config from /etc/infisical/pkcs11.conf (override the path with INFISICAL_PKCS11_CONFIG):
{
"server_url": "https://app.infisical.com",
"log_level": "info"
}
Then export the authentication credentials as environment variables:
export INFISICAL_UNIVERSAL_AUTH_CLIENT_ID="<machine-identity-client-id>"
export INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET="<machine-identity-client-secret>"
If you put the config somewhere other than /etc/infisical/pkcs11.conf:
export INFISICAL_PKCS11_CONFIG=/path/to/your/pkcs11.conf
| Field | Required | Default | Description |
|---|---|---|---|
server_url | Yes | None | Infisical server URL. Must use http:// (local dev) or https://. |
auth.method | No | universal-auth | Authentication method. |
auth.client_id | No | None | Machine identity client ID. Prefer the env var. |
auth.client_secret | No | None | Machine identity client secret. Prefer the env var. |
tls.ca_cert_path | No | None | Custom CA bundle path for self-hosted Infisical with a private CA. |
tls.skip_verify | No | false | Skip TLS verification (development only, never enable in production). |
cache.token_ttl_seconds | No | 300 | How long the auth token is cached. |
cache.cert_ttl_seconds | No | 3600 | How long fetched certificates are cached. |
cache.signer_ttl_seconds | No | 300 | How long the list of Signers is cached. |
approval.signing_duration | No | None | Auto-request signing access with this window when no active access exists. Valid range: 1m to 30d. |
approval.signing_count | No | None | Auto-request signing access for this many signatures when no active access exists. |
log_level | No | info | One of trace, debug, info, warn, error. |
log_file | No | stderr | Optional path to a log file. |
| Variable | Description |
|---|---|
INFISICAL_UNIVERSAL_AUTH_CLIENT_ID | Machine identity client ID. Overrides config. |
INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET | Machine identity client secret. Overrides config. |
INFISICAL_PKCS11_SERVER_URL | Override server_url. |
INFISICAL_PKCS11_CONFIG | Path to the config file (default /etc/infisical/pkcs11.conf). |
Use pkcs11-tool (from OpenSC: brew install opensc or apt install opensc) to confirm everything is wired up.
pkcs11-tool \
--module /usr/local/lib/libinfisical-pkcs11.so \
--list-slots
You should see one slot per Signer your machine identity is a member of:
Available slots:
Slot 0 (0x0): mobile-app-prod
token label : mobile-app-prod
token manufacturer : Infisical
token model : Code Signing
...
Slot 1 (0x1): release-signer
...
To list the objects (private key, public key, X.509 cert) in a slot:
pkcs11-tool \
--module /usr/local/lib/libinfisical-pkcs11.so \
--slot 0 \
--list-objects
A quick smoke-test sign:
echo "hello" > /tmp/payload.bin
pkcs11-tool \
--module /usr/local/lib/libinfisical-pkcs11.so \
--slot 0 \
--sign --mechanism SHA256-RSA-PKCS \
--input-file /tmp/payload.bin \
--output-file /tmp/payload.sig
If the Signer has an approval policy and you don't have active access, the sign call is rejected with CKR_GENERAL_ERROR and the module logs a "signing requires approval" line. See Automatic Signing Access Requests below to make this seamless.
When a Signer has a policy attached, sign calls without active access normally fail. The module can automatically open a signing request for you on the first denied call. You just need to add an approval block to the config:
{
"server_url": "https://app.infisical.com",
"approval": {
"signing_duration": "8h",
"signing_count": 10
}
}
signing_duration requests an access window of this duration (30m, 8h, 2d; range 1m to 30d).signing_count requests access good for this many signing operations.Set one or both depending on what the Signer's policy expects. The values are capped at the policy's maxWindow / maxSignings.
jarsigner (or any PKCS#11 tool) against the Infisical module.approval block and auto-creates a signing request with the configured duration and count.For fully unattended CI, have an Administrator pre-approve signing access via Pre-approve signing before the pipeline runs.
Turn on debug logging in the config first. It'll usually tell you exactly what went wrong:
{
"log_level": "debug",
"log_file": "/tmp/infisical-pkcs11.log"
}