| 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.PrincipalPattern |
| |
| 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 PrincipalPattern to be used to select |
| // PublicIDs from the store in absence of any other search criterea. |
| DefaultPattern security.PrincipalPattern |
| } |
| |
| // 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.PrincipalPattern) ([]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...) |
| } |
| case *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) |
| default: |
| return updatedIDs, fmt.Errorf("PublicID of type %T cannot be added to the store", 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.PrincipalPattern) 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 security.Matches(peer, peerPattern) { |
| 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 security.Matches(id, s.state.DefaultPattern) { |
| 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) SetDefaultPrincipalPattern(pattern security.PrincipalPattern) error { |
| if err := wire.ValidatePrincipalPattern(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 |
| } |