@@ -17,6 +17,9 @@ const REKEK_CTX: &[u8] = b"ghostfs-rekey-wrapping-v1";
1717const DIRENC_CTX : & [ u8 ] = b"ghostfs-dirname-encryption-v1" ;
1818const DIRNAME_BLIND_CTX : & [ u8 ] = b"ghostfs-dirname-blindindex-v1" ;
1919const SIGNING_KEY_CTX : & [ u8 ] = b"ghostfs-forensics-signing-key-v1" ;
20+ const INODE_ENC_CTX : & [ u8 ] = b"ghostfs-inode-metadata-encryption-v1" ;
21+ const XATTR_ENC_CTX : & [ u8 ] = b"ghostfs-xattr-encryption-v1" ;
22+ const XATTR_BLIND_CTX : & [ u8 ] = b"ghostfs-xattr-blindindex-v1" ;
2023
2124/// AAD = "GFS" || ino (8B LE) || block_idx (8B LE) || volume_uuid (16B)
2225/// Wiąże szyfrogram z konkretnym blokiem konkretnego pliku konkretnego wolumenu.
@@ -136,6 +139,56 @@ impl Crypto {
136139 * h. finalize ( ) . as_bytes ( )
137140 }
138141
142+ /// Klucz szyfrujący METADANE inode (rozmiar, uprawnienia, czasy,
143+ /// uid/gid) — wcześniej `fuser::FileAttr` leżał jako jawny `bincode`
144+ /// wprost w wartości klucza sled `inode:{ino}`, mimo że dane bloków
145+ /// (`data:*`) i nazwy plików (`didx:*`, patrz dirindex.rs) były już
146+ /// szyfrowane. Ktoś z dostępem tylko do plików bazy sled (skopiowany
147+ /// obraz dysku, backup bez klucza) mógł odczytać rozmiar, właściciela,
148+ /// uprawnienia i znaczniki czasu KAŻDEGO pliku bez znajomości master
149+ /// key — dla systemu plików reklamującego się jako "cybersecurity"
150+ /// to była realna luka poufności metadanych, nie tylko teoretyczna.
151+ ///
152+ /// Jeden klucz dla całego wolumenu (nie per-inode jak FEK) — metadane
153+ /// nie mają tej samej potrzeby izolacji co dane plików, a unikalność
154+ /// nonce jest i tak gwarantowana przez schemat sesyjny w `next_nonce()`.
155+ pub fn derive_inode_enc_key ( & self ) -> Key {
156+ let mut h = Hasher :: new_keyed ( & self . master_key ) ;
157+ h. update ( INODE_ENC_CTX ) ;
158+ h. update ( & self . volume_uuid ) ;
159+ * h. finalize ( ) . as_bytes ( )
160+ }
161+
162+ /// Klucz szyfrujący WARTOŚĆ rozszerzonego atrybutu (xattr) danego
163+ /// inode — patrz `fs/xattr.rs`. Wcześniej zarówno NAZWA (wprost w
164+ /// kluczu sled) jak i WARTOŚĆ xattr leżały jawnym tekstem — a xattr
165+ /// bywają wrażliwe (etykiety SELinux/ACL, tokeny, metadane aplikacji
166+ /// typu "pobrano z <url>"), więc to była realna, nie tylko teoretyczna
167+ /// luka poufności, tej samej klasy co nazwy plików i metadane inode.
168+ pub fn derive_xattr_enc_key ( & self , ino : u64 ) -> Key {
169+ let mut h = Hasher :: new_keyed ( & self . master_key ) ;
170+ h. update ( XATTR_ENC_CTX ) ;
171+ h. update ( & ino. to_le_bytes ( ) ) ;
172+ h. update ( & self . volume_uuid ) ;
173+ * h. finalize ( ) . as_bytes ( )
174+ }
175+
176+ /// Blind index dla NAZWY xattr — ten sam wzorzec co
177+ /// `dirname_blind_index`: deterministyczny, kluczowany master_key,
178+ /// pozwala na lookup/set/remove O(1) bez odszyfrowywania, ale odporny
179+ /// na atak słownikowy na typowe nazwy xattr ("security.selinux",
180+ /// "user.comment", ...) bez znajomości klucza.
181+ pub fn xattr_blind_index ( & self , ino : u64 , name : & [ u8 ] ) -> [ u8 ; 32 ] {
182+ let mut ctx_hasher = Hasher :: new_keyed ( & self . master_key ) ;
183+ ctx_hasher. update ( XATTR_BLIND_CTX ) ;
184+ ctx_hasher. update ( & ino. to_le_bytes ( ) ) ;
185+ ctx_hasher. update ( & self . volume_uuid ) ;
186+ let subkey = * ctx_hasher. finalize ( ) . as_bytes ( ) ;
187+ let mut h = Hasher :: new_keyed ( & subkey) ;
188+ h. update ( name) ;
189+ * h. finalize ( ) . as_bytes ( )
190+ }
191+
139192 /// Zaszyfruj blok danych z pełnym AAD (ino, block_idx, volume_uuid).
140193 /// Użyj tej metody dla bloków danych — nie `encrypt_with_key` bez kontekstu.
141194 pub fn encrypt_block (
0 commit comments