|
| 1 | +// SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// Lithoglyph Cryptography Module |
| 3 | +// |
| 4 | +// Provides AES-256-GCM encryption for block payloads |
| 5 | +// Designed to integrate with Svalinn Vault's existing crypto system |
| 6 | + |
| 7 | +const std = @import("std"); |
| 8 | +const builtin = @import("builtin"); |
| 9 | +const blocks = @import("blocks.zig"); |
| 10 | + |
| 11 | +// ============================================================ |
| 12 | +// Constants |
| 13 | +// ============================================================ |
| 14 | + |
| 15 | +pub const AES256_KEY_SIZE: usize = 32; // 256 bits |
| 16 | +pub const AES_GCM_NONCE_SIZE: usize = 12; // 96 bits for GCM |
| 17 | +pub const AES_GCM_TAG_SIZE: usize = 16; // 128 bits authentication tag |
| 18 | + |
| 19 | +// ============================================================ |
| 20 | +// Error Types |
| 21 | +// ============================================================ |
| 22 | + |
| 23 | +pub const CryptoError = error{ |
| 24 | + InvalidKeySize, |
| 25 | + EncryptionFailed, |
| 26 | + DecryptionFailed, |
| 27 | + AuthenticationFailed, |
| 28 | + BufferTooSmall, |
| 29 | +}; |
| 30 | + |
| 31 | +// ============================================================ |
| 32 | +// AES-256-GCM Implementation |
| 33 | +// ============================================================ |
| 34 | + |
| 35 | +// Encrypt block payload using AES-256-GCM |
| 36 | +// Uses block_id as part of nonce for uniqueness |
| 37 | +pub fn encryptBlockPayload( |
| 38 | + block: *blocks.Block, |
| 39 | + key: [AES256_KEY_SIZE]u8, |
| 40 | +) !void { |
| 41 | + if (block.header.encrypted) { |
| 42 | + return; // Already encrypted |
| 43 | + } |
| 44 | + |
| 45 | + // Create nonce: block_id (8 bytes) + counter (4 bytes) |
| 46 | + var nonce = [AES_GCM_NONCE_SIZE]u8 = undefined; |
| 47 | + @memcpy(&nonce[0..8], &block.header.block_id, 8); |
| 48 | + @memcpy(&nonce[8..12], &block.header.sequence, 4); |
| 49 | + // Last 4 bytes zero for now |
| 50 | + |
| 51 | + // Encrypt payload in-place |
| 52 | + const payload_len = block.header.payload_len; |
| 53 | + if (payload_len == 0) { |
| 54 | + return; // Nothing to encrypt |
| 55 | + } |
| 56 | + |
| 57 | + // Make space for authentication tag at end |
| 58 | + if (payload_len + AES_GCM_TAG_SIZE > blocks.PAYLOAD_SIZE) { |
| 59 | + return CryptoError.BufferTooSmall; |
| 60 | + } |
| 61 | + |
| 62 | + // Encrypt using AES-GCM |
| 63 | + // Note: In real implementation, we'd use a proper crypto library |
| 64 | + // This is a placeholder showing the structure |
| 65 | + try aes256GcmEncryptInPlace( |
| 66 | + &block.payload[0..payload_len], |
| 67 | + &key, |
| 68 | + &nonce, |
| 69 | + &block.payload[payload_len..payload_len + AES_GCM_TAG_SIZE] |
| 70 | + ); |
| 71 | + |
| 72 | + // Update header |
| 73 | + block.header.payload_len = @intCast(payload_len + AES_GCM_TAG_SIZE); |
| 74 | + block.header.encrypted = true; |
| 75 | + block.header.flags |= @as(u32, @bitCast(@intFromEnum(blocks.BlockFlags.encrypted))); |
| 76 | + |
| 77 | + // Recalculate checksum of encrypted data |
| 78 | + block.header.checksum = blocks.crc32c(&block.payload, block.header.payload_len); |
| 79 | +} |
| 80 | + |
| 81 | +// Decrypt block payload using AES-256-GCM |
| 82 | +pub fn decryptBlockPayload( |
| 83 | + block: *blocks.Block, |
| 84 | + key: [AES256_KEY_SIZE]u8, |
| 85 | +) !void { |
| 86 | + if (!block.header.encrypted) { |
| 87 | + return; // Not encrypted |
| 88 | + } |
| 89 | + |
| 90 | + const total_len = block.header.payload_len; |
| 91 | + if (total_len < AES_GCM_TAG_SIZE) { |
| 92 | + return CryptoError.AuthenticationFailed; |
| 93 | + } |
| 94 | + |
| 95 | + const payload_len = total_len - AES_GCM_TAG_SIZE; |
| 96 | + |
| 97 | + // Create nonce (same as encryption) |
| 98 | + var nonce = [AES_GCM_NONCE_SIZE]u8 = undefined; |
| 99 | + @memcpy(&nonce[0..8], &block.header.block_id, 8); |
| 100 | + @memcpy(&nonce[8..12], &block.header.sequence, 4); |
| 101 | + |
| 102 | + // Decrypt in-place |
| 103 | + try aes256GcmDecryptInPlace( |
| 104 | + &block.payload[0..payload_len], |
| 105 | + &key, |
| 106 | + &nonce, |
| 107 | + &block.payload[payload_len..total_len] |
| 108 | + ); |
| 109 | + |
| 110 | + // Update header |
| 111 | + block.header.payload_len = @intCast(payload_len); |
| 112 | + block.header.encrypted = false; |
| 113 | + block.header.flags &= ~@as(u32, @bitCast(@intFromEnum(blocks.BlockFlags.encrypted))); |
| 114 | + |
| 115 | + // Recalculate checksum of decrypted data |
| 116 | + block.header.checksum = blocks.crc32c(&block.payload, block.header.payload_len); |
| 117 | +} |
| 118 | + |
| 119 | +// ============================================================ |
| 120 | +// Journal Encryption |
| 121 | +// ============================================================ |
| 122 | + |
| 123 | +// Journal entries need special handling because they contain |
| 124 | +// both the operation and its inverse |
| 125 | +pub fn encryptJournalEntry( |
| 126 | + entry_data: []u8, |
| 127 | + key: [AES256_KEY_SIZE]u8, |
| 128 | + entry_id: u64, |
| 129 | +) ![]u8 { |
| 130 | + // Similar to block encryption but with different nonce |
| 131 | + var nonce = [AES_GCM_NONCE_SIZE]u8 = undefined; |
| 132 | + @memcpy(&nonce[0..8], &entry_id, 8); |
| 133 | + // Use fixed pattern for journal entries |
| 134 | + nonce[8] = 'J'; |
| 135 | + nonce[9] = 'N'; |
| 136 | + nonce[10] = 'L'; |
| 137 | + nonce[11] = 0; |
| 138 | + |
| 139 | + // Allocate buffer for encrypted data + tag |
| 140 | + var buffer: [entry_data.len + AES_GCM_TAG_SIZE]u8 = undefined; |
| 141 | + @memcpy(&buffer[0..entry_data.len], entry_data); |
| 142 | + |
| 143 | + // Encrypt |
| 144 | + try aes256GcmEncryptInPlace( |
| 145 | + &buffer[0..entry_data.len], |
| 146 | + &key, |
| 147 | + &nonce, |
| 148 | + &buffer[entry_data.len..entry_data.len + AES_GCM_TAG_SIZE] |
| 149 | + ); |
| 150 | + |
| 151 | + return &buffer; |
| 152 | +} |
| 153 | + |
| 154 | +// ============================================================ |
| 155 | +// Key Derivation |
| 156 | +// ============================================================ |
| 157 | + |
| 158 | +// Derive encryption key from master key and block type |
| 159 | +pub fn deriveBlockKey( |
| 160 | + master_key: []const u8, |
| 161 | + block_type: blocks.BlockType, |
| 162 | + block_id: u64, |
| 163 | +) ![AES256_KEY_SIZE]u8 { |
| 164 | + // Use BLAKE3 for key derivation (matches vault's crypto) |
| 165 | + // In real implementation, use proper KDF |
| 166 | + var key = [AES256_KEY_SIZE]u8 = undefined; |
| 167 | + |
| 168 | + // Simple XOR-based derivation for example |
| 169 | + // Real implementation would use HKDF or similar |
| 170 | + for (0..AES256_KEY_SIZE) |i| { |
| 171 | + if (i < master_key.len) { |
| 172 | + key[i] = master_key[i] ^ @as(u8, @truncate(block_type)) ^ @as(u8, @truncate(block_id >> (i % 8))); |
| 173 | + } else { |
| 174 | + key[i] = @as(u8, @truncate(block_type)) ^ @as(u8, @truncate(block_id >> (i % 8))); |
| 175 | + } |
| 176 | + } |
| 177 | + |
| 178 | + return key; |
| 179 | +} |
| 180 | + |
| 181 | +// ============================================================ |
| 182 | +// Placeholder Crypto Functions |
| 183 | +// (In real implementation, use libsodium or similar) |
| 184 | +// ============================================================ |
| 185 | + |
| 186 | +fn aes256GcmEncryptInPlace( |
| 187 | + data: []u8, |
| 188 | + key: anytype, |
| 189 | + nonce: anytype, |
| 190 | + tag_out: []u8, |
| 191 | +) !void { |
| 192 | + // This is a placeholder - real implementation would use |
| 193 | + // a proper crypto library like libsodium or OpenSSL |
| 194 | + |
| 195 | + // For now, just XOR with key (INSECURE - for structure only!) |
| 196 | + var key_bytes = @ptrCast([*]const u8, &key); |
| 197 | + for (0..data.len) |i| { |
| 198 | + data[i] ^= key_bytes[i % @intCast(key_bytes.len)]; |
| 199 | + } |
| 200 | + |
| 201 | + // Fill tag with dummy data |
| 202 | + for (0..tag_out.len) |i| { |
| 203 | + tag_out[i] = @as(u8, @truncate(i)); |
| 204 | + } |
| 205 | +} |
| 206 | + |
| 207 | +fn aes256GcmDecryptInPlace( |
| 208 | + data: []u8, |
| 209 | + key: anytype, |
| 210 | + nonce: anytype, |
| 211 | + tag: []const u8, |
| 212 | +) !void { |
| 213 | + // Verify tag (dummy check) |
| 214 | + for (0..tag.len) |i| { |
| 215 | + if (tag[i] != @as(u8, @truncate(i))) { |
| 216 | + return CryptoError.AuthenticationFailed; |
| 217 | + } |
| 218 | + } |
| 219 | + |
| 220 | + // Decrypt (same as encrypt for XOR) |
| 221 | + var key_bytes = @ptrCast([*]const u8, &key); |
| 222 | + for (0..data.len) |i| { |
| 223 | + data[i] ^= key_bytes[i % @intCast(key_bytes.len)]; |
| 224 | + } |
| 225 | +} |
0 commit comments