blob: 47635554c7e19c8cc8b783903ed6f3cd508be538 [file] [log] [blame]
package security
import (
"crypto/ecdsa"
"errors"
"fmt"
"os"
"path"
"reflect"
"sync"
"veyron/security/serialization"
"veyron2/security"
"veyron2/security/wire"
"veyron2/vom"
)
const (
dataFile = "blessingstore.data"
signatureFile = "blessingstore.sig"
)
var (
errStoreAddMismatch = errors.New("public key does not match that of existing PublicIDs in the store")
errNoMatchingIDs = errors.New("no matching PublicIDs")
)
func errCombine(err error) error {
return fmt.Errorf("could not combine matching PublicIDs: %s", err)
}
func saveErr(err error) error {
return fmt.Errorf("could not save PublicIDStore: %s", err)
}
type taggedIDStore map[security.PublicID][]security.BlessingPattern
type persistentState struct {
// Store contains a set of PublicIDs mapped to a set of (peer) patterns. The
// patterns indicate the set of peers against whom the PublicID can be used.
// All PublicIDs in the store must have the same public key.
Store taggedIDStore
// DefaultPattern is the default BlessingPattern to be used to select
// PublicIDs from the store in absence of any other search criterea.
DefaultPattern security.BlessingPattern
}
// publicIDStore implements security.PublicIDStore.
type publicIDStore struct {
state persistentState
publicKey *ecdsa.PublicKey
params *PublicIDStoreParams
mu sync.RWMutex
}
func (s *publicIDStore) addTaggedID(id security.PublicID, peerPattern security.BlessingPattern) ([]security.PublicID, error) {
var updatedIDs []security.PublicID
switch p := id.(type) {
case *setPublicID:
for _, ip := range *p {
ids, err := s.addTaggedID(ip, peerPattern)
if err != nil {
return updatedIDs, err
}
updatedIDs = append(updatedIDs, ids...)
}
default:
// TODO(ataly): Once we get rid of FakePublicID, force this case to be exactly
// *chainPublicID.
for _, pattern := range s.state.Store[id] {
if pattern == peerPattern {
return updatedIDs, nil
}
}
s.state.Store[id] = append(s.state.Store[id], peerPattern)
updatedIDs = append(updatedIDs, id)
}
return updatedIDs, nil
}
func (s *publicIDStore) revert(updatedIDs []security.PublicID) {
for _, id := range updatedIDs {
s.state.Store[id] = s.state.Store[id][:len(s.state.Store[id])-1]
}
}
func (s *publicIDStore) Add(id security.PublicID, peerPattern security.BlessingPattern) error {
s.mu.Lock()
defer s.mu.Unlock()
publicKeyIsNil := s.publicKey == nil
if !publicKeyIsNil && !reflect.DeepEqual(id.PublicKey(), s.publicKey) {
return errStoreAddMismatch
}
if publicKeyIsNil {
s.publicKey = id.PublicKey()
}
updatedIDs, err := s.addTaggedID(id, peerPattern)
if err != nil {
s.revert(updatedIDs)
return err
}
if err := s.save(); err != nil {
s.revert(updatedIDs)
if publicKeyIsNil {
s.publicKey = nil
}
return saveErr(err)
}
return nil
}
func (s *publicIDStore) ForPeer(peer security.PublicID) (security.PublicID, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var matchingIDs []security.PublicID
for id, peerPatterns := range s.state.Store {
for _, peerPattern := range peerPatterns {
if peerPattern.MatchedBy(peer.Names()...) {
matchingIDs = append(matchingIDs, id)
break
}
}
}
id, err := NewSetPublicID(matchingIDs...)
if err != nil {
return nil, errCombine(err)
}
if id == nil {
return nil, errNoMatchingIDs
}
return id, nil
}
func (s *publicIDStore) DefaultPublicID() (security.PublicID, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var matchingIDs []security.PublicID
for id, _ := range s.state.Store {
if s.state.DefaultPattern.MatchedBy(id.Names()...) {
matchingIDs = append(matchingIDs, id)
}
}
id, err := NewSetPublicID(matchingIDs...)
if err != nil {
return nil, errCombine(err)
}
if id == nil {
return nil, errNoMatchingIDs
}
return id, nil
}
func (s *publicIDStore) SetDefaultBlessingPattern(pattern security.BlessingPattern) error {
if err := wire.ValidateBlessingPattern(pattern); err != nil {
return err
}
s.mu.Lock()
defer s.mu.Unlock()
oldPattern := s.state.DefaultPattern
s.state.DefaultPattern = pattern
if err := s.save(); err != nil {
s.state.DefaultPattern = oldPattern
return saveErr(err)
}
return nil
}
func (s *publicIDStore) save() error {
if s.params == nil {
return nil
}
// Save the state to temporary data and signature files, and then move
// those files to the actually data and signature file. This reduces the
// risk of loosing all saved data on disk in the event of a Write failure.
dataPath := path.Join(s.params.Dir, dataFile)
tempDataPath := dataPath + "_tmp"
sigPath := path.Join(s.params.Dir, signatureFile)
tempSigPath := sigPath + "_tmp"
data, err := os.OpenFile(tempDataPath, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer os.Remove(tempDataPath)
sig, err := os.OpenFile(tempSigPath, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
defer os.Remove(tempSigPath)
swc, err := serialization.NewSigningWriteCloser(data, sig, s.params.Signer, nil)
if err != nil {
return err
}
if err := vom.NewEncoder(swc).Encode(s.state); err != nil {
defer swc.Close()
return err
}
if err := swc.Close(); err != nil {
return err
}
if err := os.Rename(tempDataPath, dataPath); err != nil {
return err
}
return os.Rename(tempSigPath, sigPath)
}
func (s *publicIDStore) String() string {
return fmt.Sprintf("{state: %v, params: %v}", s.state, s.params)
}
// PublicIDStoreParams specifies persistent storage where a PublicIDStore can be
// saved and loaded.
type PublicIDStoreParams struct {
// Dir specifies a path to a directory in which a serialized PublicIDStore
// can be saved and loaded.
Dir string
// Signer provides a mechanism to sign and verify the serialized bytes.
Signer security.Signer
}
// NewPublicIDStore returns a security.PublicIDStore based on params.
// * If params is nil, a new store with an empty set of PublicIDs and the default
// pattern "*" (matched by all PublicIDs) is returned. The store only lives in
// memory and is never persisted.
// * If params is non-nil, then a store obtained from the serialized data present
// in params.Dir is returned if the data exists, or else a new store with an
// empty set of PublicIDs and the default pattern "*" is returned. Any subsequent
// modifications to the returned store are always signed (using params.Signer)
// and persisted in params.Dir.
func NewPublicIDStore(params *PublicIDStoreParams) (security.PublicIDStore, error) {
store := &publicIDStore{
state: persistentState{make(taggedIDStore), security.AllPrincipals},
params: params,
}
if store.params == nil {
return store, nil
}
data, dataErr := os.Open(path.Join(store.params.Dir, dataFile))
defer data.Close()
sig, sigErr := os.Open(path.Join(store.params.Dir, signatureFile))
defer sig.Close()
switch {
case os.IsNotExist(dataErr) && os.IsNotExist(sigErr):
// No params exists, returning an empty PublicIDStore.
return store, nil
case dataErr != nil:
return nil, dataErr
case sigErr != nil:
return nil, sigErr
}
vr, err := serialization.NewVerifyingReader(data, sig, store.params.Signer.PublicKey())
if err != nil {
return nil, err
}
if vr == nil {
return nil, errors.New("could not construct VerifyingReader for reading params data")
}
if err := vom.NewDecoder(vr).Decode(&store.state); err != nil {
return nil, err
}
for id, _ := range store.state.Store {
store.publicKey = id.PublicKey()
break
}
return store, nil
}