blob: 6534a877bfe9ea7966859563dd81fee53ff5052e [file] [log] [blame]
package main
import (
"bytes"
"crypto/ecdsa"
"encoding/base64"
"fmt"
"runtime/ppapi"
"v.io/core/veyron/lib/websocket"
_ "v.io/core/veyron/profiles/chrome"
vsecurity "v.io/core/veyron/security"
"v.io/core/veyron2"
"v.io/core/veyron2/ipc"
"v.io/core/veyron2/security"
"v.io/core/veyron2/vdl"
"v.io/core/veyron2/vdl/valconv"
"v.io/core/veyron2/vlog"
"v.io/wspr/veyron/services/wsprd/browspr"
"v.io/wspr/veyron/services/wsprd/channel/channel_nacl"
)
func main() {
ppapi.Init(newBrowsprInstance)
}
// browsprInstance represents an instance of a PPAPI client and receives
// callbacks from PPAPI to handle events.
type browsprInstance struct {
ppapi.Instance
fs ppapi.FileSystem
browspr *browspr.Browspr
channel *channel_nacl.Channel
}
var _ ppapi.InstanceHandlers = (*browsprInstance)(nil)
func newBrowsprInstance(inst ppapi.Instance) ppapi.InstanceHandlers {
browsprInst := &browsprInstance{Instance: inst}
browsprInst.initFileSystem()
// Give the websocket interface the ppapi instance.
websocket.PpapiInstance = inst
// Set up the channel and register start rpc handler.
browsprInst.channel = channel_nacl.NewChannel(inst)
browsprInst.channel.RegisterRequestHandler("start", browsprInst.HandleStartMessage)
return browsprInst
}
func (inst *browsprInstance) initFileSystem() {
var err error
// Create a filesystem.
if inst.fs, err = inst.CreateFileSystem(ppapi.PP_FILESYSTEMTYPE_LOCALPERSISTENT); err != nil {
panic(err.Error())
}
if ty := inst.fs.Type(); ty != ppapi.PP_FILESYSTEMTYPE_LOCALPERSISTENT {
panic(fmt.Errorf("unexpected filesystem type: %d", ty))
}
// Open filesystem with expected size of 2K
if err = inst.fs.OpenFS(1 << 11); err != nil {
panic(fmt.Errorf("failed to open filesystem:%s", err))
}
// Create directory to store browspr keys
if err = inst.fs.MkdirAll(browsprDir); err != nil {
panic(fmt.Errorf("failed to create directory:%s", err))
}
}
const browsprDir = "/browspr/data"
// Loads a saved key if one exists, otherwise creates a new one and persists it.
func (inst *browsprInstance) initKey() (*ecdsa.PrivateKey, error) {
var ecdsaKey *ecdsa.PrivateKey
browsprKeyFile := browsprDir + "/privateKey.pem."
// See whether we have any cached keys for WSPR
if rFile, err := inst.fs.Open(browsprKeyFile); err == nil {
fmt.Print("Opening cached browspr ecdsaPrivateKey")
defer rFile.Release()
key, err := vsecurity.LoadPEMKey(rFile, nil)
if err != nil {
return nil, fmt.Errorf("failed to load browspr key:%s", err)
}
var ok bool
if ecdsaKey, ok = key.(*ecdsa.PrivateKey); !ok {
return nil, fmt.Errorf("got key of type %T, want *ecdsa.PrivateKey", key)
}
} else {
fmt.Print("Generating new browspr ecdsaPrivateKey")
// Generate new keys and store them.
var err error
if _, ecdsaKey, err = vsecurity.NewPrincipalKey(); err != nil {
return nil, fmt.Errorf("failed to generate security key:%s", err)
}
// Persist the keys in a local file.
wFile, err := inst.fs.Create(browsprKeyFile)
if err != nil {
return nil, fmt.Errorf("failed to create file to persist browspr keys:%s", err)
}
defer wFile.Release()
var b bytes.Buffer
if err = vsecurity.SavePEMKey(&b, ecdsaKey, nil); err != nil {
return nil, fmt.Errorf("failed to save browspr key:%s", err)
}
if n, err := wFile.Write(b.Bytes()); n != b.Len() || err != nil {
return nil, fmt.Errorf("failed to write browspr key:%s", err)
}
}
return ecdsaKey, nil
}
func (inst *browsprInstance) newPersistantPrincipal(peerNames []string) (security.Principal, error) {
ecdsaKey, err := inst.initKey()
if err != nil {
return nil, fmt.Errorf("failed to initialize ecdsa key:%s", err)
}
roots, err := browspr.NewFileSerializer(browsprDir+"/blessingroots.data", browsprDir+"/blessingroots.sig", inst.fs)
if err != nil {
return nil, fmt.Errorf("failed to create blessing roots serializer:%s", err)
}
store, err := browspr.NewFileSerializer(browsprDir+"/blessingstore.data", browsprDir+"/blessingstore.sig", inst.fs)
if err != nil {
return nil, fmt.Errorf("failed to create blessing store serializer:%s", err)
}
state := &vsecurity.PrincipalStateSerializer{
BlessingRoots: roots,
BlessingStore: store,
}
return vsecurity.NewPrincipalFromSigner(security.NewInMemoryECDSASigner(ecdsaKey), state)
}
type startMessage struct {
Identityd string
IdentitydBlessingRoot blessingRoot
Proxy string
NamespaceRoot string
}
// Copied from
// v.io/core/veyron/services/identity/handlers/blessing_root.go, since
// depcop prohibits importing that package.
type blessingRoot struct {
Names []string `json:"names"`
PublicKey string `json:"publicKey"`
}
// Base64-decode and unmarshal a public key.
func decodeAndUnmarshalPublicKey(k string) (security.PublicKey, error) {
decodedK, err := base64.URLEncoding.DecodeString(k)
if err != nil {
return nil, err
}
return security.UnmarshalPublicKey(decodedK)
}
func (inst *browsprInstance) HandleStartMessage(val *vdl.Value) (interface{}, error) {
fmt.Println("Starting Browspr")
var msg startMessage
if err := valconv.Convert(&msg, val); err != nil {
return nil, err
}
listenSpec := ipc.ListenSpec{
Proxy: msg.Proxy,
Addrs: ipc.ListenAddrs{{Protocol: "ws", Address: ""}},
}
principal, err := inst.newPersistantPrincipal(msg.IdentitydBlessingRoot.Names)
if err != nil {
return nil, err
}
blessingName := "browspr-default-blessing"
blessing, err := principal.BlessSelf(blessingName)
if err != nil {
return nil, fmt.Errorf("principal.BlessSelf(%v) failed: %v", blessingName, err)
}
// If msg.IdentitydBlessingRoot has a public key and names, then add
// the public key to our set of trusted roots, and limit our blessing
// to only talk to those names.
if msg.IdentitydBlessingRoot.PublicKey != "" {
if len(msg.IdentitydBlessingRoot.Names) == 0 {
return nil, fmt.Errorf("invalid IdentitydBlessingRoot: Names is empty")
}
fmt.Printf("Using blessing roots for identity with key %v and names %v", msg.IdentitydBlessingRoot.PublicKey, msg.IdentitydBlessingRoot.Names)
key, err := decodeAndUnmarshalPublicKey(msg.IdentitydBlessingRoot.PublicKey)
if err != nil {
vlog.Fatalf("decodeAndUnmarshalPublicKey(%v) failed: %v", msg.IdentitydBlessingRoot.PublicKey, err)
}
for _, name := range msg.IdentitydBlessingRoot.Names {
pattern := security.BlessingPattern(name)
// Trust the identity servers blessing root.
principal.Roots().Add(key, pattern)
// Use our blessing to only talk to the identity server.
if _, err := principal.BlessingStore().Set(blessing, pattern); err != nil {
return nil, fmt.Errorf("principal.BlessingStore().Set(%v, %v) failed: %v", blessing, pattern, err)
}
}
} else {
fmt.Printf("IdentitydBlessingRoot.PublicKey is empty. Will allow browspr blessing to be shareable with all principals.")
// Set our blessing as shareable with all peers.
if _, err := principal.BlessingStore().Set(blessing, security.AllPrincipals); err != nil {
return nil, fmt.Errorf("principal.BlessingStore().Set(%v, %v) failed: %v", blessing, security.AllPrincipals, err)
}
}
// Initialize the runtime.
// TODO(suharshs,mattr): Should we worried about not shutting down here?
ctx, _ := veyron2.Init()
ctx, err = veyron2.SetPrincipal(ctx, principal)
if err != nil {
return nil, err
}
// TODO(ataly, bprosnitz, caprita): The runtime MUST be cleaned up
// after use. Figure out the appropriate place to add the Cleanup call.
veyron2.GetNamespace(ctx).SetRoots(msg.NamespaceRoot)
fmt.Printf("Starting browspr with config: proxy=%q mounttable=%q identityd=%q identitydBlessingRoot=%q ", msg.Proxy, msg.NamespaceRoot, msg.Identityd, msg.IdentitydBlessingRoot)
inst.browspr = browspr.NewBrowspr(ctx,
inst.BrowsprOutgoingPostMessage,
&listenSpec,
msg.Identityd,
[]string{msg.NamespaceRoot})
// Add the rpc handlers that depend on inst.browspr.
inst.channel.RegisterRequestHandler("auth:create-account", inst.browspr.HandleAuthCreateAccountRpc)
inst.channel.RegisterRequestHandler("auth:associate-account", inst.browspr.HandleAuthAssociateAccountRpc)
inst.channel.RegisterRequestHandler("auth:get-accounts", inst.browspr.HandleAuthGetAccountsRpc)
inst.channel.RegisterRequestHandler("auth:origin-has-account", inst.browspr.HandleAuthOriginHasAccountRpc)
inst.channel.RegisterRequestHandler("cleanup", inst.browspr.HandleCleanupRpc)
return nil, nil
}
func (inst *browsprInstance) BrowsprOutgoingPostMessage(instanceId int32, ty string, message string) {
if message == "" {
// TODO(nlacasse,bprosnitz): VarFromString crashes if the
// string is empty, so we must use a placeholder.
message = "."
}
dict := ppapi.NewDictVar()
instVar := ppapi.VarFromInt(instanceId)
bodyVar := ppapi.VarFromString(message)
tyVar := ppapi.VarFromString(ty)
dict.DictionarySet("instanceId", instVar)
dict.DictionarySet("type", tyVar)
dict.DictionarySet("body", bodyVar)
inst.PostMessage(dict)
instVar.Release()
bodyVar.Release()
tyVar.Release()
dict.Release()
}
// HandleBrowsprMessage handles one-way messages of the type "browsprMsg" by
// sending them to browspr's handler.
func (inst *browsprInstance) HandleBrowsprMessage(instanceId int32, origin string, message ppapi.Var) error {
str, err := message.AsString()
if err != nil {
// TODO(bprosnitz) Remove. We shouldn't panic on user input.
return fmt.Errorf("Error while converting message to string: %v", err)
}
fmt.Printf("Calling browspr's HandleMessage: instanceId %d origin %s message %s", instanceId, origin, str)
if err := inst.browspr.HandleMessage(instanceId, origin, str); err != nil {
// TODO(bprosnitz) Remove. We shouldn't panic on user input.
return fmt.Errorf("Error while handling message in browspr: %v", err)
}
return nil
}
// HandleIntentionalPanic intentionally triggers a panic. This is used in tests of the extension's crash handling behavior.
// TODO(bprosnitz) We probably should conditionally compile this in via build tags so we don't hit it in production code.
func (inst *browsprInstance) HandleIntentionalPanic(instanceId int32, origin string, message ppapi.Var) error {
panic("Crashing intentionally")
}
// HandleBrowsprRpc handles two-way rpc messages of the type "browsprRpc"
// sending them to the channel's handler.
func (inst *browsprInstance) HandleBrowsprRpc(instanceId int32, origin string, message ppapi.Var) error {
fmt.Printf("Got to HandleBrowsprRpc: instanceId: %d origin %s", instanceId, origin)
inst.channel.HandleMessage(message)
return nil
}
// handleGoError handles error returned by go code.
func (inst *browsprInstance) handleGoError(err error) {
vlog.VI(2).Info(err)
inst.LogString(ppapi.PP_LOGLEVEL_ERROR, fmt.Sprintf("Error in go code: %v", err.Error()))
vlog.Error(err)
}
// HandleMessage receives messages from Javascript and uses them to perform actions.
// A message is of the form {"type": "typeName", "body": { stuff here }},
// where the body is passed to the message handler.
func (inst *browsprInstance) HandleMessage(message ppapi.Var) {
fmt.Printf("Got to HandleMessage")
instanceId, err := message.LookupIntValuedKey("instanceId")
if err != nil {
inst.handleGoError(err)
return
}
origin, err := message.LookupStringValuedKey("origin")
if err != nil {
inst.handleGoError(err)
return
}
ty, err := message.LookupStringValuedKey("type")
if err != nil {
inst.handleGoError(err)
return
}
var messageHandlers = map[string]func(int32, string, ppapi.Var) error{
"browsprMsg": inst.HandleBrowsprMessage,
"browsprRpc": inst.HandleBrowsprRpc,
"intentionallyPanic": inst.HandleIntentionalPanic,
}
h, ok := messageHandlers[ty]
if !ok {
inst.handleGoError(fmt.Errorf("No handler found for message type: %q", ty))
return
}
body, err := message.LookupKey("body")
if err != nil {
body = ppapi.VarUndefined
}
err = h(int32(instanceId), origin, body)
body.Release()
if err != nil {
inst.handleGoError(err)
}
}
func (inst browsprInstance) DidCreate(args map[string]string) bool {
fmt.Printf("Got to DidCreate")
return true
}
func (*browsprInstance) DidDestroy() {
fmt.Printf("Got to DidDestroy()")
}
func (*browsprInstance) DidChangeView(view ppapi.View) {
fmt.Printf("Got to DidChangeView(%v)", view)
}
func (*browsprInstance) DidChangeFocus(has_focus bool) {
fmt.Printf("Got to DidChangeFocus(%v)", has_focus)
}
func (*browsprInstance) HandleDocumentLoad(url_loader ppapi.Resource) bool {
fmt.Printf("Got to HandleDocumentLoad(%v)", url_loader)
return true
}
func (*browsprInstance) HandleInputEvent(event ppapi.InputEvent) bool {
fmt.Printf("Got to HandleInputEvent(%v)", event)
return true
}
func (*browsprInstance) Graphics3DContextLost() {
fmt.Printf("Got to Graphics3DContextLost()")
}
func (*browsprInstance) MouseLockLost() {
fmt.Printf("Got to MouseLockLost()")
}