veyron/services/identity: Revocation caveats are issued for every identity and can be revoked from the UI.

* Discharger services run alongside the identity server.
* Revocation manager persists revocation caveat information and ability to revoke them.
* Every Identity gets issued a veryon/services/identity/revocation.RevocationCaveat with their identity.
* Adding CSRF to the revoke page will come in later CL.
* Future UI improvements (showing what has been revoked) will come in a later CL.

Change-Id: I8fbb65c5df85e5cb48ca94293569a8a0ae41f448
diff --git a/services/identity/revocation/bless.go b/services/identity/revocation/bless.go
new file mode 100644
index 0000000..b90a6b0
--- /dev/null
+++ b/services/identity/revocation/bless.go
@@ -0,0 +1,48 @@
+package revocation
+
+import (
+	"fmt"
+	"time"
+
+	"veyron/security/audit"
+	"veyron/security/caveat"
+
+	"veyron2/security"
+)
+
+// Bless creates a blessing on behalf of the identity server.
+func Bless(server security.PrivateID, blessee security.PublicID, email string, duration time.Duration, revocationCaveat security.ThirdPartyCaveat) (security.PublicID, error) {
+	if revocationCaveat != nil {
+		// TODO(suharshs): Extend the duration for blessings with provided revocaionCaveats
+		return server.Bless(blessee, email, duration, []security.ServiceCaveat{caveat.UniversalCaveat(revocationCaveat)})
+	}
+	// return a blessing with a more limited duration, since there is no revocation caveat
+	return server.Bless(blessee, email, duration, nil)
+}
+
+type BlessingAuditEntry struct {
+	Blessee, Blessed security.PublicID
+	Start, End       time.Time
+	RevocationCaveat security.ThirdPartyCaveat
+}
+
+// ReadBlessAuditEntry is for use in the googleauth.handler to parse the arguments to the Bless call in util.Bless.
+func ReadBlessAuditEntry(entry audit.Entry) (BlessingAuditEntry, error) {
+	var blessEntry BlessingAuditEntry
+
+	if len(entry.Arguments) < 4 || len(entry.Results) < 1 {
+		return blessEntry, fmt.Errorf("entry is invalid format")
+	}
+
+	blessEntry.Blessee, _ = entry.Arguments[0].(security.PublicID)
+	blessEntry.Start = entry.Timestamp
+	if duration, ok := entry.Arguments[2].(int64); ok {
+		blessEntry.End = blessEntry.Start.Add(time.Duration(duration))
+	}
+	blessEntry.Blessed, _ = entry.Results[0].(security.PublicID)
+	caveats, _ := entry.Arguments[3].([]security.ServiceCaveat)
+	if len(caveats) > 0 {
+		blessEntry.RevocationCaveat, _ = caveats[0].Caveat.(security.ThirdPartyCaveat)
+	}
+	return blessEntry, nil
+}
diff --git a/services/identity/revocation/bless_test.go b/services/identity/revocation/bless_test.go
new file mode 100644
index 0000000..63dc2bc
--- /dev/null
+++ b/services/identity/revocation/bless_test.go
@@ -0,0 +1,97 @@
+package revocation
+
+import (
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+	"time"
+
+	"veyron/security/audit"
+	"veyron/services/security/discharger"
+
+	"veyron2/rt"
+	"veyron2/security"
+)
+
+type auditor struct {
+	LastEntry audit.Entry
+}
+
+func (a *auditor) Audit(entry audit.Entry) error {
+	a.LastEntry = entry
+	return nil
+}
+
+func newAuditedPrivateID(a *auditor) (security.PrivateID, error) {
+	r, err := rt.New()
+	if err != nil {
+		return nil, err
+	}
+	defer r.Cleanup()
+	id := r.Identity()
+	if err != nil {
+		return nil, err
+	}
+	return audit.NewPrivateID(id, a), err
+}
+
+func TestReadBlessAudit(t *testing.T) {
+	var a auditor
+	var revocationDir = filepath.Join(os.TempDir(), "util_bless_test_dir")
+	os.MkdirAll(revocationDir, 0700)
+	defer os.RemoveAll(revocationDir)
+
+	self, err := newAuditedPrivateID(&a)
+	if err != nil {
+		t.Fatalf("failed to create new audited private id: %v", err)
+	}
+
+	// Test caveat
+	correct_blessee := self.PublicID()
+
+	_, cav, err := discharger.NewRevocationCaveat(self.PublicID(), "")
+	if err != nil {
+		t.Fatalf("discharger.NewRevocationCaveat failed: %v", err)
+	}
+
+	correct_blessed, err := Bless(self, self.PublicID(), "test", time.Second, cav)
+	if err != nil {
+		t.Fatalf("Bless: failed with caveats: %v", err)
+	}
+
+	var blessEntry BlessingAuditEntry
+	blessEntry, err = ReadBlessAuditEntry(a.LastEntry)
+	if err != nil {
+		t.Fatal("ReadBlessAuditEntryFailed %v:", err)
+	}
+	if !reflect.DeepEqual(blessEntry.Blessee, correct_blessee) {
+		t.Errorf("blessee incorrect: expected %v got %v", correct_blessee, blessEntry.Blessee)
+	}
+	if !reflect.DeepEqual(blessEntry.Blessed, correct_blessed) {
+		t.Errorf("blessed incorrect: expected %v got %v", correct_blessed, blessEntry.Blessed)
+	}
+	if blessEntry.RevocationCaveat.ID() != cav.ID() {
+		t.Errorf("caveat ID incorrect: expected %s got %s", cav.ID(), blessEntry.RevocationCaveat.ID())
+	}
+
+	// Test no caveat
+	correct_blessed, err = Bless(self, self.PublicID(), "test", time.Second, nil)
+	if err != nil {
+		t.Fatalf("Bless: failed with no caveats: %v", err)
+	}
+
+	blessEntry, err = ReadBlessAuditEntry(a.LastEntry)
+	if err != nil {
+		t.Fatal("ReadBlessAuditEntryFailed %v:", err)
+	}
+	if !reflect.DeepEqual(blessEntry.Blessee, correct_blessee) {
+		t.Errorf("blessee incorrect: expected %v got %v", correct_blessee, blessEntry.Blessee)
+	}
+	if !reflect.DeepEqual(blessEntry.Blessed, correct_blessed) {
+		t.Errorf("blessed incorrect: expected %v got %v", correct_blessed, blessEntry.Blessed)
+	}
+	if blessEntry.RevocationCaveat != nil {
+		t.Errorf("caveat ID incorrect: expected %s got %s", cav.ID(), blessEntry.RevocationCaveat.ID())
+	}
+}
diff --git a/services/identity/revocation/revocation_manager.go b/services/identity/revocation/revocation_manager.go
new file mode 100644
index 0000000..e37dc90
--- /dev/null
+++ b/services/identity/revocation/revocation_manager.go
@@ -0,0 +1,108 @@
+// Package revocation provides tools to create and manage revocation caveats.
+package revocation
+
+import (
+	"crypto/rand"
+	"encoding/hex"
+	"fmt"
+	"path/filepath"
+	"sync"
+	"time"
+
+	"veyron/security/caveat"
+	"veyron/services/identity/util"
+	"veyron2/security"
+	"veyron2/vom"
+)
+
+// RevocationManager persists information for revocation caveats to provided discharges and allow for future revocations.
+type RevocationManager struct {
+	caveatMap *util.DirectoryStore // Map of blessed identity's caveats.  ThirdPartyCaveatID -> revocationCaveatID
+}
+
+var revocationMap *util.DirectoryStore
+var revocationLock sync.RWMutex
+
+// NewCaveat returns a security.ThirdPartyCaveat for which discharges will be
+// issued iff Revoke has not been called for the returned caveat.
+func (r *RevocationManager) NewCaveat(dischargerID security.PublicID, dischargerLocation string) (security.ThirdPartyCaveat, error) {
+	var revocation [16]byte
+	if _, err := rand.Read(revocation[:]); err != nil {
+		return nil, err
+	}
+	restriction := revocationCaveat(revocation)
+	cav, err := caveat.NewPublicKeyCaveat(restriction, dischargerID, dischargerLocation, security.ThirdPartyRequirements{})
+	if err != nil {
+		return nil, err
+	}
+	if err = r.caveatMap.Put(hex.EncodeToString([]byte(cav.ID())), hex.EncodeToString(revocation[:])); err != nil {
+		return nil, err
+	}
+	return cav, nil
+}
+
+// Revoke disables discharges from being issued for the provided third-party caveat.
+func (r *RevocationManager) Revoke(caveatID security.ThirdPartyCaveatID) error {
+	token, err := r.caveatMap.Get(hex.EncodeToString([]byte(caveatID)))
+	if err != nil {
+		return err
+	}
+	return revocationMap.Put(token, string(time.Now().Unix()))
+}
+
+// Returns true if the provided caveat has been revoked.
+func (r *RevocationManager) IsRevoked(caveatID security.ThirdPartyCaveatID) bool {
+	token, err := r.caveatMap.Get(hex.EncodeToString([]byte(caveatID)))
+	if err == nil {
+		exists, _ := revocationMap.Exists(token)
+		return exists
+	}
+	return false
+}
+
+type revocationCaveat [16]byte
+
+func (cav revocationCaveat) Validate(security.Context) error {
+	revocationLock.RLock()
+	if revocationMap == nil {
+		revocationLock.RUnlock()
+		return fmt.Errorf("missing call to NewRevocationManager")
+	}
+	revocationLock.RUnlock()
+	exists, err := revocationMap.Exists(hex.EncodeToString(cav[:]))
+	if err != nil {
+		return err
+	}
+	if exists {
+		return fmt.Errorf("revoked")
+	}
+	return nil
+}
+
+// NewRevocationManager returns a RevocationManager that persists information about
+// revocationCaveats and allows for revocation and caveat creation.
+// This function can only be called once because of the use of global variables.
+func NewRevocationManager(dir string) (*RevocationManager, error) {
+	revocationLock.Lock()
+	defer revocationLock.Unlock()
+	if revocationMap != nil {
+		return nil, fmt.Errorf("NewRevocationManager can only be called once")
+	}
+	// If empty string return nil revocationManager
+	if len(dir) == 0 {
+		return nil, nil
+	}
+	caveatMap, err := util.NewDirectoryStore(filepath.Join(dir, "caveat_dir"))
+	if err != nil {
+		return nil, err
+	}
+	revocationMap, err = util.NewDirectoryStore(filepath.Join(dir, "revocation_dir"))
+	if err != nil {
+		return nil, err
+	}
+	return &RevocationManager{caveatMap}, nil
+}
+
+func init() {
+	vom.Register(revocationCaveat{})
+}
diff --git a/services/identity/revocation/revoker_test.go b/services/identity/revocation/revoker_test.go
new file mode 100644
index 0000000..036bd37
--- /dev/null
+++ b/services/identity/revocation/revoker_test.go
@@ -0,0 +1,77 @@
+package revocation
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+	services "veyron/services/security"
+	"veyron/services/security/discharger"
+	"veyron2"
+	"veyron2/ipc"
+	"veyron2/naming"
+	"veyron2/rt"
+	"veyron2/security"
+)
+
+func revokerSetup(t *testing.T) (dischargerID security.PublicID, dischargerEndpoint string, revoker *RevocationManager, closeFunc func(), runtime veyron2.Runtime) {
+	var dir = filepath.Join(os.TempDir(), "revoker_test_dir")
+	r := rt.Init()
+	revokerService, err := NewRevocationManager(dir)
+	if err != nil {
+		t.Fatalf("NewRevocationManager failed: %v", err)
+	}
+
+	dischargerServer, err := r.NewServer()
+	if err != nil {
+		t.Fatalf("rt.R().NewServer: %s", err)
+	}
+	dischargerEP, err := dischargerServer.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		t.Fatalf("dischargerServer.Listen failed: %v", err)
+	}
+	dischargerServiceStub := services.NewServerDischarger(discharger.NewDischarger(r.Identity()))
+	if err := dischargerServer.Serve("", ipc.LeafDispatcher(dischargerServiceStub, nil)); err != nil {
+		t.Fatalf("dischargerServer.Serve revoker: %s", err)
+	}
+	return r.Identity().PublicID(),
+		naming.JoinAddressName(dischargerEP.String(), ""),
+		revokerService,
+		func() {
+			defer os.RemoveAll(dir)
+			dischargerServer.Stop()
+		},
+		r
+}
+
+func TestDischargeRevokeDischargeRevokeDischarge(t *testing.T) {
+	dcID, dc, revoker, closeFunc, r := revokerSetup(t)
+	defer closeFunc()
+
+	discharger, err := services.BindDischarger(dc)
+	if err != nil {
+		t.Fatalf("error binding to server: ", err)
+	}
+
+	cav, err := revoker.NewCaveat(dcID, dc)
+	if err != nil {
+		t.Fatalf("failed to create public key caveat: %s", err)
+	}
+
+	var impetus security.DischargeImpetus
+
+	if _, err = discharger.Discharge(r.NewContext(), cav, impetus); err != nil {
+		t.Fatalf("failed to get discharge: %s", err)
+	}
+	if err = revoker.Revoke(cav.ID()); err != nil {
+		t.Fatalf("failed to revoke: %s", err)
+	}
+	if discharge, err := discharger.Discharge(r.NewContext(), cav, impetus); err == nil || discharge != nil {
+		t.Fatalf("got a discharge for a revoked caveat: %s", err)
+	}
+	if err = revoker.Revoke(cav.ID()); err != nil {
+		t.Fatalf("failed to revoke again: %s", err)
+	}
+	if discharge, err := discharger.Discharge(r.NewContext(), cav, impetus); err == nil || discharge != nil {
+		t.Fatalf("got a discharge for a doubly revoked caveat: %s", err)
+	}
+}