luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

fde.c (43761B)


      1 /* FDE - Full Disk Encryption for LuajitOS
      2  *
      3  * Cascaded encryption: AES-256-GCM (inner) + XChaCha20-Poly1305 (outer)
      4  */
      5 
      6 #include "fde.h"
      7 #include "ata.h"
      8 #include "crypto/AES-256-GCM.h"
      9 #include "crypto/XChaCha20-Poly1305.h"
     10 #include "crypto/Argon2.h"
     11 #include "crypto/CSPRNG.h"
     12 #include "crypto/hashing/hash.h"
     13 #include <string.h>
     14 #include <stdlib.h>
     15 #include <stdio.h>
     16 
     17 /* External terminal function for debug output */
     18 extern void terminal_writestring(const char *str);
     19 
     20 /* ============================================================================
     21  * Internal Helper Functions
     22  * ========================================================================= */
     23 
     24 /* Constant-time memory comparison */
     25 static int secure_compare(const uint8_t *a, const uint8_t *b, size_t len) {
     26     uint8_t diff = 0;
     27     for (size_t i = 0; i < len; i++) {
     28         diff |= a[i] ^ b[i];
     29     }
     30     return diff == 0 ? 0 : -1;
     31 }
     32 
     33 /* Secure memory wipe */
     34 static void secure_wipe(void *ptr, size_t len) {
     35     volatile uint8_t *p = (volatile uint8_t *)ptr;
     36     while (len--) {
     37         *p++ = 0;
     38     }
     39 }
     40 
     41 /* Derive encryption keys from password using Argon2id */
     42 static int derive_keys(const char *password, size_t pass_len,
     43                        const uint8_t salt[FDE_SALT_SIZE],
     44                        uint8_t aes_key[FDE_KEY_SIZE],
     45                        uint8_t xchacha_key[FDE_KEY_SIZE]) {
     46     uint8_t master_key[FDE_MASTER_KEY_SIZE];
     47 
     48     /* Use Argon2id with recommended parameters */
     49     int result = argon2id((const uint8_t *)password, pass_len,
     50                           salt, FDE_SALT_SIZE,
     51                           FDE_KDF_TIME, FDE_KDF_MEMORY,
     52                           master_key, FDE_MASTER_KEY_SIZE);
     53 
     54     if (result != 0) {
     55         secure_wipe(master_key, sizeof(master_key));
     56         return FDE_ERR_CRYPTO;
     57     }
     58 
     59     /* Split master key into two keys */
     60     memcpy(aes_key, master_key, FDE_KEY_SIZE);
     61     memcpy(xchacha_key, master_key + FDE_KEY_SIZE, FDE_KEY_SIZE);
     62 
     63     secure_wipe(master_key, sizeof(master_key));
     64     return FDE_OK;
     65 }
     66 
     67 /* Build AES nonce from sector number */
     68 static void build_aes_nonce(uint32_t sector, uint8_t nonce[FDE_NONCE_AES_SIZE]) {
     69     memset(nonce, 0, FDE_NONCE_AES_SIZE);
     70     /* Little-endian sector number in first 4 bytes */
     71     nonce[0] = (sector >> 0) & 0xFF;
     72     nonce[1] = (sector >> 8) & 0xFF;
     73     nonce[2] = (sector >> 16) & 0xFF;
     74     nonce[3] = (sector >> 24) & 0xFF;
     75     /* Remaining 8 bytes are zero - unique per sector */
     76 }
     77 
     78 /* Build XChaCha nonce from sector number */
     79 static void build_xchacha_nonce(uint32_t sector, uint8_t nonce[FDE_NONCE_XCHACHA_SIZE]) {
     80     memset(nonce, 0, FDE_NONCE_XCHACHA_SIZE);
     81     /* Little-endian sector number in first 4 bytes */
     82     nonce[0] = (sector >> 0) & 0xFF;
     83     nonce[1] = (sector >> 8) & 0xFF;
     84     nonce[2] = (sector >> 16) & 0xFF;
     85     nonce[3] = (sector >> 24) & 0xFF;
     86     /* Remaining 20 bytes are zero - unique per sector */
     87 }
     88 
     89 /* Calculate how many sectors needed for tag storage */
     90 static uint32_t calc_tag_sectors(uint32_t data_sectors) {
     91     /* Each data sector needs 32 bytes of tags (16 AES + 16 XChaCha) */
     92     /* 512 bytes per sector / 32 bytes per tag = 16 tags per sector */
     93     return (data_sectors + 15) / 16;
     94 }
     95 
     96 /* Read sector tags (partition_start is added to make absolute disk address) */
     97 static int read_sector_tags(uint8_t bus, uint8_t drive, uint32_t partition_start,
     98                             uint32_t tag_start, uint32_t sector, fde_sector_tags_t *tags) {
     99     /* Each tag sector holds tags for 16 data sectors */
    100     uint32_t tag_sector = partition_start + tag_start + (sector / 16);
    101     uint32_t tag_offset = (sector % 16) * FDE_TAGS_PER_SECTOR;
    102 
    103     uint8_t tag_buffer[FDE_SECTOR_SIZE];
    104     int result = ata_read_sectors(bus, drive, tag_sector, 1, tag_buffer);
    105     if (result != ATA_OK) {
    106         return FDE_ERR_IO;
    107     }
    108 
    109     memcpy(tags, tag_buffer + tag_offset, sizeof(fde_sector_tags_t));
    110     return FDE_OK;
    111 }
    112 
    113 /* Write sector tags (partition_start is added to make absolute disk address) */
    114 static int write_sector_tags(uint8_t bus, uint8_t drive, uint32_t partition_start,
    115                              uint32_t tag_start, uint32_t sector, const fde_sector_tags_t *tags) {
    116     /* Each tag sector holds tags for 16 data sectors */
    117     uint32_t tag_sector = partition_start + tag_start + (sector / 16);
    118     uint32_t tag_offset = (sector % 16) * FDE_TAGS_PER_SECTOR;
    119 
    120     /* Read-modify-write */
    121     uint8_t tag_buffer[FDE_SECTOR_SIZE];
    122     int result = ata_read_sectors(bus, drive, tag_sector, 1, tag_buffer);
    123     if (result != ATA_OK) {
    124         char dbg[80];
    125         snprintf(dbg, sizeof(dbg), "[FDE] write_sector_tags: read failed, tag_sector=%d\n", (int)tag_sector);
    126         terminal_writestring(dbg);
    127         return FDE_ERR_IO;
    128     }
    129 
    130     memcpy(tag_buffer + tag_offset, tags, sizeof(fde_sector_tags_t));
    131 
    132     result = ata_write_sectors(bus, drive, tag_sector, 1, tag_buffer);
    133     if (result != ATA_OK) {
    134         char dbg[80];
    135         snprintf(dbg, sizeof(dbg), "[FDE] write_sector_tags: write failed, tag_sector=%d\n", (int)tag_sector);
    136         terminal_writestring(dbg);
    137         return FDE_ERR_IO;
    138     }
    139 
    140     return FDE_OK;
    141 }
    142 
    143 /* Encrypt a single sector (cascade mode) */
    144 static int encrypt_sector_cascade(const uint8_t aes_key[FDE_KEY_SIZE],
    145                                   const uint8_t xchacha_key[FDE_KEY_SIZE],
    146                                   uint32_t sector_num,
    147                                   const uint8_t plaintext[FDE_SECTOR_SIZE],
    148                                   uint8_t ciphertext[FDE_SECTOR_SIZE],
    149                                   fde_sector_tags_t *tags) {
    150     uint8_t intermediate[FDE_SECTOR_SIZE];
    151     uint8_t aes_nonce[FDE_NONCE_AES_SIZE];
    152     uint8_t xchacha_nonce[FDE_NONCE_XCHACHA_SIZE];
    153 
    154     /* Build nonces */
    155     build_aes_nonce(sector_num, aes_nonce);
    156     build_xchacha_nonce(sector_num, xchacha_nonce);
    157 
    158     /* Inner encryption: AES-256-GCM */
    159     aes256_gcm_context aes_ctx;
    160     if (aes256_gcm_init(&aes_ctx, aes_key) != 0) {
    161         return FDE_ERR_CRYPTO;
    162     }
    163 
    164     if (aes256_gcm_encrypt(&aes_ctx, aes_nonce, FDE_NONCE_AES_SIZE,
    165                            NULL, 0,  /* No AAD */
    166                            plaintext, FDE_SECTOR_SIZE,
    167                            intermediate, tags->aes_tag, FDE_TAG_SIZE) != 0) {
    168         aes256_gcm_cleanup(&aes_ctx);
    169         return FDE_ERR_CRYPTO;
    170     }
    171     aes256_gcm_cleanup(&aes_ctx);
    172 
    173     /* Outer encryption: XChaCha20-Poly1305 */
    174     xchacha20_poly1305_context xchacha_ctx;
    175     if (xchacha20_poly1305_init(&xchacha_ctx, xchacha_key, xchacha_nonce) != 0) {
    176         secure_wipe(intermediate, sizeof(intermediate));
    177         return FDE_ERR_CRYPTO;
    178     }
    179 
    180     if (xchacha20_poly1305_encrypt(&xchacha_ctx, NULL, 0,  /* No AAD */
    181                                    intermediate, FDE_SECTOR_SIZE,
    182                                    ciphertext, tags->xchacha_tag) != 0) {
    183         xchacha20_poly1305_cleanup(&xchacha_ctx);
    184         secure_wipe(intermediate, sizeof(intermediate));
    185         return FDE_ERR_CRYPTO;
    186     }
    187     xchacha20_poly1305_cleanup(&xchacha_ctx);
    188 
    189     secure_wipe(intermediate, sizeof(intermediate));
    190     return FDE_OK;
    191 }
    192 
    193 /* Decrypt a single sector (cascade mode) */
    194 static int decrypt_sector_cascade(const uint8_t aes_key[FDE_KEY_SIZE],
    195                                   const uint8_t xchacha_key[FDE_KEY_SIZE],
    196                                   uint32_t sector_num,
    197                                   const uint8_t ciphertext[FDE_SECTOR_SIZE],
    198                                   const fde_sector_tags_t *tags,
    199                                   uint8_t plaintext[FDE_SECTOR_SIZE]) {
    200     uint8_t intermediate[FDE_SECTOR_SIZE];
    201     uint8_t aes_nonce[FDE_NONCE_AES_SIZE];
    202     uint8_t xchacha_nonce[FDE_NONCE_XCHACHA_SIZE];
    203 
    204     /* Build nonces */
    205     build_aes_nonce(sector_num, aes_nonce);
    206     build_xchacha_nonce(sector_num, xchacha_nonce);
    207 
    208     /* Outer decryption: XChaCha20-Poly1305 */
    209     xchacha20_poly1305_context xchacha_ctx;
    210     if (xchacha20_poly1305_init(&xchacha_ctx, xchacha_key, xchacha_nonce) != 0) {
    211         return FDE_ERR_CRYPTO;
    212     }
    213 
    214     if (xchacha20_poly1305_decrypt(&xchacha_ctx, NULL, 0,  /* No AAD */
    215                                    ciphertext, FDE_SECTOR_SIZE,
    216                                    tags->xchacha_tag,
    217                                    intermediate) != 0) {
    218         xchacha20_poly1305_cleanup(&xchacha_ctx);
    219         return FDE_ERR_AUTH_FAILED;
    220     }
    221     xchacha20_poly1305_cleanup(&xchacha_ctx);
    222 
    223     /* Inner decryption: AES-256-GCM */
    224     aes256_gcm_context aes_ctx;
    225     if (aes256_gcm_init(&aes_ctx, aes_key) != 0) {
    226         secure_wipe(intermediate, sizeof(intermediate));
    227         return FDE_ERR_CRYPTO;
    228     }
    229 
    230     if (aes256_gcm_decrypt(&aes_ctx, aes_nonce, FDE_NONCE_AES_SIZE,
    231                            NULL, 0,  /* No AAD */
    232                            intermediate, FDE_SECTOR_SIZE,
    233                            tags->aes_tag, FDE_TAG_SIZE,
    234                            plaintext) != 0) {
    235         aes256_gcm_cleanup(&aes_ctx);
    236         secure_wipe(intermediate, sizeof(intermediate));
    237         return FDE_ERR_AUTH_FAILED;
    238     }
    239     aes256_gcm_cleanup(&aes_ctx);
    240 
    241     secure_wipe(intermediate, sizeof(intermediate));
    242     return FDE_OK;
    243 }
    244 
    245 /* Encrypt a single sector (AES only) */
    246 static int encrypt_sector_aes(const uint8_t aes_key[FDE_KEY_SIZE],
    247                               uint32_t sector_num,
    248                               const uint8_t plaintext[FDE_SECTOR_SIZE],
    249                               uint8_t ciphertext[FDE_SECTOR_SIZE],
    250                               fde_sector_tags_t *tags) {
    251     uint8_t aes_nonce[FDE_NONCE_AES_SIZE];
    252     build_aes_nonce(sector_num, aes_nonce);
    253 
    254     aes256_gcm_context aes_ctx;
    255     if (aes256_gcm_init(&aes_ctx, aes_key) != 0) {
    256         return FDE_ERR_CRYPTO;
    257     }
    258 
    259     if (aes256_gcm_encrypt(&aes_ctx, aes_nonce, FDE_NONCE_AES_SIZE,
    260                            NULL, 0,
    261                            plaintext, FDE_SECTOR_SIZE,
    262                            ciphertext, tags->aes_tag, FDE_TAG_SIZE) != 0) {
    263         aes256_gcm_cleanup(&aes_ctx);
    264         return FDE_ERR_CRYPTO;
    265     }
    266     aes256_gcm_cleanup(&aes_ctx);
    267 
    268     /* Clear unused tag */
    269     memset(tags->xchacha_tag, 0, FDE_TAG_SIZE);
    270     return FDE_OK;
    271 }
    272 
    273 /* Decrypt a single sector (AES only) */
    274 static int decrypt_sector_aes(const uint8_t aes_key[FDE_KEY_SIZE],
    275                               uint32_t sector_num,
    276                               const uint8_t ciphertext[FDE_SECTOR_SIZE],
    277                               const fde_sector_tags_t *tags,
    278                               uint8_t plaintext[FDE_SECTOR_SIZE]) {
    279     uint8_t aes_nonce[FDE_NONCE_AES_SIZE];
    280     build_aes_nonce(sector_num, aes_nonce);
    281 
    282     aes256_gcm_context aes_ctx;
    283     if (aes256_gcm_init(&aes_ctx, aes_key) != 0) {
    284         return FDE_ERR_CRYPTO;
    285     }
    286 
    287     if (aes256_gcm_decrypt(&aes_ctx, aes_nonce, FDE_NONCE_AES_SIZE,
    288                            NULL, 0,
    289                            ciphertext, FDE_SECTOR_SIZE,
    290                            tags->aes_tag, FDE_TAG_SIZE,
    291                            plaintext) != 0) {
    292         aes256_gcm_cleanup(&aes_ctx);
    293         return FDE_ERR_AUTH_FAILED;
    294     }
    295     aes256_gcm_cleanup(&aes_ctx);
    296     return FDE_OK;
    297 }
    298 
    299 /* Encrypt a single sector (XChaCha only) */
    300 static int encrypt_sector_xchacha(const uint8_t xchacha_key[FDE_KEY_SIZE],
    301                                   uint32_t sector_num,
    302                                   const uint8_t plaintext[FDE_SECTOR_SIZE],
    303                                   uint8_t ciphertext[FDE_SECTOR_SIZE],
    304                                   fde_sector_tags_t *tags) {
    305     uint8_t xchacha_nonce[FDE_NONCE_XCHACHA_SIZE];
    306     build_xchacha_nonce(sector_num, xchacha_nonce);
    307 
    308     xchacha20_poly1305_context xchacha_ctx;
    309     if (xchacha20_poly1305_init(&xchacha_ctx, xchacha_key, xchacha_nonce) != 0) {
    310         return FDE_ERR_CRYPTO;
    311     }
    312 
    313     if (xchacha20_poly1305_encrypt(&xchacha_ctx, NULL, 0,
    314                                    plaintext, FDE_SECTOR_SIZE,
    315                                    ciphertext, tags->xchacha_tag) != 0) {
    316         xchacha20_poly1305_cleanup(&xchacha_ctx);
    317         return FDE_ERR_CRYPTO;
    318     }
    319     xchacha20_poly1305_cleanup(&xchacha_ctx);
    320 
    321     /* Clear unused tag */
    322     memset(tags->aes_tag, 0, FDE_TAG_SIZE);
    323     return FDE_OK;
    324 }
    325 
    326 /* Decrypt a single sector (XChaCha only) */
    327 static int decrypt_sector_xchacha(const uint8_t xchacha_key[FDE_KEY_SIZE],
    328                                   uint32_t sector_num,
    329                                   const uint8_t ciphertext[FDE_SECTOR_SIZE],
    330                                   const fde_sector_tags_t *tags,
    331                                   uint8_t plaintext[FDE_SECTOR_SIZE]) {
    332     uint8_t xchacha_nonce[FDE_NONCE_XCHACHA_SIZE];
    333     build_xchacha_nonce(sector_num, xchacha_nonce);
    334 
    335     xchacha20_poly1305_context xchacha_ctx;
    336     if (xchacha20_poly1305_init(&xchacha_ctx, xchacha_key, xchacha_nonce) != 0) {
    337         return FDE_ERR_CRYPTO;
    338     }
    339 
    340     if (xchacha20_poly1305_decrypt(&xchacha_ctx, NULL, 0,
    341                                    ciphertext, FDE_SECTOR_SIZE,
    342                                    tags->xchacha_tag,
    343                                    plaintext) != 0) {
    344         xchacha20_poly1305_cleanup(&xchacha_ctx);
    345         return FDE_ERR_AUTH_FAILED;
    346     }
    347     xchacha20_poly1305_cleanup(&xchacha_ctx);
    348     return FDE_OK;
    349 }
    350 
    351 /* ============================================================================
    352  * Public API Implementation
    353  * ========================================================================= */
    354 
    355 /* Internal format implementation - works on partition or whole disk */
    356 static int fde_format_internal(uint8_t bus, uint8_t drive,
    357                                uint32_t part_start, uint32_t part_size,
    358                                const char *password, size_t pass_len,
    359                                uint8_t cipher) {
    360     if (!password || pass_len == 0) {
    361         return FDE_ERR_INVALID_PARAM;
    362     }
    363 
    364     if (cipher > FDE_CIPHER_CASCADE) {
    365         return FDE_ERR_INVALID_PARAM;
    366     }
    367 
    368     terminal_writestring("[FDE] Formatting with encryption...\n");
    369 
    370     {
    371         char dbg[128];
    372         snprintf(dbg, sizeof(dbg), "[FDE] bus=%d drive=%d part_start=%d part_size=%d\n",
    373                  (int)bus, (int)drive, (int)part_start, (int)part_size);
    374         terminal_writestring(dbg);
    375     }
    376 
    377     /* Initialize CSPRNG */
    378     csprng_global_init();
    379 
    380     /* Generate random salt */
    381     uint8_t salt[FDE_SALT_SIZE];
    382     random_bytes(salt, FDE_SALT_SIZE);
    383 
    384     /* Derive encryption keys */
    385     terminal_writestring("[FDE] Deriving encryption keys (Argon2id)...\n");
    386     uint8_t aes_key[FDE_KEY_SIZE];
    387     uint8_t xchacha_key[FDE_KEY_SIZE];
    388     int result = derive_keys(password, pass_len, salt, aes_key, xchacha_key);
    389     if (result != FDE_OK) {
    390         terminal_writestring("[FDE] ERROR: Key derivation failed (need 4MB for Argon2)!\n");
    391         return result;
    392     }
    393     terminal_writestring("[FDE] Key derivation complete.\n");
    394 
    395     /* Generate random master key (for encrypted storage in header) */
    396     uint8_t master_key[FDE_MASTER_KEY_SIZE];
    397     memcpy(master_key, aes_key, FDE_KEY_SIZE);
    398     memcpy(master_key + FDE_KEY_SIZE, xchacha_key, FDE_KEY_SIZE);
    399 
    400     /* Calculate layout - relative to partition */
    401     uint32_t total_partition_sectors = part_size;
    402     uint32_t header_sectors = 1;  /* Sector 0 of partition */
    403 
    404     /* Reserve sectors for tags: each data sector needs 32 bytes of tags */
    405     /* With ~90% data efficiency: total = header + tags + data */
    406     /* tags = data / 16 (each tag sector holds 16 data sector tags) */
    407     /* So: total = 1 + data/16 + data = 1 + 17*data/16 */
    408     /* data = (total - 1) * 16 / 17 */
    409     uint32_t data_sectors = ((total_partition_sectors - header_sectors) * 16) / 17;
    410     uint32_t tag_sectors = calc_tag_sectors(data_sectors);
    411 
    412     /* These are relative to partition start */
    413     uint32_t tag_start = header_sectors;
    414     uint32_t data_start = header_sectors + tag_sectors;
    415 
    416     {
    417         char dbg[120];
    418         snprintf(dbg, sizeof(dbg), "[FDE] Layout: total=%d data=%d tags=%d tag_start=%d data_start=%d\n",
    419                  (int)total_partition_sectors, (int)data_sectors, (int)tag_sectors, (int)tag_start, (int)data_start);
    420         terminal_writestring(dbg);
    421     }
    422 
    423     /* Build header */
    424     fde_header_t header;
    425     memset(&header, 0, sizeof(header));
    426     memcpy(header.magic, FDE_MAGIC, FDE_MAGIC_SIZE);
    427     header.version = FDE_VERSION;
    428     header.cipher_mode = cipher;
    429     header.kdf_iterations = FDE_KDF_TIME;
    430     header.total_sectors = data_sectors;
    431     header.data_start_sector = data_start;
    432     header.tag_start_sector = tag_start;
    433     memcpy(header.salt, salt, FDE_SALT_SIZE);
    434 
    435     /* Encrypt master key with derived keys (if cascade, encrypt with both) */
    436     if (cipher == FDE_CIPHER_CASCADE || cipher == FDE_CIPHER_AES_GCM) {
    437         uint8_t aes_nonce[FDE_NONCE_AES_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF,
    438                                                   0xFF, 0xFF, 0xFF, 0xFF,
    439                                                   0xFF, 0xFF, 0xFF, 0xFF};
    440         aes256_gcm_context aes_ctx;
    441         aes256_gcm_init(&aes_ctx, aes_key);
    442         aes256_gcm_encrypt(&aes_ctx, aes_nonce, FDE_NONCE_AES_SIZE,
    443                            NULL, 0,
    444                            master_key, FDE_MASTER_KEY_SIZE,
    445                            header.encrypted_master_key,
    446                            header.encrypted_master_key + FDE_MASTER_KEY_SIZE,
    447                            FDE_TAG_SIZE);
    448         aes256_gcm_cleanup(&aes_ctx);
    449     }
    450 
    451     if (cipher == FDE_CIPHER_CASCADE || cipher == FDE_CIPHER_XCHACHA) {
    452         uint8_t xchacha_nonce[FDE_NONCE_XCHACHA_SIZE];
    453         memset(xchacha_nonce, 0xFF, FDE_NONCE_XCHACHA_SIZE);
    454 
    455         uint8_t *data_to_encrypt = (cipher == FDE_CIPHER_CASCADE) ?
    456             header.encrypted_master_key : master_key;
    457         size_t data_len = (cipher == FDE_CIPHER_CASCADE) ?
    458             FDE_MASTER_KEY_SIZE + FDE_TAG_SIZE : FDE_MASTER_KEY_SIZE;
    459 
    460         xchacha20_poly1305_context xchacha_ctx;
    461         xchacha20_poly1305_init(&xchacha_ctx, xchacha_key, xchacha_nonce);
    462         xchacha20_poly1305_encrypt(&xchacha_ctx, NULL, 0,
    463                                    data_to_encrypt, data_len,
    464                                    header.encrypted_master_key,
    465                                    header.encrypted_master_key + FDE_MASTER_KEY_SIZE + FDE_TAG_SIZE);
    466         xchacha20_poly1305_cleanup(&xchacha_ctx);
    467     }
    468 
    469     /* Hash header (excluding hash field itself) */
    470     sha256((uint8_t *)&header, sizeof(header) - 32, header.header_hash);
    471 
    472     /* Write header to disk - at partition start */
    473     result = ata_write_sectors(bus, drive, part_start + FDE_HEADER_SECTOR, 1, &header);
    474     if (result != ATA_OK) {
    475         secure_wipe(aes_key, sizeof(aes_key));
    476         secure_wipe(xchacha_key, sizeof(xchacha_key));
    477         secure_wipe(master_key, sizeof(master_key));
    478         return FDE_ERR_IO;
    479     }
    480 
    481     /* Initialize tag area with zeros */
    482     uint8_t zero_sector[FDE_SECTOR_SIZE];
    483     memset(zero_sector, 0, sizeof(zero_sector));
    484     for (uint32_t i = 0; i < tag_sectors; i++) {
    485         result = ata_write_sectors(bus, drive, part_start + tag_start + i, 1, zero_sector);
    486         if (result != ATA_OK) {
    487             secure_wipe(aes_key, sizeof(aes_key));
    488             secure_wipe(xchacha_key, sizeof(xchacha_key));
    489             secure_wipe(master_key, sizeof(master_key));
    490             return FDE_ERR_IO;
    491         }
    492     }
    493 
    494     secure_wipe(aes_key, sizeof(aes_key));
    495     secure_wipe(xchacha_key, sizeof(xchacha_key));
    496     secure_wipe(master_key, sizeof(master_key));
    497 
    498     terminal_writestring("[FDE] Formatted successfully\n");
    499     return FDE_OK;
    500 }
    501 
    502 /* Format a disk with FDE (whole disk) */
    503 int fde_format(uint8_t bus, uint8_t drive,
    504                const char *password, size_t pass_len,
    505                uint8_t cipher) {
    506     /* Get drive info */
    507     ata_drive_info_t *info = ata_get_drive_info(bus, drive);
    508     if (!info) {
    509         return FDE_ERR_IO;
    510     }
    511 
    512     return fde_format_internal(bus, drive, 0, info->sectors, password, pass_len, cipher);
    513 }
    514 
    515 /* Format a partition with FDE */
    516 int fde_format_partition(uint8_t bus, uint8_t drive,
    517                          uint32_t part_start, uint32_t part_size,
    518                          const char *password, size_t pass_len,
    519                          uint8_t cipher) {
    520     if (part_size < 100) {  /* Need at least some minimum sectors */
    521         return FDE_ERR_INVALID_PARAM;
    522     }
    523 
    524     return fde_format_internal(bus, drive, part_start, part_size, password, pass_len, cipher);
    525 }
    526 
    527 /* Internal open implementation - works on partition or whole disk */
    528 static int fde_open_internal(fde_context_t *ctx, uint8_t bus, uint8_t drive,
    529                              uint32_t part_start, uint32_t part_size,
    530                              const char *password, size_t pass_len) {
    531     if (!ctx || !password || pass_len == 0) {
    532         return FDE_ERR_INVALID_PARAM;
    533     }
    534 
    535     /* Read header from partition start */
    536     fde_header_t header;
    537     int result = ata_read_sectors(bus, drive, part_start + FDE_HEADER_SECTOR, 1, &header);
    538     if (result != ATA_OK) {
    539         return FDE_ERR_IO;
    540     }
    541 
    542     /* Verify magic */
    543     if (memcmp(header.magic, FDE_MAGIC, FDE_MAGIC_SIZE) != 0) {
    544         return FDE_ERR_NOT_FORMATTED;
    545     }
    546 
    547     /* Verify header hash */
    548     uint8_t computed_hash[32];
    549     sha256((uint8_t *)&header, sizeof(header) - 32, computed_hash);
    550     if (secure_compare(computed_hash, header.header_hash, 32) != 0) {
    551         return FDE_ERR_CORRUPT;
    552     }
    553 
    554     /* Derive keys from password */
    555     uint8_t aes_key[FDE_KEY_SIZE];
    556     uint8_t xchacha_key[FDE_KEY_SIZE];
    557     result = derive_keys(password, pass_len, header.salt, aes_key, xchacha_key);
    558     if (result != FDE_OK) {
    559         return result;
    560     }
    561 
    562     /* Decrypt master key to verify password */
    563     uint8_t decrypted_key[FDE_MASTER_KEY_SIZE];
    564     uint8_t cipher = header.cipher_mode;
    565 
    566     if (cipher == FDE_CIPHER_CASCADE) {
    567         /* First decrypt with XChaCha (outer layer) */
    568         uint8_t xchacha_nonce[FDE_NONCE_XCHACHA_SIZE];
    569         memset(xchacha_nonce, 0xFF, FDE_NONCE_XCHACHA_SIZE);
    570 
    571         uint8_t intermediate[FDE_MASTER_KEY_SIZE + FDE_TAG_SIZE];
    572         xchacha20_poly1305_context xchacha_ctx;
    573         xchacha20_poly1305_init(&xchacha_ctx, xchacha_key, xchacha_nonce);
    574         if (xchacha20_poly1305_decrypt(&xchacha_ctx, NULL, 0,
    575                                        header.encrypted_master_key,
    576                                        FDE_MASTER_KEY_SIZE + FDE_TAG_SIZE,
    577                                        header.encrypted_master_key + FDE_MASTER_KEY_SIZE + FDE_TAG_SIZE,
    578                                        intermediate) != 0) {
    579             xchacha20_poly1305_cleanup(&xchacha_ctx);
    580             secure_wipe(aes_key, sizeof(aes_key));
    581             secure_wipe(xchacha_key, sizeof(xchacha_key));
    582             return FDE_ERR_BAD_PASSWORD;
    583         }
    584         xchacha20_poly1305_cleanup(&xchacha_ctx);
    585 
    586         /* Then decrypt with AES (inner layer) */
    587         uint8_t aes_nonce[FDE_NONCE_AES_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF,
    588                                                   0xFF, 0xFF, 0xFF, 0xFF,
    589                                                   0xFF, 0xFF, 0xFF, 0xFF};
    590         aes256_gcm_context aes_ctx;
    591         aes256_gcm_init(&aes_ctx, aes_key);
    592         if (aes256_gcm_decrypt(&aes_ctx, aes_nonce, FDE_NONCE_AES_SIZE,
    593                                NULL, 0,
    594                                intermediate, FDE_MASTER_KEY_SIZE,
    595                                intermediate + FDE_MASTER_KEY_SIZE, FDE_TAG_SIZE,
    596                                decrypted_key) != 0) {
    597             aes256_gcm_cleanup(&aes_ctx);
    598             secure_wipe(aes_key, sizeof(aes_key));
    599             secure_wipe(xchacha_key, sizeof(xchacha_key));
    600             secure_wipe(intermediate, sizeof(intermediate));
    601             return FDE_ERR_BAD_PASSWORD;
    602         }
    603         aes256_gcm_cleanup(&aes_ctx);
    604         secure_wipe(intermediate, sizeof(intermediate));
    605 
    606     } else if (cipher == FDE_CIPHER_XCHACHA) {
    607         uint8_t xchacha_nonce[FDE_NONCE_XCHACHA_SIZE];
    608         memset(xchacha_nonce, 0xFF, FDE_NONCE_XCHACHA_SIZE);
    609 
    610         xchacha20_poly1305_context xchacha_ctx;
    611         xchacha20_poly1305_init(&xchacha_ctx, xchacha_key, xchacha_nonce);
    612         if (xchacha20_poly1305_decrypt(&xchacha_ctx, NULL, 0,
    613                                        header.encrypted_master_key,
    614                                        FDE_MASTER_KEY_SIZE,
    615                                        header.encrypted_master_key + FDE_MASTER_KEY_SIZE + FDE_TAG_SIZE,
    616                                        decrypted_key) != 0) {
    617             xchacha20_poly1305_cleanup(&xchacha_ctx);
    618             secure_wipe(aes_key, sizeof(aes_key));
    619             secure_wipe(xchacha_key, sizeof(xchacha_key));
    620             return FDE_ERR_BAD_PASSWORD;
    621         }
    622         xchacha20_poly1305_cleanup(&xchacha_ctx);
    623 
    624     } else if (cipher == FDE_CIPHER_AES_GCM) {
    625         uint8_t aes_nonce[FDE_NONCE_AES_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF,
    626                                                   0xFF, 0xFF, 0xFF, 0xFF,
    627                                                   0xFF, 0xFF, 0xFF, 0xFF};
    628         aes256_gcm_context aes_ctx;
    629         aes256_gcm_init(&aes_ctx, aes_key);
    630         if (aes256_gcm_decrypt(&aes_ctx, aes_nonce, FDE_NONCE_AES_SIZE,
    631                                NULL, 0,
    632                                header.encrypted_master_key, FDE_MASTER_KEY_SIZE,
    633                                header.encrypted_master_key + FDE_MASTER_KEY_SIZE, FDE_TAG_SIZE,
    634                                decrypted_key) != 0) {
    635             aes256_gcm_cleanup(&aes_ctx);
    636             secure_wipe(aes_key, sizeof(aes_key));
    637             secure_wipe(xchacha_key, sizeof(xchacha_key));
    638             return FDE_ERR_BAD_PASSWORD;
    639         }
    640         aes256_gcm_cleanup(&aes_ctx);
    641     } else {
    642         /* No encryption */
    643         memcpy(decrypted_key, header.encrypted_master_key, FDE_MASTER_KEY_SIZE);
    644     }
    645 
    646     /* Initialize context */
    647     memset(ctx, 0, sizeof(fde_context_t));
    648     ctx->bus = bus;
    649     ctx->drive = drive;
    650     ctx->is_open = 1;
    651     ctx->cipher_mode = cipher;
    652     ctx->partition_start = part_start;
    653     ctx->partition_size = part_size;
    654     ctx->total_sectors = header.total_sectors;
    655     ctx->data_start_sector = header.data_start_sector;
    656     ctx->tag_start_sector = header.tag_start_sector;
    657 
    658     {
    659         char dbg[150];
    660         snprintf(dbg, sizeof(dbg), "[FDE] Open: part_start=%d total=%d data_start=%d tag_start=%d cipher=%d\n",
    661                  (int)ctx->partition_start, (int)ctx->total_sectors, (int)ctx->data_start_sector,
    662                  (int)ctx->tag_start_sector, (int)ctx->cipher_mode);
    663         terminal_writestring(dbg);
    664     }
    665 
    666     /* Store actual encryption keys (from decrypted master key) */
    667     memcpy(ctx->aes_key, decrypted_key, FDE_KEY_SIZE);
    668     memcpy(ctx->xchacha_key, decrypted_key + FDE_KEY_SIZE, FDE_KEY_SIZE);
    669 
    670     secure_wipe(aes_key, sizeof(aes_key));
    671     secure_wipe(xchacha_key, sizeof(xchacha_key));
    672     secure_wipe(decrypted_key, sizeof(decrypted_key));
    673 
    674     return FDE_OK;
    675 }
    676 
    677 /* Open an encrypted disk (whole disk) */
    678 int fde_open(fde_context_t *ctx, uint8_t bus, uint8_t drive,
    679              const char *password, size_t pass_len) {
    680     /* Get drive info */
    681     ata_drive_info_t *info = ata_get_drive_info(bus, drive);
    682     if (!info) {
    683         return FDE_ERR_IO;
    684     }
    685 
    686     return fde_open_internal(ctx, bus, drive, 0, info->sectors, password, pass_len);
    687 }
    688 
    689 /* Open an encrypted partition */
    690 int fde_open_partition(fde_context_t *ctx, uint8_t bus, uint8_t drive,
    691                        uint32_t part_start,
    692                        const char *password, size_t pass_len) {
    693     /* Read header to get partition size from stored values */
    694     fde_header_t header;
    695     int result = ata_read_sectors(bus, drive, part_start + FDE_HEADER_SECTOR, 1, &header);
    696     if (result != ATA_OK) {
    697         return FDE_ERR_IO;
    698     }
    699 
    700     /* Verify magic before proceeding */
    701     if (memcmp(header.magic, FDE_MAGIC, FDE_MAGIC_SIZE) != 0) {
    702         return FDE_ERR_NOT_FORMATTED;
    703     }
    704 
    705     /* Calculate partition size from header data */
    706     uint32_t tag_sectors = calc_tag_sectors(header.total_sectors);
    707     uint32_t part_size = 1 + tag_sectors + header.total_sectors;
    708 
    709     return fde_open_internal(ctx, bus, drive, part_start, part_size, password, pass_len);
    710 }
    711 
    712 /* Close an encrypted disk */
    713 int fde_close(fde_context_t *ctx) {
    714     if (!ctx) {
    715         return FDE_ERR_INVALID_PARAM;
    716     }
    717 
    718     /* Wipe keys */
    719     secure_wipe(ctx->aes_key, sizeof(ctx->aes_key));
    720     secure_wipe(ctx->xchacha_key, sizeof(ctx->xchacha_key));
    721     ctx->is_open = 0;
    722 
    723     return FDE_OK;
    724 }
    725 
    726 /* Read a decrypted sector */
    727 int fde_read_sector(fde_context_t *ctx, uint32_t sector, void *buffer) {
    728     if (!ctx || !buffer) {
    729         return FDE_ERR_INVALID_PARAM;
    730     }
    731     if (!ctx->is_open) {
    732         return FDE_ERR_NOT_OPEN;
    733     }
    734     if (sector >= ctx->total_sectors) {
    735         return FDE_ERR_INVALID_PARAM;
    736     }
    737 
    738     /* Read ciphertext from disk - add partition_start offset */
    739     uint8_t ciphertext[FDE_SECTOR_SIZE];
    740     uint32_t disk_sector = ctx->partition_start + ctx->data_start_sector + sector;
    741     int result = ata_read_sectors(ctx->bus, ctx->drive, disk_sector, 1, ciphertext);
    742     if (result != ATA_OK) {
    743         return FDE_ERR_IO;
    744     }
    745 
    746     /* No encryption mode */
    747     if (ctx->cipher_mode == FDE_CIPHER_NONE) {
    748         memcpy(buffer, ciphertext, FDE_SECTOR_SIZE);
    749         return FDE_OK;
    750     }
    751 
    752     /* Read tags - pass partition_start */
    753     fde_sector_tags_t tags;
    754     result = read_sector_tags(ctx->bus, ctx->drive, ctx->partition_start,
    755                               ctx->tag_start_sector, sector, &tags);
    756     if (result != FDE_OK) {
    757         return result;
    758     }
    759 
    760     /* Decrypt based on cipher mode */
    761     switch (ctx->cipher_mode) {
    762         case FDE_CIPHER_CASCADE:
    763             return decrypt_sector_cascade(ctx->aes_key, ctx->xchacha_key,
    764                                           sector, ciphertext, &tags, buffer);
    765         case FDE_CIPHER_AES_GCM:
    766             return decrypt_sector_aes(ctx->aes_key, sector, ciphertext, &tags, buffer);
    767         case FDE_CIPHER_XCHACHA:
    768             return decrypt_sector_xchacha(ctx->xchacha_key, sector, ciphertext, &tags, buffer);
    769         default:
    770             return FDE_ERR_INVALID_PARAM;
    771     }
    772 }
    773 
    774 /* Write and encrypt a sector */
    775 int fde_write_sector(fde_context_t *ctx, uint32_t sector, const void *buffer) {
    776     if (!ctx || !buffer) {
    777         terminal_writestring("[FDE] write_sector: invalid param\n");
    778         return FDE_ERR_INVALID_PARAM;
    779     }
    780     if (!ctx->is_open) {
    781         terminal_writestring("[FDE] write_sector: not open\n");
    782         return FDE_ERR_NOT_OPEN;
    783     }
    784     if (sector >= ctx->total_sectors) {
    785         terminal_writestring("[FDE] write_sector: sector out of range\n");
    786         return FDE_ERR_INVALID_PARAM;
    787     }
    788 
    789     uint8_t ciphertext[FDE_SECTOR_SIZE];
    790     fde_sector_tags_t tags;
    791     int result;
    792 
    793     /* No encryption mode */
    794     if (ctx->cipher_mode == FDE_CIPHER_NONE) {
    795         memcpy(ciphertext, buffer, FDE_SECTOR_SIZE);
    796         memset(&tags, 0, sizeof(tags));
    797     } else {
    798         /* Encrypt based on cipher mode */
    799         switch (ctx->cipher_mode) {
    800             case FDE_CIPHER_CASCADE:
    801                 result = encrypt_sector_cascade(ctx->aes_key, ctx->xchacha_key,
    802                                                 sector, buffer, ciphertext, &tags);
    803                 break;
    804             case FDE_CIPHER_AES_GCM:
    805                 result = encrypt_sector_aes(ctx->aes_key, sector, buffer, ciphertext, &tags);
    806                 break;
    807             case FDE_CIPHER_XCHACHA:
    808                 result = encrypt_sector_xchacha(ctx->xchacha_key, sector, buffer, ciphertext, &tags);
    809                 break;
    810             default:
    811                 return FDE_ERR_INVALID_PARAM;
    812         }
    813         if (result != FDE_OK) {
    814             terminal_writestring("[FDE] write_sector: encrypt failed\n");
    815             return result;
    816         }
    817     }
    818 
    819     /* Write tags - pass partition_start */
    820     if (ctx->cipher_mode != FDE_CIPHER_NONE) {
    821         result = write_sector_tags(ctx->bus, ctx->drive, ctx->partition_start,
    822                                    ctx->tag_start_sector, sector, &tags);
    823         if (result != FDE_OK) {
    824             terminal_writestring("[FDE] write_sector: write_sector_tags failed\n");
    825             return result;
    826         }
    827     }
    828 
    829     /* Write ciphertext to disk - add partition_start offset */
    830     uint32_t disk_sector = ctx->partition_start + ctx->data_start_sector + sector;
    831     result = ata_write_sectors(ctx->bus, ctx->drive, disk_sector, 1, ciphertext);
    832     if (result != ATA_OK) {
    833         char dbg[100];
    834         snprintf(dbg, sizeof(dbg), "[FDE] write_sector: ata_write failed, sector=%d disk_sector=%d result=%d\n",
    835                  (int)sector, (int)disk_sector, result);
    836         terminal_writestring(dbg);
    837         return FDE_ERR_IO;
    838     }
    839 
    840     return FDE_OK;
    841 }
    842 
    843 /* Read multiple sectors */
    844 int fde_read_sectors(fde_context_t *ctx, uint32_t start, uint32_t count, void *buffer) {
    845     uint8_t *buf = (uint8_t *)buffer;
    846     for (uint32_t i = 0; i < count; i++) {
    847         int result = fde_read_sector(ctx, start + i, buf + i * FDE_SECTOR_SIZE);
    848         if (result != FDE_OK) {
    849             return result;
    850         }
    851     }
    852     return FDE_OK;
    853 }
    854 
    855 /* Write multiple sectors */
    856 int fde_write_sectors(fde_context_t *ctx, uint32_t start, uint32_t count, const void *buffer) {
    857     const uint8_t *buf = (const uint8_t *)buffer;
    858     for (uint32_t i = 0; i < count; i++) {
    859         int result = fde_write_sector(ctx, start + i, buf + i * FDE_SECTOR_SIZE);
    860         if (result != FDE_OK) {
    861             return result;
    862         }
    863     }
    864     return FDE_OK;
    865 }
    866 
    867 /* Check if disk is FDE formatted */
    868 int fde_is_formatted(uint8_t bus, uint8_t drive) {
    869     fde_header_t header;
    870     int result = ata_read_sectors(bus, drive, FDE_HEADER_SECTOR, 1, &header);
    871     if (result != ATA_OK) {
    872         return 0;
    873     }
    874     return memcmp(header.magic, FDE_MAGIC, FDE_MAGIC_SIZE) == 0;
    875 }
    876 
    877 /* Get disk info */
    878 int fde_get_info(uint8_t bus, uint8_t drive, uint8_t *cipher, uint32_t *sectors) {
    879     fde_header_t header;
    880     int result = ata_read_sectors(bus, drive, FDE_HEADER_SECTOR, 1, &header);
    881     if (result != ATA_OK) {
    882         return FDE_ERR_IO;
    883     }
    884 
    885     if (memcmp(header.magic, FDE_MAGIC, FDE_MAGIC_SIZE) != 0) {
    886         return FDE_ERR_NOT_FORMATTED;
    887     }
    888 
    889     if (cipher) *cipher = header.cipher_mode;
    890     if (sectors) *sectors = header.total_sectors;
    891 
    892     return FDE_OK;
    893 }
    894 
    895 /* Get error string */
    896 const char *fde_error_string(int error) {
    897     switch (error) {
    898         case FDE_OK:              return "Success";
    899         case FDE_ERR_INVALID_PARAM: return "Invalid parameter";
    900         case FDE_ERR_NO_MEMORY:   return "Out of memory";
    901         case FDE_ERR_IO:          return "I/O error";
    902         case FDE_ERR_CRYPTO:      return "Cryptographic error";
    903         case FDE_ERR_BAD_PASSWORD: return "Invalid password";
    904         case FDE_ERR_CORRUPT:     return "Data corruption detected";
    905         case FDE_ERR_NOT_FORMATTED: return "Disk not FDE formatted";
    906         case FDE_ERR_ALREADY_OPEN: return "Disk already open";
    907         case FDE_ERR_NOT_OPEN:    return "Disk not open";
    908         case FDE_ERR_AUTH_FAILED: return "Authentication failed";
    909         default:                  return "Unknown error";
    910     }
    911 }
    912 
    913 /* Get cipher name */
    914 const char *fde_cipher_name(uint8_t cipher) {
    915     switch (cipher) {
    916         case FDE_CIPHER_NONE:     return "None (plaintext)";
    917         case FDE_CIPHER_AES_GCM:  return "AES-256-GCM";
    918         case FDE_CIPHER_XCHACHA:  return "XChaCha20-Poly1305";
    919         case FDE_CIPHER_CASCADE:  return "AES-256-GCM + XChaCha20-Poly1305";
    920         default:                  return "Unknown";
    921     }
    922 }
    923 
    924 /* ============================================================================
    925  * Lua Bindings
    926  * ========================================================================= */
    927 
    928 #include "lua.h"
    929 #include "lauxlib.h"
    930 
    931 /* Global FDE contexts (max 4 disks) - non-static so diskfs can access */
    932 fde_context_t fde_contexts[4];
    933 
    934 static int get_ctx_index(uint8_t bus, uint8_t drive) {
    935     return bus * 2 + drive;
    936 }
    937 
    938 /* fde.format(bus, drive, password, cipher) -> ok, err */
    939 static int lua_fde_format(lua_State *L) {
    940     int bus = luaL_checkinteger(L, 1);
    941     int drive = luaL_checkinteger(L, 2);
    942     size_t pass_len;
    943     const char *password = luaL_checklstring(L, 3, &pass_len);
    944     int cipher = luaL_optinteger(L, 4, FDE_CIPHER_CASCADE);
    945 
    946     int result = fde_format(bus, drive, password, pass_len, cipher);
    947 
    948     if (result != FDE_OK) {
    949         lua_pushboolean(L, 0);
    950         lua_pushstring(L, fde_error_string(result));
    951         return 2;
    952     }
    953 
    954     lua_pushboolean(L, 1);
    955     lua_pushnil(L);
    956     return 2;
    957 }
    958 
    959 /* fde.open(bus, drive, password) -> ok, err */
    960 static int lua_fde_open(lua_State *L) {
    961     int bus = luaL_checkinteger(L, 1);
    962     int drive = luaL_checkinteger(L, 2);
    963     size_t pass_len;
    964     const char *password = luaL_checklstring(L, 3, &pass_len);
    965 
    966     int idx = get_ctx_index(bus, drive);
    967     if (idx < 0 || idx >= 4) {
    968         lua_pushboolean(L, 0);
    969         lua_pushstring(L, "Invalid bus/drive");
    970         return 2;
    971     }
    972 
    973     if (fde_contexts[idx].is_open) {
    974         lua_pushboolean(L, 0);
    975         lua_pushstring(L, "Disk already open");
    976         return 2;
    977     }
    978 
    979     int result = fde_open(&fde_contexts[idx], bus, drive, password, pass_len);
    980 
    981     if (result != FDE_OK) {
    982         lua_pushboolean(L, 0);
    983         lua_pushstring(L, fde_error_string(result));
    984         return 2;
    985     }
    986 
    987     lua_pushboolean(L, 1);
    988     lua_pushnil(L);
    989     return 2;
    990 }
    991 
    992 /* fde.close(bus, drive) -> ok */
    993 static int lua_fde_close(lua_State *L) {
    994     int bus = luaL_checkinteger(L, 1);
    995     int drive = luaL_checkinteger(L, 2);
    996 
    997     int idx = get_ctx_index(bus, drive);
    998     if (idx < 0 || idx >= 4) {
    999         lua_pushboolean(L, 0);
   1000         return 1;
   1001     }
   1002 
   1003     fde_close(&fde_contexts[idx]);
   1004     lua_pushboolean(L, 1);
   1005     return 1;
   1006 }
   1007 
   1008 /* fde.read(bus, drive, sector, count) -> data, err */
   1009 static int lua_fde_read(lua_State *L) {
   1010     int bus = luaL_checkinteger(L, 1);
   1011     int drive = luaL_checkinteger(L, 2);
   1012     uint32_t sector = luaL_checkinteger(L, 3);
   1013     uint32_t count = luaL_optinteger(L, 4, 1);
   1014 
   1015     int idx = get_ctx_index(bus, drive);
   1016     if (idx < 0 || idx >= 4 || !fde_contexts[idx].is_open) {
   1017         lua_pushnil(L);
   1018         lua_pushstring(L, "Disk not open");
   1019         return 2;
   1020     }
   1021 
   1022     size_t buf_size = count * FDE_SECTOR_SIZE;
   1023     char *buffer = malloc(buf_size);
   1024     if (!buffer) {
   1025         lua_pushnil(L);
   1026         lua_pushstring(L, "Out of memory");
   1027         return 2;
   1028     }
   1029 
   1030     int result = fde_read_sectors(&fde_contexts[idx], sector, count, buffer);
   1031 
   1032     if (result != FDE_OK) {
   1033         free(buffer);
   1034         lua_pushnil(L);
   1035         lua_pushstring(L, fde_error_string(result));
   1036         return 2;
   1037     }
   1038 
   1039     lua_pushlstring(L, buffer, buf_size);
   1040     free(buffer);
   1041     lua_pushnil(L);
   1042     return 2;
   1043 }
   1044 
   1045 /* fde.write(bus, drive, sector, data) -> ok, err */
   1046 static int lua_fde_write(lua_State *L) {
   1047     int bus = luaL_checkinteger(L, 1);
   1048     int drive = luaL_checkinteger(L, 2);
   1049     uint32_t sector = luaL_checkinteger(L, 3);
   1050     size_t data_len;
   1051     const char *data = luaL_checklstring(L, 4, &data_len);
   1052 
   1053     int idx = get_ctx_index(bus, drive);
   1054     if (idx < 0 || idx >= 4 || !fde_contexts[idx].is_open) {
   1055         lua_pushboolean(L, 0);
   1056         lua_pushstring(L, "Disk not open");
   1057         return 2;
   1058     }
   1059 
   1060     if (data_len == 0 || data_len % FDE_SECTOR_SIZE != 0) {
   1061         lua_pushboolean(L, 0);
   1062         lua_pushstring(L, "Data must be multiple of 512 bytes");
   1063         return 2;
   1064     }
   1065 
   1066     uint32_t count = data_len / FDE_SECTOR_SIZE;
   1067     int result = fde_write_sectors(&fde_contexts[idx], sector, count, data);
   1068 
   1069     if (result != FDE_OK) {
   1070         lua_pushboolean(L, 0);
   1071         lua_pushstring(L, fde_error_string(result));
   1072         return 2;
   1073     }
   1074 
   1075     lua_pushboolean(L, 1);
   1076     lua_pushnil(L);
   1077     return 2;
   1078 }
   1079 
   1080 /* fde.isFormatted(bus, drive) -> bool */
   1081 static int lua_fde_is_formatted(lua_State *L) {
   1082     int bus = luaL_checkinteger(L, 1);
   1083     int drive = luaL_checkinteger(L, 2);
   1084 
   1085     lua_pushboolean(L, fde_is_formatted(bus, drive));
   1086     return 1;
   1087 }
   1088 
   1089 /* fde.getInfo(bus, drive) -> info or nil, err */
   1090 static int lua_fde_get_info(lua_State *L) {
   1091     int bus = luaL_checkinteger(L, 1);
   1092     int drive = luaL_checkinteger(L, 2);
   1093 
   1094     uint8_t cipher;
   1095     uint32_t sectors;
   1096     int result = fde_get_info(bus, drive, &cipher, &sectors);
   1097 
   1098     if (result != FDE_OK) {
   1099         lua_pushnil(L);
   1100         lua_pushstring(L, fde_error_string(result));
   1101         return 2;
   1102     }
   1103 
   1104     lua_newtable(L);
   1105 
   1106     lua_pushstring(L, "cipher");
   1107     lua_pushinteger(L, cipher);
   1108     lua_settable(L, -3);
   1109 
   1110     lua_pushstring(L, "cipherName");
   1111     lua_pushstring(L, fde_cipher_name(cipher));
   1112     lua_settable(L, -3);
   1113 
   1114     lua_pushstring(L, "sectors");
   1115     lua_pushinteger(L, sectors);
   1116     lua_settable(L, -3);
   1117 
   1118     lua_pushstring(L, "bytes");
   1119     lua_pushnumber(L, (lua_Number)sectors * FDE_SECTOR_SIZE);
   1120     lua_settable(L, -3);
   1121 
   1122     return 1;
   1123 }
   1124 
   1125 /* fde.formatPartition(bus, drive, partStart, partSize, password, cipher) -> ok, err */
   1126 static int lua_fde_format_partition(lua_State *L) {
   1127     int bus = luaL_checkinteger(L, 1);
   1128     int drive = luaL_checkinteger(L, 2);
   1129     uint32_t part_start = luaL_checkinteger(L, 3);
   1130     uint32_t part_size = luaL_checkinteger(L, 4);
   1131     size_t pass_len;
   1132     const char *password = luaL_checklstring(L, 5, &pass_len);
   1133     int cipher = luaL_optinteger(L, 6, FDE_CIPHER_CASCADE);
   1134 
   1135     int result = fde_format_partition(bus, drive, part_start, part_size,
   1136                                       password, pass_len, cipher);
   1137 
   1138     if (result != FDE_OK) {
   1139         lua_pushboolean(L, 0);
   1140         lua_pushstring(L, fde_error_string(result));
   1141         return 2;
   1142     }
   1143 
   1144     lua_pushboolean(L, 1);
   1145     lua_pushnil(L);
   1146     return 2;
   1147 }
   1148 
   1149 /* fde.openPartition(bus, drive, partStart, password) -> ok, err */
   1150 static int lua_fde_open_partition(lua_State *L) {
   1151     int bus = luaL_checkinteger(L, 1);
   1152     int drive = luaL_checkinteger(L, 2);
   1153     uint32_t part_start = luaL_checkinteger(L, 3);
   1154     size_t pass_len;
   1155     const char *password = luaL_checklstring(L, 4, &pass_len);
   1156 
   1157     int idx = get_ctx_index(bus, drive);
   1158     if (idx < 0 || idx >= 4) {
   1159         lua_pushboolean(L, 0);
   1160         lua_pushstring(L, "Invalid bus/drive");
   1161         return 2;
   1162     }
   1163 
   1164     if (fde_contexts[idx].is_open) {
   1165         lua_pushboolean(L, 0);
   1166         lua_pushstring(L, "Disk already open");
   1167         return 2;
   1168     }
   1169 
   1170     int result = fde_open_partition(&fde_contexts[idx], bus, drive, part_start,
   1171                                     password, pass_len);
   1172 
   1173     if (result != FDE_OK) {
   1174         lua_pushboolean(L, 0);
   1175         lua_pushstring(L, fde_error_string(result));
   1176         return 2;
   1177     }
   1178 
   1179     lua_pushboolean(L, 1);
   1180     lua_pushnil(L);
   1181     return 2;
   1182 }
   1183 
   1184 /* Register FDE module */
   1185 static const luaL_Reg fde_funcs[] = {
   1186     {"format", lua_fde_format},
   1187     {"formatPartition", lua_fde_format_partition},
   1188     {"open", lua_fde_open},
   1189     {"openPartition", lua_fde_open_partition},
   1190     {"close", lua_fde_close},
   1191     {"read", lua_fde_read},
   1192     {"write", lua_fde_write},
   1193     {"isFormatted", lua_fde_is_formatted},
   1194     {"getInfo", lua_fde_get_info},
   1195     {NULL, NULL}
   1196 };
   1197 
   1198 int luaopen_fde(lua_State *L) {
   1199     /* Initialize contexts */
   1200     memset(fde_contexts, 0, sizeof(fde_contexts));
   1201 
   1202     luaL_newlib(L, fde_funcs);
   1203 
   1204     /* Add constants */
   1205     lua_pushinteger(L, FDE_CIPHER_NONE);
   1206     lua_setfield(L, -2, "CIPHER_NONE");
   1207 
   1208     lua_pushinteger(L, FDE_CIPHER_AES_GCM);
   1209     lua_setfield(L, -2, "CIPHER_AES");
   1210 
   1211     lua_pushinteger(L, FDE_CIPHER_XCHACHA);
   1212     lua_setfield(L, -2, "CIPHER_XCHACHA");
   1213 
   1214     lua_pushinteger(L, FDE_CIPHER_CASCADE);
   1215     lua_setfield(L, -2, "CIPHER_CASCADE");
   1216 
   1217     lua_pushinteger(L, FDE_SECTOR_SIZE);
   1218     lua_setfield(L, -2, "SECTOR_SIZE");
   1219 
   1220     return 1;
   1221 }