Using client certificates from a string with HTTPosion

Hi all,

I’m stuck with a problem and would really appreciate any feedback or ideas you might have.

The problem

We have a Phoenix app deployed on Heroku and it’s working well. We have a requirement to integrate with a third-party service over HTTPS. That service has provided us with a client certificate that we should use when calling their end-points. We are using HTTPosion to do this and locally in development it all works fine when we read the certs from a file.

However, we need to get these certs deployed to Heroku. We don’t want to add these files to the repository as they are sensitive and Heroku doesn’t allow file storage separately for you app to read.

Possible Solution

One option we are trying out is to put the content of these certificate files in environment variables in Heroku (like all other config) and then reading those strings and using them instead of files. However, we have been unable to get this to work. Here is what the code looks like that works with files:

{:ok, response} = HTTPoison.post(
  url, xml, headers, http_options()
)

...

defp http_options do
  [
    recv_timeout: 30_000,
    ssl: [
      certfile: "path/to/cert/file",
      keyfile: "path/to/key/file",
      password: String.to_char_list("cert password")
    ]
  ]
end

When we try to use the certs from strings we get errors, here is the http_options function we tried with certs inline (snipped) for testing:

def http_options do
    public = """
    -----BEGIN CERTIFICATE-----
    MIIC0DCCAjmgAwIBAgICAyIwDQYJKoZIhvcNAQEFBQAweDELMAkGA1UEBhMCR0Ix
    ...
    L7FNVw==
    -----END CERTIFICATE-----
    """

    private = """
    -----BEGIN RSA PRIVATE KEY-----
    Proc-Type: 4,ENCRYPTED
    DEK-Info: DES-EDE3-CBC,3C6A3A4613A1C924

    OSJjqiIcqVIvljlzOmYGq3MARW86TPUouLymH2aZ4yIxJ2coQAZBm7cy3gStUJrh
    ....
    vGac+7HotjLzeY5rmuijFOdbg+EUCfJo/D6DmlNs/2a2deu2RDohYQ==
    -----END RSA PRIVATE KEY-----
    """

    [
      recv_timeout: 30_000,
      ssl: [
        cert: cert(public),
        key: cert(private, "a-password")
      ]
    ]
  end

  defp cert(text) do
    [pem] = text |> :public_key.pem_decode
    :public_key.pem_entry_decode(pem)
  end

  defp cert(text, password) do
    [pem] = text |> :public_key.pem_decode
    :public_key.pem_entry_decode(pem, String.to_charlist(password))
  end

With this, we get the following error:

** (MatchError) no match of right hand side value: {:error, %HTTPoison.Error{id: nil, reason: {:options, {:cert, {:Certificate, {:TBSCertificate, :v3, 802, {:AlgorithmIdentifier ......

I posted this question in the HTTPoison issues tracker but haven’t had a reply as yet (https://github.com/edgurgel/httpoison/issues/283). Any help or ideas you might have would be greatly appriciated.

Regards

Andy

2 Likes

It looks like HTTPoison is just handing off the ssl args to the erlang ssl module. Looking at the erlang ssl module, the typespec for ssl_options() has the following types for cert and key:

Cert: public_key:der_encoded()

Key: {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()}

So it looks like your key needs to be a two item tuple that contains an atom with the key type and a binary for the key itself. It looks like AlogorithmIdentifier in the error is learning towards this as well.

2 Likes

Thanks @kylethebaker, that makes sense now that you say it. My erlang isn’t great and I find the docs a little hard to understand. I’ve made the following change as you suggest but still getting the same error, is this what you mean?:

[
  recv_timeout: 30_000,
  ssl: [
    cert: cert(public),
    key: {:RSAPrivateKey, cert(private, "a-password")}
  ]
]

I’m not sure I’m doing the right thing in the cert functions for decoding the certificates, I saw this online somewhere (can’t remember where) as a step that was required.

Thanks again for your help!

Andy

1 Like

Both the key and cert options are expecting a public_key:der_encoded type. There is public_key:der_encode() that seems to be what you need.

The parameters it accepts are kind of tricky to figure out. The first parameter is Asn1Type, which appears to the key type atom again (:RSAPrivateKey or :RSAPublicKey), and the Entity param is just a generic term that is an “Erlang representation of Asn1Type”, which indicates to me that it varies based on whatever Ans1Type you are using.

Looking at the type specs, there are rsa_private_key and rsa_public_key types, which are both records (#'RSAPrivateKey'{} and #'RSAPublicKey'{}). Records are the erlang equivalent of module structs. I can’t find where these records are defined in the code, but I did find this: http://erlang.org/doc/apps/public_key/using_public_key.html

It shows pem_entry_decode as returning an #RSAPrivateKey record. So I think something to try is making your opts be:

ssl: [
  cert: public_key:der_encode(:RSAPublicKey, cert(public))
  key: public_key:der_encode(:RSAPrivateKey, cert(private, "a-password"))
]
1 Like

Thanks again for the help on this. I’ve tried the above like this:

[
  recv_timeout: 30_000,
  ssl: [
    cert: :public_key.der_encode(:RSAPublicKey, cert(public)),
    key: :public_key.der_encode(:RSAPrivateKey, cert(private, "a-password"))
  ]
]

And now the error is slightly different:

** (MatchError) no match of right hand side value: {:error, {:asn1, {{:badmatch, {:Certificate, {:TBSCertificate, :v3, 802, {:AlgorithmIdentifier, ...

(public_key) public_key.erl:266: :public_key.der_encode/2
(platform) lib/.../web_services.ex:124: MyApp.WebServices.http_options/0

Line 124 if the stack track is the cert config line:

cert: :public_key.der_encode(:RSAPublicKey, cert(public)),

What do you think?

Andy

1 Like

Sounds like the certificate is in the wrong format? I remember erlang being picky about it, if you can create a new one and test and show it then maybe we can see something about it?

1 Like

I think I may have figured it out, but you will need to test with your info. The cert ans1 type should be :CertificateRequest instead of :RSAPublicKey. I was able to get a successful request with the test code below. If I tweak the cert/key options I can get similiar error messages to what you were getting before. I don’t think the ssl cert is doing much in this example request, but it doesn’t throw any errors at least.

I generated the RSA keypair with ssh-keygen -t rsa -C "your_email@example.com" and then created the certificate with openssl req -new -key priv_key -out pub_cert.csr

EDIT I just realized that I was using a :CertificateRequest instead of an actual :Certificate before. I generated an actual cert using openssl x509 -req -days 365 -in erlang_cert.csr -signkey erlang_id_rsa -out erlang_cert.crt and it still seems to be working. I am using whatever ANS1 types come from the decoding rather than hardcoding them, so the code doesn’t change, just the certificate.

 defmodule ErlangSslTest do
  def priv_key, do: """
  -----BEGIN RSA PRIVATE KEY-----
  Proc-Type: 4,ENCRYPTED
  DEK-Info: AES-128-CBC,E9BF6F551AB6C6C8E2ACB1FD9B7BF860

  JkeMooohdh8OT0y2DuRIZqcotwUQmwkAYVy6LKSRQ/bpfZL1p1jQc60S7BE4e0AT
  QtjmxPi26VQjYiR13/v/PUfoP5bUwz3aEWbBARBCE++Fl92dB8jH1e+DOR3b9MZ/
  cWXI6Qi/TKCMnl9JG+mh4l+THBspTLKC3qj1RM0Fdg4Zh1NtxKmmY/zXgx+ss7oZ
  r8sAjLnuqT9mttjp+eC8ycgWIXySRsiuIE/r89ySny8rvLcOQXOc3f3SDLtpZhgM
  p1VYGYZaa0oKvuPHUu1rGKxd0rLrW0tFZ1S2G9DQGRTAD1NB8CrHlNQXChKHBbOB
  4nd2Bd2Awi4Ut9otrX8KMRaPe6lwJivOu+j8GaVyukZ+FGfHD5riWnWdaUny3Gfu
  B+plHs0DmXYfl35+Cm0ZvyorQXXrPGodhqAeWXEfWeaLqBqv8WafGcKaYng7TEbh
  MXzzZ5TxZG5jAzAutuCDXzV5ZXXe1CYgJEPKdlb7favK7UU9OziqX/Xj6fye8Sf/
  ybiUYdXJFWZVPvVqeFxwwURLIhf21Lg9J7LBt3wek9GrezTGou3JXMzr5/66ENTB
  UEPSWtZtKsLsfKWLrsf1I1WHSVbOMUhln/7t+7wOM4bb+Km3ZfqjE6RBxNCdcRQW
  u1Fxg1Bxs8O6nNpXT+YPAg7yA+gmYXdkoAinrwswKtQu+cOQhl0ZwaW9u7OTTsAC
  ZVWlp7KgpVwJ6yM5HfttdCt/RcWkU95NfhgGt3SVRNUkpk9gUoMsVyav5783Tg6t
  nfigUKKlOrDqMOUQJDP5bTDkWdWTLcmH+4AGEgGJVKuE0kF3uiU0qgU4IUH+L0oi
  1QQpdEWR+2zYdGmDJBWEyevWvoqxcCq+YTVbGxlqFjO5KImR5nmEDYz7M1/FyqXX
  Dp56X75HpoeL+zuDBl+evwKh5JiBDcvOSL7+st77gTaW1kAIrC31ItVOkuWKU259
  cXwq8SQ1Xdg8ihcxfUQmvS1BksRgVoh2G1YZK6mrIeKMG+yPnncxV3rBe65K24/U
  6oU9GNGenQx9dzvp2nhPtMzgDOnwhdvMOXnQ5iMyjWPRtEtLEFzz+dEluZwdYibv
  ZZnJvCGO3ug83guFvuQshuHy2DyipdyazKhmNR21yxUXTuV9ezzGQIjgf0viONEQ
  pIb65h16ynx4yWVNyfdTgiKy1TK8ReYzIYDHuP3k6yt/soGHNEwb32yRbzIeq1wq
  S8iSJNuij809rxgDJlvXiVPTu6a93itbUoD1W+kC+dI5wvA9uAKTSOZDqII5K2bw
  BidYt2NxIfnmzBhAR7y75TAL93UNInEw0efAenQ7I2UqMZOW2NXCrxoToYkbWXQz
  AX/VvejcghINZAArPyYmSrjqi+6YhJu9BJs1X77rJbmveVvL2aH/r9XsXGxEqVGB
  XgpYR7xwN9AmTkt8cetUUHcJ1XBU1OC9Pc4FUS0qIQ1UVjxWWP5yUiYmx/z6g5q3
  jPC8lIPjtdZYxC5tewMM5arl3eh1H9URgq5Eczg5FxpybmAYyVrtOBiVXOdD40Bf
  DUotXu7FD/w3UEeq35dVjvOIlUq38z3Ll4PjeUrcTLAsLcUfViTHrh0Aq+Cyxg13
  -----END RSA PRIVATE KEY-----
  """ |> String.trim

  def pub_cert, do: """
  -----BEGIN CERTIFICATE-----
  MIIDBjCCAe4CCQDDhq+iQYSezjANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB
  VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
  cyBQdHkgTHRkMB4XDTE3MDkxNDIxMjM0OVoXDTE4MDkxNDIxMjM0OVowRTELMAkG
  A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
  IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
  AL2y30DlnBpy0XACIqgdj1ZroIf1kl5tIuM4nOxL+piigvU/0nSCZl1tj+kvtHyC
  rOkwg7SoPn9xoDbikvDX8zr2OwOGI01zXn8zUp1jukLzPr0hcgkjxb+fTgSmTNxI
  fiZ+WYUnCS1TcKmgL50uKACXSTWt9ZZaDlZZ0Ta8gPh7LFyD2ie5rxyQyESTNykv
  LirUx02nuCuF2VaF7lPGz+cSxZHKy+OgNvHtHWDUCD3e5wIbYStVdnpetFo5VsiA
  v+/r5fbBCjanYoJTXecg5swVhxeuLi3LpjE4syOTJbpu2xtTA0KjMZhXcaMbZ6kn
  SkpaFIgHFHq/WYwDYDj14B0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAN8IWdVua
  /8ag/7EUxwTWAJn4qYQHXo9tmWJxuYy1Lf5PVcNTqVcEOQG2ZJBi7F6ADHgbLc2N
  e5fsLca2VYpQ6LvEi9sHun06t7KI6PPB3/EzesTMJzAr5aWK9NdcqjTEtjshZNnt
  TIZwFrzX7XSZzpBijS0tEItYbJuraYC2LWuFhJtzTiStK4NrzfyFJVG0oQ1V3SU4
  n9EAFBu23FS52qfxrTauTCNYL43V+ha2gvm70VJAQIVpQs4Z3MgSyKGYr+LAIuKB
  dtY/lEgcUYhXY5Dzqt7DotJLRM1i/dWUrxLjLw0oyMi4EyQn2j4GmN4nlr/yBPdM
  EVCBwVsZ06HKtA==
  -----END CERTIFICATE-----
  """ |> String.trim

  def decode_pem_bin(pem_bin) do
    pem_bin |> :public_key.pem_decode() |> hd()
  end

  def decode_pem_entry(pem_entry) do
    :public_key.pem_entry_decode(pem_entry)
  end
  def decode_pem_entry(pem_entry, password) do
    password = String.to_charlist(password)
    :public_key.pem_entry_decode(pem_entry, password)
  end

  def encode_der(ans1_type, ans1_entity) do
    :public_key.der_encode(ans1_type, ans1_entity)
  end

  def split_type_and_entry(ans1_entry) do
    ans1_type = elem(ans1_entry, 0)
    {ans1_type, ans1_entry}
  end

  def get_cert_der do
    {cert_type, cert_entry} =
      pub_cert()
      |> decode_pem_bin()
      |> decode_pem_entry()
      |> split_type_and_entry()

    {cert_type, encode_der(cert_type, cert_entry)}
  end

  def get_key_der do
    {key_type, key_entry} =
      priv_key()
      |> decode_pem_bin()
      |> decode_pem_entry("password")
      |> split_type_and_entry()

    {key_type, encode_der(key_type, key_entry)}
  end

  def poison_options do
    {_ans1_type, cert} = get_cert_der()
    key = get_key_der()
    [
      recv_timeout: 30_000,
      ssl: [
        cert: cert,
        key: key,
      ]
    ]
  end

  def test_poison do
    {:ok, response} = HTTPoison.get(
      "https://httpbin.org/get?foo=bar",
      %{},
      poison_options()
    )
  end
end
9 Likes

@kylethebaker Sir, you are a genius, it works!!

Thank you so much for all your efforts with this question and taking the time to help me, I really appreciate it!!!

This is amazing!

THANK YOU

:heart: :heart: :heart: :heart: :heart: :heart: :heart: :heart:

2 Likes

Awesome, glad I could help.

I wonder if an Elixir wrapper around the ssl/public_key erlang libraries would be useful. There is definitely a pain-point here, but I’m not sure if its from the nature of the domain (PEM, DER, ANS1, x.509, certs, keys, ohmy), in which case a wrapper won’t be too useful, or if it’s because the erlang libraries are mainly plumbing with little porcelain. My concern is that the elixir wrapper would essentially be a one-to-one mapping with small helpers and very slight differences, in which case the benefit is small compared to the maintenance burden and potential bugs (which can be especially bad because of the security implications).

I suppose the typical use case is just providing the key/cert filenames, in which case it seems like things “just work”. The files are typically PEM formatted, so it makes sense that there can be single interface that hides all of the implementation, whereas if you are providing a cert from memory then it’s harder to make assumptions about whether or not it is PEM or DER. Perhaps rather than a library what is really needed documentation covering this use case, which hopefully this post can provided in a sense.

3 Likes

@kylethebaker,

Do you know of any working example on how to use Elliptic Curve keys to sign/verify messages?

I was tying to use GitHub - farao/elixir-ecc: An elixir module for elliptic curve cryptography repo but the code is outdated and does not work for newest Elixir/Erlang versions.

Thank you for the ErlangSslTest code!

Here is what I did:

$ openssl genpkey -algorithm EC  -pkeyopt ec_paramgen_curve:prime256v1 -out ec_privkey.pem
$ openssl ec -in ec_privkey.pem -pubout -out ec_pubkey.pem
$ openssl ecparam -in ec_privkey.pem -text -noout
ASN1 OID: prime256v1
NIST CURVE: P-256

======= lib/ecc.ex =========

defmodule ECC do
  use GenServer

def init(pem) do
{:ok, %{
public: ECC.Crypto.parse_public_key(pem),
private: ECC.Crypto.parse_private_key(pem)
}}
end

def start(pem, register_name \ nil) do
if register_name do
GenServer.start(MODULE, pem, name: register_name)
else
GenServer.start(MODULE, pem)
end
end

def start_link(pem, register_name \ nil) do
if register_name do
GenServer.start(MODULE, pem, name: register_name)
else
GenServer.start(MODULE, pem)
end

end

  def handle_call(:get_state, _from, state) do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 {:reply, {:ok, state}, state}
  end

  def handle_call(:get_public_key, _from, state) do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          if state.public do
      {:reply, {:ok, state.public}, state}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   else
      {:reply, {:error, :no_public_key}, state}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              end
  end
  def handle_call({:sign, msg, hash_type}, _from, keys) do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   {:reply, {:ok, ECC.Crypto.sign(msg, hash_type, keys.private)}, keys}
  end
  def handle_call({:verify_signature, msg, signature, public_key, hash_type}, _from, keys) do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                result = ECC.Crypto.verify_signature(msg, signature, hash_type, public_key)
    {:reply, {:ok, result}, keys}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          end
end
defmodule ECC.Crypto do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    def encode_der(ans1_type, ans1_entity) do
    :public_key.der_encode(ans1_type, ans1_entity)
  end

  def parse_public_key(pem) do
    # Expected result:
    #   {#'ECPoint'{}, #'ECParameters'{} | {namedCurve, oid()}}
    try do
      pem_keys = :public_key.pem_decode(pem)
      # pem_keys |> IO.inspect(label: "parse_public_key: ")

      # 'ECParameters'{
      #   version,    % integer()
      #   fieldID,    % #'FieldID'{}
      #   curve,      % #'Curve'{}
      #   base,       % binary()
      #   order,      % integer()
      #   cofactor    % integer()
      # }

      ec_params =
        Enum.find(pem_keys, fn(k) -> elem(k, 0) == :EcpkParameters end)
          # |> IO.inspect(label: "EcpkParameters")
        |> put_elem(0, :EcpkParameters)
        |> :public_key.pem_entry_decode
        |> IO.inspect(label: "ec_params")

      pem_public =
        Enum.find(pem_keys, fn(k) -> elem(k, 0) == :SubjectPublicKeyInfo end)
          # |> IO.inspect(label: "pem_public: ", limit: :infinity)
          |> elem(1)
          # |> IO.inspect(label: "pem_public: ", limit: :infinity)

      ec_point = :public_key.der_decode(:SubjectPublicKeyInfo, pem_public)
        |> IO.inspect(label: "ec_point A", limit: :infinity)
        |> elem(1)
        |> IO.inspect(label: "ec_point B", limit: :infinity)
        |> elem(2)
        |> IO.inspect(label: "ec_point C", limit: :infinity)

      # {cert_type, encode_der(cert_type, cert_entry)}
      # {{:ECPoint, ec_point}, {:namedCurve, {1, 3, 132, 0, 35}}}
      {{:ECPoint, ec_point}, ec_params}

    rescue
      _ -> nil
    end
  end

  def parse_private_key(pem) do
    try do
      # Decodes PEM binary data and returns entries as ASN.1 DER encoded entities
      :public_key.pem_decode(pem)
      |> Enum.find(fn(k) -> elem(k,0) == :ECPrivateKey end)
      |> :public_key.pem_entry_decode
    rescue
      _ -> nil
    end
  end

  def sign(msg, hash_type, private_key) do
    try do
      # sign(Msg, DigestType, Key) -> binary()
      :public_key.sign(msg, hash_type, private_key)
    rescue
      _ -> nil
    end
  end

  def verify_signature(msg, signature, hash_type, public_key) do
    try do
      # verify(Msg, DigestType, Signature, Key) -> boolean()
      #   Msg = binary() | {digest,binary()}
      #   DigestType = rsa_digest_type() | dss_digest_type() | ecdsa_digest_type()
      #   Signature = binary()
      #   Key = rsa_public_key() | dsa_public_key() | ec_public_key()
      # public_key |> IO.inspect(label: "\n -> verify_signature with: ")
      :public_key.verify(msg, hash_type, signature, public_key)
    rescue
      _ -> nil
    end
  end
end

=== lib/demo.ex ===
defmodule Demo do
@msg “Elixir”

  def run(pem_private_file, pem_public_file) do
    # ec_private_key.pem
    pem_private = File.read!(pem_private_file)
    pem_public = File.read!(pem_public_file)
    pem = Enum.join([pem_public, pem_private])
    IO.puts("Loaded pub/private pair from: #{pem_public_file} - #{pem_private_file}")

    {:ok, pid} = ECC.start_link(pem, :ecc)
    pid |> IO.inspect(label: "ECC pid:")

    {:ok, signature} = GenServer.call(:ecc, {:sign, @msg, :sha})
    signature |> Base.encode64 |> IO.inspect(label: "\n ==> Signed messsage")

    # {:ok, {point, params} = public_key} = GenServer.call(:ecc, :get_public_key)
    {:ok, public_key} = GenServer.call(:ecc, :get_public_key)

    public_key |> IO.inspect(label: "Server pubkey")

    {:ok, result} = GenServer.call(
    :ecc, {:verify_signature, @msg, signature, public_key, :sha})
    IO.puts("#{@msg} == #{@msg}? #{result}" )

    {:ok, pid}
  end
end

============= output ==============
$ iex -S mix

Demo.run(“ec_privkey.pem”, “ec_pubkey.pem”)
Loaded pub/private pair from: ec_pubkey.pem - ec_privkey.pem
ec_params: {:namedCurve, {1, 2, 840, 10045, 3, 1, 7}}
ec_point A: {:SubjectPublicKeyInfo,
{:AlgorithmIdentifier, {1, 2, 840, 10045, 2, 1},
<<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>>},
<<4, 111, 107, 121, 97, 249, 112, 77, 164, 104, 38, 231, 35, 40, 249, 220, 167,
154, 64, 43, 172, 135, 96, 228, 104, 95, 164, 150, 168, 149, 40, 249, 40, 6,
237, 228, 215, 250, 30, 137, 49, 233, 188, 218, 153, 0, 103, 142, 124, 0, 91,
217, 152, 177, 115, 159, 203, 172, 199, 85, 204, 121, 77, 222, 209>>}
ec_point B: {:AlgorithmIdentifier, {1, 2, 840, 10045, 2, 1},
<<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>>}
ec_point C: <<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>>
ECC pid:: #PID<0.172.0>

==> Signed messsage: “MEUCIQD1hPxulbeyYvEq7Zml0VBVwHxPbS+wYQ5k5RD2xfPOxgIgFNhyOJeCuQ5nRNViHgmk0iT4jbdsEvWyWqOidFATvr0=”
Server pubkey: {{:ECPoint, <<6, 8, 42, 134, 72, 206, 61, 3, 1, 7>>},
{:namedCurve, {1, 2, 840, 10045, 3, 1, 7}}}
Elixir == Elixir? falsePreformatted text

1 Like

Andy,

I’m a little late to the party but I believe you can do the following:

https: [
  cert: find_cert(),
  key: find_key()
]

def find_cert do 
  System.get_env("SSL_CERT") |> :public_key.pem_decode() |> hd() |> elem(1)
end

def find_key do 
  {type, encoded, _atom} = System.get_env("SSL_KEY") |> :public_key.pem_decode() |> hd()

  {type, encoded}
end

According to erlang’s documentation here: http://erlang.org/doc/man/public_key.html#pem_decode-1
This “Decodes PEM binary data and returns entries as ASN.1 DER encoded entities.”

We want to match the definition of ssl_option() here: http://erlang.org/doc/man/ssl.html
Which we do with the above functions. Let me know if this simplifies your code.

5 Likes

Thanks for the post and it totally worked for me. As I need to get the key into an ENV variable so I can use it with Heroku instead of reading it from file.

One cavet to note is that pay attention to the output of find_key method. hd() only returns the head of the list. In my case the pem file had :EcpkParameters for the first few lines and I actually needed to grab the tail of the output that specifies :ECPrivateKey in the tuple. Then it works like a charm as value to the key