Most HTTP clients abstract away the connection layer: they let you pass in the options to tune the connection establishment, but when they return the response they don’t include any connection information. Even Mint, which give you quite a lot of control over the lower layers, doesn’t let you extract the server certificate using a public API.
Having said that, it is possible to kind of make this work with HTTPoison. First of all, we’ll use ‘async’ mode, in which the response is delivered to a process mailbox. Then we’ll hook into the certificate verification callback and deliver the server cert (if it is valid) to the same mailbox in a custom message:
HTTPoison.get(
"https://blog.voltone.net/",
[],
stream_to: self(),
proxy: {"localhost", 8080},
hackney: [
ssl_options: [
versions: [:"tlsv1.2"],
ciphers: :ssl.cipher_suites(:default, :"tlsv1.2"),
cacertfile: :certifi.cacertfile(),
depth: 3,
verify_fun: {fn
_, {:bad_cert, _} = reason, _ ->
{:fail, reason}
_,{:extension, _}, state ->
{:unknown, state}
_, :valid, state ->
{:valid, state}
peer_cert, :valid_peer, pid ->
if :public_key.pkix_verify_hostname(peer_cert, [dns_id: 'blog.voltone.net'], match_fun: :public_key.pkix_verify_hostname_match_fun(:https)) do
send(pid, {:peer_cert, peer_cert})
{:valid, pid}
else
{:fail, :hostname_check_failed}
end
end, self()}
]
]
)
Update the proxy address as needed, and remember to update the destination hostname in two places: the URI at the top, and the hostname verification function further down. Of course you could wrap this in a function that takes care of this automatically.
The result might look something like this:
iex(1)> HTTPoison.get(...)
{:ok, %HTTPoison.AsyncResponse{id: #Reference<0.1211898004.3575906308.200659>}}
iex(2)> flush
{:peer_cert,
{:OTPCertificate,
#...
}
}
%HTTPoison.AsyncStatus{
code: 200,
id: #Reference<0.1211898004.3575906308.200659>
}
%HTTPoison.AsyncHeaders{
headers: [
#...
]
}
#...
The certificate is sent as an Erlang record. You can convert it to other formats (DER, PEM) using the :public_key
module.
Finally, keep in mind that not every HTTP request necessarily results in a TLS handshake, and therefore a peer certificate message. The HTTP client may keep the connection open for reuse by multiple HTTP requests, and even if a new connection is established, TLS session resumption may cause the handshake to be skipped (pass reuse_sessions: false
in the ssl_options to prevent this).