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.
452 lines
11 KiB
452 lines
11 KiB
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 |
|
}
|
|
|