]> Entropealabs - wampex_client.git/commitdiff
Implement cryptosign & password authentication
authorUk Chukundah <uk@juicyway.com>
Sun, 5 Jan 2025 09:58:04 +0000 (10:58 +0100)
committerUk Chukundah <uk@juicyway.com>
Sun, 5 Jan 2025 09:58:04 +0000 (10:58 +0100)
README.md
lib/client/authentication.ex

index 697c04cca569bed4c345580ad641e343293e2907..697c300c09cc520d313fe560898fb429ebcba56b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -68,10 +68,14 @@ MessagePack and JSON serialization are both supported
 - [ ] event_history
 - [ ] topic_reflection
 
+### Authentication
+- [x] WAMP-CRA
+- [x] WAMP-Cryptosign
+- [x] Password authentication
+- [ ] Ticket authentication
+- [ ] Cookie authentication
+
 ### Other
-- [x] challenge-response authentication
-- [ ] cookie authentication
-- [ ] ticket authentication
 - [ ] rawsocket transport
 - [ ] batched WS transport
 - [ ] longpoll transport
index 511fec1022219123da02d1ae85da01bee9fd0091..55d2477f5ef60ce61e7cbb12c27b303ba6564026 100644 (file)
@@ -4,33 +4,53 @@ defmodule Wampex.Client.Authentication do
   alias Wampex.Crypto
   alias Wampex.Roles.Peer.Challenge
 
-  @enforce_keys [:authid, :authmethods, :secret]
-  defstruct [:authid, :authmethods, :secret]
-
-  @callback handle(challenge :: Challenge.t(), auth :: __MODULE__.t()) :: {binary(), map()}
+  @enforce_keys [:authid, :authmethods]
+  defstruct [:authid, :authmethods, :secret, authextra: %{}]
 
   @type t :: %__MODULE__{
           authid: binary(),
           authmethods: [binary()],
-          secret: binary()
+          secret: binary(),
+          authextra: map()
         }
 
-  def handle(
-        %Challenge{
-          auth_method: "wampcra",
-          extra: %{"challenge" => ch, "salt" => salt, "iterations" => it, "keylen" => len}
-        },
-        auth
-      ) do
-    {auth
-     |> Map.get(:secret)
-     |> Crypto.pbkdf2(salt, it, len)
-     |> Crypto.hash_challenge(ch), %{}}
+  @callback handle(challenge :: Challenge.t(), auth :: t()) :: {binary(), map()}
+
+  @spec cryptosign(binary(), binary()) :: t()
+  def cryptosign(authid, privkey) do
+    privkey = privkey |> String.upcase() |> Base.decode16!()
+    pubkey = :eddsa |> :crypto.generate_key(:ed25519, privkey) |> elem(0) |> Base.encode16()
+
+    %__MODULE__{authmethods: ~w[cryptosign], authextra: %{pubkey: pubkey}, authid: authid, secret: privkey}
   end
 
-  def handle(%Challenge{auth_method: "wampcra", extra: %{"challenge" => ch}}, auth) do
-    {auth
-     |> Map.get(:secret)
-     |> Crypto.hash_challenge(ch), %{}}
+  @spec wampcra(binary(), binary()) :: t()
+  def wampcra(authid, secret), do: %__MODULE__{authmethods: ~w[wampcra], authid: authid, secret: secret}
+
+  @spec password(binary(), binary()) :: t()
+  def password(authid, password), do: %__MODULE__{authid: authid, secret: password, authmethods: ~w[password]}
+
+  def handle(%Challenge{auth_method: "wampcra", extra: %{"challenge" => ch} = xtra}, auth) do
+    xtra
+    |> case do
+      %{"salt" => salt, "iterations" => it, "keylen" => len} ->
+        :sha256 |> :crypto.pbkdf2_hmac(auth.secret, salt, it, len) |> Base.encode64()
+
+      _ ->
+        auth.secret
+    end
+    |> Crypto.hash_challenge(ch)
+    |> then(&{&1, %{}})
+  end
+
+  def handle(%Challenge{auth_method: "password"}, auth) do
+    {auth.secret, %{}}
+  end
+
+  def handle(%Challenge{auth_method: "cryptosign", extra: %{"challenge" => ch}}, auth) do
+    ch_bytes = ch |> String.upcase() |> Base.decode16!()
+    prefix = :eddsa |> :crypto.sign(:none, ch_bytes, [auth.secret, :ed25519]) |> Base.encode16() |> String.downcase()
+
+    {"#{prefix}#{ch}", %{}}
   end
 end