Skip to content

Instantly share code, notes, and snippets.

@wisq
Created May 12, 2023 04:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wisq/f66aeed2a6ab0116b5328191adb00bd8 to your computer and use it in GitHub Desktop.
Save wisq/f66aeed2a6ab0116b5328191adb00bd8 to your computer and use it in GitHub Desktop.
defmodule CertChecker do
alias X509.Certificate, as: Cert
def check_file(file) do
[cert | chain] = read_pem_file(file)
check_certs(cert, chain)
end
def check_certs(cert, chain) do
cert_map = chain |> map_by_subject()
ca_map = cacerts() |> map_by_subject()
with {:ok, path} <- path_to_ca(cert, cert_map, ca_map) do
[root_der | _] = path_der = [cert | path] |> Enum.reverse() |> Enum.map(&Cert.to_der/1)
:public_key.pkix_path_validation(root_der, path_der, verify_fun: {&verify/3, nil})
end
end
defp read_pem_file(file) do
file
|> File.read!()
|> :public_key.pem_decode()
|> Enum.map(fn {:Certificate, der, :not_encrypted} -> Cert.from_der!(der) end)
end
defp cacerts do
:certifi.cacerts()
|> Enum.map(&X509.Certificate.from_der!/1)
end
defp map_by_subject(certs) do
Map.new(certs, &{Cert.subject(&1), &1})
end
defp path_to_ca(cert, cert_map, ca_map) do
issuer = Cert.issuer(cert)
cond do
ca = Map.get(ca_map, issuer) ->
{:ok, [ca]}
next_cert = Map.get(cert_map, issuer) ->
with {:ok, path} <- path_to_ca(next_cert, cert_map, ca_map) do
{:ok, [next_cert | path]}
end
true ->
{:error, :no_path_to_ca}
end
end
def verify_debug(cert, msg, state) do
{common_name(cert), msg} |> IO.inspect()
verify(cert, msg, state)
end
def verify(_, {:bad_cert, _} = reason, _), do: {:fail, reason}
def verify(_, {:extension, _}, state), do: {:unknown, state}
def verify(_, :valid, state), do: {:valid, state}
def verify(_, :valid_peer, state), do: {:valid, state}
defp common_name(cert) do
cert
|> X509.Certificate.subject()
|> X509.RDNSequence.get_attr("commonName")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment