From 9c65ad4af34c1c2c7a8750f4762abf08b7cc1d66 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Mon, 3 Oct 2022 11:00:36 -0700 Subject: [PATCH] Add cwtchtools, add make payment tool --- .gitignore | 5 +- protocol/connections/engine.go | 2 +- protocol/connections/make_payment.go | 59 +++++++++++ tools/cwtch_tools.go | 150 +++++++++++++++++++++++++++ utils/timeoutpolicy.go | 1 - 5 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 protocol/connections/make_payment.go create mode 100644 tools/cwtch_tools.go diff --git a/.gitignore b/.gitignore index 7a57b32..4d509d2 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,7 @@ tokens1.db arch/ testing/encryptedstorage/encrypted_storage_profiles testing/encryptedstorage/tordir -*.tar.gz \ No newline at end of file +*.tar.gz +data-dir-cwtchtool/ +tokens +tordir/ \ No newline at end of file diff --git a/protocol/connections/engine.go b/protocol/connections/engine.go index 864e5f6..64866e4 100644 --- a/protocol/connections/engine.go +++ b/protocol/connections/engine.go @@ -696,7 +696,7 @@ func (e *engine) handlePeerMessage(hostname string, eventID string, context stri e.eventManager.Publish(event.NewEvent(event.NewMessageFromPeerEngine, map[event.Field]string{event.TimestampReceived: time.Now().Format(time.RFC3339Nano), event.RemotePeer: hostname, event.Data: string(message)})) // Send an explicit acknowledgement - // Every other protocol should have a explicit acknowledgement message e.g. value lookups have responses, and file handling has an explicit flow + // Every other protocol should have an explicit acknowledgement message e.g. value lookups have responses, and file handling has an explicit flow if err := e.sendPeerMessage(hostname, pmodel.PeerMessage{ID: eventID, Context: event.ContextAck, Data: []byte{}}); err != nil { e.eventManager.Publish(event.NewEvent(event.SendMessageToPeerError, map[event.Field]string{event.RemotePeer: hostname, event.EventID: eventID, event.Error: err.Error()})) } diff --git a/protocol/connections/make_payment.go b/protocol/connections/make_payment.go new file mode 100644 index 0000000..0bbb34d --- /dev/null +++ b/protocol/connections/make_payment.go @@ -0,0 +1,59 @@ +package connections + +import ( + "cwtch.im/cwtch/utils" + "git.openprivacy.ca/cwtch.im/tapir/applications" + "git.openprivacy.ca/cwtch.im/tapir/networks/tor" + "git.openprivacy.ca/cwtch.im/tapir/primitives" + "git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass" + "git.openprivacy.ca/openprivacy/connectivity" + "git.openprivacy.ca/openprivacy/log" + "reflect" + "time" +) + +// MakePayment uses the PoW based token protocol to obtain more tokens +func MakePayment(tokenServiceOnion string, tokenService *privacypass.TokenServer, acn connectivity.ACN, handler TokenBoardHandler) error { + log.Debugf("making a payment") + id, sk := primitives.InitializeEphemeralIdentity() + client := new(tor.BaseOnionService) + client.Init(acn, sk, &id) + defer client.Shutdown() + + tokenApplication := new(applications.TokenApplication) + tokenApplication.TokenService = tokenService + powTokenApp := new(applications.ApplicationChain). + ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability). + ChainApplication(tokenApplication, applications.HasTokensCapability) + + log.Debugf("waiting for successful PoW auth...") + tp := utils.TimeoutPolicy(time.Second * 30) + err := tp.ExecuteAction(func() error { + connected, err := client.Connect(tokenServiceOnion, powTokenApp) + if connected && err == nil { + log.Debugf("waiting for successful token acquisition...") + conn, err := client.WaitForCapabilityOrClose(tokenServiceOnion, applications.HasTokensCapability) + if err == nil { + powtapp, ok := conn.App().(*applications.TokenApplication) + if ok { + log.Debugf("updating tokens") + handler.NewTokenHandler(tokenServiceOnion, powtapp.Tokens) + log.Debugf("transcript: %v", powtapp.Transcript().OutputTranscriptToAudit()) + conn.Close() + return nil + } + log.Errorf("invalid cast of powapp. this should never happen %v %v", powtapp, reflect.TypeOf(conn.App())) + return nil + } + return nil + } + return err + }) + + // we timed out + if err != nil { + log.Debugf("make payment timeout...") + return err + } + return err +} diff --git a/tools/cwtch_tools.go b/tools/cwtch_tools.go new file mode 100644 index 0000000..9375227 --- /dev/null +++ b/tools/cwtch_tools.go @@ -0,0 +1,150 @@ +package main + +import ( + "crypto/rand" + "cwtch.im/cwtch/model" + "cwtch.im/cwtch/model/constants" + "cwtch.im/cwtch/protocol/connections" + "cwtch.im/cwtch/protocol/groups" + "encoding/base64" + "encoding/json" + "flag" + "fmt" + "git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass" + "git.openprivacy.ca/openprivacy/connectivity/tor" + "git.openprivacy.ca/openprivacy/log" + "github.com/gtank/ristretto255" + mrand "math/rand" + "os" + path "path/filepath" + "strings" + "time" +) + +var tool = flag.String("tool", "", "the tool to use") +var bundle = flag.String("bundle", "", "a server bundle to parse") + +func main() { + flag.Parse() + + if *tool == "" { + fmt.Fprintf(os.Stderr, "usage: cwtch_tools -tool \n") + flag.PrintDefaults() + } + + switch *tool { + case "bundleparse": + bundle, err := bundleParse(*bundle) + if err == nil { + fmt.Printf("bundle: %s\n", bundle.Serialize()) + } else { + fmt.Printf("error parsing bundle: %v", err) + } + case "gettokens": + getTokens(*bundle) + default: + fmt.Fprintf(os.Stderr, "unknown tool: %s \n", *tool) + } + +} + +func bundleParse(bundle string) (*model.KeyBundle, error) { + if strings.HasPrefix(bundle, constants.ServerPrefix) { + bundle, err := base64.StdEncoding.DecodeString(bundle[len(constants.ServerPrefix):]) + if err != nil { + fmt.Printf("invalid server bundle: %v\n", err) + } + keyBundle, err := model.DeserializeAndVerify([]byte(bundle)) + return keyBundle, err + } else { + return nil, fmt.Errorf("unknown bundle prefix") + } +} + +func getTokens(bundle string) { + log.SetLevel(log.LevelDebug) + keyBundle, err := bundleParse(bundle) + if err != nil { + log.Errorf("error parsing keybundle: %v", err) + return + } + + privacyPassKey, err := keyBundle.GetKey(model.KeyTypePrivacyPass) + if err != nil { + log.Errorf("error parsing keybundle: %v", err) + return + } + + tokenServiceKey, err := keyBundle.GetKey(model.KeyTypeTokenOnion) + if err != nil { + log.Errorf("error parsing keybundle: %v", err) + return + } + + os.Mkdir("tordir", 0700) + dataDir := path.Join("tordir", "tor") + os.MkdirAll(dataDir, 0700) + + // we don't need real randomness for the port, just to avoid a possible conflict... + mrand.Seed(int64(time.Now().Nanosecond())) + socksPort := mrand.Intn(1000) + 9051 + controlPort := mrand.Intn(1000) + 9052 + + // generate a random password + key := make([]byte, 64) + _, err = rand.Read(key) + if err != nil { + log.Errorf("insufficient randomness: %v", err) + return + } + + torDataDir := "./data-dir-cwtchtool" + if err = os.MkdirAll(torDataDir, 0700); err != nil { + log.Errorf("could not create data dir") + return + } + + tor.NewTorrc().WithSocksPort(socksPort).WithOnionTrafficOnly().WithHashedPassword(base64.StdEncoding.EncodeToString(key)).WithControlPort(controlPort).Build("tordir/tor/torrc") + acn, err := tor.NewTorACNWithAuth("./tordir", path.Join("..", "tor"), torDataDir, controlPort, tor.HashedPasswordAuthenticator{Password: base64.StdEncoding.EncodeToString(key)}) + if err != nil { + log.Errorf("Could not start Tor: %v", err) + return + } + acn.WaitTillBootstrapped() + defer acn.Close() + + Y := new(ristretto255.Element) + Y.UnmarshalText([]byte(privacyPassKey)) + + tokenServer := privacypass.NewTokenServer() + tokenServer.Y = Y + + err = connections.MakePayment(string(tokenServiceKey), tokenServer, acn, Handler{}) + if err != nil { + log.Errorf("timed out trying to get payments") + } +} + +type Handler struct { +} + +func (h Handler) GroupMessageHandler(server string, gm *groups.EncryptedGroupMessage) { +} + +func (h Handler) ServerAuthedHandler(server string) { +} + +func (h Handler) ServerSyncedHandler(server string) { +} + +func (h Handler) ServerClosedHandler(server string) { +} + +func (h Handler) NewTokenHandler(tokenService string, tokens []*privacypass.Token) { + data, _ := json.Marshal(tokens) + os.WriteFile("tokens", data, 0600) +} + +func (h Handler) FetchToken(tokenService string) (*privacypass.Token, int, error) { + return nil, 0, nil +} diff --git a/utils/timeoutpolicy.go b/utils/timeoutpolicy.go index 5c9dca4..d0c8fbc 100644 --- a/utils/timeoutpolicy.go +++ b/utils/timeoutpolicy.go @@ -12,7 +12,6 @@ type TimeoutPolicy time.Duration // ExecuteAction runs a function and returns an error if it hasn't returned // by the time specified by TimeoutPolicy func (tp *TimeoutPolicy) ExecuteAction(action func() error) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(*tp)) defer cancel()