BasicOIDC/src/data/webauthn_manager.rs

171 lines
5.0 KiB
Rust
Raw Normal View History

2022-04-21 17:24:26 +00:00
use std::io::ErrorKind;
use std::sync::Arc;
use actix_web::web;
2022-08-24 11:33:40 +00:00
use uuid::Uuid;
2022-11-11 11:26:02 +00:00
use webauthn_rs::prelude::{
CreationChallengeResponse, Passkey, PublicKeyCredential, RegisterPublicKeyCredential,
RequestChallengeResponse,
};
2022-08-24 11:33:40 +00:00
use webauthn_rs::{Webauthn, WebauthnBuilder};
2022-11-11 11:26:02 +00:00
use crate::constants::{
APP_NAME, WEBAUTHN_LOGIN_CHALLENGE_EXPIRE, WEBAUTHN_REGISTER_CHALLENGE_EXPIRE,
};
use crate::data::app_config::AppConfig;
use crate::data::crypto_wrapper::CryptoWrapper;
use crate::data::user::{User, UserID};
use crate::utils::err::Res;
2022-04-23 18:22:32 +00:00
use crate::utils::time::time;
2022-04-21 17:24:26 +00:00
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct WebauthnPubKey {
2022-08-24 11:33:40 +00:00
creds: Passkey,
2022-04-21 17:24:26 +00:00
}
2022-04-23 16:56:14 +00:00
pub struct RegisterKeyRequest {
pub opaque_state: String,
pub creation_challenge: CreationChallengeResponse,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct RegisterKeyOpaqueData {
2022-08-24 11:33:40 +00:00
registration_state: String,
user_id: UserID,
2022-04-23 18:22:32 +00:00
expire: u64,
}
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 {
2022-08-24 11:33:40 +00:00
authentication_state: String,
2022-04-23 16:56:14 +00:00
user_id: UserID,
2022-04-23 18:22:32 +00:00
expire: u64,
2022-04-23 16:56:14 +00:00
}
pub type WebAuthManagerReq = web::Data<Arc<WebAuthManager>>;
pub struct WebAuthManager {
2022-08-24 11:33:40 +00:00
core: Webauthn,
crypto_wrapper: CryptoWrapper,
}
impl WebAuthManager {
pub fn init(conf: &AppConfig) -> Self {
Self {
2022-08-24 11:33:40 +00:00
core: WebauthnBuilder::new(
2022-11-11 11:26:02 +00:00
conf.domain_name()
.split_once(':')
2022-04-21 17:24:26 +00:00
.map(|s| s.0)
2022-08-24 11:33:40 +00:00
.unwrap_or_else(|| conf.domain_name()),
&url::Url::parse(&conf.website_origin)
2022-11-11 11:26:02 +00:00
.expect("Failed to parse configuration origin!"),
)
.expect("Invalid Webauthn configuration")
.rp_name(APP_NAME)
.build()
.expect("Failed to build webauthn"),
crypto_wrapper: CryptoWrapper::new_random(),
}
}
pub fn start_register(&self, user: &User) -> Res<RegisterKeyRequest> {
2022-11-11 11:26:02 +00:00
let (creation_challenge, registration_state) = self.core.start_passkey_registration(
2022-08-24 11:33:40 +00:00
Uuid::parse_str(&user.uid.0).expect("Failed to parse user id"),
&user.username,
2022-08-24 11:33:40 +00:00
&user.full_name(),
None,
)?;
Ok(RegisterKeyRequest {
opaque_state: self.crypto_wrapper.encrypt(&RegisterKeyOpaqueData {
2022-08-24 11:33:40 +00:00
registration_state: serde_json::to_string(&registration_state)?,
user_id: user.uid.clone(),
2022-04-23 18:22:32 +00:00
expire: time() + WEBAUTHN_REGISTER_CHALLENGE_EXPIRE,
})?,
creation_challenge,
})
}
2022-04-21 17:24:26 +00:00
2022-11-11 11:26:02 +00:00
pub fn finish_registration(
&self,
user: &User,
opaque_state: &str,
pub_cred: RegisterPublicKeyCredential,
) -> Res<WebauthnPubKey> {
2022-04-21 17:24:26 +00:00
let state: RegisterKeyOpaqueData = self.crypto_wrapper.decrypt(opaque_state)?;
if state.user_id != user.uid {
2022-11-11 11:26:02 +00:00
return Err(Box::new(std::io::Error::new(
ErrorKind::Other,
"Invalid user for pubkey!",
)));
2022-04-21 17:24:26 +00:00
}
2022-04-23 18:22:32 +00:00
if state.expire < time() {
2022-11-11 11:26:02 +00:00
return Err(Box::new(std::io::Error::new(
ErrorKind::Other,
"Challenge has expired!",
)));
2022-04-23 18:22:32 +00:00
}
2022-11-11 11:26:02 +00:00
let res = self.core.finish_passkey_registration(
&pub_cred,
&serde_json::from_str(&state.registration_state)?,
)?;
2022-04-21 17:24:26 +00:00
2022-08-24 11:33:40 +00:00
Ok(WebauthnPubKey { creds: res })
2022-04-21 17:24:26 +00:00
}
2022-04-23 16:56:14 +00:00
2022-11-11 11:26:02 +00:00
pub fn start_authentication(
&self,
user_id: &UserID,
keys: &[WebauthnPubKey],
) -> Res<AuthRequest> {
let (login_challenge, authentication_state) = self.core.start_passkey_authentication(
&keys.iter().map(|k| k.creds.clone()).collect::<Vec<_>>(),
)?;
2022-04-23 16:56:14 +00:00
Ok(AuthRequest {
opaque_state: self.crypto_wrapper.encrypt(&AuthStateOpaqueData {
2022-08-24 11:33:40 +00:00
authentication_state: serde_json::to_string(&authentication_state)?,
2022-04-23 16:56:14 +00:00
user_id: user_id.clone(),
2022-04-23 18:22:32 +00:00
expire: time() + WEBAUTHN_LOGIN_CHALLENGE_EXPIRE,
2022-04-23 16:56:14 +00:00
})?,
login_challenge,
})
}
2022-11-11 11:26:02 +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 {
2022-11-11 11:26:02 +00:00
return Err(Box::new(std::io::Error::new(
ErrorKind::Other,
"Invalid user for pubkey!",
)));
}
2022-04-23 18:22:32 +00:00
if state.expire < time() {
2022-11-11 11:26:02 +00:00
return Err(Box::new(std::io::Error::new(
ErrorKind::Other,
"Challenge has expired!",
)));
2022-04-23 18:22:32 +00:00
}
2022-11-11 11:26:02 +00:00
self.core.finish_passkey_authentication(
pub_cred,
&serde_json::from_str(&state.authentication_state)?,
)?;
Ok(())
}
2022-11-11 11:26:02 +00:00
}