├── attestation-ca ├── README.md ├── Cargo.toml └── build.rs ├── authenticator-cli ├── README.md ├── Cargo.toml └── src │ └── main.rs ├── .dockerignore ├── clippy.toml ├── CODEOWNERS ├── webauthn-authenticator-rs ├── src │ ├── tlv │ │ └── mod.rs │ ├── ctap2 │ │ ├── internal.rs │ │ ├── yubikey.rs │ │ ├── commands │ │ │ ├── selection.rs │ │ │ └── reset.rs │ │ ├── solokey.rs │ │ └── ctap21pre.rs │ ├── usb │ │ ├── yubikey.rs │ │ └── solokey.rs │ ├── nfc │ │ └── tlv.rs │ ├── ui │ │ ├── mod.rs │ │ └── cli.rs │ ├── win10 │ │ ├── rp.rs │ │ ├── native.rs │ │ ├── user.rs │ │ ├── clientdata.rs │ │ └── cose.rs │ ├── types.rs │ └── transport │ │ ├── types.rs │ │ └── mod.rs ├── examples │ ├── softtoken.rs │ └── cable_domain.rs ├── build.rs └── README.md ├── tutorial ├── wasm │ ├── build.sh │ └── Cargo.toml ├── server │ ├── axum │ │ ├── build_wasm.sh │ │ ├── README.md │ │ ├── assets │ │ │ ├── wasm │ │ │ │ └── index.html │ │ │ └── js │ │ │ │ ├── index.html │ │ │ │ └── auth.js │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── error.rs │ │ │ ├── startup.rs │ │ │ └── main.rs │ ├── actix_web │ │ ├── src │ │ │ ├── handler │ │ │ │ ├── serve_wasm.rs │ │ │ │ ├── index.rs │ │ │ │ └── mod.rs │ │ │ ├── startup.rs │ │ │ ├── main.rs │ │ │ └── session.rs │ │ └── Cargo.toml │ └── tide │ │ └── Cargo.toml ├── README.md └── Makefile ├── fido-hid-rs ├── src │ ├── linux │ │ ├── wrapper.h │ │ └── wrapper.rs │ ├── error.rs │ ├── lib.rs │ └── traits.rs ├── build.rs ├── README.md └── Cargo.toml ├── device-catalog ├── src │ ├── image.rs │ ├── manufacturer.rs │ ├── data │ │ ├── mod.rs │ │ ├── apple.rs │ │ ├── nitrokey.rs │ │ ├── microsoft.rs │ │ ├── yubico.rs │ │ └── google.rs │ ├── aaguid.rs │ ├── query.rs │ ├── quirks.rs │ ├── certificate_authority.rs │ ├── device.rs │ └── lib.rs └── Cargo.toml ├── webauthn-rs-core ├── src │ ├── constants.rs │ ├── lib.rs │ └── macros.rs ├── build.rs ├── Cargo.toml └── README.md ├── .gitignore ├── fido-key-manager ├── windows │ ├── fido-key-manager.rc │ └── fido-key-manager.exe.manifest ├── build.rs └── Cargo.toml ├── base64urlsafedata ├── README.md ├── Cargo.toml └── src │ └── human.rs ├── cable-tunnel-server ├── common │ ├── src │ │ ├── favicon.ico │ │ └── index.html │ ├── README.md │ └── Cargo.toml ├── frontend │ └── Cargo.toml └── backend │ ├── Cargo.toml │ └── README.md ├── webauthn-rp-proxy ├── README.md ├── tests │ ├── data │ │ ├── register-start.json │ │ ├── authenticate-start.json │ │ ├── register-finish.json │ │ ├── authenticate-finish.json │ │ └── authenticate-finish-fail.json │ └── integration_test.rs └── Cargo.toml ├── pull_request_template.md ├── compat_tester ├── webauthn-rs-demo │ ├── build.rs │ ├── pkg │ │ ├── webauthn_rs_demo_wasm_bg.wasm │ │ ├── snippets │ │ │ └── webauthn_rs_demo_wasm-053dfc2cea337554 │ │ │ │ └── inline0.js │ │ ├── bundle.css │ │ ├── webauthn_rs_demo_wasm_bg.wasm.d.ts │ │ └── webauthn_rs_demo_wasm.d.ts │ ├── templates │ │ ├── signin.css │ │ └── sandbox_uv.html │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── config.rs ├── webauthn-rs-demo-wasm │ ├── build_wasm.sh │ ├── src │ │ ├── lib.rs │ │ ├── error.rs │ │ ├── uv.rs │ │ ├── utils.rs │ │ └── manager.rs │ └── Cargo.toml └── webauthn-rs-demo-shared │ └── Cargo.toml ├── sshkey-attest ├── README.md ├── src │ └── proto.rs └── Cargo.toml ├── CONTRIBUTORS.md ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── an_idea_or_question.md ├── doc_manifest └── check_built_docs.sh ├── fido-mds ├── README.md └── Cargo.toml ├── fido-mds-tool ├── README.md └── Cargo.toml ├── webauthn-rs-proto ├── README.md ├── src │ ├── lib.rs │ ├── wasm.rs │ ├── tests.rs │ └── cose.rs └── Cargo.toml ├── Dockerfile ├── webauthn-rs └── Cargo.toml ├── SECURITY.md ├── CODE_OF_CONDUCT.md ├── Cargo.toml └── README.md /attestation-ca/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /authenticator-cli/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | 3 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | allow-unwrap-in-tests = true 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Firstyear @yaleman @agrinman @micolous 2 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/tlv/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ber; 2 | -------------------------------------------------------------------------------- /tutorial/wasm/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | wasm-pack build --target web 3 | -------------------------------------------------------------------------------- /fido-hid-rs/src/linux/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | -------------------------------------------------------------------------------- /device-catalog/src/image.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub struct Image { 3 | // Something 4 | } 5 | -------------------------------------------------------------------------------- /webauthn-rs-core/src/constants.rs: -------------------------------------------------------------------------------- 1 | // Can this ever change? 2 | pub const CHALLENGE_SIZE_BYTES: usize = 32; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | Cargo.lock 4 | webauthn-rs-demo-wasm/pkg 5 | .DS_Store 6 | local 7 | build.sh 8 | -------------------------------------------------------------------------------- /fido-key-manager/windows/fido-key-manager.rc: -------------------------------------------------------------------------------- 1 | #define RT_MANIFEST 24 2 | 1 RT_MANIFEST "fido-key-manager.exe.manifest" 3 | -------------------------------------------------------------------------------- /device-catalog/src/manufacturer.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Manufacturer { 3 | pub display_name: String, 4 | } 5 | -------------------------------------------------------------------------------- /tutorial/server/axum/build_wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | wasm-pack build ../../wasm --out-dir ../server/axum/assets/wasm --target web 3 | -------------------------------------------------------------------------------- /base64urlsafedata/README.md: -------------------------------------------------------------------------------- 1 | # Base64 Url Safe Serde Wrapper 2 | 3 | A wrapper to help inline deserialisation of base64 datatypes. 4 | -------------------------------------------------------------------------------- /cable-tunnel-server/common/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanidm/webauthn-rs/HEAD/cable-tunnel-server/common/src/favicon.ico -------------------------------------------------------------------------------- /webauthn-rp-proxy/README.md: -------------------------------------------------------------------------------- 1 | This tool allows external applications to consume webauthn-rs via a command line interface by 2 | passing JSON messages. 3 | -------------------------------------------------------------------------------- /device-catalog/src/data/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod android; 2 | pub mod apple; 3 | pub mod google; 4 | pub mod microsoft; 5 | pub mod nitrokey; 6 | pub mod yubico; 7 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | - [ ] cargo test has been run and passes 4 | - [ ] documentation has been updated with relevant examples (if relevant) 5 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=build.rs"); 3 | println!("cargo:rerun-if-changed=templates/index.html"); 4 | } 5 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/pkg/webauthn_rs_demo_wasm_bg.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanidm/webauthn-rs/HEAD/compat_tester/webauthn-rs-demo/pkg/webauthn_rs_demo_wasm_bg.wasm -------------------------------------------------------------------------------- /sshkey-attest/README.md: -------------------------------------------------------------------------------- 1 | # SSH Key FIDO2 Attestation 2 | 3 | This library supports attestation of the openssh `-sk` classes of keys. This attestation is similar 4 | to what is provided in webauthn. 5 | 6 | 7 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/pkg/snippets/webauthn_rs_demo_wasm-053dfc2cea337554/inline0.js: -------------------------------------------------------------------------------- 1 | export async function is_conditional_mediation_available() { 2 | PublicKeyCredential.isConditionalMediationAvailable() 3 | } -------------------------------------------------------------------------------- /device-catalog/src/aaguid.rs: -------------------------------------------------------------------------------- 1 | use crate::certificate_authority::Authority; 2 | use std::rc::Rc; 3 | use uuid::Uuid; 4 | 5 | #[derive(Debug)] 6 | pub struct Aaguid { 7 | pub id: Uuid, 8 | pub ca: Vec>, 9 | } 10 | -------------------------------------------------------------------------------- /tutorial/server/axum/README.md: -------------------------------------------------------------------------------- 1 | # Axum Server 2 | 3 | This demonstrates using Axum as the backend. 4 | 5 | By default, it serves the Javascript front-end. 6 | 7 | If you want to use the WASM frontend instead, change the features in Cargo.toml. 8 | -------------------------------------------------------------------------------- /device-catalog/src/query.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | 3 | #[derive(Debug, PartialEq, Eq)] 4 | pub enum Query { 5 | AaguidEqual(Uuid), 6 | AaguidNotEqual(Uuid), 7 | 8 | And(Box, Box), 9 | Or(Box, Box), 10 | Not(Box), 11 | } 12 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/ctap2/internal.rs: -------------------------------------------------------------------------------- 1 | use crate::{ctap2::GetInfoResponse, transport::Token, ui::UiCallback}; 2 | 3 | pub trait CtapAuthenticatorVersion<'a, T: Token, U: UiCallback> { 4 | const VERSION: &'static str; 5 | fn new_with_info(info: GetInfoResponse, token: T, ui_callback: &'a U) -> Self; 6 | } 7 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo-wasm/build_wasm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | RUSTFLAGS="--cfg=web_sys_unstable_apis" wasm-pack build --target web && \ 3 | cp -r ./pkg/snippets ./pkg/webauthn_rs_demo_wasm.js ./pkg/webauthn_rs_demo_wasm.d.ts ./pkg/webauthn_rs_demo_wasm_bg.wasm.d.ts ./pkg/webauthn_rs_demo_wasm_bg.wasm ../webauthn-rs-demo/pkg/ 4 | 5 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Authors / Maintainers 2 | 3 | * William Brown (Firstyear): wbrown@suse.de 4 | * Alex Grinman (agrinman) 5 | 6 | ## Contributors 7 | 8 | * Eric Mark Martin (ericmarkmartin) 9 | * Ben Wishovich (benwis) 10 | * Niklas Pfister (myOmikron) 11 | * Cynthia Coan (Mythra): cynthia@coan.dev 12 | * Arthur A. Gleckler (arthurgleckler): aag@alum.mit.edu -------------------------------------------------------------------------------- /webauthn-rp-proxy/tests/data/register-start.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude_credentials": ["ARLlbpXWj_8dMYj1afk1vej8dkPYTju2Ruha67c_Rf8BIfXdDK4u6FI6uUu5G3aIzOD8TEmthSzxmQBPysqM_BQ"], 3 | "rp_id": "localhost", 4 | "rp_origin": "http://localhost", 5 | "user_display_name": "Arthur A. Gleckler", 6 | "user_name": "aag", 7 | "uuid": "afcca803-a4fa-4390-a06b-1159b2974381" 8 | } -------------------------------------------------------------------------------- /device-catalog/src/quirks.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::BTreeMap; 3 | use std::collections::BTreeSet; 4 | use uuid::Uuid; 5 | 6 | #[derive(Deserialize, Serialize, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] 7 | pub enum Quirk { 8 | QuirkMcQuirkleton, 9 | } 10 | 11 | pub type Quirks = BTreeMap>; 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### I did this 11 | 12 | ### I expected the following 13 | 14 | ### What actually happened 15 | 16 | ### Version (and git commit) 17 | 18 | ### Operating System / Version 19 | 20 | ### Any other comments 21 | -------------------------------------------------------------------------------- /fido-mds/README.md: -------------------------------------------------------------------------------- 1 | FIDO Metadata Service Parser 2 | ============================ 3 | 4 | The FIDO Alliance organisation publishes a "metadata" blob, similar to a certificate 5 | transparency report, that lists the status of and trust chains of authenticator 6 | devices that have passed their certifications. 7 | 8 | This library allows that metadata to be parsed and consumed in a useful manner. 9 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo-shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn-rs-demo-shared" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | core = ["webauthn-rs-core"] 8 | 9 | [dependencies] 10 | base64urlsafedata.workspace = true 11 | serde.workspace = true 12 | 13 | webauthn-rs-core = { workspace = true, optional = true } 14 | webauthn-rs-proto = { workspace = true } 15 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | // #![deny(warnings)] 2 | #![warn(unused_extern_crates)] 3 | #![recursion_limit = "512"] 4 | use wasm_bindgen::prelude::*; 5 | 6 | mod compat; 7 | mod demo; 8 | mod error; 9 | mod manager; 10 | mod utils; 11 | 12 | #[wasm_bindgen] 13 | pub fn run_app() -> Result<(), JsValue> { 14 | yew::start_app::(); 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /cable-tunnel-server/common/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | webauthn-rs cable-tunnel-server 5 | 6 | 7 |

This is a caBLE tunnel server, but your client did not send a caBLE request.

8 |

webauthn-rs cable-tunnel-server

9 | 10 | 11 | -------------------------------------------------------------------------------- /fido-key-manager/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); 3 | 4 | if !cfg!(feature = "disable_windows_manifest") && target_os == "windows" { 5 | embed_resource::compile("windows/fido-key-manager.rc", embed_resource::NONE) 6 | .manifest_required() 7 | .expect("Unable to embed windows/fido-key-manager.rc"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tutorial/server/actix_web/src/handler/serve_wasm.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use actix_files::NamedFile; 4 | use actix_web::HttpRequest; 5 | 6 | pub const WASM_DIR: &str = "../../wasm/pkg"; 7 | 8 | pub(crate) async fn serve_wasm(req: HttpRequest) -> actix_web::Result { 9 | let fp = req.match_info().query("filename"); 10 | let path = Path::new(WASM_DIR).join(fp); 11 | Ok(NamedFile::open(path)?) 12 | } 13 | -------------------------------------------------------------------------------- /fido-mds-tool/README.md: -------------------------------------------------------------------------------- 1 | FIDO Metadata Service Parser 2 | ============================ 3 | 4 | The FIDO Alliance organisation publishes a "metadata" blob, similar to a certificate 5 | transparency report, that lists the status of and trust chains of authenticator 6 | devices that have passed their certifications. 7 | 8 | This tool allows querying that metadata blob for a human to inspect the properties 9 | of devices and their certification status. 10 | -------------------------------------------------------------------------------- /device-catalog/src/certificate_authority.rs: -------------------------------------------------------------------------------- 1 | // use base64urlsafedata::Base64UrlSafeData; 2 | 3 | use crate::prelude::*; 4 | 5 | use std::fmt; 6 | 7 | pub struct Authority { 8 | pub ca: x509::X509, 9 | } 10 | 11 | impl fmt::Debug for Authority { 12 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 13 | f.debug_struct("Authority") 14 | .field("subject", &self.ca.subject_name()) 15 | .finish() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cable-tunnel-server/common/README.md: -------------------------------------------------------------------------------- 1 | # cable-tunnel-server-common 2 | 3 | Common components for webauthn-rs' [caBLE tunnel server][] [`backend`][] and 4 | [`frontend`][]. 5 | 6 | **Important:** this library is an internal implementation detail of webauthn-rs' 7 | [caBLE tunnel server][], and has no guarantees of API stability whatsoever. It 8 | is **not** intended for use outside of that context. 9 | 10 | [`backend`]: ../backend/ 11 | [caBLE tunnel server]: ../README.md 12 | [`frontend`]: ../frontend/ 13 | -------------------------------------------------------------------------------- /tutorial/README.md: -------------------------------------------------------------------------------- 1 | # Webauthn-rs Tutorial 2 | 3 | This is a tutorial / example of how to use the webauthn-rs library in a minimal capacity. 4 | 5 | There are two halves to this tutorial - a backend server (`site`) and the front end 6 | that contains a single-page html/wasm application (`wasm`). 7 | 8 | ## Running These 9 | 10 | Try `make` for options, typically `make ` will work. 11 | 12 | ## Troubleshooting 13 | 14 | If you can't get registration to work or other errors are thrown, try clearing the cookies first. 15 | -------------------------------------------------------------------------------- /tutorial/server/tide/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tide_tutorial" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["William Brown "] 6 | license = "MPL-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | webauthn-rs = { workspace = true, features = [ 12 | "danger-allow-state-serialisation", 13 | ] } 14 | tide = "0.16" 15 | async-std = { workspace = true } 16 | tracing = { workspace = true } 17 | tracing-subscriber = { workspace = true } 18 | serde = { workspace = true } 19 | rand = { workspace = true } 20 | anyhow = { workspace = true } 21 | -------------------------------------------------------------------------------- /tutorial/server/axum/assets/wasm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebAuthn-rs Tutorial 7 | 8 | 16 | 17 | 18 |

Welcome to the WebAuthn Server!

19 | 20 | 21 | -------------------------------------------------------------------------------- /webauthn-rs-proto/README.md: -------------------------------------------------------------------------------- 1 | Webauthn Rust Proto Bindings 2 | ============================ 3 | 4 | Webauthn is a modern approach to hardware based authentication, consisting of 5 | a user with an authenticator device, a browser or client that interacts with the 6 | device, and a server that is able to generate challenges and verify the 7 | authenticator's validity. 8 | 9 | This crate contains the definitions used by Webauthn's server (Relying party, RP) 10 | and the browser javascript (wasm) client components. In most cases you should 11 | not need to interact with this library directly, opting instead to use the 12 | implementations from [Webauthn-RS](https://docs.rs/webauthn-rs/) directly. 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/an_idea_or_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: An idea or question 3 | about: Suggest an idea or change for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear description of what the problem is. Ex. I'm confused by, or would like to know how to... 12 | 13 | **Describe the solution you'd like** 14 | A description of what you'd expect to happen. 15 | 16 | **Describe alternatives you've considered** 17 | Are there any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/templates/signin.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | body { 7 | display: flex; 8 | background-color: #f5f5f5; 9 | } 10 | 11 | .form-description { 12 | padding-top: 40px; 13 | padding-bottom: 40px; 14 | width: 100%; 15 | max-width: 700px; 16 | margin: 0px auto; 17 | } 18 | 19 | .vert-center { 20 | height: 80%; 21 | display: flex; 22 | align-items: center; 23 | } 24 | 25 | .form-signin { 26 | width: 100%; 27 | max-width: 520px; 28 | padding: 20px; 29 | margin: auto; 30 | } 31 | 32 | .form-signin .form-floating:focus-within { 33 | z-index: 2; 34 | } 35 | 36 | .explain { 37 | padding: 20px; 38 | background-color: #ffffff; 39 | } 40 | -------------------------------------------------------------------------------- /tutorial/Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT: help 2 | .PHONY: help 3 | help: 4 | @grep -E -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 5 | 6 | .PHONY: wasm 7 | wasm: ## build the WASM parts 8 | wasm: 9 | cd wasm && ./build.sh 10 | 11 | .PHONY: actix 12 | actix: ## Build the WASM parts and run the actix server 13 | actix: wasm 14 | cd server/actix_web && cargo run 15 | 16 | .PHONY: axum 17 | axum: ## Build and run the axum server, builds the WASM parts regardless. 18 | axum: 19 | cd server/axum && ./build_wasm.sh 20 | cd server/axum && cargo run 21 | 22 | .PHONY: tide 23 | tide: ## Build and run the tide server 24 | tide: wasm 25 | cd server/tide && cargo run 26 | -------------------------------------------------------------------------------- /.github/doc_manifest: -------------------------------------------------------------------------------- 1 | # Manifest for check_built_docs.sh 2 | # Paths are relative to target/doc/ 3 | 4 | authenticator_cli/index.html 5 | base64urlsafedata/index.html 6 | fido_mds/index.html 7 | fido_mds_tool/index.html 8 | 9 | webauthn_authenticator_rs/index.html 10 | webauthn_authenticator_rs/bluetooth/index.html 11 | webauthn_authenticator_rs/cable/index.html 12 | webauthn_authenticator_rs/nfc/index.html 13 | webauthn_authenticator_rs/usb/index.html 14 | webauthn_authenticator_rs/u2fhid/index.html 15 | webauthn_authenticator_rs/win10/index.html 16 | 17 | webauthn_rs/index.html 18 | webauthn_rs/interface/index.html 19 | 20 | webauthn_rs_core/index.html 21 | webauthn_rs_demo/index.html 22 | webauthn_rs_proto/index.html 23 | -------------------------------------------------------------------------------- /authenticator-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "authenticator-cli" 3 | readme = "README.md" 4 | description = "Webauthn Authenticator Management Tool" 5 | 6 | version = { workspace = true } 7 | authors = { workspace = true } 8 | rust-version = { workspace = true } 9 | edition = { workspace = true } 10 | license = { workspace = true } 11 | homepage = { workspace = true } 12 | repository = { workspace = true } 13 | 14 | [dependencies] 15 | 16 | authenticator = { version = "0.3.2-dev.1", default-features = false, features = [ 17 | "crypto_openssl", 18 | ], package = "authenticator-ctap2-2021" } 19 | clap = { workspace = true } 20 | 21 | tracing = { workspace = true } 22 | tracing-subscriber = { workspace = true } 23 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo-wasm/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | use wasm_bindgen::JsValue; 4 | 5 | #[derive(Debug, Clone, PartialEq)] 6 | pub struct FetchError { 7 | err: JsValue, 8 | } 9 | 10 | impl fmt::Display for FetchError { 11 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 12 | fmt::Debug::fmt(&self.err, f) 13 | } 14 | } 15 | 16 | impl Error for FetchError {} 17 | 18 | impl From for FetchError { 19 | fn from(value: JsValue) -> Self { 20 | Self { err: value } 21 | } 22 | } 23 | 24 | impl FetchError { 25 | pub fn as_string(&self) -> String { 26 | self.err.as_string().unwrap_or_else(|| "null".to_string()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /webauthn-rp-proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn-rp-proxy" 3 | authors = ["Arthur A. Gleckler "] 4 | readme = "README.md" 5 | description = "Webauthn RP CLI Proxy" 6 | 7 | version = { workspace = true } 8 | rust-version = { workspace = true } 9 | edition = { workspace = true } 10 | license = { workspace = true } 11 | homepage = { workspace = true } 12 | repository = { workspace = true } 13 | 14 | [dependencies] 15 | anyhow = { workspace = true } 16 | clap = { workspace = true } 17 | serde = { workspace = true } 18 | serde_json = { workspace = true } 19 | uuid = { workspace = true, features = ["serde"] } 20 | webauthn-rs = { workspace = true, features = [ 21 | "danger-allow-state-serialisation", 22 | ] } 23 | -------------------------------------------------------------------------------- /attestation-ca/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn-attestation-ca" 3 | readme = "README.md" 4 | description = "Webauthn Attestation CA Descriptions" 5 | 6 | version = { workspace = true } 7 | authors = { workspace = true } 8 | rust-version = { workspace = true } 9 | edition = { workspace = true } 10 | license = { workspace = true } 11 | homepage = { workspace = true } 12 | repository = { workspace = true } 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | base64urlsafedata.workspace = true 18 | serde.workspace = true 19 | tracing.workspace = true 20 | openssl.workspace = true 21 | openssl-sys.workspace = true 22 | uuid = { workspace = true, features = ["serde"] } 23 | -------------------------------------------------------------------------------- /webauthn-rs-proto/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! JSON Protocol Structs and representations for communication with authenticators 2 | //! and clients. 3 | 4 | #![cfg_attr(docsrs, feature(doc_cfg))] 5 | #![deny(warnings)] 6 | #![warn(unused_extern_crates)] 7 | #![warn(missing_docs)] 8 | 9 | pub mod attest; 10 | pub mod auth; 11 | pub mod cose; 12 | pub mod extensions; 13 | pub mod options; 14 | 15 | #[cfg(feature = "wasm")] 16 | pub mod wasm; 17 | 18 | pub use attest::*; 19 | pub use auth::*; 20 | pub use cose::*; 21 | pub use extensions::*; 22 | pub use options::*; 23 | 24 | #[cfg(feature = "wasm")] 25 | pub use wasm::*; 26 | 27 | #[cfg(feature = "wasm")] 28 | use base64::engine::general_purpose::URL_SAFE_NO_PAD as BASE64_ENGINE; 29 | 30 | #[cfg(test)] 31 | mod tests; 32 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/pkg/bundle.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | body { 7 | display: flex; 8 | background-color: #f5f5f5; 9 | } 10 | 11 | .form-description { 12 | padding-top: 25px; 13 | padding-bottom: 20px; 14 | padding-left: 10px; 15 | padding-right: 10px; 16 | width: 100%; 17 | max-width: 700px; 18 | margin: 0px auto; 19 | } 20 | 21 | .vert-center { 22 | height: 80%; 23 | display: flex; 24 | align-items: center; 25 | } 26 | 27 | .form-signin { 28 | width: 100%; 29 | max-width: 520px; 30 | padding: 10px; 31 | margin: auto; 32 | } 33 | 34 | .form-signin .form-floating:focus-within { 35 | z-index: 2; 36 | } 37 | 38 | .explain { 39 | padding: 20px; 40 | background-color: #ffffff; 41 | } 42 | -------------------------------------------------------------------------------- /device-catalog/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn-rs-device-catalog" 3 | version = "0.5.0-20230418" 4 | authors = ["William Brown "] 5 | edition = "2021" 6 | description = "Webauthn RS Device Catalog" 7 | repository = "https://github.com/kanidm/webauthn-rs" 8 | readme = "README.md" 9 | keywords = ["webauthn", "authentication"] 10 | categories = ["authentication", "web-programming"] 11 | license = "MPL-2.0" 12 | 13 | [dependencies] 14 | webauthn-attestation-ca.workspace = true 15 | base64urlsafedata = { workspace = true } 16 | openssl = { workspace = true } 17 | peg = { workspace = true } 18 | serde = { workspace = true, features = ["derive"] } 19 | tracing.workspace = true 20 | uuid = { workspace = true, features = ["serde"] } 21 | -------------------------------------------------------------------------------- /base64urlsafedata/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "base64urlsafedata" 3 | description = "Base 64 Url Safe wrapper for Serde" 4 | keywords = ["base64", "serde"] 5 | categories = ["web-programming"] 6 | readme = "README.md" 7 | 8 | version = { workspace = true } 9 | authors = { workspace = true } 10 | rust-version = { workspace = true } 11 | edition = { workspace = true } 12 | license = { workspace = true } 13 | homepage = { workspace = true } 14 | repository = { workspace = true } 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | serde.workspace = true 20 | base64.workspace = true 21 | pastey = "0.1.1" 22 | 23 | [dev-dependencies] 24 | serde_cbor_2.workspace = true 25 | serde_json.workspace = true 26 | -------------------------------------------------------------------------------- /fido-key-manager/windows/fido-key-manager.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /fido-hid-rs/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | pub type Result = std::result::Result; 4 | 5 | #[derive(Debug, Error, PartialEq, Eq, PartialOrd, Ord)] 6 | pub enum HidError { 7 | #[error("I/O error communicating with device: {0}")] 8 | IoError(String), 9 | #[error("internal error, likely library bug")] 10 | Internal, 11 | #[error("attempted to communicate with a closed device")] 12 | Closed, 13 | #[error("device sent an unexpected message length")] 14 | InvalidMessageLength, 15 | #[error("could not send data to device")] 16 | SendError, 17 | #[error("permission denied")] 18 | PermissionDenied, 19 | } 20 | 21 | impl From for HidError { 22 | fn from(v: std::io::Error) -> Self { 23 | Self::IoError(v.to_string()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /device-catalog/src/device.rs: -------------------------------------------------------------------------------- 1 | use crate::aaguid::Aaguid; 2 | use crate::image::Image; 3 | use crate::manufacturer::Manufacturer; 4 | use crate::quirks::Quirk; 5 | use std::collections::BTreeSet; 6 | 7 | use crate::query::Query; 8 | use std::rc::Rc; 9 | 10 | #[derive(Debug)] 11 | pub struct Device { 12 | pub aaguid: Rc, 13 | pub images: Vec>, 14 | pub quirks: BTreeSet, 15 | // This is used to derive some minimum properties. 16 | pub skus: Vec>, 17 | pub mfr: Rc, 18 | // include the fido version of the mds here. 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct Sku { 23 | pub display_name: String, 24 | pub version: String, 25 | } 26 | 27 | impl Device { 28 | pub(crate) fn query_match(&self, _q: &Query) -> bool { 29 | todo!(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /fido-mds-tool/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fido-mds-tool" 3 | description = "Fido Metadata Service parsing tool" 4 | readme = "README.md" 5 | keywords = ["webauthn", "authentication"] 6 | categories = ["authentication", "web-programming"] 7 | 8 | version = { workspace = true } 9 | authors = { workspace = true } 10 | rust-version = { workspace = true } 11 | edition = { workspace = true } 12 | license = { workspace = true } 13 | homepage = { workspace = true } 14 | repository = { workspace = true } 15 | 16 | [dependencies] 17 | fido-mds.workspace = true 18 | clap.workspace = true 19 | tracing.workspace = true 20 | tracing-subscriber.workspace = true 21 | uuid.workspace = true 22 | 23 | url = { version = "2", features = ["serde"] } 24 | 25 | reqwest = { workspace = true, features = ["blocking"] } 26 | 27 | serde_json.workspace = true 28 | -------------------------------------------------------------------------------- /webauthn-rs-proto/src/wasm.rs: -------------------------------------------------------------------------------- 1 | //! Extensions for wasm types that are not part of web-sys 2 | use wasm_bindgen::prelude::*; 3 | 4 | #[wasm_bindgen] 5 | extern "C" { 6 | /// Public Key Credential Extension 7 | pub type PublicKeyCredentialExt; 8 | 9 | #[wasm_bindgen(static_method_of = PublicKeyCredentialExt, js_class = "PublicKeyCredential", js_name = isConditionalMediationAvailable, catch)] 10 | /// Is Conditional Mediation Available 11 | pub fn is_conditional_mediation_available() -> Result<::js_sys::Promise, JsValue>; 12 | 13 | #[wasm_bindgen(static_method_of = PublicKeyCredentialExt, js_class = "PublicKeyCredential", js_name = isExternalCTAP2SecurityKeySupported, catch)] 14 | /// Is External Ctap2 SecurityKey Supported 15 | pub fn is_external_ctap2_securitykey_supported() -> Result<::js_sys::Promise, JsValue>; 16 | } 17 | -------------------------------------------------------------------------------- /tutorial/wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm_tutorial" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["William Brown "] 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [dependencies] 11 | webauthn-rs-proto = { workspace = true, features = ["wasm"] } 12 | 13 | wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } 14 | serde-wasm-bindgen = "0.4" 15 | wasm-bindgen-futures = { version = "0.4" } 16 | yew = "0.19" 17 | js-sys = "0.3" 18 | gloo = "0.6" 19 | url.workspace = true 20 | serde_json.workspace = true 21 | 22 | [dependencies.web-sys] 23 | version = "0.3" 24 | features = [ 25 | "CredentialCreationOptions", 26 | "CredentialRequestOptions", 27 | "CredentialsContainer", 28 | "Navigator", 29 | "PublicKeyCredential", 30 | "PublicKeyCredentialCreationOptions", 31 | ] 32 | -------------------------------------------------------------------------------- /sshkey-attest/src/proto.rs: -------------------------------------------------------------------------------- 1 | //! Serialisable formats of attested ssh keys 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use webauthn_rs_core::proto::{AttestationFormat, ParsedAttestation, RegisteredExtensions}; 5 | 6 | pub use sshkeys::PublicKey; 7 | 8 | /// An attested public key. This contains the ssh public key as well as the 9 | /// attestation metadata. 10 | #[derive(Clone, Debug, Serialize, Deserialize)] 11 | pub struct AttestedPublicKey { 12 | /// The ssh public key 13 | pub pubkey: PublicKey, 14 | /// The set of extensions that were verified at registration, that can 15 | /// be used in future authentication attempts 16 | pub extensions: RegisteredExtensions, 17 | /// The parser attestation data 18 | pub attestation: ParsedAttestation, 19 | /// The format of the attestation presented by the device. 20 | pub attestation_format: AttestationFormat, 21 | } 22 | -------------------------------------------------------------------------------- /tutorial/server/actix_web/src/handler/index.rs: -------------------------------------------------------------------------------- 1 | use actix_web::HttpResponse; 2 | 3 | pub const WASM_JS_FILE: &str = "wasm_tutorial.js"; 4 | pub const WASM_BG_FILE: &str = "wasm_tutorial_bg.wasm"; 5 | 6 | pub(crate) async fn index() -> HttpResponse { 7 | HttpResponse::Ok().body(format!( 8 | r#" 9 | 10 | 11 | 12 | 13 | 14 | WebAuthn-rs Tutorial 15 | 16 | 24 | 25 | 26 |

Welcome to the WebAuthn Server!

27 | 28 | "#, 29 | )) 30 | } 31 | -------------------------------------------------------------------------------- /tutorial/server/axum/assets/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebAuthn-rs Tutorial 7 | 12 | 13 | 14 | 15 |

Welcome to the WebAuthn Server!

16 | 17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 |

25 |
26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /cable-tunnel-server/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cable-tunnel-server-common" 3 | version = "0.1.0" 4 | authors = ["Michael Farrell "] 5 | description = "Common components for webauthn-rs' caBLE tunnel server backend and frontend" 6 | edition = "2021" 7 | keywords = ["cable", "hybrid", "fido", "webauthn"] 8 | license = "MPL-2.0" 9 | readme = "README.md" 10 | repository = "https://github.com/kanidm/webauthn-rs/" 11 | rust-version = "1.70.0" 12 | 13 | [dependencies] 14 | clap.workspace = true 15 | hex.workspace = true 16 | http-body.workspace = true 17 | http-body-util.workspace = true 18 | hyper = { workspace = true, features = ["server"] } 19 | hyper-util.workspace = true 20 | thiserror.workspace = true 21 | tokio.workspace = true 22 | tokio-native-tls.workspace = true 23 | tokio-tungstenite.workspace = true 24 | tracing.workspace = true 25 | tracing-subscriber.workspace = true 26 | tungstenite.workspace = true 27 | -------------------------------------------------------------------------------- /fido-mds/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fido-mds" 3 | description = "Fido Metadata Service parser" 4 | readme = "README.md" 5 | keywords = ["webauthn", "authentication"] 6 | categories = ["authentication", "web-programming"] 7 | 8 | version = { workspace = true } 9 | authors = { workspace = true } 10 | rust-version = { workspace = true } 11 | edition = { workspace = true } 12 | license = { workspace = true } 13 | homepage = { workspace = true } 14 | repository = { workspace = true } 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | webauthn-attestation-ca.workspace = true 20 | base64.workspace = true 21 | crc32c = "^0.6.8" 22 | compact_jwt.workspace = true 23 | openssl.workspace = true 24 | peg = "0.8.5" 25 | serde.workspace = true 26 | serde_json.workspace = true 27 | tracing.workspace = true 28 | uuid = { workspace = true, features = ["v4", "serde"] } 29 | -------------------------------------------------------------------------------- /attestation-ca/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | const OPENSSL_DOC: &str = "https://github.com/kanidm/webauthn-rs/blob/master/OpenSSL.md"; 4 | 5 | fn main() { 6 | println!(); 7 | if let Ok(v) = env::var("DEP_OPENSSL_VERSION_NUMBER") { 8 | let version = 9 | u64::from_str_radix(&v, 16).expect("Failed to parse OpenSSL version in build.rs"); 10 | #[allow(clippy::unusual_byte_groupings)] 11 | if version >= 0x3_00_00_00_0 { 12 | return; 13 | } else { 14 | println!( 15 | r#" 16 | Your version of OpenSSL is out of date, and not supported by this library. 17 | 18 | Please upgrade to OpenSSL v3.0.0 or later. 19 | 20 | More info: {OPENSSL_DOC} 21 | OpenSSL version string: {version} 22 | "# 23 | ); 24 | panic!("The installed version of OpenSSL is unusable."); 25 | } 26 | } 27 | 28 | panic!("No version of OpenSSL is found."); 29 | } 30 | -------------------------------------------------------------------------------- /cable-tunnel-server/frontend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cable-tunnel-server-frontend" 3 | version = "0.1.0" 4 | authors = ["Michael Farrell "] 5 | categories = ["authentication"] 6 | description = "webauthn-rs caBLE tunnel server frontend" 7 | edition = "2021" 8 | keywords = ["cable", "hybrid", "fido", "webauthn"] 9 | license = "MPL-2.0" 10 | # readme = "README.md" 11 | repository = "https://github.com/kanidm/webauthn-rs/" 12 | rust-version = "1.70.0" 13 | 14 | [dependencies] 15 | cable-tunnel-server-common.workspace = true 16 | 17 | clap.workspace = true 18 | hex.workspace = true 19 | http-body-util.workspace = true 20 | hyper = { workspace = true, features = ["client", "server"] } 21 | hyper-util.workspace = true 22 | tokio.workspace = true 23 | tokio-native-tls.workspace = true 24 | tokio-tungstenite.workspace = true 25 | tracing.workspace = true 26 | tracing-subscriber.workspace = true 27 | tungstenite.workspace = true 28 | -------------------------------------------------------------------------------- /tutorial/server/axum/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "axum_tutorial" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = [ 6 | "William Brown , Ben Wishovich ", 7 | ] 8 | license = "MPL-2.0" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | tracing.workspace = true 14 | tracing-subscriber.workspace = true 15 | serde.workspace = true 16 | webauthn-rs = { workspace = true, features = [ 17 | "danger-allow-state-serialisation", 18 | ] } 19 | axum = { version = "0.7" } 20 | tokio = { workspace = true, features = ["full"] } 21 | uuid = { workspace = true, features = ["v4"] } 22 | url.workspace = true 23 | thiserror.workspace = true 24 | tower = "0.5" 25 | tower-http = { version = "0.6", features = ["fs"] } 26 | tower-sessions = "0.13" 27 | 28 | [features] 29 | default = ["javascript"] 30 | wasm = [] 31 | javascript = [] 32 | -------------------------------------------------------------------------------- /webauthn-rs-core/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | const OPENSSL_DOC: &str = "https://github.com/kanidm/webauthn-rs/blob/master/OpenSSL.md"; 4 | 5 | fn main() { 6 | println!(); 7 | if let Ok(v) = env::var("DEP_OPENSSL_VERSION_NUMBER") { 8 | let version = 9 | u64::from_str_radix(&v, 16).expect("Failed to parse OpenSSL version in build.rs"); 10 | 11 | #[allow(clippy::unusual_byte_groupings)] 12 | if version >= 0x3_00_00_00_0 { 13 | return; 14 | } else { 15 | println!( 16 | r#" 17 | Your version of OpenSSL is out of date, and not supported by this library. 18 | 19 | Please upgrade to OpenSSL v3.0.0 or later. 20 | 21 | More info: {OPENSSL_DOC} 22 | OpenSSL version string: {version} 23 | "# 24 | ); 25 | panic!("The installed version of OpenSSL is unusable."); 26 | } 27 | } 28 | 29 | panic!("No version of OpenSSL is found."); 30 | } 31 | -------------------------------------------------------------------------------- /fido-hid-rs/src/linux/wrapper.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/linux_wrapper.rs")); 6 | 7 | use nix::ioctl_read; 8 | use num_derive::FromPrimitive; 9 | 10 | // The userspace API is lies: https://bugzilla.kernel.org/show_bug.cgi?id=217463 11 | ioctl_read!(hid_ioc_rd_desc_size, b'H', 0x01, u32); 12 | ioctl_read!(hid_ioc_rd_desc, b'H', 0x02, hidraw_report_descriptor); 13 | ioctl_read!(hid_ioc_raw_info, b'H', 0x03, hidraw_devinfo); 14 | 15 | impl hidraw_report_descriptor { 16 | pub fn get_value(&self) -> &[u8] { 17 | &self.value[..HID_MAX_DESCRIPTOR_SIZE.min(self.size.max(0)) as usize] 18 | } 19 | } 20 | 21 | /// Linux input bus type. 22 | #[derive(Debug, FromPrimitive, PartialEq, Eq, PartialOrd, Ord)] 23 | #[repr(u32)] 24 | pub enum BusType { 25 | Usb = BUS_USB, 26 | Bluetooth = BUS_BLUETOOTH, 27 | Virtual = BUS_VIRTUAL, 28 | } 29 | -------------------------------------------------------------------------------- /cable-tunnel-server/backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cable-tunnel-server-backend" 3 | version = "0.1.0" 4 | authors = ["Michael Farrell "] 5 | categories = ["authentication"] 6 | description = "webauthn-rs caBLE tunnel server backend" 7 | edition = "2021" 8 | keywords = ["cable", "hybrid", "fido", "webauthn"] 9 | license = "MPL-2.0" 10 | readme = "README.md" 11 | repository = "https://github.com/kanidm/webauthn-rs/" 12 | rust-version = "1.70.0" 13 | 14 | [dependencies] 15 | cable-tunnel-server-common.workspace = true 16 | 17 | clap.workspace = true 18 | futures.workspace = true 19 | hex.workspace = true 20 | http-body.workspace = true 21 | http-body-util.workspace = true 22 | hyper = { workspace = true, features = ["server"] } 23 | hyper-util.workspace = true 24 | thiserror.workspace = true 25 | tokio.workspace = true 26 | tokio-native-tls.workspace = true 27 | tokio-tungstenite.workspace = true 28 | tracing.workspace = true 29 | tracing-subscriber.workspace = true 30 | tungstenite.workspace = true 31 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/pkg/webauthn_rs_demo_wasm_bg.wasm.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export const memory: WebAssembly.Memory; 4 | export function run_app(a: number): void; 5 | export function __wbindgen_malloc(a: number, b: number): number; 6 | export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; 7 | export const __wbindgen_export_2: WebAssembly.Table; 8 | export function wasm_bindgen__convert__closures__invoke1_mut_ref__h0f42758389f9ef59(a: number, b: number, c: number): void; 9 | export function wasm_bindgen__convert__closures__invoke1__h3626c9e41e52d750(a: number, b: number, c: number): void; 10 | export function _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h458d161319ed8eb6(a: number, b: number, c: number): void; 11 | export function __wbindgen_add_to_stack_pointer(a: number): number; 12 | export function __wbindgen_exn_store(a: number): void; 13 | export function __wbindgen_free(a: number, b: number, c: number): void; 14 | -------------------------------------------------------------------------------- /sshkey-attest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sshkey-attest" 3 | description = "FIDO SK SSH Key Attestation" 4 | readme = "README.md" 5 | keywords = ["ssh", "openssh", "parser", "attestation", "fido"] 6 | categories = [ 7 | "authentication", 8 | "cryptography", 9 | "parser-implementations", 10 | "encoding", 11 | ] 12 | 13 | version = { workspace = true } 14 | authors = { workspace = true } 15 | rust-version = { workspace = true } 16 | edition = { workspace = true } 17 | license = { workspace = true } 18 | homepage = { workspace = true } 19 | repository = { workspace = true } 20 | 21 | [dependencies] 22 | base64.workspace = true 23 | base64urlsafedata.workspace = true 24 | nom.workspace = true 25 | openssl.workspace = true 26 | serde.workspace = true 27 | serde_cbor_2.workspace = true 28 | sshkeys = { version = "0.3.4", features = ["serde"] } 29 | tracing.workspace = true 30 | uuid = { workspace = true, features = ["serde"] } 31 | webauthn-rs-core.workspace = true 32 | 33 | [dev-dependencies] 34 | tracing-subscriber.workspace = true 35 | webauthn-rs-device-catalog.workspace = true 36 | -------------------------------------------------------------------------------- /fido-hid-rs/build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::PathBuf}; 2 | 3 | const LINUX_WRAPPER_H: &str = "src/linux/wrapper.h"; 4 | 5 | fn linux_headers() { 6 | println!("cargo:rerun-if-changed={LINUX_WRAPPER_H}"); 7 | let bindings = bindgen::builder() 8 | .header(LINUX_WRAPPER_H) 9 | .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) 10 | .derive_debug(false) 11 | .derive_default(true) 12 | .allowlist_type("hidraw_report_descriptor") 13 | .allowlist_type("hidraw_devinfo") 14 | .allowlist_var("HID_MAX_DESCRIPTOR_SIZE") 15 | .allowlist_var("BUS_(USB|BLUETOOTH|VIRTUAL)") 16 | .generate() 17 | .expect("unable to generate bindings"); 18 | 19 | let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); 20 | bindings 21 | .write_to_file(out_path.join("linux_wrapper.rs")) 22 | .expect("Couldn't write bindings!"); 23 | } 24 | 25 | fn main() { 26 | let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); 27 | 28 | if target_os == "linux" { 29 | linux_headers(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/usb/yubikey.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | #[cfg(all(feature = "usb", feature = "vendor-yubikey"))] 4 | use crate::transport::yubikey::CMD_GET_CONFIG; 5 | 6 | use crate::{ 7 | prelude::WebauthnCError, 8 | transport::{ 9 | types::{U2FError, U2FHID_ERROR}, 10 | yubikey::{YubiKeyConfig, YubiKeyToken}, 11 | }, 12 | usb::{framing::U2FHIDFrame, USBToken}, 13 | }; 14 | 15 | #[async_trait] 16 | impl YubiKeyToken for USBToken { 17 | async fn get_yubikey_config(&mut self) -> Result { 18 | let cmd = U2FHIDFrame { 19 | cid: self.cid, 20 | cmd: CMD_GET_CONFIG, 21 | len: 0, 22 | data: vec![], 23 | }; 24 | self.send_one(&cmd).await?; 25 | 26 | let r = self.recv_one().await?; 27 | match r.cmd { 28 | CMD_GET_CONFIG => YubiKeyConfig::from_bytes(r.data.as_slice()), 29 | U2FHID_ERROR => Err(U2FError::from(r.data.as_slice()).into()), 30 | _ => Err(WebauthnCError::UnexpectedState), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/examples/softtoken.rs: -------------------------------------------------------------------------------- 1 | extern crate tracing; 2 | 3 | use std::fs::OpenOptions; 4 | 5 | use clap::{Args, Parser, Subcommand}; 6 | use webauthn_authenticator_rs::softtoken::{SoftToken, SoftTokenFile}; 7 | 8 | #[derive(Debug, clap::Parser)] 9 | #[clap(about = "SoftToken management tool")] 10 | pub struct CliParser { 11 | #[clap(subcommand)] 12 | pub commands: Opt, 13 | } 14 | 15 | #[derive(Debug, Subcommand)] 16 | pub enum Opt { 17 | Create(CreateArgs), 18 | } 19 | 20 | #[derive(Debug, Args)] 21 | pub struct CreateArgs { 22 | #[clap()] 23 | pub filename: String, 24 | } 25 | 26 | fn main() { 27 | use Opt::*; 28 | 29 | let opt = CliParser::parse(); 30 | tracing_subscriber::fmt::init(); 31 | match opt.commands { 32 | Create(args) => { 33 | let (token, _) = SoftToken::new(false).unwrap(); 34 | let f = OpenOptions::new() 35 | .write(true) 36 | .create_new(true) 37 | .open(args.filename) 38 | .unwrap(); 39 | let authenticator = SoftTokenFile::new(token, f); 40 | drop(authenticator); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /device-catalog/src/data/apple.rs: -------------------------------------------------------------------------------- 1 | // Apple makes a webauthn root certificate public at 2 | // https://www.apple.com/certificateauthority/private/. 3 | // The certificate data itself (as linked in the cert listing linked above) can be found at 4 | // https://www.apple.com/certificateauthority/Apple_WebAuthn_Root_CA.pem. 5 | pub const APPLE_WEBAUTHN_ROOT_CA_PEM: &[u8] = b"-----BEGIN CERTIFICATE----- 6 | MIICEjCCAZmgAwIBAgIQaB0BbHo84wIlpQGUKEdXcTAKBggqhkjOPQQDAzBLMR8w 7 | HQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJ 8 | bmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MjEzMloXDTQ1MDMx 9 | NTAwMDAwMFowSzEfMB0GA1UEAwwWQXBwbGUgV2ViQXV0aG4gUm9vdCBDQTETMBEG 10 | A1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTB2MBAGByqGSM49 11 | AgEGBSuBBAAiA2IABCJCQ2pTVhzjl4Wo6IhHtMSAzO2cv+H9DQKev3//fG59G11k 12 | xu9eI0/7o6V5uShBpe1u6l6mS19S1FEh6yGljnZAJ+2GNP1mi/YK2kSXIuTHjxA/ 13 | pcoRf7XkOtO4o1qlcaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUJtdk 14 | 2cV4wlpn0afeaxLQG2PxxtcwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA 15 | MGQCMFrZ+9DsJ1PW9hfNdBywZDsWDbWFp28it1d/5w2RPkRX3Bbn/UbDTNLx7Jr3 16 | jAGGiQIwHFj+dJZYUJR786osByBelJYsVZd2GbHQu209b5RCmGQ21gpSAk9QZW4B 17 | 1bWeT0vT 18 | -----END CERTIFICATE-----"; 19 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/ctap2/yubikey.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | 3 | use crate::{ 4 | prelude::WebauthnCError, 5 | transport::{ 6 | yubikey::{YubiKeyConfig, YubiKeyToken}, 7 | Token, 8 | }, 9 | ui::UiCallback, 10 | }; 11 | 12 | use super::Ctap20Authenticator; 13 | 14 | /// YubiKey vendor-specific commands. 15 | /// 16 | /// ## Warning 17 | /// 18 | /// These commands currently operate on *any* [`Ctap20Authenticator`][], and do 19 | /// not filter to just YubiKey devices. Due to the nature of CTAP 20 | /// vendor-specific commands, this may cause unexpected or undesirable behaviour 21 | /// on other vendors' keys. 22 | /// 23 | /// Protocol notes are in [`crate::transport::yubikey`]. 24 | #[async_trait] 25 | pub trait YubiKeyAuthenticator { 26 | async fn get_yubikey_config(&mut self) -> Result; 27 | } 28 | 29 | #[async_trait] 30 | impl<'a, T: Token + YubiKeyToken, U: UiCallback> YubiKeyAuthenticator 31 | for Ctap20Authenticator<'a, T, U> 32 | { 33 | #[inline] 34 | async fn get_yubikey_config(&mut self) -> Result { 35 | self.token.get_yubikey_config().await 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "crypto")] 2 | mod crypto { 3 | pub fn test_openssl() { 4 | use std::env; 5 | 6 | const OPENSSL_DOC: &str = "https://github.com/kanidm/webauthn-rs/blob/master/OpenSSL.md"; 7 | 8 | println!(); 9 | if let Ok(v) = env::var("DEP_OPENSSL_VERSION_NUMBER") { 10 | let version = 11 | u64::from_str_radix(&v, 16).expect("Failed to parse OpenSSL version in build.rs"); 12 | 13 | #[allow(clippy::unusual_byte_groupings)] 14 | if version >= 0x3_00_00_00_0 { 15 | return; 16 | } else { 17 | println!( 18 | r#" 19 | Your version of OpenSSL is out of date, and not supported by this library. 20 | 21 | Please upgrade to OpenSSL v3.0.0 or later. 22 | 23 | More info: {OPENSSL_DOC} 24 | OpenSSL version string: {version} 25 | "# 26 | ); 27 | panic!("The installed version of OpenSSL is unusable."); 28 | } 29 | } 30 | 31 | panic!("No version of OpenSSL is found."); 32 | } 33 | } 34 | 35 | fn main() { 36 | #[cfg(feature = "crypto")] 37 | crypto::test_openssl(); 38 | } 39 | -------------------------------------------------------------------------------- /webauthn-rp-proxy/tests/data/authenticate-start.json: -------------------------------------------------------------------------------- 1 | { 2 | "passkeys": [ 3 | { 4 | "cred": { 5 | "cred_id": "Abr4cz81v7rNJR7OnKUJeB297HaWkpwUeEPAWAGTkAWA62e0fw20tf6LDL6CWmsZ3yVse9Yw1tpXpNLK5q7e2Po", 6 | "cred": { 7 | "type_": "ES256", 8 | "key": { 9 | "EC_EC2": { 10 | "curve": "SECP256R1", 11 | "x": "HvbGIj6R0H5dnvpqNZwKacuF3KN18CdZKEPBLSrndao", 12 | "y": "bpEXmUfWwhW0XwIEREPdUr-RxBH-QIprEWFxSgki6Ms" 13 | } 14 | } 15 | }, 16 | "counter": 0, 17 | "transports": null, 18 | "user_verified": true, 19 | "backup_eligible": false, 20 | "backup_state": false, 21 | "registration_policy": "required", 22 | "extensions": { 23 | "cred_protect": "Ignored", 24 | "hmac_create_secret": "NotRequested", 25 | "appid": "NotRequested", 26 | "cred_props": "Ignored" 27 | }, 28 | "attestation": { 29 | "data": "None", 30 | "metadata": "None" 31 | }, 32 | "attestation_format": "None" 33 | } 34 | } 35 | ], 36 | "rp_id": "localhost", 37 | "rp_origin": "http://localhost:8443" 38 | } 39 | -------------------------------------------------------------------------------- /.github/check_built_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Checks that documentation artefacts are present. 3 | # 4 | # This ensures that the library's documentation was buildable and included all 5 | # features, regardless of which features were actually enabled. 6 | 7 | set -e 8 | readonly MANIFEST="$(dirname $0)/doc_manifest" 9 | readonly TARGET_DIR="$(dirname $0)/../target/doc/" 10 | c=0 11 | m=0 12 | 13 | if [[ ! -d "${TARGET_DIR}" ]] 14 | then 15 | echo "Target directory ${TARGET_DIR} does not exist!" 16 | exit 1 17 | fi 18 | 19 | echo "Checking for missing files:" 20 | while read -r f 21 | do 22 | # Ignore empty lines and comments 23 | if [[ -z "$f" || "${f:0:1}" == "#" ]] 24 | then 25 | continue 26 | fi 27 | 28 | c=$((c+1)) 29 | # File must exist and be > 0 bytes 30 | if [[ ! -s "${TARGET_DIR}/${f}" ]] 31 | then 32 | # Missing file 33 | echo "$f" 34 | m=$((m+1)) 35 | fi 36 | done < "${MANIFEST}" 37 | 38 | if [[ "$c" == "0" ]] 39 | then 40 | echo "No files listed in ${MANIFEST} to check!" 41 | exit 1 42 | fi 43 | 44 | if [[ "$m" == "0" ]] 45 | then 46 | echo "All ${c} files present." 47 | else 48 | echo "${m} of ${c} files missing!" 49 | exit 1 50 | fi 51 | -------------------------------------------------------------------------------- /tutorial/server/actix_web/src/handler/mod.rs: -------------------------------------------------------------------------------- 1 | use actix_session::{SessionGetError, SessionInsertError}; 2 | 3 | use actix_web::http::StatusCode; 4 | use thiserror::Error; 5 | use webauthn_rs::prelude::WebauthnError; 6 | 7 | pub(crate) mod auth; 8 | pub(crate) mod index; 9 | pub(crate) mod serve_wasm; 10 | 11 | /** 12 | Type alias for Errors that implement [actix_web::ResponseError] through [Error] 13 | */ 14 | type WebResult = Result; 15 | 16 | /** 17 | Unified errors for simpler Responses 18 | */ 19 | #[derive(Debug, Error)] 20 | pub(crate) enum Error { 21 | #[error("Unknown webauthn error")] 22 | Unknown(WebauthnError), 23 | #[error("Corrupt session")] 24 | SessionGet(#[from] SessionGetError), 25 | #[error("Corrupt session")] 26 | SessionInsert(#[from] SessionInsertError), 27 | #[error("Corrupt session")] 28 | CorruptSession, 29 | #[error("Bad request")] 30 | BadRequest(#[from] WebauthnError), 31 | #[error("User not found")] 32 | UserNotFound, 33 | #[error("User has no credentials")] 34 | UserHasNoCredentials, 35 | } 36 | 37 | impl actix_web::ResponseError for Error { 38 | fn status_code(&self) -> StatusCode { 39 | StatusCode::INTERNAL_SERVER_ERROR 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tutorial/server/axum/src/error.rs: -------------------------------------------------------------------------------- 1 | use axum::{ 2 | http::StatusCode, 3 | response::{IntoResponse, Response}, 4 | }; 5 | use thiserror::Error; 6 | 7 | #[derive(Error, Debug)] 8 | pub enum WebauthnError { 9 | #[error("unknown webauthn error")] 10 | Unknown, 11 | #[error("Corrupt Session")] 12 | CorruptSession, 13 | #[error("User Not Found")] 14 | UserNotFound, 15 | #[error("User Has No Credentials")] 16 | UserHasNoCredentials, 17 | #[error("Deserialising Session failed: {0}")] 18 | InvalidSessionState(#[from] tower_sessions::session::Error), 19 | } 20 | impl IntoResponse for WebauthnError { 21 | fn into_response(self) -> Response { 22 | let body = match self { 23 | WebauthnError::CorruptSession => "Corrupt Session", 24 | WebauthnError::UserNotFound => "User Not Found", 25 | WebauthnError::Unknown => "Unknown Error", 26 | WebauthnError::UserHasNoCredentials => "User Has No Credentials", 27 | WebauthnError::InvalidSessionState(_) => "Deserialising Session failed", 28 | }; 29 | 30 | // its often easiest to implement `IntoResponse` by calling other implementations 31 | (StatusCode::INTERNAL_SERVER_ERROR, body).into_response() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn-rs-demo" 3 | version = "0.1.0" 4 | authors = ["William Brown "] 5 | edition = "2021" 6 | build = "build.rs" 7 | 8 | description = "Webauthn Demonstration Server" 9 | repository = "https://github.com/kanidm/webauthn-rs" 10 | readme = "README.md" 11 | license = "MPL-2.0" 12 | 13 | [dependencies] 14 | webauthn-rs-demo-shared = { path = "../webauthn-rs-demo-shared", features = ["core"] } 15 | webauthn-rs-core.workspace = true 16 | webauthn-rs = { workspace = true, features = ["conditional-ui", "attestation", "resident-key-support", "danger-allow-state-serialisation"] } 17 | 18 | tide = "0.16" 19 | tokio.workspace = true 20 | 21 | structopt = { version = "0.3", default-features = false } 22 | tracing.workspace = true 23 | tracing-subscriber.workspace = true 24 | rand.workspace = true 25 | url = { workspace = true , features = ["serde"] } 26 | 27 | serde.workspace = true 28 | 29 | webauthn-rs-device-catalog = { workspace = true } 30 | fido-mds = { workspace = true } 31 | reqwest = { version = "0.12", default-features = false, features = [ "rustls-tls-native-roots" ] } 32 | 33 | [dependencies.tide-openssl] 34 | git = "https://github.com/victorcwai/tide-openssl.git" 35 | rev = "7d0e2215f2f1ebfa71aa30d132213ed45dd95cbf" 36 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/nfc/tlv.rs: -------------------------------------------------------------------------------- 1 | //! [CompactTlv] is an [Iterator]-based Compact-TLV parser. 2 | 3 | /// An [Iterator]-based Compact-TLV parser. 4 | pub(crate) struct CompactTlv<'a> { 5 | b: &'a [u8], 6 | } 7 | 8 | impl CompactTlv<'_> { 9 | /// Parses a Compact-TLV structure in the given slice. 10 | pub fn new(tlv: &[u8]) -> CompactTlv<'_> { 11 | // Skip null bytes at the start 12 | let mut i = 0; 13 | loop { 14 | if i >= tlv.len() || tlv[i] != 0 { 15 | break; 16 | } 17 | i += 1; 18 | } 19 | 20 | CompactTlv { b: &tlv[i..] } 21 | } 22 | } 23 | 24 | impl<'a> Iterator for CompactTlv<'a> { 25 | /// A Compact-TLV item, a tuple of `tag, value`. 26 | type Item = (u8, &'a [u8]); 27 | 28 | fn next(&mut self) -> Option { 29 | if self.b.is_empty() { 30 | return None; 31 | } 32 | let tl = self.b[0]; 33 | let tag = tl >> 4; 34 | let len: usize = (tl & 0xf).into(); 35 | 36 | if self.b.len() < len + 1 { 37 | // The length of the tag extends out of bounds 38 | return None; 39 | } 40 | let v = &self.b[1..len + 1]; 41 | 42 | // Slide the buffer along 43 | self.b = &self.b[len + 1..]; 44 | Some((tag, v)) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /fido-hid-rs/README.md: -------------------------------------------------------------------------------- 1 | # fido-hid-rs 2 | 3 | `fido-hid-rs` implements a minimal set of platform-specific USB HID bindings for 4 | communicating with FIDO authenticators. 5 | 6 | > **Important:** this library is an _internal implementation detail_ of 7 | > [webauthn-authenticator-rs][0] to work around Cargo limitations. 8 | > 9 | > **This library has no guarantees of API stability, and is not intended for use 10 | > by other parties.** 11 | > 12 | > If you want to interface with USB HID FIDO authenticators, use 13 | > [webauthn-authenticator-rs][0] instead of this library. 14 | > 15 | > If you're looking for a general-purpose Rust USB HID library, try [hidapi][]. 16 | 17 | This library currently targets (and is regularly tested on): 18 | 19 | * Linux on `x86_64` (target version TBC) 20 | * macOS 13 and later on `arm64` (Apple silicon) and `x86_64` 21 | * Windows 10 on `x86_64` and Windows 11 on `arm64` and `x86_64` 22 | 23 | We only test on the **current** service pack or point release of these operating 24 | systems. 25 | 26 | Other platforms (and older releases of these operating systems) are supported on 27 | a "passive" basis only: it might work, but we generally don't have the 28 | appropriate hardware available, and rely on users to notify us when things go 29 | wrong and provide patches! ♥️ 30 | 31 | [0]: ../webauthn-authenticator-rs 32 | [hidapi]: https://docs.rs/hidapi/latest/hidapi/ 33 | -------------------------------------------------------------------------------- /tutorial/server/actix_web/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "actix_tutorial" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Niklas Pfister "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | # Webframework 11 | actix-web = { version = ">=4.5.1" } 12 | # Session framework for actix-web 13 | actix-session = { version = "~0.10", features = ["cookie-session"] } 14 | 15 | # Serve static file. Used to serve wasm 16 | actix-files = { version = "~0.6" } 17 | 18 | 19 | # Async trait, anyhow, chrono, and rand are required for the implementation of a 20 | # server-side memory-backed session store. 21 | # Normally, you want to use a database / redis backend as session store, but for the simplicity of this 22 | # tutorial, we implement our own. 23 | async-trait = { workspace = true } 24 | anyhow = { workspace = true } 25 | chrono = { version = "~0.4" } 26 | rand = { workspace = true } 27 | 28 | # Nicer error management 29 | thiserror = { workspace = true } 30 | 31 | 32 | # Async runtime 33 | tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } 34 | 35 | tracing.workspace = true 36 | tracing-subscriber.workspace = true 37 | tracing-log.workspace = true 38 | 39 | # Webauthn framework 40 | webauthn-rs = { workspace = true, features = [ 41 | "danger-allow-state-serialisation", 42 | ] } 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM opensuse/tumbleweed:latest AS ref_repo 2 | 3 | # sed -i -E 's/https?:\/\/download.opensuse.org/http:\/\/dl.suse.blackhats.net.au:8080/g' /etc/zypp/repos.d/*.repo && \ 4 | 5 | RUN zypper ar obs://devel:languages:rust devel:languages:rust && \ 6 | sed -i -E 's/https?:\/\/download.opensuse.org/https:\/\/mirror.firstyear.id.au/g' /etc/zypp/repos.d/*.repo && \ 7 | zypper --gpg-auto-import-keys ref --force 8 | 9 | # // setup the builder pkgs 10 | FROM ref_repo AS build_base 11 | RUN zypper install -y cargo rust gcc libopenssl-devel 12 | 13 | # // setup the runner pkgs 14 | FROM ref_repo AS run_base 15 | RUN zypper install -y openssl timezone 16 | 17 | # // build artifacts 18 | FROM build_base AS builder 19 | 20 | COPY . /home/webauthn-rs/ 21 | RUN mkdir /home/webauthn-rs/.cargo 22 | WORKDIR /home/webauthn-rs/compat_tester/webauthn-rs-demo/ 23 | 24 | # RUN cp cargo_vendor.config .cargo/config 25 | RUN cargo build --release 26 | 27 | # == end builder setup, we now have static artifacts. 28 | FROM run_base 29 | MAINTAINER william@blackhats.net.au 30 | EXPOSE 8080 31 | WORKDIR / 32 | 33 | RUN cd /etc && \ 34 | ln -sf ../usr/share/zoneinfo/Australia/Brisbane localtime 35 | 36 | COPY --from=builder /home/webauthn-rs/target/release/webauthn-rs-demo /bin/ 37 | COPY --from=builder /home/webauthn-rs/compat_tester/webauthn-rs-demo/pkg /pkg 38 | 39 | ENV RUST_BACKTRACE 1 40 | CMD ["/bin/webauthn-rs-demo"] 41 | -------------------------------------------------------------------------------- /webauthn-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn-rs" 3 | description = "Webauthn Framework for Rust Web Servers" 4 | readme = "../README.md" 5 | keywords = ["webauthn", "authentication", "passkeys"] 6 | categories = ["authentication", "web-programming"] 7 | 8 | version = { workspace = true } 9 | authors = { workspace = true } 10 | rust-version = { workspace = true } 11 | edition = { workspace = true } 12 | license = { workspace = true } 13 | homepage = { workspace = true } 14 | repository = { workspace = true } 15 | 16 | [package.metadata.docs.rs] 17 | features = ["danger-allow-state-serialisation", "danger-user-presence-only-security-keys", "danger-credential-internals"] 18 | rustdoc-args = ["--cfg", "docsrs"] 19 | 20 | [features] 21 | default = ["attestation"] 22 | 23 | preview-features = ["conditional-ui"] 24 | resident-key-support = [] 25 | conditional-ui = [] 26 | attestation = [] 27 | workaround-google-passkey-specific-issues = [] 28 | danger-allow-state-serialisation = [] 29 | danger-credential-internals = [] 30 | danger-user-presence-only-security-keys = [] 31 | 32 | [dependencies] 33 | base64urlsafedata.workspace = true 34 | webauthn-rs-core.workspace = true 35 | url = { workspace = true, features = ["serde"] } 36 | tracing.workspace = true 37 | serde.workspace = true 38 | uuid = { workspace = true, features = ["v4", "serde"] } 39 | 40 | [dev-dependencies] 41 | webauthn-rs-device-catalog.workspace = true 42 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Thanks for taking the time to engage with the project! We believe in the concept of [coordinated disclosure](https://en.wikipedia.org/wiki/Coordinated_vulnerability_disclosure) and currently expect a 60 day grace period for resolution of any outstanding issues. 4 | 5 | ## Supported versions 6 | 7 | Typically, only the most recent release and current HEAD of the main branch will be supported. 8 | 9 | ## Reporting a vulnerability 10 | 11 | You can report a security vulnerability in two ways: 12 | 13 | - [Report a vulnerability privately using GitHub security advisories][1] 14 | 15 | - via email to our core team: [William](mailto:william@blackhats.net.au) and 16 | [James](mailto:james+kanidm@terminaloutcomes.com) 17 | 18 | If you have found an issue where you're not sure whether it is a "real security 19 | bug", please report it as a security bug. We can re-triage the issue as a 20 | regular issue if it turns out to not have security impacts. 21 | 22 | We expect all reports to have been verified by a human before submission. 23 | Properly triaging and investigating all issues (not just security relevant) 24 | takes up time, and we are a volunteer-driven project. 25 | 26 | We will endeavour to respond to your request as soon as possible. 27 | 28 | We do not offer bounties, but acknowledgement will be made if you like. 29 | 30 | [1]: https://github.com/kanidm/webauthn-rs/security/advisories/new 31 | -------------------------------------------------------------------------------- /device-catalog/src/data/nitrokey.rs: -------------------------------------------------------------------------------- 1 | // Nitrokey fido2 and u2f root certs 2 | pub const NITROKEY_FIDO2_ROOT_CA_PEM: &[u8] = b"-----BEGIN CERTIFICATE----- 3 | MIIBmjCCAT8CFBZiBJbp2fT/LaRJ8Xwl9qhX62boMAoGCCqGSM49BAMCME4xCzAJ 4 | BgNVBAYTAkRFMRYwFAYDVQQKDA1OaXRyb2tleSBHbWJIMRAwDgYDVQQLDAdSb290 5 | IENBMRUwEwYDVQQDDAxuaXRyb2tleS5jb20wIBcNMTkxMjA0MDczNTM1WhgPMjA2 6 | OTExMjEwNzM1MzVaME4xCzAJBgNVBAYTAkRFMRYwFAYDVQQKDA1OaXRyb2tleSBH 7 | bWJIMRAwDgYDVQQLDAdSb290IENBMRUwEwYDVQQDDAxuaXRyb2tleS5jb20wWTAT 8 | BgcqhkjOPQIBBggqhkjOPQMBBwNCAAQy6KIN2gXqaSMWdWir/Hnx58NBzjthYdNv 9 | k95hdt7jCpyW2cHqLdQ5Sqcvo0CuordgDOach0ZGB60w9GZY8SHJMAoGCCqGSM49 10 | BAMCA0kAMEYCIQDLmdy2G2mM4rZKjl6CVfjV7khilIS5D3xRQzubeqzQNAIhAKIG 11 | X29SfiB6K9k6Hb3q+q7bRn1o1dhV1cj592YYnu1/ 12 | -----END CERTIFICATE-----"; 13 | 14 | pub const NITROKEY_U2F_ROOT_CA_PEM: &[u8] = b"-----BEGIN CERTIFICATE----- 15 | MIIBlTCCATqgAwIBAgIJAMBE6C6nkMPQMAoGCCqGSM49BAMCMB0xGzAZBgNVBAMM 16 | Ek5pdHJva2V5IFJvb3QgQ0EgMjAeFw0xODEwMzAwMTQ1NTlaFw0zODEwMjUwMTQ1 17 | NTlaMB0xGzAZBgNVBAMMEk5pdHJva2V5IFJvb3QgQ0EgMjBZMBMGByqGSM49AgEG 18 | CCqGSM49AwEHA0IABD1zniCkovs56QlA2dw+idGDJLOx8vQAJqB5ZhmxPaO3KTg5 19 | CGRGr+Prk0If1K+1UemOrIhUjGM6bS+GXHfEbdOjYzBhMB0GA1UdDgQWBBSbwFLo 20 | wWVgQmHkXJwmz2vo/cZvkTAfBgNVHSMEGDAWgBSbwFLowWVgQmHkXJwmz2vo/cZv 21 | kTASBgNVHRMBAf8ECDAGAQH/AgEBMAsGA1UdDwQEAwICBDAKBggqhkjOPQQDAgNJ 22 | ADBGAiEApf7+miYmy9hZ7hjj8M9v1hxRFPTaoAmwZrrEFSsasywCIQCYYa7ZvmIE 23 | skmkHTvaRVpIFP7npdI1nvHitJG2wEx4Iw== 24 | -----END CERTIFICATE-----"; 25 | -------------------------------------------------------------------------------- /webauthn-rs-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn-rs-core" 3 | description = "Webauthn Cryptographic Operation Handling" 4 | readme = "README.md" 5 | keywords = ["webauthn", "authentication"] 6 | categories = ["authentication", "web-programming"] 7 | 8 | version = { workspace = true } 9 | authors = { workspace = true } 10 | rust-version = { workspace = true } 11 | edition = { workspace = true } 12 | license = { workspace = true } 13 | homepage = { workspace = true } 14 | repository = { workspace = true } 15 | 16 | [features] 17 | default = [] 18 | 19 | [package.metadata.docs.rs] 20 | rustdoc-args = ["--cfg", "docsrs"] 21 | 22 | [dependencies] 23 | base64.workspace = true 24 | base64urlsafedata.workspace = true 25 | hex.workspace = true 26 | webauthn-attestation-ca.workspace = true 27 | webauthn-rs-proto.workspace = true 28 | serde.workspace = true 29 | serde_cbor_2.workspace = true 30 | serde_json.workspace = true 31 | nom.workspace = true 32 | thiserror.workspace = true 33 | tracing.workspace = true 34 | openssl.workspace = true 35 | openssl-sys.workspace = true 36 | # We could consider replacing this with openssl rand. 37 | rand.workspace = true 38 | rand_chacha.workspace = true 39 | url = { workspace = true, features = ["serde"] } 40 | x509-parser = "0.16" 41 | der-parser = "9.0" 42 | uuid = { workspace = true, features = ["serde"] } 43 | 44 | [dev-dependencies] 45 | hex-literal = "0.4" 46 | tracing-subscriber.workspace = true 47 | webauthn-rs-device-catalog.workspace = true 48 | -------------------------------------------------------------------------------- /fido-key-manager/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fido-key-manager" 3 | categories = ["authentication"] 4 | description = "Management CLI for FIDO/CTAP 2.x authenticators" 5 | keywords = ["ctap", "fido", "passkeys", "webauthn"] 6 | readme = "README.md" 7 | build = "build.rs" 8 | 9 | version = { workspace = true } 10 | authors = { workspace = true } 11 | rust-version = { workspace = true } 12 | edition = { workspace = true } 13 | license = { workspace = true } 14 | homepage = { workspace = true } 15 | repository = { workspace = true } 16 | 17 | [[bin]] 18 | name = "fido-key-manager" 19 | # cargo can't run binaries needing elevation on Windows, and there's no tests 20 | # here anyway. 21 | test = false 22 | 23 | [features] 24 | # Bluetooth support is flakey on Linux and Windows, so not enabled by default. 25 | bluetooth = ["webauthn-authenticator-rs/bluetooth"] 26 | nfc = ["webauthn-authenticator-rs/nfc"] 27 | usb = ["webauthn-authenticator-rs/usb"] 28 | solokey = ["webauthn-authenticator-rs/vendor-solokey"] 29 | yubikey = ["webauthn-authenticator-rs/vendor-yubikey"] 30 | 31 | default = ["nfc", "usb"] 32 | 33 | disable_windows_manifest = [] 34 | 35 | [dependencies] 36 | webauthn-authenticator-rs = { workspace = true, features = ["ui-cli", "ctap2-management"] } 37 | 38 | clap.workspace = true 39 | hex.workspace = true 40 | tokio.workspace = true 41 | tokio-stream.workspace = true 42 | tracing.workspace = true 43 | tracing-subscriber.workspace = true 44 | webauthn-rs-core.workspace = true 45 | 46 | [build-dependencies] 47 | embed-resource = "3" 48 | -------------------------------------------------------------------------------- /webauthn-rs-proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn-rs-proto" 3 | description = "Webauthn Specification Bindings" 4 | readme = "README.md" 5 | keywords = ["webauthn", "authentication"] 6 | categories = ["authentication", "web-programming"] 7 | 8 | version = { workspace = true } 9 | authors = { workspace = true } 10 | rust-version = { workspace = true } 11 | edition = { workspace = true } 12 | license = { workspace = true } 13 | homepage = { workspace = true } 14 | repository = { workspace = true } 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [features] 19 | default = [] 20 | wasm = ["wasm-bindgen", "web-sys", "js-sys", "serde-wasm-bindgen"] 21 | 22 | [package.metadata.docs.rs] 23 | rustdoc-args = ["--cfg", "docsrs"] 24 | 25 | [dependencies] 26 | base64.workspace = true 27 | base64urlsafedata.workspace = true 28 | serde.workspace = true 29 | serde_json.workspace = true 30 | url = { workspace = true, features = ["serde"] } 31 | 32 | # Webauthn Components 33 | wasm-bindgen = { version = "0.2", features = [ 34 | "serde-serialize", 35 | ], optional = true } 36 | serde-wasm-bindgen = { version = "0.6", optional = true } 37 | js-sys = { version = "0.3", optional = true } 38 | 39 | [dependencies.web-sys] 40 | version = "0.3" 41 | optional = true 42 | features = [ 43 | "CredentialCreationOptions", 44 | "CredentialRequestOptions", 45 | "PublicKeyCredential", 46 | "PublicKeyCredentialCreationOptions", 47 | "AuthenticationExtensionsClientInputs", 48 | "AuthenticationExtensionsClientOutputs", 49 | "console", 50 | ] 51 | -------------------------------------------------------------------------------- /webauthn-rp-proxy/tests/data/register-finish.json: -------------------------------------------------------------------------------- 1 | { 2 | "passkey_registration": { 3 | "rs": { 4 | "policy": "required", 5 | "exclude_credentials": [], 6 | "challenge": "sUBoD5kVocq6Q9XLgrQYY26hO5pNxTinl4X_4a2XB9w", 7 | "credential_algorithms": [ 8 | "ES256", 9 | "RS256" 10 | ], 11 | "require_resident_key": false, 12 | "authenticator_attachment": null, 13 | "extensions": { 14 | "credentialProtectionPolicy": "userVerificationRequired", 15 | "enforceCredentialProtectionPolicy": false, 16 | "uvm": true, 17 | "credProps": true 18 | }, 19 | "allow_synchronised_authenticators": true 20 | } 21 | }, 22 | "register_public_key_credential": { 23 | "id": "ARLlbpXWj_8dMYj1afk1vej8dkPYTju2Ruha67c_Rf8BIfXdDK4u6FI6uUu5G3aIzOD8TEmthSzxmQBPysqM_BQ", 24 | "rawId": "ARLlbpXWj_8dMYj1afk1vej8dkPYTju2Ruha67c_Rf8BIfXdDK4u6FI6uUu5G3aIzOD8TEmthSzxmQBPysqM_BQ", 25 | "response": { 26 | "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQES5W6V1o__HTGI9Wn5Nb3o_HZD2E47tkboWuu3P0X_ASH13QyuLuhSOrlLuRt2iMzg_ExJrYUs8ZkAT8rKjPwUpQECAyYgASFYIMaa_KhJ_b4VF6aFzZUTKWs_y5rgbtfVdugSlOWdqhjtIlggSjBrCqWLBc3x6V2VW6iuMxFnh8esXPt1Aq1sbpMH9_o", 27 | "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoic1VCb0Q1a1ZvY3E2UTlYTGdyUVlZMjZoTzVwTnhUaW5sNFhfNGEyWEI5dyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0" 28 | }, 29 | "type": "public-key" 30 | }, 31 | "rp_id": "localhost", 32 | "rp_origin": "http://localhost:8443" 33 | } 34 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/ui/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{CableRequestType, CableState, EnrollSampleStatus}; 2 | use std::fmt::Debug; 3 | 4 | #[cfg(any(all(doc, not(doctest)), feature = "ui-cli"))] 5 | mod cli; 6 | 7 | #[cfg(any(all(doc, not(doctest)), feature = "ui-cli"))] 8 | #[doc(inline)] 9 | pub use self::cli::Cli; 10 | 11 | pub trait UiCallback: Sync + Send + Debug { 12 | /// Prompts the user to enter their PIN. 13 | fn request_pin(&self) -> Option; 14 | 15 | /// Prompts the user to interact with their authenticator, normally by 16 | /// pressing or touching its button. 17 | /// 18 | /// This method will be called synchronously, and must not block. 19 | fn request_touch(&self); 20 | 21 | /// Tell the user that the key is currently processing a request. 22 | fn processing(&self); 23 | 24 | /// Provide the user feedback when they are enrolling fingerprints. 25 | /// 26 | /// This method will be called synchronously, and must not block. 27 | fn fingerprint_enrollment_feedback( 28 | &self, 29 | remaining_samples: u32, 30 | feedback: Option, 31 | ); 32 | 33 | /// Prompt the user to scan a QR code with their mobile device to start the 34 | /// caBLE linking process. 35 | /// 36 | /// This method will be called synchronously, and must not block. 37 | fn cable_qr_code(&self, request_type: CableRequestType, url: String); 38 | 39 | /// Dismiss a displayed QR code from the screen. 40 | /// 41 | /// This method will be called synchronously, and must not block. 42 | fn dismiss_qr_code(&self); 43 | 44 | fn cable_status_update(&self, state: CableState); 45 | } 46 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webauthn_rs_demo_wasm" 3 | version = "0.1.0" 4 | authors = ["William Brown "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [lib] 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [dependencies] 12 | webauthn-rs-demo-shared = { path = "../webauthn-rs-demo-shared", default-features = false } 13 | webauthn-rs-proto = { path = "../../webauthn-rs-proto", default-features = false, features = [ 14 | "wasm", 15 | ] } 16 | wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } 17 | wasm-bindgen-futures = { version = "0.4" } 18 | yew-router = "0.18.0" 19 | yew = "0.21" 20 | js-sys = "0.3" 21 | serde = { version = "1.0", features = ["derive"] } 22 | serde-wasm-bindgen = "0.6.5" 23 | gloo = { version = "0.11.0", default-features = false, features = ["console"] } 24 | url = "2" 25 | 26 | [dependencies.web-sys] 27 | version = "0.3" 28 | features = [ 29 | "AbortController", 30 | "AbortSignal", 31 | "AuthenticationExtensionsClientOutputs", 32 | "AuthenticatorResponse", 33 | "Clipboard", 34 | "CredentialCreationOptions", 35 | "CredentialRequestOptions", 36 | "CredentialsContainer", 37 | "Element", 38 | "Event", 39 | "Headers", 40 | "HtmlCollection", 41 | "HtmlSelectElement", 42 | "Navigator", 43 | "PublicKeyCredential", 44 | "PublicKeyCredentialCreationOptions", 45 | "PublicKeyCredentialRpEntity", 46 | "PublicKeyCredentialUserEntity", 47 | "Request", 48 | "RequestCredentials", 49 | "RequestInit", 50 | "RequestMode", 51 | "RequestRedirect", 52 | "Response", 53 | "Window", 54 | ] 55 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/win10/rp.rs: -------------------------------------------------------------------------------- 1 | //! Wrappers for [RelyingParty]. 2 | use std::pin::Pin; 3 | 4 | use webauthn_rs_proto::RelyingParty; 5 | use windows::{ 6 | core::{HSTRING, PCWSTR}, 7 | Win32::Networking::WindowsWebServices::{ 8 | WEBAUTHN_RP_ENTITY_INFORMATION, WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION, 9 | }, 10 | }; 11 | 12 | use super::WinWrapper; 13 | use crate::error::WebauthnCError; 14 | 15 | /// Wrapper for [WEBAUTHN_RP_ENTITY_INFORMATION] to ensure pointer lifetime. 16 | pub struct WinRpEntityInformation { 17 | native: WEBAUTHN_RP_ENTITY_INFORMATION, 18 | id: HSTRING, 19 | name: HSTRING, 20 | } 21 | 22 | impl WinWrapper for WinRpEntityInformation { 23 | type NativeType = WEBAUTHN_RP_ENTITY_INFORMATION; 24 | fn new(rp: RelyingParty) -> Result>, WebauthnCError> { 25 | let res = Self { 26 | native: Default::default(), 27 | id: rp.id.into(), 28 | name: rp.name.into(), 29 | }; 30 | 31 | let mut boxed = Box::pin(res); 32 | 33 | let native = WEBAUTHN_RP_ENTITY_INFORMATION { 34 | dwVersion: WEBAUTHN_RP_ENTITY_INFORMATION_CURRENT_VERSION, 35 | pwszId: (&boxed.id).into(), 36 | pwszName: (&boxed.name).into(), 37 | pwszIcon: PCWSTR::null(), 38 | }; 39 | 40 | // Update the boxed type with the proper native object. 41 | unsafe { 42 | let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed); 43 | Pin::get_unchecked_mut(mut_ref).native = native; 44 | } 45 | 46 | Ok(boxed) 47 | } 48 | 49 | fn native_ptr(&self) -> &WEBAUTHN_RP_ENTITY_INFORMATION { 50 | &self.native 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo-wasm/src/uv.rs: -------------------------------------------------------------------------------- 1 | use gloo::console; 2 | use yew::prelude::*; 3 | 4 | #[derive(Debug)] 5 | pub enum UvInconsistent { 6 | Init, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub enum AppMsg { 11 | Begin, 12 | } 13 | 14 | impl Component for UvInconsistent { 15 | type Message = AppMsg; 16 | type Properties = (); 17 | 18 | fn create(_ctx: &Context) -> Self { 19 | console::log!(format!("create").as_str()); 20 | UvInconsistent::Init 21 | } 22 | 23 | fn changed(&mut self, _ctx: &Context) -> bool { 24 | false 25 | } 26 | 27 | fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { 28 | true 29 | } 30 | 31 | fn view(&self, _ctx: &Context) -> Html { 32 | html! { 33 |
34 |
35 |

{ "what's going on?" }

36 |
37 |
38 |
39 |
40 |
41 |

{ "Register a Webauthn Credential" }

42 |
43 | 44 | 45 |
46 | 47 |
48 |
49 |
50 |
51 |
52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /base64urlsafedata/src/human.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use crate::{Base64UrlSafeData, URL_SAFE_NO_PAD}; 4 | use base64::Engine; 5 | use serde::{Serialize, Serializer}; 6 | 7 | /// Serde wrapper for `Vec` which emits URL-safe, non-padded Base64 for 8 | /// *only* human-readable formats, and accepts Base64 and binary formats. 9 | /// 10 | /// * Deserialisation is described in the [module documentation][crate]. 11 | /// 12 | /// * Serialisation to [a human-readable format][0] (such as JSON) emits 13 | /// URL-safe, non-padded Base64 (per [RFC 4648 §5][sec5]). 14 | /// 15 | /// * Serialisation to [a non-human-readable format][0] (such as CBOR) emits 16 | /// a native "bytes" type, and not encode the value. 17 | /// 18 | /// [0]: https://docs.rs/serde/latest/serde/trait.Serializer.html#method.is_human_readable 19 | /// [sec5]: https://datatracker.ietf.org/doc/html/rfc4648#section-5 20 | #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] 21 | pub struct HumanBinaryData(Vec); 22 | 23 | common_impls!(HumanBinaryData); 24 | 25 | impl From for HumanBinaryData { 26 | fn from(value: Base64UrlSafeData) -> Self { 27 | Self(value.into()) 28 | } 29 | } 30 | 31 | impl PartialEq for HumanBinaryData { 32 | fn eq(&self, other: &Base64UrlSafeData) -> bool { 33 | self.0.eq(other) 34 | } 35 | } 36 | 37 | impl Serialize for HumanBinaryData { 38 | fn serialize(&self, serializer: S) -> Result 39 | where 40 | S: Serializer, 41 | { 42 | if serializer.is_human_readable() { 43 | let encoded = URL_SAFE_NO_PAD.encode(self); 44 | serializer.serialize_str(&encoded) 45 | } else { 46 | serializer.serialize_bytes(self) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /webauthn-rs-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Webauthn-rs - Webauthn for Rust Server Applications 2 | //! 3 | //! Webauthn is a standard allowing communication between servers, browsers and authenticators 4 | //! to allow strong, passwordless, cryptographic authentication to be performed. Webauthn 5 | //! is able to operate with many authenticator types, such as U2F. 6 | //! 7 | //! ⚠️ ⚠️ ⚠️ THIS IS UNSAFE. AVOID USING THIS DIRECTLY ⚠️ ⚠️ ⚠️ 8 | //! 9 | //! If possible, use the `webauthn-rs` crate, and it's safe wrapper instead! 10 | //! 11 | //! Webauthn as a standard has many traps that in the worst cases, may lead to 12 | //! bypasses and full account compromises. Many of the features of webauthn are 13 | //! NOT security policy, but user interface hints. Many options can NOT be 14 | //! enforced. `webauthn-rs` handles these correctly. USE `webauthn-rs` INSTEAD. 15 | 16 | #![cfg_attr(docsrs, feature(doc_cfg))] 17 | #![deny(warnings)] 18 | #![warn(unused_extern_crates)] 19 | #![warn(missing_docs)] 20 | #![deny(clippy::todo)] 21 | #![deny(clippy::unimplemented)] 22 | #![deny(clippy::unwrap_used)] 23 | // #![deny(clippy::expect_used)] 24 | #![deny(clippy::panic)] 25 | #![deny(clippy::unreachable)] 26 | #![deny(clippy::await_holding_lock)] 27 | #![deny(clippy::needless_pass_by_value)] 28 | #![deny(clippy::trivially_copy_pass_by_ref)] 29 | 30 | #[macro_use] 31 | extern crate tracing; 32 | 33 | #[macro_use] 34 | mod macros; 35 | 36 | mod constants; 37 | 38 | pub mod attestation; 39 | pub mod crypto; 40 | pub mod fake; 41 | 42 | mod core; 43 | pub mod error; 44 | mod interface; 45 | pub mod internals; 46 | 47 | /// Protocol bindings 48 | pub mod proto { 49 | pub use crate::interface::*; 50 | pub use base64urlsafedata::Base64UrlSafeData; 51 | pub use webauthn_rs_proto::*; 52 | } 53 | 54 | pub use crate::core::*; 55 | -------------------------------------------------------------------------------- /fido-hid-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fido-hid-rs" 3 | description = "USB HID library for FIDO authenticators" 4 | 5 | version = { workspace = true } 6 | authors = { workspace = true } 7 | rust-version = { workspace = true } 8 | edition = { workspace = true } 9 | license = { workspace = true } 10 | homepage = { workspace = true } 11 | repository = { workspace = true } 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | bitflags = "2.10.0" 17 | async-trait = "0.1.89" 18 | futures.workspace = true 19 | thiserror.workspace = true 20 | tokio.workspace = true 21 | tokio-stream.workspace = true 22 | tracing.workspace = true 23 | 24 | [build-dependencies] 25 | # Actually only required when targeting Linux, but Cargo doesn't support 26 | # build-dependencies for a single target, and you might be cross-compiling. 27 | bindgen = "0.72.1" 28 | 29 | [dev-dependencies] 30 | tracing-subscriber = { workspace = true } 31 | 32 | # descriptors tests 33 | hex = { workspace = true } 34 | num-derive = { workspace = true } 35 | num-traits = "0.2" 36 | 37 | [target.'cfg(target_os = "windows")'.dependencies] 38 | lazy_static = "1.5.0" 39 | windows = { version = "0.41.0", features = [ 40 | "Win32_Foundation", 41 | "Win32_Devices_HumanInterfaceDevice", 42 | "Devices_Enumeration", 43 | "Devices_HumanInterfaceDevice", 44 | "Foundation", 45 | "Foundation_Collections", 46 | "Storage", 47 | "Storage_Streams", 48 | ] } 49 | 50 | [target.'cfg(target_os = "macos")'.dependencies] 51 | core-foundation = ">=0.10.0" 52 | libc = "0.2" 53 | mach2 = "0.6" 54 | 55 | [target.'cfg(target_os = "linux")'.dependencies] 56 | nix = { version = "0.30.1", features = ["ioctl", "poll", "signal"] } 57 | num-derive = { workspace = true } 58 | num-traits = "0.2" 59 | udev = "0.9.3" 60 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/ctap2/commands/selection.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | use self::CBORCommand; 4 | use super::*; 5 | 6 | /// `authenticatorSelection` request type. 7 | /// 8 | /// This feature **requires** FIDO v2.1. v2.1-PRE isn't good enough. 9 | /// 10 | /// This has no parameters or response type. 11 | /// 12 | /// 13 | #[derive(Serialize, Debug, Clone)] 14 | pub struct SelectionRequest {} 15 | 16 | impl CBORCommand for SelectionRequest { 17 | const CMD: u8 = 0x0b; 18 | const HAS_PAYLOAD: bool = false; 19 | type Response = NoResponse; 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | use crate::transport::iso7816::ISO7816LengthForm; 26 | 27 | #[test] 28 | fn selection_request() { 29 | let req = SelectionRequest {}; 30 | let short = vec![0x80, 0x10, 0, 0, 1, 0xb, 0]; 31 | let ext = vec![0x80, 0x10, 0, 0, 0, 0, 1, 0xb, 0, 0]; 32 | 33 | let a = to_short_apdus(&req.cbor().unwrap()); 34 | assert_eq!(1, a.len()); 35 | assert_eq!(short, a[0].to_bytes(&ISO7816LengthForm::ShortOnly).unwrap()); 36 | assert_eq!(short, a[0].to_bytes(&ISO7816LengthForm::Extended).unwrap()); 37 | 38 | assert_eq!( 39 | ext, 40 | to_extended_apdu(req.cbor().unwrap()) 41 | .to_bytes(&ISO7816LengthForm::Extended) 42 | .unwrap() 43 | ); 44 | assert_eq!( 45 | ext, 46 | to_extended_apdu(req.cbor().unwrap()) 47 | .to_bytes(&ISO7816LengthForm::ExtendedOnly) 48 | .unwrap() 49 | ); 50 | assert!(to_extended_apdu(req.cbor().unwrap()) 51 | .to_bytes(&ISO7816LengthForm::ShortOnly) 52 | .is_err()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/pkg/webauthn_rs_demo_wasm.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /** 4 | */ 5 | export function run_app(): void; 6 | 7 | export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module; 8 | 9 | export interface InitOutput { 10 | readonly memory: WebAssembly.Memory; 11 | readonly run_app: (a: number) => void; 12 | readonly __wbindgen_malloc: (a: number, b: number) => number; 13 | readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; 14 | readonly __wbindgen_export_2: WebAssembly.Table; 15 | readonly wasm_bindgen__convert__closures__invoke1_mut_ref__h0f42758389f9ef59: (a: number, b: number, c: number) => void; 16 | readonly wasm_bindgen__convert__closures__invoke1__h3626c9e41e52d750: (a: number, b: number, c: number) => void; 17 | readonly _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h458d161319ed8eb6: (a: number, b: number, c: number) => void; 18 | readonly __wbindgen_add_to_stack_pointer: (a: number) => number; 19 | readonly __wbindgen_exn_store: (a: number) => void; 20 | readonly __wbindgen_free: (a: number, b: number, c: number) => void; 21 | } 22 | 23 | export type SyncInitInput = BufferSource | WebAssembly.Module; 24 | /** 25 | * Instantiates the given `module`, which can either be bytes or 26 | * a precompiled `WebAssembly.Module`. 27 | * 28 | * @param {SyncInitInput} module 29 | * 30 | * @returns {InitOutput} 31 | */ 32 | export function initSync(module: SyncInitInput): InitOutput; 33 | 34 | /** 35 | * If `module_or_path` is {RequestInfo} or {URL}, makes a request and 36 | * for everything else, calls `WebAssembly.instantiate` directly. 37 | * 38 | * @param {InitInput | Promise} module_or_path 39 | * 40 | * @returns {Promise} 41 | */ 42 | export default function __wbg_init (module_or_path?: InitInput | Promise): Promise; 43 | -------------------------------------------------------------------------------- /tutorial/server/actix_web/src/startup.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use actix_web::web::Data; 4 | use tokio::sync::Mutex; 5 | 6 | /* 7 | * Webauthn RS server side app state and setup code. 8 | */ 9 | 10 | // Configure the Webauthn instance by using the WebauthnBuilder. This defines 11 | // the options needed for your site, and has some implications. One of these is that 12 | // you can NOT change your rp_id (relying party id), without invalidating all 13 | // webauthn credentials. Remember, rp_id is derived from your URL origin, meaning 14 | // that it is your effective domain name. 15 | 16 | use webauthn_rs::prelude::*; 17 | 18 | pub(crate) struct UserData { 19 | pub(crate) name_to_id: HashMap, 20 | pub(crate) keys: HashMap>, 21 | } 22 | 23 | pub(crate) fn startup() -> (Data, Data>) { 24 | // Effective domain name. 25 | let rp_id = "localhost"; 26 | // Url containing the effective domain name 27 | // MUST include the port number! 28 | let rp_origin = Url::parse("http://localhost:8080").expect("Invalid URL"); 29 | let builder = WebauthnBuilder::new(rp_id, &rp_origin).expect("Invalid configuration"); 30 | 31 | // Now, with the builder you can define other options. 32 | // Set a "nice" relying party name. Has no security properties and 33 | // may be changed in the future. 34 | let builder = builder.rp_name("Actix-web webauthn-rs"); 35 | 36 | // Consume the builder and create our webauthn instance. 37 | // Webauthn has no mutable inner state, so Arc (Data) and read only is sufficient. 38 | let webauthn = Data::new(builder.build().expect("Invalid configuration")); 39 | 40 | // This needs mutability, so does require a mutex. 41 | let webauthn_users = Data::new(Mutex::new(UserData { 42 | name_to_id: HashMap::new(), 43 | keys: HashMap::new(), 44 | })); 45 | 46 | (webauthn, webauthn_users) 47 | } 48 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/ctap2/commands/reset.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | 3 | use self::CBORCommand; 4 | use super::*; 5 | 6 | /// `authenticatorReset` request type. 7 | /// 8 | /// This has no parameters or response type. This may not be available over all 9 | /// transports, and generally only works within the first 10 seconds of the 10 | /// authenticator powering up. 11 | /// 12 | /// Reference: 13 | #[derive(Serialize, Debug, Clone)] 14 | pub struct ResetRequest {} 15 | 16 | impl CBORCommand for ResetRequest { 17 | const CMD: u8 = 0x07; 18 | const HAS_PAYLOAD: bool = false; 19 | type Response = NoResponse; 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | use crate::transport::iso7816::ISO7816LengthForm; 26 | 27 | #[test] 28 | fn reset_request() { 29 | let req = ResetRequest {}; 30 | let short = vec![0x80, 0x10, 0, 0, 1, 0x7, 0]; 31 | let ext = vec![0x80, 0x10, 0, 0, 0, 0, 1, 0x7, 0, 0]; 32 | 33 | let a = to_short_apdus(&req.cbor().unwrap()); 34 | assert_eq!(1, a.len()); 35 | assert_eq!(short, a[0].to_bytes(&ISO7816LengthForm::ShortOnly).unwrap()); 36 | assert_eq!(short, a[0].to_bytes(&ISO7816LengthForm::Extended).unwrap()); 37 | 38 | assert_eq!( 39 | ext, 40 | to_extended_apdu(req.cbor().unwrap()) 41 | .to_bytes(&ISO7816LengthForm::Extended) 42 | .unwrap() 43 | ); 44 | assert_eq!( 45 | ext, 46 | to_extended_apdu(req.cbor().unwrap()) 47 | .to_bytes(&ISO7816LengthForm::ExtendedOnly) 48 | .unwrap() 49 | ); 50 | assert!(to_extended_apdu(req.cbor().unwrap()) 51 | .to_bytes(&ISO7816LengthForm::ShortOnly) 52 | .is_err()); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/README.md: -------------------------------------------------------------------------------- 1 | 2 | # webauthn-authenticator-rs 3 | 4 | WebAuthn is a modern approach to hardware based authentication, consisting of 5 | a user with an authenticator device, a browser or client that interacts with the 6 | device, and a server that is able to generate challenges and verify the 7 | authenticator's validity. 8 | 9 | This library is the client half of the authenticator process, performing the 10 | steps that would normally be taken by a web browser. Given a challenge from 11 | a Webauthn server, this library can interface with a CTAP2 device and transform 12 | the response to a Webauthn registration or assertion (authentication). 13 | 14 | ## Development 15 | 16 | ### Building docs 17 | 18 | This library contains extensive documentation in `rustdoc` format. You can build 19 | this with: 20 | 21 | ```sh 22 | cargo doc --no-deps --document-private-items 23 | ``` 24 | 25 | This library includes many references to _module-private_ items to explain how 26 | protocols work, so we use `--document-private-items`. 27 | 28 | By default, this won't add any features, so you'll want to add them with 29 | `--features ...`, or use `--all-features` (which pulls in many dependencies). 30 | 31 | To build all docs in a way that 32 | [annotates which modules and functions are avaliable with which features][doc_cfg]: 33 | 34 | 1. Install [Rust nightly][]: 35 | 36 | ```sh 37 | rustup toolchain install nightly 38 | ``` 39 | 40 | 2. Build the documentation with: 41 | 42 | ```sh 43 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --no-deps --document-private-items 44 | ``` 45 | 46 | Or with PowerShell (Windows): 47 | 48 | ```ps1 49 | $Env:RUSTDOCFLAGS = "--cfg docsrs" 50 | cargo +nightly doc --no-deps --document-private-items 51 | ``` 52 | 53 | [Rust nightly]: https://doc.rust-lang.org/book/appendix-07-nightly-rust.html 54 | [doc_cfg]: https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html 55 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/win10/native.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for working with Windows native types. 2 | use std::marker::PhantomData; 3 | use std::ops::Deref; 4 | use std::pin::Pin; 5 | 6 | use crate::error::WebauthnCError; 7 | 8 | /// Smart pointer type to automatically `free()` bare pointers we got from 9 | /// Windows' API when dropped. 10 | pub struct WinPtr<'a, T: 'a> { 11 | free: unsafe fn(*const T) -> (), 12 | ptr: *const T, 13 | phantom: PhantomData<&'a T>, 14 | } 15 | 16 | impl<'a, T> WinPtr<'a, T> { 17 | /// Creates a wrapper around a `*const T` pointer which automatically calls 18 | /// the `free` function when dropped. 19 | /// 20 | /// Returns `None` if `ptr` is null. 21 | /// 22 | /// Unsafe if `ptr` is unaligned or does not point to a `T`. 23 | pub unsafe fn new(ptr: *const T, free: unsafe fn(*const T) -> ()) -> Option { 24 | if ptr.is_null() { 25 | None 26 | } else { 27 | // trace!("new_ptr: r={:?}", ptr); 28 | Some(Self { 29 | free, 30 | ptr, 31 | phantom: PhantomData, 32 | }) 33 | } 34 | } 35 | } 36 | 37 | impl<'a, T> Deref for WinPtr<'a, T> { 38 | type Target = T; 39 | fn deref(&self) -> &T { 40 | unsafe { &(*self.ptr) } 41 | } 42 | } 43 | 44 | impl<'a, T> Drop for WinPtr<'a, T> { 45 | fn drop(&mut self) { 46 | // trace!("free_ptr: r={:?}", self.ptr); 47 | unsafe { (self.free)(self.ptr) } 48 | } 49 | } 50 | 51 | /// Wrapper for a `webauthn-authenticator-rs` type (`T`) to convert it to a 52 | /// Windows WebAuthn API type (`NativeType`). 53 | pub trait WinWrapper { 54 | /// Windows equivalent type for `T` 55 | type NativeType; 56 | /// Converts a `webauthn-authenticator-rs` type to a Windows type 57 | fn new(v: T) -> Result>, WebauthnCError>; 58 | /// Returns a pointer to the Windows equivalent type 59 | fn native_ptr(&self) -> &Self::NativeType; 60 | } 61 | -------------------------------------------------------------------------------- /webauthn-rs-core/README.md: -------------------------------------------------------------------------------- 1 | Webauthn Rust Core 2 | ================== 3 | 4 | Webauthn is a modern approach to hardware based authentication, consisting of 5 | a user with an authenticator device, a browser or client that interacts with the 6 | device, and a server that is able to generate challenges and verify the 7 | authenticator's validity. 8 | 9 | ⚠️ WARNING ⚠️ 10 | ------------- 11 | 12 | This library implements and exposes the *raw* elements to create a Webauthn Relying 13 | Party. Many of these components have many sharp edges and the ability to confuse 14 | users, accidentally allow security bypasses, and more. If possible you SHOULD use 15 | [Webauthn-RS](https://docs.rs/webauthn-rs/) instead of this crate! 16 | 17 | However, if you want to do something truly custom or specific, and you understand the 18 | risks, then this library is for you. 19 | 20 | Why OpenSSL? 21 | ------------ 22 | 23 | A question I expect is why OpenSSL rather than some other pure-Rust cryptographic 24 | providers. There are two major justfications. 25 | 26 | The first is that if this library will be used in corporate or major deployments, 27 | then cryptographic audits may have to be performed. It is much easier to point 28 | toward OpenSSL which has already undergone much more review and auditing than 29 | using a series of Rust crates which (while still great!) have not seen the same 30 | level of scrutiny. 31 | 32 | The second is that OpenSSL is the only library I have found that allows us to 33 | reconstruct an EC public key from its X/Y points or an RSA public key from its 34 | n/e for use with signature verification. 35 | Without this, we are not able to parse authenticator credentials to perform authentication. 36 | 37 | Resources 38 | --------- 39 | 40 | * Specification: https://www.w3.org/TR/webauthn-3 41 | * JSON details: https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html 42 | * Write up on interactions: https://medium.com/@herrjemand/introduction-to-webauthn-api-5fd1fb46c285 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo-wasm/src/utils.rs: -------------------------------------------------------------------------------- 1 | use gloo::console; 2 | use wasm_bindgen::JsCast; 3 | use web_sys::Clipboard; 4 | use web_sys::Document; 5 | use web_sys::Navigator; 6 | use web_sys::Window; 7 | 8 | #[allow(dead_code)] 9 | pub fn get_checked_from_element_id(id: &str) -> Option { 10 | document() 11 | .get_element_by_id(id) 12 | .and_then(|element| element.dyn_into::().ok()) 13 | .map(|element| element.checked()) 14 | } 15 | 16 | #[allow(dead_code)] 17 | pub fn get_select_value_from_element_id(id: &str) -> Option { 18 | document() 19 | .get_element_by_id(id) 20 | .and_then(|element| { 21 | console::log!("Into Dyn Options Collection."); 22 | element.dyn_into::().ok() 23 | }) 24 | .map(|element| element.value()) 25 | } 26 | 27 | #[allow(dead_code)] 28 | pub fn get_value_from_element_id(id: &str) -> Option { 29 | document() 30 | .get_element_by_id(id) 31 | .and_then(|element| element.dyn_into::().ok()) 32 | .map(|element| element.value()) 33 | } 34 | 35 | pub fn autofocus(id: &str) { 36 | // Once rendered if an element with id autofocus exists, focus it. 37 | let doc = document(); 38 | if let Some(element) = doc.get_element_by_id(id) { 39 | if let Ok(htmlelement) = element.dyn_into::() { 40 | if htmlelement.focus().is_err() { 41 | console::log!("unable to autofocus."); 42 | } 43 | } 44 | } 45 | } 46 | 47 | pub fn document() -> Document { 48 | window().document().expect("Unable to retrieve document") 49 | } 50 | 51 | pub fn window() -> Window { 52 | web_sys::window().expect("Unable to retrieve window") 53 | } 54 | 55 | pub fn navigator() -> Navigator { 56 | window().navigator() 57 | } 58 | 59 | pub fn clipboard() -> Clipboard { 60 | navigator().clipboard().expect("Unable to access clipboard") 61 | } 62 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/README.md: -------------------------------------------------------------------------------- 1 | # webauthn-rs-demo 2 | 3 | This is the demo site which powers https://webauthn.firstyear.id.au/ 4 | 5 | ## Running it locally 6 | 7 | ``` 8 | cargo run -- 9 | ``` 10 | 11 | Then navigate to "http://localhost:8080/" as the server prints out. 12 | 13 | ### HTTPS/TLS support 14 | 15 | TLS support is [enabled by default][0] with the `tls` feature. You can _disable_ 16 | it with `--no-default-features`. 17 | 18 | [0]: https://doc.rust-lang.org/cargo/reference/features.html#the-default-feature 19 | 20 | Provide the TLS public and private keys in PEM format, and specify an Origin 21 | (`--origin`) and relying party ID (`--id`): 22 | 23 | ```sh 24 | cargo run -- \ 25 | --bind 192.0.2.1:443 \ 26 | --tls-public-key /etc/ssl/certs/demo.example.com.pem \ 27 | --tls-private-key /etc/ssl/certs/demo.example.com.key \ 28 | --origin https://demo.example.com \ 29 | --id demo.example.com 30 | ``` 31 | 32 | If you're testing locally, you can build a short-lived self-signed certificate 33 | (which won't be trusted by browsers) with `openssl`: 34 | 35 | ```sh 36 | openssl genrsa -out /tmp/demo.key 37 | openssl req -new -x509 -key /tmp/demo.key -out /tmp/demo.pem -days 5 -subj "/CN=localhost/" -addext "subjectAltName = DNS:localhost" 38 | ``` 39 | 40 | Configuring and managing certificates properly is outside the scope of this 41 | document. :) 42 | 43 | ## Troubleshooting 44 | 45 | If your system can't find `localhost`, this could be a failure in name 46 | resolution. You should check your system's `/etc/hosts` file for this. 47 | 48 | If you navigate to `http://127.0.0.1:8080/`, this example **WILL FAIL** as the 49 | Origin is set to `localhost`, not `127.0.0.1`. 50 | 51 | ## TODO 52 | 53 | * Improve the Javascript to use the username field correcly. 54 | * Make it prettier and sparkly. 55 | * Add cookie handling example. 56 | 57 | ## Building Yew: 58 | 59 | ```sh 60 | cargo install wasm-pack 61 | npm install --global rollup 62 | cd ../webauthn-rs-demo-wasm 63 | ./build_wasm.sh 64 | ``` 65 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/ctap2/solokey.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use uuid::Uuid; 3 | 4 | use crate::{ 5 | prelude::WebauthnCError, transport::solokey::SoloKeyToken, transport::Token, ui::UiCallback, 6 | }; 7 | 8 | use super::Ctap20Authenticator; 9 | 10 | /// SoloKey (Trussed) vendor-specific commands. 11 | /// 12 | /// ## Warning 13 | /// 14 | /// These commands currently operate on *any* [`Ctap20Authenticator`][], and do 15 | /// not filter to just SoloKey/Trussed devices. Due to the nature of CTAP 16 | /// vendor-specific commands, this may cause unexpected or undesirable behaviour 17 | /// on other vendors' keys. 18 | /// 19 | /// Protocol notes are in [`crate::transport::solokey`]. 20 | #[async_trait] 21 | pub trait SoloKeyAuthenticator { 22 | /// Gets a SoloKey's lock (secure boot) status. 23 | async fn get_solokey_lock(&mut self) -> Result; 24 | 25 | /// Gets some random bytes from a SoloKey. 26 | async fn get_solokey_random(&mut self) -> Result<[u8; 57], WebauthnCError>; 27 | 28 | /// Gets a SoloKey's UUID. 29 | async fn get_solokey_uuid(&mut self) -> Result; 30 | 31 | /// Gets a SoloKey's firmware version. 32 | async fn get_solokey_version(&mut self) -> Result; 33 | } 34 | 35 | #[async_trait] 36 | impl<'a, T: Token + SoloKeyToken, U: UiCallback> SoloKeyAuthenticator 37 | for Ctap20Authenticator<'a, T, U> 38 | { 39 | #[inline] 40 | async fn get_solokey_lock(&mut self) -> Result { 41 | self.token.get_solokey_lock().await 42 | } 43 | 44 | #[inline] 45 | async fn get_solokey_random(&mut self) -> Result<[u8; 57], WebauthnCError> { 46 | self.token.get_solokey_random().await 47 | } 48 | 49 | #[inline] 50 | async fn get_solokey_uuid(&mut self) -> Result { 51 | self.token.get_solokey_uuid().await 52 | } 53 | 54 | #[inline] 55 | async fn get_solokey_version(&mut self) -> Result { 56 | self.token.get_solokey_version().await 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/src/config.rs: -------------------------------------------------------------------------------- 1 | use url::Url; 2 | use webauthn_rs_core::proto::{ 3 | AttestationConveyancePreference, AuthenticatorAttachment, COSEAlgorithm, 4 | }; 5 | use webauthn_rs_core::WebauthnConfig; 6 | 7 | #[derive(Debug)] 8 | pub(crate) struct WebauthnRegistrationConfig { 9 | pub rp_name: String, 10 | pub rp_id: String, 11 | pub rp_origin: Url, 12 | pub attachment: Option, 13 | pub algorithms: Vec, 14 | pub attestation: AttestationConveyancePreference, 15 | } 16 | 17 | impl WebauthnConfig for WebauthnRegistrationConfig { 18 | fn get_relying_party_name(&self) -> &str { 19 | &self.rp_name 20 | } 21 | 22 | fn get_relying_party_id(&self) -> &str { 23 | &self.rp_id 24 | } 25 | 26 | fn get_origin(&self) -> &Url { 27 | &self.rp_origin 28 | } 29 | 30 | fn get_authenticator_attachment(&self) -> Option { 31 | self.attachment.clone() 32 | } 33 | 34 | fn get_attestation_preference(&self) -> AttestationConveyancePreference { 35 | self.attestation.clone() 36 | } 37 | 38 | fn get_credential_algorithms(&self) -> Vec { 39 | self.algorithms.clone() 40 | } 41 | } 42 | 43 | #[derive(Debug)] 44 | pub(crate) struct WebauthnAuthConfig { 45 | pub rp_name: String, 46 | pub rp_id: String, 47 | pub rp_origin: Url, 48 | } 49 | 50 | impl WebauthnConfig for WebauthnAuthConfig { 51 | fn get_relying_party_name(&self) -> &str { 52 | &self.rp_name 53 | } 54 | 55 | fn get_relying_party_id(&self) -> &str { 56 | &self.rp_id 57 | } 58 | 59 | fn get_origin(&self) -> &Url { 60 | &self.rp_origin 61 | } 62 | 63 | fn get_authenticator_attachment(&self) -> Option { 64 | unreachable!(); 65 | } 66 | 67 | fn get_attestation_preference(&self) -> AttestationConveyancePreference { 68 | unreachable!(); 69 | } 70 | 71 | fn get_credential_algorithms(&self) -> Vec { 72 | unreachable!(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /fido-hid-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `fido-hid-rs` implements a minimal set of platform-specific USB HID bindings 2 | //! for communicating with FIDO authenticators. 3 | //! 4 | //! **Important:** this library is an _internal implementation detail_ of 5 | //! [webauthn-authenticator-rs][0] to work around Cargo limitations. 6 | //! 7 | //! **This library has no guarantees of API stability, and is not intended for 8 | //! use by other parties.** 9 | //! 10 | //! If you want to interface with USB HID FIDO authenticators, use 11 | //! [webauthn-authenticator-rs][0] instead of this library. 12 | //! 13 | //! If you're looking for a general-purpose Rust USB HID library, try 14 | //! [hidapi][]. 15 | //! 16 | //! [0]: https://github.com/kanidm/webauthn-rs/tree/master/webauthn-authenticator-rs 17 | //! [hidapi]: https://docs.rs/hidapi/latest/hidapi/ 18 | #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))] 19 | compile_error!("USB support is not implemented on this platform"); 20 | 21 | #[cfg(target_os = "macos")] 22 | #[macro_use] 23 | extern crate core_foundation; 24 | 25 | #[cfg(target_os = "windows")] 26 | #[macro_use] 27 | extern crate lazy_static; 28 | 29 | #[macro_use] 30 | extern crate tracing; 31 | 32 | mod error; 33 | mod traits; 34 | 35 | #[cfg(any(test, target_os = "linux"))] 36 | mod descriptors; 37 | 38 | #[cfg_attr(target_os = "linux", path = "linux/mod.rs")] 39 | #[cfg_attr(target_os = "macos", path = "macos/mod.rs")] 40 | #[cfg_attr(target_os = "windows", path = "windows/mod.rs")] 41 | mod os; 42 | 43 | #[doc(inline)] 44 | pub use crate::{ 45 | error::{HidError, Result}, 46 | os::{USBDeviceImpl, USBDeviceInfoImpl, USBDeviceManagerImpl}, 47 | traits::{USBDevice, USBDeviceInfo, USBDeviceManager, WatchEvent}, 48 | }; 49 | 50 | // u2f_hid.h 51 | const FIDO_USAGE_PAGE: u16 = 0xf1d0; 52 | const FIDO_USAGE_U2FHID: u16 = 0x01; 53 | const HID_RPT_SIZE: usize = 64; 54 | const HID_RPT_SEND_SIZE: usize = HID_RPT_SIZE + 1; 55 | #[allow(dead_code)] 56 | const U2FHID_TRANS_TIMEOUT: i32 = 3000; 57 | 58 | pub type HidReportBytes = [u8; HID_RPT_SIZE]; 59 | pub type HidSendReportBytes = [u8; HID_RPT_SEND_SIZE]; 60 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/win10/user.rs: -------------------------------------------------------------------------------- 1 | //! Wrappers for [User]. 2 | use std::pin::Pin; 3 | 4 | use webauthn_rs_proto::User; 5 | use windows::{ 6 | core::{HSTRING, PCWSTR}, 7 | Win32::Networking::WindowsWebServices::{ 8 | WEBAUTHN_USER_ENTITY_INFORMATION, WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION, 9 | }, 10 | }; 11 | 12 | use super::WinWrapper; 13 | use crate::error::WebauthnCError; 14 | 15 | /// Wrapper for [WEBAUTHN_USER_ENTITY_INFORMATION] to ensure pointer lifetime, analgous to [User]. 16 | pub struct WinUserEntityInformation { 17 | native: WEBAUTHN_USER_ENTITY_INFORMATION, 18 | _id: Vec, 19 | _name: HSTRING, 20 | _display_name: HSTRING, 21 | } 22 | 23 | impl WinWrapper for WinUserEntityInformation { 24 | type NativeType = WEBAUTHN_USER_ENTITY_INFORMATION; 25 | fn new(u: User) -> Result>, WebauthnCError> { 26 | // Construct an incomplete type first, so that all the pointers are fixed. 27 | let res = Self { 28 | native: WEBAUTHN_USER_ENTITY_INFORMATION::default(), 29 | _id: u.id.into(), 30 | _name: u.name.into(), 31 | _display_name: u.display_name.into(), 32 | }; 33 | 34 | let mut boxed = Box::pin(res); 35 | 36 | // Create the real native type, which contains bare pointers. 37 | let native = WEBAUTHN_USER_ENTITY_INFORMATION { 38 | dwVersion: WEBAUTHN_USER_ENTITY_INFORMATION_CURRENT_VERSION, 39 | cbId: boxed._id.len() as u32, 40 | pbId: boxed._id.as_ptr() as *mut _, 41 | pwszName: (&boxed._name).into(), 42 | pwszIcon: PCWSTR::null(), 43 | pwszDisplayName: (&boxed._display_name).into(), 44 | }; 45 | 46 | // Update the boxed type with the proper native object. 47 | unsafe { 48 | let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed); 49 | Pin::get_unchecked_mut(mut_ref).native = native; 50 | } 51 | 52 | Ok(boxed) 53 | } 54 | 55 | fn native_ptr(&self) -> &WEBAUTHN_USER_ENTITY_INFORMATION { 56 | &self.native 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tutorial/server/axum/src/startup.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | use tokio::sync::Mutex; 4 | use webauthn_rs::prelude::*; 5 | 6 | /* 7 | * Webauthn RS server side app state and setup code. 8 | */ 9 | 10 | // Configure the Webauthn instance by using the WebauthnBuilder. This defines 11 | // the options needed for your site, and has some implications. One of these is that 12 | // you can NOT change your rp_id (relying party id), without invalidating all 13 | // webauthn credentials. Remember, rp_id is derived from your URL origin, meaning 14 | // that it is your effective domain name. 15 | 16 | pub struct Data { 17 | pub name_to_id: HashMap, 18 | pub keys: HashMap>, 19 | } 20 | 21 | #[derive(Clone)] 22 | pub struct AppState { 23 | // Webauthn has no mutable inner state, so Arc and read only is sufficent. 24 | // Alternately, you could use a reference here provided you can work out 25 | // lifetimes. 26 | pub webauthn: Arc, 27 | // This needs mutability, so does require a mutex. 28 | pub users: Arc>, 29 | } 30 | 31 | impl AppState { 32 | pub fn new() -> Self { 33 | // Effective domain name. 34 | let rp_id = "localhost"; 35 | // Url containing the effective domain name 36 | // MUST include the port number! 37 | let rp_origin = Url::parse("http://localhost:8080").expect("Invalid URL"); 38 | let builder = WebauthnBuilder::new(rp_id, &rp_origin).expect("Invalid configuration"); 39 | 40 | // Now, with the builder you can define other options. 41 | // Set a "nice" relying party name. Has no security properties and 42 | // may be changed in the future. 43 | let builder = builder.rp_name("Axum Webauthn-rs"); 44 | 45 | // Consume the builder and create our webauthn instance. 46 | let webauthn = Arc::new(builder.build().expect("Invalid configuration")); 47 | 48 | let users = Arc::new(Mutex::new(Data { 49 | name_to_id: HashMap::new(), 50 | keys: HashMap::new(), 51 | })); 52 | 53 | AppState { webauthn, users } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /webauthn-rs-core/src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! cbor_try_map { 2 | ( 3 | $v:expr 4 | ) => {{ 5 | match $v { 6 | serde_cbor_2::Value::Map(m) => Ok(m), 7 | _ => Err(WebauthnError::COSEKeyInvalidCBORValue), 8 | } 9 | }}; 10 | } 11 | 12 | macro_rules! cbor_try_array { 13 | ( 14 | $v:expr 15 | ) => {{ 16 | match $v { 17 | serde_cbor_2::Value::Array(m) => Ok(m), 18 | _ => Err(WebauthnError::COSEKeyInvalidCBORValue), 19 | } 20 | }}; 21 | } 22 | 23 | macro_rules! cbor_try_string { 24 | ( 25 | $v:expr 26 | ) => {{ 27 | match $v { 28 | serde_cbor_2::Value::Text(m) => Ok(m), 29 | _ => Err(WebauthnError::COSEKeyInvalidCBORValue), 30 | } 31 | }}; 32 | } 33 | 34 | macro_rules! cbor_try_bytes { 35 | ( 36 | $v:expr 37 | ) => {{ 38 | match $v { 39 | serde_cbor_2::Value::Bytes(m) => Ok(m), 40 | _ => Err(WebauthnError::COSEKeyInvalidCBORValue), 41 | } 42 | }}; 43 | } 44 | 45 | macro_rules! cbor_try_i128 { 46 | ( 47 | $v:expr 48 | ) => {{ 49 | match $v { 50 | serde_cbor_2::Value::Integer(m) => Ok(*m), 51 | _ => Err(WebauthnError::COSEKeyInvalidCBORValue), 52 | } 53 | }}; 54 | } 55 | 56 | /* 57 | #[macro_export] 58 | macro_rules! cbor_try_u64 { 59 | ( 60 | $v:expr 61 | ) => {{ 62 | match $v { 63 | serde_cbor_2::Value::Integer(m) => 64 | u64::try_from(m) 65 | .map_err(|_| WebauthnError::COSEKeyInvalidCBORValue), 66 | Ok(m), 67 | _ => Err(WebauthnError::COSEKeyInvalidCBORValue), 68 | } 69 | }} 70 | } 71 | 72 | #[macro_export] 73 | macro_rules! cbor_try_i64 { 74 | ( 75 | $v:expr 76 | ) => {{ 77 | match $v { 78 | serde_cbor_2::Value::Integer(m) => 79 | i64::try_from(m) 80 | .map_err(|_| WebauthnError::COSEKeyInvalidCBORValue), 81 | Ok(m), 82 | _ => Err(WebauthnError::COSEKeyInvalidCBORValue), 83 | } 84 | }} 85 | } 86 | */ 87 | -------------------------------------------------------------------------------- /webauthn-rp-proxy/tests/data/authenticate-finish.json: -------------------------------------------------------------------------------- 1 | { 2 | "passkey_authentication": { 3 | "ast": { 4 | "credentials": [ 5 | { 6 | "cred_id": "Adabs0P_0SZOJeDPZPiQGrJO6HpW57j90cTSNvLQg06kXEIcXHXbKPzqPhusFFdRUqhqzcfYFpc81GEDhm2m0qs", 7 | "cred": { 8 | "type_": "ES256", 9 | "key": { 10 | "EC_EC2": { 11 | "curve": "SECP256R1", 12 | "x": "jPJ21rAQk1W1Sn8hNafIKmId9iL1VYPBmg-st-BcCHg", 13 | "y": "WEYHf4kAMsjLXoWdxWx9t-EbNSgUAg7RG_-4BfEYtJw" 14 | } 15 | } 16 | }, 17 | "counter": 0, 18 | "transports": null, 19 | "user_verified": true, 20 | "backup_eligible": false, 21 | "backup_state": false, 22 | "registration_policy": "required", 23 | "extensions": { 24 | "cred_protect": "Ignored", 25 | "hmac_create_secret": "NotRequested", 26 | "appid": "NotRequested", 27 | "cred_props": "Ignored" 28 | }, 29 | "attestation": { 30 | "data": "None", 31 | "metadata": "None" 32 | }, 33 | "attestation_format": "None" 34 | } 35 | ], 36 | "policy": "required", 37 | "challenge": "3Wp8Oqum3-zvB9eStp6YuUQTIVzf_b6TIh_t2wbCuAw", 38 | "appid": null, 39 | "allow_backup_eligible_upgrade": true 40 | } 41 | }, 42 | "public_key_credential": { 43 | "id": "Adabs0P_0SZOJeDPZPiQGrJO6HpW57j90cTSNvLQg06kXEIcXHXbKPzqPhusFFdRUqhqzcfYFpc81GEDhm2m0qs", 44 | "rawId": "Adabs0P_0SZOJeDPZPiQGrJO6HpW57j90cTSNvLQg06kXEIcXHXbKPzqPhusFFdRUqhqzcfYFpc81GEDhm2m0qs", 45 | "response": { 46 | "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAQ", 47 | "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiM1dwOE9xdW0zLXp2QjllU3RwNll1VVFUSVZ6Zl9iNlRJaF90MndiQ3VBdyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0", 48 | "signature": "MEUCIQCJNAjFDXDse91FJt_nDPnYCYQamE7ndSVWAD-VjGn_QAIgQDDGNxQsIDNdgJX7tmok6oJqU-aC1tXdUrYN-Ykwmh8", 49 | "userHandle": "" 50 | }, 51 | "type": "public-key" 52 | }, 53 | "rp_id": "localhost", 54 | "rp_origin": "http://localhost:8443" 55 | } 56 | -------------------------------------------------------------------------------- /webauthn-rp-proxy/tests/data/authenticate-finish-fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "passkey_authentication": { 3 | "ast": { 4 | "NOT_CREDENTIALS": [ 5 | { 6 | "cred_id": "Adabs0P_0SZOJeDPZPiQGrJO6HpW57j90cTSNvLQg06kXEIcXHXbKPzqPhusFFdRUqhqzcfYFpc81GEDhm2m0qs", 7 | "cred": { 8 | "type_": "ES256", 9 | "key": { 10 | "EC_EC2": { 11 | "curve": "SECP256R1", 12 | "x": "jPJ21rAQk1W1Sn8hNafIKmId9iL1VYPBmg-st-BcCHg", 13 | "y": "WEYHf4kAMsjLXoWdxWx9t-EbNSgUAg7RG_-4BfEYtJw" 14 | } 15 | } 16 | }, 17 | "counter": 0, 18 | "transports": null, 19 | "user_verified": true, 20 | "backup_eligible": false, 21 | "backup_state": false, 22 | "registration_policy": "required", 23 | "extensions": { 24 | "cred_protect": "Ignored", 25 | "hmac_create_secret": "NotRequested", 26 | "appid": "NotRequested", 27 | "cred_props": "Ignored" 28 | }, 29 | "attestation": { 30 | "data": "None", 31 | "metadata": "None" 32 | }, 33 | "attestation_format": "None" 34 | } 35 | ], 36 | "policy": "required", 37 | "challenge": "3Wp8Oqum3-zvB9eStp6YuUQTIVzf_b6TIh_t2wbCuAw", 38 | "appid": null, 39 | "allow_backup_eligible_upgrade": true 40 | } 41 | }, 42 | "public_key_credential": { 43 | "id": "Adabs0P_0SZOJeDPZPiQGrJO6HpW57j90cTSNvLQg06kXEIcXHXbKPzqPhusFFdRUqhqzcfYFpc81GEDhm2m0qs", 44 | "rawId": "Adabs0P_0SZOJeDPZPiQGrJO6HpW57j90cTSNvLQg06kXEIcXHXbKPzqPhusFFdRUqhqzcfYFpc81GEDhm2m0qs", 45 | "response": { 46 | "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MFAAAAAQ", 47 | "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiM1dwOE9xdW0zLXp2QjllU3RwNll1VVFUSVZ6Zl9iNlRJaF90MndiQ3VBdyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0", 48 | "signature": "MEUCIQCJNAjFDXDse91FJt_nDPnYCYQamE7ndSVWAD-VjGn_QAIgQDDGNxQsIDNdgJX7tmok6oJqU-aC1tXdUrYN-Ykwmh8", 49 | "userHandle": "" 50 | }, 51 | "type": "public-key" 52 | }, 53 | "rp_id": "localhost", 54 | "rp_origin": "http://localhost:8443" 55 | } 56 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/win10/clientdata.rs: -------------------------------------------------------------------------------- 1 | //! Wrappers for [CollectedClientData]. 2 | use std::pin::Pin; 3 | use webauthn_rs_proto::CollectedClientData; 4 | 5 | use super::WinWrapper; 6 | use crate::error::WebauthnCError; 7 | 8 | use windows::{ 9 | core::HSTRING, 10 | w, 11 | Win32::Networking::WindowsWebServices::{ 12 | WEBAUTHN_CLIENT_DATA, WEBAUTHN_CLIENT_DATA_CURRENT_VERSION, 13 | }, 14 | }; 15 | // Most constants are `&str`, but APIs expect `HSTRING`... there's no good work-around. 16 | // https://github.com/microsoft/windows-rs/issues/2049 17 | /// [windows::Win32::Networking::WindowsWebServices::WEBAUTHN_HASH_ALGORITHM_SHA_256] 18 | const SHA_256: &HSTRING = w!("SHA-256"); 19 | 20 | /// Wrapper for [WEBAUTHN_CLIENT_DATA] to ensure pointer lifetime. 21 | pub struct WinClientData { 22 | native: WEBAUTHN_CLIENT_DATA, 23 | client_data_json: String, 24 | } 25 | 26 | impl WinClientData { 27 | pub fn client_data_json(&self) -> &String { 28 | &self.client_data_json 29 | } 30 | } 31 | 32 | impl WinWrapper for WinClientData { 33 | type NativeType = WEBAUTHN_CLIENT_DATA; 34 | fn new(clientdata: CollectedClientData) -> Result>, WebauthnCError> { 35 | // Construct an incomplete type first, so that all the pointers are fixed. 36 | let res = Self { 37 | native: WEBAUTHN_CLIENT_DATA::default(), 38 | client_data_json: serde_json::to_string(&clientdata) 39 | .map_err(|_| WebauthnCError::Json)?, 40 | }; 41 | 42 | let mut boxed = Box::pin(res); 43 | 44 | // Create the real native type, which contains bare pointers. 45 | let native = WEBAUTHN_CLIENT_DATA { 46 | dwVersion: WEBAUTHN_CLIENT_DATA_CURRENT_VERSION, 47 | cbClientDataJSON: boxed.client_data_json.len() as u32, 48 | pbClientDataJSON: boxed.client_data_json.as_ptr() as *mut _, 49 | pwszHashAlgId: SHA_256.into(), 50 | }; 51 | 52 | // Update the boxed type with the proper native object. 53 | unsafe { 54 | let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed); 55 | Pin::get_unchecked_mut(mut_ref).native = native; 56 | } 57 | 58 | Ok(boxed) 59 | } 60 | 61 | fn native_ptr(&self) -> &WEBAUTHN_CLIENT_DATA { 62 | &self.native 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /webauthn-rs-proto/src/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::attest::RegisterPublicKeyCredential; 2 | 3 | #[test] 4 | fn parse_cred_props() { 5 | // Old format (extensions) 6 | let rsp_d: RegisterPublicKeyCredential = serde_json::from_str(r#"{ 7 | "id": "AfJfonHsXY_f7_gFmV1dI473Ce--_g0tHhdXUoh7JmMn0gzhYUtU9bFqpCgSljjwJxEXkjzb-11ulePZyI0RiyQ", 8 | "rawId": "AfJfonHsXY_f7_gFmV1dI473Ce--_g0tHhdXUoh7JmMn0gzhYUtU9bFqpCgSljjwJxEXkjzb-11ulePZyI0RiyQ", 9 | "response": { 10 | "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFarm78N-aFvkduzO7sTL6-dF8eCxIJsbscOzuWNl-9SpFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHyX6Jx7F2P3-_4BZldXSOO9wnvvv4NLR4XV1KIeyZjJ9IM4WFLVPWxaqQoEpY48CcRF5I82_tdbpXj2ciNEYskpQECAyYgASFYIE_9awy66uhXZ6hIzPAW2AzIrTMZ7kyC2jtZe0zuH_pOIlggFbNKhOSt8-prIx0snKRqcxULtc2u1rzUUf47g1PxTcU", 11 | "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidF9XZTEzMU5wd2xseVBMMHgyNmJ6WmdrRjVmX1h2QTdPY2I0Yjk4emx4TSIsIm9yaWdpbiI6Imh0dHBzOlwvXC93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJvcmcubW96aWxsYS5maXJlZm94In0" 12 | }, 13 | "type": "public-key", 14 | "extensions": {"credProps":{"rk":false}} 15 | }"#).unwrap(); 16 | 17 | assert!(rsp_d.extensions.cred_props.is_some()); 18 | 19 | // mdn compat format (clientExtensionResults) 20 | let _rsp_d: RegisterPublicKeyCredential = serde_json::from_str(r#"{ 21 | "id": "AfJfonHsXY_f7_gFmV1dI473Ce--_g0tHhdXUoh7JmMn0gzhYUtU9bFqpCgSljjwJxEXkjzb-11ulePZyI0RiyQ", 22 | "rawId": "AfJfonHsXY_f7_gFmV1dI473Ce--_g0tHhdXUoh7JmMn0gzhYUtU9bFqpCgSljjwJxEXkjzb-11ulePZyI0RiyQ", 23 | "response": { 24 | "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFarm78N-aFvkduzO7sTL6-dF8eCxIJsbscOzuWNl-9SpFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQHyX6Jx7F2P3-_4BZldXSOO9wnvvv4NLR4XV1KIeyZjJ9IM4WFLVPWxaqQoEpY48CcRF5I82_tdbpXj2ciNEYskpQECAyYgASFYIE_9awy66uhXZ6hIzPAW2AzIrTMZ7kyC2jtZe0zuH_pOIlggFbNKhOSt8-prIx0snKRqcxULtc2u1rzUUf47g1PxTcU", 25 | "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoidF9XZTEzMU5wd2xseVBMMHgyNmJ6WmdrRjVmX1h2QTdPY2I0Yjk4emx4TSIsIm9yaWdpbiI6Imh0dHBzOlwvXC93ZWJhdXRobi5maXJzdHllYXIuaWQuYXUiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJvcmcubW96aWxsYS5maXJlZm94In0" 26 | }, 27 | "type": "public-key", 28 | "clientExtensionResults": {"credProps":{"rk":false}} 29 | }"#).unwrap(); 30 | 31 | assert!(rsp_d.extensions.cred_props.is_some()); 32 | } 33 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/types.rs: -------------------------------------------------------------------------------- 1 | //! Types used in a public API. 2 | //! 3 | //! These types need to be present regardless of which features were selected 4 | //! at build time, because they are part of some other API which doesn't change. 5 | 6 | /// caBLE request type. 7 | #[derive(Debug, PartialEq, Eq, Clone, Default, Copy)] 8 | pub enum CableRequestType { 9 | /// Logging in with an existing credential. 10 | #[default] 11 | GetAssertion, 12 | 13 | /// Creating a new, non-discoverable credential. 14 | MakeCredential, 15 | 16 | /// Creating a new, discoverable credential. 17 | DiscoverableMakeCredential, 18 | } 19 | 20 | /// States that a caBLE connection can be in for 21 | /// [crate::ui::UiCallback::cable_status_update]. 22 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 23 | pub enum CableState { 24 | /// The initiator or authenticator is connecting to the tunnel server. 25 | ConnectingToTunnelServer, 26 | 27 | /// The authenticator is waiting for the initiator to connect to the tunnel 28 | /// server, and send a challenge. 29 | WaitingForInitiatorConnection, 30 | 31 | /// The initiator or authenticator is establishing an encrypted channel. 32 | Handshaking, 33 | 34 | /// The authenticator is waiting for the initiator to respond. 35 | WaitingForInitiatorResponse, 36 | 37 | /// The authenticator is waiting for a command from the initiator. 38 | WaitingForInitiatorCommand, 39 | 40 | /// The initiator or authenticator is processing what it received from the 41 | /// other side. 42 | Processing, 43 | 44 | /// The initiator has sent a message to the authenticator, and waiting for 45 | /// a response. This may be that the device is waiting for some sort of 46 | /// user verification action (like entering PIN or biometrics) to complete 47 | /// the operation. 48 | WaitingForAuthenticatorResponse, 49 | } 50 | 51 | // lastEnrollSampleStatus 52 | #[derive(FromPrimitive, ToPrimitive, Debug, PartialEq, Eq)] 53 | #[repr(u8)] 54 | pub enum EnrollSampleStatus { 55 | Good = 0x00, 56 | TooHigh = 0x01, 57 | TooLow = 0x02, 58 | TooLeft = 0x03, 59 | TooRight = 0x04, 60 | TooFast = 0x05, 61 | TooSlow = 0x06, 62 | PoorQuality = 0x07, 63 | TooSkewed = 0x08, 64 | TooShort = 0x09, 65 | MergeFailure = 0x0a, 66 | AlreadyExists = 0x0b, 67 | // 0x0c unused 68 | NoUserActivity = 0x0d, 69 | NoUserPresenceTransition = 0x0e, 70 | } 71 | -------------------------------------------------------------------------------- /device-catalog/src/data/microsoft.rs: -------------------------------------------------------------------------------- 1 | // https://docs.microsoft.com/en-us/windows-server/security/guarded-fabric-shielded-vm/guarded-fabric-install-trusted-tpm-root-certificates 2 | pub const MICROSOFT_TPM_ROOT_CERTIFICATE_AUTHORITY_2014_PEM: &[u8] = b"-----BEGIN CERTIFICATE----- 3 | MIIF9TCCA92gAwIBAgIQXbYwTgy/J79JuMhpUB5dyzANBgkqhkiG9w0BAQsFADCB 4 | jDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl 5 | ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjE2MDQGA1UEAxMt 6 | TWljcm9zb2Z0IFRQTSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDE0MB4X 7 | DTE0MTIxMDIxMzExOVoXDTM5MTIxMDIxMzkyOFowgYwxCzAJBgNVBAYTAlVTMRMw 8 | EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN 9 | aWNyb3NvZnQgQ29ycG9yYXRpb24xNjA0BgNVBAMTLU1pY3Jvc29mdCBUUE0gUm9v 10 | dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxNDCCAiIwDQYJKoZIhvcNAQEBBQAD 11 | ggIPADCCAgoCggIBAJ+n+bnKt/JHIRC/oI/xgkgsYdPzP0gpvduDA2GbRtth+L4W 12 | UyoZKGBw7uz5bjjP8Aql4YExyjR3EZQ4LqnZChMpoCofbeDR4MjCE1TGwWghGpS0 13 | mM3GtWD9XiME4rE2K0VW3pdN0CLzkYbvZbs2wQTFfE62yNQiDjyHFWAZ4BQH4eWa 14 | 8wrDMUxIAneUCpU6zCwM+l6Qh4ohX063BHzXlTSTc1fDsiPaKuMMjWjK9vp5UHFP 15 | a+dMAWr6OljQZPFIg3aZ4cUfzS9y+n77Hs1NXPBn6E4Db679z4DThIXyoKeZTv1a 16 | aWOWl/exsDLGt2mTMTyykVV8uD1eRjYriFpmoRDwJKAEMOfaURarzp7hka9TOElG 17 | yD2gOV4Fscr2MxAYCywLmOLzA4VDSYLuKAhPSp7yawET30AvY1HRfMwBxetSqWP2 18 | +yZRNYJlHpor5QTuRDgzR+Zej+aWx6rWNYx43kLthozeVJ3QCsD5iEI/OZlmWn5W 19 | Yf7O8LB/1A7scrYv44FD8ck3Z+hxXpkklAsjJMsHZa9mBqh+VR1AicX4uZG8m16x 20 | 65ZU2uUpBa3rn8CTNmw17ZHOiuSWJtS9+PrZVA8ljgf4QgA1g6NPOEiLG2fn8Gm+ 21 | r5Ak+9tqv72KDd2FPBJ7Xx4stYj/WjNPtEUhW4rcLK3ktLfcy6ea7Rocw5y5AgMB 22 | AAGjUTBPMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR6 23 | jArOL0hiF+KU0a5VwVLscXSkVjAQBgkrBgEEAYI3FQEEAwIBADANBgkqhkiG9w0B 24 | AQsFAAOCAgEAW4ioo1+J9VWC0UntSBXcXRm1ePTVamtsxVy/GpP4EmJd3Ub53JzN 25 | BfYdgfUL51CppS3ZY6BoagB+DqoA2GbSL+7sFGHBl5ka6FNelrwsH6VVw4xV/8kl 26 | IjmqOyfatPYsz0sUdZev+reeiGpKVoXrK6BDnUU27/mgPtem5YKWvHB/soofUrLK 27 | zZV3WfGdx9zBr8V0xW6vO3CKaqkqU9y6EsQw34n7eJCbEVVQ8VdFd9iV1pmXwaBA 28 | fBwkviPTKEP9Cm+zbFIOLr3V3CL9hJj+gkTUuXWlJJ6wVXEG5i4rIbLAV59UrW4L 29 | onP+seqvWMJYUFxu/niF0R3fSGM+NU11DtBVkhRZt1u0kFhZqjDz1dWyfT/N7Hke 30 | 3WsDqUFsBi+8SEw90rWx2aUkLvKo83oU4Mx4na+2I3l9F2a2VNGk4K7l3a00g51m 31 | iPiq0Da0jqw30PaLluTMTGY5+RnZVh50JD6nk+Ea3wRkU8aiYFnpIxfKBZ72whmY 32 | Ya/egj9IKeqpR0vuLebbU0fJBf880K1jWD3Z5SFyJXo057Mv0OPw5mttytE585ZI 33 | y5JsaRXlsOoWGRXE3kUT/MKR1UoAgR54c8Bsh+9Dq2wqIK9mRn15zvBDeyHG6+cz 34 | urLopziOUeWokxZN1syrEdKlhFoPYavm6t+PzIcpdxZwHA+V3jLJPfI= 35 | -----END CERTIFICATE-----"; 36 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/ctap2/ctap21pre.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use crate::{transport::Token, ui::UiCallback}; 4 | 5 | use super::{ 6 | commands::GetInfoResponse, ctap21_bio::BiometricAuthenticatorInfo, 7 | ctap21_cred::CredentialManagementAuthenticatorInfo, internal::CtapAuthenticatorVersion, 8 | Ctap20Authenticator, 9 | }; 10 | 11 | #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))] 12 | use super::commands::{PrototypeBioEnrollmentRequest, PrototypeCredentialManagementRequest}; 13 | 14 | /// CTAP 2.1-PRE protocol implementation. 15 | /// 16 | /// This contains only CTAP 2.1-PRE-specific functionality. All CTAP 2.0 17 | /// functionality is avaliable via a [Deref] to [Ctap20Authenticator]. 18 | #[derive(Debug)] 19 | pub struct Ctap21PreAuthenticator<'a, T: Token, U: UiCallback> { 20 | authenticator: Ctap20Authenticator<'a, T, U>, 21 | } 22 | 23 | /// For backwards compatibility, pretend to be a 24 | /// [CTAP 2.0 authenticator][Ctap20Authenticator]. 25 | impl<'a, T: Token, U: UiCallback> Deref for Ctap21PreAuthenticator<'a, T, U> { 26 | type Target = Ctap20Authenticator<'a, T, U>; 27 | 28 | fn deref(&self) -> &Self::Target { 29 | &self.authenticator 30 | } 31 | } 32 | 33 | impl DerefMut for Ctap21PreAuthenticator<'_, T, U> { 34 | fn deref_mut(&mut self) -> &mut Self::Target { 35 | &mut self.authenticator 36 | } 37 | } 38 | 39 | impl<'a, T: Token, U: UiCallback> CtapAuthenticatorVersion<'a, T, U> 40 | for Ctap21PreAuthenticator<'a, T, U> 41 | { 42 | const VERSION: &'static str = "FIDO_2_1_PRE"; 43 | fn new_with_info(info: GetInfoResponse, token: T, ui_callback: &'a U) -> Self { 44 | Self { 45 | authenticator: Ctap20Authenticator::new_with_info(info, token, ui_callback), 46 | } 47 | } 48 | } 49 | 50 | impl BiometricAuthenticatorInfo for Ctap21PreAuthenticator<'_, T, U> { 51 | #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))] 52 | type RequestType = PrototypeBioEnrollmentRequest; 53 | 54 | #[inline] 55 | fn biometrics(&self) -> Option { 56 | self.info.ctap21pre_biometrics() 57 | } 58 | } 59 | 60 | impl CredentialManagementAuthenticatorInfo 61 | for Ctap21PreAuthenticator<'_, T, U> 62 | { 63 | #[cfg(any(all(doc, not(doctest)), feature = "ctap2-management"))] 64 | type RequestType = PrototypeCredentialManagementRequest; 65 | 66 | #[inline] 67 | fn supports_credential_management(&self) -> bool { 68 | self.info.ctap21pre_credential_management() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /authenticator-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Subcommand; 2 | 3 | use authenticator::{ 4 | authenticatorservice::{AuthenticatorService, CtapVersion}, 5 | statecallback::StateCallback, 6 | InfoResult, StatusUpdate, 7 | }; 8 | use std::sync::mpsc::{channel, RecvError}; 9 | use std::thread; 10 | use tracing::{debug, error, info, level_filters::LevelFilter, trace}; 11 | use tracing_subscriber::EnvFilter; 12 | 13 | #[derive(Debug, Subcommand)] 14 | #[clap(about = "Authenticator Utility")] 15 | enum Opt { 16 | List, 17 | } 18 | 19 | fn main() { 20 | tracing_subscriber::fmt() 21 | .with_env_filter( 22 | EnvFilter::builder() 23 | .with_default_directive(LevelFilter::DEBUG.into()) 24 | .from_env_lossy(), 25 | ) 26 | .compact() 27 | .init(); 28 | 29 | info!("Starting..."); 30 | let timeout_ms = 25000; 31 | 32 | let mut manager = AuthenticatorService::new(CtapVersion::CTAP2) 33 | .expect("The auth service should initialize safely"); 34 | 35 | // Later we need to add common options for transports to consume. 36 | manager.add_u2f_usb_hid_platform_transports(); 37 | 38 | let (status_tx, status_rx) = channel::(); 39 | thread::spawn(move || loop { 40 | match status_rx.recv() { 41 | Ok(StatusUpdate::DeviceAvailable { dev_info }) => { 42 | trace!("STATUS: device available: {}", dev_info) 43 | } 44 | Ok(StatusUpdate::SelectDeviceNotice) => { 45 | info!("STATUS: Please select a device by touching one of them."); 46 | } 47 | Ok(StatusUpdate::DeviceSelected(dev_info)) => { 48 | debug!("STATUS: Continuing with device: {}", dev_info); 49 | } 50 | Err(RecvError) => { 51 | error!("STATUS: end"); 52 | return; 53 | } 54 | e => { 55 | error!("Unexpected State {:?}", e); 56 | } 57 | } 58 | }); 59 | 60 | let (register_tx, register_rx) = channel(); 61 | let callback = StateCallback::new(Box::new(move |rv| { 62 | register_tx.send(rv).unwrap(); 63 | })); 64 | 65 | if let Err(e) = manager.info(timeout_ms, status_tx, callback) { 66 | error!("Couldn't setup info request - {:?}", e); 67 | } 68 | 69 | while let Ok(info_result) = register_rx.recv() { 70 | match info_result { 71 | Ok(InfoResult::CTAP2(info)) => { 72 | info!("{}", info); 73 | } 74 | Err(e) => { 75 | error!("An error occured: {:?}", e); 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /fido-hid-rs/src/traits.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use futures::stream::BoxStream; 3 | use std::fmt::Debug; 4 | 5 | use crate::{HidReportBytes, HidSendReportBytes, Result}; 6 | 7 | /// Platform-specific USB device manager. 8 | #[async_trait] 9 | pub trait USBDeviceManager: Sized { 10 | /// The type used for USB device connections on this platform. 11 | type Device: USBDevice; 12 | /// The type used for USB device information produced on this platform. 13 | type DeviceInfo: USBDeviceInfo; 14 | /// The type used for USB device IDs on this platform. 15 | type DeviceId: Debug; 16 | 17 | /// Instantiates a new [USBDeviceManager] for this platform. 18 | async fn new() -> Result; 19 | 20 | /// Watches for USB authenticator device connection and disconnection events 21 | /// until the resulting stream is dropped. 22 | /// 23 | /// This method fires [`WatchEvent::Added`] events for any USB devices 24 | /// *already* connected, followed by [`WatchEvent::EnumerationComplete`]. 25 | async fn watch_devices(&self) -> Result>>; 26 | 27 | /// Gets a list of currently-connected USB authenticators. 28 | async fn get_devices(&self) -> Result>; 29 | } 30 | 31 | #[derive(Clone, Debug)] 32 | pub enum WatchEvent 33 | where 34 | T: USBDeviceInfo, 35 | { 36 | /// A new device was connected. 37 | Added(T), 38 | /// An existing device was disconnected. 39 | Removed(T::Id), 40 | /// Initial enumeration of existing devices completed. 41 | EnumerationComplete, 42 | } 43 | 44 | /// Platform-specific USB device info structure. 45 | #[async_trait] 46 | pub trait USBDeviceInfo: Clone + Debug + Send { 47 | /// The type used for USB device connections on this platform. 48 | type Device: USBDevice; 49 | 50 | /// The type used for USB device identifiers on this platform. 51 | type Id: Clone + Debug + Send; 52 | 53 | /// Opens a connection to this USB device. 54 | async fn open(self) -> Result; 55 | } 56 | 57 | /// Platform-specific USB device connection structure. 58 | #[async_trait] 59 | pub trait USBDevice: Send { 60 | /// The type used for USB device information on this platform. 61 | type Info: USBDeviceInfo; 62 | 63 | /// Gets the device info used to create this connection. 64 | fn get_info(&self) -> &Self::Info; 65 | 66 | /// Read some bytes from the FIDO device's HID input report descriptor. 67 | async fn read(&mut self) -> Result; 68 | 69 | /// Write some bytes to the FIDO device's HID output report descriptor. 70 | async fn write(&mut self, data: HidSendReportBytes) -> Result<()>; 71 | } 72 | -------------------------------------------------------------------------------- /webauthn-rp-proxy/tests/integration_test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use anyhow::{Error, Result}; 4 | use serde_json::Value; 5 | use std::fs::File; 6 | use std::process::{Command, Stdio}; 7 | 8 | fn run(subcommand: &str, input_file: &str) -> Result { 9 | let output = Command::new("cargo") 10 | .arg("run") 11 | .arg("--") 12 | .arg("--pretty-print") 13 | .arg(subcommand) 14 | .stdin(Stdio::from(File::open(input_file)?)) 15 | .output() 16 | .expect("failed to execute process"); 17 | 18 | Ok(serde_json::from_str( 19 | String::from_utf8_lossy(&output.stdout).as_ref(), 20 | )?) 21 | } 22 | 23 | #[test] 24 | fn test_register_start() -> Result<(), Error> { 25 | let v = run("register-start", "tests/data/register-start.json")?; 26 | 27 | assert!(v.get("error").is_none()); 28 | assert!(v["client"].is_object()); 29 | assert!(v["client"]["publicKey"].is_object()); 30 | assert!(v["server"].is_object()); 31 | assert!(v["server"]["rs"]["challenge"].is_string()); 32 | Ok(()) 33 | } 34 | 35 | #[test] 36 | fn test_register_finish() -> Result<(), Error> { 37 | let v = run("register-finish", "tests/data/register-finish.json")?; 38 | 39 | assert!(v.get("client").is_none()); 40 | assert!(v.get("error").is_none()); 41 | assert!(v["server"].is_object()); 42 | assert!(v["server"]["cred"]["cred_id"].is_string()); 43 | Ok(()) 44 | } 45 | 46 | #[test] 47 | fn test_authenticate_start() -> Result<(), Error> { 48 | let v = run("authenticate-start", "tests/data/authenticate-start.json")?; 49 | 50 | assert!(v.get("error").is_none()); 51 | assert!(v["client"].is_object()); 52 | assert!(v["client"]["publicKey"].is_object()); 53 | assert!(v["server"].is_object()); 54 | assert!(v["server"]["ast"]["credentials"].is_array()); 55 | Ok(()) 56 | } 57 | 58 | #[test] 59 | fn test_authenticate_finish() -> Result<(), Error> { 60 | let v = run("authenticate-finish", "tests/data/authenticate-finish.json")?; 61 | 62 | assert!(v.get("client").is_none()); 63 | assert!(v.get("error").is_none()); 64 | assert!(v["server"].is_object()); 65 | assert!(v["server"]["cred_id"].is_string()); 66 | Ok(()) 67 | } 68 | 69 | #[test] 70 | fn test_authenticate_finish_fail() -> Result<(), Error> { 71 | let v = run( 72 | "authenticate-finish", 73 | "tests/data/authenticate-finish-fail.json", 74 | )?; 75 | 76 | assert_eq!( 77 | v["error"], 78 | "missing field `credentials` at line 40 column 5" 79 | ); 80 | Ok(()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/ui/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::ui::*; 2 | #[cfg(feature = "qrcode")] 3 | use qrcode::{render::unicode::Dense1x2, QrCode}; 4 | use std::io::{stderr, Write}; 5 | 6 | /// Basic CLI [UiCallback] implementation, available with `--features ui-cli`. 7 | /// 8 | /// This gets input from `stdin` and sends messages to `stderr`. 9 | /// 10 | /// This is only intended for testing, and doesn't implement much functionality 11 | /// (like localization). 12 | /// 13 | /// **Tip**: to get QR codes for `cable` authenticators, enable the `qrcode` 14 | /// feature. 15 | #[derive(Debug)] 16 | pub struct Cli {} 17 | 18 | impl UiCallback for Cli { 19 | fn request_pin(&self) -> Option { 20 | rpassword::prompt_password_stderr("Enter PIN: ").ok() 21 | } 22 | 23 | fn request_touch(&self) { 24 | let mut stderr = stderr(); 25 | writeln!(stderr, "Touch the authenticator").ok(); 26 | } 27 | 28 | fn processing(&self) { 29 | let mut stderr = stderr(); 30 | writeln!(stderr, "Processing...").ok(); 31 | } 32 | 33 | fn fingerprint_enrollment_feedback( 34 | &self, 35 | remaining_samples: u32, 36 | feedback: Option, 37 | ) { 38 | let mut stderr = stderr(); 39 | writeln!(stderr, "Need {remaining_samples} more sample(s)").ok(); 40 | if let Some(feedback) = feedback { 41 | writeln!(stderr, "Last impression was {feedback:?}").ok(); 42 | } 43 | } 44 | 45 | fn cable_qr_code(&self, request_type: CableRequestType, url: String) { 46 | match request_type { 47 | CableRequestType::DiscoverableMakeCredential | CableRequestType::MakeCredential => { 48 | println!("Scan the QR code with your mobile device to create a new credential with caBLE:"); 49 | } 50 | CableRequestType::GetAssertion => { 51 | println!("Scan the QR code with your mobile device to sign in with caBLE:"); 52 | } 53 | } 54 | println!("This feature requires Android with Google Play, or iOS 16 or later."); 55 | 56 | #[cfg(feature = "qrcode")] 57 | { 58 | let qr = QrCode::new(&url).expect("Could not create QR code"); 59 | 60 | let code = qr 61 | .render::() 62 | .dark_color(Dense1x2::Light) 63 | .light_color(Dense1x2::Dark) 64 | .build(); 65 | 66 | println!("{}", code); 67 | } 68 | 69 | #[cfg(not(feature = "qrcode"))] 70 | { 71 | println!("QR code support not available in this build!") 72 | } 73 | println!("{url}"); 74 | } 75 | 76 | fn dismiss_qr_code(&self) { 77 | println!("caBLE authenticator detected, connecting..."); 78 | } 79 | 80 | fn cable_status_update(&self, state: CableState) { 81 | println!("caBLE status: {state:?}"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo/templates/sandbox_uv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebAuthn-rs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 43 | 44 |
45 |
46 |

47 | what's going on? 48 |

49 |

50 | 51 |
52 |
53 |
54 |
55 |

Register a Webauthn Credential

56 | 57 |
58 | 59 | 60 |
61 | 62 | 65 | 66 |
67 |
68 |
69 |
70 |
71 | 72 | 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /tutorial/server/actix_web/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use actix_session::SessionMiddleware; 4 | use actix_web::cookie::Key; 5 | use actix_web::middleware::Logger; 6 | use actix_web::web::JsonConfig; 7 | use actix_web::web::{get, post}; 8 | use actix_web::{App, HttpServer}; 9 | use tracing::{error, info}; 10 | 11 | use crate::handler::auth::{ 12 | finish_authentication, finish_register, start_authentication, start_register, 13 | }; 14 | use crate::handler::index::{index, WASM_BG_FILE, WASM_JS_FILE}; 15 | use crate::handler::serve_wasm::{serve_wasm, WASM_DIR}; 16 | use crate::session::MemorySession; 17 | use crate::startup::startup; 18 | 19 | mod handler; 20 | mod session; 21 | mod startup; 22 | 23 | #[tokio::main] 24 | async fn main() { 25 | if std::env::var("RUST_LOG").is_err() { 26 | std::env::set_var("RUST_LOG", "INFO"); 27 | } 28 | // initialize tracing 29 | tracing_subscriber::fmt::init(); 30 | 31 | // Generate secret key for cookies. 32 | // Normally you would read this from a configuration file. 33 | let key = Key::generate(); 34 | 35 | let (webauthn, webauthn_users) = startup(); 36 | 37 | if !Path::new(WASM_DIR).exists() { 38 | panic!("{WASM_DIR} does not exist, can't serve WASM files."); 39 | } else { 40 | info!("Found WASM dir OK"); 41 | } 42 | 43 | let mut missing_file = false; 44 | for file in [WASM_BG_FILE, WASM_JS_FILE] { 45 | if !Path::new(WASM_DIR).join(file).exists() { 46 | error!("{} does not exist, can't serve WASM files.", file); 47 | missing_file = true; 48 | } else { 49 | info!("Found {} OK", file); 50 | } 51 | } 52 | if missing_file { 53 | error!("Missing WASM files, can't continue."); 54 | return; 55 | } 56 | 57 | // Build the webserver and run it 58 | info!("Listening on: http://0.0.0.0:8080 / http://127.0.0.1:8080"); 59 | HttpServer::new(move || { 60 | App::new() 61 | .wrap(Logger::default()) 62 | .wrap( 63 | SessionMiddleware::builder(MemorySession, key.clone()) 64 | .cookie_name("webauthnrs".to_string()) 65 | .cookie_http_only(true) 66 | .cookie_secure(false) 67 | .build(), 68 | ) 69 | .app_data(JsonConfig::default()) 70 | .app_data(webauthn.clone()) 71 | .app_data(webauthn_users.clone()) 72 | .route("/", get().to(index)) 73 | .route("/pkg/{filename:.*}", get().to(serve_wasm)) 74 | .route("/register_start/{username}", post().to(start_register)) 75 | .route("/register_finish", post().to(finish_register)) 76 | .route("/login_start/{username}", post().to(start_authentication)) 77 | .route("/login_finish", post().to(finish_authentication)) 78 | }) 79 | .bind(("0.0.0.0", 8080)) 80 | .expect("Failed to start a listener on 0.0.0.0:8080") 81 | .run() 82 | .await 83 | .unwrap(); 84 | } 85 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Our Pledge 2 | 3 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 4 | Our Standards 5 | 6 | Examples of behavior that contributes to creating a positive environment include: 7 | 8 | Using welcoming and inclusive language 9 | Being respectful of differing viewpoints and experiences 10 | Gracefully accepting constructive criticism 11 | Focusing on what is best for the community 12 | Showing empathy towards other community members 13 | 14 | Examples of unacceptable behavior by participants include: 15 | 16 | The use of sexualized language or imagery and unwelcome sexual attention or advances 17 | Trolling, insulting/derogatory comments, and personal or political attacks 18 | Public or private harassment 19 | Publishing others’ private information, such as a physical or electronic address, without explicit permission 20 | Other conduct which could reasonably be considered inappropriate in a professional setting 21 | 22 | ## Our Responsibilities 23 | 24 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 25 | 26 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 27 | Scope 28 | 29 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 30 | 31 | ## Enforcement 32 | 33 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at: 34 | 35 | * william at blackhats.net.au 36 | * charcol at redhat.com 37 | 38 | All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 39 | 40 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. 41 | 42 | ## Attribution 43 | 44 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 45 | -------------------------------------------------------------------------------- /tutorial/server/axum/src/main.rs: -------------------------------------------------------------------------------- 1 | use axum::{extract::Extension, http::StatusCode, response::IntoResponse, routing::post, Router}; 2 | use std::net::SocketAddr; 3 | #[cfg(feature = "wasm")] 4 | use std::path::PathBuf; 5 | use tower_sessions::{ 6 | cookie::{time::Duration, SameSite}, 7 | Expiry, MemoryStore, SessionManagerLayer, 8 | }; 9 | 10 | mod error; 11 | /* 12 | * Webauthn RS server side tutorial. 13 | */ 14 | 15 | // The handlers that process the data can be found in the auth.rs file 16 | // This file contains the wasm client loading code and the axum routing 17 | use crate::auth::{finish_authentication, finish_register, start_authentication, start_register}; 18 | use crate::startup::AppState; 19 | 20 | #[macro_use] 21 | extern crate tracing; 22 | 23 | mod auth; 24 | mod startup; 25 | 26 | #[cfg(all(feature = "javascript", feature = "wasm", not(doc)))] 27 | compile_error!("Feature \"javascript\" and feature \"wasm\" cannot be enabled at the same time"); 28 | 29 | // 7. That's it! The user has now authenticated! 30 | 31 | // ======= 32 | // Below is glue/stubs that are needed to make the above work, but don't really affect 33 | // the work flow too much. 34 | 35 | #[tokio::main] 36 | async fn main() { 37 | if std::env::var("RUST_LOG").is_err() { 38 | std::env::set_var("RUST_LOG", "INFO"); 39 | } 40 | // initialize tracing 41 | tracing_subscriber::fmt::init(); 42 | 43 | // Create the app 44 | let app_state = AppState::new(); 45 | 46 | let session_store = MemoryStore::default(); 47 | 48 | // build our application with a route 49 | let app = Router::new() 50 | .route("/register_start/:username", post(start_register)) 51 | .route("/register_finish", post(finish_register)) 52 | .route("/login_start/:username", post(start_authentication)) 53 | .route("/login_finish", post(finish_authentication)) 54 | .layer(Extension(app_state)) 55 | .layer( 56 | SessionManagerLayer::new(session_store) 57 | .with_name("webauthnrs") 58 | .with_same_site(SameSite::Strict) 59 | .with_secure(false) // TODO: change this to true when running on an HTTPS/production server instead of locally 60 | .with_expiry(Expiry::OnInactivity(Duration::seconds(360))), 61 | ) 62 | .fallback(handler_404); 63 | 64 | #[cfg(feature = "wasm")] 65 | if !PathBuf::from("./assets/wasm").exists() { 66 | panic!("Can't find WASM files to serve!") 67 | } 68 | 69 | #[cfg(feature = "wasm")] 70 | let app = Router::new() 71 | .merge(app) 72 | .nest_service("/", tower_http::services::ServeDir::new("assets/wasm")); 73 | 74 | #[cfg(feature = "javascript")] 75 | let app = Router::new() 76 | .merge(app) 77 | .nest_service("/", tower_http::services::ServeDir::new("assets/js")); 78 | 79 | // run our app with hyper 80 | // `axum::Server` is a re-export of `hyper::Server` 81 | let addr = SocketAddr::from(([0, 0, 0, 0], 8080)); 82 | info!("listening on {addr}"); 83 | 84 | let listener = tokio::net::TcpListener::bind(addr) 85 | .await 86 | .expect("Unable to spawn tcp listener"); 87 | 88 | axum::serve(listener, app).await.unwrap(); 89 | } 90 | 91 | async fn handler_404() -> impl IntoResponse { 92 | (StatusCode::NOT_FOUND, "nothing to see here") 93 | } 94 | -------------------------------------------------------------------------------- /device-catalog/src/data/yubico.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::*; 2 | 3 | // Yubico root cert. 4 | // https://developers.yubico.com/U2F/yubico-u2f-ca-certs.txt 5 | pub const YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM: &[u8] = b"-----BEGIN CERTIFICATE----- 6 | MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ 7 | dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw 8 | MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290 9 | IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 10 | AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk 11 | 5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep 12 | 8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw 13 | nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT 14 | 9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw 15 | LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ 16 | hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN 17 | BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4 18 | MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt 19 | hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k 20 | LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U 21 | sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc 22 | U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== 23 | -----END CERTIFICATE-----"; 24 | 25 | impl DataBuilder { 26 | pub fn add_yubico(mut self) -> Self { 27 | let yk_mfr = Rc::new(Manufacturer { 28 | display_name: "Yubico".to_string(), 29 | }); 30 | 31 | // https://developers.yubico.com/U2F/yubico-u2f-ca-certs.txt 32 | let yk_ca = Rc::new(Authority { 33 | ca: x509::X509::from_pem(YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM).expect("Invalid DER"), 34 | }); 35 | 36 | // YK 5 FIPS 37 | let yk_5_fips_aaguid = Rc::new(Aaguid { 38 | id: uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"), 39 | ca: vec![yk_ca.clone()], 40 | }); 41 | 42 | let yk_5_fips_sku_5_4_3 = Rc::new(Sku { 43 | display_name: "YubiKey 5 FIPS Series".to_string(), 44 | version: "5.4.3".to_string(), 45 | }); 46 | 47 | // device 48 | let yk_5_fips = Rc::new(Device { 49 | aaguid: yk_5_fips_aaguid, 50 | skus: vec![yk_5_fips_sku_5_4_3], 51 | mfr: yk_mfr.clone(), 52 | // default 53 | images: Vec::default(), 54 | quirks: BTreeSet::default(), 55 | }); 56 | 57 | self.devices.push(yk_5_fips); 58 | 59 | // YK 5 bio series 60 | let yk_5_bio_aaguid = Rc::new(Aaguid { 61 | id: uuid::uuid!("d8522d9f-575b-4866-88a9-ba99fa02f35b"), 62 | ca: vec![yk_ca.clone()], 63 | }); 64 | 65 | let yk_5_bio_sku_5_5_6 = Rc::new(Sku { 66 | display_name: "YubiKey Bio Series".to_string(), 67 | version: "5.5.6".to_string(), 68 | }); 69 | 70 | let yk_5_bio = Rc::new(Device { 71 | aaguid: yk_5_bio_aaguid, 72 | skus: vec![yk_5_bio_sku_5_5_6], 73 | mfr: yk_mfr.clone(), 74 | // default 75 | images: Vec::default(), 76 | quirks: BTreeSet::default(), 77 | }); 78 | 79 | self.devices.push(yk_5_bio); 80 | 81 | self 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /device-catalog/src/data/google.rs: -------------------------------------------------------------------------------- 1 | pub const GOOGLE_SAFETYNET_CA_OLD: &[u8] = b"-----BEGIN CERTIFICATE----- 2 | MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G 3 | A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp 4 | Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 5 | MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG 6 | A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI 7 | hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL 8 | v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 9 | eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq 10 | tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd 11 | C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa 12 | zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB 13 | mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH 14 | V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n 15 | bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG 16 | 3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs 17 | J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO 18 | 291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS 19 | ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd 20 | AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 21 | TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== 22 | -----END CERTIFICATE-----"; 23 | 24 | pub const GOOGLE_SAFETYNET_CA: &[u8] = b"-----BEGIN CERTIFICATE----- 25 | MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH 26 | MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM 27 | QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy 28 | MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl 29 | cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB 30 | AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM 31 | f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX 32 | mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 33 | zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P 34 | fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc 35 | vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 36 | Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp 37 | zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO 38 | Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW 39 | k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ 40 | DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF 41 | lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV 42 | HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW 43 | Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 44 | d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z 45 | XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR 46 | gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 47 | d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv 48 | J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg 49 | DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM 50 | +SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy 51 | F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 52 | SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws 53 | E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl 54 | -----END CERTIFICATE-----"; 55 | -------------------------------------------------------------------------------- /cable-tunnel-server/backend/README.md: -------------------------------------------------------------------------------- 1 | # webauthn-rs cable-tunnel-server-backend 2 | 3 | This binary provides a caBLE tunnel server, which is intended for 4 | *non-production use only*. 5 | 6 | The `backend` can run in two configurations: 7 | 8 | * a single-task configuration, directly serving requests with no frontend. 9 | 10 | In this configuration, caBLE [Routing IDs][background] are ignored, and it is 11 | presumed all incoming requests can be served out of a single running task. 12 | 13 | * a multi-task configuration, with many frontend tasks. 14 | 15 | In this configuration, the backend presumes it has frontend tasks in front of 16 | it to [handle caBLE Routing IDs][background]. However, the frontend is not yet 17 | fully implemented. 18 | 19 | The `backend` is stateless, and is not capable of communicating with other 20 | tasks on its own. Each tunnel exists within one (*and only one*) `backend` task, 21 | and `backend` tasks never process caBLE [Routing IDs][background]. 22 | 23 | [background]: ../README.md#background 24 | 25 | ## Building 26 | 27 | You can build the `backend` using Cargo: 28 | 29 | ```sh 30 | cargo build 31 | ``` 32 | 33 | This will output a binary to `./target/debug/cable-tunnel-server-backend`. 34 | 35 | You can also run the server via Cargo: 36 | 37 | ```sh 38 | cargo run -- --help 39 | ``` 40 | 41 | ## Configuring the server 42 | 43 | The server is configured with command-line flags, which can be seen by running 44 | the server with `--help`. 45 | 46 | To run the server at http://127.0.0.1:8080 (for testing with 47 | `webauthn-authenticator-rs` built with the `cable-override-tunnel` feature): 48 | 49 | ```sh 50 | ./cable-tunnel-server-backend \ 51 | --bind-address 127.0.0.1:8080 \ 52 | --insecure-http-server 53 | ``` 54 | 55 | To run the server with HTTPS and strict `Origin` header checks: 56 | 57 | ```sh 58 | ./cable-tunnel-server-backend \ 59 | --bind-address 192.0.2.1:443 \ 60 | --tls-public-key /etc/ssl/certs/cable.example.com.pem \ 61 | --tls-private-key /etc/ssl/certs/cable.example.com.key \ 62 | --origin cable.example.com 63 | ``` 64 | 65 | > **Important:** caBLE has an algorithm to deriving tunnel server domain names – 66 | > you cannot host the service on an arbitrary domain name of your choosing. 67 | > 68 | > Run [`webauthn-authenticator-rs`' `cable_domain` example][cable_domain] to 69 | > derive hostnames at the command line. 70 | 71 | [cable_domain]: ../../webauthn-authenticator-rs/examples/cable_domain.rs 72 | 73 | ## Logging 74 | 75 | By default, the server runs at log level `info`. This can be changed with the 76 | `RUST_LOG` environment variable, using the 77 | [log levels available in the `tracing` crate][log-levels]. 78 | 79 | The server logs the following at each level, plus all the messages in the levels 80 | above it: 81 | 82 | * `error`: TLS handshake errors, TCP connection errors, incorrect or unknown 83 | HTTP requests 84 | 85 | * `warn`: warnings about using unencrypted HTTP 86 | 87 | * `info`: (default) start-up messages, HTTP connection lifetime, HTTP request 88 | logs, WebSocket tunnel lifetime 89 | 90 | * `debug`: n/a 91 | 92 | * `trace`: adds complete incoming HTTP requests, WebSocket tunnel messages 93 | 94 | [log-levels]: https://docs.rs/tracing/*/tracing/struct.Level.html 95 | 96 | ## Monitoring 97 | 98 | The server exports some basic metrics at `/debug`: 99 | 100 | * `server_state.strong_count`: the number of strong references to 101 | `Arc` 102 | 103 | * `peer_map`: a `HashMap` of all pending tunnels - those where the authenticator 104 | has connected but the initiator has not yet connected. 105 | 106 | * `peer_map.capacity`: the capacity of the pending tunnels `HashMap` 107 | 108 | * `peer_map.len`: the number of pending tunnels 109 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/win10/cose.rs: -------------------------------------------------------------------------------- 1 | //! Wrappers for [PubKeyCredParams]. 2 | use crate::prelude::WebauthnCError; 3 | use std::pin::Pin; 4 | use webauthn_rs_proto::PubKeyCredParams; 5 | 6 | use super::WinWrapper; 7 | 8 | use windows::{ 9 | core::HSTRING, 10 | Win32::Networking::WindowsWebServices::{ 11 | WEBAUTHN_COSE_CREDENTIAL_PARAMETER, WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, 12 | WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION, 13 | }, 14 | }; 15 | 16 | /// Wrapper for [WEBAUTHN_COSE_CREDENTIAL_PARAMETER] to ensure pointer lifetime. 17 | struct WinCoseCredentialParameter { 18 | native: WEBAUTHN_COSE_CREDENTIAL_PARAMETER, 19 | _typ: HSTRING, 20 | } 21 | 22 | impl WinCoseCredentialParameter { 23 | fn from(p: PubKeyCredParams) -> Pin> { 24 | let res = Self { 25 | native: Default::default(), 26 | _typ: p.type_.into(), 27 | }; 28 | 29 | let mut boxed = Box::pin(res); 30 | 31 | let native = WEBAUTHN_COSE_CREDENTIAL_PARAMETER { 32 | dwVersion: WEBAUTHN_COSE_CREDENTIAL_PARAMETER_CURRENT_VERSION, 33 | pwszCredentialType: (&boxed._typ).into(), 34 | lAlg: p.alg as i32, 35 | }; 36 | 37 | unsafe { 38 | let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed); 39 | Pin::get_unchecked_mut(mut_ref).native = native; 40 | } 41 | 42 | boxed 43 | } 44 | } 45 | 46 | pub struct WinCoseCredentialParameters { 47 | native: WEBAUTHN_COSE_CREDENTIAL_PARAMETERS, 48 | _params: Vec>>, 49 | _l: Vec, 50 | } 51 | 52 | impl WinWrapper> for WinCoseCredentialParameters { 53 | type NativeType = WEBAUTHN_COSE_CREDENTIAL_PARAMETERS; 54 | 55 | fn new(params: Vec) -> Result>, WebauthnCError> { 56 | let params: Vec>> = params 57 | .into_iter() 58 | .map(WinCoseCredentialParameter::from) 59 | .collect(); 60 | Ok(WinCoseCredentialParameters::from_wrapped(params)) 61 | } 62 | 63 | fn native_ptr(&self) -> &WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { 64 | &self.native 65 | } 66 | } 67 | 68 | impl WinCoseCredentialParameters { 69 | fn from_wrapped(params: Vec>>) -> Pin> { 70 | let len = params.len(); 71 | let res = Self { 72 | native: Default::default(), 73 | _l: Vec::with_capacity(len), 74 | _params: params, 75 | }; 76 | 77 | // Box and pin the struct so it's on the heap and doesn't move. 78 | let mut boxed = Box::pin(res); 79 | 80 | // Put in all the "native" values 81 | let p_ptr = boxed._params.as_ptr(); 82 | unsafe { 83 | let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed); 84 | let l = &mut Pin::get_unchecked_mut(mut_ref)._l; 85 | let l_ptr = l.as_mut_ptr(); 86 | for i in 0..len { 87 | *l_ptr.add(i) = (&(*p_ptr.add(i))).native; 88 | } 89 | 90 | l.set_len(len); 91 | } 92 | 93 | // let mut l: Vec = 94 | // params.iter().map(|p| p.native).collect(); 95 | 96 | let native = WEBAUTHN_COSE_CREDENTIAL_PARAMETERS { 97 | cCredentialParameters: boxed._l.len() as u32, 98 | pCredentialParameters: boxed._l.as_mut_ptr() as *mut _, 99 | }; 100 | 101 | unsafe { 102 | let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed); 103 | Pin::get_unchecked_mut(mut_ref).native = native; 104 | } 105 | 106 | boxed 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /device-catalog/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod aaguid; 2 | pub mod certificate_authority; 3 | pub mod device; 4 | pub mod image; 5 | pub mod manufacturer; 6 | pub mod quirks; 7 | 8 | pub mod query; 9 | 10 | pub mod data; 11 | 12 | use std::fmt; 13 | 14 | use crate::device::*; 15 | use crate::query::Query; 16 | use std::rc::Rc; 17 | 18 | use webauthn_attestation_ca::{AttestationCaList, AttestationCaListBuilder}; 19 | 20 | pub mod prelude { 21 | pub use crate::aaguid::Aaguid; 22 | pub use crate::certificate_authority::Authority; 23 | pub use crate::device::{Device, Sku}; 24 | pub use crate::manufacturer::Manufacturer; 25 | pub use crate::query::Query; 26 | pub use crate::{Data, DataBuilder}; 27 | 28 | pub(crate) use openssl::error::ErrorStack as OpenSSLErrorStack; 29 | pub(crate) use openssl::x509; 30 | pub(crate) use std::collections::BTreeSet; 31 | pub(crate) use std::rc::Rc; 32 | } 33 | 34 | #[derive(Default)] 35 | pub struct DataBuilder { 36 | devices: Vec>, 37 | } 38 | 39 | impl DataBuilder { 40 | pub fn build(self) -> Data { 41 | Data { 42 | devices: self.devices, 43 | } 44 | } 45 | } 46 | 47 | pub struct Data { 48 | devices: Vec>, 49 | } 50 | 51 | impl Default for Data { 52 | fn default() -> Self { 53 | Self::strict() 54 | } 55 | } 56 | 57 | impl fmt::Display for Data { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | write!(f, "Data") 60 | } 61 | } 62 | 63 | impl Data { 64 | /// This is a list of webauthn authenticators that are of the highest 65 | /// quality and guarantees for users and RP's. These are devices that not only 66 | /// are secure, but user friendly, consistent, and correct. 67 | pub fn strict() -> Self { 68 | DataBuilder::default().add_yubico().build() 69 | } 70 | 71 | pub fn all_known_devices() -> Self { 72 | DataBuilder::default().add_yubico().build() 73 | } 74 | 75 | pub fn query(&self, query: &Query) -> Option { 76 | tracing::debug!(?query); 77 | 78 | let devices: Vec<_> = self 79 | .devices 80 | .iter() 81 | .filter(|dev| dev.query_match(query)) 82 | // This is cheap due to Rc, 83 | .cloned() 84 | .collect(); 85 | 86 | if devices.is_empty() { 87 | None 88 | } else { 89 | Some(Data { devices }) 90 | } 91 | } 92 | 93 | pub fn iter(&self) -> impl Iterator { 94 | self.devices.iter().map(|d| d.as_ref()) 95 | } 96 | } 97 | 98 | // Allowed as AttestationCaList is foreign. 99 | #[allow(clippy::from_over_into)] 100 | impl TryInto for &Data { 101 | type Error = crate::prelude::OpenSSLErrorStack; 102 | 103 | fn try_into(self) -> Result { 104 | let mut att_ca_builder = AttestationCaListBuilder::new(); 105 | 106 | for dev in self.devices.iter() { 107 | for sku in dev.skus.iter() { 108 | for authority in dev.aaguid.ca.iter() { 109 | att_ca_builder.insert_device_x509( 110 | authority.ca.clone(), 111 | dev.aaguid.id, 112 | sku.display_name.clone(), 113 | Default::default(), 114 | )?; 115 | } 116 | } 117 | } 118 | 119 | Ok(att_ca_builder.build()) 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | mod test { 125 | use super::*; 126 | 127 | #[test] 128 | fn test_init_data() { 129 | let data = Data::default(); 130 | 131 | println!("{data}"); 132 | for i in data.iter() { 133 | println!("{i:?}"); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/usb/solokey.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use uuid::Uuid; 3 | 4 | #[cfg(all(feature = "usb", feature = "vendor-solokey"))] 5 | use crate::transport::solokey::{CMD_LOCK, CMD_RANDOM, CMD_UUID, CMD_VERSION}; 6 | 7 | use crate::{ 8 | prelude::WebauthnCError, 9 | transport::{ 10 | solokey::SoloKeyToken, 11 | types::{U2FError, U2FHID_ERROR}, 12 | }, 13 | usb::{framing::U2FHIDFrame, USBToken}, 14 | }; 15 | 16 | #[async_trait] 17 | impl SoloKeyToken for USBToken { 18 | async fn get_solokey_lock(&mut self) -> Result { 19 | let cmd = U2FHIDFrame { 20 | cid: self.cid, 21 | cmd: CMD_LOCK, 22 | len: 0, 23 | data: vec![], 24 | }; 25 | self.send_one(&cmd).await?; 26 | 27 | let r = self.recv_one().await?; 28 | match r.cmd { 29 | CMD_LOCK => { 30 | if r.len != 1 || r.data.len() != 1 { 31 | return Err(WebauthnCError::InvalidMessageLength); 32 | } 33 | 34 | Ok(r.data[0] != 0) 35 | } 36 | 37 | U2FHID_ERROR => Err(U2FError::from(r.data.as_slice()).into()), 38 | 39 | _ => Err(WebauthnCError::UnexpectedState), 40 | } 41 | } 42 | 43 | async fn get_solokey_random(&mut self) -> Result<[u8; 57], WebauthnCError> { 44 | let cmd = U2FHIDFrame { 45 | cid: self.cid, 46 | cmd: CMD_RANDOM, 47 | len: 0, 48 | data: vec![], 49 | }; 50 | self.send_one(&cmd).await?; 51 | 52 | let r = self.recv_one().await?; 53 | match r.cmd { 54 | CMD_RANDOM => r 55 | .data 56 | .try_into() 57 | .map_err(|_| WebauthnCError::InvalidMessageLength), 58 | 59 | U2FHID_ERROR => Err(U2FError::from(r.data.as_slice()).into()), 60 | 61 | _ => Err(WebauthnCError::UnexpectedState), 62 | } 63 | } 64 | 65 | async fn get_solokey_version(&mut self) -> Result { 66 | let cmd = U2FHIDFrame { 67 | cid: self.cid, 68 | cmd: CMD_VERSION, 69 | len: 0, 70 | data: vec![], 71 | }; 72 | self.send_one(&cmd).await?; 73 | 74 | let r = self.recv_one().await?; 75 | match r.cmd { 76 | CMD_VERSION => { 77 | let u = u32::from_be_bytes( 78 | r.data 79 | .try_into() 80 | .map_err(|_| WebauthnCError::InvalidMessageLength)?, 81 | ); 82 | 83 | Ok(u) 84 | } 85 | 86 | U2FHID_ERROR => Err(U2FError::from(r.data.as_slice()).into()), 87 | 88 | _ => Err(WebauthnCError::UnexpectedState), 89 | } 90 | } 91 | 92 | async fn get_solokey_uuid(&mut self) -> Result { 93 | let cmd = U2FHIDFrame { 94 | cid: self.cid, 95 | cmd: CMD_UUID, 96 | len: 0, 97 | data: vec![], 98 | }; 99 | self.send_one(&cmd).await?; 100 | 101 | let r = self.recv_one().await?; 102 | match r.cmd { 103 | CMD_UUID => { 104 | if r.len != 16 || r.data.len() != 16 { 105 | return Err(WebauthnCError::InvalidMessageLength); 106 | } 107 | 108 | let u = Uuid::from_bytes( 109 | r.data 110 | .try_into() 111 | .map_err(|_| WebauthnCError::InvalidMessageLength)?, 112 | ); 113 | 114 | Ok(u) 115 | } 116 | 117 | U2FHID_ERROR => Err(U2FError::from(r.data.as_slice()).into()), 118 | 119 | _ => Err(WebauthnCError::UnexpectedState), 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /webauthn-rs-proto/src/cose.rs: -------------------------------------------------------------------------------- 1 | //! Types related to CBOR Object Signing and Encryption (COSE) 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// A COSE signature algorithm, indicating the type of key and hash type 6 | /// that should be used. You shouldn't need to alter or use this value. 7 | #[allow(non_camel_case_types)] 8 | #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] 9 | #[repr(i32)] 10 | pub enum COSEAlgorithm { 11 | /// Identifies this key as ECDSA (recommended SECP256R1) with SHA256 hashing 12 | #[serde(alias = "ECDSA_SHA256")] 13 | ES256 = -7, // recommends curve SECP256R1 14 | /// Identifies this key as ECDSA (recommended SECP384R1) with SHA384 hashing 15 | #[serde(alias = "ECDSA_SHA384")] 16 | ES384 = -35, // recommends curve SECP384R1 17 | /// Identifies this key as ECDSA (recommended SECP521R1) with SHA512 hashing 18 | #[serde(alias = "ECDSA_SHA512")] 19 | ES512 = -36, // recommends curve SECP521R1 20 | /// Identifies this key as RS256 aka RSASSA-PKCS1-v1_5 w/ SHA-256 21 | RS256 = -257, 22 | /// Identifies this key as RS384 aka RSASSA-PKCS1-v1_5 w/ SHA-384 23 | RS384 = -258, 24 | /// Identifies this key as RS512 aka RSASSA-PKCS1-v1_5 w/ SHA-512 25 | RS512 = -259, 26 | /// Identifies this key as PS256 aka RSASSA-PSS w/ SHA-256 27 | PS256 = -37, 28 | /// Identifies this key as PS384 aka RSASSA-PSS w/ SHA-384 29 | PS384 = -38, 30 | /// Identifies this key as PS512 aka RSASSA-PSS w/ SHA-512 31 | PS512 = -39, 32 | /// Identifies this key as EdDSA (likely curve ed25519) 33 | EDDSA = -8, 34 | /// Identifies this as an INSECURE RS1 aka RSASSA-PKCS1-v1_5 using SHA-1. This is not 35 | /// used by validators, but can exist in some windows hello tpm's 36 | INSECURE_RS1 = -65535, 37 | /// Identifies this key as the protocol used for [PIN/UV Auth Protocol One](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pinProto1) 38 | /// 39 | /// This reports as algorithm `-25`, but it is a lie. Don't include this in any algorithm lists. 40 | PinUvProtocol, 41 | } 42 | 43 | impl COSEAlgorithm { 44 | /// Return the set of secure recommended COSEAlgorithm's 45 | pub fn secure_algs() -> Vec { 46 | vec![ 47 | COSEAlgorithm::ES256, 48 | // COSEAlgorithm::ES384, 49 | // COSEAlgorithm::ES512, 50 | COSEAlgorithm::RS256, 51 | // COSEAlgorithm::RS384, 52 | // COSEAlgorithm::RS512 53 | // -- Testing required 54 | // COSEAlgorithm::EDDSA, 55 | ] 56 | } 57 | 58 | /// Return the set of all possible algorithms that may exist as a COSEAlgorithm 59 | pub fn all_possible_algs() -> Vec { 60 | vec![ 61 | COSEAlgorithm::ES256, 62 | COSEAlgorithm::ES384, 63 | COSEAlgorithm::ES512, 64 | COSEAlgorithm::RS256, 65 | COSEAlgorithm::RS384, 66 | COSEAlgorithm::RS512, 67 | COSEAlgorithm::PS256, 68 | COSEAlgorithm::PS384, 69 | COSEAlgorithm::PS512, 70 | COSEAlgorithm::EDDSA, 71 | COSEAlgorithm::INSECURE_RS1, 72 | ] 73 | } 74 | } 75 | 76 | impl TryFrom for COSEAlgorithm { 77 | type Error = (); 78 | 79 | fn try_from(i: i128) -> Result { 80 | match i { 81 | -7 => Ok(COSEAlgorithm::ES256), 82 | -35 => Ok(COSEAlgorithm::ES384), 83 | -36 => Ok(COSEAlgorithm::ES512), 84 | -257 => Ok(COSEAlgorithm::RS256), 85 | -258 => Ok(COSEAlgorithm::RS384), 86 | -259 => Ok(COSEAlgorithm::RS512), 87 | -37 => Ok(COSEAlgorithm::PS256), 88 | -38 => Ok(COSEAlgorithm::PS384), 89 | -39 => Ok(COSEAlgorithm::PS512), 90 | -8 => Ok(COSEAlgorithm::EDDSA), 91 | -65535 => Ok(COSEAlgorithm::INSECURE_RS1), 92 | _ => Err(()), 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /compat_tester/webauthn-rs-demo-wasm/src/manager.rs: -------------------------------------------------------------------------------- 1 | use gloo::console; 2 | use yew::functional::*; 3 | use yew::prelude::*; 4 | use yew_router::prelude::*; 5 | 6 | use crate::compat::CompatTest; 7 | use crate::demo::Demo; 8 | // use crate::uv::UvInconsistent; 9 | 10 | // router to decide on state. 11 | #[derive(Routable, PartialEq, Clone, Debug)] 12 | pub enum Route { 13 | #[at("/")] 14 | Demo, 15 | 16 | #[at("/compat_test")] 17 | CompatTest, 18 | 19 | #[not_found] 20 | #[at("/404")] 21 | NotFound, 22 | } 23 | 24 | #[function_component(Nav)] 25 | fn nav() -> Html { 26 | let location = use_location().expect("unable to access location"); 27 | let cur_route = location.route(); 28 | 29 | html! { 30 | 62 | } 63 | } 64 | 65 | fn switch(routes: &Route) -> Html { 66 | console::log!("manager::switch"); 67 | match routes { 68 | Route::Demo => html! { }, 69 | Route::CompatTest => html! { }, 70 | Route::NotFound => { 71 | html! { 72 | <> 73 |

{ "404" }

74 | to={ Route::Demo }> 75 | { "Back to Demo" } 76 | > 77 | 78 | } 79 | } 80 | } 81 | } 82 | 83 | pub struct ManagerApp {} 84 | 85 | impl Component for ManagerApp { 86 | type Message = bool; 87 | type Properties = (); 88 | 89 | fn create(_ctx: &Context) -> Self { 90 | console::log!("manager::create"); 91 | ManagerApp {} 92 | } 93 | 94 | fn changed(&mut self, _ctx: &Context) -> bool { 95 | console::log!("manager::change"); 96 | false 97 | } 98 | 99 | fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { 100 | console::log!("manager::update"); 101 | true 102 | } 103 | 104 | fn rendered(&mut self, _ctx: &Context, _first_render: bool) { 105 | console::log!("manager::rendered"); 106 | } 107 | 108 | fn view(&self, _ctx: &Context) -> Html { 109 | html! { 110 |
111 | 112 |
116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/examples/cable_domain.rs: -------------------------------------------------------------------------------- 1 | //! Derives caBLE tunnel server hostnames. 2 | //! 3 | //! ```sh 4 | //! cargo run --example cable_domain --features cable -- --help 5 | //! ``` 6 | use clap::{error::ErrorKind, ArgGroup, CommandFactory, Parser, ValueHint}; 7 | use std::{fmt::Write, net::ToSocketAddrs}; 8 | use webauthn_authenticator_rs::cable::get_domain; 9 | 10 | #[derive(Debug, clap::Parser)] 11 | #[clap( 12 | about = "Derives caBLE tunnel server hostnames", 13 | after_help = "\ 14 | When deriving exactly one tunnel server hostname, only the hostname will be \ 15 | printed, and an unknown ID will result in an error. 16 | 17 | When deriving multiple (or all) tunnel server hostnames, or when using `--csv` \ 18 | or `--resolve`, the output will switch to CSV format. 19 | 20 | When outputting CSV, entries for unknown tunnel IDs will be an empty string, \ 21 | rather than an error." 22 | )] 23 | #[clap(group( 24 | ArgGroup::new("ids") 25 | .required(true) 26 | .args(&["tunnel_server_ids", "all"]) 27 | ))] 28 | pub struct CliParser { 29 | /// One or more tunnel server IDs. 30 | #[clap(value_name = "ID", value_hint = ValueHint::Other)] 31 | pub tunnel_server_ids: Vec, 32 | 33 | /// Derives all 65,536 possible tunnel server hostnames. 34 | #[clap(short, long)] 35 | pub all: bool, 36 | 37 | /// Enable CSV output mode. 38 | /// 39 | /// This is automatically enabled when requesting multiple (or all) tunnel 40 | /// server IDs, or when using `--resolve`. 41 | #[clap(short, long)] 42 | pub csv: bool, 43 | 44 | /// Resolve tunnel server hostnames to IP addresses. 45 | /// 46 | /// This generally results in network traffic, so will be slow. 47 | #[clap(short, long)] 48 | pub resolve: bool, 49 | } 50 | 51 | /// Resolves a hostname to (an) IP address(es) using the system resolver, 52 | /// and return it as a comma-separated list. 53 | /// 54 | /// Returns [None] on resolution failure or no results. 55 | fn resolver(hostname: &str) -> Option { 56 | (hostname, 443).to_socket_addrs().ok().and_then(|addrs| { 57 | let mut o: String = addrs.fold(String::new(), |mut out, addr| { 58 | let _ = write!(out, "{},", addr.ip()); 59 | out 60 | }); 61 | o.pop(); 62 | 63 | if o.is_empty() { 64 | return None; 65 | } 66 | 67 | Some(o) 68 | }) 69 | } 70 | 71 | /// Prints caBLE tunnel server hostnames in CSV format. 72 | fn print_hostnames(i: impl Iterator, resolve: bool) { 73 | for domain_id in i { 74 | let domain = get_domain(domain_id); 75 | let addrs = if resolve { 76 | domain.as_deref().and_then(resolver) 77 | } else { 78 | None 79 | } 80 | .unwrap_or_default(); 81 | let domain = domain.unwrap_or_default(); 82 | 83 | if resolve { 84 | println!("{domain_id},{domain:?},{addrs:?}",); 85 | } else { 86 | println!("{domain_id},{domain:?}",); 87 | } 88 | } 89 | } 90 | 91 | fn main() { 92 | let opt = CliParser::parse(); 93 | tracing_subscriber::fmt::init(); 94 | 95 | if opt.tunnel_server_ids.len() == 1 && !(opt.resolve || opt.csv) { 96 | let domain_id = opt.tunnel_server_ids[0]; 97 | match get_domain(domain_id) { 98 | Some(d) => println!("{d}"), 99 | None => CliParser::command() 100 | .error( 101 | ErrorKind::ValueValidation, 102 | format!("unknown tunnel server ID: {domain_id}"), 103 | ) 104 | .exit(), 105 | } 106 | } else { 107 | if opt.resolve { 108 | println!("tunnel_server_id,hostname,addrs"); 109 | } else { 110 | println!("tunnel_server_id,hostname"); 111 | } 112 | 113 | if opt.all { 114 | print_hostnames(u16::MIN..=u16::MAX, opt.resolve); 115 | } else { 116 | print_hostnames(opt.tunnel_server_ids.into_iter(), opt.resolve); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | version = "0.5.4" 3 | authors = [ 4 | "William Brown ", 5 | "Michael Farrell ", 6 | ] 7 | rust-version = "1.85" 8 | edition = "2021" 9 | repository = "https://github.com/kanidm/webauthn-rs" 10 | homepage = "https://github.com/kanidm/webauthn-rs" 11 | license = "MPL-2.0" 12 | 13 | [workspace] 14 | resolver = "2" 15 | members = [ 16 | # Support Libraries 17 | "base64urlsafedata", 18 | "webauthn-rs-proto", 19 | "webauthn-rs-core", 20 | # The actually library. 21 | "webauthn-rs", 22 | # Authenticator interactions 23 | "fido-hid-rs", 24 | "webauthn-authenticator-rs", 25 | # caBLE tunnel server 26 | "cable-tunnel-server/backend", 27 | "cable-tunnel-server/common", 28 | "cable-tunnel-server/frontend", 29 | "fido-key-manager", 30 | # Authenticator CLI, 31 | "authenticator-cli", 32 | # Tutorial / Example sites. 33 | "tutorial/server/tide", 34 | "tutorial/server/axum", 35 | "tutorial/server/actix_web", 36 | "tutorial/wasm", 37 | # Attestatation struct format 38 | "attestation-ca", 39 | # Fido MDS tools 40 | "fido-mds", 41 | "fido-mds-tool", 42 | # Our Device Catalog 43 | "device-catalog", 44 | # "device-catalog-tool", 45 | # The compat tester 46 | "compat_tester/webauthn-rs-demo", 47 | "compat_tester/webauthn-rs-demo-shared", 48 | # Ssh Key Attestation 49 | "sshkey-attest", 50 | # RP Proxy 51 | "webauthn-rp-proxy", 52 | ] 53 | 54 | # Due to --cfg=web_sys_unstable_apis 55 | exclude = ["compat_tester/webauthn-rs-demo-wasm", "tutorial/wasm"] 56 | 57 | [workspace.dependencies] 58 | # These are in release/dependency order. 59 | base64urlsafedata = { path = "./base64urlsafedata", version = "=0.5.4" } 60 | fido-hid-rs = { path = "./fido-hid-rs", version = "=0.5.4" } 61 | webauthn-attestation-ca = { path = "./attestation-ca", version = "=0.5.4" } 62 | webauthn-rs-proto = { path = "./webauthn-rs-proto", version = "=0.5.4" } 63 | fido-mds = { path = "./fido-mds", version = "=0.5.4" } 64 | webauthn-rs-core = { path = "./webauthn-rs-core", version = "=0.5.4" } 65 | webauthn-rs = { path = "./webauthn-rs", version = "=0.5.4" } 66 | webauthn-authenticator-rs = { path = "./webauthn-authenticator-rs", version = "=0.5.4" } 67 | 68 | # Currently un-released 69 | cable-tunnel-server-common = { path = "./cable-tunnel-server/common", version = "0.1.0" } 70 | webauthn-rs-device-catalog = { path = "./device-catalog" } 71 | 72 | 73 | tracing-subscriber = { version = "0.3", features = [ 74 | "env-filter", 75 | "std", 76 | "fmt", 77 | ] } 78 | tokio = { version = "1.47.2", features = [ 79 | "sync", 80 | "test-util", 81 | "macros", 82 | "net", 83 | "rt-multi-thread", 84 | "time", 85 | ] } 86 | async-trait = "~0.1" 87 | async-std = { version = "1.13", features = ["attributes"] } 88 | anyhow = "1.0" 89 | base64 = "0.21" 90 | clap = { version = "^4.5", features = ["derive", "env"] } 91 | compact_jwt = "0.4.3" 92 | futures = "^0.3.31" 93 | hex = "0.4.3" 94 | http = "1.4.0" 95 | http-body = "1.0.1" 96 | http-body-util = "0.1.3" 97 | hyper = { version = "1.8.1", default-features = false, features = ["http1"] } 98 | hyper-util = { version = "0.1.19", features = ["tokio"] } 99 | nom = "7.1.3" 100 | num-derive = { version = "0.4.2" } 101 | peg = "0.8.5" 102 | openssl = "^0.10.75" 103 | openssl-sys = "^0.9.111" 104 | tokio-tungstenite = { version = "^0.28.0", features = ["native-tls"] } 105 | tracing = "^0.1.43" 106 | tracing-log = { version = "0.2.0" } 107 | tungstenite = { version = "^0.28.0", default-features = false, features = [ 108 | "handshake", 109 | ] } 110 | url = "2" 111 | uuid = "^1.19.0" 112 | serde = { version = "^1.0.228", features = ["derive"] } 113 | serde_cbor_2 = { version = "0.13.0" } 114 | serde_json = "^1.0.145" 115 | rand = "0.9.2" 116 | rand_chacha = "0.9.0" 117 | reqwest = { version = "0.12.25", default-features = false, features = [ 118 | "rustls-tls-native-roots", 119 | ] } 120 | thiserror = "^1.0.69" 121 | tokio-native-tls = "^0.3.1" 122 | tokio-stream = { version = "0.1", features = ["sync"] } 123 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/transport/types.rs: -------------------------------------------------------------------------------- 1 | use super::TYPE_INIT; 2 | use crate::error::{CtapError, WebauthnCError}; 3 | 4 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 5 | use super::iso7816::ISO7816ResponseAPDU; 6 | 7 | pub const U2FHID_PING: u8 = TYPE_INIT | 0x01; 8 | #[cfg(any(doc, feature = "bluetooth"))] 9 | pub const BTLE_KEEPALIVE: u8 = TYPE_INIT | 0x02; 10 | pub const U2FHID_MSG: u8 = TYPE_INIT | 0x03; 11 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 12 | pub const U2FHID_INIT: u8 = TYPE_INIT | 0x06; 13 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 14 | pub const U2FHID_WINK: u8 = TYPE_INIT | 0x08; 15 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 16 | pub const U2FHID_CBOR: u8 = TYPE_INIT | 0x10; 17 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 18 | pub const U2FHID_CANCEL: u8 = TYPE_INIT | 0x11; 19 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 20 | pub const U2FHID_KEEPALIVE: u8 = TYPE_INIT | 0x3b; 21 | #[cfg(any(doc, feature = "bluetooth"))] 22 | pub const BTLE_CANCEL: u8 = TYPE_INIT | 0x3e; 23 | pub const U2FHID_ERROR: u8 = TYPE_INIT | 0x3f; 24 | 25 | /// Type for parsing all responses from a BTLE or USB FIDO token. 26 | #[derive(Debug, PartialEq, Eq)] 27 | pub enum Response { 28 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 29 | Init(crate::usb::InitResponse), 30 | Ping(Vec), 31 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 32 | Msg(ISO7816ResponseAPDU), 33 | #[cfg(any(all(doc, not(doctest)), feature = "usb"))] 34 | Wink, 35 | Cbor(CBORResponse), 36 | Error(U2FError), 37 | KeepAlive(KeepAliveStatus), 38 | Unknown, 39 | } 40 | 41 | /// CTAPv2 CBOR message 42 | #[derive(Debug, PartialEq, Eq)] 43 | pub struct CBORResponse { 44 | /// Status code 45 | pub status: CtapError, 46 | /// Data payload 47 | pub data: Vec, 48 | } 49 | 50 | impl TryFrom<&[u8]> for CBORResponse { 51 | type Error = WebauthnCError; 52 | fn try_from(d: &[u8]) -> Result { 53 | if d.is_empty() { 54 | return Err(WebauthnCError::MessageTooShort); 55 | } 56 | Ok(Self { 57 | status: d[0].into(), 58 | data: d[1..].to_vec(), 59 | }) 60 | } 61 | } 62 | 63 | #[derive(Debug, PartialEq, Eq)] 64 | pub enum KeepAliveStatus { 65 | Processing, 66 | UserPresenceNeeded, 67 | Unknown(u8), 68 | } 69 | 70 | impl From for KeepAliveStatus { 71 | fn from(v: u8) -> Self { 72 | use KeepAliveStatus::*; 73 | match v { 74 | 1 => Processing, 75 | 2 => UserPresenceNeeded, 76 | v => Unknown(v), 77 | } 78 | } 79 | } 80 | 81 | impl From<&[u8]> for KeepAliveStatus { 82 | fn from(d: &[u8]) -> Self { 83 | if !d.is_empty() { 84 | Self::from(d[0]) 85 | } else { 86 | Self::Unknown(0) 87 | } 88 | } 89 | } 90 | 91 | #[derive(Debug, PartialEq, Eq)] 92 | pub enum U2FError { 93 | None, 94 | InvalidCommand, 95 | InvalidParameter, 96 | InvalidMessageLength, 97 | InvalidMessageSequencing, 98 | MessageTimeout, 99 | ChannelBusy, 100 | ChannelRequiresLock, 101 | SyncCommandFailed, 102 | Unspecified, 103 | Unknown, 104 | } 105 | 106 | impl From for U2FError { 107 | fn from(v: u8) -> Self { 108 | match v { 109 | 0x00 => U2FError::None, 110 | 0x01 => U2FError::InvalidCommand, 111 | 0x02 => U2FError::InvalidParameter, 112 | 0x03 => U2FError::InvalidMessageLength, 113 | 0x04 => U2FError::InvalidMessageSequencing, 114 | 0x05 => U2FError::MessageTimeout, 115 | 0x06 => U2FError::ChannelBusy, 116 | 0x0a => U2FError::ChannelRequiresLock, 117 | 0x0b => U2FError::SyncCommandFailed, 118 | 0x7f => U2FError::Unspecified, 119 | _ => U2FError::Unknown, 120 | } 121 | } 122 | } 123 | 124 | impl From<&[u8]> for U2FError { 125 | fn from(d: &[u8]) -> Self { 126 | if !d.is_empty() { 127 | U2FError::from(d[0]) 128 | } else { 129 | U2FError::Unknown 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /webauthn-authenticator-rs/src/transport/mod.rs: -------------------------------------------------------------------------------- 1 | //! Low-level transport abstraction layer for communication with FIDO tokens. 2 | //! 3 | //! See [crate::ctap2] for a higher-level abstraction over this API. 4 | mod any; 5 | pub mod iso7816; 6 | #[cfg(any(all(doc, not(doctest)), feature = "vendor-solokey"))] 7 | pub(crate) mod solokey; 8 | #[cfg(any(doc, feature = "bluetooth", feature = "usb"))] 9 | pub(crate) mod types; 10 | #[cfg(any(all(doc, not(doctest)), feature = "vendor-yubikey"))] 11 | pub(crate) mod yubikey; 12 | 13 | pub use crate::transport::any::{AnyToken, AnyTransport}; 14 | 15 | use async_trait::async_trait; 16 | use futures::stream::BoxStream; 17 | use std::fmt; 18 | use webauthn_rs_proto::AuthenticatorTransport; 19 | 20 | use crate::{ctap2::*, error::WebauthnCError, ui::UiCallback}; 21 | 22 | #[cfg(any(doc, feature = "bluetooth", feature = "usb"))] 23 | pub(crate) const TYPE_INIT: u8 = 0x80; 24 | 25 | #[derive(Debug)] 26 | pub enum TokenEvent { 27 | Added(T), 28 | Removed(T::Id), 29 | EnumerationComplete, 30 | } 31 | 32 | /// Represents a transport layer protocol for [Token]. 33 | /// 34 | /// If you don't care which transport your application uses, use [AnyTransport] 35 | /// to automatically use all available transports on the platform. 36 | #[async_trait] 37 | pub trait Transport<'b>: Sized + fmt::Debug + Send { 38 | /// The type of [Token] returned by this [Transport]. 39 | type Token: Token + 'b; 40 | 41 | /// Watches for token connection and disconnection on this [Transport]. 42 | /// 43 | /// Initially, this send synthetic [`TokenEvent::Added`] for all 44 | /// currently-connected tokens, followed by 45 | /// [`TokenEvent::EnumerationComplete`]. 46 | async fn watch(&self) -> Result>, WebauthnCError>; 47 | 48 | /// Gets all currently-connected devices associated with this [Transport]. 49 | /// 50 | /// This method does not work for Bluetooth devices. Use 51 | /// [`watch()`][] instead. 52 | /// 53 | /// [`watch()`]: Transport::watch 54 | async fn tokens(&self) -> Result, WebauthnCError>; 55 | } 56 | 57 | /// Represents a connection to a single FIDO token over a [Transport]. 58 | /// 59 | /// This is a low level interface to FIDO tokens, passing raw messages. 60 | /// [crate::ctap2] provides a higher level abstraction. 61 | #[async_trait] 62 | pub trait Token: Sized + fmt::Debug + Sync + Send { 63 | type Id: Sized + fmt::Debug + Sync + Send; 64 | 65 | fn has_button(&self) -> bool { 66 | true 67 | } 68 | 69 | /// Gets the transport layer used for communication with this token. 70 | fn get_transport(&self) -> AuthenticatorTransport; 71 | 72 | /// Transmit a CBOR message to a token, and deserialises the response. 73 | async fn transmit<'a, C, R, U>(&mut self, cmd: C, ui: &U) -> Result 74 | where 75 | C: CBORCommand, 76 | R: CBORResponse, 77 | U: UiCallback, 78 | { 79 | let cbor = cmd.cbor().map_err(|_| WebauthnCError::Cbor)?; 80 | trace!(">>> {}", hex::encode(&cbor)); 81 | 82 | let resp = self.transmit_raw(&cbor, ui).await?; 83 | 84 | trace!("<<< {}", hex::encode(&resp)); 85 | R::try_from(resp.as_slice()).map_err(|_| { 86 | //error!("error: {:?}", e); 87 | WebauthnCError::Cbor 88 | }) 89 | } 90 | 91 | /// Transmits a command on the underlying transport. 92 | /// 93 | /// `cbor` is a CBOR-encoded command. 94 | /// 95 | /// Interfaces need to check for and return any transport-layer-specific 96 | /// error code [WebauthnCError::Ctap], but don't need to worry about 97 | /// deserialising CBOR. 98 | async fn transmit_raw(&mut self, cbor: &[u8], ui: &U) -> Result, WebauthnCError> 99 | where 100 | U: UiCallback; 101 | 102 | /// Cancels a pending request. 103 | async fn cancel(&mut self) -> Result<(), WebauthnCError>; 104 | 105 | /// Initializes the [Token] 106 | async fn init(&mut self) -> Result<(), WebauthnCError>; 107 | 108 | /// Closes the [Token] 109 | async fn close(&mut self) -> Result<(), WebauthnCError>; 110 | } 111 | -------------------------------------------------------------------------------- /tutorial/server/actix_web/src/session.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | use std::sync::Mutex; 3 | use std::{collections::HashMap, sync::LazyLock}; 4 | 5 | use actix_session::storage::{LoadError, SaveError, SessionKey, SessionStore, UpdateError}; 6 | use actix_web::cookie::time::Duration; 7 | use anyhow::anyhow; 8 | use chrono::Utc; 9 | use rand::distr::{Alphanumeric, SampleString}; 10 | 11 | /** 12 | Static map where session states are stored 13 | */ 14 | static SESSION_STATES: LazyLock>> = 15 | LazyLock::new(|| Mutex::new(HashMap::new())); 16 | 17 | pub(crate) struct State { 18 | session_state: HashMap, 19 | valid_until: chrono::DateTime, 20 | } 21 | 22 | /** 23 | Implementation of the [SessionStore] trait of [actix_session]. 24 | */ 25 | #[derive(Default)] 26 | pub(crate) struct MemorySession; 27 | 28 | impl SessionStore for MemorySession { 29 | async fn load( 30 | &self, 31 | session_key: &SessionKey, 32 | ) -> Result>, LoadError> { 33 | let now = Utc::now(); 34 | 35 | Ok(SESSION_STATES 36 | .lock() 37 | .map_err(|_| LoadError::Other(anyhow!("Poison Error")))? 38 | .get(session_key.as_ref()) 39 | .filter(|&v| v.valid_until >= now) 40 | .map(|state| state.session_state.clone())) 41 | } 42 | 43 | async fn save( 44 | &self, 45 | session_state: HashMap, 46 | ttl: &Duration, 47 | ) -> Result { 48 | let mut session_key; 49 | 50 | loop { 51 | session_key = Alphanumeric.sample_string(&mut rand::rng(), 512); 52 | 53 | if !SESSION_STATES 54 | .lock() 55 | .map_err(|_| SaveError::Other(anyhow!("Poison Error")))? 56 | .contains_key(&session_key) 57 | { 58 | break; 59 | } 60 | } 61 | 62 | SESSION_STATES 63 | .lock() 64 | .map_err(|_| SaveError::Other(anyhow!("Poison Error")))? 65 | .insert( 66 | session_key.clone(), 67 | State { 68 | session_state, 69 | valid_until: Utc::now() 70 | .add(chrono::Duration::nanoseconds(ttl.whole_nanoseconds() as i64)), 71 | }, 72 | ); 73 | 74 | SessionKey::try_from(session_key) 75 | .map_err(|_| SaveError::Serialization(anyhow!("Invalid Session Key Error"))) 76 | } 77 | 78 | async fn update( 79 | &self, 80 | session_key: SessionKey, 81 | session_state: HashMap, 82 | ttl: &Duration, 83 | ) -> Result { 84 | if let Some(entry) = SESSION_STATES 85 | .lock() 86 | .map_err(|_| UpdateError::Other(anyhow!("Poison Error")))? 87 | .get_mut(session_key.as_ref()) 88 | { 89 | entry.valid_until = 90 | Utc::now().add(chrono::Duration::nanoseconds(ttl.whole_nanoseconds() as i64)); 91 | entry.session_state = session_state; 92 | 93 | Ok(session_key) 94 | } else { 95 | Err(UpdateError::Other(anyhow!( 96 | "Didn't found session with that key" 97 | ))) 98 | } 99 | } 100 | 101 | async fn update_ttl( 102 | &self, 103 | session_key: &SessionKey, 104 | ttl: &Duration, 105 | ) -> Result<(), anyhow::Error> { 106 | if let Some(entry) = SESSION_STATES 107 | .lock() 108 | .map_err(|_| anyhow!("Poison Error"))? 109 | .get_mut(session_key.as_ref()) 110 | { 111 | entry.valid_until = 112 | Utc::now().add(chrono::Duration::nanoseconds(ttl.whole_nanoseconds() as i64)); 113 | } 114 | 115 | Ok(()) 116 | } 117 | 118 | async fn delete(&self, session_key: &SessionKey) -> Result<(), anyhow::Error> { 119 | SESSION_STATES 120 | .lock() 121 | .map_err(|_| anyhow!("Poison Error"))? 122 | .remove(session_key.as_ref()); 123 | 124 | Ok(()) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tutorial/server/axum/assets/js/auth.js: -------------------------------------------------------------------------------- 1 | function register () { 2 | let username = document.getElementById('username').value; 3 | if (username === "") { 4 | alert("Please enter a username"); 5 | return; 6 | } 7 | 8 | fetch('http://localhost:8080/register_start/' + encodeURIComponent(username), { 9 | method: 'POST' 10 | }) 11 | .then(response => response.json() ) 12 | .then(credentialCreationOptions => { 13 | credentialCreationOptions.publicKey.challenge = Base64.toUint8Array(credentialCreationOptions.publicKey.challenge); 14 | credentialCreationOptions.publicKey.user.id = Base64.toUint8Array(credentialCreationOptions.publicKey.user.id); 15 | credentialCreationOptions.publicKey.excludeCredentials?.forEach(function (listItem) { 16 | listItem.id = Base64.toUint8Array(listItem.id) 17 | }); 18 | 19 | return navigator.credentials.create({ 20 | publicKey: credentialCreationOptions.publicKey 21 | }); 22 | }) 23 | .then((credential) => { 24 | fetch('http://localhost:8080/register_finish', { 25 | method: 'POST', 26 | headers: { 27 | 'Content-Type': 'application/json' 28 | }, 29 | body: JSON.stringify({ 30 | id: credential.id, 31 | rawId: Base64.fromUint8Array(new Uint8Array(credential.rawId), true), 32 | type: credential.type, 33 | response: { 34 | attestationObject: Base64.fromUint8Array(new Uint8Array(credential.response.attestationObject), true), 35 | clientDataJSON: Base64.fromUint8Array(new Uint8Array(credential.response.clientDataJSON), true), 36 | }, 37 | }) 38 | }) 39 | .then((response) => { 40 | const flash_message = document.getElementById('flash_message'); 41 | if (response.ok){ 42 | flash_message.innerHTML = "Successfully registered!"; 43 | } else { 44 | flash_message.innerHTML = "Error whilst registering!"; 45 | } 46 | }); 47 | }) 48 | } 49 | 50 | function login() { 51 | let username = document.getElementById('username').value; 52 | if (username === "") { 53 | alert("Please enter a username"); 54 | return; 55 | } 56 | 57 | fetch('http://localhost:8080/login_start/' + encodeURIComponent(username), { 58 | method: 'POST' 59 | }) 60 | .then(response => response.json()) 61 | .then((credentialRequestOptions) => { 62 | credentialRequestOptions.publicKey.challenge = Base64.toUint8Array(credentialRequestOptions.publicKey.challenge); 63 | credentialRequestOptions.publicKey.allowCredentials?.forEach(function (listItem) { 64 | listItem.id = Base64.toUint8Array(listItem.id) 65 | }); 66 | 67 | return navigator.credentials.get({ 68 | publicKey: credentialRequestOptions.publicKey 69 | }); 70 | }) 71 | .then((assertion) => { 72 | fetch('http://localhost:8080/login_finish', { 73 | method: 'POST', 74 | headers: { 75 | 'Content-Type': 'application/json' 76 | }, 77 | body: JSON.stringify({ 78 | id: assertion.id, 79 | rawId: Base64.fromUint8Array(new Uint8Array(assertion.rawId), true), 80 | type: assertion.type, 81 | response: { 82 | authenticatorData: Base64.fromUint8Array(new Uint8Array(assertion.response.authenticatorData), true), 83 | clientDataJSON: Base64.fromUint8Array(new Uint8Array(assertion.response.clientDataJSON), true), 84 | signature: Base64.fromUint8Array(new Uint8Array(assertion.response.signature), true), 85 | userHandle: Base64.fromUint8Array(new Uint8Array(assertion.response.userHandle), true) 86 | }, 87 | }), 88 | }) 89 | .then((response) => { 90 | const flash_message = document.getElementById('flash_message'); 91 | if (response.ok){ 92 | flash_message.innerHTML = "Successfully logged in!"; 93 | } else { 94 | flash_message.innerHTML = "Error whilst logging in!"; 95 | } 96 | }); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Webauthn-rs 2 | ========== 3 | 4 | Webauthn is a modern approach to hardware based authentication, consisting of 5 | a user with an authenticator device, a browser or client that interacts with the 6 | device, and a server that is able to generate challenges and verify the 7 | authenticator's validity. 8 | 9 | Users are able to enroll their own tokens through a registration process to 10 | be associated to their accounts, and then are able to login using the token 11 | which performas a cryptographic authentication. 12 | 13 | This library aims to provide useful functions and frameworks allowing you to 14 | integrate webauthn into Rust web servers. This means the library implements the 15 | Relying Party component of the Webauthn/FIDO2 workflow. We provide template and 16 | example javascript and wasm bindings to demonstrate the browser interactions required. 17 | 18 | Code of Conduct 19 | --------------- 20 | 21 | See our [code of conduct] 22 | 23 | [code of conduct]: https://github.com/kanidm/webauthn-rs/blob/master/CODE_OF_CONDUCT.md 24 | 25 | Blockchain Support Policy 26 | ------------------------- 27 | 28 | This project does not and will not support any blockchain related use cases. We will not accept issues 29 | from organisations (or employees thereof) whose primary business is blockchain, cryptocurrency, NFTs 30 | or so-called “Web 3.0 technology”. This statement does not affect the rights and responsibilities 31 | granted under the project’s open source license(s). 32 | 33 | If you have further questions about the scope of this statement and whether it impacts you, please 34 | email webauthn at firstyear.id.au 35 | 36 | Documentation 37 | ------------- 38 | 39 | This library consists of multiple major parts. 40 | 41 | A safe, use-case driven api, which is defined in [Webauthn-RS](https://docs.rs/webauthn-rs/) 42 | 43 | The low level, protocol level interactions which is defined in [Webauthn-Core-RS](https://docs.rs/webauthn-rs-core/) 44 | 45 | Protocol bindings which are defined in [Webauthn-RS-proto](https://docs.rs/webauthn-rs-proto/) 46 | 47 | The FIDO MDS (authenticator transparency) parser [FIDO-MDS](https://docs.rs/fido-mds/) 48 | 49 | We strongly recommend you use the safe api, as webauthn has many sharp edges and ways to hold it wrong! 50 | 51 | Demonstration 52 | ------------- 53 | 54 | You can test this library via our [demonstration site](https://webauthn.firstyear.id.au/) 55 | 56 | Or you can run the demonstration your self locally with: 57 | 58 | cd compat_tester/webauthn-rs-demo 59 | cargo run 60 | 61 | For additional configuration options for the demo site: 62 | 63 | cargo run -- --help 64 | 65 | Known Supported Keys/Harwdare 66 | ----------------------------- 67 | 68 | We have extensively tested a variety of keys and devices, not limited to: 69 | 70 | * Yubico 5c / 5ci / FIPS / Bio 71 | * TouchID / FaceID (iPhone, iPad, MacBook Pro) 72 | * Android 73 | * Windows Hello (TPM) 74 | * Softtokens 75 | 76 | If your key/browser combination don't work (generally due to missing crypto routines) 77 | please conduct a [compatability test](https://webauthn.firstyear.id.au/compat_test) and then open 78 | an issue so that we can resolve the issue! 79 | 80 | Known BROKEN Keys/Hardware 81 | -------------------------- 82 | 83 | * Pixel 3a / Pixel 4 + Chrome - Does not send correct attestation certificates, 84 | and ignores requested algorithms. Not resolved. 85 | * Windows Hello with Older TPMs - Often use RSA-SHA1 signatures over attestation which may allow credential compromise/falsification. 86 | 87 | Standards Compliance 88 | -------------------- 89 | 90 | This library has been carefully implemented to follow the w3c standard for webauthn level 3+ processing 91 | to ensure secure and correct behaviour. We support most major extensions and key types, but we do not claim 92 | to be standards complaint because: 93 | 94 | * We have enforced extra constraints in the library that go above and beyond the security guarantees the standard offers. 95 | * We do not support certain esoteric options. 96 | * We do not support all cryptographic primitive types (only limited to secure ones). 97 | * A large number of advertised features in webauthn do not function in the real world. 98 | 99 | This library has passed a security audit performed by SUSE product security. Other security reviews 100 | are welcome! 101 | 102 | 103 | --------------------------------------------------------------------------------