453 lines
11 KiB
Go
453 lines
11 KiB
Go
package macaroons
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
|
|
"github.com/lightningnetwork/lnd/kvdb"
|
|
|
|
"github.com/btcsuite/btcwallet/snacl"
|
|
"github.com/btcsuite/btcwallet/walletdb"
|
|
)
|
|
|
|
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")
|
|
|
|
// encryptionKeyID 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.
|
|
encryptionKeyID = []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")
|
|
|
|
// ErrRootKeyBucketNotFound specifies that there is no macaroon root key
|
|
// bucket yet which can/should only happen if the store has been
|
|
// corrupted or was initialized incorrectly.
|
|
ErrRootKeyBucketNotFound = fmt.Errorf("root key bucket not found")
|
|
|
|
// ErrEncKeyNotFound specifies that there was no encryption key found
|
|
// even if one was expected to be generated.
|
|
ErrEncKeyNotFound = fmt.Errorf("macaroon encryption key not found")
|
|
)
|
|
|
|
// 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
|
|
}, func() {})
|
|
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)
|
|
if bucket == nil {
|
|
return ErrRootKeyBucketNotFound
|
|
}
|
|
dbKey := bucket.Get(encryptionKeyID)
|
|
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(encryptionKeyID, encKey.Marshal())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r.encKey = encKey
|
|
return nil
|
|
}, func() {})
|
|
}
|
|
|
|
// ChangePassword decrypts the macaroon root key with the old password and then
|
|
// encrypts it again with the new password.
|
|
func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error {
|
|
// We need the store to already be unlocked. With this we can make sure
|
|
// that there already is a key in the DB.
|
|
if r.encKey == nil {
|
|
return ErrStoreLocked
|
|
}
|
|
|
|
// Check if a nil password has been passed; return an error if so.
|
|
if oldPw == nil || newPw == nil {
|
|
return ErrPasswordRequired
|
|
}
|
|
|
|
return kvdb.Update(r, func(tx kvdb.RwTx) error {
|
|
bucket := tx.ReadWriteBucket(rootKeyBucketName)
|
|
if bucket == nil {
|
|
return ErrRootKeyBucketNotFound
|
|
}
|
|
encKeyDb := bucket.Get(encryptionKeyID)
|
|
rootKeyDb := bucket.Get(DefaultRootKeyID)
|
|
|
|
// Both the encryption key and the root key must be present
|
|
// otherwise we are in the wrong state to change the password.
|
|
if len(encKeyDb) == 0 || len(rootKeyDb) == 0 {
|
|
return ErrEncKeyNotFound
|
|
}
|
|
|
|
// Unmarshal parameters for old encryption key and derive the
|
|
// old key with them.
|
|
encKeyOld := &snacl.SecretKey{}
|
|
err := encKeyOld.Unmarshal(encKeyDb)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = encKeyOld.DeriveKey(&oldPw)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a new encryption key from the new password.
|
|
encKeyNew, err := snacl.NewSecretKey(
|
|
&newPw, scryptN, scryptR, scryptP,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Now try to decrypt the root key with the old encryption key,
|
|
// encrypt it with the new one and then store it in the DB.
|
|
decryptedKey, err := encKeyOld.Decrypt(rootKeyDb)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rootKey := make([]byte, len(decryptedKey))
|
|
copy(rootKey, decryptedKey)
|
|
encryptedKey, err := encKeyNew.Encrypt(rootKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = bucket.Put(DefaultRootKeyID, encryptedKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Finally, store the new encryption key parameters in the DB
|
|
// as well.
|
|
err = bucket.Put(encryptionKeyID, encKeyNew.Marshal())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r.encKey = encKeyNew
|
|
return nil
|
|
}, func() {})
|
|
}
|
|
|
|
// 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 {
|
|
bucket := tx.ReadBucket(rootKeyBucketName)
|
|
if bucket == nil {
|
|
return ErrRootKeyBucketNotFound
|
|
}
|
|
dbKey := bucket.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, encryptionKeyID) {
|
|
return nil, nil, ErrKeyValueForbidden
|
|
}
|
|
|
|
err = kvdb.Update(r, func(tx kvdb.RwTx) error {
|
|
bucket := tx.ReadWriteBucket(rootKeyBucketName)
|
|
if bucket == nil {
|
|
return ErrRootKeyBucketNotFound
|
|
}
|
|
dbKey := bucket.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 new root key, encrypt it,
|
|
// and store it in the bucket.
|
|
newKey, err := generateAndStoreNewRootKey(bucket, id, r.encKey)
|
|
rootKey = newKey
|
|
return err
|
|
}, func() {
|
|
rootKey = nil
|
|
})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return rootKey, id, nil
|
|
}
|
|
|
|
// GenerateNewRootKey generates a new macaroon root key, replacing the previous
|
|
// root key if it existed.
|
|
func (r *RootKeyStorage) GenerateNewRootKey() error {
|
|
// We need the store to already be unlocked. With this we can make sure
|
|
// that there already is a key in the DB that can be replaced.
|
|
if r.encKey == nil {
|
|
return ErrStoreLocked
|
|
}
|
|
return kvdb.Update(r, func(tx kvdb.RwTx) error {
|
|
bucket := tx.ReadWriteBucket(rootKeyBucketName)
|
|
if bucket == nil {
|
|
return ErrRootKeyBucketNotFound
|
|
}
|
|
_, err := generateAndStoreNewRootKey(
|
|
bucket, DefaultRootKeyID, r.encKey,
|
|
)
|
|
return err
|
|
}, func() {})
|
|
}
|
|
|
|
// 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()
|
|
r.encKey = nil
|
|
}
|
|
return r.Backend.Close()
|
|
}
|
|
|
|
// generateAndStoreNewRootKey creates a new random RootKeyLen-byte root key,
|
|
// encrypts it with the given encryption key and stores it in the bucket.
|
|
// Any previously set key will be overwritten.
|
|
func generateAndStoreNewRootKey(bucket walletdb.ReadWriteBucket, id []byte,
|
|
key *snacl.SecretKey) ([]byte, error) {
|
|
|
|
rootKey := make([]byte, RootKeyLen)
|
|
if _, err := io.ReadFull(rand.Reader, rootKey); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
encryptedKey, err := key.Encrypt(rootKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rootKey, bucket.Put(id, encryptedKey)
|
|
}
|
|
|
|
// 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, encryptionKeyID) {
|
|
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, encryptionKeyID) ||
|
|
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
|
|
}, func() {
|
|
rootKeyIDDeleted = nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rootKeyIDDeleted, nil
|
|
}
|