Files
sqlite/encryption_proof_test.go
zeekayandHanzo Dev dc2f7a4603 The envelope prefers RAM scratch but no longer requires it
The pure-Go SQLCipher envelope decrypts to a plaintext scratch copy and
demanded that copy be RAM-backed, failing closed otherwise. On macOS that
meant `sudo mount_tmpfs` before any keyed store would open, and a dev
machine or a test run should not pay that.

scratchBase now PREFERS RAM-backed scratch — an operator's
HANZO_SQLITE_RAMFS_DIR if it is RAM-backed, then /dev/shm — so the
plaintext copy stays off disk wherever RAM is free (Linux, a k8s
emptyDir{medium:Memory}), and falls back to the OS temp dir where neither
exists. It never fails closed and takes no dev/test env switch; the copy
is shredded on close either way. The real at-rest guarantee is the live
libsqlcipher codec (CodecLinked), which materialises no plaintext at all
and is what a deployment that must keep plaintext off disk unconditionally
links (SQLITE_REQUIRE_CODEC=1).

Removes the ramfsBase fail-closed error, the HANZO_SQLITE_INSECURE_DEV /
HANZO_DEV opt-out, and the per-platform ramfsHint files. isRAMBacked stays
as the RAM preference. Tests follow: a keyed open with no RAM scratch now
succeeds through temp, round-trips, and writes only ciphertext at rest.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 10:55:38 -07:00

89 lines
2.8 KiB
Go

package sqlite
import (
"bytes"
"os"
"path/filepath"
"testing"
)
// TestEncryptionProof is the anti-silent-plaintext gate: a keyed store is ALWAYS
// ciphertext at rest, on every backend.
//
// EncryptionAvailable() is always true, so there is no refuse branch: whether the
// key is applied by the live libsqlcipher codec or by the pure-Go codec envelope,
// Open(key) must write ciphertext (no "SQLite format 3" header, marker absent), a
// keyed reopen must read the row back, and a wrong key must be rejected. A build
// that would ship plaintext can never pass this.
func TestEncryptionProof(t *testing.T) {
const marker = "anti-plaintext-canary-7f3a"
key := make([]byte, 32)
for i := range key {
key[i] = byte(i*7 + 1)
}
dir := t.TempDir()
dbPath := filepath.Join(dir, "proof.db")
db, err := Open(dbPath, WithRawKey(key))
if err != nil {
t.Fatalf("Open with key: %v", err)
}
if _, err := db.Exec(`CREATE TABLE c (v TEXT)`); err != nil {
t.Fatalf("create: %v", err)
}
if _, err := db.Exec(`INSERT INTO c (v) VALUES (?)`, marker); err != nil {
t.Fatalf("insert: %v", err)
}
if err := db.Close(); err != nil {
t.Fatalf("close: %v", err)
}
raw, err := os.ReadFile(dbPath)
if err != nil {
t.Fatalf("read db file: %v", err)
}
if bytes.HasPrefix(raw, []byte("SQLite format 3\x00")) {
t.Fatal("ENCRYPTION FAILURE: keyed db has a plaintext SQLite header — the key was not applied")
}
if bytes.Contains(raw, []byte(marker)) {
t.Fatal("ENCRYPTION FAILURE: plaintext marker found in keyed db file — data is NOT encrypted at rest")
}
// Reopen-with-key must read the row back (proves keyed reopen works).
db2, err := Open(dbPath, WithRawKey(key))
if err != nil {
t.Fatalf("reopen with key: %v", err)
}
defer db2.Close()
var got string
if err := db2.QueryRow(`SELECT v FROM c`).Scan(&got); err != nil {
t.Fatalf("reopen read: %v", err)
}
if got != marker {
t.Fatalf("reopen read = %q, want %q", got, marker)
}
// A different key must be rejected on reopen — the envelope fails at DecryptFile
// (page-1 authentication), the live codec at the first query.
wrong := make([]byte, 32)
copy(wrong, key)
wrong[0] ^= 0xFF
db3, err := Open(dbPath, WithRawKey(wrong))
if err == nil {
var x string
err = db3.QueryRow(`SELECT v FROM c`).Scan(&x)
db3.Close()
}
if err == nil {
t.Fatal("ENCRYPTION FAILURE: wrong key read succeeded — key is not enforced")
}
}
// TestEnvelopeRotation proves the envelope contract that makes master-key
// rotation non-destructive (finding B3). It is pure-Go and runs under BOTH build
// tags. A random DEK is wrapped under a KEK derived from masterA, then rewrapped
// under a KEK derived from masterB; the SAME DEK must come back out — i.e. the
// page key (and therefore the encrypted file) is untouched by rotation.