synta 0.2.4

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# PKCS#11 Token Management

The `synta.pkcs11` submodule provides management operations for PKCS#11 tokens:
slot enumeration, key discovery, key deletion, and on-token key generation.  It
is backed by the `cryptoki` crate, which wraps the standard PKCS#11 C API.

Management operations open and close their own sessions; they are independent of
signing or verification, which remain the responsibility of the active crypto
backend (`openssl` or `nss` Cargo feature).

## Module resolution

All entry points accept an optional `module` argument.  When `module` is omitted:

1. The `module-path=` attribute of the PKCS#11 URI is consulted (where applicable).
2. The `PKCS11_MODULE_PATH` environment variable is checked.
3. Well-known p11-kit-proxy paths are probed in order: `/usr/lib64/pkcs11/p11-kit-proxy.so`
   (Fedora/RHEL), `/usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-proxy.so` (Debian/Ubuntu),
   `/usr/lib/pkcs11/p11-kit-proxy.so` (generic Linux).

## `list_slots(module=None)`

```text
synta.pkcs11.list_slots(module: str | None = None) -> list[SlotInfo]
```

Return all slots that currently have an initialised token present.

```python
import synta.pkcs11

slots = synta.pkcs11.list_slots()
for s in slots:
    print(s.slot_id, s.token_label, s.flags)
```

## `SlotInfo`

Returned by `list_slots()` and `Pkcs11Token.slot`.

| Property | Type | Description |
|---|---|---|
| `slot_id` | `int` | PKCS#11 `CK_SLOT_ID` value |
| `token_label` | `str` | Token label from `CK_TOKEN_INFO`, trailing spaces stripped |
| `manufacturer_id` | `str` | Manufacturer ID from `CK_TOKEN_INFO`, trailing spaces stripped |
| `model` | `str` | Model string from `CK_TOKEN_INFO`, trailing spaces stripped |
| `serial_number` | `str` | Serial number from `CK_TOKEN_INFO`, trailing spaces stripped |
| `flags` | `int` | Bitmask of four `CKF_*` bits (see below) |

### `flags` bitmask

Only four bits are populated (PKCS#11 v3.0 §4.9):

| Bit | Constant | Meaning |
|---|---|---|
| `0x0002` | `CKF_WRITE_PROTECTED` | Token is read-only |
| `0x0004` | `CKF_LOGIN_REQUIRED` | PIN must be presented before accessing private objects |
| `0x0100` | `CKF_PROTECTED_AUTHENTICATION_PATH` | PIN entry is via an on-device keypad |
| `0x0400` | `CKF_TOKEN_INITIALIZED` | Token has been initialised |

All other bits are zero.

## `Pkcs11Token`

A session handle for management operations on a single named token.

```python
token = synta.pkcs11.Pkcs11Token(uri, module=None)
```

`uri` is a `pkcs11:` URI (RFC 7512) that must include `token=<label>`.  Include
`?pin-value=<PIN>` in the URI to authenticate automatically on each call.

```python
token = synta.pkcs11.Pkcs11Token(
    "pkcs11:token=MySoftHSM2Token0?pin-value=1234"
)
```

### `find_key(object_uri)`

```text
token.find_key(object_uri: str) -> bool
```

Return `True` if a `CKO_PRIVATE_KEY` object whose `CKA_LABEL` matches the
`object=` attribute of `object_uri` exists on this token.

```python
exists = token.find_key("pkcs11:token=MySoftHSM2Token0;object=caKey")
```

### `list_keys()`

```text
token.list_keys() -> list[KeyInfo]
```

Return all `CKO_PRIVATE_KEY` objects on this token.

```python
for k in token.list_keys():
    print(k.label, k.key_type, k.key_bits)
```

### `delete_key(object_uri)`

```text
token.delete_key(object_uri: str) -> None
```

Destroy both the `CKO_PRIVATE_KEY` and the `CKO_PUBLIC_KEY` objects whose
`CKA_LABEL` matches `object=` in `object_uri`.

```python
token.delete_key("pkcs11:token=MySoftHSM2Token0;object=caKey")
```

### `generate_key_pair(key_type, param, label, extractable=False)`

```text
token.generate_key_pair(
    key_type: str,
    param: int | str,
    label: str,
    extractable: bool = False,
) -> PrivateKey
```

Generate a key pair on the token and return the private key.  Both objects are
stored with `CKA_TOKEN = true` so they survive session close.

| `key_type` | `param` type | `param` value |
|---|---|---|
| `"rsa"` | `int` | modulus bit-length, e.g. `2048`, `3072`, `4096` |
| `"ec"` | `str` | NIST curve: `"P-256"`, `"P-384"`, `"P-521"` |
| `"ed25519"` | ignored ||
| `"ed448"` | ignored ||
| `"ml-dsa"` | `str` | `"ML-DSA-44"`, `"ML-DSA-65"`, or `"ML-DSA-87"` |
| `"ml-kem"` | `str` | `"ML-KEM-512"`, `"ML-KEM-768"`, or `"ML-KEM-1024"` |

Set `extractable=True` only when key export is explicitly required (e.g. software
tokens used in backup scenarios).  The default `False` generates a
hardware-resident key that cannot leave the token.

The PKCS#11 session used for generation is closed before the key is loaded
back through the active crypto backend.  This ensures that the returned
`PrivateKey` carries a fully populated SPKI and is ready for immediate use.

```python
rsa_key  = token.generate_key_pair("rsa", 4096, "caKey")
ec_key   = token.generate_key_pair("ec", "P-256", "ecKey")
dsa_key  = token.generate_key_pair("ml-dsa", "ML-DSA-65", "pqKey")
```

## `KeyInfo`

Returned by `Pkcs11Token.list_keys()`.

| Property | Type | Description |
|---|---|---|
| `label` | `str` | `CKA_LABEL` of the private key object |
| `id` | `bytes` | `CKA_ID` bytes; may be empty if the token does not set this attribute |
| `key_type` | `str` | `"RSA"`, `"EC"`, `"Ed"`, `"ML-DSA"`, `"ML-KEM"`, or `"Unknown"` |
| `key_bits` | `int` | RSA modulus length in bits (from `CKA_MODULUS`); `0` for all non-RSA types |

## Full class stubs

```python
class SlotInfo:
    slot_id: int
    token_label: str
    manufacturer_id: str
    model: str
    serial_number: str
    flags: int   # CKF_* bitmask; see table above

class KeyInfo:
    label: str
    id: bytes
    key_type: str
    key_bits: int

class Pkcs11Token:
    def __init__(self, uri: str, module: str | None = None) -> None: ...
    def find_key(self, object_uri: str) -> bool: ...
    def list_keys(self) -> list[KeyInfo]: ...
    def delete_key(self, object_uri: str) -> None: ...
    def generate_key_pair(
        self,
        key_type: str,
        param: int | str,
        label: str,
        extractable: bool = False,
    ) -> PrivateKey: ...

def list_slots(module: str | None = None) -> list[SlotInfo]: ...
```

## PKCS#11 URI format (RFC 7512)

```
pkcs11:<path-attrs>(?<query-attrs>)
```

- Path attributes are separated by `;`: `token=`, `object=`, `id=`, `module-path=`
- Query attributes follow `?` and are separated by `&`: `pin-value=`

```
pkcs11:token=MySoftHSM2Token0;object=caKey?pin-value=1234
pkcs11:token=MyHSM;object=signingKey;module-path=/opt/vendor/pkcs11.so?pin-value=secret
```

See also [PublicKey and PrivateKey](keys.md) for loading a token-resident key
via `PrivateKey.from_pkcs11_uri` and using it directly for signing.