# `Ankusa.Verifier.ClientCert`
[🔗](https://github.com/jamescarr/ankusa/blob/ankusa-v0.7.0/packages/ankusa/lib/ankusa/verifier/client_cert.ex#L1)

The sender is whoever holds the client certificate the edge listener's mutual
TLS handshake accepted (`tls.verify: :verify_peer`, `Ankusa.Listener`), for
providers and internal systems that authenticate by certificate rather than by
signing the body. Like `Ankusa.Verifier.Token` it says who is calling, not
what the body is.

`opts`, at least one of:

  * `:fingerprints` — SHA-256 fingerprints of the DER certificates that may
    post to this source: 64 hex characters, any case, optional `:`
    separators (what `openssl x509 -fingerprint -sha256` prints). A pin
    holds whatever the listener's CA bundle says.
  * `:subjects` — identities a certificate may carry, in the form
    `Ankusa.Admin.Auth.MTLS.identities/1` produces: `dns:<name>`,
    `email:<address>`, `uri:<uri>`, `ip:<address>` (subject alternative
    names) or `cn:<common name>`. A subject pin trusts the listener's CA to
    have issued that identity only to the party meant: scope the listener's
    `cacertfile` to a CA that does, or pin fingerprints instead.

A certificate matching either list is accepted. Neither list set, a
malformed fingerprint or a subject without a known prefix is `:bad_pin` for
every hook, and a source declared in config refuses to boot (also when the
edge listener has no `verify: :verify_peer`: without it no certificate ever
arrives, see `Ankusa.Verifier.validate_config!/1`).

The certificate arrives as `opts[:peer_cert]`, the DER the TLS handshake
accepted (`Plug.Conn.get_peer_data/1`'s `:ssl_cert`), put there by
`Ankusa.Edge.Router` for a source with this verifier and nobody else. No
certificate (a plain-HTTP listener, or a re-verification from the quarantine
pen, which does not keep one) is `:missing_signature`; a certificate that
cannot be parsed, with `:subjects` set and no fingerprint match, is
`:malformed_signature`; a valid certificate nobody pinned is `:no_match`. Use
`on_verify_failure: reject` for a certificate source: a quarantined hook
cannot be released by replay.

# `normalize_fingerprint`

```elixir
@spec normalize_fingerprint(term()) :: {:ok, String.t()} | :error
```

The normalised form of a fingerprint (lowercase hex, no separators), or `:error`.

# `scheme_name`

```elixir
@spec scheme_name(keyword()) :: String.t()
```

Human name of the scheme for telemetry.

# `validate_opts`

```elixir
@spec validate_opts(keyword()) :: :ok | {:error, :bad_pin}
```

Check `opts` the way `verify/2` would use them, without a request: at least
one pin, every fingerprint 64 hex characters, every subject a known identity
form. `:ok`, or `{:error, :bad_pin}`.

---

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