lnd.xprv/macaroons/store.go
2020-08-11 19:17:48 +08:00

316 lines
7.5 KiB
Go

package macaroons
import (
"bytes"
"context"
"crypto/rand"
"fmt"
"io"
"sync"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
"github.com/btcsuite/btcwallet/snacl"
)
const (
// RootKeyLen is the length of a root key.
RootKeyLen = 32
)
var (
// rootKeyBucketName is the name of the root key store bucket.
rootKeyBucketName = []byte("macrootkeys")
// DefaultRootKeyID is the ID of the default root key. The first is
// just 0, to emulate the memory storage that comes with bakery.
DefaultRootKeyID = []byte("0")
// encryptedKeyID is the name of the database key that stores the
// encryption key, encrypted with a salted + hashed password. The
// format is 32 bytes of salt, and the rest is encrypted key.
encryptedKeyID = []byte("enckey")
// ErrAlreadyUnlocked specifies that the store has already been
// unlocked.
ErrAlreadyUnlocked = fmt.Errorf("macaroon store already unlocked")
// ErrStoreLocked specifies that the store needs to be unlocked with
// a password.
ErrStoreLocked = fmt.Errorf("macaroon store is locked")
// ErrPasswordRequired specifies that a nil password has been passed.
ErrPasswordRequired = fmt.Errorf("a non-nil password is required")
// ErrKeyValueForbidden is used when the root key ID uses encryptedKeyID as
// its value.
ErrKeyValueForbidden = fmt.Errorf("root key ID value is not allowed")
)
// RootKeyStorage implements the bakery.RootKeyStorage interface.
type RootKeyStorage struct {
kvdb.Backend
encKeyMtx sync.RWMutex
encKey *snacl.SecretKey
}
// NewRootKeyStorage creates a RootKeyStorage instance.
// TODO(aakselrod): Add support for encryption of data with passphrase.
func NewRootKeyStorage(db kvdb.Backend) (*RootKeyStorage, error) {
// If the store's bucket doesn't exist, create it.
err := kvdb.Update(db, func(tx kvdb.RwTx) error {
_, err := tx.CreateTopLevelBucket(rootKeyBucketName)
return err
})
if err != nil {
return nil, err
}
// Return the DB wrapped in a RootKeyStorage object.
return &RootKeyStorage{Backend: db, encKey: nil}, nil
}
// CreateUnlock sets an encryption key if one is not already set, otherwise it
// checks if the password is correct for the stored encryption key.
func (r *RootKeyStorage) CreateUnlock(password *[]byte) error {
r.encKeyMtx.Lock()
defer r.encKeyMtx.Unlock()
// Check if we've already unlocked the store; return an error if so.
if r.encKey != nil {
return ErrAlreadyUnlocked
}
// Check if a nil password has been passed; return an error if so.
if password == nil {
return ErrPasswordRequired
}
return kvdb.Update(r, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName)
dbKey := bucket.Get(encryptedKeyID)
if len(dbKey) > 0 {
// We've already stored a key, so try to unlock with
// the password.
encKey := &snacl.SecretKey{}
err := encKey.Unmarshal(dbKey)
if err != nil {
return err
}
err = encKey.DeriveKey(password)
if err != nil {
return err
}
r.encKey = encKey
return nil
}
// We haven't yet stored a key, so create a new one.
encKey, err := snacl.NewSecretKey(
password, scryptN, scryptR, scryptP,
)
if err != nil {
return err
}
err = bucket.Put(encryptedKeyID, encKey.Marshal())
if err != nil {
return err
}
r.encKey = encKey
return nil
})
}
// Get implements the Get method for the bakery.RootKeyStorage interface.
func (r *RootKeyStorage) Get(_ context.Context, id []byte) ([]byte, error) {
r.encKeyMtx.RLock()
defer r.encKeyMtx.RUnlock()
if r.encKey == nil {
return nil, ErrStoreLocked
}
var rootKey []byte
err := kvdb.View(r, func(tx kvdb.RTx) error {
dbKey := tx.ReadBucket(rootKeyBucketName).Get(id)
if len(dbKey) == 0 {
return fmt.Errorf("root key with id %s doesn't exist",
string(id))
}
decKey, err := r.encKey.Decrypt(dbKey)
if err != nil {
return err
}
rootKey = make([]byte, len(decKey))
copy(rootKey[:], decKey)
return nil
})
if err != nil {
return nil, err
}
return rootKey, nil
}
// RootKey implements the RootKey method for the bakery.RootKeyStorage
// interface.
func (r *RootKeyStorage) RootKey(ctx context.Context) ([]byte, []byte, error) {
r.encKeyMtx.RLock()
defer r.encKeyMtx.RUnlock()
if r.encKey == nil {
return nil, nil, ErrStoreLocked
}
var rootKey []byte
// Read the root key ID from the context. If no key is specified in the
// context, an error will be returned.
id, err := RootKeyIDFromContext(ctx)
if err != nil {
return nil, nil, err
}
if bytes.Equal(id, encryptedKeyID) {
return nil, nil, ErrKeyValueForbidden
}
err = kvdb.Update(r, func(tx kvdb.RwTx) error {
ns := tx.ReadWriteBucket(rootKeyBucketName)
dbKey := ns.Get(id)
// If there's a root key stored in the bucket, decrypt it and
// return it.
if len(dbKey) != 0 {
decKey, err := r.encKey.Decrypt(dbKey)
if err != nil {
return err
}
rootKey = make([]byte, len(decKey))
copy(rootKey[:], decKey[:])
return nil
}
// Otherwise, create a RootKeyLen-byte root key, encrypt it,
// and store it in the bucket.
rootKey = make([]byte, RootKeyLen)
if _, err := io.ReadFull(rand.Reader, rootKey[:]); err != nil {
return err
}
encKey, err := r.encKey.Encrypt(rootKey)
if err != nil {
return err
}
return ns.Put(id, encKey)
})
if err != nil {
return nil, nil, err
}
return rootKey, id, nil
}
// Close closes the underlying database and zeroes the encryption key stored
// in memory.
func (r *RootKeyStorage) Close() error {
r.encKeyMtx.Lock()
defer r.encKeyMtx.Unlock()
if r.encKey != nil {
r.encKey.Zero()
}
return r.Backend.Close()
}
// ListMacaroonIDs returns all the root key ID values except the value of
// encryptedKeyID.
func (r *RootKeyStorage) ListMacaroonIDs(_ context.Context) ([][]byte, error) {
r.encKeyMtx.RLock()
defer r.encKeyMtx.RUnlock()
// Check it's unlocked.
if r.encKey == nil {
return nil, ErrStoreLocked
}
var rootKeySlice [][]byte
// Read all the items in the bucket and append the keys, which are the
// root key IDs we want.
err := kvdb.View(r, func(tx kvdb.RTx) error {
// appendRootKey is a function closure that appends root key ID
// to rootKeySlice.
appendRootKey := func(k, _ []byte) error {
// Only append when the key value is not encryptedKeyID.
if !bytes.Equal(k, encryptedKeyID) {
rootKeySlice = append(rootKeySlice, k)
}
return nil
}
return tx.ReadBucket(rootKeyBucketName).ForEach(appendRootKey)
})
if err != nil {
return nil, err
}
return rootKeySlice, nil
}
// DeleteMacaroonID removes one specific root key ID. If the root key ID is
// found and deleted, it will be returned.
func (r *RootKeyStorage) DeleteMacaroonID(
_ context.Context, rootKeyID []byte) ([]byte, error) {
r.encKeyMtx.RLock()
defer r.encKeyMtx.RUnlock()
// Check it's unlocked.
if r.encKey == nil {
return nil, ErrStoreLocked
}
// Check the rootKeyID is not empty.
if len(rootKeyID) == 0 {
return nil, ErrMissingRootKeyID
}
// Deleting encryptedKeyID or DefaultRootKeyID is not allowed.
if bytes.Equal(rootKeyID, encryptedKeyID) ||
bytes.Equal(rootKeyID, DefaultRootKeyID) {
return nil, ErrDeletionForbidden
}
var rootKeyIDDeleted []byte
err := kvdb.Update(r, func(tx kvdb.RwTx) error {
bucket := tx.ReadWriteBucket(rootKeyBucketName)
// Check the key can be found. If not, return nil.
if bucket.Get(rootKeyID) == nil {
return nil
}
// Once the key is found, we do the deletion.
if err := bucket.Delete(rootKeyID); err != nil {
return err
}
rootKeyIDDeleted = rootKeyID
return nil
})
if err != nil {
return nil, err
}
return rootKeyIDDeleted, nil
}