Infisical PKCS#11 Module: Sign code and artifacts using keys managed in Infisical — private keys never leave Infisical.
Docs | Slack | Infisical Cloud | Website
The Infisical PKCS#11 Module is a shared library (.so, .dylib, .dll) that implements PKCS#11 v2.40. It acts as a bridge between standard signing tools and the Infisical API — your tool loads the library, and all cryptographic operations are performed by Infisical.
Each Signer in your Infisical project appears as a PKCS#11 slot, exposing a private key object (for signing) and a certificate object (for verification and chain building).
- Remote Signing: Private keys never leave Infisical. All signing operations are performed by Infisical.
- Universal Tool Compatibility: Works with jarsigner, osslsigncode, signtool, pkcs11-tool, GnuTLS, OpenSSL, and any PKCS#11 consumer.
- RSA and ECDSA Support: SHA-256/384/512 with PKCS#1 v1.5, PSS, and ECDSA. Supports P-256, P-384, and P-521 curves.
- Approval Workflows: Require human review before signing, bounded by a signature count and/or a time window per approval.
- Audit Logging: Every signing operation is recorded with actor, timestamp, and client metadata.
- Cross-Platform: Pre-built binaries for Linux (x86_64, ARM64), macOS (x86_64, ARM64), and Windows (x86_64).
- An Infisical instance with the Cert Manager product enabled
- At least one Signer created (Cert Manager > Code Signing > Signers)
- The Signer must be backed by an Internal CA, AWS Private CA, or Azure AD CS
- A way to authenticate as a member of the Signer with the Administrator or Operator role (the signer's Members tab): either a Machine Identity with Universal Auth, or an Infisical access token (a user's or a machine identity's). See Authentication.
- If using approval policies: an approved sign request for the signer before signing
Download the binary for your platform from the releases page:
| Platform | File |
|---|---|
| Linux x86_64 | libinfisical-pkcs11-linux-amd64.so |
| Linux ARM64 | libinfisical-pkcs11-linux-arm64.so |
| macOS x86_64 | libinfisical-pkcs11-darwin-amd64.dylib |
| macOS ARM64 | libinfisical-pkcs11-darwin-arm64.dylib |
| Windows x86_64 | libinfisical-pkcs11-windows-amd64.dll |
Or build from source (requires Go 1.21+ and a C compiler):
make buildCreate /etc/infisical/pkcs11.conf (or set INFISICAL_CONFIG to a custom path):
{
"server_url": "https://app.infisical.com"
}Set credentials via environment variables (recommended):
export INFISICAL_UNIVERSAL_AUTH_CLIENT_ID="your-client-id"
export INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET="your-client-secret"# List available signers (slots)
pkcs11-tool --module ./libinfisical-pkcs11.so --list-slots
# List objects in a slot
pkcs11-tool --module ./libinfisical-pkcs11.so --list-objects --slot 0pkcs11-tool --module ./libinfisical-pkcs11.so --sign \
--slot 0 --mechanism SHA256-RSA-PKCS \
--input-file document.bin --output-file document.sigThe module reads a JSON config file and environment variables. Environment variables take precedence over config file values for credentials.
| Variable | Description |
|---|---|
INFISICAL_UNIVERSAL_AUTH_CLIENT_ID |
Machine Identity client ID (Universal Auth) |
INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET |
Machine Identity client secret (Universal Auth) |
INFISICAL_TOKEN |
An Infisical access token (a user or machine identity token). Selects token auth; used instead of Universal Auth credentials |
INFISICAL_CONFIG |
Path to config file (default: /etc/infisical/pkcs11.conf) |
INFISICAL_SERVER_URL |
The Infisical instance URL. Sets server_url (and overrides the config file) |
| Field | Required | Default | Description |
|---|---|---|---|
server_url |
Yes | — | Infisical server URL |
auth.method |
No | inferred | Authentication method: universal-auth or token. Inferred from the credentials when unset (a token means token, otherwise universal-auth) |
auth.client_id |
No | (none) | Machine Identity client ID, for universal-auth (prefer env var) |
auth.client_secret |
No | (none) | Machine Identity client secret, for universal-auth (prefer env var) |
auth.token |
No | (none) | Infisical access token, for token auth (prefer the env var) |
tls.ca_cert_path |
No | — | Custom CA certificate for self-hosted instances |
tls.skip_verify |
No | false |
Skip TLS verification (development only) |
cache.token_ttl_seconds |
No | 300 |
Auth token cache duration |
cache.cert_ttl_seconds |
No | 3600 |
Certificate data cache duration |
cache.signer_ttl_seconds |
No | 300 |
Signer list cache duration |
approval.signing_duration |
No | — | Auto-request approval with this time window (e.g. "8h", "30m", "2d"). Range: 1m–30d |
approval.signing_count |
No | — | Auto-request approval for this many signings |
log_level |
No | info |
Log verbosity: trace, debug, info, warn, error |
log_file |
No | stderr | Path to log file |
Full config example
{
"server_url": "https://app.infisical.com",
"auth": {
"client_id": "your-client-id",
"client_secret": "your-client-secret"
},
"tls": {
"ca_cert_path": "/path/to/custom-ca.pem",
"skip_verify": false
},
"cache": {
"token_ttl_seconds": 300,
"cert_ttl_seconds": 3600,
"signer_ttl_seconds": 300
},
"approval": {
"signing_duration": "8h",
"signing_count": 10
},
"log_level": "info",
"log_file": "/var/log/infisical-pkcs11.log"
}The module supports two ways to authenticate with Infisical.
Universal Auth (Machine Identity) is the default. The module exchanges the client ID/secret for an access token and refreshes it automatically. Provide the credentials three ways (in order of precedence):
-
Environment variables (recommended for CI/CD and production):
export INFISICAL_UNIVERSAL_AUTH_CLIENT_ID="your-client-id" export INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET="your-client-secret"
-
Config file (convenient for development):
{ "auth": { "client_id": "your-client-id", "client_secret": "your-client-secret" } } -
PIN at login time — tools that call
C_Logincan pass credentials as the PIN in the formatclientId:clientSecret.
When credentials are available, the module auto-authenticates during initialization — no explicit C_Login is needed from tools.
Token auth uses an Infisical access token directly, either a user's token or a machine identity's. Provide the token three ways (in order of precedence):
-
Environment variable (selects token auth automatically):
export INFISICAL_TOKEN="your-access-token"
-
Config file: set
auth.token(token auth is selected automatically). -
PIN at login time: tools that call
C_Logincan pass the token as the PIN. The module detects the PIN type automatically: aclientId:clientSecretPIN selects universal-auth, anything else is treated as an access token.
We recommend the environment or config. The
C_LoginPIN is an alternative that works only with tools that hand it over before they list keys. Many tools list and open a key (C_GetSlotList,C_OpenSession) first and callC_Loginafterwards, so for those use the environment or config instead.
Token auth is temporary. The module uses the token as-is and does not refresh it. When the token expires, signing fails until you set a new token.
Environment variables take precedence over the config file, and INFISICAL_TOKEN takes precedence over Universal Auth credentials.
jarsigner is the standard tool for signing JAR files, included with the JDK.
Step 1: Create a SunPKCS11 provider config (infisical-pkcs11.cfg):
name = Infisical
library = /path/to/libinfisical-pkcs11.so
On Windows, use the DLL path:
name = Infisical
library = C:\path\to\libinfisical-pkcs11-windows-amd64.dll
Step 2: Sign:
# Java 9+ (recommended)
jarsigner -keystore NONE -storetype PKCS11 \
-addprovider SunPKCS11 \
-providerArg infisical-pkcs11.cfg \
-sigalg SHA256withRSA \
myapp.jar "your-signer-name"Java 8
jarsigner -keystore NONE -storetype PKCS11 \
-providerClass sun.security.pkcs11.SunPKCS11 \
-providerArg infisical-pkcs11.cfg \
-sigalg SHA256withRSA \
myapp.jar "your-signer-name"Windows (PowerShell)
$env:INFISICAL_CONFIG = "C:\path\to\pkcs11.conf"
$env:INFISICAL_UNIVERSAL_AUTH_CLIENT_ID = "your-client-id"
$env:INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET = "your-client-secret"
jarsigner -keystore NONE -storetype PKCS11 `
-addprovider SunPKCS11 `
-providerArg infisical-pkcs11.cfg `
-sigalg SHA256withRSA `
myapp.jar "your-signer-name"Step 3: Verify:
jarsigner -verify -verbose myapp.jarNote: The alias (
"your-signer-name") must match the Signer name in Infisical exactly.
osslsigncode signs Windows executables (.exe, .dll, .msi) from Linux or macOS.
osslsigncode sign \
-pkcs11module /path/to/libinfisical-pkcs11.so \
-pkcs11cert "pkcs11:object=your-signer-name;type=cert" \
-key "pkcs11:object=your-signer-name;type=private" \
-h sha256 \
-in MyApp.exe -out MyApp-signed.exe$env:INFISICAL_CONFIG = "C:\path\to\pkcs11.conf"
$env:INFISICAL_UNIVERSAL_AUTH_CLIENT_ID = "your-client-id"
$env:INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET = "your-client-secret"
signtool sign /fd SHA256 /f cert.cer `
/csp "Infisical PKCS#11" `
/kc "your-signer-name" `
MyApp.exeNote: Windows integration typically requires registering the PKCS#11 module as a CSP/KSP or using a wrapper like
pkcs11-csp.
pkcs11-tool is useful for testing and exploring the module.
# List available slots (one per signer)
pkcs11-tool --module ./libinfisical-pkcs11.so --list-slots
# List objects in a slot
pkcs11-tool --module ./libinfisical-pkcs11.so --list-objects --slot 0
# List supported mechanisms
pkcs11-tool --module ./libinfisical-pkcs11.so --list-mechanisms --slot 0
# Sign a file
pkcs11-tool --module ./libinfisical-pkcs11.so --sign \
--slot 0 --mechanism SHA256-RSA-PKCS \
--input-file document.bin --output-file document.sig# Install: macOS: brew install gnupg-pkcs11-scd | Ubuntu: apt install gnupg-pkcs11-scdAdd to ~/.gnupg/gpg-agent.conf:
scdaemon-program /usr/bin/gnupg-pkcs11-scd
Add to ~/.gnupg/gnupg-pkcs11-scd.conf:
providers infisical
provider-infisical-library /path/to/libinfisical-pkcs11.so
Restart and verify:
gpgconf --kill gpg-agent
gpg --card-status| Mechanism | PKCS#11 Constant | Key Sizes |
|---|---|---|
| SHA256-RSA-PKCS | CKM_SHA256_RSA_PKCS |
2048, 3072, 4096 |
| SHA384-RSA-PKCS | CKM_SHA384_RSA_PKCS |
2048, 3072, 4096 |
| SHA512-RSA-PKCS | CKM_SHA512_RSA_PKCS |
2048, 3072, 4096 |
| SHA256-RSA-PKCS-PSS | CKM_SHA256_RSA_PKCS_PSS |
2048, 3072, 4096 |
| SHA384-RSA-PKCS-PSS | CKM_SHA384_RSA_PKCS_PSS |
2048, 3072, 4096 |
| SHA512-RSA-PKCS-PSS | CKM_SHA512_RSA_PKCS_PSS |
2048, 3072, 4096 |
| RSA-PKCS (raw DigestInfo) | CKM_RSA_PKCS |
2048, 3072, 4096 |
| Mechanism | PKCS#11 Constant | Curves |
|---|---|---|
| ECDSA-SHA256 | CKM_ECDSA_SHA256 |
P-256, P-384, P-521 |
| ECDSA-SHA384 | CKM_ECDSA_SHA384 |
P-256, P-384, P-521 |
| ECDSA-SHA512 | CKM_ECDSA_SHA512 |
P-256, P-384, P-521 |
| ECDSA (raw pre-hashed) | CKM_ECDSA |
P-256, P-384, P-521 |
If a signer has an approval policy, you need an approved sign request before signing. Without it, sign requests will return CKR_GENERAL_ERROR (HTTP 403).
When approval.signing_duration and/or approval.signing_count are configured, the module automatically creates an approval request when signing is denied because no approved sign request exists. The sign operation still fails (an approver must approve the request first), but the request is created for you — no manual API call needed.
{
"approval": {
"signing_duration": "8h",
"signing_count": 10
}
}Once an approver approves the request (via the Infisical UI at Cert Manager > Code Signing > Signers > <signer> > Approvals tab), retrying the sign operation will succeed.
Requesting approval via API
# Authenticate
TOKEN=$(curl -s https://app.infisical.com/api/v1/auth/universal-auth/login \
-H "Content-Type: application/json" \
-d '{"clientId":"...","clientSecret":"..."}' | jq -r '.accessToken')
# Request access for an 8-hour window, capped at 10 signatures
# macOS / BSD:
START=$(date -u -v+1M +"%Y-%m-%dT%H:%M:%SZ")
END=$(date -u -v+8H +"%Y-%m-%dT%H:%M:%SZ")
# Linux / GNU coreutils (uncomment if your `date` is GNU):
# START=$(date -u -d '+1 minute' +"%Y-%m-%dT%H:%M:%SZ")
# END=$(date -u -d '+8 hours' +"%Y-%m-%dT%H:%M:%SZ")
curl -s https://app.infisical.com/api/v1/cert-manager/signers/your-signer-id/requests \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"justification\": \"CI/CD release build\",
\"requestedSignings\": 10,
\"requestedWindowStart\": \"$START\",
\"requestedWindowEnd\": \"$END\"
}"An approver must approve the request via the Infisical UI (Cert Manager > Code Signing > Signers > <signer> > Approvals tab). Once approved, signing works for the granted window.
Each approval request can be bounded by a signature count, a time window, or both. The Signer's policy sets the ceiling for each (Signatures per approval, Signing window) — a request that exceeds the policy is rejected with a 400. justification is the only required field; omitting a bound falls back to the policy ceiling.
| Field | Description |
|---|---|
justification |
Required. Free-text reason for the request (1–2048 chars), shown to approvers. |
requestedSignings |
How many sign operations the approval permits. Leave empty to fall back to the policy ceiling. |
requestedWindowStart |
ISO 8601 timestamp the access window opens. Defaults to "now". |
requestedWindowEnd |
ISO 8601 timestamp the access window closes. Leave empty to fall back to the policy ceiling. |
Administrators of a Signer can also pre-approve or revoke requests on behalf of other members:
POST /api/v1/cert-manager/signers/{signerId}/requests/pre-approve— body acceptsgranteeUserIdorgranteeIdentityIdplus the samejustification/requestedSignings/requestedWindowStart/requestedWindowEndfields. Creates a request that is already approved.POST /api/v1/cert-manager/signers/{signerId}/requests/{requestId}/revoke— revokes a pending or active request. No body.
Enable debug logging by adding to your config file:
{
"log_level": "debug",
"log_file": "/tmp/infisical-pkcs11.log"
}Then monitor: tail -f /tmp/infisical-pkcs11.log
| Error | Cause | Fix |
|---|---|---|
CKR_GENERAL_ERROR on init |
Config file not found or invalid | Check INFISICAL_CONFIG path and JSON syntax |
CKR_GENERAL_ERROR on sign |
Approval required or permission denied | Request approval, or confirm the Machine Identity is a Signer member with the Administrator or Operator role (Auditors cannot sign) |
CKR_USER_NOT_LOGGED_IN |
No credentials or token expired | Set INFISICAL_UNIVERSAL_AUTH_CLIENT_ID and CLIENT_SECRET |
CKR_PIN_INCORRECT |
Invalid credentials in PIN | For universal-auth use the format clientId:clientSecret; for token auth pass the access token as the PIN |
CKR_SLOT_ID_INVALID |
No signers visible to this Machine Identity (none exist, or the identity isn't a member of any signer) | Create a signer in Cert Manager > Code Signing, or add this identity as a member on the signer's Members tab |
CKR_DEVICE_ERROR |
Server unreachable | Check server_url and network connectivity |
Requires Go 1.21+ and a C compiler (GCC or Clang).
# Build for current platform
make build
# Run tests
make test
# Cross-compile (requires appropriate cross-compilers)
make build-linux-amd64
make build-linux-arm64
make build-darwin-amd64
make build-darwin-arm64
make build-windows-amd64 # requires MinGW- Private keys never leave Infisical. All signing happens inside Infisical.
- Use environment variables for credentials — avoid committing secrets to config files.
- If using config-file credentials, restrict permissions:
chmod 600 /etc/infisical/pkcs11.conf - Auth tokens are cached in memory only — never written to disk.
- Enable approval policies on signers to require human review before signing.
- Every signing operation is recorded in Infisical audit logs with actor, timestamp, and client metadata.
Please do not file GitHub issues or post on public forums for security vulnerabilities. If you believe you have uncovered a vulnerability, contact security@infisical.com.
Whether it's big or small, we love contributions. Check out our contributing guide to get started.
Not sure where to get started? Join our Slack and ask us any questions.