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