Pkcs11ex.Slot.Server (pkcs11ex v0.1.0)

Copy Markdown View Source

GenServer that owns a single slot's PKCS#11 module + persistent session.

One process per configured slot. State machine:

:uninitialized open :open login :logged_in
                                            
                                             logout  :open
                                             timeout  :open (with reauthentication policy)
                           shutdown  (terminate)

Sessions persist for the lifetime of the GenServer (Phase 1's per-call open/close model is replaced here). For PIN-protected token slots this matches the spec's single-session-pinned model: all sign/verify calls to this slot serialize through this GenServer's mailbox AND through the session's internal mutex, so concurrent token access is impossible by construction.

Phase 2 status

This step ships the lifecycle plumbing. The pin_callback lifecycle from api.md §4.2 lands in step 2 (PIN reauth, :reauthentication :prompt/:fail, session timeouts). For now, the PIN is supplied at sign/verify call time.

Summary

Types

State machine state of the slot.

Functions

Returns a specification to start this module under a supervisor.

Returns the slot's configured slot_config keyword list.

Provisioning: import an RSA keypair + cert into the slot's token.

Explicit one-shot login. The PIN is consumed and dropped — not stored on the GenServer state.

Explicit logout. The session stays open; subsequent sign calls will need a PIN again. Targets worker 1 — see login/2.

Sign data with key_label on the slot. PIN is supplied per call.

Start a slot server. Options

Returns the slot's current state machine state (worker 1).

Verify a signature on the slot. Routes through pool round-robin like sign/5. Emits [:pkcs11ex, :slot, :verify] telemetry on the same shape.

Types

slot_state()

@type slot_state() :: :uninitialized | :open | :logged_in

State machine state of the slot.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

get_config(slot_ref)

@spec get_config(atom()) :: {:ok, keyword()} | {:error, :slot_not_found}

Returns the slot's configured slot_config keyword list.

Used by signer-ref resolution (Pkcs11ex.sign_bytes(..., signer: {slot, key})) to look up the actual key_label for a logical key_ref without needing to read Application.config() directly. The Slot.Server is the single source of truth for whichever slot it serves — wherever it was started from (the application supervisor or a test) the same lookup works.

import_keypair(slot_ref, args, opts \\ [])

@spec import_keypair(atom(), keyword(), keyword()) :: :ok | {:error, term()}

Provisioning: import an RSA keypair + cert into the slot's token.

Used by mix pkcs11ex.import_p12. Not a runtime API — calling this from a request path violates the "no software signing" non-goal because it implies the caller has the private-key components in software memory.

args keyword list:

  • :componentsPkcs11ex.Native.RsaPrivateComponents struct
  • :cert_der — full DER-encoded leaf cert
  • :subject_der — DER-encoded subject DN (extracted from cert)
  • :key_labelCKA_LABEL for the imported private key
  • :cert_labelCKA_LABEL for the imported cert (often == :key_label)
  • :idCKA_ID byte string. Empty string means "no id".

Login follows the standard PIN priority chain (opts[:pin] → configured :pin_callback).

login(slot_ref, pin)

@spec login(atom(), binary()) :: :ok | {:error, term()}

Explicit one-shot login. The PIN is consumed and dropped — not stored on the GenServer state.

Always lands on worker 1: session_pool_size > 1 is forbidden for :token slots (the only slot type where login state is observable), so worker 1 is the only worker that has login state worth setting.

logout(slot_ref)

@spec logout(atom()) :: :ok | {:error, term()}

Explicit logout. The session stays open; subsequent sign calls will need a PIN again. Targets worker 1 — see login/2.

sign(slot_ref, key_label, mechanism, data, opts \\ [])

@spec sign(atom(), String.t(), atom(), iodata(), keyword()) ::
  {:ok, binary()} | {:error, term()}

Sign data with key_label on the slot. PIN is supplied per call.

Routes through Pkcs11ex.Slot.Pool.next_worker_index/1: for non-pool slots (session_pool_size: 1 or unconfigured) always lands on worker 1; for pool slots round-robins across the workers.

Emits [:pkcs11ex, :slot, :sign] telemetry with metadata %{slot_ref:, worker_index:, mechanism:} after the call returns.

start_link(opts)

Start a slot server. Options:

  • :slot_ref — atom from Pkcs11ex.Config.t().slots. Required.
  • :worker_index — integer ≥ 1, identifying this worker within the slot's pool. Defaults to 1. The supervisor passes 1..N for pool slots; tests and single-worker production paths use the default.
  • :slot_config — the keyword list for that slot. Required.
  • :driver_pins — the global :driver_pins map. Defaults to %{}.
  • :name — registered name. Defaults to the via-tuple in Pkcs11ex.Slot.Registry keyed by {slot_ref, worker_index}.

status(slot_ref)

@spec status(atom()) :: slot_state()

Returns the slot's current state machine state (worker 1).

verify(slot_ref, key_label, mechanism, data, signature, opts \\ [])

@spec verify(atom(), String.t(), atom(), iodata(), binary(), keyword()) ::
  :ok | {:error, term()}

Verify a signature on the slot. Routes through pool round-robin like sign/5. Emits [:pkcs11ex, :slot, :verify] telemetry on the same shape.