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};