{"id":13703035,"url":"https://github.com/tanguilp/wax","last_synced_at":"2025-05-15T13:07:10.966Z","repository":{"id":41905102,"uuid":"164239632","full_name":"tanguilp/wax","owner":"tanguilp","description":"WebAuthn for Elixir","archived":false,"fork":false,"pushed_at":"2025-05-07T18:51:30.000Z","size":423,"stargazers_count":197,"open_issues_count":7,"forks_count":20,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-07T19:43:59.950Z","etag":null,"topics":["fido2","webauthn"],"latest_commit_sha":null,"homepage":"https://hexdocs.pm/wax_","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tanguilp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["tanguilp"]}},"created_at":"2019-01-05T18:10:26.000Z","updated_at":"2025-04-16T10:44:32.000Z","dependencies_parsed_at":"2024-01-25T04:47:35.934Z","dependency_job_id":"71151bfc-884b-4322-a74a-3f4f1fd46d06","html_url":"https://github.com/tanguilp/wax","commit_stats":{"total_commits":128,"total_committers":3,"mean_commits":"42.666666666666664","dds":0.0703125,"last_synced_commit":"f55c2e9dbfe7d8d58537f89a4ff80c05e3363b96"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanguilp%2Fwax","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanguilp%2Fwax/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanguilp%2Fwax/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tanguilp%2Fwax/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tanguilp","download_url":"https://codeload.github.com/tanguilp/wax/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254346624,"owners_count":22055808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fido2","webauthn"],"created_at":"2024-08-02T21:00:49.320Z","updated_at":"2025-05-15T13:07:10.952Z","avatar_url":"https://github.com/tanguilp.png","language":"Elixir","funding_links":["https://github.com/sponsors/tanguilp"],"categories":["Server Libraries"],"sub_categories":[],"readme":"# Wax\n\nWebAuthn library for elixir\n\n\u003cimg src=\"https://raw.githubusercontent.com/tanguilp/wax/master/wax.png\" width=\"128\"/\u003e\n\nGoal: implement a *comprehensive* FIDO2 library on the server side\n(*Relying party* or RP in the WebAuthn terminology) to authenticate users with WebAuthn.\n\nFor semantics (FIDO2, WebAuthn, FIDO...), read\n[this article](https://medium.com/@herrjemand/introduction-to-webauthn-api-5fd1fb46c285)\n\n## Demo app\n\nYou can try out and study WebAuthn authentication with Wax thanks to the\n[wax_demo](https://github.com/tanguilp/wax_demo) test application.\n\nSee also a video demonstration of an authentication flow which allows replacing the password\nauthentication scheme by a WebAuthn password-less authentication:\n\n[![Demo screenshot](https://raw.githubusercontent.com/tanguilp/wax_demo/master/assets/static/images/demo_screenshot.png)](https://vimeo.com/358361625)\n\n## Project status\n\n- Support the FIDO2 standard (especially all types of attestation statement formats and\nall mandatory algorithms). See the \"Support of FIDO2\" section for further information\n- **Passes all the 170 tests** of the official test suite (tested using\n[WaxFidoTestSuiteServer](https://github.com/tanguilp/wax_fido_test_suite_server))\n- This library has **not** be reviewed by independent security / FIDO2 specialists - use\nit at your own risks or blindly trust its author!\n- This library does not come with a javascript library to handle WebAuthn calls\n\n## Compatibility\n\nOTP25+\n\n## Installation\n\nAdd the following line to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:wax_, \"~\u003e 0.6.0\"}\n  ]\nend\n```\n\nNote that due to a [name collision](https://github.com/resuelve/wax/issues/16), the application\nname is `:wax_`, and not `:wax`. It doesn't cause issues using both library because the other's\npackage doesn't use the atom `Wax` as the base module name.\n\n[Documentation](https://hexdocs.pm/wax_)\n\n## Usage\n\nTo use FIDO2 for authentication, you must first *register* a new FIDO2 key for a user. The\nprocess is therefore the following:\n1. Register a FIDO2 key for a user\n2. Authenticate as much as you want the user using the FIDO2 key registered in step 1\n\nOptionaly, you might want to store more than one key, for instance if the user have\nseveral authenticators.\n\nThe Wax library doesn't provide with a user data store to store the key generated in step\n1 and to retrieve it in step 2. Instead, it lets you use any data store. The data to\nbe stored is described in the `Wax` module's documentation.\n\n### Registration\n\nWax provides with 2 functions for registration:\n1. `Wax.new_registration_challenge/1`: generates a challenge that must subsequently be sent\nto the client for use by the javascript WebAuthn API\n2. `Wax.register/3`: takes into parameter the response of the WebAuthn javascript API and\nthe challenge generated in step 1, and verifies it\n\nSince the challenge generated in step 1 must be passed as a parameter in step 2, it is\nrequired to persist it on the server side, for instance in the session:\n\n```elixir\n# generating a challenge\n\nchallenge = Wax.new_registration_challenge()\n\nconn\n|\u003e put_session(:challenge, challenge)\n|\u003e render(register_key_page, challenge: challenge.bytes)\n# the challenge is to be sent on the client one way or another\n# this can be direct within the HTML, or using an API call\n```\n\nto be then retrieved when verifying the assertion:\n\n```elixir\nchallenge = get_session(conn, :challenge)\n\ncase Wax.register(attestation_object, client_data_json, challenge) do\n\t{:ok, {authenticator_data, attestation_result}} -\u003e\n\t\t# success case\n\n\t{:error, e} -\u003e\n\t\t# verification failure\nend\n```\n\nIn the success case, a server will save the credential id (generated by the WebAuthn\njavascript call) and the cose key in its user database for use for authentication.\n\nAuthenticator data contains the COSE key generated by the authenticator, which can be\nfound in `authenticator_data.attested_credential_data.credential_public_key`:\n\n```elixir\n%{\n  -3 =\u003e \u003c\u003c182, 81, 183, 218, 92, 107, 106, 120, 60, 51, 75, 104, 141, 130,\n    119, 232, 34, 245, 84, 203, 246, 165, 148, 179, 169, 31, 205, 126, 241,\n    188, 241, 176\u003e\u003e,\n  -2 =\u003e \u003c\u003c89, 29, 193, 225, 4, 234, 101, 162, 32, 6, 15, 14, 130, 179, 223,\n    207, 53, 2, 134, 184, 178, 127, 51, 145, 57, 180, 104, 242, 138, 96, 27,\n    221\u003e\u003e,\n  -1 =\u003e 1,\n  1 =\u003e 2,\n  3 =\u003e -7\n}\n```\n\nIt probably doesn't need to be searchable or indexed, which is why one can store as a binary.\nTo convert back and forth Elixir data structures to binary and store the keys in a database\n(SQL, for instance), take a look at the Erlang functions `:erlang.term_to_binary/1` and\n`:erlang.binary_to_term/1`.\n\nFor further information, refer to the `Wax` module documentation.\n\n### Authentication\n\nThe process is quite similar, with 2 functions for authentication:\n1. `Wax.new_authentication_challenge/1`: generates a challenge from a list of\n`{credential id, key}` saved during the registration processes. It also has to be sent to\nthe client for use by the javascript WebAuthn API\n2. `Wax.authenticate/5`: to be called to verify the WebAuthn javascript API response\nwith the returned data (composed of signature, authenticator data, etc.) with the\nchallenge generated in step 1\n\nThis also requires storing the challenge:\n```elixir\ncred_ids_and_keys = UserStore.get_keys(username)\n\nchallenge = Wax.new_authentication_challenge(allow_credentials: cred_ids_and_keys)\n\nconn\n|\u003e put_session(:authentication_challenge, challenge)\n|\u003e render(auth_verify_page, challenge: challenge.bytes, creds: cred_ids_and_keys)\n# the challenge is to be sent on the client one way or another\n# this can be direct within the HTML, or using an API call\n```\n\nto be passed as a paramter to the `Wax.authenticate/5` function:\n\n```elixir\nchallenge = get_session(conn, :authentication_challenge)\n\ncase Wax.authenticate(raw_id, authenticator_data, sig, client_data_json, challenge) do\n\t{:ok, _} -\u003e\n\t\t# ok, user authenticated\n\n\t{:error, _} -\u003e\n\t\t# invalid WebAuthn response\nend\n```\n\nFor further information, refer to the `Wax` module documentation.\n\n## Options\nThe options are set when generating the challenge (for both registration and\nauthentication). Options can be configured either globally in the configuration\nfile or when generating the challenge. Some also have default values.\n\nOption values set during challenge generation take precedence over globally configured\noptions, which takes precedence over default values.\n\nThese options are:\n\n|  Option       |  Type         |  Applies to       |  Default value                | Notes |\n|:-------------:|:-------------:|-------------------|:-----------------------------:|-------|\n|`attestation`|`\"none\"` or `\"direct\"`|registration|`\"none\"`| |\n|`origin`|`String.t()`|registration \u0026 authentication| | **Mandatory**. Example: `https://www.example.com` |\n|`rp_id`|`String.t()` or `:auto`|registration \u0026 authentication|If set to `:auto`, automatically determined from the `origin` (set to the host) | With `:auto`, it defaults to the full host (e.g.: `www.example.com`). This option allow you to set the `rp_id` to another valid value (e.g.: `example.com`) |\n|`user_verification`|`\"discouraged\"`, `\"preferred\"` or `\"required\"`|registration \u0026 authentication|`\"preferred\"`| |\n|`trusted_attestation_types`|`[t:Wax.Attestation.type/0]`|registration|`[:none, :basic, :uncertain, :attca, :anonca, :self]`| |\n|`verify_trust_root`|`boolean()`|registration|`true`|Only for `u2f` and `packed` attestation. `tpm` attestation format is always checked against metadata|\n|`acceptable_authenticator_statuses`|`[String.t()]`|registration|`[\"FIDO_CERTIFIED\", \"FIDO_CERTIFIED_L1\",  \"FIDO_CERTIFIED_L1plus\", \"FIDO_CERTIFIED_L2\", \"FIDO_CERTIFIED_L2plus\", \"FIDO_CERTIFIED_L3\", \"FIDO_CERTIFIED_L3plus\"]`| The `\"UPDATE_AVAILABLE\"` status is not whitelisted by default |\n|`timeout`|`non_neg_integer()`|registration \u0026 authentication|`20 * 60`| The validity duration of a challenge, in seconds |\n|`android_key_allow_software_enforcement`|`boolean()`|registration|`false`| When registration is a Android key, determines whether software enforcement is acceptable (`true`) or only hardware enforcement is (`false`) |\n|`silent_authentication_enabled`|`boolean()`|authentication|`false`| See [https://github.com/fido-alliance/conformance-tools-issues/issues/434](https://github.com/fido-alliance/conformance-tools-issues/issues/434) |\n|`bytes`|`binary()`|registration \u0026 authentication|random bytes|Allows to provide with your own challenge. This is **not** recommended unless you know what you're doing. Refer to the [Security considerations](#security-considerations) for more information|\n\n## FIDO2 Metadata\n\nIf you use attestation, you need to enabled metadata.\n\n### Configuring MDSv3 metadata\n\nThis is the official metadata service of the FIDO foundation.\n\nSet the `:update_metadata` environment variable to `true` and metadata will load\nautomatically through HTTP from\n[https://mds3.fidoalliance.org/](https://fidoalliance.org/metadata/).\n\n### Loading FIDO2 metadata from a directory\n\nIn addition to the FIDO2 metadata service, it is possible to load metadata from a directory.\nTo do so, the `:metadata_dir` application environment variable must be set to one of:\n- a `String.t()`: the path to the directory containing the metadata files\n- an `atom()`: in this case, the files are loaded from the `\"fido2_metadata\"` directory of the\nprivate (`\"priv/\"`) directory of the application (whose name is the atom)\n\nIn both case, Wax tries to load all files (even directories and other special files).\n\n#### Example configuration\n\n```elixir\nconfig :wax_,\n  origin: \"http://localhost:4000\",\n  rp_id: :auto,\n  metadata_dir: :my_application\n```\n\nwill try to load all files of the `\"priv/fido2_metadata/\"` of the `:my_application` as FIDO2\nmetadata statements. On failure, a warning is emitted.\n\n## Security considerations\n\n- Make sure to understand the implications of not using attested credentials before\naccepting `none` or `self` attestation types, or disabling it for `packed` and `u2f`\nformats by disabling it with the `verify_trust_root` option\n- This library has **not** be reviewed by independent security / FIDO2 specialists - use\nit at your own risks or blindly trust its author! If you're knowledgeable about\nFIDO2 and willing to help reviewing it, please contact the author\n- When providing your own challenge, please make sure to understand that:\n  - it explicitly violates the recommandation in the standard\n  - it exposes you to some attacks (such as replay attacks)\n  - you have no guarantee that the user understood what he has been signing (no specific browser UI\n  was presented)\n\n## Changes\n\nSee [CHANGELOG.md](CHANGELOG.md).\n\n## Support of FIDO2\n\n### Server Requirements and Transport Binding Profile\n\n[2. Registration and Attestations](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#registration-and-attestation)\n- [x] **Mandatory**: registration support\n- [x] **Mandatory**: random challenge\n- [2.1. Validating Attestation](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#validating-attestation)\n  - [x] **Mandatory**: attestation validation\n  - [x] **Mandatory**: attestation certificate chains (note: can be disabled through an option)\n  - [x] **Mandatory**: validation of attestation through the FIDO Metadata Service \n- [2.2. Attestation Types](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#attestation-types)\n  - [x] **Mandatory**: basic attestation\n  - [x] **Mandatory**: self attestation\n  - [x] **Mandatory**: private CA attestation\n  - [ ] *Optional*: elliptic curve direct anonymous attestation\n- [2.3. Attestation Formats](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#attestation-formats)\n  - [x] **Mandatory**: packed attestation\n  - [x] **Mandatory**: TPM attestation\n  - [x] *Optional*: Android key attestation\n  - [x] **Mandatory**: U2F attestation\n  - [ ] *Deprecated*: Android Safetynet attestation (removed in `v0.7.0`)\n  - [x] [**Mandatory**](https://medium.com/webauthnworks/webauthn-fido2-whats-new-in-mds3-migrating-from-mds2-to-mds3-a271d82cb774#c977): Apple Anonymous\n\n[3. Authentication and Assertions](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#authn-and-assertion)\n  - [x] **Mandatory**: authentication\n  - [x] **Mandatory**: random challenge\n  - [x] **Mandatory**: assertion signature validation\n  - [x] **Mandatory**: TUP verification (note: and also user verified, through an option)\n\n[4. Communication Channel Requirements](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#communication-channel-requirements)\n  - [ ] *Optional*: TokenBinding support (won't be supported following Chrome drop of the now\n  dead token binding standard)\n\n[5. Extensions](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#extensions)\n  - [x] **Mandatory**: registration and authentication support without extension\n  - [ ] *Optional*: extension support\n  - [ ] *Optional*: appid extension support\n\n[6. Other](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#other)\n  - [x] **Mandatory**: RS1 (RSASSA-PKCS1-v1_5 w/ SHA-1) algorithm support\n  - [x] **Mandatory**: RS256 (RSASSA-PKCS1-v1_5 w/ SHA-256) algorithm support\n  - [x] *Optional*: RS384 (RSASSA-PKCS1-v1_5 w/ SHA-384) algorithm support\n  - [x] *Optional*: RS512 (RSASSA-PKCS1-v1_5 w/ SHA-512) algorithm support\n  - [x] *Optional*: PS256 (RSASSA-PSS w/ SHA-256) algorithm support\n  - [x] *Optional*: PS384 (RSASSA-PSS w/ SHA-384) algorithm support\n  - [x] *Optional*: PS512 (RSASSA-PSS w/ SHA-512) algorithm support\n  - [x] **Mandatory**: ES256 (ECDSA using P-256 and SHA-256) algorithm support\n  - [x] *Optional*: ES384 (ECDSA using P-384 and SHA-384) algorithm support\n  - [x] *Optional*: ES512 (ECDSA using P-521 and SHA-512) algorithm support\n  - [x] *Optional*: EdDSA algorithm support\n  - [x] *Optional*: ES256K (ECDSA using P-256K and SHA-256) algorithm support\n  - [ ] **Mandatory**: compliance with the FIDO privacy principles (note: out-of-scope, to be implemented by the server using the Wax library)\n\n[7. Transport Binding Profile](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html#transport-binding-profile)\n  - [x] *optional*: API implementation ([`WaxAPIRest`](https://github.com/tanguilp/wax_api_rest))\n\n### FIDO Metadata Service\n\n[3.1.8 Metadata TOC object processing rules](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-metadata-service-v2.0-rd-20180702.html#metadata-toc-object-processing-rules)\n  - [ ] TOC verification against the `x5u` attribute (note: doesn't seem to be used)\n  - [x] TOC verification against the `x5c` attribute\n  - [x] TOC CRLs verification\n  - [x] Loading and verification of metadata statements against the hased value of the TOC\n  - [x] Handling of the status of the authenticator (through whitelisting, see the\n  `:acceptable_authenticator_statuses` option)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftanguilp%2Fwax","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftanguilp%2Fwax","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftanguilp%2Fwax/lists"}