refactor: move clipboard to frontend, fix cipher type mapping, add context menu
This commit is contained in:
@@ -10,18 +10,6 @@ import struct
|
||||
from base64 import b64decode, b64encode
|
||||
from typing import Optional, Tuple
|
||||
|
||||
_debug_log = "/tmp/decky-vaultwarden-debug.log"
|
||||
|
||||
def _debug(msg: str):
|
||||
import datetime
|
||||
ts = datetime.datetime.now().isoformat()
|
||||
try:
|
||||
with open(_debug_log, "a") as f:
|
||||
f.write(f"[{ts}] {msg}\n")
|
||||
f.flush()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives import hashes, padding
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
@@ -87,16 +75,9 @@ class BitwardenCrypto:
|
||||
|
||||
def stretch_master_key(self, master_key: bytes) -> Tuple[bytes, bytes]:
|
||||
"""Stretch master key into encryption key + MAC key using HKDF-expand."""
|
||||
_debug(f"[CRYPTO] stretch_master_key: master_key (first 8 b64)={b64encode(master_key[:8]).decode()}")
|
||||
_debug(f"[CRYPTO] stretch_master_key: master_key length={len(master_key)}")
|
||||
|
||||
# Bitwarden SDK: HKDF-expand(key, info) = HMAC-SHA256(key, info || 0x01)
|
||||
enc_key = hmac.new(master_key, b"enc\x01", hashlib.sha256).digest()
|
||||
mac_key = hmac.new(master_key, b"mac\x01", hashlib.sha256).digest()
|
||||
|
||||
_debug(f"[CRYPTO] stretch: enc_key (first 8 b64)={b64encode(enc_key[:8]).decode()}")
|
||||
_debug(f"[CRYPTO] stretch: mac_key (first 8 b64)={b64encode(mac_key[:8]).decode()}")
|
||||
|
||||
return enc_key, mac_key
|
||||
|
||||
def decrypt_aes_cbc_256(
|
||||
@@ -131,11 +112,9 @@ class BitwardenCrypto:
|
||||
Format: TYPE.BASE64(IV) | BASE64(CT) | BASE64(MAC)
|
||||
or: TYPE.BASE64(IV).BASE64(CT).BASE64(MAC)
|
||||
"""
|
||||
_debug(f"[CRYPTO] decrypt_cipher_string: type={enc_string[:2]}, full={enc_string[:50]}...")
|
||||
# Determine cipher type
|
||||
if enc_string.startswith(CIPHER_TYPE_AES_CBC_256_B64):
|
||||
parts = enc_string[2:].split("|")
|
||||
_debug(f"[CRYPTO] decrypt_cipher_string: type 2, parts count={len(parts)}")
|
||||
if len(parts) == 2:
|
||||
# Format: TYPE.BASE64(IV)|BASE64(CT)
|
||||
iv_b64, ct_b64 = parts
|
||||
@@ -148,13 +127,7 @@ class BitwardenCrypto:
|
||||
iv = b64decode(iv_b64)
|
||||
ct = b64decode(ct_b64)
|
||||
mac = b64decode(mac_b64)
|
||||
_debug(f"[CRYPTO] decrypt_cipher_string: verifying MAC...")
|
||||
computed_mac = self.hmac_sha256(mac_key, iv + ct)
|
||||
_debug(f"[CRYPTO] decrypt_cipher_string: computed_mac (b64)={b64encode(computed_mac).decode()}")
|
||||
_debug(f"[CRYPTO] decrypt_cipher_string: expected_mac (b64)={mac_b64}")
|
||||
_debug(f"[CRYPTO] decrypt_cipher_string: mac_keys_equal={computed_mac == mac}")
|
||||
_debug(f"[CRYPTO] decrypt_cipher_string: enc_key (first 8 b64)={b64encode(enc_key[:8]).decode()}")
|
||||
_debug(f"[CRYPTO] decrypt_cipher_string: mac_key (first 8 b64)={b64encode(mac_key[:8]).decode()}")
|
||||
if not self.verify_mac(mac_key, iv + ct, mac):
|
||||
raise ValueError("MAC verification failed")
|
||||
return self.decrypt_aes_cbc_256(enc_key, iv, ct)
|
||||
@@ -185,13 +158,10 @@ class BitwardenCrypto:
|
||||
|
||||
The user key is encrypted with the stretched master key.
|
||||
"""
|
||||
_debug(f"[CRYPTO] decrypt_user_key: encrypted_user_key (first 30)={encrypted_user_key[:30]}...")
|
||||
stretched_enc, stretched_mac = self.stretch_master_key(master_key)
|
||||
_debug(f"[CRYPTO] decrypt_user_key: calling decrypt_cipher_string...")
|
||||
user_key = self.decrypt_cipher_string(
|
||||
encrypted_user_key, stretched_enc, stretched_mac
|
||||
)
|
||||
_debug(f"[CRYPTO] decrypt_user_key: decrypted user_key length={len(user_key)}")
|
||||
|
||||
if len(user_key) == 64:
|
||||
# Has separate MAC key
|
||||
@@ -233,7 +203,7 @@ class BitwardenCrypto:
|
||||
|
||||
cipher_type = cipher_data.get("type")
|
||||
|
||||
if cipher_type == 0: # Login
|
||||
if cipher_type == 1: # Login
|
||||
login = cipher_data.get("login", {})
|
||||
result["login"] = {
|
||||
"username": self._decrypt_field(
|
||||
@@ -255,11 +225,11 @@ class BitwardenCrypto:
|
||||
for u in login.get("uris", [])
|
||||
],
|
||||
}
|
||||
elif cipher_type == 1: # Secure Note
|
||||
elif cipher_type == 2: # Secure Note
|
||||
result["notes"] = self._decrypt_field(
|
||||
cipher_data.get("notes"), enc_key, mac_key
|
||||
)
|
||||
elif cipher_type == 2: # Card
|
||||
elif cipher_type == 3: # Card
|
||||
card = cipher_data.get("card", {})
|
||||
result["card"] = {
|
||||
"cardholderName": self._decrypt_field(
|
||||
@@ -278,7 +248,7 @@ class BitwardenCrypto:
|
||||
card.get("expYear"), enc_key, mac_key
|
||||
),
|
||||
}
|
||||
elif cipher_type == 3: # Identity
|
||||
elif cipher_type == 4: # Identity
|
||||
identity = cipher_data.get("identity", {})
|
||||
result["identity"] = {
|
||||
"firstName": self._decrypt_field(
|
||||
@@ -311,7 +281,7 @@ class BitwardenCrypto:
|
||||
result["fields"] = fields
|
||||
|
||||
# Decrypt notes (only for non-Note types, Notes already handled above)
|
||||
if cipher_data.get("notes") and cipher_type != 1:
|
||||
if cipher_data.get("notes") and cipher_type != 2:
|
||||
result["notes"] = self._decrypt_field(
|
||||
cipher_data.get("notes"), enc_key, mac_key
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user