Browse Source

Fix attachments during key rotation, add individual attachment key

Daniel García 7 years ago
parent
commit
6364c05789

+ 0 - 0
migrations/2018-11-27-152651_add_att_key_columns/down.sql


+ 3 - 0
migrations/2018-11-27-152651_add_att_key_columns/up.sql

@@ -0,0 +1,3 @@
+ALTER TABLE attachments
+    ADD COLUMN
+    key TEXT;

+ 80 - 32
src/api/core/ciphers.rs

@@ -1,4 +1,4 @@
-use std::collections::HashSet;
+use std::collections::{HashSet, HashMap};
 use std::path::Path;
 
 use rocket::http::ContentType;
@@ -162,6 +162,18 @@ pub struct CipherData {
     Favorite: Option<bool>,
 
     PasswordHistory: Option<Value>,
+
+    // These are used during key rotation
+    #[serde(rename = "Attachments")]
+    _Attachments: Option<Value>, // Unused, contains map of {id: filename}
+    Attachments2: Option<HashMap<String, Attachments2Data>>
+}
+
+#[derive(Deserialize, Debug)]
+#[allow(non_snake_case)]
+pub struct Attachments2Data {
+    FileName: String,
+    Key: String,
 }
 
 #[post("/ciphers/admin", data = "<data>")]
@@ -221,6 +233,28 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &
         }
     }
 
+    // Modify attachments name and keys when rotating
+    if let Some(attachments) = data.Attachments2 {
+        for (id, attachment) in attachments {
+            let mut saved_att = match Attachment::find_by_id(&id, &conn) {
+                Some(att) => att,
+                None => err!("Attachment doesn't exist")
+            };
+
+            if saved_att.cipher_uuid != cipher.uuid {
+                err!("Attachment is not owned by the cipher")
+            }
+
+            saved_att.key = Some(attachment.Key);
+            saved_att.file_name = attachment.FileName;
+
+            match saved_att.save(&conn) {
+                Ok(()) => (),
+                Err(_) => err!("Failed to save attachment")
+            };
+        }
+    }
+
     let type_data_opt = match data.Type {
         1 => data.Login,
         2 => data.SecureNote,
@@ -252,7 +286,7 @@ pub fn update_cipher_from_data(cipher: &mut Cipher, data: CipherData, headers: &
 
     match cipher.save(&conn) {
         Ok(()) => (),
-        Err(_) => println!("Error: Failed to save cipher")
+        Err(_) => err!("Failed to save cipher")
     };
     ws.send_cipher_update(ut, &cipher, &cipher.update_users_revision(&conn));
 
@@ -299,7 +333,6 @@ fn post_ciphers_import(data: JsonUpcase<ImportData>, headers: Headers, conn: DbC
     }
 
     // Read the relations between folders and ciphers
-    use std::collections::HashMap;
     let mut relations_map = HashMap::new();
 
     for relation in data.FolderRelationships {
@@ -542,37 +575,52 @@ fn post_attachment(uuid: String, data: Data, content_type: &ContentType, headers
 
     let base_path = Path::new(&CONFIG.attachments_folder).join(&cipher.uuid);
 
+    let mut attachment_key = None;
+
     Multipart::with_body(data.open(), boundary).foreach_entry(|mut field| {
-         // This is provided by the client, don't trust it
-         let name = field.headers.filename.expect("No filename provided");
-
-        let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10]));
-        let path = base_path.join(&file_name);
-
-        let size = match field.data.save()
-            .memory_threshold(0)
-            .size_limit(None)
-            .with_path(path) {
-            SaveResult::Full(SavedData::File(_, size)) => size as i32,
-            SaveResult::Full(other) => {
-                println!("Attachment is not a file: {:?}", other);
-                return;
+        match field.headers.name.as_str() {
+            "key" => {
+                use std::io::Read;
+                let mut key_buffer = String::new();
+                if field.data.read_to_string(&mut key_buffer).is_ok() {
+                    attachment_key = Some(key_buffer);
+                }
             },
-            SaveResult::Partial(_, reason) => {
-                println!("Partial result: {:?}", reason);
-                return;
+            "data" => {                
+                // This is provided by the client, don't trust it
+                let name = field.headers.filename.expect("No filename provided");
+
+                let file_name = HEXLOWER.encode(&crypto::get_random(vec![0; 10]));
+                let path = base_path.join(&file_name);
+
+                let size = match field.data.save()
+                    .memory_threshold(0)
+                    .size_limit(None)
+                    .with_path(path) {
+                    SaveResult::Full(SavedData::File(_, size)) => size as i32,
+                    SaveResult::Full(other) => {
+                        println!("Attachment is not a file: {:?}", other);
+                        return;
+                    },
+                    SaveResult::Partial(_, reason) => {
+                        println!("Partial result: {:?}", reason);
+                        return;
+                    },
+                    SaveResult::Error(e) => {
+                        println!("Error: {:?}", e);
+                        return;
+                    }
+                };
+
+                let mut attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size);
+                attachment.key = attachment_key.clone();
+                match attachment.save(&conn) {
+                    Ok(()) => (),
+                    Err(_) => println!("Error: failed to save attachment")
+                };
             },
-            SaveResult::Error(e) => {
-                println!("Error: {:?}", e);
-                return;
-            }
-        };
-
-        let attachment = Attachment::new(file_name, cipher.uuid.clone(), name, size);
-        match attachment.save(&conn) {
-            Ok(()) => (),
-            Err(_) => println!("Error: failed to save attachment")
-        };
+            _ => println!("Error: invalid multipart name")
+        }
     }).expect("Error processing multipart data");
 
     Ok(Json(cipher.to_json(&headers.host, &headers.user.uuid, &conn)))
@@ -793,6 +841,6 @@ fn _delete_cipher_attachment_by_id(uuid: &str, attachment_id: &str, headers: &He
             ws.send_cipher_update(UpdateType::SyncCipherDelete, &cipher, &cipher.update_users_revision(&conn));
             Ok(())
         }
-        Err(_) => err!("Deleting attachement failed")
+        Err(_) => err!("Deleting attachment failed")
     }
 }

+ 5 - 2
src/db/models/attachment.rs

@@ -12,6 +12,7 @@ pub struct Attachment {
     pub cipher_uuid: String,
     pub file_name: String,
     pub file_size: i32,
+    pub key: Option<String>
 }
 
 /// Local methods
@@ -22,6 +23,7 @@ impl Attachment {
             cipher_uuid,
             file_name,
             file_size,
+            key: None
         }
     }
 
@@ -41,6 +43,7 @@ impl Attachment {
             "FileName": self.file_name,
             "Size": self.file_size.to_string(),
             "SizeName": display_size,
+            "Key": self.key,
             "Object": "attachment"
         })
     }
@@ -92,8 +95,8 @@ impl Attachment {
     }
 
     pub fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> QueryResult<()> {
-        for attachement in Attachment::find_by_cipher(&cipher_uuid, &conn) {
-            attachement.delete(&conn)?;
+        for attachment in Attachment::find_by_cipher(&cipher_uuid, &conn) {
+            attachment.delete(&conn)?;
         }
         Ok(())
     }

+ 0 - 1
src/db/models/device.rs

@@ -71,7 +71,6 @@ impl Device {
         let time_now = Utc::now().naive_utc();
         self.updated_at = time_now;
 
-
         let orgowner: Vec<_> = orgs.iter().filter(|o| o.type_ == 0).map(|o| o.org_uuid.clone()).collect();
         let orgadmin: Vec<_> = orgs.iter().filter(|o| o.type_ == 1).map(|o| o.org_uuid.clone()).collect();
         let orguser: Vec<_> = orgs.iter().filter(|o| o.type_ == 2).map(|o| o.org_uuid.clone()).collect();

+ 1 - 0
src/db/schema.rs

@@ -4,6 +4,7 @@ table! {
         cipher_uuid -> Text,
         file_name -> Text,
         file_size -> Integer,
+        key -> Nullable<Text>,
     }
 }