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