"x/ref": Bind identityd macaroon to tool's public key

Presently, the macaroon handed out by identityd as part of the
seekblessings flow is a bearer token, i.e., anyone who posseses
the macaroon can use it to obtain a blessing for the name (email)
encapsulated in the macaroon. This makes the macaroon an attractive
target for theft.

Since macaroons are only meant to be used by the principal tool they
need not be bearer tokens. This CL makes identityd bind the macaroon
to the principal tool's public key (provided by the tool as part of the
"seekblessings" request, see CL: 11169), thereby making it a non-bearer-token
and therefore robust against theft. The public key is subsequently
checked when an RPC is made using the macaroon to obtain a blessing.

Change-Id: I03186c88e4cb9bf128eb0b1ce465fff6eb4821fe
diff --git a/services/identity/internal/blesser/macaroon.go b/services/identity/internal/blesser/macaroon.go
index c96c7cf..1cada67 100644
--- a/services/identity/internal/blesser/macaroon.go
+++ b/services/identity/internal/blesser/macaroon.go
@@ -5,7 +5,9 @@
 package blesser
 
 import (
+	"errors"
 	"fmt"
+	"reflect"
 	"time"
 
 	"v.io/x/ref/services/identity"
@@ -45,6 +47,13 @@
 	if secCall.LocalPrincipal() == nil {
 		return empty, fmt.Errorf("server misconfiguration: no authentication happened")
 	}
+	macaroonPublicKey, err := security.UnmarshalPublicKey(m.PublicKey)
+	if err != nil {
+		return empty, fmt.Errorf("failed to unmarshal public key in macaroon: %v", err)
+	}
+	if !reflect.DeepEqual(secCall.RemoteBlessings().PublicKey(), macaroonPublicKey) {
+		return empty, errors.New("remote end's public key does not match public key in macaroon")
+	}
 	if len(m.Caveats) == 0 {
 		m.Caveats = []security.Caveat{security.UnconstrainedUse()}
 	}
diff --git a/services/identity/internal/blesser/macaroon_test.go b/services/identity/internal/blesser/macaroon_test.go
index eee0dab..541f903 100644
--- a/services/identity/internal/blesser/macaroon_test.go
+++ b/services/identity/internal/blesser/macaroon_test.go
@@ -22,6 +22,7 @@
 	var (
 		key            = make([]byte, 16)
 		provider, user = testutil.NewPrincipal(), testutil.NewPrincipal()
+		userKey, _     = user.PublicKey().MarshalBinary()
 		cOnlyMethodFoo = newCaveat(security.NewMethodCaveat("Foo"))
 		ctx, call      = fakeContextAndCall(provider, user)
 	)
@@ -30,12 +31,20 @@
 	}
 	blesser := NewMacaroonBlesserServer(key)
 
-	m := oauth.BlessingMacaroon{Creation: time.Now().Add(-1 * time.Hour), Name: "foo"}
+	m := oauth.BlessingMacaroon{Creation: time.Now().Add(-1 * time.Hour), Name: "foo", PublicKey: userKey}
 	wantErr := "macaroon has expired"
 	if _, err := blesser.Bless(ctx, call, newMacaroon(t, key, m)); err == nil || err.Error() != wantErr {
 		t.Errorf("Bless(...) failed with error: %v, want: %v", err, wantErr)
 	}
-	m = oauth.BlessingMacaroon{Creation: time.Now(), Name: "bugsbunny", Caveats: []security.Caveat{cOnlyMethodFoo}}
+
+	otherKey, _ := testutil.NewPrincipal().PublicKey().MarshalBinary()
+	m = oauth.BlessingMacaroon{Creation: time.Now(), Name: "foo", PublicKey: otherKey}
+	wantErr = "remote end's public key does not match public key in macaroon"
+	if _, err := blesser.Bless(ctx, call, newMacaroon(t, key, m)); err == nil || err.Error() != wantErr {
+		t.Errorf("Bless(...) failed with error: %v, want: %v", err, wantErr)
+	}
+
+	m = oauth.BlessingMacaroon{Creation: time.Now(), PublicKey: userKey, Name: "bugsbunny", Caveats: []security.Caveat{cOnlyMethodFoo}}
 	b, err := blesser.Bless(ctx, call, newMacaroon(t, key, m))
 	if err != nil {
 		t.Errorf("Bless failed: %v", err)
diff --git a/services/identity/internal/oauth/handler.go b/services/identity/internal/oauth/handler.go
index 97e962c..6d339b6 100644
--- a/services/identity/internal/oauth/handler.go
+++ b/services/identity/internal/oauth/handler.go
@@ -89,9 +89,10 @@
 
 // BlessingMacaroon contains the data that is encoded into the macaroon for creating blessings.
 type BlessingMacaroon struct {
-	Creation time.Time
-	Caveats  []security.Caveat
-	Name     string
+	Creation  time.Time
+	Caveats   []security.Caveat
+	Name      string
+	PublicKey []byte // Marshaled public key of the principal tool.
 }
 
 func redirectURL(baseURL, suffix string) string {
@@ -313,6 +314,7 @@
 
 type seekBlessingsMacaroon struct {
 	RedirectURL, State string
+	PublicKey          []byte // Marshaled public key of the principal tool.
 }
 
 func validLoopbackURL(u string) (*url.URL, error) {
@@ -340,9 +342,16 @@
 		util.HTTPBadRequest(w, r, fmt.Errorf("invalid redirect_url: %v", err))
 		return
 	}
+	pubKeyBytes, err := base64.URLEncoding.DecodeString(r.FormValue("public_key"))
+	if err != nil {
+		vlog.Infof("seekBlessings failed: invalid public_key: %v", err)
+		util.HTTPBadRequest(w, r, fmt.Errorf("invalid public_key: %v", err))
+		return
+	}
 	outputMacaroon, err := h.csrfCop.NewToken(w, r, clientIDCookie, seekBlessingsMacaroon{
 		RedirectURL: redirect,
 		State:       r.FormValue("state"),
+		PublicKey:   pubKeyBytes,
 	})
 	if err != nil {
 		vlog.Infof("Failed to create CSRF token[%v] for request %#v", err, r)
@@ -354,6 +363,7 @@
 
 type addCaveatsMacaroon struct {
 	ToolRedirectURL, ToolState, Email string
+	ToolPublicKey                     []byte // Marshaled public key of the principal tool.
 }
 
 func (h *handler) addCaveats(w http.ResponseWriter, r *http.Request) {
@@ -370,6 +380,7 @@
 	outputMacaroon, err := h.csrfCop.NewToken(w, r, clientIDCookie, addCaveatsMacaroon{
 		ToolRedirectURL: inputMacaroon.RedirectURL,
 		ToolState:       inputMacaroon.State,
+		ToolPublicKey:   inputMacaroon.PublicKey,
 		Email:           email,
 	})
 	if err != nil {
@@ -426,9 +437,10 @@
 		return
 	}
 	m := BlessingMacaroon{
-		Creation: time.Now(),
-		Caveats:  caveats,
-		Name:     strings.Join(parts, security.ChainSeparator),
+		Creation:  time.Now(),
+		Caveats:   caveats,
+		Name:      strings.Join(parts, security.ChainSeparator),
+		PublicKey: inputMacaroon.ToolPublicKey,
 	}
 	macBytes, err := vom.Encode(m)
 	if err != nil {