# `Precompiled.Trust`
[🔗](https://github.com/TomHoenderdos/elixir_precompiled/blob/v0.2.1/lib/precompiled/trust.ex#L5)

The trust chain's top: a root document shipped with this library names the
keys allowed to sign batches; a batch names every manifest by hash. Both
travel in an envelope signed over raw bytes. Sequence numbers only go up.

Every entry point here parses attacker-reachable input (a shipped file, a
cached file, or whatever a server hands back), so every helper is written
to return an error tuple instead of raising, and the public functions that
touch the network or disk also carry a function-level `rescue` as a second
line of defense.

A batch read from a bundle (`bundle: true`) is bounded by its own expiry
instead of the 24 h staleness rule. A bundle's `offline.json`
(`verify_offline/3`) has no expiry at all, and on that path the root's
expiry is not checked either (`offline: true`).

# `batch`

```elixir
@type batch() :: %{
  optional(:source_unreachable) =&gt; true,
  optional(:offline) =&gt; true,
  sequence: integer(),
  manifests: %{required(String.t()) =&gt; String.t()},
  manifests_v2: %{required(String.t()) =&gt; String.t()} | nil,
  content: [String.t()] | nil
}
```

# `conditional_fetch`

```elixir
@type conditional_fetch() :: (String.t(), String.t() | nil -&gt;
                          {:ok, binary(), String.t() | nil}
                          | :not_modified
                          | {:error, term()})
```

# `root`

```elixir
@type root() :: map()
```

# `fetch_batch_cached`

```elixir
@spec fetch_batch_cached(map(), root(), conditional_fetch()) ::
  {:ok, batch()} | {:error, String.t()}
```

The current batch, asking the source only whether it changed: the last
verified batch and its ETag are kept in the state dir and the ETag is sent
as `If-None-Match`. A 304 says only that the source still serves those
bytes: they go through the same signature, freshness, clock and sequence
checks as fresh ones, every run, so a batch older than a day is refused
however often the source answers 304. New bytes replace the cache only
once they verify. Offline, the cached batch is checked the same way and
no request is made.

When the source cannot be reached (`{:error, {:unreachable, why}}` from
`fetch`: a transport error, timeout, 5xx or the run deadline), the cached
batch is used under the same rule as offline and one line says so. A
response the source did give and that is refused (bad signature, an
older sequence, malformed bytes, a 4xx) never falls back to the cache.

# `load_root`

```elixir
@spec load_root(map(), keyword()) :: {:ok, root()} | {:error, String.t()}
```

The root in use: the shipped root, advanced along the adopted chain in
`roots.json`. The shipped root must verify under its own root role; each
chain entry must also verify under the previous root's root role and have
a higher version, and the first one that fails ends the chain. As in TUF,
only the root in use (the last one) must be unexpired: an expired
intermediate root does not end the chain, and a release keeps working
past its shipped root's expiry once a newer root has been adopted.

With `offline: true`, for verifying a bundle's `offline.json` and nothing
else, the root in use is returned even when it has expired: an offline
document is valid indefinitely, so the clock must not end it. The chain
and every signature are checked as always.

The version this ends on can never drop below one `update_root/3` already
recorded: a stored `root_version` higher than the folded root's version
means `roots.json` lost the entries that led there (deleted, truncated, or
rolled back), and that is reported rather than silently accepted.

# `root_check_due?`

```elixir
@spec root_check_due?(map()) :: boolean()
```

Whether the served root should be fetched this run: never stamped, the
stamp `:root_check_seconds` (default an hour) old or older, or a stamp in
the future (a clock set back). A batch that fails to verify fetches the
root regardless (see `Precompiled.Check`).

# `root_keys`

```elixir
@spec root_keys(root(), String.t()) ::
  {%{required(String.t()) =&gt; binary()}, pos_integer() | nil}
```

The keys and threshold for a role, or `{%{}, nil}` when `root` doesn't name
that role with a usable shape. Total: never raises, whatever `root` is.

# `state_dir`

```elixir
@spec state_dir(map(), root()) :: Path.t()
```

Where the batch sequence and adopted roots live for this source: under
`config.home`, named by the source URL and the shipped root's first root
key, so two sources (or two trust anchors) never share replay state.
Always keyed by the SHIPPED root, so a rotation keeps the same dir.
`config[:state_key]`, when set, names it instead of the source URL: the
sources that share one key (a device's network source and its bundles)
share one sequence floor.

# `update_root`

```elixir
@spec update_root(
  map(),
  root(),
  (String.t() -&gt; {:ok, binary()} | {:error, term()}),
  keyword()
) ::
  {:ok, root()}
```

Adopt the source's served root when it chains from `root` (the root in
use), appending it to `roots.json` and recording its version as the
floor `load_root/1` will not go back below. Anything else keeps `root`.

With `offline: true`, for a bundle's `root.json` read to verify its
`offline.json`, a root that chains is adopted even when it has expired
(see `load_root/2`). The online path then chains from it to a newer,
unexpired root before it judges expiry (`Precompiled.Check`).

# `verify_envelope`

```elixir
@spec verify_envelope(
  map() | binary(),
  %{required(String.t()) =&gt; binary()},
  pos_integer()
) ::
  {:ok, binary()} | {:error, :bad_envelope | :below_threshold}
```

# `verify_offline`

```elixir
@spec verify_offline(map(), root(), binary()) :: {:ok, batch()} | {:error, String.t()}
```

A bundle's offline document (`offline.json`): manifest maps signed by the
root's `offline` role, with no expiry, for a device that stays offline
(`PRECOMPILED_API.md`, "Offline bundles"). The caller reads it only from
a `file://` source the user named.

Its keys come from the `offline` role of `root` alone, with a threshold
above zero and each keyid counted once. A key that any other role of the
root also names (the online root and batch key) never counts for it, so
a batch signature can never pass as an offline one. No clock is read: not
for the document, which has no expiry, and not for the root. The document
is held to its own sequence floor, `offline_state` in the state dir, apart
from the batch sequence, and the floor is raised to its sequence.

Returns the batch shape, with `content: nil` and `offline: true`.

Refused outright when `config.source` is not a `file://` URL: a bundle's
`offline.json` is honoured only from a source the user named as a bundle,
never from a network server that could otherwise use it to dodge the 24 h
batch staleness check.

---

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