Elixir SSL Verification

Motivation: certificate pinning is the simplest way to protect self-signed SSL certificate against MITM attack.

I tried ssl_verify_fun.erl but it ends up letting everything pass, because the verify_fun callback always calls with bad_cert. Read the doc on verify_fun, you will see why I exclude extension and perform verification for all other cases.

You can plug the 2 functions below into your own module and use it in ssl options like so {:verify_fun, {&YourModule.verify_fun/3, {:sha, "SHA1_FINGERPRINT_ALL_CAPS"}}}.

def verify_fun(_, {:extension, _}, state) do
  {:unknown, state}
end
def verify_fun(cert, _, state) do
  case state do
    {:sha, match} -> verify_cert_fingerprint(cert, match)
    _ ->
      {:fail, :fingerprint_no_match}
  end
end

def verify_cert_fingerprint(cert, match) do
  cert_binary = :public_key.pkix_encode(:OTPCertificate, cert, :otp)
  hash = :crypto.hash(:sha, cert_binary) |> Base.encode16
  case hash == match do
    true -> {:valid, hash}
    false -> {:fail, :fingerprint_no_match}
  end
end

How to use it with httpotion:

opts = [
  ssl: [
    {:verify_fun,
      {&YourModule.verify_fun/3, {:sha, "SHA1_FINGERPRINT_ALL_CAPS"}}
    },
    {:verify, :verify_none}
  ]
]
HTTPoison.post "http://localhost", "body", [], opts

And to get SHA1 fingerprint:

openssl x509 -in cert.pem -sha1 -noout -fingerprint | sed s/://g