SignCore.CMS.SignedData (sign_core v0.1.0)

Copy Markdown View Source

Assemble a CMS SignedData ContentInfo envelope (RFC 5652 §5).

The hardware signature is supplied by the caller — this module never signs. The expected flow:

{:ok, attrs} = SignCore.CMS.SignedAttributes.build(digest: payload_digest)
{:ok, tbs}   = SignCore.CMS.SignedAttributes.to_be_signed(attrs)
{:ok, sig}   = Pkcs11ex.sign_bytes(tbs, signer: {:platform, :signing}, alg: :PS256)
{:ok, der}   = SignCore.CMS.SignedData.build(attrs, sig,
                 certificates: [leaf_x509 | issuer_x509s],
                 digest_algorithm: :sha256,
                 signature_algorithm: :rsa_pss_sha256)

The result is a self-contained DER blob suitable for embedding in PAdES /Contents, an XML <Object> element, or any other CMS-shaped envelope. The PAdES adapter glues this DER (hex-encoded) into the reserved /Contents placeholder.

What ships in v1

  • One SignerInfo per envelope (single-signer documents — the Phase 4 contract).
  • IssuerAndSerialNumber signer identifier (CMS version 1). The SubjectKeyIdentifier form (CMS version 3) lands later if the maintainer asks; PAdES B-B defaults to issuer+serial.
  • Detached content (eContent absent) — the signed payload is the bytes covered by /ByteRange, not embedded inside the CMS.
  • unsignedAttrs omitted (B-B specifically — B-T's timestamp token lives there in Phase 5 work).

What's deferred

  • Multi-signer SignedData (counter-signing).
  • SubjectKeyIdentifier signer identifier (CMS v3).
  • OriginatorInfo, crls, attribute certificates (none needed for B-B).
  • Embedded eContent (the EncapsulatedContentInfo eContent field is always absent in this v1 — detached signatures only).

Summary

Types

Per-call options for build/3.

Functions

Assemble a SignedData ContentInfo from already-built signedAttrs, the hardware-produced signature bytes, and the certificate chain.

Parse a CMS ContentInfo DER and project it into a SignCore.CMS.Parsed struct that carries everything a verify pipeline needs (the to-be-signed bytes, the signature, the leaf cert, the message digest and signing time from the signed attributes).

Types

build_opts()

@type build_opts() :: keyword()

Per-call options for build/3.

Required:

  • :certificates[SignCore.X509.t() | binary()]. Leaf first, then any issuers / intermediates the verifier may need. Each entry can be a parsed SignCore.X509 struct or a raw DER binary (parsed inline). The leaf identifies the signer for the IssuerAndSerialNumber field.

Optional:

  • :digest_algorithm:sha256 (default) | :sha384 | :sha512.

  • :signature_algorithm:rsa_sha256 (default — PKCS#1 v1.5) | :rsa_pss_sha256. Match the algorithm the hardware actually produced; the OID lands inside SignerInfo.signatureAlgorithm.
  • :content_oid — defaults to id-data. Must match the :content_oid passed to SignedAttributes.build/1; the codec doesn't cross-check.

Functions

build(signed_attrs, signature, opts)

@spec build(signed_attrs :: [tuple()], signature :: binary(), opts :: build_opts()) ::
  {:ok, binary()} | {:error, term()}

Assemble a SignedData ContentInfo from already-built signedAttrs, the hardware-produced signature bytes, and the certificate chain.

parse(der)

@spec parse(binary()) :: {:ok, SignCore.CMS.Parsed.t()} | {:error, term()}

Parse a CMS ContentInfo DER and project it into a SignCore.CMS.Parsed struct that carries everything a verify pipeline needs (the to-be-signed bytes, the signature, the leaf cert, the message digest and signing time from the signed attributes).

Rejects:

  • non-id-signedData ContentInfo (returns {:error, :not_signed_data})
  • envelopes carrying anything other than exactly one SignerInfo (single-signer is the v1 contract — multi-signature support is Phase 4b territory)
  • empty certificate sets (we need the leaf to verify the signature)
  • malformed or absent required signed attributes (messageDigest)