Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BEEEP] Admin Console device approval #306

Closed
wants to merge 16 commits into from
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

118 changes: 118 additions & 0 deletions crates/bitwarden/src/admin_console/auth_requests/approve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use bitwarden_api_api::models::{
AdminAuthRequestUpdateRequestModel, OrganizationUserResetPasswordDetailsResponseModel,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{
client::Client,
crypto::{encrypt_rsa, private_key_from_bytes, public_key_from_b64, Decryptable, EncString},
error::{Error, Result},
};

use super::{list_pending_requests, PendingAuthRequestResponse};

// TODO: what identifier should this take? e.g. org_user_id, request_id, etc
// using org_user_id for now
#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct AuthApproveRequest {
pub organization_user_id: Uuid,
pub organization_id: Uuid,
}

pub(crate) async fn approve_auth_request(
client: &mut Client,
input: &AuthApproveRequest,
) -> Result<()> {
let device_request =
get_pending_request(input.organization_id, input.organization_user_id, client).await;

// Get user reset password details
let reset_password_details =
bitwarden_api_api::apis::organization_users_api::organizations_org_id_users_id_reset_password_details_get(
&client.get_api_configurations().await.api,
&input.organization_id.to_string(),
&device_request.id.to_string(),
)
.await?;

let encrypted_user_key = get_encrypted_user_key(
&client,
input.organization_id,
&device_request,
reset_password_details,
)?;

bitwarden_api_api::apis::organization_auth_requests_api::organizations_org_id_auth_requests_request_id_post(
&client.get_api_configurations().await.api,
input.organization_id,
device_request.id,
Some(AdminAuthRequestUpdateRequestModel {
encrypted_user_key: Some(encrypted_user_key.to_string()),
request_approved: true
})
)
.await?;

Ok(())
}

async fn get_pending_request(
organization_id: Uuid,
organization_user_id: Uuid,
client: &mut Client,
) -> PendingAuthRequestResponse {
// hack: get all approval details and then find the one we want
// when we settle on an identifier then we should just give ourselves a better server API
// or do we require the caller to pass all this info in?
let all_device_requests = list_pending_requests(
client,
&super::PendingAuthRequestsRequest {
organization_id: organization_id,
},
)
.await;

all_device_requests
.unwrap()
.data
.into_iter()
.find(|r| r.organization_user_id == organization_user_id)
.unwrap() // TODO: error handling
}

fn get_encrypted_user_key(
client: &Client,
organization_id: Uuid,
input: &PendingAuthRequestResponse,
reset_password_details: OrganizationUserResetPasswordDetailsResponseModel,
) -> Result<EncString> {
// Decrypt organization's encrypted private key with org key
let enc = client.get_encryption_settings()?;

let org_private_key = {
let dec = reset_password_details
.encrypted_private_key
.ok_or(Error::MissingFields)?
.parse::<EncString>()?
.decrypt(enc, &Some(organization_id))?
.into_bytes();

private_key_from_bytes(&dec)?
};

// Decrypt user key with org private key
let user_key = &reset_password_details
.reset_password_key
.ok_or(Error::MissingFields)?
.parse::<EncString>()?;
let dec_user_key = user_key.decrypt_with_rsa_key(&org_private_key)?;

// re-encrypt user key with device public key
let device_public_key = public_key_from_b64(&input.public_key_b64)?;
let re_encrypted_user_key = encrypt_rsa(dec_user_key, &device_public_key)?;

EncString::from_buffer(&re_encrypted_user_key)
}
92 changes: 92 additions & 0 deletions crates/bitwarden/src/admin_console/auth_requests/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use bitwarden_api_api::models::{
PendingOrganizationAuthRequestResponseModel,
PendingOrganizationAuthRequestResponseModelListResponseModel,
};
use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{
client::Client,
error::{Error, Result},
};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PendingAuthRequestsRequest {
/// Organization to retrieve pending auth requests for
pub organization_id: Uuid,
}

pub(crate) async fn list_pending_requests(
client: &mut Client,
input: &PendingAuthRequestsRequest,
) -> Result<PendingAuthRequestsResponse> {
let config = client.get_api_configurations().await;
let res = bitwarden_api_api::apis::organization_auth_requests_api::organizations_org_id_auth_requests_get(
&config.api,
input.organization_id,
)
.await?;

PendingAuthRequestsResponse::process_response(res)
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PendingAuthRequestsResponse {
pub data: Vec<PendingAuthRequestResponse>,
}

impl PendingAuthRequestsResponse {
pub(crate) fn process_response(
response: PendingOrganizationAuthRequestResponseModelListResponseModel,
) -> Result<PendingAuthRequestsResponse> {
Ok(PendingAuthRequestsResponse {
data: response
.data
.unwrap_or_default()
.into_iter()
.map(|r| PendingAuthRequestResponse::process_response(r))
.collect::<Result<_, _>>()?,
})
}
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct PendingAuthRequestResponse {
pub id: Uuid,
pub user_id: Uuid,
pub organization_user_id: Uuid,
pub email: String,
pub public_key_b64: String,
pub request_device_identifier: String,
pub request_device_type: String,
pub request_ip_address: String,
pub creation_date: DateTime<Utc>,
}

impl PendingAuthRequestResponse {
pub(crate) fn process_response(
response: PendingOrganizationAuthRequestResponseModel,
) -> Result<Self> {
Ok(PendingAuthRequestResponse {
id: response.id.ok_or(Error::MissingFields)?,
user_id: response.user_id.ok_or(Error::MissingFields)?,
organization_user_id: response.organization_user_id.ok_or(Error::MissingFields)?,
email: response.email.ok_or(Error::MissingFields)?,
public_key_b64: response.public_key.ok_or(Error::MissingFields)?,
request_device_identifier: response
.request_device_identifier
.ok_or(Error::MissingFields)?,
request_device_type: response.request_device_type.ok_or(Error::MissingFields)?,
request_ip_address: response.request_ip_address.ok_or(Error::MissingFields)?,
creation_date: response
.creation_date
.ok_or(Error::MissingFields)?
.parse()?,
})
}
}
10 changes: 10 additions & 0 deletions crates/bitwarden/src/admin_console/auth_requests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mod approve;
mod list;

pub(crate) use list::list_pending_requests;
pub use list::{
PendingAuthRequestResponse, PendingAuthRequestsRequest, PendingAuthRequestsResponse,
};

pub(crate) use approve::approve_auth_request;
pub use approve::AuthApproveRequest;
31 changes: 31 additions & 0 deletions crates/bitwarden/src/admin_console/client_auth_requests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::{
admin_console::auth_requests::{approve_auth_request, AuthApproveRequest},
admin_console::auth_requests::{
list_pending_requests, PendingAuthRequestsRequest, PendingAuthRequestsResponse,
},
error::Result,
Client,
};

pub struct ClientAuthRequests<'a> {
pub(crate) client: &'a mut crate::Client,
}

impl<'a> ClientAuthRequests<'a> {
pub async fn list(
&mut self,
input: &PendingAuthRequestsRequest,
) -> Result<PendingAuthRequestsResponse> {
list_pending_requests(self.client, input).await
}

pub async fn approve(&mut self, input: &AuthApproveRequest) -> Result<()> {
approve_auth_request(self.client, input).await
}
}

impl<'a> Client {
pub fn client_auth_requests(&'a mut self) -> ClientAuthRequests<'a> {
ClientAuthRequests { client: self }
}
}
3 changes: 3 additions & 0 deletions crates/bitwarden/src/admin_console/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod auth_requests;

mod client_auth_requests;
10 changes: 1 addition & 9 deletions crates/bitwarden/src/client/encryption_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,15 +81,7 @@ impl EncryptionSettings {

// Decrypt the org keys with the private key
for (org_id, org_enc_key) in org_enc_keys {
let data = match org_enc_key {
EncString::Rsa2048_OaepSha1_B64 { data } => data,
_ => return Err(CryptoError::InvalidKey.into()),
};

let dec = private_key
.decrypt(Oaep::new::<sha1::Sha1>(), &data)
.map_err(|_| CryptoError::KeyDecrypt)?;

let dec = org_enc_key.decrypt_with_rsa_key(private_key)?;
let org_key = SymmetricCryptoKey::try_from(dec.as_slice())?;

self.org_keys.insert(org_id, org_key);
Expand Down
10 changes: 9 additions & 1 deletion crates/bitwarden/src/crypto/enc_string.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::{fmt::Display, str::FromStr};

use base64::Engine;
use rsa::RsaPrivateKey;
use serde::{de::Visitor, Deserialize};
use uuid::Uuid;

use crate::{
client::encryption_settings::EncryptionSettings,
crypto::{decrypt_aes256_hmac, Decryptable, Encryptable, SymmetricCryptoKey},
crypto::{decrypt_aes256_hmac, rsa::decrypt_rsa, Decryptable, Encryptable, SymmetricCryptoKey},
error::{CryptoError, EncStringParseError, Error, Result},
util::BASE64_ENGINE,
};
Expand Down Expand Up @@ -315,6 +316,13 @@ impl EncString {
_ => Err(CryptoError::InvalidKey.into()),
}
}

pub fn decrypt_with_rsa_key(&self, key: &RsaPrivateKey) -> Result<Vec<u8>> {
match self {
EncString::Rsa2048_OaepSha1_B64 { data } => decrypt_rsa(data.clone(), key),
_ => Err(CryptoError::InvalidKey.into()),
}
}
}

fn invalid_len_error(expected: usize) -> impl Fn(Vec<u8>) -> EncStringParseError {
Expand Down
9 changes: 7 additions & 2 deletions crates/bitwarden/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,16 @@ pub(crate) use master_key::{HashPurpose, MasterKey};
mod user_key;
#[cfg(feature = "internal")]
pub(crate) use user_key::UserKey;
#[cfg(feature = "internal")]
// #[cfg(feature = "internal")]
mod rsa;
#[cfg(feature = "internal")]
pub use self::rsa::encrypt_rsa;
#[cfg(feature = "internal")]
pub use self::rsa::private_key_from_bytes;
#[cfg(feature = "internal")]
pub use self::rsa::public_key_from_b64;
#[cfg(feature = "internal")]
pub use self::rsa::RsaKeyPair;

#[cfg(feature = "internal")]
mod fingerprint;
#[cfg(feature = "internal")]
Expand Down
Loading
Loading