veyron/security: Provide a common implementation of the BlessingStore
and BlessingRoots interfaces.
This commit moves the BlessingStore and BlessingRoots implementation
from veyron/runtimes/google/rt to veyron/security and provides
simple factory functions for creating a Principal implementation
that uses these (canonical) BlessingStore and BlessingRoots implementations.
As a result, a couple of silly BlessingStore and BlessingRoot implementations
that were used only in tests are now deleted.
Change-Id: Icb0cfe98351b82a59951e1cd1b795007de92abbd
diff --git a/security/acl_authorizer_test.go b/security/acl_authorizer_test.go
index 25615c1..d94964b 100644
--- a/security/acl_authorizer_test.go
+++ b/security/acl_authorizer_test.go
@@ -8,7 +8,6 @@
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/security"
- "veyron.io/veyron/veyron2/security/sectest"
)
// context implements security.Context.
@@ -56,14 +55,6 @@
}
}
-func bless(blesser, blessed security.Principal, with security.Blessings, extension string) security.Blessings {
- b, err := blesser.Bless(blessed.PublicKey(), with, extension, security.UnconstrainedUse())
- if err != nil {
- panic(err)
- }
- return b
-}
-
func testSelfRPCs(t *testing.T, authorizer security.Authorizer) {
_, file, line, _ := runtime.Caller(1)
var (
@@ -258,18 +249,3 @@
testNothingPermitted(t, authorizer)
testSelfRPCs(t, authorizer)
}
-
-func newPrincipal(selfblessing string) (security.Principal, security.Blessings) {
- p, err := sectest.NewPrincipal()
- if err != nil {
- panic(err)
- }
- b, err := p.BlessSelf(selfblessing)
- if err != nil {
- panic(err)
- }
- if err := p.AddToRoots(b); err != nil {
- panic(err)
- }
- return p, b
-}
diff --git a/security/blessingroots.go b/security/blessingroots.go
new file mode 100644
index 0000000..33bb145
--- /dev/null
+++ b/security/blessingroots.go
@@ -0,0 +1,113 @@
+package security
+
+import (
+ "crypto/sha256"
+ "errors"
+ "sync"
+
+ "veyron.io/veyron/veyron/security/serialization"
+
+ "veyron.io/veyron/veyron2/security"
+)
+
+const (
+ blessingRootsDataFile = "blessingroots.data"
+ blessingRootsSigFile = "blessingroots.sig"
+)
+
+// blessingRoots implements security.BlessingRoots.
+type blessingRoots struct {
+ dir string
+ signer serialization.Signer
+ mu sync.RWMutex
+ store map[string][]security.BlessingPattern // GUARDED_BY(mu)
+}
+
+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/security/blessingroots_test.go b/security/blessingroots_test.go
new file mode 100644
index 0000000..c346126
--- /dev/null
+++ b/security/blessingroots_test.go
@@ -0,0 +1,121 @@
+package security
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "testing"
+
+ "veyron.io/veyron/veyron2/security"
+)
+
+type rootsTester [3]security.PublicKey
+
+func newRootsTester() *rootsTester {
+ var tester rootsTester
+ var err error
+ for idx := range tester {
+ if tester[idx], _, err = newKey(); err != nil {
+ panic(err)
+ }
+ }
+ return &tester
+}
+
+func (t *rootsTester) add(br security.BlessingRoots) error {
+ testdata := []struct {
+ root security.PublicKey
+ pattern security.BlessingPattern
+ }{
+ {t[0], "veyron/..."},
+ {t[1], "google/foo/..."},
+ {t[0], "google"},
+ }
+ for _, d := range testdata {
+ if err := br.Add(d.root, d.pattern); err != nil {
+ return fmt.Errorf("Add(%v, %q) failed: %s", d.root, d.pattern, err)
+ }
+ }
+ return nil
+}
+
+func (t *rootsTester) testRecognized(br security.BlessingRoots) error {
+ testdata := []struct {
+ root security.PublicKey
+ recognized []string
+ notRecognized []string
+ }{
+ {
+ root: t[0],
+ recognized: []string{"veyron", "veyron/foo", "veyron/foo/bar", "google"},
+ notRecognized: []string{"google/foo", "foo", "foo/bar"},
+ },
+ {
+ root: t[1],
+ recognized: []string{"google", "google/foo", "google/foo/bar"},
+ notRecognized: []string{"google/bar", "veyron", "veyron/foo", "foo", "foo/bar"},
+ },
+ {
+ root: t[2],
+ recognized: []string{},
+ notRecognized: []string{"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("Recognized(%v, %q): got: %v, want nil", 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("Recognized(%v, %q): %v", d.root, b, err)
+ }
+ }
+ }
+ return nil
+}
+
+func TestBlessingRoots(t *testing.T) {
+ p, err := NewPrincipal()
+ if err != nil {
+ t.Fatal(err)
+ }
+ tester := newRootsTester()
+ if err := tester.add(p.Roots()); err != nil {
+ t.Fatal(err)
+ }
+ if err := tester.testRecognized(p.Roots()); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestBlessingRootsPersistence(t *testing.T) {
+ dir, err := ioutil.TempDir("", "TestBlessingRootsPersistence")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ tester := newRootsTester()
+ p, existed, err := NewPersistentPrincipal(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if existed {
+ t.Fatalf("%q already has a principal", dir)
+ }
+ if err := tester.add(p.Roots()); err != nil {
+ t.Error(err)
+ }
+ if err := tester.testRecognized(p.Roots()); err != nil {
+ t.Error(err)
+ }
+ // Recreate the principal (and thus BlessingRoots)
+ p2, _, err := NewPersistentPrincipal(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := tester.testRecognized(p2.Roots()); err != nil {
+ t.Error(err)
+ }
+}
diff --git a/security/blessingstore.go b/security/blessingstore.go
new file mode 100644
index 0000000..54b1b0b
--- /dev/null
+++ b/security/blessingstore.go
@@ -0,0 +1,164 @@
+package security
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "sync"
+
+ "veyron.io/veyron/veyron/security/serialization"
+
+ "veyron.io/veyron/veyron2/security"
+ "veyron.io/veyron/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 persistentState struct {
+ // Store maps BlessingPatterns to the Blessings object that is to be shared
+ // with peers which present blessings of their own that match the pattern.
+ //
+ // All blessings bind to the same public key.
+ Store map[security.BlessingPattern]security.Blessings
+ // Default is the default Blessings to be shared with peers for which
+ // no other information is available to select blessings.
+ Default security.Blessings
+}
+
+// blessingStore implements security.BlessingStore.
+type blessingStore struct {
+ publicKey security.PublicKey
+ dir string
+ signer serialization.Signer
+ mu sync.RWMutex
+ state persistentState // GUARDED_BY(mu)
+}
+
+func (s *blessingStore) Set(blessings security.Blessings, forPeers security.BlessingPattern) (security.Blessings, error) {
+ if !forPeers.IsValid() {
+ return nil, fmt.Errorf("%q is an invalid BlessingPattern", forPeers)
+ }
+ if blessings != nil && !reflect.DeepEqual(blessings.PublicKey(), s.publicKey) {
+ return nil, errStoreAddMismatch
+ }
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ old, hadold := s.state.Store[forPeers]
+ if blessings != nil {
+ s.state.Store[forPeers] = blessings
+ } else {
+ delete(s.state.Store, forPeers)
+ }
+ if err := s.save(); err != nil {
+ if hadold {
+ s.state.Store[forPeers] = old
+ } else {
+ delete(s.state.Store, forPeers)
+ }
+ return nil, err
+ }
+ return old, nil
+}
+
+func (s *blessingStore) ForPeer(peerBlessings ...string) security.Blessings {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var ret security.Blessings
+ for pattern, blessings := range s.state.Store {
+ if pattern.MatchedBy(peerBlessings...) {
+ if union, err := security.UnionOfBlessings(ret, blessings); err != nil {
+ vlog.Errorf("UnionOfBlessings(%v, %v) failed: %v, dropping the latter from BlessingStore.ForPeers(%v)", ret, blessings, err, peerBlessings)
+ } else {
+ ret = union
+ }
+ }
+ }
+ return ret
+}
+
+func (s *blessingStore) Default() security.Blessings {
+ 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.Blessings) 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)
+}
+
+// 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,
+ state: persistentState{Store: make(map[security.BlessingPattern]security.Blessings)},
+ }
+}
+
+// newPersistingBlessingStore returns a security.BlessingStore for a principal
+// that persists all updates to the specified directory and uses the provided
+// signer to ensure integrity of data read from the filesystem.
+//
+// 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 signer.
+//
+// Any errors obtained in reading or verifying the data are returned.
+func newPersistingBlessingStore(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: signer.PublicKey(),
+ state: persistentState{Store: make(map[security.BlessingPattern]security.Blessings)},
+ dir: directory,
+ signer: signer,
+ }
+
+ if err := decodeFromStorage(&s.state, s.dir, blessingStoreDataFile, blessingStoreSigFile, s.signer.PublicKey()); err != nil {
+ return nil, err
+ }
+
+ for _, b := range s.state.Store {
+ if !reflect.DeepEqual(b.PublicKey(), s.publicKey) {
+ return nil, fmt.Errorf("directory contains Blessings: %v that are not for the provided PublicKey: %v", b, s.publicKey)
+ }
+ }
+ return s, nil
+}
diff --git a/security/blessingstore_test.go b/security/blessingstore_test.go
new file mode 100644
index 0000000..c7152bb
--- /dev/null
+++ b/security/blessingstore_test.go
@@ -0,0 +1,255 @@
+package security
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "reflect"
+ "testing"
+
+ "veyron.io/veyron/veyron2/security"
+)
+
+type storeTester struct {
+ forAll, forFoo, forBar, def security.Blessings
+ other security.Blessings // Blessings bound to a different principal.
+}
+
+func (t *storeTester) testSet(s security.BlessingStore) error {
+ testdata := []struct {
+ blessings security.Blessings
+ pattern security.BlessingPattern
+ wantErr string
+ }{
+ {t.forAll, "...", ""},
+ {t.forFoo, "foo/...", ""},
+ {t.forBar, "bar", ""},
+ {t.other, "...", "public key does not match"},
+ {t.forAll, "", "invalid BlessingPattern"},
+ {t.forAll, "foo...", "invalid BlessingPattern"},
+ {t.forAll, "...foo", "invalid BlessingPattern"},
+ {t.forAll, "foo/.../bar", "invalid BlessingPattern"},
+ }
+ for _, d := range testdata {
+ _, err := s.Set(d.blessings, d.pattern)
+ if merr := matchesError(err, d.wantErr); merr != nil {
+ return fmt.Errorf("Set(%v, %q): %v", d.blessings, d.pattern, merr)
+ }
+ }
+ return nil
+}
+
+func (t *storeTester) testSetDefault(s security.BlessingStore, currentDefault security.Blessings) error {
+ if got := s.Default(); !reflect.DeepEqual(got, currentDefault) {
+ return fmt.Errorf("Default(): got: %v, want: %v", got, currentDefault)
+ }
+ if err := s.SetDefault(t.def); err != nil {
+ return fmt.Errorf("SetDefault(%v): %v", t.def, err)
+ }
+ if got, want := s.Default(), t.def; !reflect.DeepEqual(got, want) {
+ return fmt.Errorf("Default returned %v, want %v", got, want)
+ }
+ // Changing default to an invalid blessing should not affect the existing default.
+ if err := matchesError(s.SetDefault(t.other), "public key does not match"); err != nil {
+ return err
+ }
+ if got, want := s.Default(), t.def; !reflect.DeepEqual(got, want) {
+ return fmt.Errorf("Default returned %v, want %v", got, want)
+ }
+ return nil
+}
+
+func (t *storeTester) testForPeer(s security.BlessingStore) error {
+ testdata := []struct {
+ peers []string
+ blessings security.Blessings
+ }{
+ {nil, t.forAll},
+ {[]string{"baz"}, t.forAll},
+ {[]string{"foo"}, unionOfBlessings(t.forAll, t.forFoo)},
+ {[]string{"bar"}, unionOfBlessings(t.forAll, t.forBar)},
+ {[]string{"foo/foo"}, unionOfBlessings(t.forAll, t.forFoo)},
+ {[]string{"bar/baz"}, t.forAll},
+ {[]string{"foo/foo/bar"}, unionOfBlessings(t.forAll, t.forFoo)},
+ {[]string{"bar/foo", "foo"}, unionOfBlessings(t.forAll, t.forFoo)},
+ {[]string{"bar", "foo"}, unionOfBlessings(t.forAll, t.forFoo, t.forBar)},
+ }
+ for _, d := range testdata {
+ if got, want := s.ForPeer(d.peers...), d.blessings; !reflect.DeepEqual(got, want) {
+ return fmt.Errorf("ForPeer(%v): got: %v, want: %v", d.peers, got, want)
+ }
+ }
+ return nil
+}
+
+func newStoreTester(blessed security.Principal) *storeTester {
+ var (
+ blessing = func(root, extension string) security.Blessings {
+ blesser, err := NewPrincipal()
+ if err != nil {
+ panic(err)
+ }
+ blessing, err := blesser.Bless(blessed.PublicKey(), blessSelf(blesser, root), extension, security.UnconstrainedUse())
+ if err != nil {
+ panic(err)
+ }
+ return blessing
+ }
+ )
+ pother, err := NewPrincipal()
+ if err != nil {
+ panic(err)
+ }
+
+ s := &storeTester{}
+ s.forAll = blessing("bar", "alice")
+ s.forFoo = blessing("foo", "alice")
+ s.forBar = unionOfBlessings(s.forAll, s.forFoo)
+ s.def = blessing("default", "alice")
+ s.other = blessSelf(pother, "other")
+ return s
+}
+
+func TestBlessingStore(t *testing.T) {
+ p, err := NewPrincipal()
+ if err != nil {
+ t.Fatal(err)
+ }
+ tester := newStoreTester(p)
+ s := p.BlessingStore()
+ if err := tester.testSet(s); err != nil {
+ t.Error(err)
+ }
+ if err := tester.testForPeer(s); err != nil {
+ t.Error(err)
+ }
+ if err := tester.testSetDefault(s, tester.forAll); err != nil {
+ t.Error(err)
+ }
+}
+
+func TestBlessingStorePersistence(t *testing.T) {
+ dir, err := ioutil.TempDir("", "TestPersistingBlessingStore")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+ p, existed, err := NewPersistentPrincipal(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if existed {
+ t.Errorf("%q already has a principal in it", dir)
+ }
+ tester := newStoreTester(p)
+ s := p.BlessingStore()
+
+ if err := tester.testSet(s); err != nil {
+ t.Error(err)
+ }
+ if err := tester.testForPeer(s); err != nil {
+ t.Error(err)
+ }
+ if err := tester.testSetDefault(s, tester.forAll); err != nil {
+ t.Error(err)
+ }
+
+ // Recreate the BlessingStore from the directory.
+ p2, _, err := NewPersistentPrincipal(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ s = p2.BlessingStore()
+ if err := tester.testForPeer(s); err != nil {
+ t.Error(err)
+ }
+ if got, want := s.Default(), tester.def; !reflect.DeepEqual(got, want) {
+ t.Fatalf("Default(): got: %v, want: %v", got, want)
+ }
+}
+
+func TestBlessingStoreSetOverridesOldSetting(t *testing.T) {
+ p, err := NewPrincipal()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var (
+ alice = blessSelf(p, "alice")
+ bob = blessSelf(p, "bob")
+ s = p.BlessingStore()
+ )
+ // Set(alice, "alice")
+ // Set(bob, "alice/...")
+ // So, {alice, bob} is shared with "alice", whilst {bob} is shared with "alice/tv"
+ if _, err := s.Set(alice, "alice"); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := s.Set(bob, "alice/..."); err != nil {
+ t.Fatal(err)
+ }
+ if got, want := s.ForPeer("alice"), unionOfBlessings(alice, bob); !reflect.DeepEqual(got, want) {
+ t.Errorf("Got %v, want %v", got, want)
+ }
+ if got, want := s.ForPeer("alice/friend"), bob; !reflect.DeepEqual(got, want) {
+ t.Errorf("Got %v, want %v", got, want)
+ }
+
+ // Clear out the blessing associated with "alice".
+ // Now, bob should be shared with both alice and alice/friend.
+ if _, err := s.Set(nil, "alice"); err != nil {
+ t.Fatal(err)
+ }
+ if got, want := s.ForPeer("alice"), bob; !reflect.DeepEqual(got, want) {
+ t.Errorf("Got %v, want %v", got, want)
+ }
+ if got, want := s.ForPeer("alice/friend"), bob; !reflect.DeepEqual(got, want) {
+ t.Errorf("Got %v, want %v", got, want)
+ }
+
+ // Clearing out an association that doesn't exist should have no effect.
+ if _, err := s.Set(nil, "alice/enemy"); err != nil {
+ t.Fatal(err)
+ }
+ if got, want := s.ForPeer("alice"), bob; !reflect.DeepEqual(got, want) {
+ t.Errorf("Got %v, want %v", got, want)
+ }
+ if got, want := s.ForPeer("alice/friend"), bob; !reflect.DeepEqual(got, want) {
+ t.Errorf("Got %v, want %v", got, want)
+ }
+
+ // Clear everything
+ if _, err := s.Set(nil, "alice/..."); err != nil {
+ t.Fatal(err)
+ }
+ if got := s.ForPeer("alice"); got != nil {
+ t.Errorf("Got %v, want nil", got)
+ }
+ if got := s.ForPeer("alice/friend"); got != nil {
+ t.Errorf("Got %v, want nil", got)
+ }
+}
+
+func TestBlessingStoreSetReturnsOldValue(t *testing.T) {
+ p, err := NewPrincipal()
+ if err != nil {
+ t.Fatal(err)
+ }
+ var (
+ alice = blessSelf(p, "alice")
+ bob = blessSelf(p, "bob")
+ s = p.BlessingStore()
+ )
+
+ if old, err := s.Set(alice, "..."); old != nil || err != nil {
+ t.Errorf("Got (%v, %v)", old, err)
+ }
+ if old, err := s.Set(alice, "..."); !reflect.DeepEqual(old, alice) || err != nil {
+ t.Errorf("Got (%v, %v) want (%v, nil)", old, err, alice)
+ }
+ if old, err := s.Set(bob, "..."); !reflect.DeepEqual(old, alice) || err != nil {
+ t.Errorf("Got (%v, %v) want (%v, nil)", old, err, alice)
+ }
+ if old, err := s.Set(nil, "..."); !reflect.DeepEqual(old, bob) || err != nil {
+ t.Errorf("Got (%v, %v) want (%v, nil)", old, err, bob)
+ }
+}
diff --git a/security/doc.go b/security/doc.go
new file mode 100644
index 0000000..14bd17c
--- /dev/null
+++ b/security/doc.go
@@ -0,0 +1,2 @@
+// Package security provides utility functions for creating/using veyron security primitives.
+package security
diff --git a/security/principal.go b/security/principal.go
new file mode 100644
index 0000000..3f5888e
--- /dev/null
+++ b/security/principal.go
@@ -0,0 +1,100 @@
+package security
+
+import (
+ "crypto/ecdsa"
+ "crypto/elliptic"
+ "crypto/rand"
+ "fmt"
+ "os"
+ "path"
+
+ "veyron.io/veyron/veyron2/security"
+)
+
+const privateKeyFile = "privatekey.pem"
+
+// newKey generates an ECDSA (public, private) key pair..
+func newKey() (security.PublicKey, *ecdsa.PrivateKey, error) {
+ priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+ if err != nil {
+ return nil, nil, err
+ }
+ return security.NewECDSAPublicKey(&priv.PublicKey), priv, nil
+}
+
+// NewPrincipal mints a new private key and generates a principal based on
+// this key, storing its BlessingRoots and BlessingStore in memory.
+func NewPrincipal() (security.Principal, error) {
+ pub, priv, err := newKey()
+ if err != nil {
+ return nil, err
+ }
+ return security.CreatePrincipal(security.NewInMemoryECDSASigner(priv), newInMemoryBlessingStore(pub), newInMemoryBlessingRoots())
+}
+
+// NewPersistentPrincipal reads state for a principal (private key, BlessingRoots, BlessingStore)
+// from the provided directory 'dir' and commits all state changes to the same directory.
+//
+// If the directory does not contain state, then a new private key is minted and all state of
+// the principal is committed to 'dir'. If the directory does not exist, it is created.
+//
+// Returns the security.Principal implementation, a boolean that is set to true iff the directory
+// already contained a principal and any errors in initialization.
+func NewPersistentPrincipal(dir string) (principal security.Principal, existed bool, err error) {
+ if finfo, err := os.Stat(dir); err == nil {
+ if !finfo.IsDir() {
+ return nil, false, fmt.Errorf("%q is not a directory", dir)
+ }
+ } else if err := os.MkdirAll(dir, 0700); err != nil {
+ return nil, false, fmt.Errorf("failed to create %q: %v", dir, err)
+ }
+ key, existed, err := initKey(dir)
+ if err != nil {
+ return nil, false, fmt.Errorf("could not initialize private key from credentials directory %v: %v", dir, err)
+ }
+ signer, err := security.CreatePrincipal(security.NewInMemoryECDSASigner(key), nil, nil)
+ if err != nil {
+ return nil, false, fmt.Errorf("failed to create serialization.Signer: %v", err)
+ }
+ roots, err := newPersistingBlessingRoots(dir, signer)
+ if err != nil {
+ return nil, false, fmt.Errorf("failed to load BlessingRoots from %q: %v", dir, err)
+ }
+ store, err := newPersistingBlessingStore(dir, signer)
+ if err != nil {
+ return nil, false, fmt.Errorf("failed to load BlessingStore from %q: %v", dir, err)
+ }
+ p, err := security.CreatePrincipal(security.NewInMemoryECDSASigner(key), store, roots)
+ return p, existed, err
+}
+
+func initKey(dir string) (*ecdsa.PrivateKey, bool, error) {
+ keyFile := path.Join(dir, privateKeyFile)
+ if f, err := os.Open(keyFile); err == nil {
+ defer f.Close()
+ v, err := LoadPEMKey(f, nil)
+ if err != nil {
+ return nil, false, fmt.Errorf("failed to load PEM data from %q: %v", keyFile, v)
+ }
+ key, ok := v.(*ecdsa.PrivateKey)
+ if !ok {
+ return nil, false, fmt.Errorf("%q contains a %T, not an ECDSA private key", keyFile, v)
+ }
+ return key, true, nil
+ } else if !os.IsNotExist(err) {
+ return nil, false, fmt.Errorf("failed to read %q: %v", keyFile, err)
+ }
+ _, key, err := newKey()
+ if err != nil {
+ return nil, false, fmt.Errorf("failed to generate a private key: %v", err)
+ }
+ f, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE, 0600)
+ if err != nil {
+ return nil, false, fmt.Errorf("failed to open %q for writing: %v", keyFile, err)
+ }
+ defer f.Close()
+ if err := SavePEMKey(f, key, nil); err != nil {
+ return nil, false, fmt.Errorf("failed to save private key to %q: %v", keyFile, err)
+ }
+ return key, false, nil
+}
diff --git a/security/principal_test.go b/security/principal_test.go
new file mode 100644
index 0000000..a8ad464
--- /dev/null
+++ b/security/principal_test.go
@@ -0,0 +1,38 @@
+package security
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+func TestNewPersistentPrincipal(t *testing.T) {
+ // Persistence of the BlessingRoots and BlessingStore objects is
+ // tested in other files. Here just test the persistence of the key.
+ dir, err := ioutil.TempDir("", "TestNewPersistentPrincipal")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(dir)
+
+ p, existed, err := NewPersistentPrincipal(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if existed {
+ t.Fatalf("%q already has data", existed)
+ }
+ message := []byte("this is a test message")
+ sig, err := p.Sign(message)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ p2, _, err := NewPersistentPrincipal(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !sig.Verify(p2.PublicKey(), message) {
+ t.Errorf("p.PublicKey=%v, p2.PublicKey=%v", p.PublicKey(), p2.PublicKey())
+ }
+}
diff --git a/security/storage.go b/security/storage.go
new file mode 100644
index 0000000..0dd755b
--- /dev/null
+++ b/security/storage.go
@@ -0,0 +1,67 @@
+package security
+
+import (
+ "io/ioutil"
+ "os"
+ "path"
+
+ "veyron.io/veyron/veyron/security/serialization"
+
+ "veyron.io/veyron/veyron2/security"
+ "veyron.io/veyron/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)
+}
diff --git a/security/testutil_test.go b/security/testutil_test.go
new file mode 100644
index 0000000..f3c5606
--- /dev/null
+++ b/security/testutil_test.go
@@ -0,0 +1,75 @@
+package security
+
+import (
+ "fmt"
+ "strings"
+
+ "veyron.io/veyron/veyron2/security"
+)
+
+func matchesError(got error, want string) error {
+ if (got == nil) && len(want) == 0 {
+ return nil
+ }
+ if got == nil {
+ return fmt.Errorf("Got nil error, wanted to match %q", want)
+ }
+ if !strings.Contains(got.Error(), want) {
+ return fmt.Errorf("Got error %q, wanted to match %q", got, want)
+ }
+ return nil
+}
+
+func newPrincipal(selfblessings ...string) (security.Principal, security.Blessings) {
+ p, err := NewPrincipal()
+ if err != nil {
+ panic(err)
+ }
+ if len(selfblessings) == 0 {
+ return p, nil
+ }
+ var def security.Blessings
+ for _, str := range selfblessings {
+ b, err := p.BlessSelf(str)
+ if err != nil {
+ panic(err)
+ }
+ if def, err = security.UnionOfBlessings(def, b); err != nil {
+ panic(err)
+ }
+ }
+ if err := p.AddToRoots(def); err != nil {
+ panic(err)
+ }
+ if err := p.BlessingStore().SetDefault(def); err != nil {
+ panic(err)
+ }
+ if _, err := p.BlessingStore().Set(def, security.AllPrincipals); err != nil {
+ panic(err)
+ }
+ return p, def
+}
+
+func bless(blesser, blessed security.Principal, with security.Blessings, extension string) security.Blessings {
+ b, err := blesser.Bless(blessed.PublicKey(), with, extension, security.UnconstrainedUse())
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+func blessSelf(p security.Principal, name string) security.Blessings {
+ b, err := p.BlessSelf(name)
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
+
+func unionOfBlessings(blessings ...security.Blessings) security.Blessings {
+ b, err := security.UnionOfBlessings(blessings...)
+ if err != nil {
+ panic(err)
+ }
+ return b
+}