]> Entropealabs - wampex_client.git/commitdiff
adds authentication, will also queue messages until handshake is complete, once sessi...
authorChristopher Coté <ccote@thecitybase.com>
Thu, 20 Feb 2020 22:42:27 +0000 (16:42 -0600)
committerChristopher Coté <ccote@thecitybase.com>
Thu, 20 Feb 2020 22:42:27 +0000 (16:42 -0600)
lib/wampex/authentication.ex
lib/wampex/roles/peer.ex
lib/wampex/session.ex
mix.exs
mix.lock
priv/session.json
test/wampex_test.exs

index 132517998087481ab34b2199d73fb6b8e3490aae..93070fe08c40014674c58b353561c1589e0ca307 100644 (file)
@@ -1,9 +1,40 @@
 defmodule Wampex.Authentication do
   @moduledoc false
-  defstruct [:authid, :authmethods]
+  @enforce_keys [:authid, :authmethods, :secret]
+  defstruct [:authid, :authmethods, :secret]
+
+  @callback handle(challenge :: binary(), auth :: Authentication.t()) :: {binary(), map()}
 
   @type t :: %__MODULE__{
           authid: binary(),
-          authmethods: [binary()]
+          authmethods: [binary()],
+          secret: binary()
         }
+
+  def handle(
+        {"wampcra", %{"challenge" => ch, "salt" => salt, "iterations" => it, "keylen" => len}},
+        auth
+      ) do
+    {auth
+     |> Map.get(:secret)
+     |> pbkdf2(salt, it, len)
+     |> hash_challenge(ch), %{}}
+  end
+
+  def handle({"wampcra", %{"challenge" => ch}}, auth) do
+    {auth
+     |> Map.get(:secret)
+     |> hash_challenge(ch), %{}}
+  end
+
+  defp hash_challenge(secret, challenge) do
+    :sha256
+    |> :crypto.hmac(secret, challenge)
+    |> :base64.encode()
+  end
+
+  defp pbkdf2(secret, salt, iterations, keylen) do
+    {:ok, derived} = :pbkdf2.pbkdf2(:sha256, secret, salt, iterations, keylen)
+    Base.encode64(derived)
+  end
 end
index 43662cc8d099acac572d84fdde3f2392bd4d3607..5a6581b3762271ea8171c46e529963bc29d4fc00 100644 (file)
@@ -2,15 +2,14 @@ defmodule Wampex.Role.Peer do
   @moduledoc """
   Handles requests and responses for low-level Peer/Session interactions
   """
-  alias StatesLanguage, as: SL
-  alias Wampex.Session
-
   alias Wampex.Role
   @behaviour Role
 
   @hello 1
   @welcome 2
   @abort 3
+  @challenge 4
+  @authenticate 5
   @goodbye 6
   @error 8
 
@@ -38,6 +37,17 @@ defmodule Wampex.Role.Peer do
           }
   end
 
+  defmodule Authenticate do
+    @moduledoc false
+    @enforce_keys [:signature, :extra]
+    defstruct [:signature, :extra]
+
+    @type t :: %__MODULE__{
+            signature: binary(),
+            extra: map()
+          }
+  end
+
   @impl true
   def add(roles), do: roles
 
@@ -54,10 +64,14 @@ defmodule Wampex.Role.Peer do
     [@goodbye, opts, r]
   end
 
+  @spec authenticate(Authenticate.t()) :: Wampex.message()
+  def authenticate(%Authenticate{signature: s, extra: e}) do
+    [@authenticate, s, e]
+  end
+
   @impl true
-  def handle([@welcome, session_id, _dets], %SL{data: %Session{} = data} = sl) do
-    {%SL{sl | data: %Session{data | id: session_id}}, [{:next_event, :internal, :noop}], nil,
-     {:ok, session_id}}
+  def handle([@welcome, session_id, _dets], sl) do
+    {sl, [{:next_event, :internal, :noop}], nil, {:update, :id, session_id}}
   end
 
   @impl true
@@ -70,6 +84,11 @@ defmodule Wampex.Role.Peer do
     {sl, [{:next_event, :internal, :goodbye}], nil, {:update, :goodbye, reason}}
   end
 
+  @impl true
+  def handle([@challenge, auth_method, extra], sl) do
+    {sl, [{:next_event, :internal, :challenge}], nil, {:update, :challenge, {auth_method, extra}}}
+  end
+
   @impl true
   def handle([@error, type, id, dets, error], sl) do
     handle([@error, type, id, dets, error, [], %{}], sl)
index 82cbc0308179eb2bd59abe1546ec668f7339f9c5..86aedc282fa3eba2971d6a4d4908c84735a28cc1 100644 (file)
@@ -10,7 +10,7 @@ defmodule Wampex.Session do
   alias Wampex.Realm
   alias Wampex.Authentication
   alias Wampex.Role.Peer
-  alias Wampex.Role.Peer.Hello
+  alias Wampex.Role.Peer.{Authenticate, Hello}
   alias Wampex.Serializer.MessagePack
   alias __MODULE__, as: Sess
   alias Wampex.Transport.WebSocket
@@ -32,6 +32,8 @@ defmodule Wampex.Session do
     :name,
     :roles,
     :authentication,
+    :challenge,
+    message_queue: [],
     request_id: 0,
     protocol: "wamp.2.msgpack",
     transport: WebSocket,
@@ -50,7 +52,9 @@ defmodule Wampex.Session do
           realm: Realm.t(),
           name: module() | nil,
           roles: [module()],
-          authentication: Authentication.t(),
+          authentication: Authentication.t() | nil,
+          challenge: {binary(), binary()} | nil,
+          message_queue: [],
           request_id: integer(),
           protocol: binary(),
           transport: module(),
@@ -73,7 +77,7 @@ defmodule Wampex.Session do
   def handle_call(
         {:send_request, request},
         from,
-        _,
+        "Established",
         %SL{data: %Sess{request_id: r_id, transport: tt, transport_pid: t} = sess} = data
       ) do
     request_id = do_send(r_id, tt, t, request)
@@ -90,9 +94,20 @@ defmodule Wampex.Session do
   end
 
   @impl true
-  def handle_cast(
+  def handle_call(
         {:send_request, request},
+        from,
         _,
+        %SL{data: %Sess{message_queue: mq} = sess} = data
+      ) do
+    Logger.info("Queueing request: #{inspect(request)}")
+    {:ok, %SL{data | data: %Sess{sess | message_queue: [{request, from} | mq]}}, []}
+  end
+
+  @impl true
+  def handle_cast(
+        {:send_request, request},
+        "Established",
         %SL{data: %Sess{request_id: r_id, transport: tt, transport_pid: t} = sess} = data
       ) do
     request_id = do_send(r_id, tt, t, request)
@@ -104,6 +119,16 @@ defmodule Wampex.Session do
      }, []}
   end
 
+  @impl true
+  def handle_cast(
+        {:send_request, request},
+        _,
+        %SL{data: %Sess{message_queue: mq} = sess} = data
+      ) do
+    Logger.info("Queueing request: #{inspect(request)}")
+    {:ok, %SL{data | data: %Sess{sess | message_queue: [{request, nil} | mq]}}, []}
+  end
+
   @impl true
   def handle_resource(
         "Hello",
@@ -124,6 +149,20 @@ defmodule Wampex.Session do
     {:ok, data, [{:next_event, :internal, :hello_sent}]}
   end
 
+  @impl true
+  def handle_resource(
+        "HandleChallenge",
+        _,
+        "Challenge",
+        %SL{
+          data: %Sess{transport: tt, transport_pid: t, challenge: challenge, authentication: auth}
+        } = sl
+      ) do
+    {signature, extra} = auth.__struct__.handle(challenge, auth)
+    tt.send_request(t, Peer.authenticate(%Authenticate{signature: signature, extra: extra}))
+    {:ok, sl, [{:next_event, :internal, :challenged}]}
+  end
+
   @impl true
   def handle_resource(
         "GoodBye",
@@ -168,7 +207,37 @@ defmodule Wampex.Session do
   end
 
   @impl true
-  def handle_resource("Established", _, "Established", data) do
+  def handle_resource(
+        "Established",
+        _,
+        "Established",
+        %SL{
+          data: %Sess{request_id: r_id, transport: tt, transport_pid: t, message_queue: mq} = sess
+        } = data
+      ) do
+    {request_id, requests} =
+      mq
+      |> Enum.reverse()
+      |> Enum.reduce({r_id, []}, fn {request, from}, {id, requests} ->
+        r = do_send(id, tt, t, request)
+        {r, [{r, from} | requests]}
+      end)
+
+    requests =
+      Enum.filter(requests, fn
+        {_, nil} -> false
+        {_, _} -> true
+      end)
+
+    {:ok,
+     %SL{
+       data
+       | data: %Sess{sess | requests: requests, request_id: request_id, message_queue: []}
+     }, []}
+  end
+
+  @impl true
+  def handle_resource("HandleHandshake", _, "Handshake", data) do
     {:ok, data, []}
   end
 
@@ -214,7 +283,7 @@ defmodule Wampex.Session do
   end
 
   @impl true
-  def handle_info({:set_message, message}, "Established", %SL{data: %Sess{} = sess} = data) do
+  def handle_info({:set_message, message}, _, %SL{data: %Sess{} = sess} = data) do
     {:ok, %SL{data | data: %Sess{sess | message: message}},
      [{:next_event, :internal, :message_received}]}
   end
diff --git a/mix.exs b/mix.exs
index e2d927937f657ccdca961451bacd5be201092519..f28dd69c905574c1a6cbd9cff41e7d29d738a2c1 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -43,6 +43,7 @@ defmodule Wampex.MixProject do
       {:ex_doc, "~> 0.21", only: :dev, runtime: false},
       {:jason, "~> 1.1"},
       {:msgpack, "~> 0.7.0"},
+      {:pbkdf2, "~> 2.0"},
       {:states_language, "~> 0.2"},
       {:websockex, "~> 0.4"}
     ]
index 16d1737fea749a8a9262b1e9544e2d524b342bb8..5fb72e4212d426042919b8eac2fdee89b70908ed 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -21,6 +21,7 @@
   "msgpack": {:hex, :msgpack, "0.7.0", "128ae0a2227c7e7a2847c0f0f73551c268464f8c1ee96bffb920bc0a5712b295", [:rebar3], [], "hexpm", "4649353da003e6f438d105e4b1e0f17757f6f5ec8687a6f30875ff3ac4ce2a51"},
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
+  "pbkdf2": {:hex, :pbkdf2, "2.0.0", "11c23279fded5c0027ab3996cfae77805521d7ef4babde2bd7ec04a9086cf499", [:rebar3], [], "hexpm", "1e793ce6fdb0576613115714deae9dfc1d1537eaba74f07efb36de139774488d"},
   "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
   "states_language": {:hex, :states_language, "0.2.8", "f9dfd3c0bd9a9d7bda25ef315f2d90944cd6b2022a7f3c403deb1d4ec451825e", [:mix], [{:elixpath, "~> 0.1.0", [hex: :elixpath, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4.0", [hex: :json_xema, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xema, "~> 0.11.0", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "a5231691e7cb37fe32dc7de54c2dc86d1d60e84c4f0379f3246e55be2a85ec78"},
   "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
index 439a1b55ff49ecbb378880e3122f95ff95aa9aac..1329a99498c40b1f370d35b56dbccbfab70749e5 100644 (file)
@@ -18,7 +18,7 @@
       "Type": "Task",
       "Resource": "Hello",
       "TransitionEvent": ":hello_sent",
-      "Next": "Established",
+      "Next": "Handshake",
       "Catch": [
         {
           "ErrorEquals": [":abort"],
         
       ]
     },
+    "Handshake": {
+      "Type": "Choice",
+      "Resource": "HandleHandshake",
+      "Choices": [
+        {
+          "StringEquals": ":message_received",
+          "Next": "HandleMessage"
+        },
+        {
+          "StringEquals": ":abort",
+          "Next": "Abort"
+        }
+      ]
+    },
     "Established": {
       "Type": "Choice",
       "Resource": "Established",
         {
           "StringEquals": ":goodbye",
           "Next": "GoodBye"
+        },
+        {
+          "StringEquals": ":challenge",
+          "Next": "Challenge"
         }
       ]
     },
+    "Challenge": {
+      "Type": "Task",
+      "Resource": "HandleChallenge",
+      "Next": "Handshake",
+      "TransitionEvent": ":challenged"
+    },
     "Event": {
       "Type": "Task",
       "Resource": "HandleEvent",
index d0ea1b5236252fa8505a0a82e09843cfb8b13698..6b8ae23922abbb771f875bb686c254df6d45c68f 100644 (file)
@@ -15,7 +15,7 @@ defmodule WampexTest do
   @realm %Realm{name: "com.myrealm"}
   @roles [Callee, Caller, Publisher, Subscriber]
   @device "as987d9a8sd79a87ds"
-  @auth %Authentication{authid: "entone", authmethods: ["wampcra"]}
+  @auth %Authentication{authid: "entone", authmethods: ["wampcra"], secret: "test1234"}
 
   @session %Session{url: @url, realm: @realm, roles: @roles, authentication: @auth}
 
@@ -172,6 +172,6 @@ defmodule WampexTest do
       })
     )
 
-    assert_receive {:event, _, _, _, _, _}
+    assert_receive {:event, _, _, _, _, _}, 2000
   end
 end