You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
319 lines
7.6 KiB
319 lines
7.6 KiB
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 |
|
}, func() { |
|
rootKey = 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) |
|
}, func() { |
|
rootKeySlice = nil |
|
}) |
|
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 |
|
}
|
|
|