From c5624fec67753a8d9331f3270e6a8b5e8fe4a557 Mon Sep 17 00:00:00 2001 From: Alejandro Mezcua Date: Tue, 29 Nov 2016 19:05:25 +0100 Subject: [PATCH 1/3] Halt the response after sending the 401 error. --- lib/plug.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/plug.ex b/lib/plug.ex index 1f23937..365f034 100644 --- a/lib/plug.ex +++ b/lib/plug.ex @@ -56,5 +56,9 @@ defmodule Jwt.Plug do defp continue_if_verified({:ok, claims}, conn) do assign(conn, :jwtclaims, claims) end - defp continue_if_verified({:error, _}, conn), do: send_resp(conn, 401, "") + defp continue_if_verified({:error, _}, conn) do + conn + |> send_resp(401, "") + |> halt + end end \ No newline at end of file From 387ceee662ed3d76c13f664a104d3fb9130fb9f6 Mon Sep 17 00:00:00 2001 From: Alejandro Mezcua Date: Wed, 11 Jan 2017 18:17:44 +0100 Subject: [PATCH 2/3] Added support for verifying the tokens returned by the Firebase JavaScript authentication API. The certificates location and format is different from those returned by the Android API and now both URLs are retrieved and cached in search for the public key certificates associated to the tokens. --- README.md | 6 ++-- config/dev.exs | 3 +- config/prod.exs | 3 +- config/test.exs | 1 + lib/etscache.ex | 2 +- lib/firebasecerts.ex | 28 +++++++++++++++ lib/googlecerts.ex | 4 +-- lib/jwt.ex | 28 ++++++++++----- lib/pemparser.ex | 20 +++++++++++ mix.exs | 2 +- test/firebase_certs_test.exs | 34 +++++++++++++++++++ .../{certs_test.exs => google_certs_test.exs} | 4 +-- test/jwt_test.exs | 23 ++++++++----- test/support/firebasecertsmock.ex | 9 +++++ test/support/googlecertsmock.ex | 8 +++-- 15 files changed, 146 insertions(+), 29 deletions(-) create mode 100644 lib/firebasecerts.ex create mode 100644 lib/pemparser.ex create mode 100644 test/firebase_certs_test.exs rename test/{certs_test.exs => google_certs_test.exs} (96%) create mode 100644 test/support/firebasecertsmock.ex diff --git a/README.md b/README.md index 0ee566c..6169429 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ Elixir library that verifies Google generated JWT tokens (such as those returned by Firebase authentication) and returns the claims data. -The intended use case is to validate signed tokens retrieved by a mobile app using [Firebase Authentication](https://firebase.google.com/docs/auth/), where the app talks directly with the Google Authentication service and retrieves an authentication token (a Json Web Token) that can be later sent to a server for verification. +The intended use case is to validate signed tokens retrieved by a mobile app using [Firebase Authentication](https://firebase.google.com/docs/auth/), where the app talks directly with the Google Authentication service and retrieves an authentication token (a Json Web Token) that can be later sent to a server for verification or by web apps that use the Firebase [JavaScript API](https://firebase.google.com/docs/auth/web/google-signin). -JWT tokens are also returned by other Google authentication services and this library can be used to verify them too. +JWT tokens are also returned by other Google authentication services and this library could be used to verify them too. ## Usage @@ -17,7 +17,7 @@ JWT tokens are also returned by other Google authentication services and this li ## Installation -The package can be installed as (will try to make it available in Hex in a future version): +The package can be installed as follows (will try to make it available in Hex in a future version): 1. Add `jwt` to your list of dependencies in `mix.exs`: diff --git a/config/dev.exs b/config/dev.exs index b4a149d..7254a6c 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -2,4 +2,5 @@ use Mix.Config config :logger, level: :debug -config :jwt, :googlecerts, Jwt.GoogleCerts.PublicKey \ No newline at end of file +config :jwt, :googlecerts, Jwt.GoogleCerts.PublicKey +config :jwt, :firebasecerts, Jwt.FirebaseCerts.PublicKey \ No newline at end of file diff --git a/config/prod.exs b/config/prod.exs index b4a149d..7254a6c 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -2,4 +2,5 @@ use Mix.Config config :logger, level: :debug -config :jwt, :googlecerts, Jwt.GoogleCerts.PublicKey \ No newline at end of file +config :jwt, :googlecerts, Jwt.GoogleCerts.PublicKey +config :jwt, :firebasecerts, Jwt.FirebaseCerts.PublicKey \ No newline at end of file diff --git a/config/test.exs b/config/test.exs index 7c48e70..d10a9a1 100644 --- a/config/test.exs +++ b/config/test.exs @@ -3,4 +3,5 @@ use Mix.Config config :logger, level: :debug config :jwt, :googlecerts, Jwt.GoogleCerts.PublicKey.Mock +config :jwt, :firebasecerts, Jwt.FirebaseCerts.PublicKey.Mock config :jwt, :timeutils, Jwt.TimeUtils.Mock \ No newline at end of file diff --git a/lib/etscache.ex b/lib/etscache.ex index a22db11..03a14f5 100644 --- a/lib/etscache.ex +++ b/lib/etscache.ex @@ -44,7 +44,7 @@ defmodule Jwt.Cache.Ets do defp _get(uri) do value = :ets.lookup(@cache_table_name, uri) - Logger.debug "#{inspect uri} -> #{inspect value}" + #Logger.debug "#{inspect uri} -> #{inspect value}" case value do [] -> {:error, uri} _ -> {:ok, elem(Enum.at(value, 0), 1)} diff --git a/lib/firebasecerts.ex b/lib/firebasecerts.ex new file mode 100644 index 0000000..74f717e --- /dev/null +++ b/lib/firebasecerts.ex @@ -0,0 +1,28 @@ +defmodule Jwt.FirebaseCerts.PublicKey do + + @httpclient Jwt.HttpCacheClient + @certs_url "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com" + + def getfor(id), do: fetch id + + defp fetch(id) do + @httpclient.get!(@certs_url) + |> get_response_body + |> extract_public_key_for_id(id) + end + + defp get_response_body(%{body: body, headers: _headers, status_code: 200}), do: {:ok, body} + defp get_response_body(%{body: body, headers: _headers, status_code: _status_code}), do: {:error, body} + + defp extract_public_key_for_id({:error, _}, _id), do: nil + defp extract_public_key_for_id({:ok, body}, id) do + {:ok, parsed} = Poison.Parser.parse body + + cert = parsed[id] + + case cert do + nil -> {:error, "Public key id not found"} + cert_string -> Jwt.PemParser.extract_exponent_and_modulus_from_pem_cert cert_string + end + end +end \ No newline at end of file diff --git a/lib/googlecerts.ex b/lib/googlecerts.ex index 086c146..0ff96a3 100644 --- a/lib/googlecerts.ex +++ b/lib/googlecerts.ex @@ -29,7 +29,7 @@ defmodule Jwt.GoogleCerts.PublicKey do defp extract_certificated_url({:ok, body}) do {:ok, parsed} = Poison.Parser.parse body case parsed[@jwt_uri_in_discovery] do - nil -> {:error, "No JWT url found"} + nil -> {:notfounderror, "No JWT url found"} _ -> {:ok, parsed[@jwt_uri_in_discovery]} end end @@ -46,7 +46,7 @@ defmodule Jwt.GoogleCerts.PublicKey do key = List.first(Enum.filter parsed["keys"], fn key -> key["kid"] == id end) case key do - nil -> {:error, "Public key id not found"} + nil -> {:notfounderror, "Public key id not found"} _ -> {:ok, %{exp: key[@exponent], mod: key[@modulus]}} end end diff --git a/lib/jwt.ex b/lib/jwt.ex index 8dc60ad..8db5a26 100644 --- a/lib/jwt.ex +++ b/lib/jwt.ex @@ -1,16 +1,17 @@ defmodule Jwt do @google_certs_api Application.get_env(:jwt, :googlecerts, Jwt.GoogleCerts.PublicKey) + @firebase_certs_api Application.get_env(:jwt, :firebasecerts, Jwt.FirebaseCerts.PublicKey) @invalid_token_error {:error, "Invalid token"} @invalid_signature_error {:error, "Invalid signature"} @key_id "kid" @alg "alg" @doc """ - Verifies a Google generated JWT token against the current public Google certificates and returns the claims + Verifies a Google or Firebase generated JWT token against the current public certificates and returns the claims if the token's signature is verified successfully. ## Example - iex > {:ok, {claims}} = Jwt.verify token + {:ok, {claims}} = Jwt.verify token """ def verify(token) do token_parts = String.split token, "." @@ -19,23 +20,34 @@ defmodule Jwt do end defp _verify([{:ok, header}, {:ok, _claims}, {:ok, signature}], [header_b64, claims_b64, _signature_b64]) do - Poison.Parser.parse!(header)[@key_id] - |> @google_certs_api.getfor + header + |> extract_key_id + |> retrieve_cert_exp_and_mod_for_key |> verify_signature(header_b64, claims_b64, signature) end defp _verify(_,_), do: @invalid_token_error - defp verify_signature({:ok, public_key}, header_b64, claims_b64, signature) do + defp extract_key_id(header), do: Poison.Parser.parse!(header)[@key_id] + + defp retrieve_cert_exp_and_mod_for_key(key_id) do + @google_certs_api.getfor(key_id) + |> case do + {:ok, cert_data} -> {:ok, cert_data} + {:notfounderror, _} -> @firebase_certs_api.getfor(key_id) + _ -> @invalid_token_error + end + end + + defp verify_signature({:ok, %{exp: exponent, mod: modulus}}, header_b64, claims_b64, signature) do msg = header_b64 <> "." <> claims_b64 - mod = :binary.decode_unsigned(Base.url_decode64!(public_key.mod, padding: false)) - exp = :binary.decode_unsigned(Base.url_decode64!(public_key.exp, padding: false)) - case :crypto.verify :rsa, :sha256, msg, signature, [exp, mod] do + case :crypto.verify :rsa, :sha256, msg, signature, [exponent, modulus] do true -> {:ok, Poison.Parser.parse! Base.url_decode64!(claims_b64, padding: false)} false -> @invalid_signature_error end end + defp verify_signature({:error, _}, _, _, _), do: @invalid_token_error defp verify_signature(_, _, _, _), do: @invalid_signature_error end diff --git a/lib/pemparser.ex b/lib/pemparser.ex new file mode 100644 index 0000000..86cf3c2 --- /dev/null +++ b/lib/pemparser.ex @@ -0,0 +1,20 @@ +defmodule Jwt.PemParser do + def extract_exponent_and_modulus_from_pem_cert(pem_cert) do + [{_, dert, _}] = :public_key.pem_decode(pem_cert) + otp = :public_key.pkix_decode_cert(dert, :otp) + + [_, mod, exp] = otp + |> Tuple.to_list + |> Enum.slice(1..1) + |> hd + |> Tuple.to_list + |> Enum.slice(7..7) + |> hd + |> Tuple.to_list + |> Enum.slice(2..2) + |> hd + |> Tuple.to_list + + {:ok, %{exp: exp, mod: mod}} + end +end \ No newline at end of file diff --git a/mix.exs b/mix.exs index 47800f6..d47582f 100644 --- a/mix.exs +++ b/mix.exs @@ -3,7 +3,7 @@ defmodule Jwt.Mixfile do def project do [app: :jwt, - version: "0.5.0", + version: "0.6.0", elixir: "~> 1.3", elixirc_paths: elixirc_paths(Mix.env), build_embedded: Mix.env == :prod, diff --git a/test/firebase_certs_test.exs b/test/firebase_certs_test.exs new file mode 100644 index 0000000..9d781ca --- /dev/null +++ b/test/firebase_certs_test.exs @@ -0,0 +1,34 @@ +defmodule FirebaseCertsTest do + use ExUnit.Case, async: true + + require Logger + + @certificate "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIHCf0ZvzSh6gwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTcw\nMTAzMDA0NTI2WhcNMTcwMTA2MDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAMew823wkK4hu02ROzImYBkryY8V6hH5aKbIcbXoaktTn/en\n5PB29MFqgy5GXrPsg6knIxsx2RR+yX5qWvMJJlxYz/NytMNkgFZPh+wtgEG0XmA5\n34J1nS6p9Lg/5jgxTyDmN9/WOj9Ml4DgQSFzz4f4AGwStw+dOERXBz+wASrs+8qL\ntxLt/Z2ENAqMDnxaY8VdOqNlFeQuBca3KQsZEvv3jObFGwrtFsCa+gnQ2JNYCACz\nuaJou68I4P0SOc17NqX8NZtyW/UmBam2WEHfE9C8qGUk96sfIktSL5MwRYQyl3UP\nU+SiorVkGBJgTGMHSymsbR/Ia4tB+nQ4KIuEbWUCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAJ3M+1HJTt/IQZ/09eW8B9gwgBBVhawZ1n0+aY7SuKSB\njHz474nUlQVz6SxBmeVScb9IXh2lL1K+YYrP/O7sP/OUYXHfjxKGNFMDl/lo/Pgq\nMmSOXnvbvEOgXrsgF8/ytj2BEyMe7wuZC7impUCkbarV4FKcvqjff1iLzAKWn4et\nRJJEyEngemOfAl2l9JBuQG/mouOyapv1g7B9mfRTTYKtbFl1o3N1GJtWC78pD+K9\noawYyz9tiGmm8IKW/KYiJhj616eq06ugEb3VHDjFL+hjGPmORPUND/al+XibYU/q\nNN45J+dag1ZeKxu1Ugc9p2IQB1RMtLMfGmaBldHQ5dY=\n-----END CERTIFICATE-----\n" + + @header "eyJhbGciOiJSUzI1NiIsImtpZCI6ImU3MGRiMDg5MzU5MDBkNTY0YWFiMjdiMzllNmJkNWM4NDdkMDQxM2QifQ" + @claims "eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vYmFkYWNoaW4tNTUzYjkiLCJuYW1lIjoiQWxlamFuZHJvIE1lemN1YSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vLWZJSlQ3RzJ1WjNFL0FBQUFBQUFBQUFJL0FBQUFBQUFBQVFZL0p2Q1ZtRkhYbnI4L3Bob3RvLmpwZyIsImF1ZCI6ImJhZGFjaGluLTU1M2I5IiwiYXV0aF90aW1lIjoxNDgzMzUxNjY5LCJ1c2VyX2lkIjoiTVNsZ0JueVU1NWdRQ3Q3WElTNWpiQzVYdE5BMyIsInN1YiI6Ik1TbGdCbnlVNTVnUUN0N1hJUzVqYkM1WHROQTMiLCJpYXQiOjE0ODM0MjkzMzYsImV4cCI6MTQ4MzQzMjkzNiwiZW1haWwiOiJhbGVqYW5kcm8ubWV6Y3VhQGJ5dGVhYnl0ZS5uZXQiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjExMDM2MTQwMDI0NDg3MTIyNTQxNCJdLCJlbWFpbCI6WyJhbGVqYW5kcm8ubWV6Y3VhQGJ5dGVhYnl0ZS5uZXQiXX0sInNpZ25faW5fcHJvdmlkZXIiOiJnb29nbGUuY29tIn19" + @signature "tk8mTHJnRCqn1KQ0Ha5R_34sOWYoxV0QFCgLQ32UCLWy00kkq_waLyoYdb7G7Tns6dLk6e2rPcrz97JCcWTCqD3erMd9oN2IyqAGPUOU7FJr8PjtaMzHWDUdTinI05sKpRJNxMcEiP7Wd0VKk4ubuImbO7MphKZL5-KLz2KTcQztQaJC0dw1Ey3mBCSbdOkIMuTxCzfjkvXrbmGirIAfn8INJyIEg8Vnav-NaBT5ShIcie_ovVZy97iReNcubRZn34ipMQrai_k_JaRjlVMn0m0u9dee44S4XHlvdC4hkC-DpBZubDDj3BVQq-Ztk9H9CZREuPUjdfy3anflBE_BwQ" + + test "Verify signature Firebase certificate" do + [{_, dert, _}] = :public_key.pem_decode(@certificate) + otp = :public_key.pkix_decode_cert(dert, :otp) + + [_, mod, exp] = otp + |> Tuple.to_list + |> Enum.slice(1..1) + |> hd + |> Tuple.to_list + |> Enum.slice(7..7) + |> hd + |> Tuple.to_list + |> Enum.slice(2..2) + |> hd + |> Tuple.to_list + + pk = [exp, mod] + signature = Base.url_decode64! @signature, padding: false + msg = @header <> "." <> @claims + + assert :crypto.verify :rsa, :sha256, msg, signature, pk + end +end \ No newline at end of file diff --git a/test/certs_test.exs b/test/google_certs_test.exs similarity index 96% rename from test/certs_test.exs rename to test/google_certs_test.exs index d1a9f23..c025267 100644 --- a/test/certs_test.exs +++ b/test/google_certs_test.exs @@ -1,4 +1,4 @@ -defmodule CertsTest do +defmodule GoogleCertsTest do use ExUnit.Case, async: true @mod "zkSRsA8npcga4dKSt91-OtSXA481Y94jt5tn64h2MUtUnQ_1JP-4xcDBYVG52m1Cdc7Fq2_cpUOvm27jAxIc4oYxLk1YtyJX9ce5p2rkbKyC71nSq5om3rBE4n3hYUa0nPCcXNC0uC_G0UTVY_OsiYS6hSNVSnHqySn50yid8EBWY8sHHCsqEtlk4uwXXalgnpZ5BXI22yQWQASnZdeIiRKhxSWdkDrbLUq1FmyfNn9vabhIADZsdjCL3iCfJVW8YTdntObZRVsuh_ezm9K7-l3U400EvZA7RN_Dt5QGC6gSjo4syP5TkGXD6iC6rUx67FLzgww_Lo0O4kYEFzDLzw" @@ -7,7 +7,7 @@ defmodule CertsTest do @claims "eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiIzNDczODQ1NjIxMTMtcmRtNnNsZG0xbWIzOGs0dW1yY28zcDhsN3I1aGcwazUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTAzNjE0MDAyNDQ4NzEyMjU0MTQiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiMzQ3Mzg0NTYyMTEzLXIyOHBqZDB1Yzlwb2Y1Y20xcDBubmwyNXM5N2o4dXFwLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJieXRlYWJ5dGUubmV0IiwiZW1haWwiOiJhbGVqYW5kcm8ubWV6Y3VhQGJ5dGVhYnl0ZS5uZXQiLCJpYXQiOjE0NzMyMjU4NjQsImV4cCI6MTQ3MzIyOTQ2NCwibmFtZSI6IkFsZWphbmRybyBNZXpjdWEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1mSUpUN0cydVozRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFRWS9KdkNWbUZIWG5yOC9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiQWxlamFuZHJvIiwiZmFtaWx5X25hbWUiOiJNZXpjdWEiLCJsb2NhbGUiOiJlbiJ9" @signature "Kc90u_gtZyhq6glw6UoYQSInZx9r16uqrRO7g50x17JWH7VkyAZrh3sfjdBYpGtDNJjDRBKSxuinpDjpyfiCp3-XAqqOUWqziyYvkV4-CdQvNhcnUQFXjjx_CzNiiEi5PRPCHhX4ajidet1NH4Me02S17gwOZiaZfed1BMWQuQ_7Hf2RsX5FID1xqOpcaaouMFcrqQFmdBIbcstHamWxs9D83c4JpOsioNOMb6-LBinzOg7qdxr1D4NvHD6VSXBTbyXiOBjK2elLU1iCz_Hz_BH-R1IYCdTRr5PczRWdSCgoTdZ7ds1nTTglfuXlGNbaEhhzsFxX8OCR4uNK6vbWXQ" - test "Verify signature" do + test "Verify signature Google certificate" do mod = :binary.decode_unsigned(Base.url_decode64!(@mod, padding: false)) exp = :binary.decode_unsigned(Base.url_decode64!(@exp, padding: false)) pk = [exp, mod] diff --git a/test/jwt_test.exs b/test/jwt_test.exs index c50d46d..2eada3e 100644 --- a/test/jwt_test.exs +++ b/test/jwt_test.exs @@ -1,14 +1,16 @@ defmodule JwtTest do use ExUnit.Case, async: true - doctest Jwt - @google_certs_api Application.get_env(:jwt, :googlecerts) @invalid_token_error {:error, "Invalid token"} @base64part "eyJhbGciOiJSUzI1NiIsImtpZCI6IjdkMWEzMTcxMTE5NTFiZDI0MjdlMjZmMjA3Nzc3MzRlYjgwZjY1YTUifQ" @non_base64part "1" - @valid_token_header "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEwZWZiZjlmOWEzZThlYzVlN2RmYTc5NjFkNzFlMmU0YmZkYTI0MzUifQ" - @valid_token_claims "eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiIzNDczODQ1NjIxMTMtcmRtNnNsZG0xbWIzOGs0dW1yY28zcDhsN3I1aGcwazUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTAzNjE0MDAyNDQ4NzEyMjU0MTQiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiMzQ3Mzg0NTYyMTEzLXIyOHBqZDB1Yzlwb2Y1Y20xcDBubmwyNXM5N2o4dXFwLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJieXRlYWJ5dGUubmV0IiwiZW1haWwiOiJhbGVqYW5kcm8ubWV6Y3VhQGJ5dGVhYnl0ZS5uZXQiLCJpYXQiOjE0NzMyMjU4NjQsImV4cCI6MTQ3MzIyOTQ2NCwibmFtZSI6IkFsZWphbmRybyBNZXpjdWEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1mSUpUN0cydVozRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFRWS9KdkNWbUZIWG5yOC9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiQWxlamFuZHJvIiwiZmFtaWx5X25hbWUiOiJNZXpjdWEiLCJsb2NhbGUiOiJlbiJ9" - @valid_token_signature "Kc90u_gtZyhq6glw6UoYQSInZx9r16uqrRO7g50x17JWH7VkyAZrh3sfjdBYpGtDNJjDRBKSxuinpDjpyfiCp3-XAqqOUWqziyYvkV4-CdQvNhcnUQFXjjx_CzNiiEi5PRPCHhX4ajidet1NH4Me02S17gwOZiaZfed1BMWQuQ_7Hf2RsX5FID1xqOpcaaouMFcrqQFmdBIbcstHamWxs9D83c4JpOsioNOMb6-LBinzOg7qdxr1D4NvHD6VSXBTbyXiOBjK2elLU1iCz_Hz_BH-R1IYCdTRr5PczRWdSCgoTdZ7ds1nTTglfuXlGNbaEhhzsFxX8OCR4uNK6vbWXQ" + @valid_google_token_header "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEwZWZiZjlmOWEzZThlYzVlN2RmYTc5NjFkNzFlMmU0YmZkYTI0MzUifQ" + @valid_google_token_claims "eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhdWQiOiIzNDczODQ1NjIxMTMtcmRtNnNsZG0xbWIzOGs0dW1yY28zcDhsN3I1aGcwazUuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTAzNjE0MDAyNDQ4NzEyMjU0MTQiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiMzQ3Mzg0NTYyMTEzLXIyOHBqZDB1Yzlwb2Y1Y20xcDBubmwyNXM5N2o4dXFwLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJieXRlYWJ5dGUubmV0IiwiZW1haWwiOiJhbGVqYW5kcm8ubWV6Y3VhQGJ5dGVhYnl0ZS5uZXQiLCJpYXQiOjE0NzMyMjU4NjQsImV4cCI6MTQ3MzIyOTQ2NCwibmFtZSI6IkFsZWphbmRybyBNZXpjdWEiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1mSUpUN0cydVozRS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFRWS9KdkNWbUZIWG5yOC9zOTYtYy9waG90by5qcGciLCJnaXZlbl9uYW1lIjoiQWxlamFuZHJvIiwiZmFtaWx5X25hbWUiOiJNZXpjdWEiLCJsb2NhbGUiOiJlbiJ9" + @valid_google_token_signature "Kc90u_gtZyhq6glw6UoYQSInZx9r16uqrRO7g50x17JWH7VkyAZrh3sfjdBYpGtDNJjDRBKSxuinpDjpyfiCp3-XAqqOUWqziyYvkV4-CdQvNhcnUQFXjjx_CzNiiEi5PRPCHhX4ajidet1NH4Me02S17gwOZiaZfed1BMWQuQ_7Hf2RsX5FID1xqOpcaaouMFcrqQFmdBIbcstHamWxs9D83c4JpOsioNOMb6-LBinzOg7qdxr1D4NvHD6VSXBTbyXiOBjK2elLU1iCz_Hz_BH-R1IYCdTRr5PczRWdSCgoTdZ7ds1nTTglfuXlGNbaEhhzsFxX8OCR4uNK6vbWXQ" + + @valid_firebase_token_header "eyJhbGciOiJSUzI1NiIsImtpZCI6ImU3MGRiMDg5MzU5MDBkNTY0YWFiMjdiMzllNmJkNWM4NDdkMDQxM2QifQ" + @valid_firebase_token_claims "eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vYmFkYWNoaW4tNTUzYjkiLCJuYW1lIjoiQWxlamFuZHJvIE1lemN1YSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vLWZJSlQ3RzJ1WjNFL0FBQUFBQUFBQUFJL0FBQUFBQUFBQVFZL0p2Q1ZtRkhYbnI4L3Bob3RvLmpwZyIsImF1ZCI6ImJhZGFjaGluLTU1M2I5IiwiYXV0aF90aW1lIjoxNDgzMzUxNjY5LCJ1c2VyX2lkIjoiTVNsZ0JueVU1NWdRQ3Q3WElTNWpiQzVYdE5BMyIsInN1YiI6Ik1TbGdCbnlVNTVnUUN0N1hJUzVqYkM1WHROQTMiLCJpYXQiOjE0ODM0MjkzMzYsImV4cCI6MTQ4MzQzMjkzNiwiZW1haWwiOiJhbGVqYW5kcm8ubWV6Y3VhQGJ5dGVhYnl0ZS5uZXQiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJnb29nbGUuY29tIjpbIjExMDM2MTQwMDI0NDg3MTIyNTQxNCJdLCJlbWFpbCI6WyJhbGVqYW5kcm8ubWV6Y3VhQGJ5dGVhYnl0ZS5uZXQiXX0sInNpZ25faW5fcHJvdmlkZXIiOiJnb29nbGUuY29tIn19" + @valid_firebase_token_signature "tk8mTHJnRCqn1KQ0Ha5R_34sOWYoxV0QFCgLQ32UCLWy00kkq_waLyoYdb7G7Tns6dLk6e2rPcrz97JCcWTCqD3erMd9oN2IyqAGPUOU7FJr8PjtaMzHWDUdTinI05sKpRJNxMcEiP7Wd0VKk4ubuImbO7MphKZL5-KLz2KTcQztQaJC0dw1Ey3mBCSbdOkIMuTxCzfjkvXrbmGirIAfn8INJyIEg8Vnav-NaBT5ShIcie_ovVZy97iReNcubRZn34ipMQrai_k_JaRjlVMn0m0u9dee44S4XHlvdC4hkC-DpBZubDDj3BVQq-Ztk9H9CZREuPUjdfy3anflBE_BwQ" test "Invalid Jwt tokens are rejected" do invalid_token = "invalid_token" @@ -36,9 +38,14 @@ defmodule JwtTest do assert @invalid_token_error = Jwt.verify(invalid_token) end - test "The claims are extracted from a valid token" do - valid_token = @valid_token_header <> "." <> @valid_token_claims <> "." <> @valid_token_signature - assert {:ok, _} = Jwt.verify(valid_token) + test "The claims are extracted from a valid Google token" do + valid_google_token = @valid_google_token_header <> "." <> @valid_google_token_claims <> "." <> @valid_google_token_signature + assert {:ok, _} = Jwt.verify(valid_google_token) + end + + test "The claims are extracted from a valid Firebase token" do + valid_firebase_token = @valid_firebase_token_header <> "." <> @valid_firebase_token_claims <> "." <> @valid_firebase_token_signature + assert {:ok, _} = Jwt.verify(valid_firebase_token) end end diff --git a/test/support/firebasecertsmock.ex b/test/support/firebasecertsmock.ex new file mode 100644 index 0000000..1e2ab94 --- /dev/null +++ b/test/support/firebasecertsmock.ex @@ -0,0 +1,9 @@ +defmodule Jwt.FirebaseCerts.PublicKey.Mock do + require Logger + + @certificate "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIHCf0ZvzSh6gwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTcw\nMTAzMDA0NTI2WhcNMTcwMTA2MDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAMew823wkK4hu02ROzImYBkryY8V6hH5aKbIcbXoaktTn/en\n5PB29MFqgy5GXrPsg6knIxsx2RR+yX5qWvMJJlxYz/NytMNkgFZPh+wtgEG0XmA5\n34J1nS6p9Lg/5jgxTyDmN9/WOj9Ml4DgQSFzz4f4AGwStw+dOERXBz+wASrs+8qL\ntxLt/Z2ENAqMDnxaY8VdOqNlFeQuBca3KQsZEvv3jObFGwrtFsCa+gnQ2JNYCACz\nuaJou68I4P0SOc17NqX8NZtyW/UmBam2WEHfE9C8qGUk96sfIktSL5MwRYQyl3UP\nU+SiorVkGBJgTGMHSymsbR/Ia4tB+nQ4KIuEbWUCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAJ3M+1HJTt/IQZ/09eW8B9gwgBBVhawZ1n0+aY7SuKSB\njHz474nUlQVz6SxBmeVScb9IXh2lL1K+YYrP/O7sP/OUYXHfjxKGNFMDl/lo/Pgq\nMmSOXnvbvEOgXrsgF8/ytj2BEyMe7wuZC7impUCkbarV4FKcvqjff1iLzAKWn4et\nRJJEyEngemOfAl2l9JBuQG/mouOyapv1g7B9mfRTTYKtbFl1o3N1GJtWC78pD+K9\noawYyz9tiGmm8IKW/KYiJhj616eq06ugEb3VHDjFL+hjGPmORPUND/al+XibYU/q\nNN45J+dag1ZeKxu1Ugc9p2IQB1RMtLMfGmaBldHQ5dY=\n-----END CERTIFICATE-----\n" + + def getfor(id) do + Jwt.PemParser.extract_exponent_and_modulus_from_pem_cert @certificate + end +end \ No newline at end of file diff --git a/test/support/googlecertsmock.ex b/test/support/googlecertsmock.ex index 09f48c7..758ecb4 100644 --- a/test/support/googlecertsmock.ex +++ b/test/support/googlecertsmock.ex @@ -1,9 +1,13 @@ defmodule Jwt.GoogleCerts.PublicKey.Mock do - + @firebaseid "e70db08935900d564aab27b39e6bd5c847d0413d" @exponent "AQAB" @modulus "zkSRsA8npcga4dKSt91-OtSXA481Y94jt5tn64h2MUtUnQ_1JP-4xcDBYVG52m1Cdc7Fq2_cpUOvm27jAxIc4oYxLk1YtyJX9ce5p2rkbKyC71nSq5om3rBE4n3hYUa0nPCcXNC0uC_G0UTVY_OsiYS6hSNVSnHqySn50yid8EBWY8sHHCsqEtlk4uwXXalgnpZ5BXI22yQWQASnZdeIiRKhxSWdkDrbLUq1FmyfNn9vabhIADZsdjCL3iCfJVW8YTdntObZRVsuh_ezm9K7-l3U400EvZA7RN_Dt5QGC6gSjo4syP5TkGXD6iC6rUx67FLzgww_Lo0O4kYEFzDLzw" + def getfor(@firebaseid) do + {:notfounderror, "Public key id not found"} + end + def getfor(_id) do - {:ok, %{exp: @exponent, mod: @modulus}} + {:ok, %{exp: :binary.decode_unsigned(Base.url_decode64!(@exponent, padding: false)), mod: :binary.decode_unsigned(Base.url_decode64!(@modulus, padding: false))}} end end \ No newline at end of file From 05d820623f8a612fd6b23994dfd48b8d0e48dd30 Mon Sep 17 00:00:00 2001 From: Alejandro Mezcua Date: Wed, 11 Jan 2017 18:22:30 +0100 Subject: [PATCH 3/3] Removed comment --- lib/etscache.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/etscache.ex b/lib/etscache.ex index 03a14f5..f243e2e 100644 --- a/lib/etscache.ex +++ b/lib/etscache.ex @@ -44,7 +44,6 @@ defmodule Jwt.Cache.Ets do defp _get(uri) do value = :ets.lookup(@cache_table_name, uri) - #Logger.debug "#{inspect uri} -> #{inspect value}" case value do [] -> {:error, uri} _ -> {:ok, elem(Enum.at(value, 0), 1)}