defmodule Webauthn.AuthenticatorData do alias Webauthn.AuthenticatorData @moduledoc """ Information about the authenticator data can be found at the link below https://www.w3.org/TR/webauthn/#authenticator-data """ defstruct [ :acd_included, :attested_credential_data, :extension_included, :extensions, :raw_data, :rp_id_hash, :user_present, :user_verified, :sign_count ] def parse(%{"authData" => auth_data}), do: parse(auth_data) def parse(%CBOR.Tag{tag: :bytes, value: value}) do parse_rp_id(value, %AuthenticatorData{raw_data: value}) end def parse(value) when is_binary(value) do parse_rp_id(value, %AuthenticatorData{raw_data: value}) end def parse(_other), do: {:error, "Invalid authenticator data format"} def parse_rp_id(<>, ad) do parse_flags(rest, Map.merge(ad, %{rp_id_hash: rp_id_hash})) end defp parse_flags(<>, ad) do parse_sign_count(rest, Map.merge(ad, %{ acd_included: at, extension_included: ed, user_present: up, user_verified: uv })) end defp parse_sign_count(<>, ad) do parse_acd(rest, Map.merge(ad, %{sign_count: sign_count})) end # https://www.w3.org/TR/webauthn/#sec-attested-credential-data defp parse_acd(<>, %AuthenticatorData{acd_included: 1} = ad) do case CBOR.decode(pk_bin) do {:ok, public_key, rest} -> parse_extensions(rest, Map.merge(ad, %{ attested_credential_data: %{ aaguid: aaguid, credential_id: cred_id, credential_public_key: public_key } })) error -> error end end defp parse_acd(binary, ad), do: parse_extensions(binary, ad) # https://www.w3.org/TR/webauthn/#extensions defp parse_extensions(binary, %AuthenticatorData{extension_included: 1} = ad) do case CBOR.decode(binary) do {:ok, extensions, _rest} -> {:ok, Map.merge(ad, %{extensions: extensions})} error -> error end end defp parse_extensions(_binary, ad), do: {:ok, ad} end