Current section
Files
Jump to
Current section
Files
lib/challenge.ex
defmodule AcmeEx.Challenge do
require Logger
alias AcmeEx.Order
def child_spec(_opts \\ []) do
{Task.Supervisor, name: AcmeEx.ChallengeSupervisor, restart: :transient, max_restarts: 2}
end
def start_verify({order, account}, nil, _thumbprint) do
Logger.info(fn -> "Circumventing verification, no DNS records provided" end)
Order.update(account.id, %{order | status: :valid})
end
def start_verify({order, account}, dns, thumbprint) do
AcmeEx.ChallengeSupervisor
|> Task.Supervisor.start_child(fn ->
if verify_domains(order.domains, dns, order.token, thumbprint) do
Order.update(account.id, %{order | status: :valid})
else
Process.exit(self(), "Verification of #{order.domains |> Kernel.inspect()} failed.")
end
end)
end
def await_all() do
AcmeEx.ChallengeSupervisor
|> Task.Supervisor.children()
|> Enum.each(&await_verify/1)
end
def await_verify({:ok, pid}), do: await_verify(pid)
def await_verify(pid) do
ref = Process.monitor(pid)
receive do
{:DOWN, ^ref, _, _, _} -> :ok
end
end
def verify_domains(domains, dns, token, thumbprint) do
domains
|> Task.async_stream(fn domain ->
domain
|> server(dns)
|> verify_server(token, thumbprint)
end)
|> Enum.map(fn
{:ok, result} -> result
_ -> :error
end)
|> Enum.all?(&(&1 == :ok))
end
defp verify_server(server, token, thumbprint) do
server
|> request(token)
|> match(token, thumbprint)
|> (fn {ok, _msg} -> ok end).()
end
def request(server, token) do
Logger.info(fn -> "Sending challenge [#{token}] to #{server}" end)
:httpc.request(
:get,
{'http://#{server}/.well-known/acme-challenge/#{token}', []},
[],
body_format: :binary
)
end
defp match({:ok, {{_, 200, _}, _headers, reply}}, token, expected) do
token
|> challenge(expected)
|> case do
^reply -> {:ok, "Matched on #{reply}"}
other -> {:error, "Mistached and received #{other}"}
end
end
defp match({:ok, {{_, status, _}, _headers, _reply}}, _token, _expected), do: {:error, status}
defp match({:error, _reason} = error, _token, _expected), do: error
defp challenge(token, thumbprint), do: "#{token}.#{thumbprint}"
defp server(domain, dns) do
case Map.fetch(dns, domain) do
{:ok, resolver} -> resolver.()
:error -> domain
end
end
end