blob: 5b9db6b7d23d8f709ef463ce76ea157cc4b9c674 [file] [log] [blame]
// 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.
// Daemon browsprd implements the wspr web socket proxy as a Native Client
// executable, to be run as a Chrome extension.
package main
import (
"bytes"
"crypto/ecdsa"
"encoding/base64"
"fmt"
"runtime"
"runtime/ppapi"
"v.io/v23"
"v.io/v23/security"
"v.io/v23/vdl"
"v.io/x/lib/vlog"
vsecurity "v.io/x/ref/lib/security"
_ "v.io/x/ref/profiles/chrome"
"v.io/x/ref/profiles/internal/lib/websocket"
"v.io/x/ref/services/wspr/internal/browspr"
"v.io/x/ref/services/wspr/internal/channel/channel_nacl"
"v.io/x/ref/services/wspr/internal/rpc/server"
)
func main() {
security.OverrideCaveatValidation(server.CaveatValidation)
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 {
runtime.GOMAXPROCS(4)
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"
func (inst *browsprInstance) loadKeyFromStorage(browsprKeyFile string) (*ecdsa.PrivateKey, error) {
vlog.VI(1).Infof("Attempting to read key from file %v", browsprKeyFile)
rFile, err := inst.fs.Open(browsprKeyFile)
if err != nil {
vlog.VI(1).Infof("Key not found in file %v", browsprKeyFile)
return nil, err
}
vlog.VI(1).Infof("Attempting to load cached browspr ecdsaPrivateKey in file %v", browsprKeyFile)
defer rFile.Release()
key, err := vsecurity.LoadPEMKey(rFile, nil)
if err != nil {
return nil, fmt.Errorf("failed to load browspr key:%s", err)
}
if ecdsaKey, ok := key.(*ecdsa.PrivateKey); !ok {
return nil, fmt.Errorf("got key of type %T, want *ecdsa.PrivateKey", key)
} else {
return ecdsaKey, nil
}
}
// Loads a saved key if one exists, otherwise creates a new one and persists it.
func (inst *browsprInstance) initKey() (*ecdsa.PrivateKey, error) {
browsprKeyFile := browsprDir + "/privateKey.pem."
if ecdsaKey, err := inst.loadKeyFromStorage(browsprKeyFile); err == nil {
return ecdsaKey, nil
} else {
vlog.VI(1).Infof("inst.loadKeyFromStorage(%v) failed: %v", browsprKeyFile, err)
}
vlog.VI(1).Infof("Generating new browspr ecdsaPrivateKey")
// Generate new keys and store them.
var ecdsaKey *ecdsa.PrivateKey
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) newPrincipal(ecdsaKey *ecdsa.PrivateKey, blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig string) (security.Principal, error) {
roots, err := browspr.NewFileSerializer(blessingRootsData, blessingRootsSig, inst.fs)
if err != nil {
return nil, fmt.Errorf("failed to create blessing roots serializer:%s", err)
}
store, err := browspr.NewFileSerializer(blessingStoreData, blessingStoreSig, 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)
}
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)
}
blessingRootsData := browsprDir + "/blessingroots.data"
blessingRootsSig := browsprDir + "/blessingroots.sig"
blessingStoreData := browsprDir + "/blessingstore.data"
blessingStoreSig := browsprDir + "/blessingstore.sig"
principal, err := inst.newPrincipal(ecdsaKey, blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig)
if err != nil {
vlog.VI(1).Infof("inst.newPrincipal(%v, %v, %v, %v, %v) failed: %v", ecdsaKey, blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig)
// Delete the files and try again.
for _, file := range []string{blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig} {
inst.fs.Remove(file)
}
principal, err = inst.newPrincipal(ecdsaKey, blessingRootsData, blessingRootsSig, blessingStoreData, blessingStoreSig)
}
return principal, err
}
// 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) (*vdl.Value, error) {
vlog.VI(1).Info("Starting Browspr")
var msg browspr.StartMessage
if err := vdl.Convert(&msg, val); err != nil {
return nil, fmt.Errorf("HandleStartMessage did not receive StartMessage, received: %v, %v", val, err)
}
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")
}
vlog.VI(1).Infof("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 {
vlog.VI(1).Infof("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, _ := v23.Init()
ctx, err = v23.WithPrincipal(ctx, principal)
if err != nil {
return nil, err
}
// Configure logger with level and module from start message.
vlog.VI(1).Infof("Configuring vlog with v=%v, modulesSpec=%v", msg.LogLevel, msg.LogModule)
moduleSpec := vlog.ModuleSpec{}
moduleSpec.Set(msg.LogModule)
if err := vlog.Log.Configure(vlog.OverridePriorConfiguration(true), vlog.Level(msg.LogLevel), moduleSpec); 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.
v23.GetNamespace(ctx).SetRoots(msg.NamespaceRoot)
listenSpec := v23.GetListenSpec(ctx)
listenSpec.Proxy = msg.Proxy
vlog.VI(1).Infof("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("create-instance", inst.browspr.HandleCreateInstanceRpc)
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 {
return fmt.Errorf("Error while converting message to string: %v", err)
}
vlog.VI(1).Infof("Calling browspr's HandleMessage: instanceId %d origin %s message %s", instanceId, origin, str)
if err := inst.browspr.HandleMessage(instanceId, origin, str); err != nil {
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 {
// NOTE(nlacasse): Calling panic directly (not inside a goroutine)
// sometimes blocks during the panic, causing the plugin to not emit a
// "crash" event. I'm not sure why this happens, but it breaks the
// test-nacl-plugin-crash test. Calling panic from within a goroutine seems
// to reliably panic "all the way" and emit a "crash" event.
go panic("Crashing intentionally")
return nil
}
// 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 {
vlog.VI(1).Infof("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) {
vlog.VI(2).Infof("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 {
vlog.VI(2).Infof("Got to DidCreate")
return true
}
func (*browsprInstance) DidDestroy() {
vlog.VI(2).Infof("Got to DidDestroy()")
}
func (*browsprInstance) DidChangeView(view ppapi.View) {
vlog.VI(2).Infof("Got to DidChangeView(%v)", view)
}
func (*browsprInstance) DidChangeFocus(has_focus bool) {
vlog.VI(2).Infof("Got to DidChangeFocus(%v)", has_focus)
}
func (*browsprInstance) HandleDocumentLoad(url_loader ppapi.Resource) bool {
vlog.VI(2).Infof("Got to HandleDocumentLoad(%v)", url_loader)
return true
}
func (*browsprInstance) HandleInputEvent(event ppapi.InputEvent) bool {
vlog.VI(2).Infof("Got to HandleInputEvent(%v)", event)
return true
}
func (*browsprInstance) Graphics3DContextLost() {
vlog.VI(2).Infof("Got to Graphics3DContextLost()")
}
func (*browsprInstance) MouseLockLost() {
vlog.VI(2).Infof("Got to MouseLockLost()")
}