Skip to content

Commit

Permalink
WIP attachment export
Browse files Browse the repository at this point in the history
  • Loading branch information
Hinton committed Sep 25, 2023
1 parent bbbfdc6 commit 78996ea
Show file tree
Hide file tree
Showing 14 changed files with 258 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct AttachmentResponseModel {
#[serde(rename = "key", skip_serializing_if = "Option::is_none")]
pub key: Option<String>,
#[serde(rename = "size", skip_serializing_if = "Option::is_none")]
pub size: Option<i64>,
pub size: Option<String>,
#[serde(rename = "sizeName", skip_serializing_if = "Option::is_none")]
pub size_name: Option<String>,
}
Expand Down
6 changes: 6 additions & 0 deletions crates/bitwarden/src/crypto/enc_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,12 @@ impl Encryptable<EncString> for String {
}
}

impl Encryptable<EncString> for &[u8] {
fn encrypt(self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<EncString> {
enc.encrypt(self, org_id)
}
}

impl Decryptable<String> for EncString {
fn decrypt(&self, enc: &EncryptionSettings, org_id: &Option<Uuid>) -> Result<String> {
enc.decrypt(self, org_id)
Expand Down
21 changes: 4 additions & 17 deletions crates/bitwarden/src/platform/sync.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use bitwarden_api_api::models::{
CipherDetailsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel,
SyncResponseModel,
ProfileOrganizationResponseModel, ProfileResponseModel, SyncResponseModel,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand All @@ -9,6 +8,7 @@ use uuid::Uuid;
use crate::{
client::{encryption_settings::EncryptionSettings, Client},
error::{Error, Result},
vault::Cipher,
};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
Expand Down Expand Up @@ -57,17 +57,13 @@ pub struct ProfileOrganizationResponse {
pub id: Uuid,
}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct CipherDetailsResponse {}

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SyncResponse {
/// Data about the user, including their encryption keys and the organizations they are a part of
pub profile: ProfileResponse,
/// List of ciphers accesible by the user
pub ciphers: Vec<CipherDetailsResponse>,
pub ciphers: Vec<Cipher>,
}

impl SyncResponse {
Expand All @@ -80,20 +76,11 @@ impl SyncResponse {

Ok(SyncResponse {
profile: ProfileResponse::process_response(profile, enc)?,
ciphers: ciphers
.into_iter()
.map(CipherDetailsResponse::process_response)
.collect::<Result<_, _>>()?,
ciphers: ciphers.into_iter().map(|c| c.into()).collect(),
})
}
}

impl CipherDetailsResponse {
fn process_response(_response: CipherDetailsResponseModel) -> Result<CipherDetailsResponse> {
Ok(CipherDetailsResponse {})
}
}

impl ProfileOrganizationResponse {
fn process_response(
response: ProfileOrganizationResponseModel,
Expand Down
10 changes: 8 additions & 2 deletions crates/bitwarden/src/tool/exporters/client_exporter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ use crate::{
Client,
};

use super::export_vault_attachments;

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

impl<'a> ClientExporters<'a> {
Expand All @@ -27,10 +29,14 @@ impl<'a> ClientExporters<'a> {
) -> Result<String> {
export_organization_vault(collections, ciphers, format)
}

pub async fn export_vault_attachments(&mut self) -> Result<()> {
export_vault_attachments(self._client).await
}
}

impl<'a> Client {
pub fn exporters(&'a self) -> ClientExporters<'a> {
pub fn exporters(&'a mut self) -> ClientExporters<'a> {
ClientExporters { _client: self }
}
}
48 changes: 47 additions & 1 deletion crates/bitwarden/src/tool/exporters/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use log::{debug, info};
use schemars::JsonSchema;

use crate::{
crypto::Decryptable,
error::Result,
vault::{Cipher, Collection, Folder},
platform::SyncRequest,
vault::{download_attachment, Cipher, CipherView, Collection, Folder},
Client,
};

mod client_exporter;
Expand Down Expand Up @@ -31,3 +35,45 @@ pub(super) fn export_organization_vault(
) -> Result<String> {
todo!();
}

pub(super) async fn export_vault_attachments(client: &mut Client) -> Result<()> {
info!("Syncing vault");
let sync = client
.sync(&SyncRequest {
exclude_subdomains: None,
})
.await?;

debug!("{:?}", sync);

info!("Vault synced got {} ciphers", sync.ciphers.len());

let ciphers_with_attachments = sync.ciphers.iter().filter(|c| !c.attachments.is_empty());

info!(
"Found {} ciphers with attachments",
ciphers_with_attachments.count()
);

info!("Decrypting ciphers");

let enc_settings = client.get_encryption_settings()?;

let decrypted: Vec<CipherView> = sync
.ciphers
.iter()
.map(|c| c.decrypt(enc_settings, &None).unwrap())
.collect();

let num_attachments = decrypted.iter().flat_map(|c| &c.attachments).count();

info!("Found {} attachments, starting export", num_attachments);

for cipher in decrypted {
for attachment in cipher.attachments {
download_attachment(client, cipher.id.unwrap(), &attachment.id.unwrap()).await?;
}
}

Ok(())
}
7 changes: 7 additions & 0 deletions crates/bitwarden/src/vault/api/attachment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use reqwest::Response;

use crate::error::Result;

pub(crate) async fn attachment_get(url: &str) -> Result<Response> {
reqwest::get(url).await.map_err(|e| e.into())
}
2 changes: 2 additions & 0 deletions crates/bitwarden/src/vault/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod attachment;
pub(super) use attachment::attachment_get;
81 changes: 76 additions & 5 deletions crates/bitwarden/src/vault/cipher/attachment.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
use std::{
fs::{create_dir_all, File},
io::Write,
path::Path,
str::FromStr,
};

use bitwarden_api_api::apis::ciphers_api::ciphers_id_attachment_attachment_id_get;
use log::debug;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{
client::encryption_settings::EncryptionSettings,
crypto::{Decryptable, EncString, Encryptable},
crypto::{Decryptable, EncString, Encryptable, SymmetricCryptoKey},
error::Result,
vault::api::attachment_get,
Client,
};

#[derive(Serialize, Deserialize, Debug, JsonSchema)]
Expand All @@ -30,7 +41,48 @@ pub struct AttachmentView {
pub size: Option<String>,
pub size_name: Option<String>,
pub file_name: Option<String>,
pub key: Option<String>,
pub key: Option<Vec<u8>>, // TODO: Should be made into SymmetricCryptoKey
}

pub async fn download_attachment(
client: &mut Client,
cipher_id: Uuid,
attachment_id: &str,
) -> Result<Vec<u8>> {
// The attachments from sync doesn't contain the correct url
let configuration = &client.get_api_configurations().await.api;
let response = ciphers_id_attachment_attachment_id_get(
configuration,
cipher_id.to_string().as_str(),
attachment_id.to_string().as_str(),
)
.await?;

let attachment: Attachment = response.into();
let enc = client.get_encryption_settings()?;
let view = attachment.decrypt(enc, &None)?;

let key = SymmetricCryptoKey::try_from(view.key.unwrap().as_slice())?;
let enc = EncryptionSettings::new_single_key(key);

let response = attachment_get(&view.url.unwrap()).await?;
let bytes = response.bytes().await?;

let buf = EncString::from_buffer(&bytes)?;
let dec = enc.decrypt_bytes(&buf, &None)?;

let path = Path::new("attachments")
.join(cipher_id.to_string())
.join(attachment_id)
.join(view.file_name.unwrap());

create_dir_all(path.parent().unwrap())?;
let mut file = File::create(path)?;
file.write_all(&dec)?;

debug!("{:?}", bytes.len());

todo!()
}

impl Encryptable<Attachment> for AttachmentView {
Expand All @@ -41,7 +93,7 @@ impl Encryptable<Attachment> for AttachmentView {
size: self.size,
size_name: self.size_name,
file_name: self.file_name.encrypt(enc, org_id)?,
key: self.key.encrypt(enc, org_id)?,
key: self.key.map(|k| k.encrypt(enc, org_id)).transpose()?,
})
}
}
Expand All @@ -53,8 +105,27 @@ impl Decryptable<AttachmentView> for Attachment {
url: self.url.clone(),
size: self.size.clone(),
size_name: self.size_name.clone(),
file_name: self.file_name.decrypt(enc, org_id)?,
key: self.key.decrypt(enc, org_id)?,
file_name: self.file_name.decrypt(enc, org_id).unwrap(),
key: self
.key
.as_ref()
.map(|key| enc.decrypt_bytes(key, org_id).unwrap()),
})
}
}

impl From<bitwarden_api_api::models::AttachmentResponseModel> for Attachment {
fn from(attachment: bitwarden_api_api::models::AttachmentResponseModel) -> Self {
debug!("{:?}", attachment);
Self {
id: attachment.id,
url: attachment.url,
size: attachment.size.map(|s| s.to_string()),
size_name: attachment.size_name,
file_name: attachment
.file_name
.map(|s| EncString::from_str(&s).unwrap()),
key: attachment.key.map(|s| EncString::from_str(&s).unwrap()),
}
}
}
Loading

0 comments on commit 78996ea

Please sign in to comment.