# `Pkcs11ex.Audit`
[🔗](https://github.com/utaladriz/pkcs11ex/blob/v0.1.0/lib/pkcs11ex/audit.ex#L1)

Append-only hash-chained audit log.

> #### Future extraction {: .info}
>
> Per `docs/specs/specs.md` §9 Phase 5, audit lives in a sister
> library `pkcs11ex_audit`. This namespace ships inside `pkcs11ex` for
> now to keep the working session moving; the public API
> (`Pkcs11ex.Audit`, `Pkcs11ex.Audit.Entry`, `Pkcs11ex.Audit.Storage`)
> is what gets extracted, with the same module names.

## What this is

A tamper-evident log: each entry's `:content_hash` includes the
previous entry's hash as a prefix. Walking the chain end-to-end and
recomputing each hash detects any modification — `verify/1` does that
walk and reports the first divergence.

Storage is pluggable (`Pkcs11ex.Audit.Storage` behaviour). The library
ships `Pkcs11ex.Audit.Storage.InMemory` for dev/tests; production
deployments plug a durable adapter (Postgres, SQLite, append-only
files, S3 with Object Lock, etc.).

## What this is NOT

  * **Authenticated**. The chain proves "no entry was modified after
    insertion" given an honest verifier holding the head hash. It does
    NOT prove "the operator didn't replay/truncate the whole chain
    from a saved state." External anchoring (RFC 3161 trusted
    timestamping over the chain head) is the answer to that — Phase 5
    step 2.
  * **Encrypted**. Payload is stored in cleartext per the storage
    adapter's contract. Apps that need confidentiality encrypt the
    payload before calling `append/3`.
  * **A signing primitive**. The "chain root signed by the platform
    key" pattern lives a layer above; this module is the substrate.

## Usage

    {:ok, _} = Pkcs11ex.Audit.Storage.InMemory.start_link(name: :sigs)
    audit = Pkcs11ex.Audit.new(Pkcs11ex.Audit.Storage.InMemory, :sigs)

    {:ok, entry} =
      Pkcs11ex.Audit.append(audit, %{
        jws: jws,
        subject_id: :acme_corp,
        key_ref: {:platform, :signing}
      })

    :ok = Pkcs11ex.Audit.verify(audit)

# `append_opts`

```elixir
@type append_opts() :: [{:inserted_at, DateTime.t()}]
```

# `t`

```elixir
@type t() :: %Pkcs11ex.Audit{storage_handle: term(), storage_module: module()}
```

# `anchor_head`

```elixir
@spec anchor_head(t(), String.t(), keyword()) ::
  {:ok, Pkcs11ex.Audit.Entry.t()} | {:error, term()}
```

Anchor the current chain head against an RFC 3161 Time-Stamping
Authority. Reads the head, sends its `content_hash` to the TSA, stores
the returned TimeStampToken (TST) as a new audit entry whose payload
carries the anchored seq + hash + opaque TST bytes.

This addresses the "operator-replay/truncate" gap of a bare hash chain
by binding the chain state to a TSA-attested time. The TST itself is
a CMS SignedData; auditors verify its signature against the TSA's
cert chain (out of scope for this library — store the bytes, hand to
whoever audits).

## Required

  * `tsa_url` — the TSA's HTTP endpoint (e.g.,
    `"http://timestamp.digicert.com"`).

## Optional opts

  * `:timeout` — milliseconds, default 10_000.

## Returns

`{:ok, anchor_entry}` on success, where `anchor_entry.payload` is a
map `%{kind: :rfc3161_anchor, anchored_seq, anchored_hash, nonce, tst}`.
Returns `{:error, :empty_chain}` if there's nothing to anchor.

# `append`

```elixir
@spec append(t(), term(), append_opts()) ::
  {:ok, Pkcs11ex.Audit.Entry.t()} | {:error, term()}
```

Append `payload` as a new entry. Returns the constructed `Entry`.

Reads the current head, computes `content_hash`, and asks the storage
to persist. Storage adapters are expected to serialize concurrent
appends (see `Pkcs11ex.Audit.Storage` moduledoc).

# `at`

```elixir
@spec at(t(), pos_integer()) :: {:ok, Pkcs11ex.Audit.Entry.t()} | {:error, :not_found}
```

Convenience wrapper around the storage's `at/2`.

# `head`

```elixir
@spec head(t()) :: {:ok, Pkcs11ex.Audit.Entry.t()} | {:error, :empty}
```

Convenience wrapper around the storage's `head/1`.

# `new`

```elixir
@spec new(module(), term()) :: t()
```

Construct an audit reference around a running storage process / handle.

# `verify`

```elixir
@spec verify(t()) :: :ok | {:error, :empty_chain | {atom(), pos_integer()}}
```

Walk the chain head-to-tail. Recomputes each `content_hash` and checks
the `prev_hash` linkage. Returns `:ok` on a clean chain,
`{:error, :empty_chain}` for a chain with no entries, or
`{:error, {reason, seq}}` at the first divergence.

An empty chain returns `:empty_chain` (not `:ok`) so callers can
distinguish "nothing to verify" from "everything verified clean."
Database-wipe attacks reduce a populated chain to empty; treating
empty as success would silently obscure that. RFC 3161 anchoring
(see `anchor_head/3`) is the cross-cutting answer to truncation,
but `verify/1` should at least surface the truncation-shaped state.

Reasons for a divergent chain:
  * `:seq_gap` — `seq` doesn't follow the previous entry's `seq + 1`.
  * `:prev_hash_mismatch` — `prev_hash` doesn't match the previous
    entry's `content_hash`.
  * `:content_hash_mismatch` — recomputed hash differs from the stored
    one (the entry's `payload` or `inserted_at` was tampered with).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
