ref/cmd/principal: Add more blessing information getters.

This closes veyron/release-issues#991.

This change add the following commands to make driling into
blessings more command line friendly:
(1) 'principal get publickey' gets the publickey of the principal.
(2) 'principal get default' and 'principal get forpeer <peer>'
    now take the following flags.
    (a) 'principal get default --names' prints the names of the blessings.
    (b) 'principal get default --rootkey <chainname> prints the root key of
        one of the names from (a).
    (c) 'principal get default --caveats <chainname> prints the caveats of
        one fo the names from (a).

Change-Id: I00754e2dd047748931c9be504033118e3171ca68
diff --git a/cmd/principal/doc.go b/cmd/principal/doc.go
index 93a5977..9ffcd6d 100644
--- a/cmd/principal/doc.go
+++ b/cmd/principal/doc.go
@@ -223,7 +223,7 @@
 
 The principal dump flags are:
  -s=false
-   If true, show a only the default blessing names
+   If true, show only the default blessing names
 
 Principal Dumpblessings
 
@@ -389,6 +389,7 @@
 The principal get commands are:
    default         Return blessings marked as default
    forpeer         Return blessings marked for the provided peer
+   publickey       Prints the public key of the principal.
    recognizedroots Return recognized blessings, and their associated public key.
    peermap         Shows the map from peer pattern to which blessing name to
                    present.
@@ -396,22 +397,29 @@
 Principal Get Default
 
 Returns blessings that are marked as default in the BlessingStore specified by
-the environment that this tool is running in.
+the environment that this tool is running in. Providing --names will print the
+default blessings' chain names. Providing --rootkey <chain_name> will print the
+root key of the certificate chain with chain_name. Providing --caveats
+<chain_name> will print the caveats on the certificate chain with chain_name.
 
 Usage:
    principal get default [flags]
 
 The principal get default flags are:
- -name=false
+ -caveats=
+   Shows the caveats on the provided certificate chain name.
+ -names=false
    If true, shows the value of the blessing name to be presented to the peer
- -rootkey=false
-   If true, shows the value of the root key of the certificate chain to be
-   presented to the peer
+ -rootkey=
+   Shows the value of the root key of the provided certificate chain name.
 
 Principal Get Forpeer
 
 Returns blessings that are marked for the provided peer in the BlessingStore
-specified by the environment that this tool is running in.
+specified by the environment that this tool is running in. Providing --names
+will print the blessings' chain names. Providing --rootkey <chain_name> will
+print the root key of the certificate chain with chain_name. Providing --caveats
+<chain_name> will print the caveats on the certificate chain with chain_name.
 
 Usage:
    principal get forpeer [flags] [<peer_1> ... <peer_k>]
@@ -423,11 +431,20 @@
 the "..." pattern).
 
 The principal get forpeer flags are:
- -name=false
+ -caveats=
+   Shows the caveats on the provided certificate chain name.
+ -names=false
    If true, shows the value of the blessing name to be presented to the peer
- -rootkey=false
-   If true, shows the value of the root key of the certificate chain to be
-   presented to the peer
+ -rootkey=
+   Shows the value of the root key of the provided certificate chain name.
+
+Principal Get Publickey
+
+Prints out the public key of the principal specified by the environment that
+this tool is running in.
+
+Usage:
+   principal get publickey
 
 Principal Get Recognizedroots
 
diff --git a/cmd/principal/main.go b/cmd/principal/main.go
index e6f2a6b..5aa4ae8 100644
--- a/cmd/principal/main.go
+++ b/cmd/principal/main.go
@@ -18,6 +18,7 @@
 	"io/ioutil"
 	"os"
 	"os/user"
+	"strings"
 	"time"
 
 	"v.io/v23"
@@ -68,12 +69,14 @@
 	flagRecvBlessingsSetDefault bool
 	flagRecvBlessingsForPeer    string
 
-	// Flags for the commands the get blessings
-	flagBlessingsName    bool
-	flagBlessingsRootKey bool
+	// Flags for the commands that get blessings
+	flagBlessingsNames   bool
+	flagBlessingsRootKey string
+	flagBlessingsCaveats string
 
 	errNoCaveats = fmt.Errorf("no caveats provided: it is generally dangerous to bless another principal without any caveats as that gives them almost unrestricted access to the blesser's credentials. If you really want to do this, set --require-caveats=false")
-	cmdDump      = &cmdline.Command{
+
+	cmdDump = &cmdline.Command{
 		Name:  "dump",
 		Short: "Dump out information about the principal",
 		Long: `
@@ -286,6 +289,22 @@
 		},
 	}
 
+	cmdGetPublicKey = &cmdline.Command{
+		Name:  "publickey",
+		Short: "Prints the public key of the principal.",
+		Long: `
+Prints out the public key of the principal specified by the environment
+that this tool is running in.
+`,
+		Run: func(cmd *cmdline.Command, args []string) error {
+			ctx, shutdown := v23.Init()
+			defer shutdown()
+			p := v23.GetPrincipal(ctx)
+			fmt.Println(p.PublicKey())
+			return nil
+		},
+	}
+
 	cmdGetTrustedRoots = &cmdline.Command{
 		Name:  "recognizedroots",
 		Short: "Return recognized blessings, and their associated public key.",
@@ -327,6 +346,11 @@
 Returns blessings that are marked for the provided peer in the
 BlessingStore specified by the environment that this tool is
 running in.
+Providing --names will print the blessings' chain names.
+Providing --rootkey <chain_name> will print the root key of the certificate chain
+with chain_name.
+Providing --caveats <chain_name> will print the caveats on the certificate chain
+with chain_name.
 `,
 		ArgsName: "[<peer_1> ... <peer_k>]",
 		ArgsLong: `
@@ -349,6 +373,11 @@
 		Long: `
 Returns blessings that are marked as default in the BlessingStore specified by
 the environment that this tool is running in.
+Providing --names will print the default blessings' chain names.
+Providing --rootkey <chain_name> will print the root key of the certificate chain
+with chain_name.
+Providing --caveats <chain_name> will print the caveats on the certificate chain
+with chain_name.
 `,
 		Run: func(cmd *cmdline.Command, args []string) error {
 			ctx, shutdown := v23.Init()
@@ -846,7 +875,7 @@
 	cmdBlessSelf.Flags.Var(&flagBlessSelfCaveats, "caveat", flagBlessSelfCaveats.usage())
 	cmdBlessSelf.Flags.DurationVar(&flagBlessSelfFor, "for", 0, "Duration of blessing validity (zero implies no expiration)")
 
-	cmdDump.Flags.BoolVar(&flagDumpShort, "s", false, "If true, show a only the default blessing names")
+	cmdDump.Flags.BoolVar(&flagDumpShort, "s", false, "If true, show only the default blessing names")
 
 	cmdFork.Flags.BoolVar(&flagCreateOverwrite, "overwrite", false, "If true, any existing principal data in the directory will be overwritten")
 	cmdFork.Flags.Var(&flagForkCaveats, "caveat", flagForkCaveats.usage())
@@ -878,11 +907,13 @@
 	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'")
 
-	cmdGetForPeer.Flags.BoolVar(&flagBlessingsName, "name", false, "If true, shows the value of the blessing name to be presented to the peer")
-	cmdGetForPeer.Flags.BoolVar(&flagBlessingsRootKey, "rootkey", false, "If true, shows the value of the root key of the certificate chain to be presented to the peer")
+	cmdGetForPeer.Flags.BoolVar(&flagBlessingsNames, "names", false, "If true, shows the value of the blessing name to be presented to the peer")
+	cmdGetForPeer.Flags.StringVar(&flagBlessingsRootKey, "rootkey", "", "Shows the value of the root key of the provided certificate chain name.")
+	cmdGetForPeer.Flags.StringVar(&flagBlessingsCaveats, "caveats", "", "Shows the caveats on the provided certificate chain name.")
 
-	cmdGetDefault.Flags.BoolVar(&flagBlessingsName, "name", false, "If true, shows the value of the blessing name to be presented to the peer")
-	cmdGetDefault.Flags.BoolVar(&flagBlessingsRootKey, "rootkey", false, "If true, shows the value of the root key of the certificate chain to be presented to the peer")
+	cmdGetDefault.Flags.BoolVar(&flagBlessingsNames, "names", false, "If true, shows the value of the blessing name to be presented to the peer")
+	cmdGetDefault.Flags.StringVar(&flagBlessingsRootKey, "rootkey", "", "Shows the value of the root key of the provided certificate chain name.")
+	cmdGetDefault.Flags.StringVar(&flagBlessingsCaveats, "caveats", "", "Shows the caveats on the provided certificate chain name.")
 
 	cmdSet := &cmdline.Command{
 		Name:  "set",
@@ -904,7 +935,7 @@
 
 All blessings are printed to stdout using base64-VOM-encoding.
 `,
-		Children: []*cmdline.Command{cmdGetDefault, cmdGetForPeer, cmdGetTrustedRoots, cmdGetPeerMap},
+		Children: []*cmdline.Command{cmdGetDefault, cmdGetForPeer, cmdGetPublicKey, cmdGetTrustedRoots, cmdGetPeerMap},
 	}
 
 	root := &cmdline.Command{
@@ -942,20 +973,82 @@
 	if blessings.IsZero() {
 		return fmt.Errorf("no blessings found")
 	}
-	if flagBlessingsName {
-		fmt.Println(blessings)
+	if flagBlessingsNames {
+		fmt.Println(strings.Replace(fmt.Sprint(blessings), ",", "\n", -1))
 		return nil
-	} else if flagBlessingsRootKey {
-		wire, err := blessings2wire(blessings)
+	} else if len(flagBlessingsRootKey) > 0 {
+		chain, err := getChainByName(blessings, flagBlessingsRootKey)
 		if err != nil {
 			return err
 		}
-		fmt.Println(rootkey(wire.CertificateChains[0]))
+		fmt.Println(rootkey(chain))
+		return nil
+	} else if len(flagBlessingsCaveats) > 0 {
+		chain, err := getChainByName(blessings, flagBlessingsCaveats)
+		if err != nil {
+			return err
+		}
+		cavs, err := prettyPrintCaveats(chain)
+		if err != nil {
+			return err
+		}
+		for _, c := range cavs {
+			fmt.Println(c)
+		}
 		return nil
 	}
 	return dumpBlessings(blessings)
 }
 
+func prettyPrintCaveats(chain []security.Certificate) ([]string, error) {
+	var cavs []security.Caveat
+	for _, cert := range chain {
+		cavs = append(cavs, cert.Caveats...)
+	}
+	var s []string
+	for _, cav := range cavs {
+		if cav.Id == security.PublicKeyThirdPartyCaveatX.Id {
+			c := cav.ThirdPartyDetails()
+			s = append(s, fmt.Sprintf("ThirdPartyCaveat: Requires discharge from %v (ID=%q)", c.Location(), c.ID()))
+			continue
+		}
+		var param interface{}
+		if err := vom.Decode(cav.ParamVom, &param); err != nil {
+			return nil, err
+		}
+		switch cav.Id {
+		case security.ConstCaveat.Id:
+			// In the case a ConstCaveat is specified, we only want to print it
+			// if it never validates.
+			if !param.(bool) {
+				s = append(s, fmt.Sprintf("Never validates"))
+			}
+		case security.ExpiryCaveatX.Id:
+			s = append(s, fmt.Sprintf("Expires at %v", param))
+		case security.MethodCaveatX.Id:
+			s = append(s, fmt.Sprintf("Restricted to methods %v", param))
+		case security.PeerBlessingsCaveat.Id:
+			s = append(s, fmt.Sprintf("Restricted to peers with blessings %v", param))
+		default:
+			s = append(s, cav.String())
+		}
+	}
+	return s, nil
+}
+
+func getChainByName(b security.Blessings, name string) ([]security.Certificate, error) {
+	wire, err := blessings2wire(b)
+	if err != nil {
+		return nil, err
+	}
+	for _, chain := range wire.CertificateChains {
+		if chainName(chain) == name {
+			return chain, nil
+		}
+	}
+	return nil, fmt.Errorf("no chains of name %v in %v", name, b)
+}
+
 func read(fname string) (string, error) {
 	if len(fname) == 0 {
 		return "", nil
@@ -1010,6 +1103,14 @@
 	return fmt.Sprintf("%v", key)
 }
 
+func chainName(chain []security.Certificate) string {
+	exts := make([]string, len(chain))
+	for i, cert := range chain {
+		exts[i] = cert.Extension
+	}
+	return strings.Join(exts, security.ChainSeparator)
+}
+
 func base64VomEncode(i interface{}) (string, error) {
 	buf := &bytes.Buffer{}
 	closer := base64.NewEncoder(base64.URLEncoding, buf)
diff --git a/cmd/principal/principal_v23_test.go b/cmd/principal/principal_v23_test.go
index 1f3cef1..04f9fa3 100644
--- a/cmd/principal/principal_v23_test.go
+++ b/cmd/principal/principal_v23_test.go
@@ -72,14 +72,14 @@
 		aliceDir    = filepath.Join(outputDir, "alice")
 		aliceFriend = filepath.Join(outputDir, "alice.bless")
 		bobDir      = filepath.Join(outputDir, "bob")
-		bobForPeer  = filepath.Join(outputDir, "bob.store.forpeer")
+		bobForPeer  = filepath.Join(outputDir, "bob.get.forpeer")
 	)
 
 	// Create two principals: alice and bob.
 	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
 	bin.Start("create", bobDir, "bob").WaitOrDie(os.Stdout, os.Stderr)
 
-	// Bless Alice with Bob's principal.
+	// Bless Bob with Alice's principal.
 	blessEnv := credEnv(aliceDir)
 	redirect(t, bin.WithEnv(blessEnv).Start("bless", "--for=1m", bobDir, "friend"), aliceFriend)
 
@@ -99,7 +99,30 @@
     (0) ExpiryCaveat
 `
 	if want != got {
-		t.Fatalf("unexpected output, got\n%s, wanted\n%s", got, want)
+		t.Errorf("unexpected output, got\n%s, wanted\n%s", got, want)
+	}
+
+	// Test the names flag.
+	got = bin.WithEnv(blessEnv).Start("--v23.credentials="+bobDir, "get", "forpeer", "--names", "alice/server").Output()
+	want = `bob
+alice/friend
+`
+	if got != want {
+		t.Errorf("unexpected output, got %s, want %s", got, want)
+	}
+
+	// Test the rootkey flag. In particular alice/friend's rootkey should be equal to alice's publickey.
+	got = bin.WithEnv(blessEnv).Start("--v23.credentials="+bobDir, "get", "forpeer", "--rootkey", "alice/friend", "alice/server").Output()
+	want = bin.WithEnv(blessEnv).Start("get", "publickey").Output()
+	if got != want {
+		t.Errorf("unexpected output, got %s, want %s", got, want)
+	}
+
+	// Test the caveats flag.
+	got = bin.WithEnv(blessEnv).Start("--v23.credentials="+bobDir, "get", "forpeer", "--caveats", "alice/friend", "alice/server").Output()
+	want = "Expires at"
+	if !strings.HasPrefix(got, want) {
+		t.Errorf("unexpected output, got %s, want %s", got, want)
 	}
 }
 
@@ -157,7 +180,7 @@
 	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
 
 	blessEnv := credEnv(aliceDir)
-	got := removePublicKeys(bin.WithEnv(blessEnv).Start("get", "peermap").Output())
+	got := bin.WithEnv(blessEnv).Start("get", "peermap").Output()
 	want := `Default Blessings                alice
 Peer pattern                     Blessings
 ...                              alice