Back to Infisical

PKCS#11 Module

docs/documentation/platform/pki/code-signing/pkcs11-module.mdx

0.160.1110.4 KB
Original Source

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.

<Info> Before installing the module, ensure a Product Admin has created a [Signer](/documentation/platform/pki/code-signing/signers) and added the machine identity (or user) you plan to authenticate as. If the Signer has an [approval policy](/documentation/platform/pki/code-signing/approvals), you'll also need [active access](/documentation/platform/pki/code-signing/approvals#access-lifecycle). </Info>

Installation

Grab the pre-built binary for your platform from the releases page:

PlatformFile
Linux x86_64libinfisical-pkcs11.so
Linux ARM64libinfisical-pkcs11.so
macOS x86_64libinfisical-pkcs11.dylib
macOS ARM64libinfisical-pkcs11.dylib
Windows x86_64libinfisical-pkcs11.dll

Drop the binary in a known location:

bash
# 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

Building from source

If you want to build instead of downloading, you need Go 1.24+ and a C compiler:

bash
git clone https://github.com/Infisical/infisical-pkcs-11.git
cd infisical-pkcs-11
make build

The output binary lands in the current directory.

Configuration

The module reads its config from /etc/infisical/pkcs11.conf (override the path with INFISICAL_PKCS11_CONFIG):

json
{
  "server_url": "https://app.infisical.com",
  "log_level": "info"
}

Then export the authentication credentials as environment variables:

bash
export INFISICAL_UNIVERSAL_AUTH_CLIENT_ID="<machine-identity-client-id>"
export INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET="<machine-identity-client-secret>"
<Note> Credentials can also live in the config file under `auth.client_id` and `auth.client_secret`, but environment variables are strongly recommended so secrets don't end up in version control, backups, or shared logs. </Note>

If you put the config somewhere other than /etc/infisical/pkcs11.conf:

bash
export INFISICAL_PKCS11_CONFIG=/path/to/your/pkcs11.conf

Configuration reference

FieldRequiredDefaultDescription
server_urlYesNoneInfisical server URL. Must use http:// (local dev) or https://.
auth.methodNouniversal-authAuthentication method.
auth.client_idNoNoneMachine identity client ID. Prefer the env var.
auth.client_secretNoNoneMachine identity client secret. Prefer the env var.
tls.ca_cert_pathNoNoneCustom CA bundle path for self-hosted Infisical with a private CA.
tls.skip_verifyNofalseSkip TLS verification (development only, never enable in production).
cache.token_ttl_secondsNo300How long the auth token is cached.
cache.cert_ttl_secondsNo3600How long fetched certificates are cached.
cache.signer_ttl_secondsNo300How long the list of Signers is cached.
approval.signing_durationNoNoneAuto-request signing access with this window when no active access exists. Valid range: 1m to 30d.
approval.signing_countNoNoneAuto-request signing access for this many signatures when no active access exists.
log_levelNoinfoOne of trace, debug, info, warn, error.
log_fileNostderrOptional path to a log file.

Environment variable reference

VariableDescription
INFISICAL_UNIVERSAL_AUTH_CLIENT_IDMachine identity client ID. Overrides config.
INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRETMachine identity client secret. Overrides config.
INFISICAL_PKCS11_SERVER_URLOverride server_url.
INFISICAL_PKCS11_CONFIGPath to the config file (default /etc/infisical/pkcs11.conf).
<Note> Environment variables always take precedence over values in the configuration file. </Note>

Verify the Module

Use pkcs11-tool (from OpenSC: brew install opensc or apt install opensc) to confirm everything is wired up.

bash
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:

text
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:

bash
pkcs11-tool \
  --module /usr/local/lib/libinfisical-pkcs11.so \
  --slot 0 \
  --list-objects

A quick smoke-test sign:

bash
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.

Automatic Signing Access Requests

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:

json
{
  "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.

<Note> The first sign call **still fails** until an approver approves the auto-created request. Once approved, retry the sign and it succeeds. The module logs that the request was created so you know what to do. </Note>

Example CI workflow

  1. Pipeline calls jarsigner (or any PKCS#11 tool) against the Infisical module.
  2. No active access exists, so Infisical returns 403.
  3. The module sees the approval block and auto-creates a signing request with the configured duration and count.
  4. Pipeline fails on this run.
  5. A reviewer approves the request in the Infisical UI.
  6. Pipeline is rerun and the sign call succeeds under the freshly-issued access record.

For fully unattended CI, have an Administrator pre-approve signing access via Pre-approve signing before the pipeline runs.

Troubleshooting

Turn on debug logging in the config first. It'll usually tell you exactly what went wrong:

json
{
  "log_level": "debug",
  "log_file": "/tmp/infisical-pkcs11.log"
}
<AccordionGroup> <Accordion title="No slots listed"> Almost always one of three things: 1. The machine identity is not a member of any Signer in your organization. Have an Admin add it to a Signer (any role works). 2. The credentials are wrong. Check the auth token line in the log; if it says `401`, regenerate the universal-auth client secret. 3. The `server_url` points at the wrong instance. The module log line `Initialized with universal-auth (auto-authenticated)` confirms a successful login. </Accordion> <Accordion title="CKR_GENERAL_ERROR on sign"> Almost always means the server rejected the sign. Common reasons (visible in the debug log): - **No active access.** The Signer has an approval policy and you don't yet have access. See [Request to sign](/documentation/platform/pki/code-signing/approvals#request-to-sign). - **Access expired or signatures exhausted.** Request access again. - **Signer is Disabled.** Re-enable it in the UI. - **Certificate expired.** Issue a new one or wait for auto-renewal. </Accordion> <Accordion title="CKR_DEVICE_ERROR or connection refused"> Network or TLS problem. Check `server_url`, confirm the host is reachable from the machine, and verify TLS. If you self-host with a private CA, set `tls.ca_cert_path` to its bundle. As a last resort during local dev, you can set `tls.skip_verify: true` (**never in production**). </Accordion> <Accordion title="server_url scheme must be http or https"> The module rejects any other scheme (`file://`, `gopher://`, etc.) to prevent credentials being sent to unintended targets. Make sure your `server_url` starts with `http://` or `https://`. </Accordion> <Accordion title="The PKCS#11 module sees the slot but says 'signer X has no certificate'"> The Signer is in **Pending** or **Failed** status; no certificate has been issued yet. Check the Signer's status in the UI; for external CAs (AWS Private CA, Azure AD CS), issuance can take minutes. Once the Signer becomes Active, the cert is fetched automatically. </Accordion> </AccordionGroup>

What's next

<CardGroup cols={3}> <Card title="Sign JARs" icon="java" href="/documentation/platform/pki/guides/code-signing/jarsigner"> `jarsigner` with the PKCS#11 module. </Card> <Card title="Sign containers" icon="docker" href="/documentation/platform/pki/guides/code-signing/cosign"> `cosign sign` with Infisical. </Card> <Card title="Sign Windows binaries" icon="windows" href="/documentation/platform/pki/guides/code-signing/osslsigncode"> `osslsigncode` for `.exe` / `.dll` / `.msi`. </Card> <Card title="Sign APKs" icon="android" href="/documentation/platform/pki/guides/code-signing/apksigner"> `apksigner` for Android. </Card> <Card title="Sign with OpenSSL" icon="lock" href="/documentation/platform/pki/guides/code-signing/openssl"> Raw signing primitives. </Card> <Card title="Sign with GPG" icon="key" href="/documentation/platform/pki/guides/code-signing/gpg"> Use GPG via the PKCS#11 module. </Card> </CardGroup>