05/22 Enhance codes and fix bugs
This commit is contained in:
+15
-2
@@ -575,7 +575,9 @@ def change_password():
|
|||||||
current_auth_hash = data.get('current_auth_hash', '')
|
current_auth_hash = data.get('current_auth_hash', '')
|
||||||
new_auth_hash = data.get('new_auth_hash', '')
|
new_auth_hash = data.get('new_auth_hash', '')
|
||||||
new_enc_key_salt = data.get('new_enc_key_salt', '')
|
new_enc_key_salt = data.get('new_enc_key_salt', '')
|
||||||
items = data.get('items', []) # [{id, enc_data, iv}, ...]
|
items = data.get('items', []) # [{id, enc_data, iv, enc_name?, iv_name?}, ...]
|
||||||
|
sharing_private_key_enc = data.get('sharing_private_key_enc', '')
|
||||||
|
sharing_private_key_iv = data.get('sharing_private_key_iv', '')
|
||||||
|
|
||||||
if not current_auth_hash or not new_auth_hash or not new_enc_key_salt:
|
if not current_auth_hash or not new_auth_hash or not new_enc_key_salt:
|
||||||
return jsonify({'error': 'current_auth_hash, new_auth_hash, and new_enc_key_salt are required'}), 400
|
return jsonify({'error': 'current_auth_hash, new_auth_hash, and new_enc_key_salt are required'}), 400
|
||||||
@@ -629,6 +631,11 @@ def change_password():
|
|||||||
# Clear recovery data — it was encrypted with the old vault key and is now invalid
|
# Clear recovery data — it was encrypted with the old vault key and is now invalid
|
||||||
user.recovery_enc_salt = None
|
user.recovery_enc_salt = None
|
||||||
user.recovery_iv = None
|
user.recovery_iv = None
|
||||||
|
# Re-encrypt sharing private key with new vault key if the client sent it.
|
||||||
|
# Without this update, the old ciphertext would be undecryptable after key rotation.
|
||||||
|
if sharing_private_key_enc and sharing_private_key_iv:
|
||||||
|
user.sharing_private_key_enc = sharing_private_key_enc
|
||||||
|
user.sharing_private_key_iv = sharing_private_key_iv
|
||||||
|
|
||||||
AuditLog.log(
|
AuditLog.log(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
@@ -976,7 +983,13 @@ def recovery_items():
|
|||||||
items = VaultItem.query.filter_by(user_id=user.id).all()
|
items = VaultItem.query.filter_by(user_id=user.id).all()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'items': [
|
'items': [
|
||||||
{'id': item.id, 'enc_data': item.enc_data, 'iv': item.iv}
|
{
|
||||||
|
'id': item.id,
|
||||||
|
'enc_data': item.enc_data,
|
||||||
|
'iv': item.iv,
|
||||||
|
'enc_name': item.enc_name,
|
||||||
|
'iv_name': item.iv_name,
|
||||||
|
}
|
||||||
for item in items
|
for item in items
|
||||||
]
|
]
|
||||||
}), 200
|
}), 200
|
||||||
@@ -339,11 +339,23 @@ const Recover = (() => {
|
|||||||
item.enc_data,
|
item.enc_data,
|
||||||
item.iv,
|
item.iv,
|
||||||
);
|
);
|
||||||
const { enc_data, iv } = await Crypto.encryptItem(
|
const { enc_data, iv } = await Crypto.encryptItem(newVaultKey, plain);
|
||||||
newVaultKey,
|
let encNamePayload = {};
|
||||||
plain,
|
if (item.enc_name && item.iv_name) {
|
||||||
|
const plainName = await Crypto.decryptName(
|
||||||
|
_oldVaultKey,
|
||||||
|
item.enc_name,
|
||||||
|
item.iv_name,
|
||||||
);
|
);
|
||||||
reEncryptedItems.push({ id: item.id, enc_data, iv });
|
if (plainName) {
|
||||||
|
const { enc_name, iv_name } = await Crypto.encryptName(
|
||||||
|
newVaultKey,
|
||||||
|
plainName,
|
||||||
|
);
|
||||||
|
encNamePayload = { enc_name, iv_name };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reEncryptedItems.push({ id: item.id, enc_data, iv, ...encNamePayload });
|
||||||
} catch {
|
} catch {
|
||||||
// Item decryption failed — skip (shouldn't happen if recovery code is correct)
|
// Item decryption failed — skip (shouldn't happen if recovery code is correct)
|
||||||
console.warn(`Could not re-encrypt item ${item.id}`);
|
console.warn(`Could not re-encrypt item ${item.id}`);
|
||||||
|
|||||||
@@ -3323,6 +3323,48 @@ const Vault = (() => {
|
|||||||
reEncrypted.push({ id: item.id, enc_data, iv, ...encNamePayload });
|
reEncrypted.push({ id: item.id, enc_data, iv, ...encNamePayload });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-encrypt sharing private key with new vault key so sharing stays functional.
|
||||||
|
// The private key is stored as AES-GCM ciphertext on the server; rotating the
|
||||||
|
// vault key without re-encrypting it would leave it permanently unreadable.
|
||||||
|
let sharingKeyPayload = {};
|
||||||
|
try {
|
||||||
|
const sharingRes = await apiFetch("/api/sharing/keys");
|
||||||
|
if (sharingRes && sharingRes.ok) {
|
||||||
|
const sharingData = await sharingRes.json();
|
||||||
|
if (
|
||||||
|
sharingData.keys_setup &&
|
||||||
|
sharingData.private_key_enc &&
|
||||||
|
sharingData.private_key_iv
|
||||||
|
) {
|
||||||
|
const b64ToArr = (b64) =>
|
||||||
|
Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
||||||
|
const arrToB64 = (arr) =>
|
||||||
|
btoa(String.fromCharCode(...new Uint8Array(arr)));
|
||||||
|
const privKeyBytes = await window.crypto.subtle.decrypt(
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv: b64ToArr(sharingData.private_key_iv),
|
||||||
|
},
|
||||||
|
vaultKey,
|
||||||
|
b64ToArr(sharingData.private_key_enc),
|
||||||
|
);
|
||||||
|
const newPrivIv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||||
|
const reEncPriv = await window.crypto.subtle.encrypt(
|
||||||
|
{ name: "AES-GCM", iv: newPrivIv },
|
||||||
|
newVaultKey,
|
||||||
|
privKeyBytes,
|
||||||
|
);
|
||||||
|
sharingKeyPayload = {
|
||||||
|
sharing_private_key_enc: arrToB64(reEncPriv),
|
||||||
|
sharing_private_key_iv: arrToB64(newPrivIv),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("[PassKeeper] Could not re-encrypt sharing key:", err);
|
||||||
|
// Non-fatal — password change continues; user can regenerate sharing keys
|
||||||
|
}
|
||||||
|
|
||||||
// Submit atomic password change
|
// Submit atomic password change
|
||||||
const res = await apiFetch("/api/auth/change-password", {
|
const res = await apiFetch("/api/auth/change-password", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -3331,6 +3373,7 @@ const Vault = (() => {
|
|||||||
new_auth_hash: newAuthHash,
|
new_auth_hash: newAuthHash,
|
||||||
new_enc_key_salt: newEncKeySalt,
|
new_enc_key_salt: newEncKeySalt,
|
||||||
items: reEncrypted,
|
items: reEncrypted,
|
||||||
|
...sharingKeyPayload,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user