Files
sqlite3/vfs/adiantum/hbsh.go

289 lines
6.9 KiB
Go
Raw Normal View History

2024-04-18 01:39:47 +01:00
package adiantum
import (
"encoding/binary"
"encoding/hex"
"io"
2024-10-18 12:20:32 +01:00
"lukechampine.com/adiantum/hbsh"
2024-04-18 01:39:47 +01:00
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/internal/util"
2024-10-18 18:07:24 +01:00
"github.com/ncruces/go-sqlite3/util/vfsutil"
2024-04-18 01:39:47 +01:00
"github.com/ncruces/go-sqlite3/vfs"
)
type hbshVFS struct {
vfs.VFS
init HBSHCreator
2024-04-18 01:39:47 +01:00
}
func (h *hbshVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
2024-07-26 01:23:35 +01:00
// notest // OpenFilename is called instead
2025-08-14 15:04:10 +01:00
if name == "" {
return h.OpenFilename(nil, flags)
}
return nil, flags, sqlite3.CANTOPEN
2024-04-18 01:39:47 +01:00
}
func (h *hbshVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs.File, _ vfs.OpenFlag, err error) {
2024-10-25 00:12:29 +01:00
file, flags, err = vfsutil.WrapOpenFilename(h.VFS, name, flags)
2024-04-30 20:56:42 +01:00
2024-04-27 12:19:46 +01:00
// Encrypt everything except super journals and memory files.
if err != nil || flags&(vfs.OPEN_SUPER_JOURNAL|vfs.OPEN_MEMORY) != 0 {
2024-04-18 01:39:47 +01:00
return file, flags, err
}
2024-04-27 12:19:46 +01:00
var hbsh *hbsh.HBSH
2024-10-21 14:51:30 +01:00
if f, ok := vfsutil.UnwrapFile[*hbshFile](name.DatabaseFile()); ok {
2024-04-27 12:19:46 +01:00
hbsh = f.hbsh
} else {
var key []byte
if params := name.URIParameters(); name == nil {
key = h.init.KDF("") // Temporary files get a random key.
2024-04-27 12:19:46 +01:00
} else if t, ok := params["key"]; ok {
key = []byte(t[0])
} else if t, ok := params["hexkey"]; ok {
key, _ = hex.DecodeString(t[0])
} else if t, ok := params["textkey"]; ok && len(t[0]) > 0 {
key = h.init.KDF(t[0])
2024-05-02 12:09:39 +01:00
} else if flags&vfs.OPEN_MAIN_DB != 0 {
2024-10-25 00:12:29 +01:00
// Main databases may have their key specified as a PRAGMA.
return &hbshFile{File: file, init: h.init}, flags, nil
2024-04-27 12:19:46 +01:00
}
hbsh = h.init.HBSH(key)
2024-04-27 12:19:46 +01:00
}
2024-04-30 20:56:42 +01:00
2024-05-02 12:09:39 +01:00
if hbsh == nil {
2024-10-25 00:12:29 +01:00
file.Close()
2025-10-18 11:06:50 +01:00
return nil, flags, sqlite3.IOERR_BADKEY
2024-04-30 20:56:42 +01:00
}
return &hbshFile{File: file, hbsh: hbsh, init: h.init}, flags, nil
2024-04-18 01:39:47 +01:00
}
// Larger blocks improve both security (wide-block cipher)
// and throughput (cheap hashes amortize the block cipher's cost).
// Use the default SQLite page size;
// smaller pages pay the cost of unaligned access.
// https://sqlite.org/pgszchng2016.html
2024-04-18 01:39:47 +01:00
const (
tweakSize = 8
2024-04-28 10:33:39 +01:00
blockSize = 4096
2024-04-18 01:39:47 +01:00
)
2024-10-21 14:51:30 +01:00
// Ensure blockSize is a power of two.
var _ [0]struct{} = [blockSize & (blockSize - 1)]struct{}{}
func roundDown(i int64) int64 {
return i &^ (blockSize - 1)
}
func roundUp[T int | int64](i T) T {
return (i + (blockSize - 1)) &^ (blockSize - 1)
}
2024-04-18 01:39:47 +01:00
type hbshFile struct {
vfs.File
init HBSHCreator
2024-04-18 01:39:47 +01:00
hbsh *hbsh.HBSH
tweak [tweakSize]byte
2024-04-28 10:33:39 +01:00
block [blockSize]byte
2024-04-18 01:39:47 +01:00
}
2024-04-27 12:19:46 +01:00
func (h *hbshFile) Pragma(name string, value string) (string, error) {
var key []byte
switch name {
case "key":
key = []byte(value)
case "hexkey":
key, _ = hex.DecodeString(value)
case "textkey":
if len(value) > 0 {
key = h.init.KDF(value)
}
2024-04-27 12:19:46 +01:00
default:
2024-10-18 18:07:24 +01:00
return vfsutil.WrapPragma(h.File, name, value)
2024-04-27 12:19:46 +01:00
}
if h.hbsh = h.init.HBSH(key); h.hbsh != nil {
2024-04-27 12:19:46 +01:00
return "ok", nil
}
2025-10-18 11:06:50 +01:00
return "", sqlite3.IOERR_BADKEY
2024-04-27 12:19:46 +01:00
}
2024-04-18 01:39:47 +01:00
func (h *hbshFile) ReadAt(p []byte, off int64) (n int, err error) {
2024-04-27 12:19:46 +01:00
if h.hbsh == nil {
2024-04-30 20:56:42 +01:00
// Only OPEN_MAIN_DB can have a missing key.
2024-04-27 12:19:46 +01:00
if off == 0 && len(p) == 100 {
2024-04-30 20:56:42 +01:00
// SQLite is trying to read the header of a database file.
// Pretend the file is empty so the key may be specified as a PRAGMA.
2024-04-27 12:19:46 +01:00
return 0, io.EOF
}
return 0, sqlite3.CANTOPEN
}
2024-10-21 14:51:30 +01:00
min := roundDown(off)
max := roundUp(off + int64(len(p)))
2024-04-18 01:39:47 +01:00
2024-04-19 18:51:27 +01:00
// Read one block at a time.
2024-04-18 01:39:47 +01:00
for ; min < max; min += blockSize {
m, err := h.File.ReadAt(h.block[:], min)
if m != blockSize {
return n, err
}
binary.LittleEndian.PutUint64(h.tweak[:], uint64(min))
data := h.hbsh.Decrypt(h.block[:], h.tweak[:])
if off > min {
data = data[off-min:]
}
n += copy(p[n:], data)
}
if n != len(p) {
panic(util.AssertErr())
}
return n, nil
}
func (h *hbshFile) WriteAt(p []byte, off int64) (n int, err error) {
2024-04-27 12:19:46 +01:00
if h.hbsh == nil {
return 0, sqlite3.READONLY
}
2024-10-21 14:51:30 +01:00
min := roundDown(off)
max := roundUp(off + int64(len(p)))
2024-04-18 01:39:47 +01:00
2024-04-19 18:51:27 +01:00
// Write one block at a time.
2024-04-18 01:39:47 +01:00
for ; min < max; min += blockSize {
binary.LittleEndian.PutUint64(h.tweak[:], uint64(min))
data := h.block[:]
2024-04-18 10:12:17 +01:00
if off > min || len(p[n:]) < blockSize {
2024-04-19 18:51:27 +01:00
// Partial block write: read-update-write.
2024-04-18 01:39:47 +01:00
m, err := h.File.ReadAt(h.block[:], min)
2025-07-22 22:41:29 +01:00
if m == blockSize {
data = h.hbsh.Decrypt(h.block[:], h.tweak[:])
} else if err != io.EOF {
return n, err
} else {
2024-04-30 20:56:42 +01:00
// Writing past the EOF.
2024-04-19 18:51:27 +01:00
// We're either appending an entirely new block,
// or the final block was only partially written.
2024-04-28 10:33:39 +01:00
// A partially written block can't be decrypted,
2024-04-19 18:51:27 +01:00
// and is as good as corrupt.
// Either way, zero pad the file to the next block size.
2024-04-18 10:12:17 +01:00
clear(data)
}
if off > min {
data = data[off-min:]
2024-04-18 01:39:47 +01:00
}
}
t := copy(data, p[n:])
h.hbsh.Encrypt(h.block[:], h.tweak[:])
m, err := h.File.WriteAt(h.block[:], min)
if m != blockSize {
return n, err
}
n += t
}
if n != len(p) {
panic(util.AssertErr())
}
return n, nil
}
func (h *hbshFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
2024-10-21 14:51:30 +01:00
var _ [0]struct{} = [blockSize - 4096]struct{}{} // Ensure blockSize is 4K.
2024-04-18 01:39:47 +01:00
return h.File.DeviceCharacteristics() & (0 |
2024-11-26 11:39:31 +00:00
// These flags are safe:
2024-10-21 14:51:30 +01:00
vfs.IOCAP_ATOMIC4K |
2024-04-18 01:39:47 +01:00
vfs.IOCAP_IMMUTABLE |
2024-11-26 11:39:31 +00:00
vfs.IOCAP_SEQUENTIAL |
vfs.IOCAP_SUBPAGE_READ |
vfs.IOCAP_BATCH_ATOMIC |
vfs.IOCAP_UNDELETABLE_WHEN_OPEN)
2024-04-18 01:39:47 +01:00
}
2024-10-21 14:51:30 +01:00
func (h *hbshFile) SectorSize() int {
return util.LCM(h.File.SectorSize(), blockSize)
}
2024-04-28 10:33:39 +01:00
2024-10-21 14:51:30 +01:00
func (h *hbshFile) Truncate(size int64) error {
return h.File.Truncate(roundUp(size))
2024-04-18 01:39:47 +01:00
}
2024-04-25 13:29:19 +01:00
func (h *hbshFile) ChunkSize(size int) {
2024-10-21 14:51:30 +01:00
vfsutil.WrapChunkSize(h.File, roundUp(size))
2024-04-25 13:29:19 +01:00
}
2024-04-18 01:39:47 +01:00
func (h *hbshFile) SizeHint(size int64) error {
2024-10-21 14:51:30 +01:00
return vfsutil.WrapSizeHint(h.File, roundUp(size))
2024-04-18 01:39:47 +01:00
}
2025-07-22 22:41:29 +01:00
// Wrap optional methods.
2024-10-21 14:51:30 +01:00
func (h *hbshFile) Unwrap() vfs.File {
2025-07-22 22:41:29 +01:00
return h.File // notest
2024-04-18 01:39:47 +01:00
}
2024-10-21 14:51:30 +01:00
func (h *hbshFile) SharedMemory() vfs.SharedMemory {
2025-07-22 22:41:29 +01:00
return vfsutil.WrapSharedMemory(h.File) // notest
2024-10-21 14:51:30 +01:00
}
func (h *hbshFile) LockState() vfs.LockLevel {
return vfsutil.WrapLockState(h.File) // notest
2024-04-18 01:39:47 +01:00
}
2024-10-18 11:59:36 +01:00
func (h *hbshFile) PersistentWAL() bool {
2025-01-13 09:28:47 +00:00
return vfsutil.WrapPersistWAL(h.File) // notest
2024-10-18 11:59:36 +01:00
}
func (h *hbshFile) SetPersistentWAL(keepWAL bool) {
2025-01-14 16:47:48 +00:00
vfsutil.WrapSetPersistWAL(h.File, keepWAL) // notest
2024-10-18 11:59:36 +01:00
}
2024-10-21 14:51:30 +01:00
func (h *hbshFile) HasMoved() (bool, error) {
return vfsutil.WrapHasMoved(h.File) // notest
}
func (h *hbshFile) Overwrite() error {
return vfsutil.WrapOverwrite(h.File) // notest
}
2025-01-14 16:47:48 +00:00
func (h *hbshFile) SyncSuper(super string) error {
return vfsutil.WrapSyncSuper(h.File, super) // notest
}
2024-04-18 01:39:47 +01:00
func (h *hbshFile) CommitPhaseTwo() error {
2024-10-18 18:07:24 +01:00
return vfsutil.WrapCommitPhaseTwo(h.File) // notest
2024-04-18 01:39:47 +01:00
}
func (h *hbshFile) BeginAtomicWrite() error {
2024-10-18 18:07:24 +01:00
return vfsutil.WrapBeginAtomicWrite(h.File) // notest
2024-04-18 01:39:47 +01:00
}
func (h *hbshFile) CommitAtomicWrite() error {
2024-10-18 18:07:24 +01:00
return vfsutil.WrapCommitAtomicWrite(h.File) // notest
2024-04-18 01:39:47 +01:00
}
func (h *hbshFile) RollbackAtomicWrite() error {
2024-10-18 18:07:24 +01:00
return vfsutil.WrapRollbackAtomicWrite(h.File) // notest
2024-04-18 01:39:47 +01:00
}
2024-04-25 13:29:19 +01:00
2024-10-21 14:51:30 +01:00
func (h *hbshFile) CheckpointStart() {
vfsutil.WrapCheckpointStart(h.File) // notest
2024-04-25 13:29:19 +01:00
}
2024-10-21 14:51:30 +01:00
func (h *hbshFile) CheckpointDone() {
vfsutil.WrapCheckpointDone(h.File) // notest
2024-04-25 13:29:19 +01:00
}
2025-01-14 16:47:48 +00:00
func (h *hbshFile) BusyHandler(handler func() bool) {
vfsutil.WrapBusyHandler(h.File, handler) // notest
}