# `SoftSigner.PKCS12`
[🔗](https://github.com/utaladriz/pkcs11ex/blob/v0.1.0/lib/soft_signer/pkcs12.ex#L1)

Software signer backed by a PKCS#12 (`.p12` / `.pfx`) bundle.

Loads the bundle once via `load/2` (uses the `openssl pkcs12` CLI
for decryption — pure-Erlang PKCS#12 decode is fragile across
vendor encodings) and returns a struct that implements
`SignCore.Signer`. Sign operations route through
`:public_key.sign/3` with the right padding for the requested
algorithm.

## Threat model — key material is BEAM-resident

The decoded RSA private key is held as an Erlang `RSAPrivateKey`
record on the returned `%SoftSigner.PKCS12{}` struct. **It lives
in BEAM heap memory for the entire lifetime of any reference to
the struct** — including indirect references via process state,
ETS tables, supervised GenServer state, etc. The BEAM has no
zeroization primitive for managed binaries / integers, and the GC
may retain freed copies well after the explicit reference is gone.

Use this signer only when the threat model accepts that the key:

  * is readable by anyone with BEAM-process memory access (other
    OS processes with `/proc/<pid>/mem` rights, core dumps,
    hibernation snapshots, swapped pages on a memory-pressured
    host, attached debuggers);
  * is recoverable from a kernel core dump or crash dump;
  * may transiently appear in `:erlang.process_info/2` output for
    processes holding the struct.

For threat models that exclude any of these (regulated tax
certificates, banking integration keys, anything that must never
be software-copyable), use `pkcs11ex` instead — the key stays
inside the HSM / token across the PKCS#11 boundary and never
enters the BEAM.

This package is named `soft_signer` precisely to make the
hardware-vs-software distinction part of the dependency graph: a
deployment that omits `:soft_signer` from its `mix.lock` cannot
software-sign by package boundary, not just by configuration.

## Usage

    {:ok, signer} = SoftSigner.PKCS12.load("path/to/bundle.p12", password: "secret")

    {:ok, signed_pdf} =
      SignCore.PDF.sign(pdf,
        signer: signer,
        alg: :PS256,
        x5c: SoftSigner.PKCS12.cert_chain(signer)
      )

## Cert chain

PKCS#12 bundles carry the leaf cert (and often intermediate
CA certs). `cert_chain/1` returns them as a list of DER binaries,
leaf first — drop straight into the `:x5c` opt of any format
adapter.

# `t`

```elixir
@type t() :: %SoftSigner.PKCS12{
  chain_ders: [binary()],
  leaf_der: binary(),
  rsa_key: tuple()
}
```

# `cert_chain`

```elixir
@spec cert_chain(t()) :: [binary()]
```

Returns the bundle's certificate chain as a list of DER binaries,
leaf first. Drop into any format adapter's `:x5c` opt.

# `load`

```elixir
@spec load(
  Path.t(),
  keyword()
) :: {:ok, t()} | {:error, term()}
```

Load a PKCS#12 bundle from `path` and return a `%SoftSigner.PKCS12{}`
struct ready for `SignCore.Signer.sign/3`.

Required opts:

  * `:password` — bundle password (string).

Optional:

  * `:openssl` — path to the `openssl` CLI. Default
    `System.find_executable("openssl")`.

Returns `{:error, {:openssl, message}}` if the CLI is missing or
the bundle won't decrypt.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
