117 lines
4.0 KiB
Nim
117 lines
4.0 KiB
Nim
# SPDX-License-Identifier: LSL-1.0
|
|
# Copyright (c) 2026 Markus Maiwald
|
|
# Stewardship: Self Sovereign Society Foundation
|
|
#
|
|
# This file is part of the Nexus Sovereign Core.
|
|
# See legal/LICENSE_SOVEREIGN.md for license terms.
|
|
|
|
## Nexus Membrane: The Monolith (4MB Key)
|
|
##
|
|
## Implements the Zero-Friction Encryption per SPEC-021.
|
|
## - L0 (Factory): Unprotected 4MB random key
|
|
## - L1 (Sovereignty): Password-protected (Argon2id + XChaCha20)
|
|
|
|
import ../blk
|
|
|
|
const
|
|
MONOLITH_SECTOR_START* = 2048'u64 # After bootloader
|
|
MONOLITH_SIZE* = 4 * 1024 * 1024 # 4MB = 4,194,304 bytes
|
|
MONOLITH_SECTORS* = MONOLITH_SIZE div 512 # 8192 sectors
|
|
|
|
# Magic header to detect encrypted vs raw Monolith
|
|
MAGIC_ENCRYPTED* = 0xFFFFFFFF'u32
|
|
MAGIC_RAW* = 0x4D4F4E4F'u32 # "MONO"
|
|
|
|
type
|
|
MonolithHeader* = object
|
|
magic*: uint32
|
|
version*: uint32
|
|
salt*: array[32, byte] # Argon2id salt (if encrypted)
|
|
nonce*: array[24, byte] # XChaCha20-Poly1305 nonce
|
|
reserved*: array[448, byte]
|
|
|
|
# Static buffer for Monolith (4MB in RAM)
|
|
# Note: This is heavy but necessary for single-copy unlock
|
|
var monolith_buffer: array[MONOLITH_SIZE, byte]
|
|
var monolith_loaded: bool = false
|
|
|
|
# ION Client for Crypto
|
|
import ../ion_client
|
|
# TODO: Add XChaCha20-Poly1305 and Argon2id imports when implementing Phase B (lock/unlock)
|
|
|
|
proc monolith_read*(): bool =
|
|
## Read 4MB Monolith from disk into RAM buffer
|
|
## Returns: true on success
|
|
for i in 0'u64 ..< MONOLITH_SECTORS:
|
|
let offset = int(i * 512)
|
|
if blk_read(MONOLITH_SECTOR_START + i, addr monolith_buffer[offset]) < 0:
|
|
return false
|
|
monolith_loaded = true
|
|
return true
|
|
|
|
proc monolith_get_header*(): ptr MonolithHeader =
|
|
## Get pointer to the Monolith header (first 512 bytes)
|
|
if not monolith_loaded: return nil
|
|
return cast[ptr MonolithHeader](addr monolith_buffer[0])
|
|
|
|
proc monolith_is_encrypted*(): bool =
|
|
## Check if the Monolith is password-protected
|
|
if not monolith_loaded: return false
|
|
let hdr = monolith_get_header()
|
|
return hdr.magic == MAGIC_ENCRYPTED
|
|
|
|
proc monolith_derive_volume_key*(out_key: ptr array[32, byte]) =
|
|
## Derive VolumeKey = BLAKE3(Monolith)
|
|
## This is the master key for SFS encryption
|
|
if not monolith_loaded: return
|
|
|
|
# Skip header (512 bytes), hash the rest
|
|
let data_start = addr monolith_buffer[512]
|
|
let data_len = uint64(MONOLITH_SIZE - 512)
|
|
out_key[] = crypto_blake3(data_start, data_len)
|
|
|
|
proc monolith_generate*(dest_sectors: uint64 = MONOLITH_SECTOR_START): bool =
|
|
## Generate a new random Monolith (called during `nexus forge --image`)
|
|
## Note: In real impl, we'd use HAL RNG. Here we use a simple PRNG placeholder.
|
|
|
|
# Initialize header
|
|
var hdr: MonolithHeader
|
|
hdr.magic = MAGIC_RAW
|
|
hdr.version = 1
|
|
|
|
# Write header
|
|
copyMem(addr monolith_buffer[0], addr hdr, sizeof(MonolithHeader))
|
|
|
|
# Fill rest with "random" data (placeholder - real impl uses hal_crypto_random)
|
|
# For now, use a simple LCG seeded by a counter
|
|
var seed: uint64 = 0xDEADBEEF12345678'u64
|
|
for i in 512 ..< MONOLITH_SIZE:
|
|
seed = seed * 6364136223846793005'u64 + 1442695040888963407'u64
|
|
monolith_buffer[i] = byte(seed shr 56)
|
|
|
|
# Write to disk
|
|
for i in 0'u64 ..< MONOLITH_SECTORS:
|
|
let offset = int(i * 512)
|
|
if blk_write(dest_sectors + i, addr monolith_buffer[offset]) < 0:
|
|
return false
|
|
|
|
discard blk_sync()
|
|
monolith_loaded = true
|
|
return true
|
|
|
|
# =========================================================
|
|
# Phase B: Sovereignty Claim (Future Implementation)
|
|
# =========================================================
|
|
# proc monolith_lock*(password: string): bool
|
|
# ## Encrypt Monolith with user password
|
|
# ## 1. Derive key = Argon2id(password, random_salt)
|
|
# ## 2. Encrypt Monolith = XChaCha20-Poly1305(raw_data, key)
|
|
# ## 3. Write encrypted Monolith with MAGIC_ENCRYPTED header
|
|
#
|
|
# proc monolith_unlock*(password: string): bool
|
|
# ## Decrypt Monolith with user password
|
|
# ## 1. Read header, extract salt + nonce
|
|
# ## 2. Derive key = Argon2id(password, salt)
|
|
# ## 3. Decrypt data = XChaCha20-Poly1305.open(ciphertext, key, nonce)
|
|
# ## 4. Verify Poly1305 tag; return false if invalid
|