diff --git a/.gitignore b/.gitignore index 6745c3fe..c37d6236 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,8 @@ gradle.properties -chaincli -smccli +*/chaincli +*/smccli profile.cov report.json diff --git a/server/scripts/setup.sh b/server/scripts/setup.sh index f9fe7377..c4bbb0d2 100755 --- a/server/scripts/setup.sh +++ b/server/scripts/setup.sh @@ -35,7 +35,7 @@ tmux send-keys -t ${S}:smc.0 "./publish_roster.sh" C-m tmux select-pane -t ${S}:smc.0 tmux send-keys -t ${S}:smc.0 "# TMUX MINI CHEAT SHEET" C-m tmux send-keys -t ${S}:smc.0 "# Use 'tmux lscm' to list tmux commands" C-m -tmux send-keys -t ${S}:smc.0 "# Use 'Ctrl+B N (or P)' for next (previous) window" C-m +tmux send-keys -t ${S}:smc.0 "# Use 'Ctrl+B N (or P)' for next (or previous) window" C-m tmux send-keys -t ${S}:smc.0 "# Use 'Ctrl+B ' to select pane" C-m tmux send-keys -t ${S}:smc.0 "# './teardown.sh' to clean this tmux session" C-m tmux attach -t ${S} diff --git a/server/scripts/start_chain.sh b/server/scripts/start_chain.sh index 03c9d06c..e4a33d55 100755 --- a/server/scripts/start_chain.sh +++ b/server/scripts/start_chain.sh @@ -59,7 +59,7 @@ do echo -e "${GREEN}creating node #${i} on port ${p}${NC}" # session s, window 0, panes 1 to N tmux send-keys -t ${S}:${W}.${i} "LLVL=${L} LOGF=./${W}${i}.log chaincli --config /tmp/${W}${i} start --listen tcp://127.0.0.1:${p}" C-m - sleep 0.5 + sleep 1 i=$((i + 1)); done @@ -71,6 +71,7 @@ while [ ${i} -le ${N} ] do echo -e "joining node ${i} on master pane ${MASTERPANE}" tmux send-keys -t "${MASTERPANE}" "chaincli --config /tmp/${W}${i} minogrpc join --address //127.0.0.1:${p} $(chaincli --config /tmp/${W}1 minogrpc token)" C-m + sleep 1 i=$((i + 1)); done diff --git a/server/scripts/start_smc.sh b/server/scripts/start_smc.sh index cf799d9e..c7a340c7 100755 --- a/server/scripts/start_smc.sh +++ b/server/scripts/start_smc.sh @@ -65,7 +65,9 @@ i=2; p=$((P + 1)) while [ ${i} -le ${N} ] do + sleep 1 tmux send-keys -t "${MASTERPANE}" "smccli --config /tmp/${W}${i} minogrpc join --address //127.0.0.1:${p} $(smccli --config /tmp/${W}1 minogrpc token)" C-m + sleep 1 i=$((i + 1)); done diff --git a/server/smc/smccli/web/action.go b/server/smc/smccli/web/action.go new file mode 100644 index 00000000..5e092b36 --- /dev/null +++ b/server/smc/smccli/web/action.go @@ -0,0 +1,264 @@ +package web + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/gorilla/mux" + "github.com/rs/zerolog/log" + "go.dedis.ch/dela" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/dkg" + "go.dedis.ch/dela/mino/proxy" + "go.dedis.ch/kyber/v3" + "go.dedis.ch/kyber/v3/suites" + + "golang.org/x/xerrors" +) + +// suite is the Kyber suite for Pedersen. +var suite = suites.MustFind("Ed25519") + +const separator = ":" +const malformedEncoded = "malformed encoded: %s" + +// RegisterAction is an action to register the HTTP handlers +// +// - implements node.ActionTemplate +type RegisterAction struct{} + +// Execute implements node.ActionTemplate. It registers the handlers using the +// default proxy from the injector. +func (a *RegisterAction) Execute(ctx node.Context) error { + var p proxy.Proxy + err := ctx.Injector.Resolve(&p) + if err != nil { + return xerrors.Errorf("failed to resolve proxy: %v", err) + } + + router := mux.NewRouter() + + pk := &pubKeyHandler{ctx} + router.HandleFunc("/smc/pubkey", pk.ServeHTTP).Methods("GET") + + re := &reencryptHandler{ctx} + router.HandleFunc("/smc/reencrypt", re.ServeHTTP).Methods("POST") + + router.NotFoundHandler = http.HandlerFunc(notFoundHandler) + router.MethodNotAllowedHandler = http.HandlerFunc(notAllowedHandler) + + p.RegisterHandler("/smc/", router.ServeHTTP) + + dela.Logger.Info().Msg("proxy handlers registered") + + return nil +} + +type pubKeyHandler struct { + ctx node.Context +} + +func (h *pubKeyHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) { + var a dkg.Actor + err := h.ctx.Injector.Resolve(&a) + if err != nil { + http.Error(w, fmt.Sprintf("failed to resolve DKG actor: %v", err), + http.StatusInternalServerError) + return + } + + pk, err := a.GetPublicKey() + if err != nil { + http.Error(w, fmt.Sprintf("failed retrieving public key: %v", err), + http.StatusInternalServerError) + return + } + + b, err := pk.MarshalBinary() + if err != nil { + http.Error(w, fmt.Sprintf("failed to marshal public key: %v", err), + http.StatusInternalServerError) + return + } + + encoder := json.NewEncoder(w) + err = encoder.Encode(b) + if err != nil { + http.Error(w, fmt.Sprintf("failed to respond: %v", err), + http.StatusInternalServerError) + return + } +} + +type reencryptHandler struct { + ctx node.Context +} + +func (h *reencryptHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var a dkg.Actor + err := h.ctx.Injector.Resolve(&a) + if err != nil { + http.Error(w, fmt.Sprintf("failed to resolve DKG actor: %v", err), + http.StatusInternalServerError) + return + } + + err = r.ParseForm() + if err != nil { + log.Err(err).Msg("failed to parse form") + http.Error(w, "failed to parse form", http.StatusInternalServerError) + return + } + + // XHATENC=$(smccli --config /tmp/smc1 dkg reencrypt --encrypted ${CIPHER} --pubk ${PUBK}) + + // retrieve the public key + pubkString := r.FormValue("pubk") + pubk, err := decodePublicKey(pubkString) + if err != nil { + http.Error(w, fmt.Sprintf("failed to decode public key str: %v", err), + http.StatusInternalServerError) + return + } + + // retrieve the encrypted cypher + encrypted := r.FormValue("encrypted") + k, _, err := decodeEncrypted(encrypted) + if err != nil { + http.Error(w, fmt.Sprintf("failed to decode encrypted str: %v", err), + http.StatusInternalServerError) + return + } + + // re-encrypt the message + hatenc, err := a.Reencrypt(k, pubk) + if err != nil { + http.Error(w, fmt.Sprintf("failed to re-encrypt: %v", err), + http.StatusInternalServerError) + return + } + + // write back the re-encrypted message + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + encoder := json.NewEncoder(w) + err = encoder.Encode(hatenc) + if err != nil { + http.Error(w, fmt.Sprintf("failed to encode response: %v", err), + http.StatusInternalServerError) + return + } + + dela.Logger.Debug().Msgf("Re-encrypted message: %v", hatenc) +} + +// ----------------------------------------------------------------------------- +// Helper functions +func decodePublicKey(str string) (kyber.Point, error) { + pkbuff, err := hex.DecodeString(str) + if err != nil { + return nil, xerrors.Errorf(malformedEncoded, str) + } + + pk := suite.Point() + err = pk.UnmarshalBinary(pkbuff) + if err != nil { + return nil, xerrors.Errorf("failed to unmarshal pk: %v", err) + } + + return pk, nil +} + +func decodeEncrypted(str string) (kyber.Point, []kyber.Point, error) { + parts := strings.Split(str, separator) + if len(parts) < 2 { + return nil, nil, xerrors.Errorf(malformedEncoded, str) + } + + // Decode K + kbuff, err := hex.DecodeString(parts[0]) + if err != nil { + return nil, nil, xerrors.Errorf("failed to decode k point: %v", err) + } + + k := suite.Point() + err = k.UnmarshalBinary(kbuff) + if err != nil { + return nil, nil, xerrors.Errorf("failed to unmarshal k point: %v", err) + } + + // Decode Cs + cs := make([]kyber.Point, 0, len(parts)-1) + for _, p := range parts[1:] { + cbuff, err := hex.DecodeString(p) + if err != nil { + return nil, nil, xerrors.Errorf("failed to decode c point: %v", err) + } + + c := suite.Point() + err = c.UnmarshalBinary(cbuff) + if err != nil { + return nil, nil, xerrors.Errorf("failed to unmarshal c point: %v", err) + } + + cs = append(cs, c) + } + + dela.Logger.Debug().Msgf("Decoded K: %v and Cs: %v", k, cs) + + return k, cs, nil +} + +// ----------------------------------------------------------------------------- +// Helper functions + +// HTTPError defines the standard error format +type HTTPError struct { + Title string + Code uint + Message string + Args map[string]interface{} +} + +// notFoundHandler defines a generic handler for 404 +func notFoundHandler(w http.ResponseWriter, r *http.Request) { + err := HTTPError{ + Title: "Not found", + Code: http.StatusNotFound, + Message: "The requested endpoint was not found", + Args: map[string]interface{}{ + "url": r.URL.String(), + "method": r.Method, + }, + } + + buf, _ := json.MarshalIndent(&err, "", " ") + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(http.StatusNotFound) + fmt.Fprintln(w, string(buf)) +} + +// notAllowedHandler degines a generic handler for 405 +func notAllowedHandler(w http.ResponseWriter, r *http.Request) { + err := HTTPError{ + Title: "Not allowed", + Code: http.StatusMethodNotAllowed, + Message: "The requested endpoint was not allowed", + Args: map[string]interface{}{ + "url": r.URL.String(), + "method": r.Method, + }, + } + + buf, _ := json.MarshalIndent(&err, "", " ") + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(http.StatusMethodNotAllowed) + fmt.Fprintln(w, string(buf)) +} diff --git a/server/smc/smccli/web/web.go b/server/smc/smccli/web/web.go new file mode 100644 index 00000000..ded79c84 --- /dev/null +++ b/server/smc/smccli/web/web.go @@ -0,0 +1,87 @@ +package web + +import ( + "os" + "time" + + "go.dedis.ch/dela" + "go.dedis.ch/dela/cli" + "go.dedis.ch/dela/cli/node" + "go.dedis.ch/dela/mino/proxy" + "go.dedis.ch/dela/mino/proxy/http" + "golang.org/x/xerrors" +) + +var defaultRetry = 10 +var proxyFac func(string) proxy.Proxy = http.NewHTTP + +const defaultProxyAddr = "127.0.0.1:3002" + +// NewController returns a new controller initializer +func NewController() node.Initializer { + return controller{} +} + +// controller is an initializer with a set of commands. +// +// - implements node.Initializer +type controller struct{} + +// Build implements node.Initializer. +func (m controller) SetCommands(builder node.Builder) { + builder.SetStartFlags( + cli.StringFlag{ + Name: "proxyaddr", + Usage: "the proxy address", + Required: false, + Value: defaultProxyAddr, + }, + ) +} + +// OnStart implements node.Initializer. It creates and registers a pedersen DKG. +func (m controller) OnStart(ctx cli.Flags, inj node.Injector) error { + dela.Logger.Info().Msg("Installing SMC proxy") + + proxyAddr := ctx.String("proxyaddr") + + proxyhttp := proxyFac(proxyAddr) + + inj.Inject(proxyhttp) + + go proxyhttp.Listen() + + for i := 0; i < defaultRetry && proxyhttp.GetAddr() == nil; i++ { + time.Sleep(time.Second) + } + + if proxyhttp.GetAddr() == nil { + return xerrors.Errorf("failed to start proxy server") + } + + // We assume the listen worked proprely, however it might not be the case. + // The log should inform the user about that. + dela.Logger.Info().Msgf("started proxy server on %s", proxyhttp.GetAddr().String()) + + // + // Register the smc proxy handlers + // + + register := RegisterAction{} + err := register.Execute(node.Context{ + Injector: inj, + Flags: node.FlagSet{}, + Out: os.Stdout, + }) + + if err != nil { + return xerrors.Errorf("failed to register SMC web handlers: %v", err) + } + + return nil +} + +// OnStop implements node.Initializer. +func (controller) OnStop(_ node.Injector) error { + return nil +}