use std::io::ErrorKind; use std::sync::Arc; use actix_web::web; use webauthn_rs::{AuthenticationState, RegistrationState, Webauthn, WebauthnConfig}; use webauthn_rs::proto::{CreationChallengeResponse, Credential, RegisterPublicKeyCredential, RequestChallengeResponse}; use crate::constants::APP_NAME; use crate::data::app_config::AppConfig; use crate::data::crypto_wrapper::CryptoWrapper; use crate::data::user::{User, UserID}; use crate::utils::err::Res; #[derive(Debug)] struct WebAuthnAppConfig { origin: url::Url, relying_party_id: String, } impl WebauthnConfig for WebAuthnAppConfig { fn get_relying_party_name(&self) -> &str { APP_NAME } fn get_origin(&self) -> &url::Url { &self.origin } fn get_relying_party_id(&self) -> &str { &self.relying_party_id } } #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct WebauthnPubKey { creds: Credential, } pub struct RegisterKeyRequest { pub opaque_state: String, pub creation_challenge: CreationChallengeResponse, } #[derive(Debug, serde::Serialize, serde::Deserialize)] struct RegisterKeyOpaqueData { registration_state: RegistrationState, user_id: UserID, } pub struct AuthRequest { pub opaque_state: String, pub login_challenge: RequestChallengeResponse, } #[derive(Debug, serde::Serialize, serde::Deserialize)] struct AuthStateOpaqueData { authentication_state: AuthenticationState, user_id: UserID, } pub type WebAuthManagerReq = web::Data>; pub struct WebAuthManager { core: Webauthn, crypto_wrapper: CryptoWrapper, } impl WebAuthManager { pub fn init(conf: &AppConfig) -> Self { Self { core: Webauthn::new(WebAuthnAppConfig { origin: url::Url::parse(&conf.website_origin) .expect("Failed to parse configuration origin!"), relying_party_id: conf.domain_name().split_once(':') .map(|s| s.0) .unwrap_or_else(|| conf.domain_name()) .to_string(), }), crypto_wrapper: CryptoWrapper::new_random(), } } pub fn start_register(&self, user: &User) -> Res { let (creation_challenge, registration_state) = self.core.generate_challenge_register( &user.username, false, )?; Ok(RegisterKeyRequest { opaque_state: self.crypto_wrapper.encrypt(&RegisterKeyOpaqueData { registration_state, user_id: user.uid.clone(), })?, creation_challenge, }) } pub fn finish_registration(&self, user: &User, opaque_state: &str, pub_cred: RegisterPublicKeyCredential) -> Res { let state: RegisterKeyOpaqueData = self.crypto_wrapper.decrypt(opaque_state)?; if state.user_id != user.uid { return Err(Box::new( std::io::Error::new(ErrorKind::Other, "Invalid user for pubkey!"))); } let res = self.core .register_credential(&pub_cred, &state.registration_state, |_| Ok(false))?; Ok(WebauthnPubKey { creds: res.0 }) } pub fn start_authentication(&self, user_id: &UserID, key: &WebauthnPubKey) -> Res { let (login_challenge, authentication_state) = self.core.generate_challenge_authenticate(vec![ key.creds.clone() ])?; Ok(AuthRequest { opaque_state: self.crypto_wrapper.encrypt(&AuthStateOpaqueData { authentication_state, user_id: user_id.clone(), })?, login_challenge, }) } }