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 }