package database import ( "bytes" "errors" "github.com/number571/go-peer/pkg/crypto/hashing" "github.com/number571/go-peer/pkg/crypto/keybuilder" "github.com/number571/go-peer/pkg/crypto/random" "github.com/number571/go-peer/pkg/crypto/symmetric" "github.com/number571/go-peer/pkg/utils" "go.etcd.io/bbolt" ) var ( _ IKVDatabase = &sKVDatabase{} ) const ( cBucket = "_BUCKET_" cSaltKey = "__SALT__" cRandKey = "__RAND__" cHashKey = "__HASH__" ) const ( cSaltSize = 32 cRandSize = 32 ) type sKVDatabase struct { fDB *bbolt.DB fSettings ISettings fCipher symmetric.ICipher fAuthKey []byte } func NewKVDatabase(pSett ISettings) (IKVDatabase, error) { db, err := bbolt.Open(pSett.GetPath(), 0600, &bbolt.Options{}) if err != nil { return nil, utils.MergeErrors(ErrOpenDB, err) } saltValue, initValue, err := getSaltValue(db) if err != nil { return nil, utils.MergeErrors(ErrGetSalt, err) } cipherKey, authKey := getCipherAuthKeys(pSett, saltValue) randValue, hashRand, err := getRandHashValues(db, initValue, authKey) if err != nil { return nil, utils.MergeErrors(ErrGetHashRand, err) } newHashRand := hashing.NewHMACSHA256Hasher(authKey, randValue).ToBytes() if !bytes.Equal(hashRand, newHashRand) { return nil, ErrInvalidHash } return &sKVDatabase{ fDB: db, fSettings: pSett, fCipher: symmetric.NewAESCipher(cipherKey), fAuthKey: authKey, }, nil } func (p *sKVDatabase) GetSettings() ISettings { return p.fSettings } func (p *sKVDatabase) Set(pKey []byte, pValue []byte) error { key := hashing.NewHMACSHA256Hasher(p.fAuthKey, pKey).ToBytes() val := doEncrypt(p.fCipher, p.fAuthKey, pValue) if err := setDB(p.fDB, key, val); err != nil { return utils.MergeErrors(ErrSetValueDB, err) } return nil } func setDB(pDB *bbolt.DB, pKey []byte, pValue []byte) error { return pDB.Update(func(tx *bbolt.Tx) error { bucket, err := tx.CreateBucketIfNotExists([]byte(cBucket)) if err != nil { return err } return bucket.Put(pKey, pValue) }) } func (p *sKVDatabase) Get(pKey []byte) ([]byte, error) { key := hashing.NewHMACSHA256Hasher(p.fAuthKey, pKey).ToBytes() encValue, err := getDB(p.fDB, key) if err != nil { return nil, utils.MergeErrors(ErrGetValueDB, err) } return tryDecrypt( p.fCipher, p.fAuthKey, encValue, ) } func getDB(pDB *bbolt.DB, pKey []byte) ([]byte, error) { var encValue []byte err := pDB.View(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte(cBucket)) if b == nil { return ErrNotFound } val := b.Get(pKey) if val == nil { return ErrNotFound } encValue = make([]byte, len(val)) copy(encValue, val) return nil }) return encValue, err } func (p *sKVDatabase) Del(pKey []byte) error { key := hashing.NewHMACSHA256Hasher(p.fAuthKey, pKey).ToBytes() if err := delDB(p.fDB, key); err != nil { return utils.MergeErrors(ErrDelValueDB, err) } return nil } func delDB(pDB *bbolt.DB, pKey []byte) error { return pDB.Update(func(tx *bbolt.Tx) error { b := tx.Bucket([]byte(cBucket)) if b == nil { return nil } return b.Delete(pKey) }) } func (p *sKVDatabase) Close() error { if err := p.fDB.Close(); err != nil { return utils.MergeErrors(ErrCloseDB, err) } return nil } func getSaltValue(pDB *bbolt.DB) ([]byte, bool, error) { initValue := false saltValue, err := getDB(pDB, []byte(cSaltKey)) if err != nil { if !errors.Is(err, ErrNotFound) { return nil, false, utils.MergeErrors(ErrReadSalt, err) } initValue = true saltValue = random.NewCSPRNG().GetBytes(cSaltSize) if err := setDB(pDB, []byte(cSaltKey), saltValue); err != nil { return nil, false, utils.MergeErrors(ErrPushSalt, err) } } return saltValue, initValue, nil } func getCipherAuthKeys(pSett ISettings, pSaltValue []byte) ([]byte, []byte) { keyBuilder := keybuilder.NewKeyBuilder(1<