defmodule ExAws.SNS.PublicKeyCache do use GenServer def start_link(opts \\ []) do GenServer.start_link(__MODULE__, :ok, opts) end def get(cert_url) do case :ets.lookup(__MODULE__, cert_url) do [{_cert_url, public_key}] -> {:ok, public_key} [] -> GenServer.call(__MODULE__, {:get_public_key, cert_url}) end end ## Callbacks def init(:ok) do ets = :ets.new(__MODULE__, [:named_table, read_concurrency: true]) {:ok, ets} end def handle_call({:get_public_key, cert_url}, _from, ets) do with {:ok, cert} <- fetch_certificate(cert_url), public_key <- get_public_key(cert) do :ets.insert(__MODULE__, {cert_url, public_key}) {:reply, {:ok, public_key}, ets} else error -> {:reply, error, ets} end end defp fetch_certificate(cert_url) do http_client = Application.get_env(:ex_aws, :http_client, ExAws.Request.Hackney) with {:ok, %{status_code: 200, body: cert_binary}} <- http_client.request(:get, cert_url) do get_pem_entry(:public_key.pem_decode(cert_binary)) else {:ok, %{status_code: status_code}} -> {:error, "Could not fetch certificate from #{cert_url}, expected http code 200, got: #{status_code}"} {:error, %{reason: reason}} -> {:error, "Unexpected error, could not fetch certificate from #{cert_url}, got #{inspect(reason)}"} end end defp get_pem_entry(pem_entries) do case pem_entries do [entry] -> try do {:ok, :public_key.pem_entry_decode(entry)} catch _kind, error -> {:error, "Unexpected error while decoding pem entry: #{inspect(error)}"} end entries -> {:error, "Invalid PEM entries: #{inspect(entries)}"} end end defp get_public_key(cert) do :public_key.der_decode(:RSAPublicKey, cert |> elem(1) |> elem(7) |> elem(2)) end end