Register user security keys

This commit is contained in:
2022-04-21 19:24:26 +02:00
parent 1f0e6d05c8
commit 49716a8bf5
6 changed files with 387 additions and 7 deletions

View File

@ -1,12 +1,14 @@
use actix::Addr;
use actix_web::{HttpResponse, Responder, web};
use uuid::Uuid;
use webauthn_rs::proto::RegisterPublicKeyCredential;
use crate::actors::users_actor;
use crate::actors::users_actor::UsersActor;
use crate::data::current_user::CurrentUser;
use crate::data::totp_key::TotpKey;
use crate::data::user::{FactorID, TwoFactor, TwoFactorType, User};
use crate::data::webauthn_manager::WebAuthManagerReq;
#[derive(serde::Deserialize)]
pub struct AddTOTPRequest {
@ -45,6 +47,43 @@ pub async fn save_totp_factor(user: CurrentUser, form: web::Json<AddTOTPRequest>
}
}
#[derive(serde::Deserialize)]
pub struct AddWebauthnRequest {
opaque_state: String,
factor_name: String,
credential: RegisterPublicKeyCredential,
}
pub async fn save_webauthn_factor(user: CurrentUser, form: web::Json<AddWebauthnRequest>,
users: web::Data<Addr<UsersActor>>,
manager: WebAuthManagerReq) -> impl Responder {
let key = match manager.finish_registration(
&user,
&form.0.opaque_state,
form.0.credential,
) {
Ok(k) => k,
Err(e) => {
log::error!("Failed to register security key! {:?}", e);
return HttpResponse::InternalServerError().body("Failed to register key!");
}
};
let mut user = User::from(user);
user.add_factor(TwoFactor {
id: FactorID(Uuid::new_v4().to_string()),
name: form.0.factor_name,
kind: TwoFactorType::WEBAUTHN(key),
});
let res = users.send(users_actor::UpdateUserRequest(user)).await.unwrap().0;
if !res {
HttpResponse::InternalServerError().body("Failed to update user information!")
} else {
HttpResponse::Ok().body("Added new factor!")
}
}
#[derive(serde::Deserialize)]
pub struct DeleteFactorRequest {
id: FactorID,

View File

@ -2,6 +2,7 @@ use crate::data::client::ClientID;
use crate::data::entity_manager::EntityManager;
use crate::data::login_redirect::LoginRedirect;
use crate::data::totp_key::TotpKey;
use crate::data::webauthn_manager::WebauthnPubKey;
use crate::utils::err::Res;
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
@ -13,7 +14,7 @@ pub struct FactorID(pub String);
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum TwoFactorType {
TOTP(TotpKey),
_OTHER,
WEBAUTHN(WebauthnPubKey),
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
@ -27,7 +28,7 @@ impl TwoFactor {
pub fn type_str(&self) -> &'static str {
match self.kind {
TwoFactorType::TOTP(_) => "Authenticator app",
_ => unimplemented!()
TwoFactorType::WEBAUTHN(_) => "Security key",
}
}
@ -35,7 +36,8 @@ impl TwoFactor {
match self.kind {
TwoFactorType::TOTP(_) => format!("/2fa_otp?id={}&redirect={}",
self.id.0, redirect_uri.get_encoded()),
_ => unimplemented!()
TwoFactorType::WEBAUTHN(_) => format!("/2fa_webauthn?id={}&redirect={}",
self.id.0, redirect_uri.get_encoded()),
}
}
}

View File

@ -1,8 +1,9 @@
use std::io::ErrorKind;
use std::sync::Arc;
use actix_web::web;
use webauthn_rs::{RegistrationState, Webauthn, WebauthnConfig};
use webauthn_rs::proto::CreationChallengeResponse;
use webauthn_rs::proto::{CreationChallengeResponse, Credential, RegisterPublicKeyCredential};
use crate::constants::APP_NAME;
use crate::data::app_config::AppConfig;
@ -35,6 +36,11 @@ pub struct RegisterKeyRequest {
pub creation_challenge: CreationChallengeResponse,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct WebauthnPubKey {
creds: Credential,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct RegisterKeyOpaqueData {
registration_state: RegistrationState,
@ -54,7 +60,10 @@ impl WebAuthManager {
core: Webauthn::new(WebAuthnAppConfig {
origin: url::Url::parse(&conf.website_origin)
.expect("Failed to parse configuration origin!"),
relying_party_id: conf.domain_name().to_string(),
relying_party_id: conf.domain_name().split_once(':')
.map(|s| s.0)
.unwrap_or(conf.domain_name())
.to_string(),
}),
crypto_wrapper: CryptoWrapper::new_random(),
}
@ -63,7 +72,7 @@ impl WebAuthManager {
pub fn start_register(&self, user: &User) -> Res<RegisterKeyRequest> {
let (creation_challenge, registration_state) = self.core.generate_challenge_register(
&user.username,
true,
false,
)?;
Ok(RegisterKeyRequest {
@ -74,4 +83,18 @@ impl WebAuthManager {
creation_challenge,
})
}
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 })
}
}

View File

@ -133,6 +133,7 @@ async fn main() -> std::io::Result<()> {
// User API
.route("/settings/api/two_factor/save_totp_factor", web::post().to(two_factor_api::save_totp_factor))
.route("/settings/api/two_factor/save_webauthn_factor", web::post().to(two_factor_api::save_webauthn_factor))
.route("/settings/api/two_factor/delete_factor", web::post().to(two_factor_api::delete_factor))
// Admin routes