Skip to main content

webauthn_kit/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![cfg_attr(docsrs, allow(unused_attributes))]
3//! # webauthn-kit
4//!
5//! Standalone `WebAuthn` / FIDO2 passkey verification kit: real CTAP2/COSE
6//! cryptographic verification with zero framework coupling. This is not a
7//! wrapper around `navigator.credentials` server emulation — it implements
8//! the server side of the protocol directly on `ring`.
9//!
10//! ## What it does
11//!
12//! - Full CTAP2 authenticator data parsing (rpIdHash, flags, signCount,
13//!   attested credential data incl. AAGUID) — [`protocol`]
14//! - `verify_registration` / `verify_authentication` ceremonies — [`protocol`]
15//! - Attestation statement verification for the `none`, `packed` (self- and
16//!   x5c-based basic/AttCA), and `fido-u2f` formats, with X.509 chain
17//!   verification against caller-configured trust anchors — [`attestation`]
18//! - COSE public key parsing and signature verification for **ES256**
19//!   (ECDSA P-256 + SHA-256), **ES384** (ECDSA P-384 + SHA-384), **EdDSA**
20//!   (Ed25519), and **RS256** (RSA PKCS#1 v1.5 + SHA-256) via `ring` —
21//!   [`crypto`]
22//! - Enforceable credential policies — user verification (UV), multi-device
23//!   / backup eligibility (BE/BS), and registration algorithm allowlists —
24//!   [`policy`]
25//! - Challenge generation from the OS CSPRNG, single-use consumption
26//!   (replay protection), freshness/expiry enforcement, and the sign-count
27//!   clone-detection state machine — [`challenge`]
28//! - Storage-free credential record + browser wire DTOs — [`credential`]
29//!
30//! ## Quick start
31//!
32//! ```
33//! use webauthn_kit::{
34//!     check_sign_count, verify_authentication, verify_registration, AttestationPolicy,
35//!     AuthenticationParams, ChallengeStore, CredentialPolicy, UserVerificationPolicy,
36//!     WebauthnConfig,
37//! };
38//!
39//! let config = WebauthnConfig {
40//!     rp_id: "example.com".into(),
41//!     rp_name: "Example".into(),
42//!     rp_origins: vec!["https://example.com".into()],
43//!     allowed_algorithms: vec![-7, -35, -257], // ES256, ES384, RS256
44//!     challenge_timeout_secs: 300,
45//!     attestation: AttestationPolicy::default(),
46//!     // Server-side enforcement: UV required, synced passkeys rejected.
47//!     credential_policy: CredentialPolicy {
48//!         user_verification: UserVerificationPolicy::Required,
49//!         ..CredentialPolicy::default()
50//!     },
51//!     resident_key: Default::default(),
52//!     attestation_conveyance: Default::default(),
53//! };
54//!
55//! // 1. Issue a challenge (single-use; store the (id, bytes) pair).
56//! let store = ChallengeStore::new();
57//! let (challenge_b64, create_options) =
58//!     store.generate_registration_challenge(&config, "alice", "Alice", &[]);
59//! // ... send `create_options` to the browser, receive RegistrationResponse ...
60//!
61//! // 2. Verify a registration response (see tests/vectors.rs for a full
62//! //    roundtrip with a synthetic keypair).
63//! // let result = verify_registration(&challenge, &client_data_json_b64,
64//! //     &attestation_object_b64, "", &config.rp_id, &config.rp_origins,
65//! //     &config.attestation)?;
66//!
67//! // 3. Verify an assertion; persist result.new_sign_count afterwards.
68//! // let result = verify_authentication(&AuthenticationParams { ... })?;
69//! ```
70//!
71//! ## Storage contract (what this crate does NOT do)
72//!
73//! This crate is deliberately storage-free. The integrator owns:
74//!
75//! - **Credential persistence**: after [`verify_registration`] succeeds,
76//!   store a [`credential::WebauthnCredential`] (credential ID, COSE public
77//!   key, sign count, timestamps). After each successful
78//!   [`verify_authentication`], atomically persist
79//!   [`credential::AuthenticationResult::new_sign_count`].
80//! - **Challenge durability**: [`ChallengeStore`] holds pending challenges
81//!   in memory; they are lost on restart (users simply retry). Keep the
82//!   consume-then-verify ordering so restarts can never enable replays.
83//! - **User mapping and session issuance** (cookies, JWTs, ...).
84//! - **UI / HTTP layer**: the `credential` DTOs serialize with the `serde`
85//!   feature but no HTTP types are included.
86//!
87//! ## Security notes
88//!
89//! - `#![forbid(unsafe_code)]`; all cryptography via `ring`.
90//! - Every parser accepts hostile input and returns `Err` instead of
91//!   panicking (property/fuzz-tested in `tests/fuzz.rs`).
92//! - Challenge bytes are 32 bytes from the OS CSPRNG; consumption is
93//!   single-use and freshness-bounded.
94//! - **Attestation is verified for `none`, `packed`, and `fido-u2f`**;
95//!   unknown formats are rejected unless the
96//!   `AttestationPolicy::allow_unknown_formats` escape hatch is set
97//!   (trust-weakening). Provenance claims are bounded by the configured
98//!   trust anchors: with none configured, unanchored certificate chains are
99//!   accepted but flagged (`TrustLevel::BasicAtt` + warning) and must be
100//!   treated as self-attested. Only `TrustLevel::AttCa` attests device
101//!   provenance. Other formats (`android-key`, `tpm`, ...) are tranche-2
102//!   work — see [`attestation`].
103//! - Supported algorithms: ES256 (COSE −7), ES384 (COSE −35), EdDSA
104//!   (Ed25519, COSE −8), and RS256 (COSE −257). Curve/algorithm bindings are
105//!   enforced at parse time (a P-256 key claiming ES384 is rejected); other
106//!   algorithms are rejected with [`WebauthnError::UnsupportedAlgorithm`].
107//! - Sign-count policy: a stored counter of 0 disables the check
108//!   (authenticators without counters); otherwise a strictly decreasing
109//!   counter is rejected. Equal counters are allowed by design.
110//! - **User verification** ([`policy::UserVerificationPolicy`]): `Required`
111//!   is enforced server-side — ceremonies whose authenticator UV flag is
112//!   clear fail with [`WebauthnError::UserVerificationRequired`]. The
113//!   default (`Preferred`) reports the flag without rejecting, preserving
114//!   per-caller policy decisions.
115//! - **Multi-device credentials** ([`policy::BackupPolicy`]): the BE/BS
116//!   flags are parsed from signed authenticator data and reported on both
117//!   ceremony results; `BackupPolicy::RequireDeviceBound` rejects syncable
118//!   (BE=1) credentials. See the policy module docs for account-recovery
119//!   implications before allowing synced passkeys on sensitive flows.
120
121#![forbid(unsafe_code)]
122#![deny(missing_docs)]
123
124pub mod aaguid;
125pub mod attestation;
126pub mod challenge;
127pub mod config;
128pub mod credential;
129pub mod crypto;
130pub mod error;
131pub mod policy;
132pub mod protocol;
133
134pub use aaguid::known_aaguid;
135pub use attestation::{
136    verify_attestation, AttestationFormat, AttestationPolicy, AttestationResult, TrustLevel,
137};
138pub use challenge::{check_sign_count, ChallengeStore};
139pub use config::WebauthnConfig;
140pub use credential::{
141    AllowCredential, AuthenticationOptions, AuthenticationResponse, AuthenticationResult,
142    AuthenticatorSelection, ExcludeCredential, PubKeyCredParam, RegistrationOptions,
143    RegistrationResponse, RegistrationResult, RelyingParty, WebauthnCredential, WebauthnUser,
144};
145pub use crypto::{
146    alg_to_name, base64_decode_urlsafe, base64_encode_urlsafe, cbor_bytes, cbor_map_entries,
147    generate_challenge_bytes, parse_cose_key, verify_cose_signature, CosePublicKey, COSE_ALG_EDDSA,
148    COSE_ALG_ES256, COSE_ALG_ES384, COSE_ALG_RS256, COSE_CRV_ED25519, COSE_CRV_P384, COSE_KTY_EC2,
149    COSE_KTY_OKP, COSE_KTY_RSA,
150};
151pub use error::WebauthnError;
152pub use policy::{
153    AttestationConveyance, BackupPolicy, CredentialPolicy, ResidentKeyPolicy,
154    UserVerificationPolicy,
155};
156pub use protocol::{verify_authentication, verify_registration, AuthenticationParams};