X.509 request Cert chain validation plug for Alexa Skills

Hello,

I am trying to get an alexa skill certified by Amazon. As part of the certification process, they want the server to validate the request has a valid X.509 certificate chain from an Amazon domain. They provide an impl in Java.
(https://github.com/amzn/alexa-skills-kit-java/blob/master/src/com/amazon/speech/speechlet/authentication/SpeechletRequestSignatureVerifier.java)

I am trying to build a version in Phoenix / Elixir. (I have the rest of the system working - just not this.).

Does anyone have a good suggestion as to which libraries/plug-ins I should use to implement these functions (specifically reading and verifying the X.509 certificate chain)?

I am assuming it would work best as a Phoenix Plug but I am open to other suggestions too.

I am also happy to make it open source if we can figure out a solution that works.

Thanks!

3 Likes

The first place I’d look is the crypto module in the erlang main distribution. You have access to it off the bat, you just have to do Application.start :crypto. There’s also the public_key module that seems related.

1 Like

Except that will not include it in a release, instead you want to add it to the (extra) applications list in your mix.exs file, that way it will both be included in release as well as loaded. :slight_smile:

2 Likes

Yes, obviously I meant for when he wants to check it out in iex. It’s not even started in a normal iex session, nor is it accessible for autocomplete, hence the suggestion.

1 Like

There’s a trick. If you do

iex> h :crypto

the module will be loaded and then the tab completion will work.

Thank you guys very much! public_key is definitely the library I want to use. I am not sure I even need the RSA library/application which is great.

I am totally inexperienced with Erlang unfortunately and cannot seem to get it all working via iex. I am wondering what I am doing wrong.

Here’s what I got:

Application.ensure_all_started :inets
Application.ensure_all_started :ssl

\# amazon cert chain
{:ok, resp} = :httpc.request(:get, {'https://s3.amazonaws.com/echo.api/echo-api-cert.pem', []}, [], [body_format: :binary])
{{_, 200, 'OK'}, _headers, certificate_chain_bin} = resp
cert_chain = :public_key.pem_decode(certificate_chain_bin)

\# root cert
{:ok, resp} = :httpc.request(:get, {'https://www.symantec.com/content/dam/symantec/docs/other-resources/verisign-class-3-public-primary-certification-authority-g5-en.pem', []}, [], [body_format: :binary])
{{_, 200, 'OK'}, _headers, root_cert} = resp
[root_cert_decoded] = :public_key.pem_decode(root_cert)

foo = :public_key.pkix_path_validation(root_cert_decoded, cert_chain, nil)

No matter what I put in (nil, or a function call, etc…) it is returning with an error of some sort.

It looks like the erlang library is asking me to pass a function as part of the options but when I try to send it, it complains. I have tried looking at the Erlang codebase but could not identify the problem (let alone if I am using the correct version of Erlang :)). Thoughts on what I am doing wrong?

I have also tried the following but to no avail:

verify_fun = fn(cert, event, state) -> { :valid, state} end
foo = :public_key.pkix_path_validation(:RSA, cert_chain, [{:verify_fun, verify_fun}])

Docs are here:

http://erlang.org/doc/man/public_key.html#pkix_path_validation-3

Thanks in advance if you are able to figure out what I am doing wrong!

2 Likes

Ok. Made some progress. it looks like all of the path_validation needed to be converted to otp style certificates. Working on an update which I hope to post if I get it actually working. Oy!

2 Likes

I’m not familiar with X.509 but based on my experience with ssl cert verification I think you are supposed to do just a bit more work on the result of :public_key.pem_decode.

I created a quick script here https://gist.github.com/xlphs/2c283f4bef52bb649a7077f95ee01d37, run it with elixir public_key_verify.exs. I got a bunch of errors printed out inside verify_fun, the echo cert has expired.

1 Like

Thanks, xlphs!

I got one step further on my own but am still stuck. The live valid certificate is at : https://s3.amazonaws.com/echo.api/echo-api-cert-4.pem

And I think the trusted cert should be: https://www.symantec.com/content/dam/symantec/docs/other-resources/verisign-class-3-public-primary-certification-authority-g4-en.pem

Unfortunately, if I change the URLs in your script I still get :invalid_issuer, :invalid_key_usage, :invalid_signature errors.

I am guessing we might have the wrong trusted cert, but no matter what I try I get the same errors. (I have tried multiple root certs).

In any case, that’s my current blocking point. Feels like I should not be ignoring those errors :slight_smile:

The trust anchor is actually the root certificate from @xlphs’s script, the one ending with “G5”. One way to verify how the various certificates relate is to use OpenSSL’s CLI:

cat cert.pem | openssl x509 -text -noout

You can either compare the Subject and Issuer RDNs, or check if the Authority Key Identifier matches the Subject Key Identifier of the issuing cert. Note that you’d have to split Amazon’s PEM file into two files to be able to pipe each on into OpenSSL.

The other thing to note is that pkix_path_validation expects the certificate chain to be in the opposite order of what’s in the file from Amazon, with the peer cert at the end:

CertChain = [der_encoded()]
A list of DER-encoded certificates in trust order ending with the peer certificate.

So make sure you pass the list through Enum.reverse/1 first.

Awesome! It worked! I thought the certs were in order and not reversed.

One other question - is there any way to access an erlang set of Trusted certificates or do I just need to build out a list myself since this is a lower level library? I noticed the ssl package has trusted certs but could not find a way to access it directly. No worries if not. I think it is fine to just download or cache the one I need for now.

Thanks again everyone!

Erlang and Elixir do not include a trust store. The Hex client does, as explained in the README file, and there is a package on Hex called certifi that is used by the Hackney and HTTPoison HTTP clients.

BTW, I haven’t looked closely at the Java code, but I believe it does more than just verify the certificate chain: it also checks for a certain hostname in the peer certificate SAN extension (in Erlang 19.3 you can use :public_key.pkix_verify_hostname(cert, dns_id: 'example.net') for that), and uses the certificate’s public key to verify the request signature (use :public_key.verify/4, avoid using :crypto directly)

Thanks Voltone. I’ll look into those libraries.

And I got verify_hostname working separately (it required an update to erlang 19.3 which I did not figure out for a bit). As for the signature verification, I am now in the midst of the fun of redoing the endpoint parsers to get the raw body so that I can verify the signature. (That’s the only way I have discovered to get the raw body as it is removed by the json parser otherwise).

So I got the verification of the signature working great - thanks to your all help! Path verification works, domain verification works, and I even got caching of the cert working with ConCache so that it won’t keep fetching and validating the cert chain for every request.

I am unfortunately now having problems with message / signature verification.

The :public_key.verify function has Msg, digest, signature, and Key parameters.

I am confused as to what exactly I should be putting into the digest and key parameters.

Specifically, for the digest parameter, do I put in an :rsa atom? or something else?
And for the key parameter, do I put in the top cert from the chain or do I need to further convert it. It says it requires an rsa_public_key but we only have Certificates.

I have tried looking at the erlang example docs but to no avail.

Here are quick links to the docs in case they help.
http://erlang.org/doc/man/public_key.html#verify-4
http://erlang.org/doc/apps/public_key/using_public_key.html

This is I believe the last step so any help would be appreciated!

Thank you guys so much once again!

1 Like

I think you might be confused with how verification works. The message cannot be encrypted directly using private key, you need to use a hash function like sha1 to hash it into a fixed number of bytes, then encrypt that with private key, the result is a signed digest. On the other hand, when you have the message and signed digest and public key, you hash the message using the same hash function, then verify the signed digest using public key.

Sorry xlphs, I might not have been clear. I have the signed digest (I called that a “signature”), I have the raw message, and I have the X509 public certificate chain (which i presume has the key). I am trying to use :public_key.verify with those but haven’t figured out how to get it to work. In particular, I haven’t figured out what to put for the sha and key parameters. Do I use the first certificate from the chain as the key?

And what do I use as the second parameter - do I use the atom :sha?

The public key should be the peer certificate I think, so yes the first cert from the chain. Second parameter is the hash function used to hash message into a fixed length digest, if you are supposed to use SHA1 hash then yes put :sha.

Thanks for validating, xlphs. I am realizing the public key is inside of the certificate and not the certificate itself. It seems like it is a proplist/record in erlang and people are accessing it as follows:

Decoded = decoded_cert

   PublicKey = Decoded#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subjectPublicKeyInfo#'OTPSubjectPublicKeyInfo'.subjectPublicKey,

In Elixir this is coming across as multiple tuples with atoms as the first value. Is there a easy way to traverse the tuple in Elixir? I am afraid to use pattern matching for the tuples as I am not sure if there is variability among the number of entries in each tuple (i.e OTPCertificate tuple may be 2 or 3 values, etc…) and I am not sure if the placement in the tuple is variable as well (i.e. OTPSubjectPublicKeyInfo might be the 2nd or 5th entry in the subjectPublicKeyInfo tuple). Do I have to use Record in Elixir and re-define a series of Records for each of these?

not saying the code is pretty but hope it helps…

def verify do
  Application.ensure_all_started :inets
  Application.ensure_all_started :ssl
  Application.ensure_all_started :public_key

  {:ok, resp} = :httpc.request(:get, {'https://s3.amazonaws.com/echo.api/echo-api-cert.pem', []}, [], [body_format: :binary])
  {_, _headers, body} = resp

  certs = :public_key.pem_decode(body)
  for {_,raw_cert,_} <- certs do
    decoded = :public_key.pkix_decode_cert(raw_cert, :otp)
    case decoded do
      {:OTPCertificate,
       {:OTPTBSCertificate, _, _, _, _, validity, _,
        {:OTPSubjectPublicKeyInfo, _, key}, _, _, extensions}, _, _} ->
        for {:Extension, _, _, val} <- extensions do
          case val do
          	[dNSName: uri] ->
                    #do more validation
          		if uri == 'echo-api.amazon.com' do
          		  IO.inspect "WOHOOO"
                   #do some validation here
                   {:Validity, {:utcTime, from}, {:utcTime, to}} = validity
                   IO.inspect from
                   IO.inspect to
                   IO.inspect key
          		end
          	_ ->
          		IO.inspect "NO"
          end
        end
    end
  end
end

Using Record would be the proper way to do it. Something like:

require Record

Record.defrecordp :otp_tbs_certificate, :OTPTBSCertificate,
  Record.extract(:OTPTBSCertificate, from_lib: "public_key/include/OTP-PUB-KEY.hrl")
# and so on...

However, since the structures of these records are unlikely to change, as they are built directly from the X.509 specifications, a shortcut might be acceptable. So you could do this instead:

public_key_der = cert |> elem(1) |> elem(7) |> elem(2)

You’d have to decode the binary key before you pass it into verify/4:

public_key = :public_key.der_decode(:RSAPublicKey, public_key_der)