cmd/principal: Add --file flag to recvblessings and bless.

As per https://github.com/veyron/release-issues/issues/946,
users can now run

principal recvblessings --file foo

The remote_key, remote_token, and principal will be writte to that file,
Then users can invoke

principal bless --file foo extension

to successfully get a blessing.

Change-Id: Icc47c8550f4e5c61e8ff646bf176075e6f453768
diff --git a/cmd/principal/doc.go b/cmd/principal/doc.go
index a461daf..8bdec5f 100644
--- a/cmd/principal/doc.go
+++ b/cmd/principal/doc.go
@@ -184,7 +184,7 @@
     principal --veyron.proxy=proxy recvblessings
 
 The command to be run at the sender is of the form:
-    principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS
+    principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS EXTENSION
 
 The --remote_key flag is used to by the sender to "authenticate" the receiver,
 ensuring it blesses the intended recipient and not any attacker that may have
@@ -194,6 +194,11 @@
 receiver. This helps ensure that the receiver rejects blessings from senders who
 just happened to guess the network address of the 'recvblessings' invocation.
 
+If the --remote_arg_file flag is provided to recvblessings, the remote key,
+remote token and object address of this principal will be written to the
+specified location. This file can be supplied to bless:
+		principal bless --remote_arg_file FILE EXTENSION
+
 Usage:
    principal recvblessings [flags]
 
@@ -201,6 +206,10 @@
  -for_peer=...
    If non-empty, the blessings received will be marked for peers matching this
    pattern in the store
+ -remote_arg_file=
+   If non-empty, the remote key, remote token, and principal will be written to
+   the specified file in a JSON object. This can be provided to 'principal bless
+   --remote_arg_file FILE EXTENSION'.
  -set_default=true
    If true, the blessings received will be set as the default blessing in the
    store
@@ -263,8 +272,11 @@
 dumped to STDOUT but sent to the remote end. Use 'principal help recvblessings'
 for more details on that.
 
+When --remote_arg_file is specified, only the blessing extension is required, as
+all other arguments will be extracted from the specified file.
+
 Usage:
-   principal bless [flags] <principal to bless> <extension>
+   principal bless [flags] [<principal to bless>] <extension>
 
 <principal to bless> represents the principal to be blessed (i.e., whose public
 key will be provided with a name).  This can be either: (a) The directory
@@ -274,6 +286,9 @@
 OR (c) The object name produced by the 'recvblessings' command of this tool
     running on behalf of another principal (if the --remote_key and
     --remote_token flags are specified).
+OR (d) None (if the --remote_arg_file flag is specified, only <extension> should
+be provided
+    to bless).
 
 <extension> is the string extension that will be applied to create the blessing.
 
@@ -282,6 +297,10 @@
    "package/path".CaveatName:VDLExpressionParam to attach to this blessing
  -for=0
    Duration of blessing validity (zero implies no expiration caveat)
+ -remote_arg_file=
+   File containing bless arguments written by 'principal recvblessings
+   -remote_arg_file FILE EXTENSION' command. This can be provided to bless in
+   place of --remote_key, --remote_token, and <principal>.
  -remote_key=
    Public key of the remote principal to bless (obtained from the
    'recvblessings' command run by the remote principal
diff --git a/cmd/principal/main.go b/cmd/principal/main.go
index 8700c37..05c6e2b 100644
--- a/cmd/principal/main.go
+++ b/cmd/principal/main.go
@@ -12,8 +12,10 @@
 	"crypto/rand"
 	"crypto/subtle"
 	"encoding/base64"
+	"encoding/json"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"os"
 	"os/user"
 	"time"
@@ -57,6 +59,7 @@
 	// Flags common to many commands
 	flagAddToRoots      bool
 	flagCreateOverwrite bool
+	flagRemoteArgFile   string
 
 	// Flags for the "recvblessings" command
 	flagRecvBlessingsSetDefault bool
@@ -193,8 +196,11 @@
 bless a principal on a remote machine as well. In this case, the blessing is
 not dumped to STDOUT but sent to the remote end. Use 'principal help
 recvblessings' for more details on that.
+
+When --remote_arg_file is specified, only the blessing extension is required, as all other
+arguments will be extracted from the specified file.
 `,
-		ArgsName: "<principal to bless> <extension>",
+		ArgsName: "[<principal to bless>] <extension>",
 		ArgsLong: `
 <principal to bless> represents the principal to be blessed (i.e., whose public
 key will be provided with a name).  This can be either:
@@ -206,13 +212,19 @@
 (c) The object name produced by the 'recvblessings' command of this tool
     running on behalf of another principal (if the --remote_key and
     --remote_token flags are specified).
+OR
+(d) None (if the --remote_arg_file flag is specified, only <extension> should be provided
+    to bless).
 
 <extension> is the string extension that will be applied to create the
 blessing.
+
 	`,
 		Run: func(cmd *cmdline.Command, args []string) error {
-			if len(args) != 2 {
-				return fmt.Errorf("require exactly two arguments, provided %d", len(args))
+			if len(flagRemoteArgFile) > 0 && len(args) != 1 {
+				return fmt.Errorf("when --remote_arg_file is provided, only <extension> is expected, provided %d", len(args))
+			} else if len(flagRemoteArgFile) == 0 && len(args) != 2 {
+				return fmt.Errorf("require exactly two arguments when --remote_arg_file is not provided, provided %d", len(args))
 			}
 
 			ctx, shutdown := v23.Init()
@@ -241,33 +253,23 @@
 			if len(caveats) == 0 {
 				return errNoCaveats
 			}
-			tobless, extension := args[0], args[1]
-			if (len(flagBlessRemoteKey) == 0) != (len(flagBlessRemoteToken) == 0) {
-				return fmt.Errorf("either both --remote_key and --remote_token should be set, or neither should")
-			}
-			if len(flagBlessRemoteKey) > 0 {
-				// Send blessings to a "server" started by a "recvblessings" command
-				granter := &granter{p, with, extension, caveats, flagBlessRemoteKey}
-				return sendBlessings(ctx, tobless, granter, flagBlessRemoteToken)
-			}
-			// Blessing a principal whose key is available locally.
-			var key security.PublicKey
-			if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
-				other, err := vsecurity.LoadPersistentPrincipal(tobless, nil)
-				if err != nil {
-					if other, err = vsecurity.CreatePersistentPrincipal(tobless, nil); err != nil {
-						return fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
-					}
-				}
-				key = other.PublicKey()
-			} else if other, err := decodeBlessings(tobless); err != nil {
-				return fmt.Errorf("failed to decode blessings in %q: %v", tobless, err)
-			} else {
-				key = other.PublicKey()
-			}
-			blessings, err := p.Bless(key, with, extension, caveats[0], caveats[1:]...)
+
+			tobless, extension, remoteKey, remoteToken, err := blessArgs(args)
 			if err != nil {
-				return fmt.Errorf("Bless(%v, %v, %q, ...) failed: %v", key, with, extension, err)
+				return err
+			}
+
+			// Send blessings to a "server" started by a "recvblessings" command, either
+			// with the --remote_arg_file flag, or with --remote_key and --remote_token flags.
+			if len(remoteKey) > 0 {
+				granter := &granter{p, with, extension, caveats, remoteKey}
+				return blessOverNetwork(ctx, tobless, granter, remoteToken)
+			}
+
+			// Blessing a principal whose key is available locally.
+			blessings, err := blessOverFileSystem(p, tobless, with, extension, caveats)
+			if err != nil {
+				return err
 			}
 			return dumpBlessings(blessings)
 		},
@@ -657,7 +659,7 @@
     principal --veyron.proxy=proxy recvblessings
 
 The command to be run at the sender is of the form:
-    principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS
+    principal bless --remote_key=KEY --remote_token=TOKEN ADDRESS EXTENSION
 
 The --remote_key flag is used to by the sender to "authenticate" the receiver,
 ensuring it blesses the intended recipient and not any attacker that may have
@@ -667,6 +669,12 @@
 receiver. This helps ensure that the receiver rejects blessings from senders
 who just happened to guess the network address of the 'recvblessings'
 invocation.
+
+If the --remote_arg_file flag is provided to recvblessings, the remote key, remote token
+and object address of this principal will be written to the specified location.
+This file can be supplied to bless:
+		principal bless --remote_arg_file FILE EXTENSION
+
 `,
 		Run: func(cmd *cmdline.Command, args []string) error {
 			if len(args) != 0 {
@@ -705,7 +713,15 @@
 			fmt.Println("You may want to adjust flags affecting the caveats on this blessing, for example using")
 			fmt.Println("the --for flag, or change the extension to something more meaningful")
 			fmt.Println()
-			fmt.Printf("principal bless --remote_key=%v --remote_token=%v %v %v\n", p.PublicKey(), service.token, eps[0].Name(), extension)
+			if len(flagRemoteArgFile) > 0 {
+				if err := writeRecvBlessingsInfo(flagRemoteArgFile, p.PublicKey().String(), service.token, eps[0].Name()); err != nil {
+					return fmt.Errorf("failed to write recvblessings info to %v: %v", flagRemoteArgFile, err)
+				}
+				fmt.Printf("make %q accessible to the blesser, possibly by copying the file over and then run:\n", flagRemoteArgFile)
+				fmt.Printf("principal bless --remote_arg_file=%v %v", flagRemoteArgFile, extension)
+			} else {
+				fmt.Printf("principal bless --remote_key=%v --remote_token=%v %v %v\n", p.PublicKey(), service.token, eps[0].Name(), extension)
+			}
 			fmt.Println()
 			fmt.Println("...waiting for sender..")
 			return <-service.notify
@@ -713,6 +729,76 @@
 	}
 )
 
+func blessArgs(args []string) (tobless, extension, remoteKey, remoteToken string, err error) {
+	if len(flagRemoteArgFile) > 0 && (len(flagBlessRemoteKey)+len(flagBlessRemoteToken) > 0) {
+		return "", "", "", "", fmt.Errorf("--remote_key and --remote_token cannot be provided with --remote_arg_file")
+	}
+	if (len(flagBlessRemoteKey) == 0) != (len(flagBlessRemoteToken) == 0) {
+		return "", "", "", "", fmt.Errorf("either both --remote_key and --remote_token should be set, or neither should")
+	}
+
+	if len(flagRemoteArgFile) == 0 {
+		tobless, extension = args[0], args[1]
+		remoteKey = flagBlessRemoteKey
+		remoteToken = flagBlessRemoteToken
+	} else if len(flagRemoteArgFile) > 0 {
+		extension = args[0]
+		remoteKey, remoteToken, tobless, err = blessArgsFromFile(flagRemoteArgFile)
+	}
+	return
+}
+
+func blessOverFileSystem(p security.Principal, tobless string, with security.Blessings, extension string, caveats []security.Caveat) (security.Blessings, error) {
+	var key security.PublicKey
+	if finfo, err := os.Stat(tobless); err == nil && finfo.IsDir() {
+		other, err := vsecurity.LoadPersistentPrincipal(tobless, nil)
+		if err != nil {
+			if other, err = vsecurity.CreatePersistentPrincipal(tobless, nil); err != nil {
+				return security.Blessings{}, fmt.Errorf("failed to read principal in directory %q: %v", tobless, err)
+			}
+		}
+		key = other.PublicKey()
+	} else if other, err := decodeBlessings(tobless); err != nil {
+		return security.Blessings{}, fmt.Errorf("failed to decode blessings in %q: %v", tobless, err)
+	} else {
+		key = other.PublicKey()
+	}
+	return p.Bless(key, with, extension, caveats[0], caveats[1:]...)
+}
+
+type recvBlessingsInfo struct {
+	RemoteKey   string `json:remote_key`
+	RemoteToken string `json:remote_token`
+	Name        string `json:name`
+}
+
+func writeRecvBlessingsInfo(fname string, remoteKey, remoteToken, name string) error {
+	f, err := os.OpenFile(fname, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		return err
+	}
+	b, err := json.Marshal(recvBlessingsInfo{remoteKey, remoteToken, name})
+	if err != nil {
+		return err
+	}
+	if _, err := f.Write(b); err != nil {
+		return err
+	}
+	return nil
+}
+
+func blessArgsFromFile(fname string) (remoteKey, remoteToken, tobless string, err error) {
+	blessJSON, err := ioutil.ReadFile(fname)
+	if err != nil {
+		return "", "", "", err
+	}
+	var binfo recvBlessingsInfo
+	if err := json.Unmarshal(blessJSON, &binfo); err != nil {
+		return "", "", "", err
+	}
+	return binfo.RemoteKey, binfo.RemoteToken, binfo.Name, err
+}
+
 func main() {
 	cmdBlessSelf.Flags.Var(&flagBlessSelfCaveats, "caveat", flagBlessSelfCaveats.usage())
 	cmdBlessSelf.Flags.DurationVar(&flagBlessSelfFor, "for", 0, "Duration of blessing validity (zero implies no expiration)")
@@ -729,6 +815,7 @@
 	cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing blessing to extend")
 	cmdBless.Flags.StringVar(&flagBlessRemoteKey, "remote_key", "", "Public key of the remote principal to bless (obtained from the 'recvblessings' command run by the remote principal")
 	cmdBless.Flags.StringVar(&flagBlessRemoteToken, "remote_token", "", "Token provided by principal running the 'recvblessings' command")
+	cmdBless.Flags.StringVar(&flagRemoteArgFile, "remote_arg_file", "", "File containing bless arguments written by 'principal recvblessings -remote_arg_file FILE EXTENSION' command. This can be provided to bless in place of --remote_key, --remote_token, and <principal>.")
 
 	cmdSeekBlessings.Flags.StringVar(&flagSeekBlessingsFrom, "from", "https://dev.v.io/auth/google", "URL to use to begin the seek blessings process")
 	cmdSeekBlessings.Flags.BoolVar(&flagSeekBlessingsSetDefault, "set_default", true, "If true, the blessings obtained will be set as the default blessing in the store")
@@ -744,6 +831,7 @@
 
 	cmdRecvBlessings.Flags.BoolVar(&flagRecvBlessingsSetDefault, "set_default", true, "If true, the blessings received will be set as the default blessing in the store")
 	cmdRecvBlessings.Flags.StringVar(&flagRecvBlessingsForPeer, "for_peer", string(security.AllPrincipals), "If non-empty, the blessings received will be marked for peers matching this pattern in the store")
+	cmdRecvBlessings.Flags.StringVar(&flagRemoteArgFile, "remote_arg_file", "", "If non-empty, the remote key, remote token, and principal will be written to the specified file in a JSON object. This can be provided to 'principal bless --remote_arg_file FILE EXTENSION'.")
 
 	cmdSet := &cmdline.Command{
 		Name:  "set",
@@ -948,7 +1036,7 @@
 }
 func (*granter) RPCCallOpt() {}
 
-func sendBlessings(ctx *context.T, object string, granter *granter, remoteToken string) error {
+func blessOverNetwork(ctx *context.T, object string, granter *granter, remoteToken string) error {
 	client := v23.GetClient(ctx)
 	// The receiver is being authorized based on the hash of its public key
 	// (see Grant), so it should be fine to ignore the blessing names in the endpoint
diff --git a/cmd/principal/principal_v23_test.go b/cmd/principal/principal_v23_test.go
index 174e224..0f27924 100644
--- a/cmd/principal/principal_v23_test.go
+++ b/cmd/principal/principal_v23_test.go
@@ -12,7 +12,6 @@
 	"path/filepath"
 	"regexp"
 	"strings"
-	"syscall"
 
 	"v.io/x/ref/envvar"
 	"v.io/x/ref/test/v23tests"
@@ -148,14 +147,17 @@
 
 func V23TestRecvBlessings(t *v23tests.T) {
 	var (
-		outputDir = t.NewTempDir()
-		bin       = t.BuildGoPkg("v.io/x/ref/cmd/principal")
-		aliceDir  = filepath.Join(outputDir, "alice")
-		carolDir  = filepath.Join(outputDir, "carol")
+		outputDir    = t.NewTempDir()
+		bin          = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir     = filepath.Join(outputDir, "alice")
+		bobDir       = filepath.Join(outputDir, "bob")
+		carolDir     = filepath.Join(outputDir, "carol")
+		bobBlessFile = filepath.Join(outputDir, "bobBlessInfo")
 	)
 
 	// Generate principals for alice and carol.
 	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
 	bin.Start("create", carolDir, "carol").WaitOrDie(os.Stdout, os.Stderr)
 
 	// Run recvblessings on carol, and have alice send blessings over
@@ -163,27 +165,32 @@
 	var args []string
 	{
 		inv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings")
-		defer inv.Kill(syscall.SIGTERM)
 		args = append([]string{"bless", "--require_caveats=false"}, blessArgsFromRecvBlessings(inv)...)
 		// Replace the random extension suggested by recvblessings with "friend/carol"
 		args[len(args)-1] = "friend/carol"
 	}
-
 	bin.WithEnv(credEnv(aliceDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
 
 	// Run recvblessings on carol, and have alice send blessings over
 	// (blessings received must be set as shareable with peers matching 'alice/...'.)
 	{
 		inv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings", "--for_peer=alice", "--set_default=false")
-		defer inv.Kill(syscall.SIGTERM)
 		// recvblessings suggests a random extension, find the extension and replace it with friend/carol/foralice.
 		args = append([]string{"bless", "--require_caveats=false"}, blessArgsFromRecvBlessings(inv)...)
 		args[len(args)-1] = "friend/carol/foralice"
 	}
 	bin.WithEnv(credEnv(aliceDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
 
+	// Run recvblessings on carol with the --remote_arg_file flag, and have bob send blessings over with the --remote_arg_file flag.
+	{
+		inv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings", "--for_peer=bob", "--set_default=false", "--remote_arg_file="+bobBlessFile)
+		// recvblessings suggests a random extension, use friend/carol/forbob instead.
+		args = append([]string{"bless", "--require_caveats=false"}, blessArgsFromRecvBlessings(inv)...)
+		args[len(args)-1] = "friend/carol/forbob"
+	}
+	bin.WithEnv(credEnv(bobDir)).Start(args...).WaitOrDie(os.Stdout, os.Stderr)
+
 	listenerInv := bin.Start("--veyron.credentials="+carolDir, "--veyron.tcp.address=127.0.0.1:0", "recvblessings", "--for_peer=alice/...", "--set_default=false", "--vmodule=*=2", "--logtostderr")
-	defer listenerInv.Kill(syscall.SIGTERM)
 
 	args = append([]string{"bless", "--require_caveats=false"}, blessArgsFromRecvBlessings(listenerInv)...)
 
@@ -213,8 +220,7 @@
 		}
 	}
 
-	// Dump carol out, the only blessing that survives should be from the
-	// first "bless" command. (alice/friend/carol).
+	// Dump carol out, all three blessings should be in the store.
 	got := removePublicKeys(bin.Start("--veyron.credentials="+carolDir, "dump").Output())
 	want := `Public key : XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
 ---------------- BlessingStore ----------------
@@ -222,9 +228,11 @@
 Peer pattern                   : Blessings
 ...                            : alice/friend/carol
 alice                          : alice/friend/carol/foralice
+bob                            : bob/friend/carol/forbob
 ---------------- BlessingRoots ----------------
 Public key                                      : Pattern
 XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [alice]
+XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [bob]
 XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX : [carol]
 `
 	if want != got {