From 0186b2ee400c3310d28b406b6e081be6f6c06dd5 Mon Sep 17 00:00:00 2001 From: Nick Zana Date: Wed, 10 May 2023 13:51:40 -0400 Subject: [PATCH] Add webauthn3-proto crate The webauthn3-proto crate provides a FIDO WebAuthn3 API definition that implements the w3c CredentialManagement API defined in the credential-management-proto crate. --- crates/webauthn3-proto/.gitignore | 2 + crates/webauthn3-proto/Cargo.toml | 15 +++ crates/webauthn3-proto/src/attestation.rs | 28 +++++ crates/webauthn3-proto/src/authenticator.rs | 35 ++++++ crates/webauthn3-proto/src/client.rs | 49 ++++++++ crates/webauthn3-proto/src/lib.rs | 34 ++++++ crates/webauthn3-proto/src/public_key.rs | 51 +++++++++ .../webauthn3-proto/src/public_key/create.rs | 106 ++++++++++++++++++ .../webauthn3-proto/src/public_key/request.rs | 46 ++++++++ crates/webauthn3-proto/src/token.rs | 20 ++++ 10 files changed, 386 insertions(+) create mode 100644 crates/webauthn3-proto/.gitignore create mode 100644 crates/webauthn3-proto/Cargo.toml create mode 100644 crates/webauthn3-proto/src/attestation.rs create mode 100644 crates/webauthn3-proto/src/authenticator.rs create mode 100644 crates/webauthn3-proto/src/client.rs create mode 100644 crates/webauthn3-proto/src/lib.rs create mode 100644 crates/webauthn3-proto/src/public_key.rs create mode 100644 crates/webauthn3-proto/src/public_key/create.rs create mode 100644 crates/webauthn3-proto/src/public_key/request.rs create mode 100644 crates/webauthn3-proto/src/token.rs diff --git a/crates/webauthn3-proto/.gitignore b/crates/webauthn3-proto/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/crates/webauthn3-proto/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/crates/webauthn3-proto/Cargo.toml b/crates/webauthn3-proto/Cargo.toml new file mode 100644 index 0000000..f19abed --- /dev/null +++ b/crates/webauthn3-proto/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "webauthn3-proto" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base64 = "0.21.0" +credential-management-proto = { path = "../credential-management-proto" } +fido-common = { path = "../fido-common" } +serde = { version = "1.0", features = ["derive"], optional = true } + +[features] +serde = ["dep:serde"] diff --git a/crates/webauthn3-proto/src/attestation.rs b/crates/webauthn3-proto/src/attestation.rs new file mode 100644 index 0000000..a6e1707 --- /dev/null +++ b/crates/webauthn3-proto/src/attestation.rs @@ -0,0 +1,28 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug)] +/// > WebAuthn Relying Parties may use AttestationConveyancePreference to +/// > specify their preference regarding attestation conveyance during +/// > credential generation. +/// > +/// > +pub enum ConveyancePreference { + /// > The Relying Party is not interested in authenticator attestation. + #[cfg_attr(feature = "serde", serde(rename = "none"))] + None, + /// > The Relying Party wants to receive a verifiable attestation statement, + /// > but allows the client to decide how to obtain such an attestation + /// > statement. + #[cfg_attr(feature = "serde", serde(rename = "indirect"))] + Indirect, + /// > The Relying Party wants to receive the attestation statement as + /// > generated by the authenticator. + #[cfg_attr(feature = "serde", serde(rename = "direct"))] + Direct, + /// > The Relying Party wants to receive an attestation statement that may + /// > include uniquely identifying information. + #[cfg_attr(feature = "serde", serde(rename = "enterprise"))] + Enterprise, +} diff --git a/crates/webauthn3-proto/src/authenticator.rs b/crates/webauthn3-proto/src/authenticator.rs new file mode 100644 index 0000000..925d1ee --- /dev/null +++ b/crates/webauthn3-proto/src/authenticator.rs @@ -0,0 +1,35 @@ +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy)] +/// > This enumeration’s values describe authenticators' attachment modalities. +/// > Relying Parties use this to express a preferred authenticator attachment +/// > modality when calling `navigator.credentials.create()` to create a +/// > credential, and clients use this to report the authenticator attachment +/// > modality used to complete a registration or authentication ceremony. +/// > +/// > +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Attachment { + #[cfg_attr(feature = "serde", serde(rename = "platform"))] + Platform, + #[cfg_attr(feature = "serde", serde(rename = "cross-platform"))] + CrossPlatform, +} + +#[derive(Debug)] +/// Contains the contents of an authenticator's response to a Relying Party's +/// request. +/// +/// > +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Response { + // TODO: Fill out response??? + // Attestation { + // client_data: client::Data<{ client::DataType::Create }>, + // attestation_object: Vec, + // }, + // Assertion { + // client_data: client::Data<{ client::DataType::Get }>, + // }, +} diff --git a/crates/webauthn3-proto/src/client.rs b/crates/webauthn3-proto/src/client.rs new file mode 100644 index 0000000..1cb9379 --- /dev/null +++ b/crates/webauthn3-proto/src/client.rs @@ -0,0 +1,49 @@ +use crate::token; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DataType { + #[cfg_attr(feature = "serde", serde(rename = "webauthn.create"))] + Create, + #[cfg_attr(feature = "serde", serde(rename = "webauthn.get"))] + Get, +} + +/// > The client data represents the contextual bindings of both the +/// > `WebAuthn` Relying Party and the client. +/// > +/// > +pub struct Data { + /// > This member contains the base64url encoding of the challenge + /// > provided by the Relying Party. + pub challenge: String, + /// > This member contains the fully qualified origin of the requester, + /// > as provided to the authenticator by the client, in the syntax + /// > defined by + /// > [RFC6454](https://www.w3.org/TR/webauthn-3/#biblio-rfc6454). + pub origin: String, + // TODO: Description + pub cross_origin: Option, + /// > ...contains information about the state of the Token Binding + /// > protocol... used when communicating with the Relying Party. Its + /// > absence indicates that the client doesn’t support token binding. + pub token_binding: Option, +} + +#[cfg(feature = "serde")] +impl Serialize for Data { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Keys are: "type", "challenge", "origin", "topOrigin", "crossOrigin" + const LEN: usize = 5; + let mut map = serializer.serialize_map(Some(LEN))?; + + // map.serialize_entry("type", value) + todo!() + } +} diff --git a/crates/webauthn3-proto/src/lib.rs b/crates/webauthn3-proto/src/lib.rs new file mode 100644 index 0000000..883f9e3 --- /dev/null +++ b/crates/webauthn3-proto/src/lib.rs @@ -0,0 +1,34 @@ +#![feature(async_fn_in_trait, adt_const_params, associated_const_equality)] +#![allow(incomplete_features, clippy::unused_async, clippy::doc_markdown)] + +pub mod attestation; +pub mod authenticator; +pub mod client; +pub mod public_key; +pub mod token; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Debug, Clone, Copy)] +/// > A WebAuthn Relying Party may require user verification for some of its +/// > operations but not for others, and may use this type to express its needs. +pub enum UserVerificationRequirement { + /// > The Relying Party requires user verification for the operation and + /// > will fail the overall ceremony if the response does not have the UV + /// > flag set. The client MUST return an error if user verification cannot + /// > be performed. + #[cfg_attr(feature = "serde", serde(rename = "required"))] + Required, + /// > The Relying Party prefers user verification for the operation if + /// > possible, but will not fail the operation if the response does not + /// > have the UV flag set. + #[cfg_attr(feature = "serde", serde(rename = "preferred"))] + Preferred, + /// > The Relying Party does not want user verification employed during the + /// > operation (e.g., in the interest of minimizing disruption to the user + /// > interaction flow). + #[cfg_attr(feature = "serde", serde(rename = "discouraged"))] + Discouraged, +} diff --git a/crates/webauthn3-proto/src/public_key.rs b/crates/webauthn3-proto/src/public_key.rs new file mode 100644 index 0000000..e42759e --- /dev/null +++ b/crates/webauthn3-proto/src/public_key.rs @@ -0,0 +1,51 @@ +use crate::authenticator; +use credential_management_proto::{credential, discovery}; + +pub mod create; +pub mod request; + +/// > The [`public_key::Credential`] interface inherits from +/// > [`credential::Credential`], and contains the attributes that are returned +/// > to the caller when a new credential is created, or a new assertion is +/// > requested. +/// > +/// > +pub trait Credential: credential::Credential { + /// Returns the raw byte array of the credential's `id`. + fn raw_id(&self) -> &[u8]; + + /// > This attribute contains the authenticator's response to the client’s + /// > request to either create a public key credential, or generate an + /// > authentication assertion. If the [`public_key::Credential`] is created + /// > in response to `create()`, this attribute’s value will be an + /// > [`authenticator::Response::Attestation`], otherwise, the + /// > [`public_key::Credential`] was created in response to `get()`, and + /// > this attribute’s value will be an + /// > [`authenticator::Response::Assertion`]. + fn response(&self) -> &authenticator::Response; + + /// > This attribute reports the authenticator attachment modality in effect + /// > at the time the `navigator.credentials.create()` or + /// > `navigator.credentials.get()` methods successfully complete. + /// + /// If the attachment method is unknown, this function returns `None`. + fn authenticator_attachment(&self) -> Option; +} + +pub trait Container: + for<'a> credential::Container< + Credential = Self::PublicKeyCredential, + RequestOptions = Self::PublicKeyRequestOptions, + CreateOptions = Self::PublicKeyCreateOptions, + DISCOVERY_MODE = { discovery::Mode::Remote }, +> +{ + type PublicKeyCredential: Credential; + type PublicKeyRequestOptions: request::Options; + type PublicKeyCreateOptions: create::Options; + + /// > ...indicate[s] availability for conditional mediation. + /// > + /// > + async fn is_conditional_mediation_available() -> bool; +} diff --git a/crates/webauthn3-proto/src/public_key/create.rs b/crates/webauthn3-proto/src/public_key/create.rs new file mode 100644 index 0000000..519245b --- /dev/null +++ b/crates/webauthn3-proto/src/public_key/create.rs @@ -0,0 +1,106 @@ +use fido_common::{attestation::FormatIdentifier, credential::public_key}; + +use crate::{attestation, authenticator, UserVerificationRequirement}; + +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// > +pub trait Options { + /// > This member contains a name and an identifier for the Relying Party + /// > responsible for the request. + fn public_key_credential_relying_party_entity(&self) -> &public_key::RelyingPartyEntity; + + /// > This member contains names and an identifier for the user account + /// > performing the registration. + fn public_key_credential_user_entity(&self) -> &public_key::UserEntity; + + /// > This member specifies a challenge that the authenticator signs, along + /// > with other data, when producing an attestation object for the newly + /// > created credential. + fn challenge(&self) -> &[u8]; + + /// > This member lists the key types and signature algorithms the Relying + /// > Party supports, ordered from most preferred to least preferred. + fn public_key_credential_parameters(&self) -> &[public_key::Parameters]; + + /// > This OPTIONAL member specifies a time, in milliseconds, that the + /// > Relying Party is willing to wait for the call to complete. This is + /// > treated as a hint, and MAY be overridden by the client. + fn timeout(&self) -> Option; + + /// > The Relying Party SHOULD use this OPTIONAL member to list any existing + /// > credentials mapped to this user account (as identified by `user.id`). + /// > This ensures that the new credential is not created on an + /// > authenticator that already contains a credential mapped to this user + /// > account. If it would be, the client is requested to instead guide the + /// > user to use a different authenticator, or return an error if that + /// > fails. + fn exclude_credentials(&self) -> Option<&[public_key::Descriptor]>; + + /// > The Relying Party MAY use this OPTIONAL member to specify capabilities + /// > and settings that the authenticator MUST or SHOULD satisfy to + /// > participate in the `create()` operation. + fn authenticator_selection(&self) -> Option; + + /// > The Relying Party MAY use this OPTIONAL member to specify a preference + /// > regarding attestation conveyance. + fn attestation(&self) -> Option; + + /// > The Relying Party MAY use this OPTIONAL member to specify a preference + /// > regarding the attestation statement format used by the authenticator. + fn attestation_formats(&self) -> &[FormatIdentifier]; +} + +/// > WebAuthn Relying Parties may use the [`AuthenticatorSelectionCriteria`] +/// > dictionary to specify their requirements regarding authenticator +/// > attributes. +/// > +/// > +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct AuthenticatorSelectionCriteria { + /// > If this member is present, eligible authenticators are filtered to be + /// > only those authenticators attached with the specified authenticator + /// > attachment modality... If this member is absent, then any attachment + /// > modality is acceptable. + #[cfg_attr(feature = "serde", serde(rename = "authenticatorAttachment"))] + pub attachment: Option, + /// > Specifies the extent to which the Relying Party desires to create a + /// > client-side discoverable credential. + #[cfg_attr(feature = "serde", serde(rename = "residentKey"))] + pub resident_key_requirement: ResidentKeyRequirement, + /// > This member specifies the Relying Party's requirements regarding user + /// > verification for the `create()` operation. + pub user_verification_requirement: UserVerificationRequirement, +} + +/// > This enumeration’s values describe the Relying Party's requirements for +/// > client-side discoverable credentials (formerly known as resident +/// > credentials or resident keys): +/// > +/// > +#[derive(Debug, Clone, Copy)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +pub enum ResidentKeyRequirement { + /// > The Relying Party prefers creating a server-side credential, but will + /// > accept a client-side discoverable credential. The client and + /// > authenticator SHOULD create a server-side credential if possible. + Discouraged, + /// > The Relying Party strongly prefers creating a client-side discoverable + /// > credential, but will accept a server-side credential. The client and + /// > authenticator SHOULD create a discoverable credential if possible. For + /// > example, the client SHOULD guide the user through setting up user + /// > verification if needed to create a discoverable credential. This takes + /// > precedence over the setting of + /// > [`AuthenticatorSelectionCriteria::user_verification`]. + Preferred, + /// > The Relying Party requires a client-side discoverable credential. The + /// > client MUST return an error if a client-side discoverable credential + /// > cannot be created. + Required, +} diff --git a/crates/webauthn3-proto/src/public_key/request.rs b/crates/webauthn3-proto/src/public_key/request.rs new file mode 100644 index 0000000..6434fee --- /dev/null +++ b/crates/webauthn3-proto/src/public_key/request.rs @@ -0,0 +1,46 @@ +use fido_common::{attestation::FormatIdentifier, credential::public_key}; + +use crate::{attestation, UserVerificationRequirement}; + +/// > [This struct] supplies `get()` with the data it needs to generate an +/// > assertion. +pub trait Options { + /// > This member specifies a challenge that the authenticator signs, along + /// > with other data, when producing an authentication assertion. + fn challenge(&self) -> &[u8]; + /// > This OPTIONAL member specifies a time, in milliseconds, that the + /// > Relying Party is willing to wait for the call to complete. The value + /// > is treated as a hint, and MAY be overridden by the client. + fn timeout(&self) -> Option; + /// > This OPTIONAL member specifies the RP ID claimed by the Relying Party. + /// > The client MUST verify that the Relying Party's origin matches the + /// > scope of this RP ID. The authenticator MUST verify that this RP ID + /// > exactly equals the rpId of the credential to be used for the + /// > authentication ceremony. + /// > + /// > If not specified, its value will be the [`CredentialsContainer`] + /// > object’s relevant settings object's origin's effective domain. + fn relying_party_id(&self) -> Option<&str>; + /// > This OPTIONAL member is used by the client to find authenticators + /// > eligible for this authentication ceremony. + /// > ... + /// > If not empty, the client MUST return an error if none of the listed + /// > credentials can be used. + /// > + /// > The list is ordered in descending order of preference: the first item + /// > in the list is the most preferred credential, and the last is the + /// > least preferred. + fn allow_credentials(&self) -> Option<&[public_key::Descriptor]>; + /// > ... specifies the Relying Party's requirements regarding user + /// > verification for the get() operation... Eligible authenticators are + /// > filtered to only those capable of satisfying this requirement. + fn user_verification(&self) -> Option; + /// > The Relying Party MAY use this OPTIONAL member to specify a preference + /// > regarding attestation conveyance. + fn attestation(&self) -> Option; + /// > The Relying Party MAY use this OPTIONAL member to specify a preference + /// > regarding the attestation statement format used by the + /// > authenticator... Values are ordered from most preferable to least + /// > preferable. + fn attestation_formats(&self) -> Option<&[FormatIdentifier]>; +} diff --git a/crates/webauthn3-proto/src/token.rs b/crates/webauthn3-proto/src/token.rs new file mode 100644 index 0000000..2fd466b --- /dev/null +++ b/crates/webauthn3-proto/src/token.rs @@ -0,0 +1,20 @@ +pub enum BindingStatus { + /// > Indicates token binding was used when communicating with the + /// > Relying + /// > Party. In this case, the `TokenBinding::id` member MUST be + /// > present. + Present, + /// > Indicates the client supports token binding, but it was not + /// > negotiated + /// > when communicating with the Relying Party. + Supported, +} + +pub struct Binding { + /// > ...a base64url encoding of the Token Binding ID that was used when + /// > communicating with the Relying Party. + pub id: String, + /// Indicates the usage and support status of token binding by the + /// client. + pub status: BindingStatus, +}