ref: Remote REST signer implementation.

MultiPart: 2/2

Change-Id: I8de2eb4def9f857695b0ef8334aae20af60bcb45
diff --git a/services/identity/internal/rest_signer_test/main.go b/services/identity/internal/rest_signer_test/main.go
new file mode 100644
index 0000000..03995d6
--- /dev/null
+++ b/services/identity/internal/rest_signer_test/main.go
@@ -0,0 +1,25 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+package main
+
+import (
+	"fmt"
+
+	"v.io/x/ref/services/identity/internal/server"
+)
+
+func main() {
+	signer, err := server.NewRestSigner()
+	if err != nil {
+		fmt.Printf("NewRestSigner error: %v\n", err)
+		return
+	}
+	sig, err := signer.Sign([]byte("purpose"), []byte("message"))
+	if err != nil {
+		fmt.Printf("Sign error: %v\n", err)
+		return
+	}
+	ok := sig.Verify(signer.PublicKey(), []byte("message"))
+	fmt.Printf("Verified: %v\n", ok)
+}
diff --git a/services/identity/internal/server/rest_signer.go b/services/identity/internal/server/rest_signer.go
new file mode 100644
index 0000000..5a8efbf
--- /dev/null
+++ b/services/identity/internal/server/rest_signer.go
@@ -0,0 +1,69 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package server
+
+import (
+	"crypto/ecdsa"
+	"crypto/x509"
+	"encoding/base64"
+	"fmt"
+	"math/big"
+
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+	"v.io/v23/security"
+	"v.io/x/ref/services/identity/internal/signer/v1"
+)
+
+func DecodePublicKey(k *signer.PublicKey) (*ecdsa.PublicKey, error) {
+	bytes, err := base64.URLEncoding.DecodeString(k.Base64)
+	if err != nil {
+		return nil, err
+	}
+	key, err := x509.ParsePKIXPublicKey(bytes)
+	if err != nil {
+		return nil, err
+	}
+	pub, ok := key.(*ecdsa.PublicKey)
+	if !ok {
+		return nil, fmt.Errorf("Not an ECDSA public key")
+	}
+	return pub, nil
+}
+
+func DecodeSignature(sig *signer.VSignature) (r, s *big.Int, err error) {
+	r, s = new(big.Int), new(big.Int)
+	if _, ok := r.SetString(sig.R, 0); !ok {
+		return nil, nil, fmt.Errorf("unable to parse big.Int %s", sig.R)
+	}
+	if _, ok := s.SetString(sig.S, 0); !ok {
+		return nil, nil, fmt.Errorf("unable to parse big.Int %s", sig.S)
+	}
+	return
+}
+
+func NewRestSigner() (security.Signer, error) {
+	client, err := signer.New(oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource("")))
+	if err != nil {
+		return nil, err
+	}
+	jkey, err := client.PublicKey().Do()
+	if err != nil {
+		return nil, err
+	}
+	key, err := DecodePublicKey(jkey)
+	if err != nil {
+		return nil, err
+	}
+	sign := func(message []byte) (r, s *big.Int, err error) {
+		msgBase64 := base64.URLEncoding.EncodeToString(message)
+		jsig, err := client.Sign(msgBase64).Do()
+		if err != nil {
+			return nil, nil, err
+		}
+		return DecodeSignature(jsig)
+	}
+	return security.NewECDSASigner(key, sign), nil
+}
diff --git a/services/identity/internal/server/rest_signer_test.go b/services/identity/internal/server/rest_signer_test.go
new file mode 100644
index 0000000..ef4cd11
--- /dev/null
+++ b/services/identity/internal/server/rest_signer_test.go
@@ -0,0 +1,35 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package server_test
+
+import (
+	"math/big"
+	"testing"
+
+	"v.io/v23/security"
+	"v.io/x/ref/services/identity/internal/server"
+	"v.io/x/ref/services/identity/internal/signer/v1"
+)
+
+func TestDecode(t *testing.T) {
+	encodedKey := &signer.PublicKey{Base64: "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPqDbuT2B9Bb3JMcOGd2mm4bQuKSREeSKRt8_oofo0jRYiKFQ2ZVuCqssA-IUvFArT5KfXc6B9BNesgS10rPKrg=="}
+	encodedSig := &signer.VSignature{R: "0x42bca58e435f906c874536789cfc31656dd8f9ffbd3b7be84181611cc04eaf74", S: "0xa6f57e858a9f36b559e9cd9f13854b90fad49e0c5591ed66033fd286682b2078"}
+
+	key, err := server.DecodePublicKey(encodedKey)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := security.NewECDSASigner(key, func(message []byte) (r, s *big.Int, err error) {
+		return server.DecodeSignature(encodedSig)
+	})
+	sig, err := s.Sign([]byte("purpose"), []byte("message"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !sig.Verify(s.PublicKey(), []byte("message")) {
+		t.Fatal("Signature does not verify")
+	}
+}
diff --git a/services/identity/internal/signer/v1/README b/services/identity/internal/signer/v1/README
new file mode 100644
index 0000000..23fa070
--- /dev/null
+++ b/services/identity/internal/signer/v1/README
@@ -0,0 +1,12 @@
+Auto generated client for the signer service.
+
+The service is implemented using Cloud Endpoints
+https://cloud.google.com/endpoints/
+
+The signer-api.json file is auto generated from the server implementation.
+
+The signer-gen.go file is generated using
+google.golang.org/api/google-api-go-generator:
+
+google-api-go-generator --api_json_file=signer-api.json -output=signer-gen.go
+
diff --git a/services/identity/internal/signer/v1/signer-api.json b/services/identity/internal/signer/v1/signer-api.json
new file mode 100644
index 0000000..3a4e012
--- /dev/null
+++ b/services/identity/internal/signer/v1/signer-api.json
@@ -0,0 +1,130 @@
+{
+ "kind": "discovery#restDescription",
+ "etag": "\"u_zXkMELIlX4ktyNbM2XKD4vK8E/fv51HUFpIp2SZ4FxWW-y5odvVuE\"",
+ "discoveryVersion": "v1",
+ "id": "signer:v1",
+ "name": "signer",
+ "version": "v1",
+ "description": "Vanadium remote signer",
+ "icons": {
+  "x16": "http://www.google.com/images/icons/product/search-16.gif",
+  "x32": "http://www.google.com/images/icons/product/search-32.gif"
+ },
+ "protocol": "rest",
+ "baseUrl": "https://vanadium-production.appspot.com/_ah/api/signer/v1/",
+ "basePath": "/_ah/api/signer/v1/",
+ "rootUrl": "https://vanadium-production.appspot.com/_ah/api/",
+ "servicePath": "signer/v1/",
+ "batchPath": "batch",
+ "parameters": {
+  "alt": {
+   "type": "string",
+   "description": "Data format for the response.",
+   "default": "json",
+   "enum": [
+    "json"
+   ],
+   "enumDescriptions": [
+    "Responses with Content-Type of application/json"
+   ],
+   "location": "query"
+  },
+  "fields": {
+   "type": "string",
+   "description": "Selector specifying which fields to include in a partial response.",
+   "location": "query"
+  },
+  "key": {
+   "type": "string",
+   "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
+   "location": "query"
+  },
+  "oauth_token": {
+   "type": "string",
+   "description": "OAuth 2.0 token for the current user.",
+   "location": "query"
+  },
+  "prettyPrint": {
+   "type": "boolean",
+   "description": "Returns response with indentations and line breaks.",
+   "default": "true",
+   "location": "query"
+  },
+  "quotaUser": {
+   "type": "string",
+   "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.",
+   "location": "query"
+  },
+  "userIp": {
+   "type": "string",
+   "description": "IP address of the site where the request originates. Use this if you want to enforce per-user limits.",
+   "location": "query"
+  }
+ },
+ "auth": {
+  "oauth2": {
+   "scopes": {
+    "https://www.googleapis.com/auth/userinfo.email": {
+     "description": "View your email address"
+    }
+   }
+  }
+ },
+ "schemas": {
+  "PublicKey": {
+   "id": "PublicKey",
+   "type": "object",
+   "properties": {
+    "base64": {
+     "type": "string"
+    }
+   }
+  },
+  "VSignature": {
+   "id": "VSignature",
+   "type": "object",
+   "properties": {
+    "r": {
+     "type": "string"
+    },
+    "s": {
+     "type": "string"
+    }
+   }
+  }
+ },
+ "methods": {
+  "publicKey": {
+   "id": "signer.publicKey",
+   "path": "publicKey",
+   "httpMethod": "POST",
+   "response": {
+    "$ref": "PublicKey"
+   },
+   "scopes": [
+    "https://www.googleapis.com/auth/userinfo.email"
+   ]
+  },
+  "sign": {
+   "id": "signer.sign",
+   "path": "sign/{base64}",
+   "httpMethod": "POST",
+   "parameters": {
+    "base64": {
+     "type": "string",
+     "required": true,
+     "location": "path"
+    }
+   },
+   "parameterOrder": [
+    "base64"
+   ],
+   "response": {
+    "$ref": "VSignature"
+   },
+   "scopes": [
+    "https://www.googleapis.com/auth/userinfo.email"
+   ]
+  }
+ }
+}
diff --git a/services/identity/internal/signer/v1/signer-gen.go b/services/identity/internal/signer/v1/signer-gen.go
new file mode 100644
index 0000000..98308ed
--- /dev/null
+++ b/services/identity/internal/signer/v1/signer-gen.go
@@ -0,0 +1,207 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package signer provides access to the .
+//
+// Usage example:
+//
+//   import "google.golang.org/api/signer/v1"
+//   ...
+//   signerService, err := signer.New(oauthHttpClient)
+package signer
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"golang.org/x/net/context"
+	"google.golang.org/api/googleapi"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+// Always reference these packages, just in case the auto-generated code
+// below doesn't.
+var _ = bytes.NewBuffer
+var _ = strconv.Itoa
+var _ = fmt.Sprintf
+var _ = json.NewDecoder
+var _ = io.Copy
+var _ = url.Parse
+var _ = googleapi.Version
+var _ = errors.New
+var _ = strings.Replace
+var _ = context.Background
+
+const apiId = "signer:v1"
+const apiName = "signer"
+const apiVersion = "v1"
+const basePath = "https://vanadium-production.appspot.com/_ah/api/signer/v1/"
+
+// OAuth2 scopes used by this API.
+const (
+	// View your email address
+	UserinfoEmailScope = "https://www.googleapis.com/auth/userinfo.email"
+)
+
+func New(client *http.Client) (*Service, error) {
+	if client == nil {
+		return nil, errors.New("client is nil")
+	}
+	s := &Service{client: client, BasePath: basePath}
+	return s, nil
+}
+
+type Service struct {
+	client   *http.Client
+	BasePath string // API endpoint base URL
+}
+
+type PublicKey struct {
+	Base64 string `json:"base64,omitempty"`
+}
+
+type VSignature struct {
+	R string `json:"r,omitempty"`
+
+	S string `json:"s,omitempty"`
+}
+
+// method id "signer.publicKey":
+
+type PublicKeyCall struct {
+	s    *Service
+	opt_ map[string]interface{}
+}
+
+// PublicKey:
+func (s *Service) PublicKey() *PublicKeyCall {
+	c := &PublicKeyCall{s: s, opt_: make(map[string]interface{})}
+	return c
+}
+
+// Fields allows partial responses to be retrieved.
+// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
+// for more information.
+func (c *PublicKeyCall) Fields(s ...googleapi.Field) *PublicKeyCall {
+	c.opt_["fields"] = googleapi.CombineFields(s)
+	return c
+}
+
+func (c *PublicKeyCall) Do() (*PublicKey, error) {
+	var body io.Reader = nil
+	params := make(url.Values)
+	params.Set("alt", "json")
+	if v, ok := c.opt_["fields"]; ok {
+		params.Set("fields", fmt.Sprintf("%v", v))
+	}
+	urls := googleapi.ResolveRelative(c.s.BasePath, "publicKey")
+	urls += "?" + params.Encode()
+	req, _ := http.NewRequest("POST", urls, body)
+	googleapi.SetOpaque(req.URL)
+	req.Header.Set("User-Agent", "google-api-go-client/0.5")
+	res, err := c.s.client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer googleapi.CloseBody(res)
+	if err := googleapi.CheckResponse(res); err != nil {
+		return nil, err
+	}
+	var ret *PublicKey
+	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
+		return nil, err
+	}
+	return ret, nil
+	// {
+	//   "httpMethod": "POST",
+	//   "id": "signer.publicKey",
+	//   "path": "publicKey",
+	//   "response": {
+	//     "$ref": "PublicKey"
+	//   },
+	//   "scopes": [
+	//     "https://www.googleapis.com/auth/userinfo.email"
+	//   ]
+	// }
+
+}
+
+// method id "signer.sign":
+
+type SignCall struct {
+	s      *Service
+	base64 string
+	opt_   map[string]interface{}
+}
+
+// Sign:
+func (s *Service) Sign(base64 string) *SignCall {
+	c := &SignCall{s: s, opt_: make(map[string]interface{})}
+	c.base64 = base64
+	return c
+}
+
+// Fields allows partial responses to be retrieved.
+// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
+// for more information.
+func (c *SignCall) Fields(s ...googleapi.Field) *SignCall {
+	c.opt_["fields"] = googleapi.CombineFields(s)
+	return c
+}
+
+func (c *SignCall) Do() (*VSignature, error) {
+	var body io.Reader = nil
+	params := make(url.Values)
+	params.Set("alt", "json")
+	if v, ok := c.opt_["fields"]; ok {
+		params.Set("fields", fmt.Sprintf("%v", v))
+	}
+	urls := googleapi.ResolveRelative(c.s.BasePath, "sign/{base64}")
+	urls += "?" + params.Encode()
+	req, _ := http.NewRequest("POST", urls, body)
+	googleapi.Expand(req.URL, map[string]string{
+		"base64": c.base64,
+	})
+	req.Header.Set("User-Agent", "google-api-go-client/0.5")
+	res, err := c.s.client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer googleapi.CloseBody(res)
+	if err := googleapi.CheckResponse(res); err != nil {
+		return nil, err
+	}
+	var ret *VSignature
+	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
+		return nil, err
+	}
+	return ret, nil
+	// {
+	//   "httpMethod": "POST",
+	//   "id": "signer.sign",
+	//   "parameterOrder": [
+	//     "base64"
+	//   ],
+	//   "parameters": {
+	//     "base64": {
+	//       "location": "path",
+	//       "required": true,
+	//       "type": "string"
+	//     }
+	//   },
+	//   "path": "sign/{base64}",
+	//   "response": {
+	//     "$ref": "VSignature"
+	//   },
+	//   "scopes": [
+	//     "https://www.googleapis.com/auth/userinfo.email"
+	//   ]
+	// }
+
+}