Pluggable signature-source contract used by SignCore.PDF.sign/2,
SignCore.XML.sign/2, and SignCore.JWS.sign/2.
Implementations carry whatever state is needed to produce a raw signature over arbitrary bytes — a PKCS#11 slot reference, a loaded PKCS#12 bundle, a cloud KMS handle, etc. Each provider ships a struct that implements this protocol.
Contract
sign(signer, tbs, opts)returns{:ok, raw_signature}or{:error, reason}.tbsis the bytes the format adapter wants signed (e.g., DER-encodedsignedAttrsfor CMS, canonical<ds:SignedInfo>for XML-DSig, the JWS signing input for JWS).optscarries the algorithm + format hints — at minimum:alg(atom —:PS256,:RS256, ...) and:encoding_context(:deror:jose). Implementations ignore opts they don't recognise.- The returned
raw_signatureis in the wire format the requested:encoding_contextexpects::der— for ECDSA, DERSEQUENCE(r, s); for RSA, raw octets.:jose— for ECDSA, fixed-sizer ‖ s; for RSA, raw octets.
Why a protocol
Different providers carry different state: pkcs11ex's signer is a
small struct (%Pkcs11ex.Signer{slot_ref, key_ref}) that resolves
to a running Pkcs11ex.Slot.Server lookup. soft_signer's signer
is a struct holding the loaded RSA key plus optional cert chain
(%SoftSigner.PKCS12{rsa_key, leaf, chain}). A protocol
dispatches over the struct type cleanly.
Implementing for a new provider
defmodule MyProvider.Signer do
defstruct [:state]
end
defimpl SignCore.Signer, for: MyProvider.Signer do
def sign(%MyProvider.Signer{} = signer, tbs, opts) do
alg = Keyword.fetch!(opts, :alg)
encoding_context = Keyword.get(opts, :encoding_context, :der)
# Compute the raw signature however your provider does it,
# then format for the requested context via the algorithm
# adapter (which knows e.g. ES256 raw -> DER conversion).
with {:ok, raw} <- do_sign(signer.state, tbs, alg),
{:ok, adapter} <- SignCore.Algorithm.lookup(alg) do
adapter.encode_signature(raw, encoding_context)
end
end
end
Summary
Functions
Sign tbs and return the raw signature bytes formatted for the
requested :encoding_context (defaulting to :der).
Types
@type t() :: term()
All the types that implement this protocol.