Get SSL expiry date from an http request using Mint / Finch / Mojito

Hi,

Does anyone know how to access the SSL certificate to get the expiry date from an http request using Mint / Finch / Mojito

Finch and Mojito do not expose the underlying connections at all, while Mint wraps them in its own, opaque structs. If you don’t mind accessing private, undocumented fields in Mint’s structs, then you can get the certificate from the socket like this:

with {:ok, conn} <- Mint.HTTP.connect(:https, "blog.voltone.net", 443),
     {:ok, der} <- :ssl.peercert(conn.socket) do
  :public_key.pkix_decode_cert(der)
end

You can find the ‘not before’ and ‘not after’ timestamps in the :Validity record.

1 Like

Brilliant thank you

I needed to add :plain to pkix_decode_cert

The code I ended up with is below in case it helps anyone else

def check_ssl(url) do
    uri = URI.parse(url)

    cert =
      with {:ok, conn} <- Mint.HTTP.connect(:https, uri.host, uri.port),
           {:ok, der} <- :ssl.peercert(conn.socket) do
        :public_key.pkix_decode_cert(der, :plain)
      end

    validity =
      cert
      |> elem(1)
      |> elem(5)

    case validity do
      {:Validity, {:utcTime, validFrom}, {:utcTime, validTo}} ->
        {:ok, from_cert_time(validFrom), from_cert_time(validTo)}

      x ->
        {:error, x}
    end
  end

  def from_cert_time(time) when is_list(time), do: List.to_string(time) |> from_cert_time()

  def from_cert_time(<<year::binary-size(2), month::binary-size(2), day::binary-size(2), _time::binary-size(6), "Z">>) do
    Date.new!(String.to_integer("20#{year}"), String.to_integer(month), String.to_integer(day))
  end

  def from_cert_time(<<year::binary-size(4), month::binary-size(2), day::binary-size(2), _time::binary-size(6), "Z">>) do
    Date.new!(String.to_integer(year), String.to_integer(month), String.to_integer(day))
  end

I needed to add :plain to pkix_decode_cert

Ah, right, sorry. I would use :otp myself, it makes it easier to work with extensions and RDNs. But it makes no difference for Validity.

If you are only interested in the certificate, and not making any HTTP request over the resulting connection, you may want to consider skipping the HTTP client altogether and just use :ssl.connect/3 (though that does not take into account proxies).

Finally, I just want to (shamelessly) plug my x509 package. With it you can do:

with {:ok, sock} <- :ssl.connect('blog.voltone.net', 443, []),
     {:ok, der} <- :ssl.peercert(sock),
     :ssl.close(sock),
     {:ok, cert} <- X509.Certificate.from_der(der),
     {:Validity, not_before, not_after} <- X509.Certificate.validity(cert) do
  {:ok, X509.DateTime.to_datetime(not_before), X509.DateTime.to_datetime(not_after)}
end
2 Likes