Skip to content

Commit

Permalink
Merge pull request #16 from ritou/work/fix-extention-handling-for-att…
Browse files Browse the repository at this point in the history
…estation

Use external CBOR library and add handling of ED flags
  • Loading branch information
ritou authored Dec 3, 2023
2 parents 636f780 + ed0895f commit dde0f59
Show file tree
Hide file tree
Showing 15 changed files with 770 additions and 293 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.4.0 (2023-09-29)

* Support BE, BS flags

## 0.3.0 (2023-09-28)

* Update aaguid handling
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ by adding `web_authn_lite` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:web_authn_lite, "~> 0.3"}
{:web_authn_lite, "~> 0.4"}
]
end
```
Expand Down
12 changes: 9 additions & 3 deletions lib/web_authn_lite/attestation_object.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defmodule WebAuthnLite.AttestationObject do
* Handling AttestationStatement
"""

alias WebAuthnLite.{AuthenticatorData, CBOR}
alias WebAuthnLite.AuthenticatorData

defstruct [:auth_data, :fmt, :att_stmt, :raw]

Expand All @@ -26,14 +26,20 @@ defmodule WebAuthnLite.AttestationObject do
def decode(base64_url_encoded_attestation_object) do
try do
with raw <- base64_url_encoded_attestation_object |> Base.url_decode64!(padding: false),
%{"authData" => auth_data_binary, "fmt" => fmt, "attStmt" => att_stmt} <-
raw |> CBOR.decode!(),
{:ok,
%{
"authData" => %CBOR.Tag{tag: :bytes, value: auth_data_binary},
"fmt" => fmt,
"attStmt" => att_stmt
}, _} <- raw |> CBOR.decode(),
{:ok, auth_data} <-
auth_data_binary
|> WebAuthnLite.AuthenticatorData.from_binary() do
# TODO: handling attestation statement
{:ok, %__MODULE__{auth_data: auth_data, fmt: fmt, att_stmt: att_stmt, raw: raw}}
else
# cbor decode error
{:error, :cbor_function_clause_error} -> @rounded_error
{:error, _} = error -> error
_ -> @rounded_error
end
Expand Down
32 changes: 25 additions & 7 deletions lib/web_authn_lite/attested_credential_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ defmodule WebAuthnLite.AttestedCredentialData do
https://www.w3.org/TR/webauthn/#sec-attested-credential-data
"""

alias WebAuthnLite.{CredentialPublicKey, CBOR}
alias WebAuthnLite.CredentialPublicKey

defstruct [:aaguid, :authenticator_name, :credential_id, :credential_public_key, :raw]
defstruct [
:aaguid,
:authenticator_name,
:credential_id,
:credential_public_key,
:raw,
:extensions
]

@min_size_of_attested_credential_data 18

Expand All @@ -16,7 +23,8 @@ defmodule WebAuthnLite.AttestedCredentialData do
authenticator_name: String.t(),
credential_id: String.t(),
credential_public_key: term,
raw: binary
raw: binary,
extensions: map | nil
}

@rounded_error {:error, :invalid_attested_credential_data}
Expand Down Expand Up @@ -52,21 +60,22 @@ defmodule WebAuthnLite.AttestedCredentialData do
attested_credential_data
|> :binary.part(18, credential_id_length)
|> Base.url_encode64(padding: false),
credential_public_key <-
{:ok, decoded, extensions} <-
attested_credential_data
|> :binary.part(
18 + credential_id_length,
byte_size(attested_credential_data) - credential_id_length - 18
)
|> CBOR.decode!()
|> CredentialPublicKey.from_cbor_map() do
|> CBOR.decode(),
credential_public_key <- decoded |> CredentialPublicKey.from_cbor_map() do
{:ok,
%__MODULE__{
aaguid: aaguid,
authenticator_name: authenticator_name,
credential_id: credential_id,
credential_public_key: credential_public_key,
raw: attested_credential_data
raw: attested_credential_data,
extensions: parse_extensions(extensions)
}}
else
{:error, _} = error -> error
Expand Down Expand Up @@ -102,4 +111,13 @@ defmodule WebAuthnLite.AttestedCredentialData do
true -> nil
end
end

defp parse_extensions(""), do: nil

defp parse_extensions(bytes) do
case CBOR.decode(bytes) do
{:ok, decoded, _} -> decoded
_ -> nil
end
end
end
37 changes: 22 additions & 15 deletions lib/web_authn_lite/authenticator_data.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defmodule WebAuthnLite.AuthenticatorData do
sign_count: Integer.t(),
raw: String.t(),
attested_credential_data: binary,
extensions: binary
extensions: map | nil
}

@min_size_of_authenticator_data 37
Expand All @@ -38,27 +38,34 @@ defmodule WebAuthnLite.AuthenticatorData do
true <- raw |> byte_size() >= @min_size_of_authenticator_data,
rp_id_hash <- raw |> :binary.part(0, 32),
flags <- raw |> :binary.part(32, 1) |> Flags.from_binary() |> elem(1),
sign_count <- raw |> :binary.part(33, 4) |> :binary.decode_unsigned() do
attested_credential_data =
if flags.at && !flags.ed,
do:
raw
|> :binary.part(37, byte_size(raw) - 37)
|> AttestedCredentialData.from_binary()
|> elem(1),
else: nil

extensions =
if !flags.at && flags.ed, do: raw |> :binary.part(37, byte_size(raw) - 37), else: nil

sign_count <- raw |> :binary.part(33, 4) |> :binary.decode_unsigned(),
{:ok, attested_credential_data} <-
(if flags.at do
raw
|> :binary.part(37, byte_size(raw) - 37)
|> AttestedCredentialData.from_binary()
else
{:ok, nil}
end),
{:ok, extensions, _} <-
(if !flags.at && flags.ed do
raw |> :binary.part(37, byte_size(raw) - 37) |> CBOR.decode()
else
{:ok, nil, nil}
end) do
{:ok,
%__MODULE__{
rp_id_hash: rp_id_hash |> Base.url_encode64(padding: false),
flags: flags,
sign_count: sign_count,
raw: raw,
attested_credential_data: attested_credential_data,
extensions: extensions
extensions:
if attested_credential_data do
attested_credential_data.extensions
else
extensions
end
}}
else
{:error, _} = error -> error
Expand Down
85 changes: 0 additions & 85 deletions lib/web_authn_lite/cbor.ex

This file was deleted.

14 changes: 11 additions & 3 deletions lib/web_authn_lite/credential_public_key/es256.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ defmodule WebAuthnLite.CredentialPublicKey.ES256 do

@spec from_cbor_map(map) :: t
def from_cbor_map(cbor_map) do
with key_map <- %{
with %CBOR.Tag{
tag: :bytes,
value: x_bytes
} <- cbor_map[-2],
%CBOR.Tag{
tag: :bytes,
value: y_bytes
} <- cbor_map[-3],
key_map <- %{
"kty" => "EC",
"crv" => "P-256",
"x" => cbor_map[-2] |> Base.url_encode64(padding: false),
"y" => cbor_map[-3] |> Base.url_encode64(padding: false)
"x" => x_bytes |> Base.url_encode64(padding: false),
"y" => y_bytes |> Base.url_encode64(padding: false)
},
{@jose_jwk_kty, key} <- JOSE.JWK.from_map(key_map).kty do
%__MODULE__{digest_type: :sha256, key: key, map: key_map, json: key_map |> Jason.encode!()}
Expand Down
14 changes: 11 additions & 3 deletions lib/web_authn_lite/credential_public_key/rs256.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@ defmodule WebAuthnLite.CredentialPublicKey.RS256 do

@spec from_cbor_map(map) :: t
def from_cbor_map(cbor_map) do
with key_map <- %{
with %CBOR.Tag{
tag: :bytes,
value: n_bytes
} <- cbor_map[-1],
%CBOR.Tag{
tag: :bytes,
value: e_bytes
} <- cbor_map[-2],
key_map <- %{
"kty" => "RSA",
"n" => cbor_map[-1] |> Base.url_encode64(padding: false),
"e" => cbor_map[-2] |> Base.url_encode64(padding: false)
"n" => n_bytes |> Base.url_encode64(padding: false),
"e" => e_bytes |> Base.url_encode64(padding: false)
},
{@jose_jwk_kty, key} <- JOSE.JWK.from_map(key_map).kty do
%__MODULE__{digest_type: :sha256, key: key, map: key_map, json: key_map |> Jason.encode!()}
Expand Down
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ defmodule WebAuthnLite.MixProject do
def project do
[
app: :web_authn_lite,
version: "0.3.0",
version: "0.4.0",
elixir: "~> 1.15",
start_permanent: Mix.env() == :prod,
description:
Expand All @@ -30,6 +30,7 @@ defmodule WebAuthnLite.MixProject do
[
{:jose, "~> 1.8"},
{:jason, "~> 1.1"},
{:cbor, "~> 1.0"},
{:ex_doc, ">= 0.0.0", only: :dev}
]
end
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
%{
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm", "fab09b20e3f5db886725544cbcf875b8e73ec93363954eb8a1a9ed834aa8c1f9"},
"cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"},
"earmark": {:hex, :earmark, "1.3.0", "17f0c38eaafb4800f746b457313af4b2442a8c2405b49c645768680f900be603", [:mix], [], "hexpm", "f8b8820099caf0d5e72ae6482d2b0da96f213cbbe2b5b2191a37966e119eaa27"},
"ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "dc87f778d8260da0189a622f62790f6202af72f2f3dee6e78d91a18dd2fcd137"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
Expand Down
9 changes: 9 additions & 0 deletions test/lib/web_authn_lite/authenticator_data_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ defmodule WebAuthnLite.ClientDataJSONTest do

@encoded_authenticator_data_with_attested_credential_data "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAAC_igEfOMCk0VgAYXER-e3H0AEDIBRoyihvjNZOR2yfjLPhulAQIDJiABIVggIXUM4qBXox--h7XwLrTlN4oPj-8bE27wjXlEZIRHL4kiWCBFllqpZSGGRUTgbLTjR5_H4oUr0SJIm3oE659m5sVxUw"

@encoded_authenticator_data_with_at_and_ed_flags "LWYN6Zr3LGATQ5ltOzp9kKaO99hlDHvEX0cJz9XAja_FAAAAAgAAAAAAAAAAAAAAAAAAAAAAELTtWv7zjeH54PG64kKvaZWlAQIDJiABIVggiNnMCzfEsztcR_MWS0hqXh_1pYOPUBRyiEiic8IeYfoiWCA2RRMKV77d4qdiJxzMew4L9vw8jb9zv2dg-owBglUZDqFrY3JlZFByb3RlY3QC"

test "decode, from_binary" do
assert {:ok, authenticator_data} = AuthenticatorData.decode(@valid_encoded_authenticator_data)

Expand Down Expand Up @@ -46,6 +48,13 @@ defmodule WebAuthnLite.ClientDataJSONTest do
refute is_nil(authenticator_data.attested_credential_data)
end

test "with AT and ED" do
assert {:ok, authenticator_data} =
AuthenticatorData.decode(@encoded_authenticator_data_with_at_and_ed_flags)

assert authenticator_data.extensions == %{"credProtect" => 2}
end

test "valid_rp_id_hash?" do
assert AuthenticatorData.valid_rp_id_hash?(
"localhost",
Expand Down
Loading

0 comments on commit dde0f59

Please sign in to comment.