2022-04-21 17:24:26 +00:00
|
|
|
use std::io::ErrorKind;
|
2022-04-20 19:06:53 +00:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
use actix_web::web;
|
2022-04-23 16:56:14 +00:00
|
|
|
use webauthn_rs::{AuthenticationState, RegistrationState, Webauthn, WebauthnConfig};
|
2022-04-23 18:17:49 +00:00
|
|
|
use webauthn_rs::proto::{CreationChallengeResponse, Credential, PublicKeyCredential, RegisterPublicKeyCredential, RequestChallengeResponse};
|
2022-04-20 19:06:53 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-21 17:24:26 +00:00
|
|
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
|
|
|
pub struct WebauthnPubKey {
|
|
|
|
creds: Credential,
|
|
|
|
}
|
|
|
|
|
2022-04-23 16:56:14 +00:00
|
|
|
pub struct RegisterKeyRequest {
|
|
|
|
pub opaque_state: String,
|
|
|
|
pub creation_challenge: CreationChallengeResponse,
|
|
|
|
}
|
|
|
|
|
2022-04-20 19:06:53 +00:00
|
|
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
|
|
|
struct RegisterKeyOpaqueData {
|
|
|
|
registration_state: RegistrationState,
|
|
|
|
user_id: UserID,
|
2022-04-23 18:17:49 +00:00
|
|
|
// TODO : add time
|
2022-04-20 19:06:53 +00:00
|
|
|
}
|
|
|
|
|
2022-04-23 16:56:14 +00:00
|
|
|
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,
|
2022-04-23 18:17:49 +00:00
|
|
|
// TODO : add time
|
2022-04-23 16:56:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-04-20 19:06:53 +00:00
|
|
|
pub type WebAuthManagerReq = web::Data<Arc<WebAuthManager>>;
|
|
|
|
|
|
|
|
pub struct WebAuthManager {
|
|
|
|
core: Webauthn<WebAuthnAppConfig>,
|
|
|
|
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!"),
|
2022-04-21 17:24:26 +00:00
|
|
|
relying_party_id: conf.domain_name().split_once(':')
|
|
|
|
.map(|s| s.0)
|
2022-04-21 17:26:50 +00:00
|
|
|
.unwrap_or_else(|| conf.domain_name())
|
2022-04-21 17:24:26 +00:00
|
|
|
.to_string(),
|
2022-04-20 19:06:53 +00:00
|
|
|
}),
|
|
|
|
crypto_wrapper: CryptoWrapper::new_random(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start_register(&self, user: &User) -> Res<RegisterKeyRequest> {
|
|
|
|
let (creation_challenge, registration_state) = self.core.generate_challenge_register(
|
|
|
|
&user.username,
|
2022-04-21 17:24:26 +00:00
|
|
|
false,
|
2022-04-20 19:06:53 +00:00
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(RegisterKeyRequest {
|
|
|
|
opaque_state: self.crypto_wrapper.encrypt(&RegisterKeyOpaqueData {
|
|
|
|
registration_state,
|
|
|
|
user_id: user.uid.clone(),
|
|
|
|
})?,
|
|
|
|
creation_challenge,
|
|
|
|
})
|
|
|
|
}
|
2022-04-21 17:24:26 +00:00
|
|
|
|
|
|
|
pub fn finish_registration(&self, user: &User, opaque_state: &str,
|
|
|
|
pub_cred: RegisterPublicKeyCredential) -> Res<WebauthnPubKey> {
|
|
|
|
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 })
|
|
|
|
}
|
2022-04-23 16:56:14 +00:00
|
|
|
|
|
|
|
pub fn start_authentication(&self, user_id: &UserID, key: &WebauthnPubKey) -> Res<AuthRequest> {
|
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
2022-04-23 18:17:49 +00:00
|
|
|
|
|
|
|
pub fn finish_authentication(&self, user_id: &UserID, opaque_state: &str,
|
|
|
|
pub_cred: &PublicKeyCredential) -> Res {
|
|
|
|
let state: AuthStateOpaqueData = self.crypto_wrapper.decrypt(opaque_state)?;
|
|
|
|
if &state.user_id != user_id {
|
|
|
|
return Err(Box::new(
|
|
|
|
std::io::Error::new(ErrorKind::Other, "Invalid user for pubkey!")));
|
|
|
|
}
|
|
|
|
|
|
|
|
self.core.authenticate_credential(pub_cred, &state.authentication_state)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-04-20 19:06:53 +00:00
|
|
|
}
|