WebAuthn relying-party verification of registrations and assertions

README.md

nox-webauthn #

WebAuthn relying-party verification for OCaml: passkey and security-key registrations and assertions, checked step by step against W3C Web Authentication Level 3.

A relying party asks a browser to create a credential (registration) or to sign a challenge with one (authentication), and receives byte strings back: clientDataJSON and attestationObject, or clientDataJSON, authenticatorData and signature. Webauthn.Registration.verify runs the steps of section 7.1 over the first pair and returns the credential record to store; Webauthn.Assertion.verify runs those of section 7.2 over the second and returns the record updated with the new signature counter. A refusal is a Webauthn.Error.t naming the ceremony and the step that failed.

The none and packed attestation formats are verified, packed through the certificate requirements of section 8.2.1 and a chain to the relying party's own trust anchors. tpm, android-key, apple and fido-u2f statements are refused by name. Credential public keys are COSE_Keys of the cose package (ES256, ES384, ES512, EdDSA, PS256, RS256).

Verification is pure: the relying party, the challenge it issued, the stored credential and the current time are arguments, so the library reads no clock, no network and no database.

Installation #

$ opam install nox-webauthn

Usage #

let rp =
  Webauthn.Relying_party.v ~id:"example.org"
    ~origins:[ "https://example.org" ] ()

(* After navigator.credentials.create: [challenge] is the one this
   server issued, the two strings are the base64url-decoded response and
   [now] is the current time. *)
let register ~now ~challenge ~client_data_json attestation_object =
  match
    Webauthn.Registration.verify ~rp
      ~algorithms:[ Cose.Algorithm.ES256; Cose.Algorithm.RS256 ]
      ~challenge ~now ~client_data_json
      attestation_object
  with
  | Ok r -> Ok (Webauthn.Registration.credential r)
  | Error e -> Error (Fmt.str "%a" Webauthn.Error.pp e)

(* After navigator.credentials.get, with the record stored above. *)
let authenticate ~challenge ~credential ~client_data_json
    ~authenticator_data signature =
  Webauthn.Assertion.verify ~user_verification:Webauthn.Required ~rp
    ~challenge ~credential ~client_data_json ~authenticator_data signature
  |> Result.map Webauthn.Assertion.credential

Storing the record, refusing a credential id already registered (section 7.1 step 26) and finding the record an assertion names (section 7.2 steps 5 and 6) are the caller's.

  • webauthn (robur, BSD-2) verifies the none and fido-u2f attestations with P-256 keys and leaves counters and user verification to the caller.
  • webauthn-rs (Rust) and go-webauthn (Go) cover every attestation format and FIDO metadata; this package's packed attestation tests replay some of their fixtures.

References #

Licence #

ISC. See LICENSE.md. The test fixtures keep their own licences; see test/fixtures/README.md.