SoftSigner.PKCS12 (soft_signer v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

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

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

Types

t()

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

Functions

cert_chain(pkcs12)

@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(path, opts)

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