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