05/18 Enhanced codes and functionalities 4
This commit is contained in:
@@ -151,6 +151,38 @@ const SharingCrypto = (() => {
|
||||
return JSON.parse(new TextDecoder().decode(plaintext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a display name string with the ECDH shared key.
|
||||
* Uses the same AES-256-GCM primitive as encryptForShare but operates on
|
||||
* a plain string rather than a JSON-serialised object.
|
||||
*/
|
||||
async function encryptName(sharedKey, name) {
|
||||
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||
const plaintext = new TextEncoder().encode(name);
|
||||
const ciphertext = await subtle.encrypt({ name: 'AES-GCM', iv }, sharedKey, plaintext);
|
||||
return {
|
||||
enc_name: bytesToBase64(new Uint8Array(ciphertext)),
|
||||
iv_name: bytesToBase64(iv),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an encrypted display name.
|
||||
* Returns null on failure (e.g. legacy share with no enc_name).
|
||||
*/
|
||||
async function decryptName(sharedKey, enc_name, iv_name) {
|
||||
try {
|
||||
const plaintext = await subtle.decrypt(
|
||||
{ name: 'AES-GCM', iv: base64ToBytes(iv_name) },
|
||||
sharedKey,
|
||||
base64ToBytes(enc_name),
|
||||
);
|
||||
return new TextDecoder().decode(plaintext);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
return {
|
||||
@@ -162,6 +194,8 @@ const SharingCrypto = (() => {
|
||||
deriveSharedKey,
|
||||
encryptForShare,
|
||||
decryptShare,
|
||||
encryptName,
|
||||
decryptName,
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
+33
-4
@@ -1413,7 +1413,15 @@ const Vault = (() => {
|
||||
${
|
||||
!s.accepted
|
||||
? `<button class="btn-primary btn-sm" data-accept="${s.id}">Accept</button>`
|
||||
: `<button class="btn-secondary btn-sm" data-view-share="${s.id}" data-owner-key="${escHtml(s.owner_public_key || "")}" data-enc="${escHtml(s.enc_data)}" data-iv="${escHtml(s.iv)}" data-name="${escHtml(s.item_name)}" data-type="${escHtml(s.item_type || "")}">View</button>`
|
||||
: `<button class="btn-secondary btn-sm"
|
||||
data-view-share="${s.id}"
|
||||
data-owner-key="${escHtml(s.owner_public_key || "")}"
|
||||
data-enc="${escHtml(s.enc_data)}"
|
||||
data-iv="${escHtml(s.iv)}"
|
||||
data-enc-name="${escHtml(s.enc_name || "")}"
|
||||
data-iv-name="${escHtml(s.iv_name || "")}"
|
||||
data-name="${escHtml(s.item_name)}"
|
||||
data-type="${escHtml(s.item_type || "")}">View</button>`
|
||||
}
|
||||
</li>`,
|
||||
)
|
||||
@@ -1452,7 +1460,18 @@ const Vault = (() => {
|
||||
btn.dataset.enc,
|
||||
btn.dataset.iv,
|
||||
);
|
||||
showSharedItemDetails(btn.dataset.name, btn.dataset.type, plain);
|
||||
// Decrypt the item name if an encrypted version is available.
|
||||
// Falls back to the non-sensitive label for legacy shares.
|
||||
let displayName = btn.dataset.name;
|
||||
if (btn.dataset.encName && btn.dataset.ivName) {
|
||||
const decrypted = await SharingCrypto.decryptName(
|
||||
sharedKey,
|
||||
btn.dataset.encName,
|
||||
btn.dataset.ivName,
|
||||
);
|
||||
if (decrypted) displayName = decrypted;
|
||||
}
|
||||
showSharedItemDetails(displayName, btn.dataset.type, plain);
|
||||
} catch (err) {
|
||||
showToast("Could not decrypt: " + err.message, "error");
|
||||
}
|
||||
@@ -1564,6 +1583,14 @@ const Vault = (() => {
|
||||
item.plain,
|
||||
);
|
||||
|
||||
// Encrypt the display name with the same ECDH shared key so the server
|
||||
// never sees the plaintext name. item_name is set to item_type as a
|
||||
// non-sensitive fallback label for legacy-compat (server requires the field).
|
||||
const { enc_name, iv_name } = await SharingCrypto.encryptName(
|
||||
sharedKey,
|
||||
item.name,
|
||||
);
|
||||
|
||||
const res = await apiFetch("/api/sharing", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
@@ -1571,8 +1598,10 @@ const Vault = (() => {
|
||||
recipient_email: recipientEmail,
|
||||
enc_data,
|
||||
iv,
|
||||
item_name: item.name,
|
||||
item_name: item.item_type, // non-sensitive label; real name is in enc_name
|
||||
item_type: item.item_type,
|
||||
enc_name,
|
||||
iv_name,
|
||||
}),
|
||||
});
|
||||
if (!res) return;
|
||||
@@ -4016,4 +4045,4 @@ const Vault = (() => {
|
||||
}
|
||||
})();
|
||||
|
||||
document.addEventListener("DOMContentLoaded", Vault.init);
|
||||
document.addEventListener("DOMContentLoaded", Vault.init);
|
||||
|
||||
Reference in New Issue
Block a user