Merge "veyron/services/wspr/wsprd: Added identity store for wspr."
diff --git a/services/wspr/wsprd/lib/identity.go b/services/wspr/wsprd/lib/identity.go
new file mode 100644
index 0000000..823d4d8
--- /dev/null
+++ b/services/wspr/wsprd/lib/identity.go
@@ -0,0 +1,243 @@
+// Implements an identity manager that maps origins to veyron identities. Each instance
+// of wspr is expected to have only one user at a time that will be signed in. In this case,
+// the user means the person using the app. Each user may use many different names, which in
+// practice will be done by having multiple accounts across many identity providers (i.e google,
+// facebook,etc). This is similar to having a master identity that is linked to multiple identities
+// in today's technology. For each app/origin, the user may choose which name to provide that app,
+// which results in a blessed identity for that app. Each blessed identity will have a different
+// private key and ideally, all accounts will share the same private key, but for now they are also
+// separate. The identity manager only serializes the mapping of app to account and the account
+// information, but not the private keys for each app.
+// TODO(bjornick,ataly,ashankar): Have all the accounts share the same private key which will be stored
+// in a TPM, so no private key gets serialized to disk.
+
+package lib
+
+import (
+ "crypto/ecdsa"
+ "crypto/sha256"
+ "io"
+ "sync"
+ "time"
+
+ "veyron2"
+ "veyron2/security"
+ "veyron2/verror"
+ "veyron2/vom"
+)
+
+// permissions is a set of a permissions given to an app, containing the account
+// the app has access to and the caveats associated with it.
+type permissions struct {
+ // The account name that is given to an app.
+ Account string
+ Caveats []security.ServiceCaveat
+}
+
+// persistentState is the state of the manager that will be persisted to disk.
+type persistentState struct {
+ // A mapping of origins to the permissions provide for the origin (such as
+ // caveats and the account given to the origin)
+ Origins map[string]permissions
+
+ // A set of accounts that maps from a name to the account.
+ Accounts map[string]security.PrivateID
+}
+
+// Serializer is a factory for managing the readers and writers used by the IDManager
+// for serialization and deserialization
+type Serializer interface {
+ // DataWriter returns a writer that is used to write the data portion
+ // of the IDManager
+ DataWriter() io.WriteCloser
+
+ // SignatureWriter returns a writer that is used to write the signature
+ // of the serialized data.
+ SignatureWriter() io.WriteCloser
+
+ // DataReader returns a reader that is used to read the serialized data.
+ // If nil is returned, then there is no seralized data to load.
+ DataReader() io.Reader
+
+ // SignatureReader returns a reader that is used to read the signature of the
+ // serialized data. If nil is returned, then there is no signature to load.
+ SignatureReader() io.Reader
+}
+
+var OriginDoesNotExist = verror.NotFoundf("origin not found")
+
+// IDManager manages app identities. We only serialize the accounts associated with
+// this id manager and the mapping of apps to permissions that they were given.
+type IDManager struct {
+ mu sync.Mutex
+ state persistentState
+
+ // The runtime that will be used to create new identities
+ rt veyron2.Runtime
+
+ serializer Serializer
+}
+
+// NewIDManager creates a new IDManager from the reader passed in. serializer can't be nil
+func NewIDManager(rt veyron2.Runtime, serializer Serializer) (*IDManager, error) {
+ result := &IDManager{
+ rt: rt,
+ state: persistentState{
+ Origins: map[string]permissions{},
+ Accounts: map[string]security.PrivateID{},
+ },
+ serializer: serializer,
+ }
+
+ reader := serializer.DataReader()
+ var hadData bool
+ hash := sha256.New()
+ if reader != nil {
+ hadData = true
+ if err := vom.NewDecoder(io.TeeReader(reader, hash)).Decode(&result.state); err != nil {
+ return nil, err
+ }
+
+ }
+ signed := hash.Sum(nil)
+
+ var sig security.Signature
+
+ reader = serializer.SignatureReader()
+
+ var hadSig bool
+ if reader != nil {
+ hadSig = true
+ if err := vom.NewDecoder(serializer.SignatureReader()).Decode(&sig); err != nil {
+ return nil, err
+ }
+ }
+
+ if !hadSig && !hadData {
+ return result, nil
+ }
+
+ if !ecdsa.Verify(rt.Identity().PublicID().PublicKey(), signed, sig.R, sig.S) {
+ return nil, verror.NotAuthorizedf("signature verification failed")
+ }
+
+ return result, nil
+}
+
+// Save serializes the IDManager to the writer.
+func (i *IDManager) save() error {
+ hash := sha256.New()
+ writer := i.serializer.DataWriter()
+
+ if err := vom.NewEncoder(io.MultiWriter(writer, hash)).Encode(i.state); err != nil {
+ return err
+ }
+
+ if err := writer.Close(); err != nil {
+ return err
+ }
+
+ signed := hash.Sum(nil)
+ signature, err := i.rt.Identity().Sign(signed)
+
+ if err != nil {
+ return err
+ }
+
+ writer = i.serializer.SignatureWriter()
+
+ if err := vom.NewEncoder(writer).Encode(signature); err != nil {
+ return err
+ }
+
+ return writer.Close()
+}
+
+// Identity returns the identity for an origin. Returns OriginDoesNotExist if
+// there is no identity for the origin.
+func (i *IDManager) Identity(origin string) (security.PrivateID, error) {
+ i.mu.Lock()
+ defer i.mu.Unlock()
+ perm, found := i.state.Origins[origin]
+ if !found {
+ return nil, OriginDoesNotExist
+ }
+
+ return i.generateBlessedID(origin, perm.Account, perm.Caveats)
+}
+
+// AccountsMatching returns a list of accounts that match the given pattern.
+func (i *IDManager) AccountsMatching(trustedRoot security.PrincipalPattern) []string {
+ i.mu.Lock()
+ defer i.mu.Unlock()
+ result := []string{}
+ for name, id := range i.state.Accounts {
+ if id.PublicID().Match(trustedRoot) {
+ result = append(result, name)
+ }
+ }
+ return result
+}
+
+// AddAccount associates a PrivateID with an account name.
+func (i *IDManager) AddAccount(name string, id security.PrivateID) error {
+ i.mu.Lock()
+ defer i.mu.Unlock()
+ old, existed := i.state.Accounts[name]
+ i.state.Accounts[name] = id
+ if err := i.save(); err != nil {
+ delete(i.state.Accounts, name)
+ if existed {
+ i.state.Accounts[name] = old
+ }
+ return err
+ }
+ return nil
+}
+
+// AddOrigin adds an origin to the manager linked to a the given account.
+func (i *IDManager) AddOrigin(origin string, account string, caveats []security.ServiceCaveat) error {
+ i.mu.Lock()
+ defer i.mu.Unlock()
+ if _, found := i.state.Accounts[account]; !found {
+ return verror.NotFoundf("unknown account %s", account)
+ }
+
+ old, existed := i.state.Origins[origin]
+
+ i.state.Origins[origin] = permissions{account, caveats}
+
+ if err := i.save(); err != nil {
+ delete(i.state.Origins, origin)
+ if existed {
+ i.state.Origins[origin] = old
+ }
+
+ return err
+ }
+
+ return nil
+}
+
+func (i *IDManager) generateBlessedID(name string, account string, caveats []security.ServiceCaveat) (security.PrivateID, error) {
+ blessor := i.state.Accounts[account]
+ if blessor == nil {
+ return nil, verror.NotFoundf("unknown account %s", account)
+ }
+ // The name here is irrelevant, since we'll only be storing the blessed name.
+ blessee, err := i.rt.NewIdentity(name)
+ if err != nil {
+ return nil, err
+ }
+
+ blessed, err := blessor.Bless(blessee.PublicID(), name, 24*time.Hour, caveats)
+
+ if err != nil {
+ return nil, verror.NotAuthorizedf("failed to bless id: %v", err)
+ }
+
+ if blessee, err = blessee.Derive(blessed); err != nil {
+ return nil, verror.Internalf("failed to derive private id: %v", err)
+ }
+ return blessee, nil
+}
diff --git a/services/wspr/wsprd/lib/identity_test.go b/services/wspr/wsprd/lib/identity_test.go
new file mode 100644
index 0000000..b7ccbad
--- /dev/null
+++ b/services/wspr/wsprd/lib/identity_test.go
@@ -0,0 +1,158 @@
+package lib
+
+import (
+ "bytes"
+ "io"
+ "reflect"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ "veyron2"
+ "veyron2/rt"
+ "veyron2/security"
+)
+
+func createChain(r veyron2.Runtime, name string) security.PrivateID {
+ id := r.Identity()
+
+ for _, component := range strings.Split(name, "/") {
+ newID, err := r.NewIdentity(component)
+ if err != nil {
+ panic(err)
+ }
+ if id == nil {
+ id = newID
+ continue
+ }
+ blessedID, err := id.Bless(newID.PublicID(), component, time.Hour, nil)
+ if err != nil {
+ panic(err)
+ }
+ id, err = newID.Derive(blessedID)
+ if err != nil {
+ panic(err)
+ }
+ }
+ return id
+}
+
+type bufferCloser struct {
+ bytes.Buffer
+}
+
+func (*bufferCloser) Close() error {
+ return nil
+}
+
+type serializer struct {
+ data bufferCloser
+ signature bufferCloser
+ hasData bool
+}
+
+func (s *serializer) DataWriter() io.WriteCloser {
+ s.hasData = true
+ s.data.Reset()
+ return &s.data
+}
+
+func (s *serializer) SignatureWriter() io.WriteCloser {
+ s.signature.Reset()
+ return &s.signature
+}
+
+func (s *serializer) DataReader() io.Reader {
+ if s.hasData {
+ return &s.data
+ }
+ return nil
+}
+
+func (s *serializer) SignatureReader() io.Reader {
+ if s.hasData {
+ return &s.signature
+ }
+ return nil
+}
+
+func TestSavingAndFetchingIdentity(t *testing.T) {
+ r := rt.Init()
+ manager, err := NewIDManager(r, &serializer{})
+ if err != nil {
+ t.Fatalf("creating identity manager failed with: %v", err)
+ }
+ manager.AddAccount("google/user1", createChain(r, "google/user1"))
+ if err := manager.AddOrigin("sampleapp.com", "google/user1", nil); err != nil {
+ t.Fatalf("failed to generate id: %v", err)
+ }
+
+ if _, err := manager.Identity("sampleapp.com"); err != nil {
+ t.Errorf("failed to get an identity for sampleapp.com: %v", err)
+ }
+
+ if _, err := manager.Identity("unknown.com"); err != OriginDoesNotExist {
+ t.Error("should not have found an identity for unknown.com")
+ }
+}
+
+func TestAccountsMatching(t *testing.T) {
+ r := rt.Init()
+ topLevelName := r.Identity().PublicID().Names()[0]
+ manager, err := NewIDManager(r, &serializer{})
+ if err != nil {
+ t.Fatalf("creating identity manager failed with: %v", err)
+ }
+ manager.AddAccount("google/user1", createChain(r, "google/user1"))
+ manager.AddAccount("google/user2", createChain(r, "google/user2"))
+ manager.AddAccount("facebook/user1", createChain(r, "facebook/user1"))
+
+ result := manager.AccountsMatching(security.PrincipalPattern(topLevelName + "/google/*"))
+ sort.StringSlice(result).Sort()
+ expected := []string{"google/user1", "google/user2"}
+ if !reflect.DeepEqual(result, expected) {
+ t.Errorf("unexpected result from AccountsMatching, expected :%v, got: %v", expected, result)
+ }
+}
+
+func TestGenerateIDWithUnknownBlesser(t *testing.T) {
+ r := rt.Init()
+ manager, err := NewIDManager(r, &serializer{})
+ if err != nil {
+ t.Fatalf("creating identity manager failed with: %v", err)
+ }
+
+ err = manager.AddOrigin("sampleapp.com", "google/user1", nil)
+
+ if err == nil {
+ t.Errorf("should have failed to generated an id blessed by google/user1")
+ }
+}
+
+func TestSerializingAndDeserializing(t *testing.T) {
+ r := rt.Init()
+ var serializer serializer
+
+ manager, err := NewIDManager(r, &serializer)
+ if err != nil {
+ t.Fatalf("creating identity manager failed with: %v", err)
+ }
+ manager.AddAccount("google/user1", createChain(r, "google/user1"))
+ if err = manager.AddOrigin("sampleapp.com", "google/user1", nil); err != nil {
+ t.Fatalf("failed to generate id: %v", err)
+ }
+
+ newManager, err := NewIDManager(r, &serializer)
+
+ if err != nil {
+ t.Fatalf("failed to deserialize data: %v", err)
+ }
+ if _, err := newManager.Identity("sampleapp.com"); err != nil {
+ t.Errorf("can't find the sampleapp.com identity: %v", err)
+ }
+
+ if err := newManager.AddOrigin("sampleapp2.com", "google/user1", nil); err != nil {
+ t.Errorf("failed to create sampleapp2.com identity: %v", err)
+ }
+}