2.0 KiB · text History 4b6fe3b
defmodule OhMyExOhIrcd.IRC.Cloak do
  @moduledoc """
  Hashed-IP host cloaking.

  When cloaking is enabled (the listener has a non-nil `:cloak_host`), a
  connecting client's real IP is replaced in their `nick!user@host`
  prefix with `<hash>.<cloak_host>`, e.g. `a1b2c3d4e5f6.irc.skyjeep.club`.

  Properties:

    * **Stable** — the same IP always yields the same cloak, so per-host
      bans (`*@a1b2c3d4e5f6.irc.skyjeep.club`) work.
    * **Opaque** — the IP can't be recovered from the cloak; the hash is
      salted with a per-deployment secret so cloaks aren't comparable
      across networks or brute-forceable from the IP space alone.

  Operators still see the real IP via WHOIS numeric 338 (RPL_WHOISACTUALLY),
  and the real IP is what K-line / O-line matching uses — cloaking is a
  display concern only.
  """

  # Length of the lowercase-hex hash label. 12 hex chars = 48 bits of the
  # SHA-256 digest: short enough to read, wide enough that collisions are
  # not a practical concern at our scale.
  @hash_len 12

  @doc """
  Compute the display host for `real_ip`. With a nil/blank `base`,
  cloaking is off and the real IP is returned unchanged.
  """
  @spec host(String.t(), String.t() | nil) :: String.t()
  def host(real_ip, base) when is_binary(base) and base != "" and is_binary(real_ip) do
    "#{hash(real_ip)}.#{base}"
  end

  def host(real_ip, _base), do: real_ip

  defp hash(ip) do
    :crypto.hash(:sha256, salt() <> ip)
    |> Base.encode16(case: :lower)
    |> binary_part(0, @hash_len)
  end

  # Per-deployment salt. Prefer an explicit `:cloak_salt`; otherwise reuse
  # the endpoint's secret_key_base (already required in prod) so cloaks are
  # unguessable without copying yet another secret around. Falls back to a
  # constant only in dev/test where there is no secret.
  defp salt do
    Application.get_env(:oh_my_ex_oh_ircd, __MODULE__, [])[:salt] ||
      Application.get_env(:oh_my_ex_oh_ircd, OhMyExOhIrcdWeb.Endpoint, [])[:secret_key_base] ||
      "oh-my-ex-oh-cloak-dev-salt"
  end
end