Tincture.PDF.CMS (Tincture v0.1.0)

Copy Markdown View Source

Detached PKCS#7 / CMS signatures, for signing PDFs.

A PDF signature's /Contents holds a CMS SignedData blob covering the rest of the file. OTP ships no CMS encoder — :public_key handles certificates and raw signing, not this — so the structure is built here directly in DER.

Detached means the signed content is not carried inside the blob: the bytes live in the PDF, and the signature refers to them by digest. That is what lets a signature cover the very file it sits in.

What is produced

ContentInfo
  contentType  id-signedData
  content [0]  SignedData
    version           1
    digestAlgorithms  { sha256 | sha384 | sha512 }
    encapContentInfo  id-data, with no content  detached
    certificates [0]  the signer, plus any chain given
    signerInfos       one SignerInfo
      sid              issuer and serial number
      signedAttrs [0]  contentType, signingTime, messageDigest
      signature        RSA over the DER of signedAttrs

The signature covers the signed attributes, not the content directly, which is what CMS requires once signedAttrs is present. Those attributes carry the content's digest, so the content is still bound to the signature.

Interoperability

Output is verified against OpenSSL in the test suite rather than only against itself, because a signature format that only its author can read is not a signature format.

Summary

Types

digest()

@type digest() :: :sha256 | :sha384 | :sha512

Functions

sign_detached(content, private_key, certificate_der, chain, digest, signing_time)

@spec sign_detached(binary(), tuple(), binary(), [binary()], digest(), DateTime.t()) ::
  binary()

Build a detached CMS signature over content.

certificate_der is the signer's certificate; chain is any intermediate certificates to include, which a verifier needs when it does not already hold them. signing_time is a DateTime — passed in rather than read from the clock so that signing is reproducible and testable.