veyron/tools/mgmt/device/impl/...

-Add option for a claimer to verify the public-key of the device.

Change-Id: I71b8062075262f0f12ef921ff73bd4f6bfb0de8a
diff --git a/runtimes/google/ipc/client.go b/runtimes/google/ipc/client.go
index f38c589..c3e7136 100644
--- a/runtimes/google/ipc/client.go
+++ b/runtimes/google/ipc/client.go
@@ -519,7 +519,7 @@
 				var err error
 				patterns := resolved.Servers[r.index].BlessingPatterns
 				if serverB, grantedB, err = c.authorizeServer(ctx, r.flow, name, method, patterns, opts); err != nil {
-					r.err = verror.New(errNotTrusted, ctx, name, r.flow.RemoteBlessings(), err)
+					r.err = verror.New(verror.ErrNotTrusted, ctx, name, r.flow.RemoteBlessings(), err)
 					vlog.VI(2).Infof("ipc: err: %s", r.err)
 					r.flow.Close()
 					r.flow = nil
@@ -612,7 +612,7 @@
 	for _, r := range responses {
 		if r != nil && r.err != nil {
 			switch {
-			case verror.Is(r.err, errNotTrusted.ID) || verror.Is(r.err, errAuthError.ID):
+			case verror.Is(r.err, verror.ErrNotTrusted.ID) || verror.Is(r.err, errAuthError.ID):
 				untrusted = append(untrusted, "("+r.err.Error()+") ")
 			default:
 				noconn = append(noconn, "("+r.err.Error()+") ")
diff --git a/services/mgmt/device/starter/starter.go b/services/mgmt/device/starter/starter.go
index 4abcebd..a6dea2e 100644
--- a/services/mgmt/device/starter/starter.go
+++ b/services/mgmt/device/starter/starter.go
@@ -3,6 +3,7 @@
 package starter
 
 import (
+	"encoding/base64"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -129,7 +130,12 @@
 		shutdown()
 		return nil, err
 	}
-	vlog.Infof("Unclaimed device manager (%v) published as %v", endpoints[0].Name(), claimableServerName)
+	publicKey, err := veyron2.GetPrincipal(ctx).PublicKey().MarshalBinary()
+	if err != nil {
+		shutdown()
+		return nil, err
+	}
+	vlog.Infof("Unclaimed device manager (%v) published as %v with public_key:%s", endpoints[0].Name(), claimableServerName, base64.URLEncoding.EncodeToString(publicKey))
 	return shutdown, nil
 }
 
diff --git a/tools/mgmt/device/doc.go b/tools/mgmt/device/doc.go
index 2592498..9c7f62e 100644
--- a/tools/mgmt/device/doc.go
+++ b/tools/mgmt/device/doc.go
@@ -193,13 +193,19 @@
 Claim the device.
 
 Usage:
-   device claim <device> <grant extension> <pairing token>
+   device claim <device> <grant extension> <pairing token> <device publickey>
 
 <device> is the veyron object name of the device manager's device service.
 
 <grant extension> is used to extend the default blessing of the current
 principal when blessing the app instance.
 
+<pairing token> is a token that the device manager expects to be replayed during
+a claim operation on the device.
+
+<device publickey> is the marshalled public key of the device manager we are
+claiming.
+
 Device Stop
 
 Stop the given application instance.
diff --git a/tools/mgmt/device/impl/devicemanager_mock_test.go b/tools/mgmt/device/impl/devicemanager_mock_test.go
index 7c039e9..baadc8c 100644
--- a/tools/mgmt/device/impl/devicemanager_mock_test.go
+++ b/tools/mgmt/device/impl/devicemanager_mock_test.go
@@ -275,7 +275,7 @@
 }
 
 func (d *dispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
-	return device.DeviceServer(&mockDeviceInvoker{tape: d.tape, t: d.t}), nil, nil
+	return &mockDeviceInvoker{tape: d.tape, t: d.t}, nil, nil
 }
 
 func startServer(t *testing.T, ctx *context.T, tape *Tape) (ipc.Server, naming.Endpoint, error) {
diff --git a/tools/mgmt/device/impl/impl.go b/tools/mgmt/device/impl/impl.go
index 8cf1b90..6fbd325 100644
--- a/tools/mgmt/device/impl/impl.go
+++ b/tools/mgmt/device/impl/impl.go
@@ -1,6 +1,7 @@
 package impl
 
 import (
+	"encoding/base64"
 	"encoding/json"
 	"fmt"
 
@@ -149,16 +150,22 @@
 	Name:     "claim",
 	Short:    "Claim the device.",
 	Long:     "Claim the device.",
-	ArgsName: "<device> <grant extension> <pairing token>",
+	ArgsName: "<device> <grant extension> <pairing token> <device publickey>",
 	ArgsLong: `
 <device> is the veyron object name of the device manager's device service.
 
 <grant extension> is used to extend the default blessing of the
-current principal when blessing the app instance.`,
+current principal when blessing the app instance.
+
+<pairing token> is a token that the device manager expects to be replayed
+during a claim operation on the device.
+
+<device publickey> is the marshalled public key of the device manager we
+are claiming.`,
 }
 
 func runClaim(cmd *cmdline.Command, args []string) error {
-	if expected, max, got := 2, 3, len(args); expected > got || got > max {
+	if expected, max, got := 2, 4, len(args); expected > got || got > max {
 		return cmd.UsageErrorf("claim: incorrect number of arguments, expected atleast %d (max: %d), got %d", expected, max, got)
 	}
 	deviceName, grant := args[0], args[1]
@@ -166,11 +173,22 @@
 	if len(args) > 2 {
 		pairingToken = args[2]
 	}
-	principal := veyron2.GetPrincipal(gctx)
-	// Skip server authorization since an unclaimed device has no
-	// credentials that will be recognized by the claimer.
-	if err := device.ClaimableClient(deviceName).Claim(gctx, pairingToken, &granter{p: principal, extension: grant}, options.SkipResolveAuthorization{}); err != nil {
-		return fmt.Errorf("Claim failed: %v", err)
+	var serverKeyOpts ipc.CallOpt
+	if len(args) > 3 {
+		marshalledPublicKey, err := base64.URLEncoding.DecodeString(args[3])
+		if err != nil {
+			return fmt.Errorf("Failed to base64 decode publickey: %v", err)
+		}
+		if deviceKey, err := security.UnmarshalPublicKey(marshalledPublicKey); err != nil {
+			return fmt.Errorf("Failed to unmarshal device public key:%v", err)
+		} else {
+			serverKeyOpts = options.ServerPublicKey{deviceKey}
+		}
+	}
+	// Skip server resolve authorization since an unclaimed device might have
+	// roots that will not be recognized by the claimer.
+	if err := device.ClaimableClient(deviceName).Claim(gctx, pairingToken, &granter{p: veyron2.GetPrincipal(gctx), extension: grant}, serverKeyOpts, options.SkipResolveAuthorization{}); err != nil {
+		return err
 	}
 	fmt.Fprintln(cmd.Stdout(), "Successfully claimed.")
 	return nil
diff --git a/tools/mgmt/device/impl/impl_test.go b/tools/mgmt/device/impl/impl_test.go
index a23ab5f..da1f540 100644
--- a/tools/mgmt/device/impl/impl_test.go
+++ b/tools/mgmt/device/impl/impl_test.go
@@ -2,17 +2,20 @@
 
 import (
 	"bytes"
+	"encoding/base64"
 	"encoding/json"
 	"fmt"
 	"reflect"
 	"strings"
 	"testing"
 
+	"v.io/core/veyron2"
 	"v.io/core/veyron2/naming"
 	"v.io/core/veyron2/services/mgmt/application"
 	"v.io/core/veyron2/services/mgmt/device"
 	"v.io/core/veyron2/verror"
 
+	"v.io/core/veyron/security"
 	"v.io/core/veyron/tools/mgmt/device/impl"
 )
 
@@ -272,10 +275,6 @@
 	}
 }
 
-// TODO(ashankar): Re-enable
-// The mock server must provide both the device.Claimable and
-// device.Device interfaces.
-/*
 func TestClaimCommand(t *testing.T) {
 	shutdown := initTest()
 	defer shutdown()
@@ -292,34 +291,54 @@
 	var stdout, stderr bytes.Buffer
 	cmd.Init(nil, &stdout, &stderr)
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
+	deviceKey, err := veyron2.GetPrincipal(gctx).PublicKey().MarshalBinary()
+	if err != nil {
+		t.Fatalf("Failed to marshal principal public key: %v", err)
+	}
 
 	// Confirm that we correctly enforce the number of arguments.
 	if err := cmd.Execute([]string{"claim", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
-	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 3), got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 4), got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
 		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
 	}
 	stdout.Reset()
 	stderr.Reset()
 	tape.Rewind()
 
-	if err := cmd.Execute([]string{"claim", "nope", "nope", "nope", "nope"}); err == nil {
+	if err := cmd.Execute([]string{"claim", "nope", "nope", "nope", "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
-	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 3), got 4", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
+	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 4), got 5", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
 		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
 	}
 	stdout.Reset()
 	stderr.Reset()
 	tape.Rewind()
 
+	// Incorrect operation
+	var pairingToken string
+	var deviceKeyWrong []byte
+	if publicKey, _, err := security.NewPrincipalKey(); err != nil {
+		t.Fatalf("NewPrincipalKey failed: %v", err)
+	} else {
+		if deviceKeyWrong, err = publicKey.MarshalBinary(); err != nil {
+			t.Fatalf("Failed to marshal principal public key: %v", err)
+		}
+	}
+	if err := cmd.Execute([]string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKeyWrong)}); !verror.Is(err, verror.ErrNotTrusted.ID) {
+		t.Fatalf("wrongly failed to receive correct error on claim with incorrect device key:%v id:%v", err, verror.ErrorID(err))
+	}
+	stdout.Reset()
+	stderr.Reset()
+	tape.Rewind()
+
 	// Correct operation.
 	tape.SetResponses([]interface{}{
 		nil,
 	})
-	var pairingToken string
-	if err := cmd.Execute([]string{"claim", deviceName, "grant", pairingToken}); err != nil {
+	if err := cmd.Execute([]string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKey)}); err != nil {
 		t.Fatalf("Claim(%s, %s, %s) failed: %v", deviceName, "grant", pairingToken, err)
 	}
 	if got, expected := len(tape.Play()), 1; got != expected {
@@ -354,9 +373,7 @@
 	tape.Rewind()
 	stdout.Reset()
 	stderr.Reset()
-
 }
-*/
 
 func TestStartCommand(t *testing.T) {
 	shutdown := initTest()