PdfElixide.Signature.Certificate (pdf_elixide v0.23.0)

Copy Markdown View Source

An X.509 certificate: who a signature names as its signer, and the window the issuer vouched for that name.

PdfElixide.Signature.certificate/1 reads the one a signature carries. parse/1, parse_pem/1 and parse_pkcs12/2 read certificates you hold yourself, from DER, PEM or a .p12 / .pfx file:

{:ok, [signature]} = PdfElixide.Signature.list(doc)
{:ok, certificate} = PdfElixide.Signature.certificate(signature)
certificate.subject_common_name
#=> "pdf_elixide test signer"

The "The signer's certificate" section of the Signatures guide puts this in sequence with the rest of a signature check.

Nothing here is a trust decision

The fields are claims read from the certificate. They are not checked against a trusted root, a revocation source or a list of accepted signers. Use :der with :public_key or another library to make those decisions.

Names

:subject and :issuer use RFC 4514 form, with the most specific attribute first. Values that cannot be rendered as text appear as oid=#hex rather than being dropped.

:subject_common_name is the unescaped common name nearest the leaf. It is nil when the subject has no textual common name and may contain text that the full :subject renders as hexadecimal.

A certificate is a plain value with no handle behind it, so it survives PdfElixide.Document.close/1 and can be passed between processes freely.

Summary

Types

t()

One parsed X.509 certificate.

Functions

Parses a DER-encoded X.509 certificate.

Same as parse/1, but raises PdfElixide.Error on failure.

Parses every certificate in PEM text, in file order.

Same as parse_pem/1, but raises PdfElixide.Error on failure.

Reads the certificates out of a PKCS#12 (.p12 / .pfx) file.

Whether the certificate's validity window covers instant.

Types

t()

@type t() :: %PdfElixide.Signature.Certificate{
  der: binary(),
  issuer: String.t() | nil,
  not_after: DateTime.t(),
  not_before: DateTime.t(),
  serial: String.t(),
  subject: String.t() | nil,
  subject_common_name: String.t() | nil
}

One parsed X.509 certificate.

  • :der — the certificate's DER without trailing padding, suitable for OTP's :public_key or another certificate library.
  • :subject — who the certificate is about, in RFC 4514 form. nil for a certificate that names no subject at all, which is legal for one identified only by an extension.
  • :subject_common_name — the common name nearest the leaf of :subject, unescaped. See "Names" in the module documentation for when it is nil.
  • :issuer — who issued it, in RFC 4514 form. nil only for a certificate that names no issuer, which is not a conforming one.
  • :serial — the issuer's serial number as uppercase hexadecimal DER content bytes, without a 0x prefix or fixed width. Any sign octet is preserved. This matches PdfElixide.Signature.Timestamp's :serial.
  • :not_before, :not_after — the window the issuer vouched for, as DateTimes in UTC with one-second resolution. valid_at?/2 compares an instant against them.

Functions

parse(der)

@spec parse(binary()) :: {:ok, t()} | {:error, PdfElixide.Error.t()}

Parses a DER-encoded X.509 certificate.

Tolerates trailing zero padding, so a PdfElixide.Signature.DSS entry can be passed straight in:

{:ok, dss} = PdfElixide.Signature.dss(doc)
[der | _] = dss.certificates
{:ok, certificate} = PdfElixide.Signature.Certificate.parse(der)
certificate.issuer
#=> "CN=pdf_elixide test signer,O=pdf_elixide,C=UA"

Reports %PdfElixide.Error{reason: :invalid_pdf} for a malformed or unsupported certificate, an unrepresentable validity date, or trailing bytes other than zero padding. A second concatenated certificate is a trailing value, not a chain, and is rejected.

parse!(der)

@spec parse!(binary()) :: t()

Same as parse/1, but raises PdfElixide.Error on failure.

parse_pem(pem)

@spec parse_pem(binary()) :: {:ok, [t(), ...]} | {:error, PdfElixide.Error.t()}

Parses every certificate in PEM text, in file order.

Reads CERTIFICATE blocks, including the legacy X509 CERTIFICATE label. Surrounding text and other blocks, including private keys, are ignored rather than decoded.

{:ok, certificates} =
  "fullchain.pem" |> File.read!() |> PdfElixide.Signature.Certificate.parse_pem()

Reports %PdfElixide.Error{reason: :not_found} when the input holds no certificate block, and :invalid_pdf when a block is malformed or does not hold exactly one X.509 certificate. One bad block fails the whole call.

parse_pem!(pem)

@spec parse_pem!(binary()) :: [t(), ...]

Same as parse_pem/1, but raises PdfElixide.Error on failure.

parse_pkcs12(data, password)

@spec parse_pkcs12(binary(), String.t()) ::
  {:ok, [t(), ...]} | {:error, PdfElixide.Error.t()}

Reads the certificates out of a PKCS#12 (.p12 / .pfx) file.

Returns every certificate in the file. When the file records which one belongs to its private key, that certificate and its available issuer chain come first; the rest keep their stored order. This ordering verifies nothing. The private key is neither decrypted nor returned.

{:ok, mine} =
  "signer.p12"
  |> File.read!()
  |> PdfElixide.Signature.Certificate.parse_pkcs12("passphrase")

password is text, as PKCS#12 defines it, so it must be valid UTF-8. Pass "" for a file protected by an empty password.

Reports %PdfElixide.Error{reason: :wrong_password} when an integrity check rejects the password. A file without one cannot reject a password; unreadable encrypted certificate data instead reports :invalid_pdf. Malformed input also reports :invalid_pdf, a file without certificates reports :not_found, and an unsupported protection scheme reports :unsupported.

parse_pkcs12!(data, password)

@spec parse_pkcs12!(binary(), String.t()) :: [t(), ...]

Same as parse_pkcs12/2, but raises PdfElixide.Error on failure.

valid_at?(certificate, instant)

@spec valid_at?(t(), DateTime.t()) :: boolean()

Whether the certificate's validity window covers instant.

Both ends are inclusive.

PdfElixide.Signature.Certificate.valid_at?(certificate, DateTime.utc_now())
#=> true

For a signature, compare against its claimed signing time from PdfElixide.Signature.signing_time_utc/1, or against the :time of a PdfElixide.Signature.Timestamp whose attachment was confirmed by PdfElixide.Signature.verify_timestamp/2 and whose authenticity was checked by PdfElixide.Signature.Timestamp.verify/1. Only the latter is evidence rather than the signer's claim, and it is still only as trustworthy as the timestamp authority. A true result does not establish trust or rule out revocation.