package badger
import (
"bytes"
"crypto/aes"
"crypto/rand"
"encoding/binary"
"hash/crc32"
"io"
"os"
"path/filepath"
"sync"
"time"
"github.com/dgraph-io/badger/v4/pb"
"github.com/dgraph-io/badger/v4/y"
"google.golang.org/protobuf/proto"
)
const (
KeyRegistryFileName = "KEYREGISTRY"
KeyRegistryRewriteFileName = "REWRITE-KEYREGISTRY"
)
var sanityText = []byte ("Hello Badger" )
type KeyRegistry struct {
sync .RWMutex
dataKeys map [uint64 ]*pb .DataKey
lastCreated int64
nextKeyID uint64
fp *os .File
opt KeyRegistryOptions
}
type KeyRegistryOptions struct {
Dir string
ReadOnly bool
EncryptionKey []byte
EncryptionKeyRotationDuration time .Duration
InMemory bool
}
func newKeyRegistry(opt KeyRegistryOptions ) *KeyRegistry {
return &KeyRegistry {
dataKeys : make (map [uint64 ]*pb .DataKey ),
nextKeyID : 0 ,
opt : opt ,
}
}
func OpenKeyRegistry (opt KeyRegistryOptions ) (*KeyRegistry , error ) {
if len (opt .EncryptionKey ) > 0 {
switch len (opt .EncryptionKey ) {
default :
return nil , y .Wrapf (ErrInvalidEncryptionKey , "During OpenKeyRegistry" )
case 16 , 24 , 32 :
break
}
}
if opt .InMemory {
return newKeyRegistry (opt ), nil
}
path := filepath .Join (opt .Dir , KeyRegistryFileName )
var flags y .Flags
if opt .ReadOnly {
flags |= y .ReadOnly
} else {
flags |= y .Sync
}
fp , err := y .OpenExistingFile (path , flags )
if os .IsNotExist (err ) {
kr := newKeyRegistry (opt )
if opt .ReadOnly {
return kr , nil
}
if err := WriteKeyRegistry (kr , opt ); err != nil {
return nil , y .Wrapf (err , "Error while writing key registry." )
}
fp , err = y .OpenExistingFile (path , flags )
if err != nil {
return nil , y .Wrapf (err , "Error while opening newly created key registry." )
}
} else if err != nil {
return nil , y .Wrapf (err , "Error while opening key registry." )
}
kr , err := readKeyRegistry (fp , opt )
if err != nil {
fp .Close ()
return nil , err
}
if opt .ReadOnly {
return kr , fp .Close ()
}
kr .fp = fp
return kr , nil
}
type keyRegistryIterator struct {
encryptionKey []byte
fp *os .File
lenCrcBuf [8 ]byte
}
func newKeyRegistryIterator(fp *os .File , encryptionKey []byte ) (*keyRegistryIterator , error ) {
return &keyRegistryIterator {
encryptionKey : encryptionKey ,
fp : fp ,
lenCrcBuf : [8 ]byte {},
}, validRegistry (fp , encryptionKey )
}
func validRegistry(fp *os .File , encryptionKey []byte ) error {
iv := make ([]byte , aes .BlockSize )
var err error
if _, err = fp .Read (iv ); err != nil {
return y .Wrapf (err , "Error while reading IV for key registry." )
}
eSanityText := make ([]byte , len (sanityText ))
if _, err = fp .Read (eSanityText ); err != nil {
return y .Wrapf (err , "Error while reading sanity text." )
}
if len (encryptionKey ) > 0 {
if eSanityText , err = y .XORBlockAllocate (eSanityText , encryptionKey , iv ); err != nil {
return y .Wrapf (err , "During validRegistry" )
}
}
if !bytes .Equal (eSanityText , sanityText ) {
return ErrEncryptionKeyMismatch
}
return nil
}
func (kri *keyRegistryIterator ) next () (*pb .DataKey , error ) {
var err error
if _, err = kri .fp .Read (kri .lenCrcBuf [:]); err != nil {
if err != io .EOF {
return nil , y .Wrapf (err , "While reading crc in keyRegistryIterator.next" )
}
return nil , err
}
l := int64 (binary .BigEndian .Uint32 (kri .lenCrcBuf [0 :4 ]))
data := make ([]byte , l )
if _, err = kri .fp .Read (data ); err != nil {
if err != io .EOF {
return nil , y .Wrapf (err , "While reading protobuf in keyRegistryIterator.next" )
}
return nil , err
}
if crc32 .Checksum (data , y .CastagnoliCrcTable ) != binary .BigEndian .Uint32 (kri .lenCrcBuf [4 :]) {
return nil , y .Wrapf (y .ErrChecksumMismatch , "Error while checking checksum for data key." )
}
dataKey := &pb .DataKey {}
if err = proto .Unmarshal (data , dataKey ); err != nil {
return nil , y .Wrapf (err , "While unmarshal of datakey in keyRegistryIterator.next" )
}
if len (kri .encryptionKey ) > 0 {
if dataKey .Data , err = y .XORBlockAllocate (dataKey .Data , kri .encryptionKey , dataKey .Iv ); err != nil {
return nil , y .Wrapf (err , "While decrypting datakey in keyRegistryIterator.next" )
}
}
return dataKey , nil
}
func readKeyRegistry(fp *os .File , opt KeyRegistryOptions ) (*KeyRegistry , error ) {
itr , err := newKeyRegistryIterator (fp , opt .EncryptionKey )
if err != nil {
return nil , err
}
kr := newKeyRegistry (opt )
var dk *pb .DataKey
dk , err = itr .next ()
for err == nil && dk != nil {
if dk .KeyId > kr .nextKeyID {
kr .nextKeyID = dk .KeyId
}
if dk .CreatedAt > kr .lastCreated {
kr .lastCreated = dk .CreatedAt
}
kr .dataKeys [dk .KeyId ] = dk
dk , err = itr .next ()
}
if err == io .EOF {
err = nil
}
return kr , err
}
func WriteKeyRegistry (reg *KeyRegistry , opt KeyRegistryOptions ) error {
buf := &bytes .Buffer {}
iv , err := y .GenerateIV ()
y .Check (err )
eSanity := sanityText
if len (opt .EncryptionKey ) > 0 {
var err error
eSanity , err = y .XORBlockAllocate (eSanity , opt .EncryptionKey , iv )
if err != nil {
return y .Wrapf (err , "Error while encrypting sanity text in WriteKeyRegistry" )
}
}
y .Check2 (buf .Write (iv ))
y .Check2 (buf .Write (eSanity ))
for _ , k := range reg .dataKeys {
if err := storeDataKey (buf , opt .EncryptionKey , k ); err != nil {
return y .Wrapf (err , "Error while storing datakey in WriteKeyRegistry" )
}
}
tmpPath := filepath .Join (opt .Dir , KeyRegistryRewriteFileName )
fp , err := y .OpenTruncFile (tmpPath , true )
if err != nil {
return y .Wrapf (err , "Error while opening tmp file in WriteKeyRegistry" )
}
if _, err = fp .Write (buf .Bytes ()); err != nil {
fp .Close ()
return y .Wrapf (err , "Error while writing buf in WriteKeyRegistry" )
}
if err = fp .Close (); err != nil {
return y .Wrapf (err , "Error while closing tmp file in WriteKeyRegistry" )
}
if err = os .Rename (tmpPath , filepath .Join (opt .Dir , KeyRegistryFileName )); err != nil {
return y .Wrapf (err , "Error while renaming file in WriteKeyRegistry" )
}
return syncDir (opt .Dir )
}
func (kr *KeyRegistry ) DataKey (id uint64 ) (*pb .DataKey , error ) {
kr .RLock ()
defer kr .RUnlock ()
if id == 0 {
return nil , nil
}
dk , ok := kr .dataKeys [id ]
if !ok {
return nil , y .Wrapf (ErrInvalidDataKeyID , "Error for the KEY ID %d" , id )
}
return dk , nil
}
func (kr *KeyRegistry ) LatestDataKey () (*pb .DataKey , error ) {
if len (kr .opt .EncryptionKey ) == 0 {
return nil , nil
}
validKey := func () (*pb .DataKey , bool ) {
diff := time .Since (time .Unix (kr .lastCreated , 0 ))
if diff < kr .opt .EncryptionKeyRotationDuration {
return kr .dataKeys [kr .nextKeyID ], true
}
return nil , false
}
kr .RLock ()
key , valid := validKey ()
kr .RUnlock ()
if valid {
return key , nil
}
kr .Lock ()
defer kr .Unlock ()
key , valid = validKey ()
if valid {
return key , nil
}
k := make ([]byte , len (kr .opt .EncryptionKey ))
iv , err := y .GenerateIV ()
if err != nil {
return nil , err
}
_, err = rand .Read (k )
if err != nil {
return nil , err
}
kr .nextKeyID ++
dk := &pb .DataKey {
KeyId : kr .nextKeyID ,
Data : k ,
CreatedAt : time .Now ().Unix (),
Iv : iv ,
}
if !kr .opt .InMemory {
buf := &bytes .Buffer {}
if err = storeDataKey (buf , kr .opt .EncryptionKey , dk ); err != nil {
return nil , err
}
if _, err = kr .fp .Write (buf .Bytes ()); err != nil {
return nil , err
}
}
dk .Data = k
kr .lastCreated = dk .CreatedAt
kr .dataKeys [kr .nextKeyID ] = dk
return dk , nil
}
func (kr *KeyRegistry ) Close () error {
if !(kr .opt .ReadOnly || kr .opt .InMemory ) {
return kr .fp .Close ()
}
return nil
}
func storeDataKey(buf *bytes .Buffer , storageKey []byte , k *pb .DataKey ) error {
xor := func () error {
if len (storageKey ) == 0 {
return nil
}
var err error
k .Data , err = y .XORBlockAllocate (k .Data , storageKey , k .Iv )
return err
}
var err error
if err = xor (); err != nil {
return y .Wrapf (err , "Error while encrypting datakey in storeDataKey" )
}
var data []byte
if data , err = proto .Marshal (k ); err != nil {
err = y .Wrapf (err , "Error while marshaling datakey in storeDataKey" )
var err2 error
if err2 = xor (); err2 != nil {
return y .Wrapf (err ,
y .Wrapf (err2 , "Error while decrypting datakey in storeDataKey" ).Error())
}
return err
}
var lenCrcBuf [8 ]byte
binary .BigEndian .PutUint32 (lenCrcBuf [0 :4 ], uint32 (len (data )))
binary .BigEndian .PutUint32 (lenCrcBuf [4 :8 ], crc32 .Checksum (data , y .CastagnoliCrcTable ))
y .Check2 (buf .Write (lenCrcBuf [:]))
y .Check2 (buf .Write (data ))
return xor ()
}
The pages are generated with Golds v0.8.4 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .