github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/store/localdb/localdb.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package localdb
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"path/filepath"
    12  	"sync/atomic"
    13  
    14  	"github.com/decred/politeia/politeiad/backendv2/tstorebe/store"
    15  	"github.com/decred/politeia/util"
    16  	"github.com/marcopeereboom/sbox"
    17  	"github.com/syndtr/goleveldb/leveldb"
    18  )
    19  
    20  const (
    21  	// encryptionKeyFilename is the filename of the encryption key that
    22  	// is created in the store data directory.
    23  	encryptionKeyFilename = "leveldb-sbox.key"
    24  )
    25  
    26  var (
    27  	_ store.BlobKV = (*localdb)(nil)
    28  )
    29  
    30  // localdb implements the store BlobKV interface using leveldb.
    31  //
    32  // NOTE: this implementation was created for testing. The encryption techniques
    33  // used may not be suitable for a production environment. A random secretbox
    34  // encryption key is created on startup and saved to the politeiad application
    35  // dir. Blobs are encrypted using random 24 byte nonces.
    36  type localdb struct {
    37  	shutdown uint64
    38  	db       *leveldb.DB
    39  	key      [32]byte
    40  }
    41  
    42  func (l *localdb) isShutdown() bool {
    43  	return atomic.LoadUint64(&l.shutdown) != 0
    44  }
    45  
    46  func (l *localdb) encrypt(data []byte) ([]byte, error) {
    47  	return sbox.Encrypt(0, &l.key, data)
    48  }
    49  
    50  func (l *localdb) decrypt(data []byte) ([]byte, uint32, error) {
    51  	return sbox.Decrypt(&l.key, data)
    52  }
    53  
    54  // Put saves the provided key-value entries to the database. New entries are
    55  // inserted. Existing entries are updated.
    56  //
    57  // This operation is atomic.
    58  //
    59  // This function satisfies the store BlobKV interface.
    60  func (l *localdb) Put(blobs map[string][]byte, encrypt bool) error {
    61  	log.Tracef("Put: %v blobs", len(blobs))
    62  
    63  	if l.isShutdown() {
    64  		return store.ErrShutdown
    65  	}
    66  
    67  	// Encrypt blobs
    68  	if encrypt {
    69  		for k, v := range blobs {
    70  			e, err := l.encrypt(v)
    71  			if err != nil {
    72  				return fmt.Errorf("encrypt: %v", err)
    73  			}
    74  			blobs[k] = e
    75  		}
    76  	}
    77  
    78  	// Setup batch
    79  	batch := new(leveldb.Batch)
    80  	for k, v := range blobs {
    81  		batch.Put([]byte(k), v)
    82  	}
    83  
    84  	// Write batch
    85  	err := l.db.Write(batch, nil)
    86  	if err != nil {
    87  		return fmt.Errorf("write batch: %v", err)
    88  	}
    89  
    90  	log.Debugf("Saved blobs (%v) to store", len(blobs))
    91  
    92  	return nil
    93  }
    94  
    95  // Del deletes the key-value entries from the database for the provided keys.
    96  //
    97  // This operation is atomic.
    98  //
    99  // This function satisfies the store BlobKV interface.
   100  func (l *localdb) Del(keys []string) error {
   101  	log.Tracef("Del: %v", keys)
   102  
   103  	if l.isShutdown() {
   104  		return store.ErrShutdown
   105  	}
   106  
   107  	batch := new(leveldb.Batch)
   108  	for _, v := range keys {
   109  		batch.Delete([]byte(v))
   110  	}
   111  	err := l.db.Write(batch, nil)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	log.Debugf("Deleted blobs (%v) from store", len(keys))
   117  
   118  	return nil
   119  }
   120  
   121  // isEncrypted returns whether the provided blob has been prefixed with an sbox
   122  // header, indicating that it is an encrypted blob.
   123  func isEncrypted(b []byte) bool {
   124  	return bytes.HasPrefix(b, []byte("sbox"))
   125  }
   126  
   127  // Get retrieves the key-value entries from the database for the provided keys.
   128  //
   129  // An entry will not exist in the returned map for any blobs that are not
   130  // found. It is the responsibility of the caller to ensure a blob was returned
   131  // for all provided keys.
   132  //
   133  // This function satisfies the store BlobKV interface.
   134  func (l *localdb) Get(keys []string) (map[string][]byte, error) {
   135  	log.Tracef("Get: %v", keys)
   136  
   137  	if l.isShutdown() {
   138  		return nil, store.ErrShutdown
   139  	}
   140  
   141  	// Lookup blobs
   142  	blobs := make(map[string][]byte, len(keys))
   143  	for _, v := range keys {
   144  		b, err := l.db.Get([]byte(v), nil)
   145  		if err != nil {
   146  			if errors.Is(err, leveldb.ErrNotFound) {
   147  				// File does not exist. This is ok.
   148  				continue
   149  			}
   150  			return nil, fmt.Errorf("get %v: %v", v, err)
   151  		}
   152  		blobs[v] = b
   153  	}
   154  
   155  	// Decrypt blobs
   156  	for k, v := range blobs {
   157  		encrypted := isEncrypted(v)
   158  		log.Tracef("Blob is encrypted: %v", encrypted)
   159  		if !encrypted {
   160  			continue
   161  		}
   162  		b, _, err := l.decrypt(v)
   163  		if err != nil {
   164  			return nil, fmt.Errorf("decrypt: %v", err)
   165  		}
   166  		blobs[k] = b
   167  	}
   168  
   169  	return blobs, nil
   170  }
   171  
   172  // Close closes the database connection.
   173  //
   174  // This function satisfies the store BlobKV interface.
   175  func (l *localdb) Close() {
   176  	log.Tracef("Close")
   177  
   178  	atomic.AddUint64(&l.shutdown, 1)
   179  
   180  	// Zero the encryption key
   181  	util.Zero(l.key[:])
   182  
   183  	// Close database
   184  	l.db.Close()
   185  }
   186  
   187  // New returns a new localdb.
   188  func New(appDir, dataDir string) (*localdb, error) {
   189  	// Load encryption key.
   190  	keyFile := filepath.Join(appDir, encryptionKeyFilename)
   191  	key, err := util.LoadEncryptionKey(log, keyFile)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	// Open database
   197  	db, err := leveldb.OpenFile(dataDir, nil)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	// Create context
   203  	ldb := localdb{
   204  		db: db,
   205  	}
   206  	copy(ldb.key[:], key[:])
   207  	util.Zero(key[:])
   208  
   209  	return &ldb, nil
   210  }