> ## Documentation Index
> Fetch the complete documentation index at: https://infisical.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# PKCS#11 Module

> Use jarsigner, cosign, osslsigncode, and other standard signing tools with Infisical Signers.

The Infisical **PKCS#11 module** is a small native library (`.so`, `.dylib`, or `.dll`) that exposes your Infisical [Signers](/documentation/platform/pki/code-signing/signers) to any tool that supports the [PKCS#11 v2.40 standard](https://www.oasis-open.org/standard/pkcs-11-v2-40/). 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.

<Note>
  Signing on Windows with `signtool`? Use the [Windows KSP](/documentation/platform/pki/code-signing/windows-ksp) instead.
</Note>

## Before you start

You authenticate as a member of the Signer, using either a **Machine Identity** or your own Infisical **access token**. To use a Machine Identity, set it up once:

1. **Create a Machine Identity** and enable **[Universal Auth](/documentation/platform/identities/universal-auth)** on it, then copy its **Client ID** and **Client Secret**. The module uses these to authenticate, and you will set them in [Configuration](#configuration) below.
2. **Add the identity to the Signer** with the Administrator or Operator role, from the Signer's **Members** tab. Auditors cannot sign.
3. If the Signer has an [approval policy](/documentation/platform/pki/code-signing/approvals), get [active signing access](/documentation/platform/pki/code-signing/approvals#access-lifecycle) before signing (or use [Automatic Signing Access Requests](#automatic-signing-access-requests)).

<Note>
  To sign as yourself instead, use your own access token (see [Configuration](#configuration)). You still need to be a member of the Signer with the Administrator or Operator role.
</Note>

<Info>
  The [Signer](/documentation/platform/pki/code-signing/signers) itself is created by a Product Admin. If you do not have one yet, create it first.
</Info>

## Installation

Grab the pre-built binary for your platform from the [releases page](https://github.com/Infisical/infisical-pkcs-11/releases):

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

```bash theme={"dark"}
# 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 theme={"dark"}
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_CONFIG`):

```json theme={"dark"}
{
  "server_url": "https://app.infisical.com",
  "log_level": "info"
}
```

Then export the authentication credentials as environment variables:

```bash theme={"dark"}
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>

#### Sign with an access token instead

Instead of a machine identity, you can hand the module an Infisical access token directly, either your own or a machine identity's. Set `INFISICAL_TOKEN` and the module uses it as-is:

```bash theme={"dark"}
export INFISICAL_SERVER_URL="https://app.infisical.com"
export INFISICAL_TOKEN="<your-access-token>"
```

<Warning>
  JWT tokens expire, so this access is **temporary**. The module does not refresh the token; once it expires, signing fails until you supply a new one. This is handy for signing as yourself or for a quick one-off; for unattended or CI/CD signing use a machine identity with Universal Auth (client ID and secret), which the module re-authenticates automatically.
</Warning>

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

```bash theme={"dark"}
export INFISICAL_CONFIG=/path/to/your/pkcs11.conf
```

### Configuration reference

| Field                       | Required | Default  | Description                                                                                                                                       |
| --------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `server_url`                | Yes      | None     | Infisical server URL. Must use `http://` (local dev) or `https://`.                                                                               |
| `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 the env var.                                                                             |
| `auth.client_secret`        | No       | None     | Machine identity client secret, for `universal-auth`. Prefer the env var.                                                                         |
| `auth.token`                | No       | None     | An Infisical access token, for `token` auth. 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.                                                                                                                      |

### Environment variable reference

| 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_TOKEN`                        | An Infisical access token (a user or machine identity token). Selects token auth; used instead of the client ID/secret. |
| `INFISICAL_SERVER_URL`                   | The Infisical instance URL. Sets `server_url` (and overrides the config file).                                          |
| `INFISICAL_CONFIG`                       | Path 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 theme={"dark"}
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 theme={"dark"}
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 theme={"dark"}
pkcs11-tool \
  --module /usr/local/lib/libinfisical-pkcs11.so \
  --slot 0 \
  --list-objects
```

A quick smoke-test sign:

```bash theme={"dark"}
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](#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 theme={"dark"}
{
  "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](/documentation/platform/pki/code-signing/approvals#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 theme={"dark"}
{
  "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>
