diff --git a/lib/lti_result.ex b/lib/lti_result.ex index 5274c36..f1478ff 100644 --- a/lib/lti_result.ex +++ b/lib/lti_result.ex @@ -27,24 +27,26 @@ defmodule LTIResult do {:ok, "iyyQNRQyXTlpLJPJns3ireWjQxo%3D"} """ def signature(url, oauth_header, secret) do - {parameters, [{"oauth_signature", received_signature}]} = - extract_header_elements(oauth_header) - - with {:ok, _} <- validate_parameters(parameters) do - basestring = base_string(url, parameters) - - signature = generate_signature(secret, basestring) - - if signature == received_signature do - {:ok, signature} - else - {:error, [:unmatching_signatures]} + with {parameters, [{"oauth_signature", received_signature}]} <- + extract_header_elements(oauth_header), + {:ok, _} <- validate_parameters(parameters), + {_, signature_method} <- + List.keyfind(parameters, "oauth_signature_method", 0), + basestring <- base_string(url, parameters), + signature <- generate_signature(secret, signature_method, basestring) do + case signature == received_signature do + true -> + {:ok, signature} + + false -> + {:error, [:unmatching_signatures]} end end end - defp generate_signature(secret, basestring) do - :sha + defp generate_signature(secret, signature_method, basestring) do + signature_method + |> get_signature_method() |> :crypto.hmac( percent_encode(secret) <> "&", basestring @@ -52,6 +54,9 @@ defmodule LTIResult do |> Base.encode64() end + defp get_signature_method("HMAC-SHA256"), do: :sha256 + defp get_signature_method(_), do: :sha + defp extract_header_elements(header) do header |> String.trim_leading("OAuth ") diff --git a/test/lti_result_test.exs b/test/lti_result_test.exs index 77f1cbf..5d54a04 100644 --- a/test/lti_result_test.exs +++ b/test/lti_result_test.exs @@ -2,175 +2,219 @@ defmodule LTIResultTest do use ExUnit.Case doctest LTIResult, only: [signature: 4] - test "returns {:ok, determined_signature} if the signature is correct" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:ok, "iyyQNRQyXTlpLJPJns3ireWjQxo="} + describe "signature_method SHMAC-SHA1" do + test "returns {:ok, determined_signature} if the signature is correct" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:ok, "iyyQNRQyXTlpLJPJns3ireWjQxo="} + end + + test "returns identical signatures for downcase url and url with capitals" do + return1 = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + return2 = + LTIResult.signature( + "https://ExamPle.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return1 == return2 + end + + test "returns {:ok, determined_signature} if a bodyhash is included" do + return = + LTIResult.signature( + "http://474c3d0e.ngrok.io/capp11/api/v1/lti_results", + "OAuth oauth_version=\"1.0\",oauth_nonce=\"tjtwip19l78355dl\",oauth_timestamp=\"1528808890\",oauth_consumer_key=\"Defacto\",oauth_body_hash=\"qvrl3dbLTUqxHeCDqof%2Ffz%2Bygc0%3D\",oauth_signature_method=\"HMAC-SHA1\",oauth_signature=\"WF9NUX6QCgKXNb2nNYEZ4evBmSk%3D\"", + "random_secret" + ) + + assert return == {:ok, "WF9NUX6QCgKXNb2nNYEZ4evBmSk="} + end + + test "returns an error if the signature is incorrect due to difference in key" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key12345\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:unmatching_signatures]} + end + + test "returns an error if the signature is incorrect due to difference in method" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA2\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:unmatching_signatures]} + end + + test "returns an error if the signature is incorrect due to difference in timestamp" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"152500000\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:unmatching_signatures]} + end + + test "returns an error if the signature is incorrect due to difference in nonce" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"32123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:unmatching_signatures]} + end + + test "returns an error if version is incorrect" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"2.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:incorrect_version]} + end + + test "returns an error if duplicated parameters are present" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:duplicated_parameters]} + end + + test "returns an error if the consumer key is missing" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:missing_required_parameters]} + end + + test "returns an error if the signature method is missing" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:missing_required_parameters]} + end + + test "returns an error if the timestamp is missing" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:missing_required_parameters]} + end + + test "returns an error if oauth version is missing" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:incorrect_version, :missing_required_parameters]} + end + + test "returns an error if nonce is missing" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:missing_required_parameters]} + end + + test "returns an error if an unsupported parameter is provided" do + return = + LTIResult.signature( + "https://example.com", + "OAuth unsupported_derpvalue=\"123\",oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return == {:error, [:unsupported_parameters]} + end end - test "returns identical signatures for downcase url and url with capitals" do - return1 = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - return2 = - LTIResult.signature( - "https://ExamPle.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return1 == return2 - end - - test "returns {:ok, determined_signature} if a bodyhash is included" do - return = - LTIResult.signature( - "http://474c3d0e.ngrok.io/capp11/api/v1/lti_results", - "OAuth oauth_version=\"1.0\",oauth_nonce=\"tjtwip19l78355dl\",oauth_timestamp=\"1528808890\",oauth_consumer_key=\"Defacto\",oauth_body_hash=\"qvrl3dbLTUqxHeCDqof%2Ffz%2Bygc0%3D\",oauth_signature_method=\"HMAC-SHA1\",oauth_signature=\"WF9NUX6QCgKXNb2nNYEZ4evBmSk%3D\"", - "random_secret" - ) - - assert return == {:ok, "WF9NUX6QCgKXNb2nNYEZ4evBmSk="} - end - - test "returns an error if the signature is incorrect due to difference in key" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key12345\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:unmatching_signatures]} - end - - test "returns an error if the signature is incorrect due to difference in method" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA2\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:unmatching_signatures]} - end - - test "returns an error if the signature is incorrect due to difference in timestamp" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"152500000\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:unmatching_signatures]} - end - - test "returns an error if the signature is incorrect due to difference in nonce" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"32123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:unmatching_signatures]} - end - - test "returns an error if version is incorrect" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"2.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:incorrect_version]} - end - - test "returns an error if duplicated parameters are present" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:duplicated_parameters]} - end - - test "returns an error if the consumer key is missing" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:missing_required_parameters]} - end - - test "returns an error if the signature method is missing" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:missing_required_parameters]} - end - - test "returns an error if the timestamp is missing" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:missing_required_parameters]} - end - - test "returns an error if oauth version is missing" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:incorrect_version, :missing_required_parameters]} - end - - test "returns an error if nonce is missing" do - return = - LTIResult.signature( - "https://example.com", - "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:missing_required_parameters]} - end - - test "returns an error if an unsupported parameter is provided" do - return = - LTIResult.signature( - "https://example.com", - "OAuth unsupported_derpvalue=\"123\",oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA1\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", - "random_secret" - ) - - assert return == {:error, [:unsupported_parameters]} + describe "signature_method SHMAC-SHA256" do + test "returns {:ok, determined_signature} if the signature is correct" do + return = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA256\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"wLwhR7dk5e4dVsvlhjK+fyWKSGzKw+v+tO96twgaHI8%3D\"", + "random_secret" + ) + + assert return == {:ok, "wLwhR7dk5e4dVsvlhjK+fyWKSGzKw+v+tO96twgaHI8="} + end + + test "returns identical signatures for downcase url and url with capitals" do + return1 = + LTIResult.signature( + "https://example.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA256\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + return2 = + LTIResult.signature( + "https://ExamPle.com", + "OAuth oauth_consumer_key=\"key1234\",oauth_signature_method=\"HMAC-SHA256\",oauth_timestamp=\"1525076552\",oauth_nonce=\"123\",oauth_version=\"1.0\",oauth_signature=\"iyyQNRQyXTlpLJPJns3ireWjQxo%3D\"", + "random_secret" + ) + + assert return1 == return2 + end + + test "returns {:ok, determined_signature} if a bodyhash is included" do + return = + LTIResult.signature( + "http://474c3d0e.ngrok.io/capp11/api/v1/lti_results", + "OAuth oauth_version=\"1.0\",oauth_nonce=\"tjtwip19l78355dl\",oauth_timestamp=\"1528808890\",oauth_consumer_key=\"Defacto\",oauth_body_hash=\"qvrl3dbLTUqxHeCDqof%2Ffz%2Bygc0%3D\",oauth_signature_method=\"HMAC-SHA256\",oauth_signature=\"aols3rXaIgpdQ9Ux4jQtbSl9E/AX+kvl7xp6BPzzzmk%3D\"", + "random_secret" + ) + + assert return == {:ok, "aols3rXaIgpdQ9Ux4jQtbSl9E/AX+kvl7xp6BPzzzmk="} + end end end