"veyron2/security": Added BlessingRoots and BlessingStore
Added the BlessingRoots and BlessingStore interfaces from
https://veyron-review.googlesource.com/#/c/4102/ and
implemented them in veyron/runtimes/google/rt
At the moment the interfaces are not used anywhere, the next CL
will replace PublicIDStore and the current TrustedKeys map
with BlessingStore and BlessingRoots respectively.
Change-Id: I8f16d739098b51cb82ebbd25a17cee30547c80f5
diff --git a/runtimes/google/rt/blessingroots.go b/runtimes/google/rt/blessingroots.go
new file mode 100644
index 0000000..bff2c08
--- /dev/null
+++ b/runtimes/google/rt/blessingroots.go
@@ -0,0 +1,113 @@
+package rt
+
+import (
+ "crypto/sha256"
+ "errors"
+ "sync"
+
+ "veyron/security/serialization"
+
+ "veyron2/security"
+)
+
+const (
+ blessingRootsDataFile = "blessingroots.data"
+ blessingRootsSigFile = "blessingroots.sig"
+)
+
+// blessingRoots implements security.BlessingRoots.
+type blessingRoots struct {
+ store map[string][]security.BlessingPattern // GUARDED_BY(mu)
+ dir string
+ signer serialization.Signer
+ mu sync.RWMutex
+}
+
+func storeMapKey(root security.PublicKey) (string, error) {
+ rootBytes, err := root.MarshalBinary()
+ if err != nil {
+ return "", err
+ }
+ rootBytesHash := sha256.Sum256(rootBytes)
+ return string(rootBytesHash[:]), nil
+}
+
+func (br *blessingRoots) Add(root security.PublicKey, pattern security.BlessingPattern) error {
+ key, err := storeMapKey(root)
+ if err != nil {
+ return err
+ }
+
+ br.mu.Lock()
+ defer br.mu.Unlock()
+ patterns := br.store[key]
+ for _, p := range patterns {
+ if p == pattern {
+ return nil
+ }
+ }
+ br.store[key] = append(patterns, pattern)
+
+ if err := br.save(); err != nil {
+ br.store[key] = patterns[:len(patterns)-1]
+ return err
+ }
+ return nil
+}
+
+func (br *blessingRoots) Recognized(root security.PublicKey, blessing string) error {
+ key, err := storeMapKey(root)
+ if err != nil {
+ return err
+ }
+
+ br.mu.RLock()
+ defer br.mu.RUnlock()
+ for _, p := range br.store[key] {
+ if p.MatchedBy(blessing) {
+ return nil
+ }
+ }
+ return errors.New("PublicKey is not a recognized root for this blessing")
+}
+
+func (br *blessingRoots) save() error {
+ if (br.signer == nil) && (br.dir == "") {
+ return nil
+ }
+ return encodeAndStore(br.store, br.dir, blessingRootsDataFile, blessingRootsSigFile, br.signer)
+}
+
+// NewInMemoryBlessingRoots returns an in-memory security.BlessingRoots.
+//
+// The returned BlessingRoots is initialized with an empty set of keys.
+func NewInMemoryBlessingRoots() security.BlessingRoots {
+ return &blessingRoots{
+ store: make(map[string][]security.BlessingPattern),
+ }
+}
+
+// NewPersistingBlessingRoots returns a security.BlessingRoots that signs
+// and persists all updates to the provided directory. Signing is carried
+// out using the provided signer.
+//
+// The returned BlessingRoots is initialized from the existing data present
+// in the directory. The data is verified to have been written by a persisting
+// BlessingRoots object constructed from the same signer.
+//
+// Any errors obtained in reading or verifying the data are returned.
+func NewPersistingBlessingRoots(directory string, signer serialization.Signer) (security.BlessingRoots, error) {
+ if directory == "" || signer == nil {
+ return nil, errors.New("directory or signer is not specified")
+ }
+ br := &blessingRoots{
+ store: make(map[string][]security.BlessingPattern),
+ dir: directory,
+ signer: signer,
+ }
+
+ if err := decodeFromStorage(&br.store, br.dir, blessingRootsDataFile, blessingRootsSigFile, br.signer.PublicKey()); err != nil {
+ return nil, err
+ }
+ return br, nil
+}
diff --git a/runtimes/google/rt/blessingroots_test.go b/runtimes/google/rt/blessingroots_test.go
new file mode 100644
index 0000000..39d823a
--- /dev/null
+++ b/runtimes/google/rt/blessingroots_test.go
@@ -0,0 +1,134 @@
+package rt
+
+import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "strings"
+ "testing"
+
+ "veyron2/security"
+)
+
+type s []string
+
+type tester struct {
+ k1, k2, k3 security.PublicKey
+}
+
+func (t *tester) testAdd(br security.BlessingRoots) error {
+ testdata := []struct {
+ root security.PublicKey
+ pattern security.BlessingPattern
+ }{
+ {t.k1, "veyron/..."},
+ {t.k2, "google/foo/..."},
+ {t.k1, "google"},
+ }
+ for _, d := range testdata {
+ if err := br.Add(d.root, d.pattern); err != nil {
+ return fmt.Errorf("%v.Add(%v, %q) failed: %s", br, d.root, d.pattern, err)
+ }
+ }
+ return nil
+}
+
+func (t *tester) testRecognized(br security.BlessingRoots) error {
+ testdata := []struct {
+ root security.PublicKey
+ recognized []string
+ notRecognized []string
+ }{
+ {t.k1, s{"veyron", "veyron/foo", "veyron/foo/bar", "google"}, s{"google/foo", "foo", "foo/bar"}},
+ {t.k2, s{"google", "google/foo", "google/foo/bar"}, s{"google/bar", "veyron", "veyron/foo", "foo", "foo/bar"}},
+ {t.k3, s{}, s{"veyron", "veyron/foo", "veyron/bar", "google", "google/foo", "google/bar", "foo", "foo/bar"}},
+ }
+ for _, d := range testdata {
+ for _, b := range d.recognized {
+ if err := br.Recognized(d.root, b); err != nil {
+ return fmt.Errorf("%v.Recognized(%v, %q): got: %v, want nil", br, d.root, b, err)
+ }
+ }
+ for _, b := range d.notRecognized {
+ if err := matchesError(br.Recognized(d.root, b), "not a recognized root"); err != nil {
+ return fmt.Errorf("%v.Recognized(%v, %q): %v", br, d.root, b, err)
+ }
+ }
+ }
+ return nil
+}
+
+func mkKey() security.PublicKey {
+ s, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ panic(err)
+ }
+ return security.NewECDSAPublicKey(&s.PublicKey)
+}
+
+func matchesError(err error, pattern string) error {
+ retErr := fmt.Errorf("got error: %v, want to match: %v", err, pattern)
+ if (len(pattern) == 0) != (err == nil) {
+ return retErr
+ }
+ if (err != nil) && (strings.Index(err.Error(), pattern) < 0) {
+ return retErr
+ }
+ return nil
+}
+
+func TestInMemoryBlessingRoots(t *testing.T) {
+ br := NewInMemoryBlessingRoots()
+ tester := tester{mkKey(), mkKey(), mkKey()}
+ if err := tester.testAdd(br); err != nil {
+ t.Error(err)
+ }
+ if err := tester.testRecognized(br); err != nil {
+ t.Error(err)
+ }
+}
+func TestPersistingBlessingRoots(t *testing.T) {
+ newTempDir := func(name string) string {
+ dir, err := ioutil.TempDir("", name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return dir
+ }
+
+ tester := tester{mkKey(), mkKey(), mkKey()}
+
+ // Create a new persisting BlessingRoots and add key k1 as an authority over
+ // blessings matching "veyron/...".
+ dir := newTempDir("blessingstore")
+ defer os.RemoveAll(dir)
+ signer := newChain("signer")
+ br, err := NewPersistingBlessingRoots(dir, signer)
+ if err != nil {
+ t.Fatalf("NewPersistingBlessingRoots failed: %s", err)
+ }
+
+ if err := tester.testAdd(br); err != nil {
+ t.Error(err)
+ }
+ if err := tester.testRecognized(br); err != nil {
+ t.Error(err)
+ }
+
+ // Test that all mutations are appropriately reflected in a BlessingRoots
+ // constructed from same directory and signer.
+ br, err = NewPersistingBlessingRoots(dir, signer)
+ if err != nil {
+ t.Fatalf("NewPersistingBlessingRoots failed: %s", err)
+ }
+
+ if err := tester.testAdd(br); err != nil {
+ t.Error(err)
+ }
+ if err := tester.testRecognized(br); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/runtimes/google/rt/blessingstore.go b/runtimes/google/rt/blessingstore.go
new file mode 100644
index 0000000..f353111
--- /dev/null
+++ b/runtimes/google/rt/blessingstore.go
@@ -0,0 +1,212 @@
+package rt
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+ "sync"
+
+ isecurity "veyron/runtimes/google/security"
+ "veyron/security/serialization"
+
+ "veyron2/security"
+ "veyron2/vlog"
+)
+
+const (
+ blessingStoreDataFile = "blessingstore.data"
+ blessingStoreSigFile = "blessingstore.sig"
+)
+
+var errStoreAddMismatch = errors.New("blessing's public key does not match store's public key")
+
+type markedBlessings struct {
+ Blessings security.PublicID
+ Patterns []security.BlessingPattern
+}
+
+type persistentState struct {
+ // Store contains a set of Blessings marked with a set of BlessingsPatterns.
+ // These blessings are intended to be shared with peers whose blessings
+ // match the corresponding pattern. All Blessings in the store must have
+ // the same public key.
+ Store []markedBlessings
+ // Default is the default Blessings to be shared with peers for which
+ // no other information is available to select blessings.
+ Default security.PublicID
+}
+
+// blessingStore implements security.BlessingStore.
+type blessingStore struct {
+ state persistentState // GUARDED_BY(mu)
+ publicKey security.PublicKey
+ dir string
+ signer serialization.Signer
+ mu sync.RWMutex
+}
+
+func (s *blessingStore) Add(blessings security.PublicID, forPeers security.BlessingPattern) error {
+ if !reflect.DeepEqual(blessings.PublicKey(), s.publicKey) {
+ return errStoreAddMismatch
+ }
+ if err := validateBlessingPattern(forPeers); err != nil {
+ return err
+ }
+
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ var entry *markedBlessings
+ for i, mb := range s.state.Store {
+ if !reflect.DeepEqual(mb.Blessings, blessings) {
+ continue
+ }
+ entry = &s.state.Store[i]
+ for _, p := range mb.Patterns {
+ if p == forPeers {
+ return nil
+ }
+ }
+ break
+ }
+ if entry == nil {
+ s.state.Store = append(s.state.Store, markedBlessings{blessings, []security.BlessingPattern{forPeers}})
+ } else {
+ entry.Patterns = append(entry.Patterns, forPeers)
+ }
+
+ if err := s.save(); err != nil {
+ if entry == nil {
+ s.state.Store = s.state.Store[:len(s.state.Store)-1]
+ } else {
+ entry.Patterns = entry.Patterns[:len(entry.Patterns)-1]
+ }
+ return err
+ }
+ return nil
+}
+
+func (s *blessingStore) ForPeer(peerBlessings ...string) security.PublicID {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var matchingBlessings []security.PublicID
+ for _, mb := range s.state.Store {
+ for _, p := range mb.Patterns {
+ if p.MatchedBy(peerBlessings...) {
+ matchingBlessings = append(matchingBlessings, mb.Blessings)
+ break
+ }
+ }
+ }
+
+ blessings, err := isecurity.NewSetPublicID(matchingBlessings...)
+ if err != nil {
+ // This case should never be hit.
+ vlog.Errorf("BlessingStore: %s is broken, could not combine PublicIDs from it: %s", s, err)
+ return nil
+ }
+ return blessings
+}
+
+func (s *blessingStore) Default() security.PublicID {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ if s.state.Default != nil {
+ return s.state.Default
+ }
+ return s.ForPeer()
+}
+
+func (s *blessingStore) SetDefault(blessings security.PublicID) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if !reflect.DeepEqual(blessings.PublicKey(), s.publicKey) {
+ return errStoreAddMismatch
+ }
+ oldDefault := s.state.Default
+ s.state.Default = blessings
+ if err := s.save(); err != nil {
+ s.state.Default = oldDefault
+ }
+ return nil
+}
+
+func (s *blessingStore) PublicKey() security.PublicKey {
+ return s.publicKey
+}
+
+func (s *blessingStore) String() string {
+ return fmt.Sprintf("{state: %v, publicKey: %v, dir: %v}", s.state, s.publicKey, s.dir)
+}
+
+func (s *blessingStore) save() error {
+ if (s.signer == nil) && (s.dir == "") {
+ return nil
+ }
+ return encodeAndStore(s.state, s.dir, blessingStoreDataFile, blessingStoreSigFile, s.signer)
+}
+
+// TODO(ataly): Use ValidateBlessingPattern from "veyron2/security" when available.
+func validateBlessingPattern(pattern security.BlessingPattern) error {
+ errInvalidPattern := func(pattern security.BlessingPattern) error {
+ return fmt.Errorf("invalid blessing pattern:%q", pattern)
+ }
+
+ patternParts := strings.Split(string(pattern), security.ChainSeparator)
+ for i, p := range patternParts {
+ if p == "" {
+ return errInvalidPattern(pattern)
+ }
+ glob := string(security.AllPrincipals)
+ if p != glob && strings.Contains(p, glob) {
+ return errInvalidPattern(pattern)
+ }
+ if p == glob && (i < len(patternParts)-1) {
+ return errInvalidPattern(pattern)
+ }
+ }
+ return nil
+}
+
+// NewInMemoryBlessingStore returns an in-memory security.BlessingStore for a
+// principal with the provided PublicKey.
+//
+// The returned BlessingStore is initialized with an empty set of blessings.
+func NewInMemoryBlessingStore(publicKey security.PublicKey) security.BlessingStore {
+ return &blessingStore{
+ publicKey: publicKey,
+ }
+}
+
+// NewPersistingBlessingStore returns a security.BlessingStore for a principal
+// with the provided PublicKey that signs and persists all updates to the
+// specified directory. Signing is carried out using the provided signer.
+//
+// The returned BlessingStore is initialized from the existing data present in
+// the directory. The data is verified to have been written by a persisting
+// BlessingStore object constructed from the same PublicKey and signer.
+//
+// Any errors obtained in reading or verifying the data are returned.
+func NewPersistingBlessingStore(publicKey security.PublicKey, directory string, signer serialization.Signer) (security.BlessingStore, error) {
+ if directory == "" || signer == nil {
+ return nil, errors.New("directory or signer is not specified")
+ }
+ s := &blessingStore{
+ publicKey: publicKey,
+ dir: directory,
+ signer: signer,
+ }
+
+ if err := decodeFromStorage(&s.state, s.dir, blessingStoreDataFile, blessingStoreSigFile, s.signer.PublicKey()); err != nil {
+ return nil, err
+ }
+
+ for _, mb := range s.state.Store {
+ if !reflect.DeepEqual(mb.Blessings.PublicKey(), publicKey) {
+ return nil, fmt.Errorf("directory contains Blessings: %v that are not for the provided PublicKey: %v", mb.Blessings, publicKey)
+ }
+ }
+ return s, nil
+}
diff --git a/runtimes/google/rt/blessingstore_test.go b/runtimes/google/rt/blessingstore_test.go
new file mode 100644
index 0000000..05395f1
--- /dev/null
+++ b/runtimes/google/rt/blessingstore_test.go
@@ -0,0 +1,312 @@
+package rt
+
+import (
+ "bytes"
+ "io/ioutil"
+ "os"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ isecurity "veyron/runtimes/google/security"
+ "veyron2/security"
+ "veyron2/vom"
+)
+
+var (
+ cVeyron = newChain("veyron")
+ cGoogle = newChain("google")
+)
+
+func newChain(name string) security.PrivateID {
+ id, err := isecurity.NewPrivateID(name, nil)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+func newSetPublicID(ids ...security.PublicID) security.PublicID {
+ id, err := isecurity.NewSetPublicID(ids...)
+ if err != nil {
+ panic(err)
+ }
+ return id
+}
+
+func bless(blessee security.PublicID, blessor security.PrivateID, name string, caveats []security.Caveat) security.PublicID {
+ blessed, err := blessor.Bless(blessee, name, 5*time.Minute, caveats)
+ if err != nil {
+ panic(err)
+ }
+ return blessed
+}
+
+func verifyBlessingsAndPublicKey(blessings security.PublicID, wantBlessings []string, wantPublicKey security.PublicKey) bool {
+ if blessings == nil {
+ return len(wantBlessings) == 0
+ }
+ gotBlessings := blessings.Names()
+ sort.Strings(gotBlessings)
+ sort.Strings(wantBlessings)
+ return reflect.DeepEqual(gotBlessings, wantBlessings) && reflect.DeepEqual(blessings.PublicKey(), wantPublicKey)
+}
+
+func TestStoreSetters(t *testing.T) {
+ matchesErrorPattern := func(err error, pattern string) bool {
+ if (len(pattern) == 0) != (err == nil) {
+ return false
+ }
+ return err == nil || strings.Index(err.Error(), pattern) >= 0
+ }
+ var (
+ // test principals
+ cAlice = newChain("alice")
+ cAlicePub = cAlice.PublicID()
+ cBobPub = newChain("bob").PublicID()
+ cVeyronAlice = bless(cAlice.PublicID(), cVeyron, "alice", nil)
+ sAlice = newSetPublicID(cAlicePub, cVeyronAlice)
+
+ pkey = cAlicePub.PublicKey()
+ )
+
+ s := NewInMemoryBlessingStore(pkey)
+
+ testDataForAdd := []struct {
+ blessings []security.PublicID
+ patterns []security.BlessingPattern
+ wantErr string
+ }{
+ {
+ blessings: []security.PublicID{cAlicePub, cVeyronAlice, sAlice},
+ patterns: []security.BlessingPattern{"...", "foo", "foo/bar"},
+ },
+ {
+ blessings: []security.PublicID{cBobPub},
+ patterns: []security.BlessingPattern{"...", "foo", "foo/bar"},
+ wantErr: "public key does not match",
+ },
+ {
+ blessings: []security.PublicID{cAlicePub, cVeyronAlice, sAlice},
+ patterns: []security.BlessingPattern{"", "foo...", "...foo", "/bar", "foo/", "foo/.../bar"},
+ wantErr: "invalid blessing pattern",
+ },
+ }
+ for _, d := range testDataForAdd {
+ for _, b := range d.blessings {
+ for _, p := range d.patterns {
+ if got := s.Add(b, p); !matchesErrorPattern(got, d.wantErr) {
+ t.Errorf("%v.Add(%v, %q): got error: %v, want to match: %q", s, b, p, got, d.wantErr)
+ }
+ }
+ }
+ }
+ testDataForSetDefault := []struct {
+ blessings []security.PublicID
+ wantErr string
+ }{
+ {
+ blessings: []security.PublicID{cAlicePub, cVeyronAlice, sAlice},
+ },
+ {
+ blessings: []security.PublicID{cBobPub},
+ wantErr: "public key does not match",
+ },
+ }
+ for _, d := range testDataForSetDefault {
+ for _, b := range d.blessings {
+ if got := s.SetDefault(b); !matchesErrorPattern(got, d.wantErr) {
+ t.Errorf("%v.SetDefault(%v): got error: %v, want to match: %q", s, b, got, d.wantErr)
+ }
+ }
+ }
+}
+
+func TestStoreGetters(t *testing.T) {
+ add := func(s security.BlessingStore, blessings security.PublicID, forPeers security.BlessingPattern) {
+ if err := s.Add(blessings, forPeers); err != nil {
+ t.Fatalf("%v.Add(%v, %q) failed unexpectedly: %s", s, blessings, forPeers, err)
+ }
+ }
+ var (
+ // test principals
+ cAlice = newChain("alice")
+ cVeyronAlice = bless(cAlice.PublicID(), cVeyron, "alice", nil)
+ cGoogleAlice = bless(cAlice.PublicID(), cGoogle, "alice", nil)
+ sAlice = newSetPublicID(cVeyronAlice, cGoogleAlice)
+
+ pkey = cAlice.PublicID().PublicKey()
+ )
+
+ // Create a new BlessingStore for Alice and add her blessings to it.
+ s := NewInMemoryBlessingStore(pkey)
+
+ add(s, cVeyronAlice, "veyron/foo/...")
+ add(s, cGoogleAlice, "veyron/bar")
+ add(s, sAlice, "google")
+
+ // Test ForPeer
+ testDataForPeer := []struct {
+ peerBlessings []string
+ blessings []string
+ }{
+ {nil, nil},
+ {[]string{"foo"}, nil},
+ {[]string{"google"}, []string{"veyron/alice", "google/alice"}},
+ {[]string{"veyron"}, []string{"veyron/alice", "google/alice"}},
+ {[]string{"google/foo"}, nil},
+ {[]string{"veyron/baz"}, nil},
+ {[]string{"veyron/foo"}, []string{"veyron/alice"}},
+ {[]string{"veyron/bar"}, []string{"google/alice"}},
+ {[]string{"foo", "veyron/bar"}, []string{"google/alice"}},
+ {[]string{"veyron/foo/bar", "veyron/bar"}, []string{"veyron/alice", "google/alice"}},
+ {[]string{"veyron/foo/bar", "veyron/bar/baz"}, []string{"veyron/alice"}},
+ }
+ for _, d := range testDataForPeer {
+ if got := s.ForPeer(d.peerBlessings...); !verifyBlessingsAndPublicKey(got, d.blessings, pkey) {
+ t.Errorf("%v.ForPeer(%v): got: %q, want Blessings: %q", s, d.peerBlessings, got, d.blessings)
+ }
+ }
+
+ // Test Default
+ // Default should return nil as SetDefault has not been invoked and no blessing
+ // has been marked for all peers.
+ if got := s.Default(); got != nil {
+ t.Errorf("%v.Default(): got: %v, want: nil", s, got)
+ }
+
+ // Mark the blessings: cVeyronAlice and cGoogleAlice for all peers, and check that
+ // Default returns the union of those blessings
+ add(s, cVeyronAlice, security.AllPrincipals)
+ add(s, cGoogleAlice, security.AllPrincipals)
+ defaultBlessings := []string{"veyron/alice", "google/alice"}
+ if got := s.Default(); !verifyBlessingsAndPublicKey(got, defaultBlessings, pkey) {
+ t.Errorf("%v.Default(): got: %v, want Blessings: %v", s, got, defaultBlessings)
+ }
+
+ // Set the blessing cVeyronAlice as default and check that Default returns it.
+ if err := s.SetDefault(cVeyronAlice); err != nil {
+ t.Fatalf("%v.SetDefault(%v) failed: %s", s, cVeyronAlice, err)
+ }
+ if got, want := s.Default(), cVeyronAlice; !reflect.DeepEqual(got, want) {
+ t.Errorf("%v.Default(): got: %v, want: %v", s, got, want)
+ }
+}
+
+func TestBlessingStoreDuplicates(t *testing.T) {
+ roundTrip := func(blessings security.PublicID) security.PublicID {
+ var b bytes.Buffer
+ if err := vom.NewEncoder(&b).Encode(blessings); err != nil {
+ t.Fatalf("could not VOM-Encode Blessings: %s", err)
+ }
+ var decodedBlessings security.PublicID
+ if err := vom.NewDecoder(&b).Decode(&decodedBlessings); err != nil {
+ t.Fatalf("could not VOM-Decode Blessings: %s", err)
+ }
+ return decodedBlessings
+ }
+ add := func(s security.BlessingStore, blessings security.PublicID, forPeers security.BlessingPattern) {
+ if err := s.Add(blessings, forPeers); err != nil {
+ t.Fatalf("%v.Add(%v, %q) failed unexpectedly: %s", s, blessings, forPeers, err)
+ }
+ }
+ var (
+ // test principals
+ cAlice = newChain("alice")
+ cVeyronAlice = bless(cAlice.PublicID(), cVeyron, "alice", nil)
+
+ pkey = cAlice.PublicID().PublicKey()
+ )
+
+ // Create a new BlessingStore add the blessings cVeyronAlice to it twice.
+ s := NewInMemoryBlessingStore(pkey)
+ add(s, cVeyronAlice, "google")
+ add(s, roundTrip(cVeyronAlice), "google/foo")
+
+ peer := "google"
+ wantBlessings := []string{"veyron/alice"}
+ if got := s.ForPeer(peer); !verifyBlessingsAndPublicKey(got, wantBlessings, pkey) {
+ t.Errorf("%v.ForPeer(%v): got: %q, want Blessings: %q", s, peer, got, wantBlessings)
+ }
+}
+
+func TestPersistingBlessingStore(t *testing.T) {
+ newTempDir := func(name string) string {
+ dir, err := ioutil.TempDir("", name)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return dir
+ }
+
+ var (
+ signer = newChain("signer")
+
+ cAlice = newChain("alice")
+ cVeyronAlice = bless(cAlice.PublicID(), cVeyron, "alice", nil)
+ cGoogleAlice = bless(cAlice.PublicID(), cGoogle, "alice", nil)
+
+ pkey = cAlice.PublicID().PublicKey()
+ )
+
+ // Create a new persisting BlessingStore.
+ dir := newTempDir("blessingstore")
+ defer os.RemoveAll(dir)
+
+ s, err := NewPersistingBlessingStore(pkey, dir, signer)
+ if err != nil {
+ t.Fatalf("NewPersistingBlessingStore failed: %s", err)
+ }
+ if err := s.Add(cVeyronAlice, "veyron/..."); err != nil {
+ t.Fatalf("%v.Add(%v, ...) failed: %s", s, cVeyronAlice, err)
+ }
+ if err := s.SetDefault(cGoogleAlice); err != nil {
+ t.Fatalf("%v.SetDefault(%v) failed: %s", s, cGoogleAlice, err)
+ }
+
+ // Test that all mutations are appropriately reflected in a BlessingStore constructed
+ // from same public key, directory and signer.
+ s, err = NewPersistingBlessingStore(pkey, dir, signer)
+ if err != nil {
+ t.Fatalf("NewPersistingBlessingStore failed: %s", err)
+ }
+
+ if got, want := s.PublicKey(), pkey; !reflect.DeepEqual(got, want) {
+ t.Errorf("%v.PublicKey(): got: %v, want: %v", s, got, want)
+ }
+
+ testDataForPeer := []struct {
+ peerBlessings []string
+ blessings []string
+ }{
+ {peerBlessings: nil, blessings: nil},
+ {peerBlessings: []string{"google"}, blessings: nil},
+ {peerBlessings: []string{"veyron"}, blessings: []string{"veyron/alice"}},
+ {peerBlessings: []string{"veyron/foo"}, blessings: []string{"veyron/alice"}},
+ {peerBlessings: []string{"google", "veyron/foo"}, blessings: []string{"veyron/alice"}},
+ }
+ for _, d := range testDataForPeer {
+ if got := s.ForPeer(d.peerBlessings...); !verifyBlessingsAndPublicKey(got, d.blessings, pkey) {
+ t.Errorf("%v.ForPeer(%s): got: %q, want Blessings: %q", s, d.peerBlessings, got, d.blessings)
+ }
+ }
+
+ if got, want := s.Default(), cGoogleAlice; !reflect.DeepEqual(got, want) {
+ t.Errorf("%v.Default(): got: %v, want: %v", s, got, want)
+ }
+
+ // Test that constructing a BlesssingStore from the same directory and signer, but for a different
+ // publicKey fails
+ pkey = newChain("irrelevant").PublicID().PublicKey()
+ _, err = NewPersistingBlessingStore(pkey, dir, signer)
+ if err == nil {
+ t.Fatalf("NewPersistingBlessingStore(%v, %v, %v) passed uneexpectedly", pkey, dir, signer)
+ }
+}
+
+func init() {
+ isecurity.TrustIdentityProviders(cVeyron)
+ isecurity.TrustIdentityProviders(cGoogle)
+}
diff --git a/runtimes/google/rt/util.go b/runtimes/google/rt/util.go
new file mode 100644
index 0000000..d8bfbd1
--- /dev/null
+++ b/runtimes/google/rt/util.go
@@ -0,0 +1,67 @@
+package rt
+
+import (
+ "io/ioutil"
+ "os"
+ "path"
+
+ "veyron/security/serialization"
+
+ "veyron2/security"
+ "veyron2/vom"
+)
+
+func encodeAndStore(obj interface{}, dir, dataFile, sigFile string, signer serialization.Signer) error {
+ // Save the object to temporary data and signature files, and then move
+ // those files to the actual data and signature file. This reduces the
+ // risk of loosing all saved data on disk in the event of a Write failure.
+ data, err := ioutil.TempFile(dir, "data")
+ if err != nil {
+ return err
+ }
+ defer os.Remove(data.Name())
+ sig, err := ioutil.TempFile(dir, "sig")
+ if err != nil {
+ return err
+ }
+ defer os.Remove(sig.Name())
+
+ swc, err := serialization.NewSigningWriteCloser(data, sig, signer, nil)
+ if err != nil {
+ return err
+ }
+ if err := vom.NewEncoder(swc).Encode(obj); err != nil {
+ swc.Close()
+ return err
+ }
+ if err := swc.Close(); err != nil {
+ return err
+ }
+
+ if err := os.Rename(data.Name(), path.Join(dir, dataFile)); err != nil {
+ return err
+ }
+ return os.Rename(sig.Name(), path.Join(dir, sigFile))
+}
+
+func decodeFromStorage(obj interface{}, dir, dataFile, sigFile string, publicKey security.PublicKey) error {
+ data, dataErr := os.Open(path.Join(dir, dataFile))
+ defer data.Close()
+ sig, sigErr := os.Open(path.Join(dir, sigFile))
+ defer sig.Close()
+
+ switch {
+ case os.IsNotExist(dataErr) && os.IsNotExist(sigErr):
+ return nil
+ case dataErr != nil:
+ return dataErr
+ case sigErr != nil:
+ return sigErr
+ }
+
+ vr, err := serialization.NewVerifyingReader(data, sig, publicKey)
+ if err != nil {
+ return err
+ }
+ return vom.NewDecoder(vr).Decode(obj)
+}