Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AES-XTS VFS #171

Merged
merged 6 commits into from
Oct 17, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
XTS tests.
  • Loading branch information
ncruces committed Oct 17, 2024
commit 47afd3cdea61b6092b1f0b014831e04bf0184df4
2 changes: 1 addition & 1 deletion vfs/adiantum/api.go
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ func Register(name string, base vfs.VFS, cipher HBSHCreator) {
}
vfs.Register(name, &hbshVFS{
VFS: base,
hbsh: cipher,
init: cipher,
})
}

18 changes: 9 additions & 9 deletions vfs/adiantum/hbsh.go
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ import (

type hbshVFS struct {
vfs.VFS
hbsh HBSHCreator
init HBSHCreator
}

func (h *hbshVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
@@ -39,24 +39,24 @@ func (h *hbshVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs
} else {
var key []byte
if params := name.URIParameters(); name == nil {
key = h.hbsh.KDF("") // Temporary files get a random key.
key = h.init.KDF("") // Temporary files get a random key.
} 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 {
key = h.hbsh.KDF(t[0])
key = h.init.KDF(t[0])
} else if flags&vfs.OPEN_MAIN_DB != 0 {
// Main datatabases may have their key specified as a PRAGMA.
return &hbshFile{File: file, reset: h.hbsh}, flags, nil
return &hbshFile{File: file, init: h.init}, flags, nil
}
hbsh = h.hbsh.HBSH(key)
hbsh = h.init.HBSH(key)
}

if hbsh == nil {
return nil, flags, sqlite3.CANTOPEN
}
return &hbshFile{File: file, hbsh: hbsh, reset: h.hbsh}, flags, nil
return &hbshFile{File: file, hbsh: hbsh, init: h.init}, flags, nil
}

const (
@@ -66,8 +66,8 @@ const (

type hbshFile struct {
vfs.File
init HBSHCreator
hbsh *hbsh.HBSH
reset HBSHCreator
tweak [tweakSize]byte
block [blockSize]byte
}
@@ -80,15 +80,15 @@ func (h *hbshFile) Pragma(name string, value string) (string, error) {
case "hexkey":
key, _ = hex.DecodeString(value)
case "textkey":
key = h.reset.KDF(value)
key = h.init.KDF(value)
default:
if f, ok := h.File.(vfs.FilePragma); ok {
return f.Pragma(name, value)
}
return "", sqlite3.NOTFOUND
}

if h.hbsh = h.reset.HBSH(key); h.hbsh != nil {
if h.hbsh = h.init.HBSH(key); h.hbsh != nil {
return "ok", nil
}
return "", sqlite3.CANTOPEN
47 changes: 47 additions & 0 deletions vfs/tests/mptest/mptest_test.go
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import (
"github.com/ncruces/go-sqlite3/vfs"
_ "github.com/ncruces/go-sqlite3/vfs/adiantum"
"github.com/ncruces/go-sqlite3/vfs/memdb"
_ "github.com/ncruces/go-sqlite3/vfs/xts"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
@@ -294,6 +295,52 @@ func Test_crash01_adiantum_wal(t *testing.T) {
mod.Close(ctx)
}

func Test_crash01_xts(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
if os.Getenv("CI") != "" {
t.Skip("skipping in CI")
}
if !vfs.SupportsFileLocking {
t.Skip("skipping without locks")
}

ctx := util.NewContext(newContext(t))
name := "file:" + filepath.Join(t.TempDir(), "test.db") +
"?hexkey=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
cfg := config(ctx).WithArgs("mptest", name, "crash01.test",
"--vfs", "xts")
mod, err := rt.InstantiateModule(ctx, module, cfg)
if err != nil {
t.Fatal(err)
}
mod.Close(ctx)
}

func Test_crash01_xts_wal(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
if os.Getenv("CI") != "" {
t.Skip("skipping in CI")
}
if !vfs.SupportsSharedMemory {
t.Skip("skipping without shared memory")
}

ctx := util.NewContext(newContext(t))
name := "file:" + filepath.Join(t.TempDir(), "test.db") +
"?hexkey=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
cfg := config(ctx).WithArgs("mptest", name, "crash01.test",
"--vfs", "xts", "--journalmode", "wal")
mod, err := rt.InstantiateModule(ctx, module, cfg)
if err != nil {
t.Fatal(err)
}
mod.Close(ctx)
}

func newContext(t *testing.T) context.Context {
return context.WithValue(context.Background(), logger{}, &testWriter{T: t})
}
20 changes: 20 additions & 0 deletions vfs/tests/speedtest1/speedtest1_test.go
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ import (
"github.com/ncruces/go-sqlite3/vfs"
_ "github.com/ncruces/go-sqlite3/vfs/adiantum"
_ "github.com/ncruces/go-sqlite3/vfs/memdb"
_ "github.com/ncruces/go-sqlite3/vfs/xts"
)

//go:embed testdata/speedtest1.wasm.bz2
@@ -125,3 +126,22 @@ func Benchmark_adiantum(b *testing.B) {
}
mod.Close(ctx)
}

func Benchmark_xts(b *testing.B) {
output.Reset()
ctx := util.NewContext(context.Background())
name := "file:" + filepath.Join(b.TempDir(), "test.db") +
"?hexkey=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
args := append(options, "--vfs", "xts", "--size", strconv.Itoa(b.N), name)
cfg := wazero.NewModuleConfig().
WithArgs(args...).WithName("speedtest1").
WithStdout(&output).WithStderr(&output).
WithSysWalltime().WithSysNanotime().WithSysNanosleep().
WithOsyield(runtime.Gosched).
WithRandSource(rand.Reader)
mod, err := rt.InstantiateModule(ctx, module, cfg)
if err != nil {
b.Fatal(err)
}
mod.Close(ctx)
}
3 changes: 3 additions & 0 deletions vfs/xts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Go `xts` SQLite VFS

This package wraps an SQLite VFS to offer encryption at rest.
34 changes: 34 additions & 0 deletions vfs/xts/aes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package xts

import (
"crypto/aes"
"crypto/rand"
"crypto/sha512"

"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/xts"
)

// This variable can be replaced with -ldflags:
//
// go build -ldflags="-X github.com/ncruces/go-sqlite3/vfs/xts.pepper=xts"
var pepper = "github.com/ncruces/go-sqlite3/vfs/xts"

type aesCreator struct{}

func (aesCreator) XTS(key []byte) *xts.Cipher {
c, err := xts.NewCipher(aes.NewCipher, key)
if err != nil {
return nil
}
return c
}

func (aesCreator) KDF(text string) []byte {
if text == "" {
key := make([]byte, 32)
n, _ := rand.Read(key)
return key[:n]
}
return pbkdf2.Key([]byte(text), []byte(pepper), 10_000, 32, sha512.New)
}
88 changes: 88 additions & 0 deletions vfs/xts/aes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package xts_test

import (
_ "embed"
"path/filepath"
"strings"
"testing"

"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
_ "github.com/ncruces/go-sqlite3/internal/testcfg"
"github.com/ncruces/go-sqlite3/util/ioutil"
"github.com/ncruces/go-sqlite3/vfs"
"github.com/ncruces/go-sqlite3/vfs/readervfs"
"github.com/ncruces/go-sqlite3/vfs/xts"
)

//go:embed testdata/test.db
var testDB string

func Test_fileformat(t *testing.T) {
readervfs.Create("test.db", ioutil.NewSizeReaderAt(strings.NewReader(testDB)))
xts.Register("rxts", vfs.Find("reader"), nil)

db, err := driver.Open("file:test.db?vfs=rxts")
if err != nil {
t.Fatal(err)
}
defer db.Close()

_, err = db.Exec(`PRAGMA textkey='correct+horse+battery+staple'`)
if err != nil {
t.Fatal(err)
}

var version uint32
err = db.QueryRow(`PRAGMA user_version`).Scan(&version)
if err != nil {
t.Fatal(err)
}
if version != 0xBADDB {
t.Error(version)
}
}

func Benchmark_nokey(b *testing.B) {
tmp := filepath.Join(b.TempDir(), "test.db")
sqlite3.Initialize()
b.ResetTimer()

for n := 0; n < b.N; n++ {
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1")
if err != nil {
b.Fatal(err)
}
db.Close()
}
}
func Benchmark_hexkey(b *testing.B) {
tmp := filepath.Join(b.TempDir(), "test.db")
sqlite3.Initialize()
b.ResetTimer()

for n := 0; n < b.N; n++ {
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1" +
"&vfs=xts&hexkey=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
if err != nil {
b.Fatal(err)
}
db.Close()
}
}

func Benchmark_textkey(b *testing.B) {
tmp := filepath.Join(b.TempDir(), "test.db")
sqlite3.Initialize()
b.ResetTimer()

for n := 0; n < b.N; n++ {
db, err := sqlite3.Open("file:" + filepath.ToSlash(tmp) + "?nolock=1" +
"&vfs=xts&textkey=correct+horse+battery+staple")
if err != nil {
b.Fatal(err)
}
db.Close()
}
}
36 changes: 36 additions & 0 deletions vfs/xts/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Package xts wraps an SQLite VFS to offer encryption at rest.
package xts

import (
"github.com/ncruces/go-sqlite3/vfs"
"golang.org/x/crypto/xts"
)

func init() {
Register("xts", vfs.Find(""), nil)
}

// Register registers an encrypting VFS, wrapping a base VFS,
// and possibly using a custom XTS cipher construction.
// To use the default AES-XTS construction, set cipher to nil.
func Register(name string, base vfs.VFS, cipher XTSCreator) {
if cipher == nil {
cipher = aesCreator{}
}
vfs.Register(name, &xtsVFS{
VFS: base,
init: cipher,
})
}

// XTSCreator creates an [xts.Cipher]
// given key material.
type XTSCreator interface {
// KDF derives an XTS key from a secret.
// If no secret is given, a random key is generated.
KDF(secret string) (key []byte)

// XTS creates an XTS cipher given a key.
// If key is not appropriate, nil is returned.
XTS(key []byte) *xts.Cipher
}
22 changes: 22 additions & 0 deletions vfs/xts/math.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package xts

func abs(n int) int {
if n < 0 {
return -n
}
return n
}

func gcd(m, n int) int {
for n != 0 {
m, n = n, m%n
}
return abs(m)
}

func lcm(m, n int) int {
if n == 0 {
return 0
}
return abs(n) * (abs(m) / gcd(m, n))
}
Loading