cmd/principal: addtoroots can now be used to add (key, blessingpattern)
pairs directly (without a blessing).

I intend to use this in the prod-services test, where I will fetch
the public key via HTTP and then add to the roots of programs used
to talk to production services.

Change-Id: I49563b22ac8912f692a5929015f039f5c0062b54
diff --git a/cmd/principal/doc.go b/cmd/principal/doc.go
index fc65e1b..5385f29 100644
--- a/cmd/principal/doc.go
+++ b/cmd/principal/doc.go
@@ -26,7 +26,8 @@
    blessself     Generate a self-signed blessing
    bless         Bless another principal
    store         Manipulate and inspect the principal's blessing store
-   addtoroots    Add provided blessings to root set
+   addtoroots    Add to the set of identity providers recognized by this
+                 principal
    help          Display help for commands or topics
 Run "principal help [command]" for command usage.
 
@@ -378,22 +379,31 @@
 
 Principal Addtoroots
 
-Adds the provided blessings to the set of trusted roots for this principal.
+Adds an identity provider to the set of recognized roots public keys for this
+principal.
 
-'addtoroots b' adds blessings b to the trusted root set.
+It accepts either a single argument (which points to a file containing a
+blessing) or two arguments (a name and a base64-encoded DER-encoded public key).
 
-For example, to make the principal in credentials directory A trust the root of
-the default blessing in credentials directory B:
+For example, to make the principal in credentials directory A recognize the root
+of the default blessing in credentials directory B:
   principal -veyron.credentials=B bless A some_extension |
   principal -veyron.credentials=A addtoroots -
-
 The extension 'some_extension' has no effect in the command above.
 
-Usage:
-   principal addtoroots <file>
+Or to make the principal in credentials director A recognize the base64-encoded
+public key KEY for blessing patterns P:
+  principal -veyron.credentials=A addtoroots KEY P
 
-<file> is the path to a file containing a blessing typically obtained from this
-tool. - is used for STDIN.
+Usage:
+   principal addtoroots <key|blessing> [<blessing pattern>]
+
+<blessing> is the path to a file containing a blessing typically obtained from
+this tool. - is used for STDIN.
+
+<key> is a base64-encoded, DER-encoded public key.
+
+<blessing pattern> is the blessing pattern for which <key> should be recognized.
 
 Principal Help
 
diff --git a/cmd/principal/main.go b/cmd/principal/main.go
index 0a79e3b..8cf43f4 100644
--- a/cmd/principal/main.go
+++ b/cmd/principal/main.go
@@ -365,41 +365,60 @@
 
 	cmdAddToRoots = &cmdline.Command{
 		Name:  "addtoroots",
-		Short: "Add provided blessings to root set",
+		Short: "Add to the set of identity providers recognized by this principal",
 		Long: `
-Adds the provided blessings to the set of trusted roots for this principal.
+Adds an identity provider to the set of recognized roots public keys for this principal.
 
-'addtoroots b' adds blessings b to the trusted root set.
+It accepts either a single argument (which points to a file containing a blessing)
+or two arguments (a name and a base64-encoded DER-encoded public key).
 
-For example, to make the principal in credentials directory A trust the
+For example, to make the principal in credentials directory A recognize the
 root of the default blessing in credentials directory B:
   principal -veyron.credentials=B bless A some_extension |
   principal -veyron.credentials=A addtoroots -
-
 The extension 'some_extension' has no effect in the command above.
+
+Or to make the principal in credentials director A recognize the base64-encoded
+public key KEY for blessing patterns P:
+  principal -veyron.credentials=A addtoroots KEY P
 `,
-		ArgsName: "<file>",
+		ArgsName: "<key|blessing> [<blessing pattern>]",
 		ArgsLong: `
-<file> is the path to a file containing a blessing typically obtained
-from this tool. - is used for STDIN.
+<blessing> is the path to a file containing a blessing typically obtained from
+this tool. - is used for STDIN.
+
+<key> is a base64-encoded, DER-encoded public key.
+
+<blessing pattern> is the blessing pattern for which <key> should be recognized.
 `,
 		Run: func(cmd *cmdline.Command, args []string) error {
-			if len(args) != 1 {
-				return fmt.Errorf("requires exactly one argument <file>, provided %d", len(args))
+			if len(args) != 1 && len(args) != 2 {
+				return fmt.Errorf("requires either one argument <file>, or two arguments <key> <blessing pattern>, provided %d", len(args))
 			}
-			blessings, err := decodeBlessings(args[0])
-			if err != nil {
-				return fmt.Errorf("failed to decode provided blessings: %v", err)
-			}
-
 			ctx, shutdown := v23.Init()
 			defer shutdown()
 
 			p := v23.GetPrincipal(ctx)
-			if err := p.AddToRoots(blessings); err != nil {
-				return fmt.Errorf("AddToRoots failed: %v", err)
+			if len(args) == 1 {
+				blessings, err := decodeBlessings(args[0])
+				if err != nil {
+					return fmt.Errorf("failed to decode provided blessings: %v", err)
+				}
+				if err := p.AddToRoots(blessings); err != nil {
+					return fmt.Errorf("AddToRoots failed: %v", err)
+				}
+				return nil
 			}
-			return nil
+			// len(args) == 2
+			der, err := base64.URLEncoding.DecodeString(args[0])
+			if err != nil {
+				return fmt.Errorf("invalid base64 encoding of public key: %v", err)
+			}
+			key, err := security.UnmarshalPublicKey(der)
+			if err != nil {
+				return fmt.Errorf("invalid DER encoding of public key: %v", err)
+			}
+			return p.Roots().Add(key, security.BlessingPattern(args[1]))
 		},
 	}
 
diff --git a/cmd/principal/principal_v23_test.go b/cmd/principal/principal_v23_test.go
index 5f4b522..ac7ff5d 100644
--- a/cmd/principal/principal_v23_test.go
+++ b/cmd/principal/principal_v23_test.go
@@ -475,7 +475,7 @@
 	}
 }
 
-func V23TestAddToRoots(t *v23tests.T) {
+func V23TestAddBlessingsToRoots(t *v23tests.T) {
 	var (
 		bin          = t.BuildGoPkg("v.io/x/ref/cmd/principal")
 		aliceDir     = t.NewTempDir()
@@ -519,6 +519,32 @@
 	}
 }
 
+func V23TestAddKeyToRoots(t *v23tests.T) {
+	var (
+		bin      = t.BuildGoPkg("v.io/x/ref/cmd/principal")
+		aliceDir = t.NewTempDir()
+	)
+	bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+	// The second argument and the "want" line below were generated by:
+	//   import "encoding/base64"
+	//   import "v.io/x/ref/security"
+	//
+	//    key, _, _ := security.NewPrincipalKey()
+	//    der, _ := key.MarshalBinary()
+	//    b64 := base64.URLEncoding.EncodeToString(der)  // argument to addtoroots
+	//    str := fmt.Sprintf("%v", key)                  // for the "want" line
+	bin.Start("--veyron.credentials="+aliceDir, "addtoroots", "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9iRjaFDoGJI9tarUwWqIW31ti72krThkYByn1v9Lf89D9VA0Mg2oUL7FDDM7qxjZcVM1ktM_W4tBfMVuRZmVCA==", "some_other_provider").WaitOrDie(os.Stdout, os.Stderr)
+	// "foo" should appear in the set of BlessingRoots
+	output := bin.Start("--veyron.credentials="+aliceDir, "dump").Output()
+	want := fmt.Sprintf("41:26:f6:aa:54:f9:31:d4:9d:1f:d2:69:c6:c5:50:70 : [some_other_provider]")
+	for _, line := range strings.Split(output, "\n") {
+		if line == want {
+			return
+		}
+	}
+	t.Errorf("Could not find line:\n%v\nin output:\n%v\n", want, output)
+}
+
 func credEnv(dir string) string {
 	return fmt.Sprintf("%s=%s", envvar.Credentials, dir)
 }
diff --git a/cmd/principal/v23_test.go b/cmd/principal/v23_test.go
index b2a9bb3..c0d9525 100644
--- a/cmd/principal/v23_test.go
+++ b/cmd/principal/v23_test.go
@@ -60,6 +60,10 @@
 	v23tests.RunTest(t, V23TestBless)
 }
 
-func TestV23AddToRoots(t *testing.T) {
-	v23tests.RunTest(t, V23TestAddToRoots)
+func TestV23AddBlessingsToRoots(t *testing.T) {
+	v23tests.RunTest(t, V23TestAddBlessingsToRoots)
+}
+
+func TestV23AddKeyToRoots(t *testing.T) {
+	v23tests.RunTest(t, V23TestAddKeyToRoots)
 }