SoftSigner.PKCS8 (soft_signer v0.1.0)

Copy Markdown View Source

Software signer backed by a PKCS#8 PEM private key plus a separate certificate (PEM, single or chained).

Different shape from SoftSigner.PKCS12: PKCS#12 bundles carry the cert with the key, so cert-chain extraction is automatic. PKCS#8 is just the key, so the caller supplies the cert separately. This split is common in cloud deployments where TLS-style key + cert files live next to each other on disk.

Supports both encrypted (-----BEGIN ENCRYPTED PRIVATE KEY-----) and unencrypted PKCS#8, plus the older PKCS#1 -----BEGIN RSA PRIVATE KEY----- format.

Threat model — key material is BEAM-resident

Same threat model as SoftSigner.PKCS12 — the decoded RSA private key lives in BEAM heap memory for the lifetime of any reference to the struct. The BEAM cannot zeroize managed terms; the GC may retain freed copies. The key is readable by anyone with BEAM-process memory access (debuggers, /proc/<pid>/mem, core dumps, swapped pages, hibernation snapshots).

Use pkcs11ex instead for threat models that require the key to stay on hardware. The package boundary between :soft_signer and :pkcs11ex is intentional: omitting :soft_signer from mix.lock prevents software signing by deployment topology, not just by configuration.

Usage

# From file paths:
{:ok, signer} =
  SoftSigner.PKCS8.load(
    key_path: "/keys/legal-proxy.key.pem",
    cert_path: "/keys/legal-proxy.cert.pem",
    password: "secret"     # only needed if the key is encrypted
  )

# From in-memory PEM strings (the same opts but suffix `_pem`):
{:ok, signer} =
  SoftSigner.PKCS8.load(
    key_pem: File.read!("/keys/legal-proxy.key.pem"),
    cert_pem: File.read!("/keys/legal-proxy.cert.pem")
  )

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

Options

Required (key — pick one):

  • :key_path — filesystem path to a PEM file.
  • :key_pem — PEM bytes already in memory.

Required (cert — pick one):

  • :cert_path — filesystem path to a PEM file. May contain a single cert or a chain (leaf first, then intermediates).
  • :cert_pem — PEM bytes already in memory.

Optional:

  • :password — required only if the key PEM is encrypted. Surfaces {:error, :wrong_password} on a mismatch.

Summary

Functions

Returns the cert chain as [leaf_der | intermediates_der] — drop into any format adapter's :x5c opt.

Load a PKCS#8 key + cert chain. See moduledoc for opts.

Types

t()

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

Functions

cert_chain(pkcs8)

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

Returns the cert chain as [leaf_der | intermediates_der] — drop into any format adapter's :x5c opt.

load(opts)

@spec load(keyword()) :: {:ok, t()} | {:error, term()}

Load a PKCS#8 key + cert chain. See moduledoc for opts.