# `Sat.Certificados.Ocsp`

Validación OCSP (Online Certificate Status Protocol — RFC 6960) contra
el responder del SAT.

El SAT expone `https://cfdi.sat.gob.mx/edofiel` para consultar el estado
(`GOOD` / `REVOKED`) de un certificado en línea. Para validar un certificado
de un contribuyente se necesitan tres certificados:

  1. **subject**: el cert del contribuyente que estás verificando.
  2. **issuer**: el cert raíz del SAT que lo emitió (AC4 o AC5).
  3. **ocsp**: el cert que firma la respuesta del responder OCSP del SAT.

## Ejemplo

    {:ok, subject} = Certificate.from_file("contribuyente.cer")
    {:ok, issuer}  = Certificate.from_file("AC5_SAT.cer")
    {:ok, ocsp_c}  = Certificate.from_file("ocsp.ac5_sat.cer")

    ocsp = Ocsp.new!("https://cfdi.sat.gob.mx/edofiel", issuer, subject, ocsp_c)
    {:ok, %{status: status, revocation_time: t}} = Ocsp.verify(ocsp)

## Parsing offline

Las funciones `parse_response_status/1` y `parse_certificate_status/1`
permiten inspeccionar respuestas OCSP grabadas en archivo, útiles para
pruebas sin red.

## Tests

La suite de este paquete incluye tests offline (parsing de respuestas
OCSP grabadas en `packages/files/certificados/efirma/{revoked,tryLater}.der`)
que corren siempre con `mix test`.

El test que pega al endpoint en vivo del SAT está marcado con `@tag :online`
y **no corre por default** para evitar requerir red en CI. Para ejecutarlo:

    mix test --include online

Lo recomendado es dejarlo así: el test online es flaky (depende de la
disponibilidad del responder del SAT) y los offline ya cubren toda la
lógica de parseo y verificación de firma.

# `cert_status_result`

```elixir
@type cert_status_result() :: %{
  :status =&gt; status(),
  optional(:revocation_time) =&gt; DateTime.t()
}
```

# `status`

```elixir
@type status() :: :good | :revoked | :unknown | :undefined
```

# `t`

```elixir
@type t() :: %Sat.Certificados.Ocsp{
  issuer: Sat.Certificados.Certificate.t(),
  ocsp_cert: Sat.Certificados.Certificate.t(),
  subject: Sat.Certificados.Certificate.t(),
  url: String.t()
}
```

# `verify_response`

```elixir
@type verify_response() :: %{
  :status =&gt; status(),
  optional(:revocation_time) =&gt; DateTime.t(),
  ocsp_request_base64: String.t(),
  ocsp_response_base64: String.t()
}
```

# `new`

```elixir
@spec new(
  String.t(),
  Sat.Certificados.Certificate.t(),
  Sat.Certificados.Certificate.t(),
  Sat.Certificados.Certificate.t()
) :: {:ok, t()} | {:error, :invalid_url}
```

Construye una struct `%Ocsp{}` validando la URL.

# `new!`

```elixir
@spec new!(
  String.t(),
  Sat.Certificados.Certificate.t(),
  Sat.Certificados.Certificate.t(),
  Sat.Certificados.Certificate.t()
) :: t()
```

Como `new/4`, pero lanza `ArgumentError` si la URL es inválida.

# `parse_certificate_status`

```elixir
@spec parse_certificate_status(binary()) :: cert_status_result()
```

Extrae `%{status, revocation_time?}` desde el DER de una `BasicOCSPResponse`.
Útil para inspeccionar respuestas pre-grabadas.

# `parse_response_status`

```elixir
@spec parse_response_status(binary()) :: status() | :try_later | :successful | atom()
```

Parsea el `responseStatus` (primer campo de OCSPResponse, RFC 6960 §4.2.1)
desde el DER. Útil para inspeccionar respuestas pre-grabadas.

# `verify`

```elixir
@spec verify(t()) :: {:ok, verify_response()} | {:error, term()}
```

Construye la solicitud OCSP, la POSTea al endpoint, parsea la respuesta y
devuelve el estado del certificado del contribuyente.

Lanza si el servicio responde error o si la firma de la respuesta no
corresponde al `ocsp_cert`.

---

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