Skip to content

Commit 27a6633

Browse files
authored
Merge pull request #56 from Chocapikk/fix/security-hardening
Fix: Critical security hardening
2 parents d1b778c + 88ee0b8 commit 27a6633

5 files changed

Lines changed: 217 additions & 114 deletions

File tree

src-tauri/Cargo.lock

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ libreauth = "0.18.1"
2626
uuid = { version = "1.20.0", features = ["v4"] }
2727
zeroize = { version = "1.8.2", features = ["zeroize_derive"] }
2828
memsec = "0.7.0"
29+
argon2 = "0.5"

src-tauri/src/lib.rs

Lines changed: 101 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,39 @@ mod vault;
77

88
#[tauri::command]
99
fn resolve_twofactor(password: &str, uid: &str) -> Value {
10-
let mut container = driver::read(password);
11-
12-
let credentials = container.as_array_mut().unwrap();
13-
14-
let credential = credentials.iter_mut().find(|credential| {
15-
let credential = credential.as_object().unwrap();
16-
let id = credential.get("uid").unwrap().as_str().unwrap();
17-
18-
id == uid
19-
});
20-
21-
let credential_obj = credential.unwrap().as_object_mut().unwrap();
22-
23-
let credentials = credential_obj
24-
.get_mut("credential")
25-
.unwrap()
26-
.as_object_mut()
27-
.unwrap();
28-
29-
let secret = credentials.get("twoFactor").unwrap().as_str().unwrap();
30-
let totp = TOTPBuilder::new().base32_key(secret).finalize().unwrap();
31-
32-
let code = totp.generate();
33-
let response: Value = json!({ "success": true, "code": code });
10+
let container = driver::read(password);
3411

35-
response
12+
let credentials = match container.as_array() {
13+
Some(arr) => arr,
14+
None => return json!({ "success": false, "message": "Failed to read vault" }),
15+
};
16+
17+
let credential = match credentials.iter().find(|c| {
18+
c.as_object()
19+
.and_then(|obj| obj.get("uid"))
20+
.and_then(|id| id.as_str())
21+
.map(|id| id == uid)
22+
.unwrap_or(false)
23+
}) {
24+
Some(c) => c,
25+
None => return json!({ "success": false, "message": "Credential not found" }),
26+
};
27+
28+
let secret = match credential
29+
.get("credential")
30+
.and_then(|c| c.get("twoFactor"))
31+
.and_then(|t| t.as_str())
32+
{
33+
Some(s) => s,
34+
None => return json!({ "success": false, "message": "No 2FA secret found" }),
35+
};
36+
37+
let totp = match TOTPBuilder::new().base32_key(secret).finalize() {
38+
Ok(t) => t,
39+
Err(_) => return json!({ "success": false, "message": "Invalid 2FA secret" }),
40+
};
41+
42+
json!({ "success": true, "code": totp.generate() })
3643
}
3744

3845
#[tauri::command]
@@ -52,50 +59,60 @@ fn export_credentials(password: &str) -> Value {
5259
return json!({ "success": false, "message": "Authentication failed. Invalid password." });
5360
}
5461

55-
let response: Value = json!({ "success": true, "data": data });
56-
57-
response
62+
json!({ "success": true, "data": data })
5863
}
5964

6065
#[tauri::command]
6166
fn remove_credentials(password: &str, uid: &str) -> Value {
6267
let mut container = driver::read(password);
63-
let credentials = container.as_array_mut().unwrap();
6468

65-
credentials.retain(|credential| {
66-
let credential = credential.as_object().unwrap();
67-
let id = credential.get("uid").unwrap().as_str().unwrap();
69+
let credentials = match container.as_array_mut() {
70+
Some(arr) => arr,
71+
None => return json!({ "success": false, "message": "Failed to read vault" }),
72+
};
6873

69-
id != uid
74+
credentials.retain(|credential| {
75+
credential
76+
.as_object()
77+
.and_then(|obj| obj.get("uid"))
78+
.and_then(|id| id.as_str())
79+
.map(|id| id != uid)
80+
.unwrap_or(true)
7081
});
7182

7283
driver::write(password, json!(credentials));
7384

74-
let response: Value = json!({ "success": true });
75-
76-
response
85+
json!({ "success": true })
7786
}
7887

7988
#[tauri::command]
8089
fn update_credential(password: &str, uid: &str, credential_type: &str, credential: Value) -> Value {
8190
let mut container = driver::read(password);
82-
let credentials = container.as_array_mut().unwrap();
8391

84-
for item in credentials.iter_mut() {
85-
let credential_obj = item.as_object_mut().unwrap();
86-
let id = credential_obj.get("uid").unwrap().as_str().unwrap();
92+
let credentials = match container.as_array_mut() {
93+
Some(arr) => arr,
94+
None => return json!({ "success": false, "message": "Failed to read vault" }),
95+
};
8796

88-
if id == uid {
89-
credential_obj.insert("type".to_string(), json!(credential_type));
90-
credential_obj.insert("credential".to_string(), credential.clone());
91-
break;
97+
for item in credentials.iter_mut() {
98+
if let Some(obj) = item.as_object_mut() {
99+
let matches = obj
100+
.get("uid")
101+
.and_then(|id| id.as_str())
102+
.map(|id| id == uid)
103+
.unwrap_or(false);
104+
105+
if matches {
106+
obj.insert("type".to_string(), json!(credential_type));
107+
obj.insert("credential".to_string(), credential.clone());
108+
break;
109+
}
92110
}
93111
}
94112

95113
driver::write(password, json!(credentials));
96114

97-
let response: Value = json!({ "success": true });
98-
response
115+
json!({ "success": true })
99116
}
100117

101118
#[tauri::command]
@@ -104,7 +121,11 @@ fn add_credential(password: &str, credential_type: &str, credential: Value) -> V
104121

105122
let uid = Uuid::new_v4().to_string();
106123

107-
let credentials = container.as_array_mut().unwrap();
124+
let credentials = match container.as_array_mut() {
125+
Some(arr) => arr,
126+
None => return json!({ "success": false, "message": "Failed to read vault" }),
127+
};
128+
108129
credentials.push(json!({
109130
"uid": uid,
110131
"type": credential_type,
@@ -113,9 +134,7 @@ fn add_credential(password: &str, credential_type: &str, credential: Value) -> V
113134

114135
driver::write(password, json!(credentials));
115136

116-
let response: Value = json!({ "success": true });
117-
118-
response
137+
json!({ "success": true })
119138
}
120139

121140
#[tauri::command]
@@ -125,30 +144,23 @@ fn get_credentials(password: &str) -> Result<Vec<Value>, String> {
125144
return Err("Authentication failed. Invalid password.".to_string());
126145
}
127146

128-
let response: Vec<Value> = data
147+
let credentials = data
129148
.as_array()
130-
.unwrap()
149+
.ok_or("Invalid vault format")?;
150+
151+
let response: Vec<Value> = credentials
131152
.iter()
132153
.map(|credential| {
133154
let mut credential = credential.clone();
134-
let credential_obj = credential.as_object_mut().unwrap();
135-
136-
let credentials = credential_obj
137-
.get_mut("credential")
138-
.unwrap()
139-
.as_object_mut()
140-
.unwrap();
141-
142-
let twofactor = credentials
143-
.entry("twoFactor")
144-
.or_insert(serde_json::Value::Bool(false));
145-
146-
if twofactor.is_boolean() {
147-
*twofactor = serde_json::Value::Bool(false);
148-
} else {
149-
*twofactor = serde_json::Value::Bool(true);
155+
if let Some(obj) = credential.as_object_mut() {
156+
if let Some(creds) = obj.get_mut("credential").and_then(|c| c.as_object_mut()) {
157+
let has_twofactor = creds
158+
.get("twoFactor")
159+
.map(|t| t.is_string())
160+
.unwrap_or(false);
161+
creds.insert("twoFactor".to_string(), Value::Bool(has_twofactor));
162+
}
150163
}
151-
152164
credential
153165
})
154166
.collect();
@@ -162,20 +174,32 @@ fn register(password: &str) -> Value {
162174

163175
driver::write(password, json!([]));
164176

165-
let response: Value = json!({ "success": true });
166-
167-
response
177+
json!({ "success": true })
168178
}
169179

170180
#[tauri::command]
171181
fn login(password: &str) -> bool {
172-
let container = driver::read(password);
173-
174-
if container.is_array() {
175-
return true;
182+
let container_path = vault::get_container_path();
183+
let encrypted_data = match std::fs::read(&container_path) {
184+
Ok(data) => data,
185+
Err(_) => return false,
186+
};
187+
188+
// Try legacy format first (fast, no KDF)
189+
if let Ok(plaintext) = vault::decrypt_legacy_public(password, &encrypted_data) {
190+
if let Ok(container) = serde_json::from_str::<serde_json::Value>(&plaintext) {
191+
if container.is_array() {
192+
// Re-encrypt with Argon2id
193+
driver::write(password, container);
194+
return true;
195+
}
196+
}
197+
return false;
176198
}
177199

178-
false
200+
// Try new Argon2id format
201+
let container = driver::read(password);
202+
container.is_array()
179203
}
180204

181205
#[tauri::command]

0 commit comments

Comments
 (0)