Skip to content

Commit 39f3e8d

Browse files
committed
Refactor authentication
1 parent be05c49 commit 39f3e8d

File tree

10 files changed

+178
-288
lines changed

10 files changed

+178
-288
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ jobs:
66
name: lint OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
77
strategy:
88
matrix:
9-
otp: ['25']
10-
elixir: ['1.13.2']
9+
otp: ['26']
10+
elixir: ['1.16']
1111
steps:
1212
- uses: actions/checkout@v2
1313
- uses: erlef/setup-beam@v1
@@ -16,6 +16,7 @@ jobs:
1616
elixir-version: ${{matrix.elixir}}
1717
- run: mix deps.get
1818
- run: mix credo
19+
- run: mix dialyzer
1920
- run: mix format --check-formatted
2021
- run: mix docs 2>&1 | (! grep -q "warning:")
2122

@@ -24,11 +25,10 @@ jobs:
2425
name: test OTP ${{matrix.otp}} / Elixir ${{matrix.elixir}}
2526
strategy:
2627
matrix:
27-
otp: ['25']
28-
elixir: ['1.13.2']
28+
otp: ['26']
29+
elixir: ['1.16']
2930
env:
3031
MIX_ENV: test
31-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3232
steps:
3333
- uses: actions/checkout@v2
3434
- uses: erlef/setup-beam@v1

README.md

Lines changed: 94 additions & 83 deletions
Large diffs are not rendered by default.

bench/message.exs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ alias ExSTUN.Message.Type
1414
# therefore they are not fully symmetric with "encode" benchmarks
1515

1616
fix_lt_password = :crypto.mac(:hmac, :sha, "123456789", "someusername") |> :base64.encode()
17-
fix_lt_key = "someusername" <> ":" <> "somerealm" <> ":" <> fix_lt_password
18-
fix_lt_key = :crypto.hash(:md5, fix_lt_key)
17+
fix_lt_key = Message.lt_key("someusername", fix_lt_password, "somerealm")
1918
fix_st_key = "somekey"
2019

2120
<<fix_t_id::12*8>> = :crypto.strong_rand_bytes(12)
@@ -77,15 +76,20 @@ Benchee.run(
7776
"binding_response.decode" => fn -> {:ok, _} = Message.decode(fix_enc_binding_response) end,
7877
"message_full.encode" => fn -> full_msg_enc.(fix_st_key) end,
7978
"message_full.decode" => fn -> {:ok, _} = Message.decode(fix_enc_full_message) end,
80-
"message_full.authenticate_st" => fn ->
81-
{:ok, _} = Message.authenticate_st(fix_full_message, "someusername", fix_st_key)
79+
"message_full.authenticate (short-term)" => fn ->
80+
# check if username is correct and authenticate
81+
{:ok, username} = Message.get_attribute(fix_full_message, Username)
82+
username.value == "someusername"
83+
:ok = Message.authenticate(fix_full_message, fix_st_key)
8284
end,
83-
"message_full.authenticate_lt" => fn ->
84-
password = :crypto.mac(:hmac, :sha, "123456789", "someusername") |> :base64.encode()
85-
{:ok, _} = Message.authenticate_lt(fix_full_lt_message, password)
85+
"message_full.authenticate (long-term)" => fn ->
86+
{:ok, username} = Message.get_attribute(fix_full_lt_message, Username)
87+
{:ok, realm} = Message.get_attribute(fix_full_lt_message, Realm)
88+
key = Message.lt_key(username.value, fix_lt_password, realm.value)
89+
:ok = Message.authenticate(fix_full_lt_message, key)
8690
end,
8791
"message_full.check_fingerprint" => fn ->
88-
true = Message.check_fingerprint(fix_full_message)
92+
:ok = Message.check_fingerprint(fix_full_message)
8993
end,
9094
"type.to_value" => fn ->
9195
Type.to_value(%Type{class: :success_response, method: :binding})

codecov.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
codecov:
2+
require_ci_to_pass: false
3+
4+
comment:
5+
layout: "header, diff, files, footer"
6+
behavior: default
7+
8+
coverage:
9+
status:
10+
project:
11+
default:
12+
informational: true
13+
patch:
14+
default:
15+
informational: true
16+
17+
github_checks:
18+
annotations: false

lib/ex_stun.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ defmodule ExSTUN do
66
@doc """
77
Checks if binary is a STUN message.
88
"""
9-
@spec is_stun(binary()) :: boolean()
10-
def is_stun(<<first_byte::8, _rem_header::binary-size(19), _rest::binary>>)
9+
@spec stun?(binary()) :: boolean()
10+
def stun?(<<first_byte::8, _rem_header::binary-size(19), _rest::binary>>)
1111
when first_byte in 0..3 do
1212
true
1313
end
1414

15-
def is_stun(_other), do: false
15+
def stun?(_other), do: false
1616
end

lib/ex_stun/message.ex

Lines changed: 19 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ defmodule ExSTUN.Message do
1919
```
2020
"""
2121
import Bitwise
22-
alias ExSTUN.Message.Attribute.Fingerprint
23-
alias ExSTUN.Message.Attribute.{MessageIntegrity, Realm, Username}
22+
23+
alias ExSTUN.Message.Attribute.{Fingerprint, MessageIntegrity}
2424
alias ExSTUN.Message.{RawAttribute, Type}
2525

2626
@magic_cookie 0x2112A442
@@ -221,74 +221,40 @@ defmodule ExSTUN.Message do
221221
end
222222

223223
@doc """
224-
Authenticates a message long-term mechanism.
225-
226-
`password` depends on the STUN authentication method and has to
227-
be provided from the outside.
228-
229-
`key` is a key used for calculating MAC and can be used
230-
for adding message integrity in a response. See `with_integrity/2`.
224+
Create longe-term authentication key.
231225
"""
232-
@spec authenticate_lt(t(), binary()) ::
233-
{:ok, key :: binary()}
234-
| {:error,
235-
:no_message_integrity
236-
| :no_username
237-
| :no_realm
238-
| :no_matching_message_integrity
239-
| atom()}
240-
def authenticate_lt(msg, password) do
241-
with {:ok, %MessageIntegrity{} = msg_int} <- get_message_integrity(msg),
242-
{:ok, %Username{value: username}} <- get_username(msg),
243-
{:ok, %Realm{value: realm}} <- get_realm(msg) do
244-
key = username <> ":" <> realm <> ":" <> password
245-
key = :crypto.hash(:md5, key)
246-
247-
# + 20 for STUN message header
248-
# - 24 for message integrity
249-
len = msg.len_to_int + 20 - 24
250-
<<msg_without_integrity::binary-size(len), _rest::binary>> = msg.raw
251-
<<pre_len::binary-size(2), _len::16, post_len::binary>> = msg_without_integrity
252-
msg_without_integrity = <<pre_len::binary, msg.len_to_int::16, post_len::binary>>
253-
254-
mac = :crypto.mac(:hmac, :sha, key, msg_without_integrity)
255-
256-
if mac == msg_int.value do
257-
{:ok, key}
258-
else
259-
{:error, :no_matching_message_integrity}
260-
end
261-
else
262-
{:error, _reason} = err -> err
263-
end
226+
@spec lt_key(binary(), binary(), binary()) :: binary()
227+
def lt_key(username, password, realm) do
228+
:crypto.hash(:md5, username <> ":" <> realm <> ":" <> password)
264229
end
265230

266231
@doc """
267-
Authenticates a message using short-term mechanism.
232+
Authenticates a message.
268233
269-
It is assumed that username attribute of this message is valid.
234+
`key` depends on the authentication method.
235+
When authenticating using short-term mechanism, it is simply a password.
236+
When authenticating using long-term mechanism, use `lt_key/3` to obtain the key.
270237
271-
`key` is a key used for calculating MAC and can be used
272-
for adding message integrity in a response. See `with_integrity/2`.
238+
Presence of username, realm and nonce attributes is not checked.
239+
Depending on the authentication method and its context (client/server side),
240+
user has to perform those checks on their own.
273241
"""
274-
@spec authenticate_st(t(), binary()) ::
275-
{:ok, key :: binary()}
276-
| {:error, :no_message_integrity | :no_matching_message_integrity | atom()}
277-
def authenticate_st(msg, password) do
242+
@spec authenticate(t(), binary()) ::
243+
:ok | {:error, :no_message_integrity, :no_matching_message_integrity | atom()}
244+
def authenticate(msg, key) do
278245
case get_message_integrity(msg) do
279246
{:ok, %MessageIntegrity{} = msg_int} ->
280247
# + 20 for STUN message header
281248
# - 24 for message integrity
282-
len = msg.len_to_int + 20 - (20 + 4)
249+
len = msg.len_to_int + 20 - 24
283250
<<msg_without_integrity::binary-size(len), _rest::binary>> = msg.raw
284251
<<pre_len::binary-size(2), _len::16, post_len::binary>> = msg_without_integrity
285252
msg_without_integrity = <<pre_len::binary, msg.len_to_int::16, post_len::binary>>
286253

287-
# in short-term authentication key == password
288-
mac = :crypto.mac(:hmac, :sha, password, msg_without_integrity)
254+
mac = :crypto.mac(:hmac, :sha, key, msg_without_integrity)
289255

290256
if mac == msg_int.value do
291-
{:ok, password}
257+
:ok
292258
else
293259
{:error, :no_matching_message_integrity}
294260
end
@@ -404,18 +370,4 @@ defmodule ExSTUN.Message do
404370
other -> other
405371
end
406372
end
407-
408-
defp get_username(msg) do
409-
case get_attribute(msg, Username) do
410-
nil -> {:error, :no_username}
411-
other -> other
412-
end
413-
end
414-
415-
defp get_realm(msg) do
416-
case get_attribute(msg, Realm) do
417-
nil -> {:error, :no_realm}
418-
other -> other
419-
end
420-
end
421373
end

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule ExSTUN.MixProject do
1010
version: @version,
1111
elixir: "~> 1.13",
1212
start_permanent: Mix.env() == :prod,
13-
description: "Implementation of STUN protocol",
13+
description: "Implementation of the STUN protocol",
1414
package: package(),
1515
deps: deps(),
1616

0 commit comments

Comments
 (0)