# `SignCore.JWS`
[🔗](https://github.com/utaladriz/pkcs11ex/blob/v0.1.0/lib/sign_core/jws.ex#L1)

JWS format adapter — detached (RFC 7797) by default, attached
(RFC 7515) opt-in.

## Wire formats

    <header_b64u>..<sig_b64u>            # detached (default, RFC 7797)
    <header_b64u>.<payload_b64u>.<sig>   # attached (opt-in, RFC 7515)

Detached form sets `b64: false` + `crit: ["b64"]` in the
protected header and uses the raw payload bytes (not base64url'd)
in the signing input. Attached form follows standard RFC 7515
framing — payload is base64url-encoded into the middle segment.

## Sign

    SignCore.JWS.sign(payload,
      signer: %SomeSigner{...},          # any SignCore.Signer impl
      alg: :PS256,
      x5c: leaf_der_binary,              # or [leaf_der, intermediate_der, ...]
      attached: false,                   # default — detached
      extra_headers: %{"kid" => "platform-1"}   # optional
    )

### Optional `:x5c` with `:kid`

When `:extra_headers` carries a `kid`, `:x5c` becomes optional —
the verifier will look up the cert by `kid` (via `:kid_certs` opt
on `verify/3` or a kid-aware policy). RFC 7515 §4.1.4.

## Verify

    SignCore.JWS.verify(jws, payload, opts \\ [])

Auto-detects detached vs attached from the wire format. For
detached, `payload` is required. For attached, `payload` may be
`nil` (extracted from the middle segment) or supplied (cross-
checked against the embedded payload — `:payload_mismatch` if
they differ).

Verification is **software-side** via OTP `:public_key`. No
PKCS#11 access needed. Trust resolution flows through
`SignCore.Policy` (see `:trust_policy` opt) or via `:kid_certs`
for kid-only flows. The signer's certificate from `x5c` is
treated as untrusted input until the policy or kid lookup
resolves it (specs.md §7.1).

# `jws`

```elixir
@type jws() :: binary()
```

# `payload`

```elixir
@type payload() :: iodata()
```

# `sign`

```elixir
@spec sign(
  payload(),
  keyword()
) :: {:ok, jws()} | {:error, term()}
```

Build a detached JWS over `payload` and return the wire-format string.

See the moduledoc for the option surface in Phase 1.

# `verify`

```elixir
@spec verify(jws(), payload() | nil, keyword()) :: {:ok, term()} | {:error, term()}
```

Verify a JWS, detached or attached.

Auto-detects from the wire format:

  * **Detached** (RFC 7797 — `<header>..<sig>`): the empty middle
    segment marks the payload as supplied externally. The
    `payload` argument is required.
  * **Attached** (RFC 7515 — `<header>.<payload>.<sig>`): the
    payload is encoded in the middle segment. The `payload`
    argument is optional; if supplied, it is cross-checked
    against the embedded payload (`:payload_mismatch` if they
    differ).

Returns `{:ok, subject_id}` on success.

## Identity resolution

By default, the embedded `x5c` chain is routed through the
configured `SignCore.Policy` (`resolve/2` then `validate/3`).
For kid-only JWS (no `x5c` in the header), supply `:kid_certs`
as a `%{kid_string => leaf_der}` map to look up the leaf cert
by `kid` directly:

    SignCore.JWS.verify(jws, payload, kid_certs: %{"acme-2025" => leaf_der})

When `:kid_certs` resolves a cert, `policy.resolve/2` is
bypassed but `policy.validate/3` still runs to derive the
`subject_id`.

---

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