diff --git a/xyz-iinuwa-credential-manager-portal-gtk/data/resources/ui/window.ui b/xyz-iinuwa-credential-manager-portal-gtk/data/resources/ui/window.ui index 41157812..3f63456a 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/data/resources/ui/window.ui +++ b/xyz-iinuwa-credential-manager-portal-gtk/data/resources/ui/window.ui @@ -148,29 +148,6 @@ - - - internal - This device - - - vertical - - - Enter your device PIN - - - - - - Enter your device PIN - - - - - - - completed diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/cbor.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/cbor.rs index 9a3c5f11..3c3eef6a 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/cbor.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/cbor.rs @@ -139,6 +139,7 @@ where } } +#[allow(dead_code)] enum MajorType { PositiveInteger, NegativeInteger, diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/cose.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/cose.rs index 752336f5..b798033f 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/cose.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/cose.rs @@ -1,68 +1,14 @@ use libwebauthn::proto::ctap2::Ctap2COSEAlgorithmIdentifier; -use ring::{ - rand::SystemRandom, - signature::{ - EcdsaKeyPair, Ed25519KeyPair, KeyPair, RsaKeyPair, ECDSA_P256_SHA256_ASN1_SIGNING, - }, -}; use tracing::debug; #[derive(Clone, Copy, Debug, PartialEq)] #[repr(i64)] pub(super) enum CoseKeyType { - ES256_P256, - EDDSA_ED25519, + Es256P256, + EddsaEd25519, RS256, } -impl CoseKeyType { - pub fn algorithm(&self) -> CoseKeyAlgorithmIdentifier { - let params: CoseKeyParameters = (*self).into(); - params.algorithm() - } -} - -impl CoseKeyType { - pub fn curve(&self) -> Option { - let params: CoseKeyParameters = (*self).into(); - params.curve() - } -} - -pub(super) struct CoseKeyParameters { - alg: CoseKeyAlgorithmIdentifier, - crv: Option, -} - -impl CoseKeyParameters { - pub fn algorithm(&self) -> CoseKeyAlgorithmIdentifier { - self.alg - } - - pub fn curve(&self) -> Option { - self.crv - } -} - -impl From for CoseKeyParameters { - fn from(value: CoseKeyType) -> Self { - match value { - CoseKeyType::ES256_P256 => CoseKeyParameters { - alg: CoseKeyAlgorithmIdentifier::ES256, - crv: Some(CoseEllipticCurveIdentifier::P256), - }, - CoseKeyType::EDDSA_ED25519 => CoseKeyParameters { - alg: CoseKeyAlgorithmIdentifier::EdDSA, - crv: Some(CoseEllipticCurveIdentifier::Ed25519), - }, - CoseKeyType::RS256 => CoseKeyParameters { - alg: CoseKeyAlgorithmIdentifier::RS256, - crv: None, - }, - } - } -} - #[derive(Clone, Copy, Debug, PartialEq)] pub enum CoseKeyAlgorithmIdentifier { ES256, @@ -99,7 +45,7 @@ impl TryFrom for CoseKeyAlgorithmIdentifier { Ctap2COSEAlgorithmIdentifier::ES256 => Ok(CoseKeyAlgorithmIdentifier::ES256), Ctap2COSEAlgorithmIdentifier::TOPT => { debug!("Unknown public key algorithm type: {:?}", value); - return Err(Error::Unsupported); + Err(Error::Unsupported) } } } @@ -133,99 +79,3 @@ pub enum Error { InvalidKey, Unsupported, } - -pub(super) fn encode_pkcs8_key(key_type: CoseKeyType, pkcs8_key: &[u8]) -> Result, Error> { - match key_type { - CoseKeyType::ES256_P256 => { - let key_pair = EcdsaKeyPair::from_pkcs8( - &ECDSA_P256_SHA256_ASN1_SIGNING, - pkcs8_key, - &SystemRandom::new(), - ) - .unwrap(); - let public_key = key_pair.public_key().as_ref(); - // ring outputs public keys with uncompressed 32-byte x and y coordinates - if public_key.len() != 65 || public_key[0] != 0x04 { - return Err(Error::InvalidKey); - } - let (x, y) = public_key[1..].split_at(32); - let mut cose_key: Vec = Vec::new(); - cose_key.push(0b101_00101); // map with 5 items - cose_key.extend([0b000_00001, 0b000_00010]); // kty (1): EC2 (2) - cose_key.extend([0b000_00011, 0b001_00110]); // alg (3): ECDSA-SHA256 (-7) - cose_key.extend([0b001_00000, 0b000_00001]); // crv (-1): P256 (1) - cose_key.extend([0b001_00001, 0b010_11000, 0b0010_0000]); // x (-2): <32-byte string> - cose_key.extend(x); - cose_key.extend([0b001_00010, 0b010_11000, 0b0010_0000]); // y (-3): <32-byte string> - cose_key.extend(y); - Ok(cose_key) - } - CoseKeyType::EDDSA_ED25519 => { - let key_pair = Ed25519KeyPair::from_pkcs8(pkcs8_key).map_err(|_| Error::InvalidKey)?; - let public_key = key_pair.public_key().as_ref(); - let mut cose_key: Vec = Vec::new(); - cose_key.push(0b101_00100); // map with 4 items - cose_key.extend([0b000_00001, 0b000_00001]); // kty (1): OKP (1) - cose_key.extend([0b000_00011, 0b001_00111]); // alg (3): EdDSA (-8) - cose_key.extend([0b001_00000, 0b000_00110]); // crv (-1): ED25519 (6) - cose_key.extend([0b001_00001, 0b010_11000, 0b0010_0000]); // x (-2): <32-byte string> - cose_key.extend(public_key); - Ok(cose_key) - } - CoseKeyType::RS256 => { - let key_pair = RsaKeyPair::from_pkcs8(pkcs8_key).map_err(|_| Error::InvalidKey)?; - let public_key = key_pair.public_key().as_ref(); - // TODO: This is ASN.1 with DER encoding. We could parse this to extract - // the modulus and exponent properly, but the key length will - // probably not change, so we're winging it - // https://stackoverflow.com/a/12750816/11931787 - let n = &public_key[9..(9 + 256)]; - let e = &public_key[public_key.len() - 3..]; - debug_assert_eq!(n.len(), key_pair.public().modulus_len()); - let mut cose_key: Vec = Vec::new(); - cose_key.push(0b101_00100); // map with 4 items - cose_key.extend([0b000_00001, 0b000_00010]); // kty (1): RSA (3) - cose_key.extend([0b000_00011, 0b001_00110]); // alg (3): RSASSA-PKCS1-v1_5 using SHA-256 (-257) - cose_key.extend([0b001_00000, 0b010_11001, 0b0000_0001, 0b0000_0000]); // n (-1): <256-byte string> - cose_key.extend(n); - cose_key.extend([0b001_00001, 0b010_00011]); // e (-2): <3-byte string> - cose_key.extend(e); - Ok(cose_key) - } - _ => todo!(), - } -} - -/// returns CTAP2-serialized public key and algorithm -pub(crate) fn encode_cose_key(public_key: &cosey::PublicKey) -> Result, Error> { - match public_key { - cosey::PublicKey::P256Key(p256_key) => { - let mut cose_key: Vec = Vec::new(); - cose_key.push(0b101_00101); // map with 5 items - cose_key.extend([0b000_00001, 0b000_00010]); // kty (1): EC2 (2) - cose_key.extend([0b000_00011, 0b001_00110]); // alg (3): ECDSA-SHA256 (-7) - cose_key.extend([0b001_00000, 0b000_00001]); // crv (-1): P256 (1) - cose_key.extend([0b001_00001, 0b010_11000, 0b0010_0000]); // x (-2): <32-byte string> - cose_key.extend(p256_key.x.clone()); - cose_key.extend([0b001_00010, 0b010_11000, 0b0010_0000]); // y (-3): <32-byte string> - cose_key.extend(p256_key.y.clone()); - Ok(cose_key) - } - cosey::PublicKey::Ed25519Key(ed25519_key) => { - // TODO: Check this - let mut cose_key: Vec = Vec::new(); - cose_key.push(0b101_00100); // map with 4 items - cose_key.extend([0b000_00001, 0b000_00001]); // kty (1): OKP (1) - cose_key.extend([0b000_00011, 0b001_00111]); // alg (3): EdDSA (-8) - cose_key.extend([0b001_00000, 0b000_00110]); // crv (-1): ED25519 (6) - cose_key.extend([0b001_00001, 0b010_11000, 0b0010_0000]); // x (-2): <32-byte string> - cose_key.extend(ed25519_key.x.clone()); - Ok(cose_key) - } - - _ => { - debug!("Cannot serialize unknown key type {:?}", public_key); - Err(Error::Unsupported) - } - } -} diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs index 9799c591..8e7c1c37 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/credential_service/mod.rs @@ -1,8 +1,4 @@ -use std::{ - ops::Add, - sync::{Arc, Mutex, OnceLock}, - time::{Duration, SystemTime, UNIX_EPOCH}, -}; +use std::sync::{Arc, Mutex, OnceLock}; use libwebauthn::{ self, @@ -15,7 +11,6 @@ use libwebauthn::{ use async_std::{ channel::TryRecvError, sync::{Arc as AsyncArc, Mutex as AsyncMutex}, - task, }; use tokio::runtime::Runtime; use tracing::{debug, warn}; @@ -25,7 +20,7 @@ use crate::{ CredentialRequest, CredentialResponse, GetAssertionResponseInternal, MakeCredentialResponseInternal, }, - view_model::{Device, InternalPinState, Transport}, + view_model::{Device, Transport}, }; #[derive(Debug)] @@ -35,11 +30,6 @@ pub struct CredentialService { usb_state: AsyncArc>, usb_uv_handler: UsbUvHandler, - internal_device_credentials: Vec, - internal_device_state: InternalDeviceState, - internal_pin_attempts_left: u32, - internal_pin_unlock_time: Option, - cred_request: CredentialRequest, // Place to store data to be returned to the caller cred_response: Arc>>, @@ -50,30 +40,10 @@ impl CredentialService { cred_request: CredentialRequest, cred_response: Arc>>, ) -> Self { - let devices = vec![ - Device { - id: String::from("0"), - transport: Transport::Usb, - }, - Device { - id: String::from("1"), - transport: Transport::Internal, - }, - ]; - let internal_device_credentials = vec![ - CredentialMetadata { - id: String::from("0"), - origin: String::from("foo.example.com"), - display_name: String::from("Foo"), - username: String::from("joecool"), - }, - CredentialMetadata { - id: String::from("1"), - origin: String::from("bar.example.org"), - display_name: String::from("Bar"), - username: String::from("cooliojoe"), - }, - ]; + let devices = vec![Device { + id: String::from("0"), + transport: Transport::Usb, + }]; let usb_state = AsyncArc::new(AsyncMutex::new(UsbState::Idle)); Self { devices, @@ -81,11 +51,6 @@ impl CredentialService { usb_state: usb_state.clone(), usb_uv_handler: UsbUvHandler::new(), - internal_device_credentials, - internal_device_state: InternalDeviceState::Idle, - internal_pin_attempts_left: 5, - internal_pin_unlock_time: None, - cred_request, cred_response, } @@ -97,7 +62,7 @@ impl CredentialService { pub(crate) async fn poll_device_discovery_usb(&mut self) -> Result { debug!("polling for USB status"); - let prev_usb_state = self.usb_state.lock().await.clone(); + let prev_usb_state = *self.usb_state.lock().await; let next_usb_state = match prev_usb_state { UsbState::Idle | UsbState::Waiting => { let devices = libwebauthn::transport::hid::list_devices().await.unwrap(); @@ -285,7 +250,6 @@ impl CredentialService { } } UsbState::Completed => Ok(prev_usb_state), - UsbState::UserCancelled => Ok(prev_usb_state), }?; *self.usb_state.lock().await = next_usb_state; @@ -299,7 +263,7 @@ impl CredentialService { } pub(crate) async fn validate_usb_device_pin(&mut self, pin: &str) -> Result<(), ()> { - let current_state = self.usb_state.lock().await.clone(); + let current_state = *self.usb_state.lock().await; match current_state { UsbState::NeedsPin { attempts_left: Some(attempts_left), @@ -311,91 +275,6 @@ impl CredentialService { } } - pub(crate) async fn get_internal_device_credentials( - &self, - ) -> Result<&Vec, ()> { - Ok(&self.internal_device_credentials) - } - - pub(crate) async fn validate_internal_device_pin( - &mut self, - pin: &str, - cred_id: &str, - ) -> Result { - // TODO: Should this have the selected credential ID included with it to make sure the - // frontend and backend are talking about the same credential? - let now = SystemTime::now(); - if let Some(unlock_time) = self.internal_pin_unlock_time { - if unlock_time < now { - let t = unlock_time.duration_since(UNIX_EPOCH).unwrap(); - return Ok(InternalPinState::LockedOut { unlock_time: t }); - } else { - self.internal_pin_unlock_time = None; - } - } - if pin == "123456" { - let device = self - .devices - .iter() - .find(|d| d.transport == Transport::Internal) - .unwrap() - .clone(); - self.internal_device_state = InternalDeviceState::Completed { - device, - cred_id: cred_id.to_owned(), - }; - Ok(InternalPinState::PinCorrect { - completion_token: "pin".to_string(), - }) - } else { - self.internal_device_state = InternalDeviceState::NeedsPin; - self.internal_pin_attempts_left -= 1; - if self.internal_pin_attempts_left > 0 { - Ok(InternalPinState::PinIncorrect { - attempts_left: self.internal_pin_attempts_left, - }) - } else { - let t = now.add(Duration::from_secs(10)); - self.internal_pin_unlock_time = Some(t); - Ok(InternalPinState::LockedOut { - unlock_time: t.duration_since(UNIX_EPOCH).unwrap(), - }) - } - } - } - - pub(crate) async fn start_device_discovery_internal( - &mut self, - ) -> Result { - println!("frontend: Start Internal flow"); - if let InternalDeviceState::Idle = self.internal_device_state { - self.internal_device_state = InternalDeviceState::NeedsPin; - Ok(self.internal_device_state.clone()) - } else { - Err(format!( - "Invalid state to begin discovery: {:?}", - self.internal_device_state - )) - } - } - - pub(crate) async fn poll_device_discovery_internal( - &mut self, - ) -> Result { - task::sleep(Duration::from_millis(5)).await; - - if let InternalDeviceState::Idle = self.internal_device_state { - return Err(String::from("Internal polling not started.")); - } - - Ok(self.internal_device_state.clone()) - } - - pub(crate) async fn cancel_device_discovery_internal(&mut self) -> Result<(), String> { - self.internal_device_state = InternalDeviceState::Idle; - Ok(()) - } - pub(crate) fn complete_auth(&mut self) { // let mut data = self.output_data.lock().unwrap(); // data.replace((self.cred_response)); @@ -415,58 +294,19 @@ pub enum UsbState { Connected, /// The device needs the PIN to be entered. - NeedsPin { - attempts_left: Option, - }, + NeedsPin { attempts_left: Option }, /// The device needs on-device user verification. - NeedsUserVerification { - attempts_left: Option, - }, + NeedsUserVerification { attempts_left: Option }, /// The device needs evidence of user presence (e.g. touch) to release the credential. NeedsUserPresence, /// USB tapped, received credential Completed, - + // TODO: implement cancellation // This isn't actually sent from the server. - UserCancelled, -} - -#[derive(Clone, Debug, Default, PartialEq)] -pub enum InternalDeviceState { - /// Not awaiting for internal FIDO device. - #[default] - Idle, - - /// The device needs the PIN to be entered. - NeedsPin, - - /// Internal device credentials - Completed { - device: Device, - cred_id: String, - }, - - // This isn't actually sent from the server. - UserCancelled, -} - -#[derive(Debug)] -pub(crate) struct CredentialMetadata { - /// ID of credential, to be used in `SelectCredential()`. - pub(crate) id: String, - - /// Origin of credential. - // TODO: Does this need to be multiple origins? - pub(crate) origin: String, - - /// User-chosen name for the credential. - pub(crate) display_name: String, - - /// Username of credential, if any. - pub(crate) username: String, + // UserCancelled, } #[derive(Clone, Debug)] @@ -534,7 +374,7 @@ async fn handle_usb_updates( .unwrap(); } UxUpdate::PinRequired(pin_update) => { - if pin_update.attempts_left.map_or(false, |num| num <= 1) { + if pin_update.attempts_left.is_some_and(|num| num <= 1) { // TODO: cancel authenticator operation signal_tx.send(Err("No more PIN attempts allowed. Select a different authenticator or try again later.".to_string())).await.unwrap(); continue; diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs index a43b0069..aa0927a0 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/dbus.rs @@ -111,28 +111,20 @@ struct CredentialManager { impl CredentialManager { async fn create_credential( &self, - mut request: CreateCredentialRequest, + request: CreateCredentialRequest, ) -> fdo::Result { if let Some(tx) = self.app_lock.try_lock() { - let origin = request - .origin - .clone() - .unwrap_or("xyz.iinuwa.credentials.CredentialManager:local".to_string()); + if request.origin.is_none() { + todo!("Implicit caller-origin binding not yet implemented.") + }; let is_same_origin = request.is_same_origin.unwrap_or(false); - let response = match ( - request.r#type.as_ref(), - &request.password, - &request.public_key, - ) { - ("password", Some(password_request), _) => { - let password_response = - create_password(&origin, is_same_origin, password_request).await?; - Ok(password_response.into()) - } - ("publicKey", _, Some(passkey_request)) => { - _ = request.origin.get_or_insert( - "xyz.iinuwa.credentials.CredentialManager:local".to_string(), - ); + let response = match (request.r#type.as_ref(), &request.public_key) { + ("publicKey", Some(_)) => { + if !is_same_origin { + return Err(fdo::Error::AccessDenied(String::from( + "Cross-origin public-key credentials are not allowed.", + ))); + } let (make_cred_request, client_data_json) = request.clone().try_into_ctap2_request().map_err(|e| { fdo::Error::Failed(format!( @@ -173,28 +165,20 @@ impl CredentialManager { async fn get_credential( &self, - mut request: GetCredentialRequest, + request: GetCredentialRequest, ) -> fdo::Result { if let Some(tx) = self.app_lock.try_lock() { - let origin = request - .origin - .clone() - .unwrap_or("xyz.iinuwa.credentials.CredentialManager:local".to_string()); + if request.origin.is_none() { + todo!("Implicit caller-origin binding is not yet implemented."); + } let is_same_origin = request.is_same_origin.unwrap_or(false); - let response = match ( - request.r#type.as_ref(), - &request.password, - &request.public_key, - ) { - ("password", Some(password_request), _) => { - let password_response = - get_password(&origin, is_same_origin, password_request).await?; - Ok(password_response.into()) - } - ("publicKey", _, Some(passkey_request)) => { - _ = request.origin.get_or_insert( - "xyz.iinuwa.credentials.CredentialManager:local".to_string(), - ); + let response = match (request.r#type.as_ref(), &request.public_key) { + ("publicKey", Some(_)) => { + if !is_same_origin { + return Err(fdo::Error::AccessDenied(String::from( + "Cross-origin public-key credentials are not allowed.", + ))); + } // TODO: assert that RP ID is bound to origin: // - if RP ID is not set, set the RP ID to the origin's effective domain // - if RP ID is set, assert that it matches origin's effective domain @@ -257,50 +241,6 @@ impl CredentialManager { } } -async fn create_password( - origin: &str, - is_same_origin: bool, - request: &CreatePasswordCredentialRequest, -) -> fdo::Result { - if !is_same_origin { - return Err(fdo::Error::AccessDenied( - "Passwords may only be requested from same-origin contexts".to_string(), - )); - } - /* - store::store_password(&request.origin, &request.id, &request.password).await - .map(|_| CreatePasswordCredentialResponse{}) - .map_err(|_| fdo::Error::Failed("Failed to store password".to_string())); - */ - let contents = format!( - "id={}&password={}", - request.id.replace('%', "%25").replace('&', "%26"), - request.password.replace('%', "%25").replace('&', "%26") - ); - let display_name = format!("Password for {origin}"); // TODO - /* - store::store_secret( - &[origin], - &display_name, - &request.id, - "secret/password", - None, - contents.as_bytes(), - ) - .await - .map_err(|_| fdo::Error::Failed("".to_string()))?; - */ - Ok(CreatePasswordCredentialResponse {}) -} - -async fn get_password( - origin: &str, - is_same_origin: bool, - request: &GetPasswordCredentialRequest, -) -> Result { - todo!() -} - // D-Bus <-> internal types #[derive(Clone, Debug)] pub(crate) enum CredentialRequest { @@ -358,7 +298,6 @@ pub struct CreateCredentialRequest { is_same_origin: Option, #[zvariant(rename = "type")] r#type: String, - password: Option, #[zvariant(rename = "publicKey")] public_key: Option, } @@ -526,13 +465,6 @@ impl CreateCredentialRequest { } } -#[derive(Clone, Debug, DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct CreatePasswordCredentialRequest { - id: String, - password: String, -} - #[derive(Clone, Debug, DeserializeDict, Type)] #[zvariant(signature = "dict")] pub struct CreatePublicKeyCredentialRequest { @@ -571,7 +503,6 @@ impl CreatePublicKeyCredentialResponse { let registration_response_json = webauthn::CreatePublicKeyCredentialResponse::new( attested_credential.credential_id.clone(), attestation_object, - authenticator_data_blob, client_data_json, Some(response.transport.clone()), unsigned_extensions, @@ -590,24 +521,9 @@ impl CreatePublicKeyCredentialResponse { pub struct CreateCredentialResponse { #[zvariant(rename = "type")] r#type: String, - password: Option, public_key: Option, } -#[derive(SerializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct CreatePasswordCredentialResponse {} - -impl From for CreateCredentialResponse { - fn from(response: CreatePasswordCredentialResponse) -> Self { - CreateCredentialResponse { - r#type: "password".to_string(), - password: Some(response), - public_key: None, - } - } -} - #[derive(SerializeDict, Type)] #[zvariant(signature = "dict")] pub struct CreatePublicKeyCredentialResponse { @@ -620,7 +536,6 @@ impl From for CreateCredentialResponse { // TODO: Decide on camelCase or kebab-case for cred types r#type: "public-key".to_string(), public_key: Some(response), - password: None, } } } @@ -632,7 +547,6 @@ pub struct GetCredentialRequest { is_same_origin: Option, #[zvariant(rename = "type")] r#type: String, - password: Option, #[zvariant(rename = "publicKey")] public_key: Option, } @@ -754,13 +668,6 @@ impl GetCredentialRequest { } } -#[derive(Clone, Debug, DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct GetPasswordCredentialRequest { - id: String, - password: String, -} - #[derive(Clone, Debug, DeserializeDict, Type)] #[zvariant(signature = "dict")] pub struct GetPublicKeyCredentialRequest { @@ -817,24 +724,9 @@ impl GetPublicKeyCredentialResponse { pub struct GetCredentialResponse { #[zvariant(rename = "type")] r#type: String, - password: Option, public_key: Option, } -#[derive(SerializeDict, Type)] -#[zvariant(signature = "dict")] -pub struct GetPasswordCredentialResponse {} - -impl From for GetCredentialResponse { - fn from(response: GetPasswordCredentialResponse) -> Self { - GetCredentialResponse { - r#type: "password".to_string(), - password: Some(response), - public_key: None, - } - } -} - #[derive(SerializeDict, Type)] #[zvariant(signature = "dict")] pub struct GetPublicKeyCredentialResponse { @@ -847,7 +739,6 @@ impl From for GetCredentialResponse { // TODO: Decide on camelCase or kebab-case for cred types r#type: "public-key".to_string(), public_key: Some(response), - password: None, } } } diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/main.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/main.rs index c8648974..e91e3dcb 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/main.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/main.rs @@ -5,7 +5,6 @@ mod config; mod cose; mod credential_service; mod dbus; -mod platform_authenticator; mod serde; #[allow(dead_code)] mod view_model; diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/platform_authenticator/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/platform_authenticator/mod.rs deleted file mode 100644 index 8f49c64c..00000000 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/platform_authenticator/mod.rs +++ /dev/null @@ -1,781 +0,0 @@ -mod store; - -use std::collections::HashMap; - -use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; -use libwebauthn::fido::AuthenticatorDataFlags; -use openssl::{pkey::PKey, rsa::Rsa}; -use ring::{ - digest::{self}, - rand::SystemRandom, - signature::{ - EcdsaKeyPair, EcdsaSigningAlgorithm, Ed25519KeyPair, RsaKeyPair, - ECDSA_P256_SHA256_ASN1_SIGNING, RSA_PKCS1_SHA256, - }, -}; -use serde::Deserialize; - -use crate::cose::{encode_pkcs8_key, CoseKeyType}; -use crate::webauthn::{ - self, AttestationStatement, AttestationStatementFormat, CreatePublicKeyCredentialResponse, - CredentialDescriptor, CredentialSource, Error as WebAuthnError, GetPublicKeyCredentialResponse, - MakeCredentialOptions, PublicKeyCredentialParameters, PublicKeyCredentialType, -}; - -static P256: &EcdsaSigningAlgorithm = &ECDSA_P256_SHA256_ASN1_SIGNING; -// static RNG: &Box = &Box::new(SystemRandom::new()); - -const CAN_CREATE_DISCOVERABLE_CREDENTIAL: bool = true; - -/* -async fn create_passkey( - origin: &str, - is_same_origin: bool, - request: &CreatePublicKeyCredentialRequest, -) -> fdo::Result { - let (response, cred_source, user) = - webauthn::create_credential(origin, &request.request_json, true).map_err(|_| { - fdo::WebAuthnError::Failed("Failed to create public key credential".to_string()) - })?; - - let mut contents = String::new(); - contents.push_str("type=public-key"); // TODO: Don't hardcode public-key? - contents.push_str("&id="); - URL_SAFE_NO_PAD.encode_string(cred_source.id, &mut contents); - contents.push_str("&key="); - URL_SAFE_NO_PAD.encode_string(cred_source.private_key, &mut contents); - contents.push_str("&rp_id="); - contents.push_str(&cred_source.rp_id); - if let Some(user_handle) = &cred_source.user_handle { - contents.push_str("&user_handle="); - URL_SAFE_NO_PAD.encode_string(user_handle, &mut contents); - } - - if let Some(other_ui) = cred_source.other_ui { - contents.push_str("&other_ui="); - contents.push_str(&other_ui); - } - let content_type = "secret/public-key"; - let display_name = "test"; // TODO - store::store_secret( - &[origin], - display_name, - &user.display_name, - content_type, - None, - contents.as_bytes(), - ) - .await - .map_err(|_| fdo::WebAuthnError::Failed("Failed to save passkey to storage".to_string()))?; - - Ok(CreatePublicKeyCredentialResponse { - registration_response_json: response.to_json(), - }) -} -*/ - -#[derive(Deserialize)] -pub(crate) struct RelyingParty { - pub name: String, - pub id: String, -} - -/// https://www.w3.org/TR/webauthn-3/#dictionary-user-credential-params -#[derive(Deserialize)] -pub(crate) struct User { - pub id: String, - pub name: String, - #[serde(rename = "displayName")] - pub display_name: String, -} - -struct Assertion {} - -pub(crate) fn create_attested_credential_data( - credential_id: &[u8], - public_key: &[u8], - aaguid: &[u8], -) -> Result, webauthn::Error> { - let mut attested_credential_data: Vec = Vec::new(); - if aaguid.len() != 16 { - return Err(webauthn::Error::Unknown); - } - attested_credential_data.extend(aaguid); - let cred_length: u16 = TryInto::::try_into(credential_id.len()).unwrap(); - let cred_length_bytes: Vec = cred_length.to_be_bytes().to_vec(); - attested_credential_data.extend(&cred_length_bytes); - attested_credential_data.extend(credential_id); - attested_credential_data.extend(public_key); - Ok(attested_credential_data) -} - -pub(crate) fn create_authenticator_data( - rp_id_hash: &[u8], - flags: &AuthenticatorDataFlags, - signature_counter: u32, - attested_credential_data: Option<&[u8]>, - processed_extensions: Option<&[u8]>, -) -> Vec { - let mut authenticator_data: Vec = Vec::new(); - authenticator_data.extend(rp_id_hash); - - authenticator_data.push(flags.bits()); - - authenticator_data.extend(signature_counter.to_be_bytes()); - - if let Some(attested_credential_data) = attested_credential_data { - authenticator_data.extend(attested_credential_data); - } - - if let Some(extensions) = processed_extensions { - authenticator_data.extend(extensions); - } - authenticator_data -} - -pub(crate) fn create_credential( - origin: &str, - options: &str, - same_origin: bool, -) -> Result<(CreatePublicKeyCredentialResponse, CredentialSource, User), WebAuthnError> { - let request_value = serde_json::from_str::(options) - .map_err(|_| WebAuthnError::Internal("Invalid request JSON".to_string()))?; - let json = request_value - .as_object() - .ok_or_else(|| WebAuthnError::Internal("Invalid request JSON".to_string()))?; - let challenge = json - .get("challenge") - .and_then(|c| c.as_str()) - .ok_or_else(|| WebAuthnError::Internal("JSON missing `challenge` field".to_string()))? - .to_owned(); - let rp = json - .get("rp") - .and_then(|val| serde_json::from_str::(&val.to_string()).ok()) - .ok_or_else(|| WebAuthnError::Internal("JSON missing `rp` field".to_string()))?; - let user = json - .get("user") - .ok_or(WebAuthnError::Internal( - "JSON missing `user` field".to_string(), - )) - .and_then(|val| { - serde_json::from_str::(&val.to_string()).map_err(|e| { - let msg = format!("JSON missing `user` field: {e}"); - WebAuthnError::Internal(msg) - }) - })?; - let other_options = serde_json::from_str::(&request_value.to_string()) - .map_err(|_| WebAuthnError::Internal("Invalid request JSON".to_string()))?; - let (require_resident_key, require_user_verification) = - if let Some(authenticator_selection) = other_options.authenticator_selection { - let is_authenticator_storage_capable = true; - let require_resident_key = authenticator_selection.resident_key.map_or_else( - || false, - |r| r == "required" || (r == "preferred" && is_authenticator_storage_capable), - ); // fallback to authenticator_selection.require_resident_key == true for WebAuthn Level 1? - - let authenticator_can_verify_users = true; - let require_user_verification = authenticator_selection.user_verification.map_or_else( - || false, - |r| r == "required" || (r == "preferred" && authenticator_can_verify_users), - ); - - (require_resident_key, require_user_verification) - } else { - (false, false) - }; - let require_user_presence = true; - let enterprise_attestation_possible = false; - let extensions = None; - let credential_parameters = request_value - .clone() - .get("pubKeyCredParams") - .ok_or_else(|| { - WebAuthnError::Internal( - "Request JSON missing or invalid `pubKeyCredParams` key".to_string(), - ) - }) - .and_then(|val| { - serde_json::from_str::>(&val.to_string()).map_err( - |e| { - WebAuthnError::Internal(format!( - "Request JSON missing or invalid `pubKeyCredParams` key: {e}" - )) - }, - ) - })?; - let excluded_credentials = other_options.excluded_credentials.unwrap_or(Vec::new()); - - make_credential( - challenge, - origin, - !same_origin, - rp, - &user, - require_resident_key, - require_user_presence, - require_user_verification, - credential_parameters, - excluded_credentials, - enterprise_attestation_possible, - extensions, - ) - .map(|(response, cred_source)| (response, cred_source, user)) -} - -pub(crate) fn make_credential( - challenge: String, - origin: &str, - cross_origin: bool, - rp_entity: RelyingParty, - user_entity: &User, - require_resident_key: bool, - require_user_presence: bool, - require_user_verification: bool, - cred_pub_key_algs: Vec, - exclude_credential_descriptor_list: Vec, - enterprise_attestation_possible: bool, - extensions: Option<()>, -) -> Result<(CreatePublicKeyCredentialResponse, CredentialSource), WebAuthnError> { - // Before performing this operation, all other operations in progress in the authenticator session MUST be aborted by running the authenticatorCancel operation. - // TODO: - let supported_algorithms: [CoseKeyType; 3] = [ - CoseKeyType::ES256_P256, - CoseKeyType::EDDSA_ED25519, - CoseKeyType::RS256, - ]; - - // When this operation is invoked, the authenticator MUST perform the following procedure: - // Check if all the supplied parameters are syntactically well-formed and of the correct length. If not, return an error code equivalent to "UnknownError" and terminate the operation. - let cross_origin_str = if cross_origin { "true" } else { "false" }; - let client_data_json = format!("{{\"type\":\"webauthn.create\",\"challenge\":\"{challenge}\",\"origin\":\"{origin}\",\"crossOrigin\":{cross_origin_str}}}"); - let client_data_hash = digest::digest(&digest::SHA256, client_data_json.as_bytes()) - .as_ref() - .to_owned(); - if client_data_hash.len() != 32 { - return Err(WebAuthnError::Unknown); - } - if rp_entity.id.is_empty() || rp_entity.name.is_empty() { - return Err(WebAuthnError::Unknown); - } - if user_entity.id.is_empty() || user_entity.name.is_empty() { - return Err(WebAuthnError::Unknown); - } - - // Check if at least one of the specified combinations of PublicKeyCredentialType and cryptographic parameters in credTypesAndPubKeyAlgs is supported. If not, return an error code equivalent to "NotSupportedError" and terminate the operation. - let cred_pub_key_parameters = match cred_pub_key_algs - .iter() - .filter(|p| p.cred_type == "public-key") - .find(|p| { - if let Ok(ref key_type) = (*p).try_into() { - supported_algorithms.contains(key_type) - } else { - false - } - }) { - Some(cred_pub_key_parameters) => cred_pub_key_parameters, - None => return Err(WebAuthnError::NotSupported), - }; - - // For each descriptor of excludeCredentialDescriptorList: - for cd in exclude_credential_descriptor_list.iter() { - // If looking up descriptor.id in this authenticator returns non-null, - // and the returned item's RP ID and type match rpEntity.id and - // excludeCredentialDescriptorList.type respectively, then collect an - // authorization gesture confirming user consent for creating a new - // credential. The authorization gesture MUST include a test of user - // presence. - if let Some((found, rp)) = lookup_stored_credentials(&cd.id) { - if rp.id == rp_entity.id && found.cred_type == cd.cred_type { - let has_consent: bool = ask_disclosure_consent(); - // If the user confirms consent to create a new credential - if has_consent { - // return an error code equivalent to "InvalidStateError" and terminate the operation. - return Err(WebAuthnError::InvalidState); - } - // does not consent to create a new credential - else { - // return an error code equivalent to "NotAllowedError" and terminate the operation. - return Err(WebAuthnError::NotAllowed); - } - // Note: The purpose of this authorization gesture is not to proceed with creating a credential, but for privacy reasons to authorize disclosure of the fact that descriptor.id is bound to this authenticator. If the user consents, the client and Relying Party can detect this and guide the user to use a different authenticator. If the user does not consent, the authenticator does not reveal that descriptor.id is bound to it, and responds as if the user simply declined consent to create a credential. - } - } - } - - // If requireResidentKey is true and the authenticator cannot store a client-side discoverable public key credential source, return an error code equivalent to "ConstraintError" and terminate the operation. - if require_resident_key && !CAN_CREATE_DISCOVERABLE_CREDENTIAL { - return Err(WebAuthnError::Constraint); - } - - // If requireUserVerification is true and the authenticator cannot perform user verification, return an error code equivalent to "ConstraintError" and terminate the operation. - if require_user_verification && !is_user_verification_available() { - return Err(WebAuthnError::Constraint); - } - // Collect an authorization gesture confirming user consent for creating a - // new credential. The prompt for the authorization gesture is shown by the - // authenticator if it has its own output capability, or by the user agent - // otherwise. The prompt SHOULD display rpEntity.id, rpEntity.name, - // userEntity.name and userEntity.displayName, if possible. - // If requireUserVerification is true, the authorization gesture MUST include user verification. - - // If requireUserPresence is true, the authorization gesture MUST include a test of user presence. - if collect_authorization_gesture(require_user_verification, require_user_presence).is_err() { - // If the user does not consent or if user verification fails, return an error code equivalent to "NotAllowedError" and terminate the operation. - return Err(WebAuthnError::NotAllowed); - } - let mut flags = if require_user_verification { - AuthenticatorDataFlags::USER_PRESENT | AuthenticatorDataFlags::USER_VERIFIED - } else { - AuthenticatorDataFlags::USER_PRESENT - }; - - // Once the authorization gesture has been completed and user consent has been obtained, generate a new credential object: - // Let (publicKey, privateKey) be a new pair of cryptographic keys using the combination of PublicKeyCredentialType and cryptographic parameters represented by the first item in credTypesAndPubKeyAlgs that is supported by this authenticator. - let key_type = cred_pub_key_parameters - .try_into() - .map_err(|_| WebAuthnError::Unknown)?; - let key_pair = create_key_pair(key_type)?; - // Let userHandle be userEntity.id. - let user_handle = URL_SAFE_NO_PAD - .decode(user_entity.id.clone()) - .map_err(|_| WebAuthnError::Unknown)?; - - // If requireResidentKey is true or the authenticator chooses to create a client-side discoverable public key credential source: - // Let credentialId be a new credential id. - // Note: We'll always create a discoverable credential, so generate a random credential ID. - let credential_id: Vec = ring::rand::generate::<[u8; 16]>(&SystemRandom::new()) - .map_err(|_e| WebAuthnError::Unknown)? - .expose() - .into(); - - // Let credentialSource be a new public key credential source with the fields: - let credential_source = CredentialSource { - // type - // public-key. - cred_type: PublicKeyCredentialType::PublicKey, - // Set credentialSource.id to credentialId. - id: credential_id.to_vec(), - // privateKey - // privateKey - private_key: key_pair.clone(), - key_parameters: cred_pub_key_parameters.clone(), - // rpId - // rpEntity.id - rp_id: rp_entity.id, - // userHandle - // userHandle - user_handle: Some(user_handle), - // otherUI - // Any other information the authenticator chooses to include. - other_ui: None, - }; - - // If any error occurred while creating the new credential object, return an error code equivalent to "UnknownError" and terminate the operation. - - // Let processedExtensions be the result of authenticator extension processing for each supported extension identifier → authenticator extension input in extensions. - if let Some(extensions) = extensions { - process_authenticator_extensions(extensions) - .expect("Extension processing not yet supported"); - }; - - // If the authenticator: - - let counter_type = WebAuthnDeviceCounterType::PerCredential; - let signature_counter: u32 = match counter_type { - // is a U2F device - // let the signature counter value for the new credential be zero. (U2F devices may support signature counters but do not return a counter when making a credential. See [FIDO-U2F-Message-Formats].) - WebAuthnDeviceCounterType::U2F => 0, - // supports a global signature counter - // Use the global signature counter's actual value when generating authenticator data. - WebAuthnDeviceCounterType::Global => todo!(), // authenticator.sign_count - // supports a per credential signature counter - - // allocate the counter, associate it with the new credential, and initialize the counter value as zero. - WebAuthnDeviceCounterType::PerCredential => 0, - // does not support a signature counter - - // let the signature counter value for the new credential be constant at zero. - WebAuthnDeviceCounterType::Unsupported => 0, - }; - - // Let attestedCredentialData be the attested credential data byte array including the credentialId and publicKey. - let aaguid = vec![0_u8; 16]; - let public_key = encode_pkcs8_key(key_type, &key_pair).map_err(|_| WebAuthnError::Unknown)?; - let attested_credential_data = - create_attested_credential_data(&credential_id, &public_key, &aaguid)?; - - flags = flags | AuthenticatorDataFlags::ATTESTED_CREDENTIALS; - // Let authenticatorData be the byte array specified in § 6.1 Authenticator Data, including attestedCredentialData as the attestedCredentialData and processedExtensions, if any, as the extensions. - let rp_id_hash = ring::digest::digest(&digest::SHA256, &credential_source.rp_id.as_bytes()); - let authenticator_data = create_authenticator_data( - rp_id_hash.as_ref(), - &flags, - signature_counter, - Some(&attested_credential_data), - None, - ); - - // Create an attestation object for the new credential using the procedure specified in § 6.5.4 Generating an Attestation Object, using an authenticator-chosen attestation statement format, authenticatorData, and hash, as well as taking into account the value of enterpriseAttestationPossible. For more details on attestation, see § 6.5 Attestation. - // TODO: attestation not supported for now - let signature = sign_attestation(&authenticator_data, &client_data_hash, &key_pair, &key_type)?; - let attestation_statment = AttestationStatement::Packed { - algorithm: key_type.algorithm(), - signature, - certificates: vec![], - }; - let attestation_object = webauthn::create_attestation_object( - &authenticator_data, - &attestation_statment, - enterprise_attestation_possible, - )?; - - // On successful completion of this operation, the authenticator returns the attestation object to the client. - let response = CreatePublicKeyCredentialResponse::new( - credential_id, - attestation_object, - authenticator_data, - client_data_json, - None, - None, - String::from("platform"), - ); - Ok((response, credential_source)) -} - -fn get_credential( - rp_entity: RelyingParty, - - challenge: String, - origin: &str, - cross_origin: bool, - top_origin: Option, - allow_credential_descriptor_list: Option>, - require_user_presence: bool, - require_user_verification: bool, - enterprise_attestation_possible: bool, - attestation_formats: Vec, - stored_credentials: HashMap, CredentialSource>, - - extensions: Option<()>, -) -> Result { - // Note: Before performing this operation, all other operations in progress in the authenticator session MUST be aborted by running the authenticatorCancel operation. - - // When this method is invoked, the authenticator MUST perform the following procedure: - - // Check if all the supplied parameters are syntactically well-formed and of the correct length. If not, return an error code equivalent to "UnknownError" and terminate the operation. - let cross_origin_str = if cross_origin { "true" } else { "false" }; - let client_data_json = format!("{{\"type\":\"webauthn.create\",\"challenge\":\"{challenge}\",\"origin\":\"{origin}\",\"crossOrigin\":{cross_origin_str}}}"); - let client_data_hash = digest::digest(&digest::SHA256, client_data_json.as_bytes()) - .as_ref() - .to_owned(); - if client_data_hash.len() != 32 { - return Err(WebAuthnError::Unknown); - } - - // Let credentialOptions be a new empty set of public key credential sources. - // If allowCredentialDescriptorList was supplied, then for each descriptor of allowCredentialDescriptorList: - let credential_options: Vec<&CredentialSource> = - if let Some(ref allowed_credentials) = allow_credential_descriptor_list { - // Let credSource be the result of looking up descriptor.id in this authenticator. - // If credSource is not null, append it to credentialOptions. - allowed_credentials - .iter() - .filter_map(|cred| stored_credentials.get(&cred.id)) - // Remove any items from credentialOptions whose rpId is not equal to rpId. - .filter(|cred_source| cred_source.rp_id == rp_entity.id) - .collect() - } else { - // Otherwise (allowCredentialDescriptorList was not supplied), for each key → credSource of this authenticator’s credentials map, append credSource to credentialOptions. - stored_credentials - .values() - // Remove any items from credentialOptions whose rpId is not equal to rpId. - .filter(|cred_source| cred_source.rp_id == rp_entity.id) - .collect() - }; - - // If credentialOptions is now empty, return an error code equivalent to "NotAllowedError" and terminate the operation. - if credential_options.is_empty() { - return Err(WebAuthnError::NotAllowed); - } - // Prompt the user to select a public key credential source selectedCredential from credentialOptions. Collect an authorization gesture confirming user consent for using selectedCredential. The prompt for the authorization gesture may be shown by the authenticator if it has its own output capability, or by the user agent otherwise. - // TODO, already done? Move up to D-Bus call - // If requireUserVerification is true, the authorization gesture MUST include user verification. - // If requireUserPresence is true, the authorization gesture MUST include a test of user presence. - // If the user does not consent, return an error code equivalent to "NotAllowedError" and terminate the operation. - if collect_authorization_gesture(require_user_presence, require_user_verification).is_err() { - return Err(WebAuthnError::NotAllowed); - } - let flags = if require_user_verification { - AuthenticatorDataFlags::USER_PRESENT | AuthenticatorDataFlags::USER_VERIFIED - } else { - AuthenticatorDataFlags::USER_VERIFIED - }; - - // TODO: pass selected_credential to this method - let selected_credential = credential_options[0]; - // Let processedExtensions be the result of authenticator extension processing for each supported extension identifier → authenticator extension input in extensions. - if let Some(extensions) = extensions { - // TODO: support extensions - process_authenticator_extensions(extensions) - .expect("Processing extensions not supported yet."); - } - - // Increment the credential associated signature counter or the global signature counter value, depending on which approach is implemented by the authenticator, by some positive value. If the authenticator does not implement a signature counter, let the signature counter value remain constant at zero. - let signature_counter = 0; - /* TODO - let counter_type = WebAuthnDeviceCounterType::PerCredential; - let signature_counter: u32 = match counter_type { - // is a U2F device - // let the signature counter value for the new credential be zero. (U2F devices may support signature counters but do not return a counter when making a credential. See [FIDO-U2F-Message-Formats].) - WebAuthnDeviceCounterType::U2F => 0, - // supports a global signature counter - // Use the global signature counter's actual value when generating authenticator data. - WebAuthnDeviceCounterType::Global => todo!(), // authenticator.sign_count - // supports a per credential signature counter - - // allocate the counter, associate it with the new credential, and initialize the counter value as zero. - WebAuthnDeviceCounterType::PerCredential => cred_source., - // does not support a signature counter - - // let the signature counter value for the new credential be constant at zero. - WebAuthnDeviceCounterType::Unsupported => 0, - }; - */ - - // If attestationFormats: - // is not empty - // let attestationFormat be the first supported attestation statement format from attestationFormats, taking into account enterpriseAttestationPossible. If none are supported, fallthrough to: - // is empty - // let attestationFormat be the attestation statement format most preferred by this authenticator. If it does not support attestation during assertion then let this be none. - let supported_formats = [AttestationStatementFormat::Packed]; - let preferred_format = AttestationStatementFormat::None; - let attestation_format = attestation_formats - .iter() - .find(|f| supported_formats.contains(f)) - .unwrap_or(&preferred_format); - - let key_type = (&selected_credential.key_parameters) - .try_into() - .map_err(|_| WebAuthnError::Unknown)?; - let public_key = encode_pkcs8_key(key_type, &selected_credential.private_key) - .map_err(|_| WebAuthnError::Unknown)?; - - // TODO: Assign AAGUID? - let aaguid = vec![0_u8; 16]; - let attested_credential_data = if *attestation_format != AttestationStatementFormat::None { - create_attested_credential_data(&selected_credential.id, &public_key, &aaguid).ok() - } else { - None - }; - // Let authenticatorData be the byte array specified in § 6.1 Authenticator Data including processedExtensions, if any, as the extensions and excluding attestedCredentialData. This authenticatorData MUST include attested credential data if, and only if, attestationFormat is not none. - let rp_id_hash = digest::digest(&digest::SHA256, selected_credential.rp_id.as_bytes()); - let authenticator_data = create_authenticator_data( - rp_id_hash.as_ref(), - &flags, - signature_counter, - attested_credential_data.as_deref(), - None, - ); - // Let signature be the assertion signature of the concatenation authenticatorData || hash using the privateKey of selectedCredential as shown in Figure , below. A simple, undelimited concatenation is safe to use here because the authenticator data describes its own length. The hash of the serialized client data (which potentially has a variable length) is always the last element. - let signature = sign_attestation( - &authenticator_data, - &client_data_hash, - &selected_credential.private_key, - &key_type, - )?; - - // If any error occurred then return an error code equivalent to "UnknownError" and terminate the operation. - // Return to the user agent: - let response = GetPublicKeyCredentialResponse { - cred_type: "public-key".to_string(), - client_data_json, - - // selectedCredential.id, if either a list of credentials (i.e., allowCredentialDescriptorList) of length 2 or greater was supplied by the client, or no such list was supplied. - // Note: If, within allowCredentialDescriptorList, the client supplied exactly one credential and it was successfully employed, then its credential ID is not returned since the client already knows it. This saves transmitting these bytes over what may be a constrained connection in what is likely a common case. - raw_id: if allow_credential_descriptor_list.map_or(true, |l| l.len() > 1) { - Some(selected_credential.id.clone()) - } else { - None - }, - - // authenticatorData - authenticator_data, - - // signature - signature, - - // selectedCredential.userHandle - // Note: In cases where allowCredentialDescriptorList was supplied the returned userHandle value may be null, see: userHandleResult. - user_handle: selected_credential.user_handle.clone(), - attachment_modality: String::from("platform"), - extensions: None, - }; - Ok(response) - // If the authenticator cannot find any credential corresponding to the specified Relying Party that matches the specified criteria, it terminates the operation and returns an error. -} - -fn create_key_pair(parameters: CoseKeyType) -> Result, WebAuthnError> { - let rng = &SystemRandom::new(); - let key_pair = match parameters { - CoseKeyType::ES256_P256 => { - EcdsaKeyPair::generate_pkcs8(P256, rng).map(|d| d.as_ref().to_vec()) - } - CoseKeyType::EDDSA_ED25519 => { - Ed25519KeyPair::generate_pkcs8(rng).map(|d| d.as_ref().to_vec()) - } - CoseKeyType::RS256 => { - let rsa_key = Rsa::generate(2048).unwrap(); - let private_key = PKey::from_rsa(rsa_key).unwrap(); - let pkcs8 = private_key.private_key_to_pkcs8().unwrap(); - Ok(pkcs8.to_vec()) - } - _ => todo!("Unknown signature algorithm given pair generated"), - }; - key_pair.map_err(|_e| WebAuthnError::Unknown) -} - -fn lookup_stored_credentials(id: &[u8]) -> Option<(CredentialDescriptor, RelyingParty)> { - todo!() -} - -fn ask_disclosure_consent() -> bool { - todo!(); -} - -fn is_user_verification_available() -> bool { - todo!(); -} - -fn collect_authorization_gesture( - _require_user_presence: bool, - _require_user_verification: bool, -) -> Result<(), WebAuthnError> { - // todo!(); - Ok(()) -} - -fn process_authenticator_extensions(_extensions: ()) -> Result<(), WebAuthnError> { - todo!(); -} - -fn sign_attestation( - authenticator_data: &[u8], - client_data_hash: &[u8], - key_pair: &[u8], - key_type: &CoseKeyType, -) -> Result, WebAuthnError> { - let signed_data: Vec = [authenticator_data, client_data_hash].concat(); - let rng = &SystemRandom::new(); - match key_type { - CoseKeyType::ES256_P256 => { - let ecdsa = EcdsaKeyPair::from_pkcs8( - &ECDSA_P256_SHA256_ASN1_SIGNING, - key_pair, - &SystemRandom::new(), - ) - .unwrap(); - Ok(ecdsa.sign(rng, &signed_data).unwrap().as_ref().to_vec()) - } - CoseKeyType::EDDSA_ED25519 => { - let eddsa = Ed25519KeyPair::from_pkcs8(key_pair).unwrap(); - Ok(eddsa.sign(&signed_data).as_ref().to_vec()) - } - CoseKeyType::RS256 => { - let rsa = RsaKeyPair::from_pkcs8(key_pair).unwrap(); - let mut signature = vec![0; rsa.public().modulus_len()]; - let _ = rsa.sign(&RSA_PKCS1_SHA256, rng, &signed_data, &mut signature); - Ok(signature) - } - _ => Err(WebAuthnError::NotSupported), - } -} - -enum WebAuthnDeviceCounterType { - /// Authenticator is a U2F device (and therefore does not support a counter - /// on registration and may or may not support a counter on assertion). - U2F, - /// Authenticator supports a global signature counter. - Global, - /// Authenticator supports a per credential signature counter. - PerCredential, - /// Authenticator does not support a signature counter. - Unsupported, -} - -#[cfg(test)] -mod test { - use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; - use libwebauthn::fido::AuthenticatorDataFlags; - use ring::digest::{digest, SHA256}; - - use crate::cose::encode_pkcs8_key; - - use crate::webauthn::{ - create_attestation_object, AttestationStatement, CredentialSource, - PublicKeyCredentialParameters, PublicKeyCredentialType, - }; - - use super::{create_attested_credential_data, create_authenticator_data, sign_attestation}; - - #[test] - fn test_attestation() { - let key_file = std::fs::read("private-key1.pk8").unwrap(); - // let key_pair = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, &key_file, &SystemRandom::new()).unwrap(); - let key_parameters = PublicKeyCredentialParameters { - alg: -7, - cred_type: "public-key".to_string(), - }; - let key_type = (&key_parameters).try_into().unwrap(); - let public_key = encode_pkcs8_key(key_type, &key_file).unwrap(); - let signature_counter = 1u32; - let credential_id = [ - 0x92, 0x11, 0xb7, 0x6d, 0x8b, 0x19, 0xf9, 0x50, 0x6c, 0x2d, 0x75, 0x2f, 0x09, 0xc4, - 0x3c, 0x5a, 0xeb, 0xf3, 0x36, 0xf6, 0xba, 0x89, 0x66, 0xdc, 0x6e, 0x71, 0x93, 0x52, - 0x08, 0x72, 0x1d, 0x16, - ] - .to_vec(); - let aaguid = [ - 01, 02, 03, 04, 05, 06, 07, 08, 01, 02, 03, 04, 05, 06, 07, 08, - ]; - let attested_credential_data = - create_attested_credential_data(&credential_id, &public_key, &aaguid).unwrap(); - let user_handle = [ - 0x64, 0x47, 0x56, 0x7a, 0x64, 0x47, 0x46, 0x69, 0x65, 0x6e, 0x6f, - ] - .to_vec(); - let credential_source = CredentialSource { - cred_type: PublicKeyCredentialType::PublicKey, - id: credential_id, - private_key: key_file.clone(), - key_parameters: PublicKeyCredentialParameters::new(key_type.algorithm().into()), - rp_id: "webauthn.io".to_string(), - user_handle: Some(user_handle), - other_ui: None, - }; - - let flags = AuthenticatorDataFlags::USER_PRESENT - | AuthenticatorDataFlags::USER_VERIFIED - | AuthenticatorDataFlags::ATTESTED_CREDENTIALS; - let authenticator_data = create_authenticator_data( - &credential_source.rp_id_hash(), - &flags, - signature_counter, - Some(&attested_credential_data), - None, - ); - let client_data_encoded = "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiWWlReFY0VWhjZk9pUmZBdkF4bWpEakdhaUVXbkYtZ0ZFcWxndmdEaWsyakZiSGhoaVlxUGJqc0F5Q0FrbDlMUGQwRGRQaHNNb2luY0cxckV5cFlXUVEiLCJvcmlnaW4iOiJodHRwczovL3dlYmF1dGhuLmlvIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ"; - let client_data = URL_SAFE_NO_PAD.decode(client_data_encoded).unwrap(); - let client_data_hash = digest(&SHA256, &client_data).as_ref().to_vec(); - let signature = - sign_attestation(&authenticator_data, &client_data_hash, &key_file, &key_type).unwrap(); - let att_stmt = AttestationStatement::Packed { - algorithm: key_type.algorithm(), - signature, - certificates: vec![], - }; - let attestation_object = - create_attestation_object(&authenticator_data, &att_stmt, false).unwrap(); - let expected = std::fs::read("output.bin").unwrap(); - assert_eq!(expected, attestation_object); - } -} diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/platform_authenticator/store.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/platform_authenticator/store.rs deleted file mode 100644 index 5598fa63..00000000 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/platform_authenticator/store.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::collections::HashMap; -use std::env; -use std::fs::{self, File}; -use std::io::{Read, Write}; -use std::path::PathBuf; -use std::str::FromStr; - -use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; -use ring::rand::{self, SecureRandom}; - -use crate::webauthn::Error; -static mut CRED_DIR: String = String::new(); - -pub(crate) fn initialize() { - let cred_dir = get_cred_dir(); - fs::create_dir_all(cred_dir).unwrap(); -} - -pub(crate) async fn store_secret<'a>( - origins: &'a [&str], - display_name: &'a str, - user_display_name: &'a str, - content_type: &'a str, - metadata: Option>, - contents: &'a [u8], -) -> Result { - let id = { - let rng = rand::SystemRandom::new(); - let mut buf = [0; 32]; - rng.fill(&mut buf).map_err(|_| Error::Unknown)?; - URL_SAFE_NO_PAD.encode(buf) - }; - /* - let service = oo7::dbus::Service::new(oo7::dbus::Algorithm::Encrypted).await?; - let collection = service.with_label("WEBAUTHN").await?.unwrap(); - collection.create_item( - "Item Label", - HashMap::from([( - "cred_id", credential_source.id, - "version", 1 - )]), - credential_source.key_pair, - true, - "application/octet-stream" - ).await?; - */ - let cred_path = { - let mut d = get_cred_dir(); - d.push(&id); - d - }; - let mut cred_file = File::create(cred_path).unwrap(); - cred_file.write_all(b"Origin: ").unwrap(); - for (i, origin) in origins.iter().enumerate() { - cred_file.write_all(origin.as_bytes()).unwrap(); - if i < origins.len() - 1 { - cred_file.write_all(b"; ").unwrap(); - } else { - cred_file.write_all(b"\r\n").unwrap(); - } - } - cred_file - .write_fmt(format_args!("Name: {display_name}\r\n")) - .unwrap(); - cred_file - .write_fmt(format_args!("User-Display-Name: {user_display_name}\r\n")) - .unwrap(); - cred_file - .write_fmt(format_args!("Content-Type: {content_type}\r\n")) - .unwrap(); - if let Some(metadata) = metadata { - for (key, value) in metadata.iter() { - if key.contains(':') { - return Err(Error::Unknown); - } - cred_file - .write_fmt(format_args!("{key}: {value}\r\n")) - .unwrap(); - } - } - - cred_file.write_all(b"\r\n").unwrap(); - - cred_file.write_all(contents).unwrap(); - Ok(id) -} - -pub(crate) async fn lookup_password_credentials(origin: &str) -> Option<(String, String)> { - let cred_path = get_cred_dir(); - 'file: for cred_file in cred_path.read_dir().expect("credential directory to exist") { - let credential = match File::open(cred_file.unwrap().path()) { - Ok(mut cred_file) => { - let mut cred = String::new(); - cred_file.read_to_string(&mut cred).unwrap(); - let mut password: Option = None; - let mut id: Option = None; - let mut origin_matches: bool = false; - let mut content_type = None; - - let boundary = cred.find("\r\n\r\n").unwrap(); - let headers = cred[..boundary].split("\r\n"); - for header in headers { - if let Some((key, value)) = header.split_once(": ") { - if key == "Origin" { - if !value.split("; ").any(|o| o == origin) { - continue 'file; - } else { - origin_matches = true; - } - } else if key == "Content-Type" { - content_type = Some(value); - } - } else { - break; - } - } - if origin_matches && content_type.is_some_and(|ct| ct == "secret/password") { - let body = &cred[boundary + 4..]; - for pair in body.split('&') { - let decoded = pair.replace("%26", "&").replace("%25", "%"); - if let Some((key, value)) = decoded.split_once('=') { - if key == "id" { - id = Some(value.to_string()) - } else if key == "password" { - password = Some(value.to_string()) - } - } - } - id.zip(password) - } else { - None - } - } - _ => None, - }; - if credential.is_some() { - return credential; - } - } - None -} - -fn get_cred_dir() -> PathBuf { - let data_home = if let Ok(data_home) = env::var("XDG_DATA_HOME") { - PathBuf::from_str(&data_home).unwrap() - } else { - PathBuf::from_str(&env::var("HOME").expect("$HOME not set")) - .unwrap() - .join(".local/share") - }; - fs::create_dir_all(&data_home).unwrap(); - data_home.join("xyz.iinuwa.CredentialManager/credentials") -} diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/serde/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/serde/mod.rs index fd0ccb08..353b0f2c 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/serde/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/serde/mod.rs @@ -1,15 +1,7 @@ pub(crate) mod b64 { use base64::{self, engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; - - pub(crate) fn serialize(value: &Vec, serializer: S) -> Result - where - S: Serializer, - { - let s = URL_SAFE_NO_PAD.encode(value); - String::serialize(&s, serializer) - } + use serde::{de, Deserialize, Deserializer}; pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs index 51c67d2f..2294d72d 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/gtk/mod.rs @@ -93,8 +93,10 @@ impl ViewModel { self, async move { loop { - let rx = view_model.imp().rx.borrow(); - let rx = rx.as_ref().expect("rx to exist"); + let rx = { + let rx_ptr = view_model.imp().rx.borrow(); + rx_ptr.as_ref().expect("rx to exist").clone() + }; match rx.recv().await { Ok(update) => { // TODO: hack so I don't have to unset this in every event manually. @@ -121,7 +123,7 @@ impl ViewModel { Some(attempts_left) => format!( "Enter your PIN. {attempts_left} attempts remaining." ), - None => format!("Enter your PIN."), + None => "Enter your PIN.".to_string(), }; view_model.set_prompt(prompt); view_model.set_usb_pin_entry_visible(true); @@ -130,7 +132,7 @@ impl ViewModel { let prompt = match attempts_left { Some(1) => "Touch your device again. 1 attempt remaining.".to_string(), Some(attempts_left) => format!("Touch your device again. {attempts_left} attempts remaining."), - None => format!("Touch your device."), + None => "Touch your device.".to_string(), }; view_model.set_prompt(prompt); } @@ -175,7 +177,7 @@ impl ViewModel { Transport::Nfc => "nfc-symbolic", Transport::Usb => "media-removable-symbolic", // Transport::PasskeyProvider => ("symbolic-link-symbolic", "ACME Password Manager"), - _ => "question-symbolic", + // _ => "question-symbolic", }; let b = gtk::Box::builder() @@ -263,10 +265,6 @@ impl ViewModel { self.send_event(ViewEvent::ButtonClicked).await; } - pub async fn send_internal_device_pin(&self, pin: String) { - self.send_event(ViewEvent::InternalPinEntered(pin)).await; - } - pub async fn send_usb_device_pin(&self, pin: String) { self.send_event(ViewEvent::UsbPinEntered(pin)).await; } diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs index 271c08b8..5419df97 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/view_model/mod.rs @@ -10,7 +10,7 @@ use async_std::{ }; use tracing::info; -use crate::credential_service::{CredentialService, InternalDeviceState}; +use crate::credential_service::CredentialService; #[derive(Debug)] pub(crate) struct ViewModel { @@ -29,13 +29,6 @@ pub(crate) struct ViewModel { providers: Vec, - internal_uv_methods: Vec, - internal_selected_uv_method: UserVerificationMethod, - internal_device_credentials: Vec, - internal_device_pin_state: InternalPinState, // TOOD: I think this is a duplicate - internal_fingerprint_sensor_state: FingerprintSensorState, - internal_device_state: InternalDeviceState, - usb_device_state: UsbState, usb_device_pin_state: UsbPinState, @@ -65,12 +58,6 @@ impl ViewModel { selected_device: None, selected_credential: None, providers: Vec::new(), - internal_uv_methods: Vec::new(), - internal_selected_uv_method: UserVerificationMethod::default(), - internal_device_credentials: Vec::new(), - internal_device_state: InternalDeviceState::default(), - internal_device_pin_state: InternalPinState::default(), - internal_fingerprint_sensor_state: FingerprintSensorState::default(), usb_device_state: UsbState::default(), usb_device_pin_state: UsbPinState::default(), hybrid_qr_state: HybridState::default(), @@ -112,9 +99,6 @@ impl ViewModel { fn select_uv_method(&self) { todo!("not implemented"); } - fn send_internal_device_pin(&self) { - todo!("not implemented"); - } fn finish_authentication(&self) { todo!("not implemented"); @@ -147,28 +131,6 @@ impl ViewModel { .unwrap(); } - async fn update_internal_credentials(&mut self) { - let credential_service = self.credential_service.lock().await; - let credentials: Vec = credential_service - .get_internal_device_credentials() - .await - .unwrap() - .iter() - .map(|c| Credential { - id: c.id.to_owned(), - name: c.display_name.to_owned(), - username: Some(c.username.to_owned()), - }) - .collect(); - self.internal_device_credentials.extend(credentials); - self.tx_update - .send(ViewUpdate::SetCredentials( - self.internal_device_credentials.to_owned(), - )) - .await - .unwrap(); - } - pub(crate) async fn select_device(&mut self, id: &str) { let device = self.devices.iter().find(|d| d.id == id).unwrap(); println!("{:?}", device); @@ -186,14 +148,6 @@ impl ViewModel { .cancel_device_discovery_usb() .await .unwrap(), - Transport::Internal { .. } => { - self.credential_service - .lock() - .await - .cancel_device_discovery_internal() - .await - .unwrap(); - } _ => { todo!() } @@ -248,37 +202,6 @@ impl ViewModel { } }); } - Transport::Internal => { - let cred_service = self.credential_service.clone(); - _ = self - .credential_service - .lock() - .await - .start_device_discovery_internal() - .await - .unwrap(); - let tx = self.bg_update.clone(); - async_std::task::spawn(async move { - // TODO: add cancellation - let mut prev_state = InternalDeviceState::default(); - while let Ok(current_state) = cred_service - .lock() - .await - .poll_device_discovery_internal() - .await - { - if prev_state != current_state { - println!("{:?}", current_state); - tx.send(BackgroundEvent::InternalDeviceStateChanged( - current_state.clone(), - )) - .await - .unwrap(); - } - prev_state = current_state; - } - }); - } _ => { todo!() } @@ -299,7 +222,6 @@ impl ViewModel { Event::View(ViewEvent::Initiated) => { self.update_title().await; self.update_devices().await; - self.update_internal_credentials().await; } Event::View(ViewEvent::ButtonClicked) => { println!("Got it!") @@ -309,35 +231,13 @@ impl ViewModel { println!("Selected device {id}"); } Event::View(ViewEvent::UsbPinEntered(pin)) => { - _ = self - .credential_service + self.credential_service .lock() .await .validate_usb_device_pin(&pin) .await .unwrap(); } - Event::View(ViewEvent::InternalPinEntered(pin)) => { - // TODO: This might be racy; put cred_id in the view event instead. - let cred_id = self.selected_credential.as_ref().unwrap(); - let state = self - .credential_service - .lock() - .await - .validate_internal_device_pin(&pin, cred_id) - .await - .unwrap(); - println!("{:?}", state); - match state { - InternalPinState::PinCorrect { completion_token } => { - // I think this will be handled by the bacground polling - // Otherwise, we might want to show some sort of check mark that the pin is correct before transitioning to complete. - // self.credential_service.lock().await.complete_auth(self.selected_device.completion_token); - // self.tx_update.send(ViewUpdate::).await.unwrap(); - } - _ => todo!(), - } - } Event::View(ViewEvent::CredentialSelected(cred_id)) => { println!( "Credential selected: {:?}. Current Device: {:?}", @@ -385,19 +285,6 @@ impl ViewModel { _ => {} } } - Event::Background(BackgroundEvent::InternalDeviceStateChanged(state)) => { - self.internal_device_state = state.clone(); - match state { - // InternalDeviceState::NeedsPin => { - // self.tx_update.send(ViewUpdate::InternalDeviceNeedsPin).await.unwrap(); - // }, - InternalDeviceState::Completed { device, cred_id } => { - self.credential_service.lock().await.complete_auth(); - self.tx_update.send(ViewUpdate::Completed).await.unwrap(); - } - _ => {} - } - } }; } } @@ -409,7 +296,6 @@ pub enum ViewEvent { DeviceSelected(String), CredentialSelected(String), UsbPinEntered(String), - InternalPinEntered(String), } pub enum ViewUpdate { @@ -427,7 +313,6 @@ pub enum ViewUpdate { pub enum BackgroundEvent { UsbPressed, UsbStateChanged(UsbState), - InternalDeviceStateChanged(InternalDeviceState), } pub enum Event { @@ -472,10 +357,10 @@ pub enum HybridState { /// Connecting to caBLE tunnel. Connecting, - /// Connected to device via caBLE tunnel. - // I don't think is necessary to signal - // Connected, - + /* I don't think is necessary to signal. + /// Connected to device via caBLE tunnel. + Connected, + */ /// Credential received over tunnel. Completed, @@ -483,24 +368,6 @@ pub enum HybridState { UserCancelled, } -#[derive(Debug, Default)] -pub enum InternalPinState { - #[default] - Waiting, - - PinIncorrect { - attempts_left: u32, - }, - - LockedOut { - unlock_time: Duration, - }, - - PinCorrect { - completion_token: String, - }, -} - #[derive(Debug)] pub enum Operation { Create { cred_type: CredentialType }, @@ -614,7 +481,6 @@ impl From for UsbState { } crate::credential_service::UsbState::NeedsUserPresence => UsbState::NeedsUserPresence, crate::credential_service::UsbState::Completed => UsbState::Completed, - crate::credential_service::UsbState::UserCancelled => UsbState::UserCancelled, } } } diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/webauthn.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/webauthn.rs index 563080d9..cc5e136c 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/webauthn.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/webauthn.rs @@ -8,7 +8,6 @@ use libwebauthn::{ Ctap2PublicKeyCredentialType, Ctap2Transport, }, }; -use ring::digest; use serde::{Deserialize, Serialize}; use serde_json::json; use tracing::debug; @@ -70,32 +69,6 @@ pub(crate) fn create_attestation_object( Ok(attestation_object) } -/* -#[derive(DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub(crate) struct ClientData { - client_data_type: String, - challenge: String, - origin: String, - cross_origin: bool, - token_binding: Option, -} - -#[derive(DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub(crate) struct TokenBinding { - status: String, - id: Option, -} -*/ - -#[derive(DeserializeDict, Type)] -#[zvariant(signature = "dict")] -pub(crate) struct AssertionOptions { - user_verification: Option, - user_presence: Option, -} - #[derive(Debug, Deserialize)] pub(crate) struct MakeCredentialOptions { /// Timeout in milliseconds @@ -107,6 +80,7 @@ pub(crate) struct MakeCredentialOptions { #[serde(rename = "authenticatorSelection")] pub authenticator_selection: Option, /// https://www.w3.org/TR/webauthn-3/#enum-attestation-convey + #[allow(dead_code)] pub attestation: Option, /// extensions input as a JSON object pub extensions: Option, @@ -300,20 +274,9 @@ pub(crate) struct AuthenticatorSelectionCriteria { #[derive(Clone, Deserialize)] /// https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialparameters pub(crate) struct PublicKeyCredentialParameters { - #[serde(rename = "type")] - pub cred_type: String, pub alg: i64, } -impl PublicKeyCredentialParameters { - pub(crate) fn new(alg: i64) -> Self { - Self { - cred_type: "public-key".to_string(), - alg, - } - } -} - impl TryFrom<&PublicKeyCredentialParameters> for Ctap2CredentialType { type Error = Error; @@ -339,8 +302,8 @@ impl TryFrom<&PublicKeyCredentialParameters> for CoseKeyType { type Error = String; fn try_from(value: &PublicKeyCredentialParameters) -> Result { match value.alg { - -7 => Ok(CoseKeyType::ES256_P256), - -8 => Ok(CoseKeyType::EDDSA_ED25519), + -7 => Ok(CoseKeyType::Es256P256), + -8 => Ok(CoseKeyType::EddsaEd25519), -257 => Ok(CoseKeyType::RS256), _ => Err("Invalid or unsupported algorithm specified".to_owned()), } @@ -354,58 +317,6 @@ impl TryFrom for CoseKeyType { } } -#[derive(Clone)] -pub struct CredentialSource { - pub cred_type: PublicKeyCredentialType, - - /// A probabilistically-unique byte sequence identifying a public key - /// credential source and its authentication assertions. - pub id: Vec, - - /// The credential private key - pub private_key: Vec, - - pub key_parameters: PublicKeyCredentialParameters, - - /// The Relying Party Identifier, for the Relying Party this public key - /// credential source is scoped to. - pub rp_id: String, - - /// The user handle is specified by a Relying Party, as the value of - /// `user.id`, and used to map a specific public key credential to a specific - /// user account with the Relying Party. Authenticators in turn map RP IDs - /// and user handle pairs to public key credential sources. - /// - /// A user handle is an opaque byte sequence with a maximum size of 64 - /// bytes, and is not meant to be displayed to the user. - pub user_handle: Option>, - - // Any other information the authenticator chooses to include. - /// other information used by the authenticator to inform its UI. For - /// example, this might include the user’s displayName. otherUI is a - /// mutable item and SHOULD NOT be bound to the public key credential - /// source in a way that prevents otherUI from being updated. - pub other_ui: Option, -} - -impl CredentialSource { - pub(crate) fn rp_id_hash<'a>(&'a self) -> Vec { - let hash = digest::digest(&digest::SHA256, self.rp_id.as_bytes()); - hash.as_ref().to_owned() - } -} - -#[derive(Clone)] -pub(crate) enum PublicKeyCredentialType { - PublicKey, -} - -#[derive(Debug, PartialEq)] -pub(crate) enum AttestationStatementFormat { - None, - Packed, -} - #[derive(Debug, PartialEq)] pub(crate) enum AttestationStatement { None, @@ -439,15 +350,13 @@ impl TryFrom<&Ctap2AttestationStatement> for AttestationStatement { } _ => { debug!("Unsupported attestation type: {:?}", value); - return Err(Error::NotSupported); + Err(Error::NotSupported) } } } } pub struct CreatePublicKeyCredentialResponse { - cred_type: String, - /// Raw bytes of credential ID. raw_id: Vec, @@ -534,30 +443,23 @@ pub struct AttestationResponse { /// /// but others may be specified. transports: Vec, - - /// Encodes contextual bindings made by the authenticator. These bindings - /// are controlled by the authenticator itself. - authenticator_data: Vec, } impl CreatePublicKeyCredentialResponse { pub fn new( id: Vec, attestation_object: Vec, - authenticator_data: Vec, client_data_json: String, transports: Option>, extension_output_json: Option, attachment_modality: String, ) -> Self { Self { - cred_type: "public-key".to_string(), raw_id: id, response: AttestationResponse { client_data_json, attestation_object, transports: transports.unwrap_or_default(), - authenticator_data, }, extensions: extension_output_json, attachment_modality, @@ -592,8 +494,6 @@ impl CreatePublicKeyCredentialResponse { } pub struct GetPublicKeyCredentialResponse { - pub(crate) cred_type: String, - /// clientDataJSON. pub(crate) client_data_json: String, @@ -707,7 +607,6 @@ impl GetPublicKeyCredentialResponse { extensions: Option, ) -> Self { Self { - cred_type: "public-key".to_string(), client_data_json, raw_id: id, authenticator_data, @@ -728,7 +627,7 @@ impl GetPublicKeyCredentialResponse { // unambiguously specified in the request. As a convenience, we should // always return a credential ID, even if the authenticator doesn't. // This means we'll have to remember the ID on the request if the allow-list has exactly one - // credential descriptor, then we'll need. This should probably be done in libwebauthn. + // credential descriptor. This should probably be done in libwebauthn. let id = self.raw_id.as_ref().map(|id| URL_SAFE_NO_PAD.encode(id)); let output = json!({ diff --git a/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs b/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs index 00d58400..be5d16da 100644 --- a/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs +++ b/xyz-iinuwa-credential-manager-portal-gtk/src/window.rs @@ -62,20 +62,6 @@ mod imp { } )); } - - #[template_callback] - fn handle_internal_pin_entered(&self, entry: >k::PasswordEntry) { - let view_model = &self.view_model.borrow(); - let view_model = view_model.as_ref().unwrap(); - let pin = entry.text().to_string(); - glib::spawn_future_local(clone!( - #[weak] - view_model, - async move { - view_model.send_internal_device_pin(pin).await; - } - )); - } } impl Default for ExampleApplicationWindow { @@ -173,7 +159,6 @@ impl ExampleApplicationWindow { // If so, we need to transition this to choose_credential as well. // For now, we'll skip it. Ok(Transport::Usb) => stack.set_visible_child_name("usb"), - Ok(Transport::Internal) => stack.set_visible_child_name("choose_credential"), _ => {} }; } @@ -194,7 +179,6 @@ impl ExampleApplicationWindow { .expect("selected device to exist at notify"); match d.transport().try_into() { Ok(Transport::Usb) => stack.set_visible_child_name("usb"), - Ok(Transport::Internal) => stack.set_visible_child_name("internal"), _ => {} }; }