6e3abdfd14
This commit is a precautionary commit put in place in order to ensure that the logic of macaroon retrieval doesn’t run into a bug triggered by returning a reference into bolt’s active memory map. This can arise if one returns a pointer directly read from the database. We seek to avoid this by instead ensuring all byte slices are fully copied before returning.
158 lines
3.8 KiB
Go
158 lines
3.8 KiB
Go
package macaroons
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/boltdb/bolt"
|
|
)
|
|
|
|
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.
|
|
//
|
|
// TODO(aakselrod): Add support for key rotation.
|
|
defaultRootKeyID = "0"
|
|
|
|
// macaroonBucketName is the name of the macaroon store bucket.
|
|
macaroonBucketName = []byte("macaroons")
|
|
)
|
|
|
|
// RootKeyStorage implements the bakery.RootKeyStorage interface.
|
|
type RootKeyStorage struct {
|
|
*bolt.DB
|
|
}
|
|
|
|
// NewRootKeyStorage creates a RootKeyStorage instance.
|
|
// TODO(aakselrod): Add support for encryption of data with passphrase.
|
|
func NewRootKeyStorage(db *bolt.DB) (*RootKeyStorage, error) {
|
|
// If the store's bucket doesn't exist, create it.
|
|
err := db.Update(func(tx *bolt.Tx) error {
|
|
_, err := tx.CreateBucketIfNotExists(rootKeyBucketName)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Return the DB wrapped in a RootKeyStorage object.
|
|
return &RootKeyStorage{db}, nil
|
|
}
|
|
|
|
// Get implements the Get method for the bakery.RootKeyStorage interface.
|
|
func (r *RootKeyStorage) Get(id string) ([]byte, error) {
|
|
var rootKey []byte
|
|
err := r.View(func(tx *bolt.Tx) error {
|
|
dbKey := tx.Bucket(rootKeyBucketName).Get([]byte(id))
|
|
if len(dbKey) == 0 {
|
|
return fmt.Errorf("root key with id %s doesn't exist",
|
|
id)
|
|
}
|
|
|
|
rootKey = make([]byte, len(dbKey))
|
|
copy(rootKey[:], dbKey)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rootKey, nil
|
|
}
|
|
|
|
// RootKey implements the RootKey method for the bakery.RootKeyStorage
|
|
// interface.
|
|
// TODO(aakselrod): Add support for key rotation.
|
|
func (r *RootKeyStorage) RootKey() ([]byte, string, error) {
|
|
var rootKey []byte
|
|
id := defaultRootKeyID
|
|
err := r.Update(func(tx *bolt.Tx) error {
|
|
ns := tx.Bucket(rootKeyBucketName)
|
|
rootKey = ns.Get([]byte(id))
|
|
|
|
// If there's no root key stored in the bucket yet, create one.
|
|
if len(rootKey) != 0 {
|
|
return nil
|
|
}
|
|
|
|
// Create a RootKeyLen-byte root key.
|
|
rootKey = make([]byte, RootKeyLen)
|
|
if _, err := io.ReadFull(rand.Reader, rootKey[:]); err != nil {
|
|
return err
|
|
}
|
|
return ns.Put([]byte(id), rootKey)
|
|
})
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
return rootKey, id, nil
|
|
}
|
|
|
|
// Storage implements the bakery.Storage interface.
|
|
type Storage struct {
|
|
*bolt.DB
|
|
}
|
|
|
|
// NewStorage creates a Storage instance.
|
|
//
|
|
// TODO(aakselrod): Add support for encryption of data with passphrase.
|
|
func NewStorage(db *bolt.DB) (*Storage, error) {
|
|
// If the store's bucket doesn't exist, create it.
|
|
err := db.Update(func(tx *bolt.Tx) error {
|
|
_, err := tx.CreateBucketIfNotExists(macaroonBucketName)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Return the DB wrapped in a Storage object.
|
|
return &Storage{db}, nil
|
|
}
|
|
|
|
// Put implements the Put method for the bakery.Storage interface.
|
|
func (s *Storage) Put(location string, item string) error {
|
|
return s.Update(func(tx *bolt.Tx) error {
|
|
return tx.Bucket(macaroonBucketName).Put([]byte(location),
|
|
[]byte(item))
|
|
})
|
|
}
|
|
|
|
// Get implements the Get method for the bakery.Storage interface.
|
|
func (s *Storage) Get(location string) (string, error) {
|
|
var item []byte
|
|
err := s.View(func(tx *bolt.Tx) error {
|
|
itemBytes := tx.Bucket(macaroonBucketName).Get([]byte(location))
|
|
if len(itemBytes) == 0 {
|
|
return fmt.Errorf("couldn't get item for location %s",
|
|
location)
|
|
}
|
|
|
|
item = make([]byte, len(itemBytes))
|
|
copy(item, itemBytes)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(item), nil
|
|
}
|
|
|
|
// Del implements the Del method for the bakery.Storage interface.
|
|
func (s *Storage) Del(location string) error {
|
|
return s.Update(func(tx *bolt.Tx) error {
|
|
return tx.Bucket(macaroonBucketName).Delete([]byte(location))
|
|
})
|
|
}
|