Hi,
Does anyone know how to access the SSL certificate to get the 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.
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