soft_signer

Software-key implementations of SignCore.Signer for PKCS#12 (.p12 / .pfx) bundles and PKCS#8 PEM private keys.

Part of the pkcs11ex family. Pair with sign_core to sign PDFs, XMLs, or JWS payloads with filesystem-resident keys.

When to use

If your production deployment runs against a hardware HSM, use pkcs11ex instead — keep soft_signer out of the dep tree to enforce "no software signing" at the package boundary.

PKCS#12

{:ok, signer} = SoftSigner.PKCS12.load("invoice-signer.p12", password: "...")

{:ok, signed_pdf} =
  SignCore.PDF.sign(pdf,
    signer: signer,
    alg: :PS256,
    x5c: SoftSigner.PKCS12.cert_chain(signer)   # P12 carries its own chain
  )

P12 decryption shells out to the openssl pkcs12 CLI — pure-Erlang PKCS#12 decode is fragile across vendor encodings, so we let openssl handle the bytes-to-PEM step. Sign math runs through :public_key.sign/3 with PSS padding for :PS256 or PKCS#1 v1.5 for :RS256.

Errors:

PKCS#8 PEM (key + separate cert)

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

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

Supports:

You can supply key and cert from in-memory PEM strings instead of paths:

{:ok, signer} =
  SoftSigner.PKCS8.load(
    key_pem: System.get_env("SIGNING_KEY_PEM"),
    cert_pem: File.read!("/keys/legal-proxy.cert.pem")
  )

The cert PEM may contain a single certificate or a chain (leaf first, then intermediates). Errors:

Why two structs not one

Both SoftSigner.PKCS12 and SoftSigner.PKCS8 produce the same internal shape (%{rsa_key, leaf_der, chain_ders}) and share their defimpl SignCore.Signer logic. They're separate modules because the load contract differs:

Keeping them separate makes the type signature of each load function unambiguous and avoids a Boolean opt to switch between modes.

Algorithm support

Adding ECDSA / Ed25519 is a small extension to the defimpl block; not implemented yet because the production keys we've encountered are all RSA-2048.

License

Apache 2.0.