diff --git a/go.mod b/go.mod index e907f3a..cfcb967 100644 --- a/go.mod +++ b/go.mod @@ -7,23 +7,11 @@ require ( github.com/kalafut/imohash v1.1.1 github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b github.com/minio/highwayhash v1.0.4 - github.com/schollz/cli/v2 v2.2.1 - github.com/schollz/logger v1.2.0 - github.com/schollz/pake/v3 v3.1.1 - github.com/schollz/progressbar/v3 v3.19.1 golang.org/x/crypto v0.53.0 golang.org/x/net v0.56.0 ) require ( - filippo.io/edwards25519 v1.2.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/stretchr/testify v1.11.1 // indirect - github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406 // indirect github.com/twmb/murmur3 v1.1.8 // indirect golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect ) diff --git a/go.sum b/go.sum index 0d56269..c1e384c 100644 --- a/go.sum +++ b/go.sum @@ -1,45 +1,11 @@ -filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= -filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= -github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= -github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kalafut/imohash v1.1.1 h1:G/HYtKgteQSVU96LidSJEbUGoZOMiBcuXYxbeb2W9e4= github.com/kalafut/imohash v1.1.1/go.mod h1:6cn9lU0Sj8M4eu9UaQm1kR/5y3k/ayB68yntRhGloL4= github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b h1:xZ59n7Frzh8CwyfAapUZLSg+gXH5m63YEaFCMpDHhpI= github.com/magisterquis/connectproxy v0.0.0-20200725203833-3582e84f0c9b/go.mod h1:uDd4sYVYsqcxAB8j+Q7uhL6IJCs/r1kxib1HV4bgOMg= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/minio/highwayhash v1.0.4 h1:asJizugGgchQod2ja9NJlGOWq4s7KsAWr5XUc9Clgl4= github.com/minio/highwayhash v1.0.4/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/schollz/cli/v2 v2.2.1 h1:ou22Mj7ZPjrKz+8k2iDTWaHskEEV5NiAxGrdsCL36VU= -github.com/schollz/cli/v2 v2.2.1/go.mod h1:My6bfphRLZUhZdlFUK8scAxMWHydE7k4s2ed2Dtnn+s= -github.com/schollz/logger v1.2.0 h1:5WXfINRs3lEUTCZ7YXhj0uN+qukjizvITLm3Ca2m0Ho= -github.com/schollz/logger v1.2.0/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM= -github.com/schollz/pake/v3 v3.1.1 h1:lyoU5uNQ3thfjEzrahgxWWBm6+pbI1F2KAZ3gs6LIV8= -github.com/schollz/pake/v3 v3.1.1/go.mod h1:420+m3AakXcS0n7Uwc7eRs2CosQ2YfE/vKcIkilvqZc= -github.com/schollz/progressbar/v3 v3.19.1 h1:iv8BgwOvdML/S3p84uBpy/IMigv4U9594vPZYa2EdrU= -github.com/schollz/progressbar/v3 v3.19.1/go.mod h1:LFL7jqimKxfhero4K1eCkUr/6R39AgQeiPCJtlTWIW8= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406 h1:sDWDZkwYqX0jvLWstKzFwh+pYhQNaVg65BgSkCP/f7U= -github.com/tscholl2/siec v0.0.0-20240310163802-c2c6f6198406/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw= github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= @@ -50,9 +16,3 @@ golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/share b/share index 985056d..ca688b5 100755 Binary files a/share and b/share differ diff --git a/src/cli/cli.go b/src/cli/cli.go index 9ccd3b4..de674a6 100644 --- a/src/cli/cli.go +++ b/src/cli/cli.go @@ -1,35 +1,26 @@ package cli import ( + "flag" "fmt" "os" "runtime" - "time" + "strconv" - "github.com/schollz/cli/v2" + log "github.com/tzgyn/share/src/logger" "github.com/tzgyn/share/src/models" "github.com/tzgyn/share/src/swarm" "github.com/tzgyn/share/src/tcp" "github.com/tzgyn/share/src/utils" - log "github.com/schollz/logger" ) var Version string -func Run() (err error) { - runtime.GOMAXPROCS(runtime.NumCPU()) +const usage = `share [GLOBAL OPTIONS] [COMMAND] [COMMAND OPTIONS] [filename(s)] - app := cli.NewApp() - app.Name = "share" - if Version == "" { - Version = "v0.1.0" - } - app.Version = Version - app.Compiled = time.Now() - app.Usage = "a torrent-like swarm: everyone who joins a room can send and receive" - app.UsageText = `share [GLOBAL OPTIONS] [COMMAND] [COMMAND OPTIONS] [filename(s)] +a torrent-like swarm: everyone who joins a room can send and receive - USAGE EXAMPLES: +USAGE EXAMPLES: Seed a file into a new room (prints a code): share send file.txt @@ -37,129 +28,165 @@ func Run() (err error) { share send --code secret-code file1.txt file2.txt Join a room to receive whatever's shared there (and help re-share it): - share secret-code` - app.Commands = []*cli.Command{ - { - Name: "send", - Usage: "seed file(s) into a room (see options with share send -h)", - Description: "seed file(s) into a swarm room so others can join and receive them", - ArgsUsage: "[filename(s)]", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "code", Aliases: []string{"c"}, Usage: "room codephrase (generated if not given)"}, - }, - HelpName: "share send", - Action: send, - }, - { - Name: "relay", - Usage: "start your own relay (optional)", - Description: "start relay", - HelpName: "share relay", - Action: relay, - Flags: []cli.Flag{ - &cli.StringFlag{Name: "host", Usage: "host of the relay"}, - &cli.IntFlag{Name: "port", Value: 9009, Usage: "port of the relay", EnvVars: []string{"SHARE_PORT"}}, - }, - }, - } - app.Flags = []cli.Flag{ - &cli.BoolFlag{Name: "internal-dns", Usage: "use a built-in DNS stub resolver rather than the host operating system"}, - &cli.BoolFlag{Name: "classic", Usage: "toggle between the classic mode (insecure due to local attack vector) and new mode (secure)"}, - &cli.BoolFlag{Name: "remember", Usage: "save these settings to reuse next time"}, - &cli.BoolFlag{Name: "debug", Usage: "toggle debug mode"}, - &cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"}, - &cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"}, - &cli.BoolFlag{Name: "no-compress", Usage: "disable compression"}, - &cli.BoolFlag{Name: "ask", Usage: "make sure sender and recipient are prompted"}, - &cli.BoolFlag{Name: "local", Usage: "force to use only local connections"}, - &cli.BoolFlag{Name: "ignore-stdin", Usage: "ignore piped stdin"}, - &cli.BoolFlag{Name: "overwrite", Usage: "do not prompt to overwrite or resume"}, - &cli.BoolFlag{Name: "testing", Usage: "flag for testing purposes"}, - &cli.BoolFlag{Name: "quiet", Usage: "disable all output"}, - &cli.BoolFlag{Name: "disable-clipboard", Usage: "disable copy to clipboard"}, - &cli.BoolFlag{Name: "extended-clipboard", Usage: "copy full command with secret as env variable to clipboard"}, - &cli.StringFlag{Name: "multicast", Value: "239.255.255.250", Usage: "multicast address to use for local discovery"}, - &cli.StringFlag{Name: "curve", Value: "p256", Usage: "choose an encryption curve"}, - &cli.StringFlag{Name: "ip", Value: "", Usage: "set sender ip if known e.g. 10.0.0.1:9009, [::1]:9009"}, - &cli.StringFlag{Name: "relay", Value: models.DEFAULT_RELAY, Usage: "address of the relay", EnvVars: []string{"SHARE_RELAY"}}, - &cli.StringFlag{Name: "relay6", Value: models.DEFAULT_RELAY6, Usage: "ipv6 address of the relay", EnvVars: []string{"SHARE_RELAY6"}}, - &cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"}, - &cli.BoolFlag{Name: "one-time", Usage: "exit once everything currently in the room is downloaded, instead of staying to help reseed it"}, - &cli.StringFlag{Name: "socks5", Value: "", Usage: "add a socks5 proxy", EnvVars: []string{"SOCKS5_PROXY"}}, - &cli.StringFlag{Name: "connect", Value: "", Usage: "add a http proxy", EnvVars: []string{"HTTP_PROXY"}}, - &cli.StringFlag{Name: "throttleUpload", Value: "", Usage: "throttle the upload speed e.g. 500k"}, - } - app.EnableBashCompletion = true - app.HideHelp = false - app.HideVersion = false - app.Action = func(c *cli.Context) error { - return receive(c) + share secret-code + +COMMANDS: + send seed file(s) into a room (see options with: share send -h) + relay start your own relay (optional; see options with: share relay -h) + +Run "share [command] -h" for the options of a given command. +` + +// Run parses os.Args and dispatches to the send, relay, or default +// (receive) command. The subcommand keyword ("send"/"relay") is found +// wherever it appears, not just as the very first argument, so flags can +// come before or after it (e.g. both "share --relay X send file" and +// "share send --code X file" work). +func Run() error { + runtime.GOMAXPROCS(runtime.NumCPU()) + if Version == "" { + Version = "v0.1.0" } - return app.Run(os.Args) + args := os.Args[1:] + if len(args) > 0 { + switch args[0] { + case "-h", "--help", "help": + fmt.Print(usage) + return nil + case "-v", "--version": + fmt.Println(Version) + return nil + } + } + + for i, a := range args { + switch a { + case "send": + return runSend(splice(args, i)) + case "relay": + return runRelay(splice(args, i)) + } + } + return runReceive(args) } -func setDebugLevel(c *cli.Context) { - if c.Bool("quiet") { +// splice removes the subcommand keyword at index i, leaving flags that +// appeared before and after it in one list for that subcommand's own +// flag.FlagSet to parse. +func splice(args []string, i int) []string { + out := make([]string, 0, len(args)-1) + out = append(out, args[:i]...) + out = append(out, args[i+1:]...) + return out +} + +func setDebugLevel(quiet, debug bool) { + switch { + case quiet: log.SetLevel("error") - } else if c.Bool("debug") { + case debug: log.SetLevel("debug") log.Debug("debug mode on") - } else { + default: log.SetLevel("info") } } -func send(c *cli.Context) (err error) { - setDebugLevel(c) - files := c.Args().Slice() +func defaultRelay() string { + if v := os.Getenv("SHARE_RELAY"); v != "" { + return v + } + return models.DEFAULT_RELAY +} + +func runSend(args []string) error { + fs := flag.NewFlagSet("send", flag.ExitOnError) + fs.Usage = func() { + fmt.Fprintln(os.Stderr, "share send [options] file1 [file2 ...]") + fs.PrintDefaults() + } + code := fs.String("code", "", "room codephrase (generated if not given)") + relay := fs.String("relay", defaultRelay(), "address of the relay") + debug := fs.Bool("debug", false, "toggle debug mode") + quiet := fs.Bool("quiet", false, "disable all output") + if err := fs.Parse(args); err != nil { + return err + } + setDebugLevel(*quiet, *debug) + + files := fs.Args() if len(files) == 0 { return fmt.Errorf("no files specified; usage: share send file1 [file2 ...]") } - code := c.String("code") - if code == "" { - code = utils.GetRandomName() + roomCode := *code + if roomCode == "" { + roomCode = utils.GetRandomName() } - fmt.Printf("Code is: %s\n", code) + fmt.Printf("Code is: %s\n", roomCode) fmt.Println("On the other computer, run:") - fmt.Printf("\tshare %s\n", code) + fmt.Printf("\tshare %s\n", roomCode) return swarm.Run(swarm.Options{ - RelayAddress: c.String("relay"), - Code: code, + RelayAddress: *relay, + Code: roomCode, Files: files, }) } -func receive(c *cli.Context) (err error) { - setDebugLevel(c) - code := c.Args().First() - if code == "" { - return cli.ShowAppHelp(c) +func runReceive(args []string) error { + fs := flag.NewFlagSet("share", flag.ExitOnError) + fs.Usage = func() { + fmt.Print(usage) + fs.PrintDefaults() } - outDir := c.String("out") - if outDir == "" { - outDir = "." + relay := fs.String("relay", defaultRelay(), "address of the relay") + out := fs.String("out", ".", "specify an output folder to receive the file") + oneTime := fs.Bool("one-time", false, "exit once everything currently in the room is downloaded, instead of staying to help reseed it") + debug := fs.Bool("debug", false, "toggle debug mode") + quiet := fs.Bool("quiet", false, "disable all output") + if err := fs.Parse(args); err != nil { + return err + } + setDebugLevel(*quiet, *debug) + + code := fs.Arg(0) + if code == "" { + fs.Usage() + return nil } return swarm.Run(swarm.Options{ - RelayAddress: c.String("relay"), + RelayAddress: *relay, Code: code, - OutDir: outDir, - OneTime: c.Bool("one-time"), + OutDir: *out, + OneTime: *oneTime, }) } -func relay(c *cli.Context) (err error) { - log.Infof("starting share relay version %v", Version) +func runRelay(args []string) error { + fs := flag.NewFlagSet("relay", flag.ExitOnError) + fs.Usage = func() { + fmt.Fprintln(os.Stderr, "share relay [options]") + fs.PrintDefaults() + } + defaultPort := 9009 + if v := os.Getenv("SHARE_PORT"); v != "" { + if p, err := strconv.Atoi(v); err == nil { + defaultPort = p + } + } + host := fs.String("host", "", "host of the relay") + port := fs.Int("port", defaultPort, "port of the relay") + debug := fs.Bool("debug", false, "toggle debug mode") + if err := fs.Parse(args); err != nil { + return err + } + debugString := "info" - if c.Bool("debug") { + if *debug { debugString = "debug" } - host := c.String("host") - port := c.Int("port") - if port == 0 { - port = 9009 - } - return tcp.Run(debugString, host, fmt.Sprintf("%d", port)) + log.Infof("starting share relay version %v", Version) + return tcp.Run(debugString, *host, strconv.Itoa(*port)) } diff --git a/src/comm/comm.go b/src/comm/comm.go index 1dd157b..0841178 100644 --- a/src/comm/comm.go +++ b/src/comm/comm.go @@ -13,7 +13,7 @@ import ( "github.com/magisterquis/connectproxy" "github.com/tzgyn/share/src/utils" - log "github.com/schollz/logger" + log "github.com/tzgyn/share/src/logger" "golang.org/x/net/proxy" ) diff --git a/src/logger/logger.go b/src/logger/logger.go new file mode 100644 index 0000000..7b5943d --- /dev/null +++ b/src/logger/logger.go @@ -0,0 +1,78 @@ +// Package logger is a minimal leveled logger, replacing the schollz/logger +// dependency with an in-repo equivalent covering the same call surface +// (SetLevel/SetOutput and Trace/Debug/Info/Warn/Error, each with an -f +// variant) used throughout this project. +package logger + +import ( + "fmt" + "io" + "log" + "os" +) + +type level int + +const ( + levelTrace level = iota + levelDebug + levelInfo + levelWarn + levelError +) + +var ( + current level = levelInfo + loggers = map[level]*log.Logger{ + levelTrace: log.New(os.Stdout, "[trace]\t", log.Ltime|log.Lmicroseconds|log.Lshortfile), + levelDebug: log.New(os.Stdout, "[debug]\t", log.Ltime|log.Lshortfile), + levelInfo: log.New(os.Stdout, "[info]\t", log.Ldate|log.Ltime), + levelWarn: log.New(os.Stdout, "[warn]\t", log.Ldate|log.Ltime), + levelError: log.New(os.Stdout, "[error]\t", log.Ldate|log.Ltime|log.Lshortfile), + } +) + +// SetOutput redirects all log levels to w. +func SetOutput(w io.Writer) { + for _, l := range loggers { + l.SetOutput(w) + } +} + +// SetLevel sets the minimum level that will be printed: one of +// "trace", "debug", "info", "warn", "error". Unrecognized values are +// treated as "trace" (print everything), matching the previous dependency's +// behavior. +func SetLevel(s string) { + switch s { + case "debug": + current = levelDebug + case "info": + current = levelInfo + case "warn": + current = levelWarn + case "error": + current = levelError + default: + current = levelTrace + } +} + +func output(lvl level, s string) { + if lvl < current { + return + } + loggers[lvl].Output(3, s) +} + +func Tracef(format string, v ...interface{}) { output(levelTrace, fmt.Sprintf(format, v...)) } +func Debugf(format string, v ...interface{}) { output(levelDebug, fmt.Sprintf(format, v...)) } +func Infof(format string, v ...interface{}) { output(levelInfo, fmt.Sprintf(format, v...)) } +func Warnf(format string, v ...interface{}) { output(levelWarn, fmt.Sprintf(format, v...)) } +func Errorf(format string, v ...interface{}) { output(levelError, fmt.Sprintf(format, v...)) } + +func Trace(v ...interface{}) { output(levelTrace, fmt.Sprint(v...)) } +func Debug(v ...interface{}) { output(levelDebug, fmt.Sprint(v...)) } +func Info(v ...interface{}) { output(levelInfo, fmt.Sprint(v...)) } +func Warn(v ...interface{}) { output(levelWarn, fmt.Sprint(v...)) } +func Error(v ...interface{}) { output(levelError, fmt.Sprint(v...)) } diff --git a/src/models/constants.go b/src/models/constants.go index e10e2d4..b90d083 100644 --- a/src/models/constants.go +++ b/src/models/constants.go @@ -9,7 +9,7 @@ import ( "time" "github.com/tzgyn/share/src/utils" - log "github.com/schollz/logger" + log "github.com/tzgyn/share/src/logger" ) // TCP_BUFFER_SIZE is the maximum packet size diff --git a/src/pake/LICENSE b/src/pake/LICENSE new file mode 100644 index 0000000..8662a07 --- /dev/null +++ b/src/pake/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Zack + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/pake/pake.go b/src/pake/pake.go new file mode 100644 index 0000000..11ed967 --- /dev/null +++ b/src/pake/pake.go @@ -0,0 +1,197 @@ +// Package pake implements a password-authenticated key exchange (a +// SPAKE2-style protocol) over the NIST P256 curve from Go's standard +// library. It's a trimmed fork of github.com/schollz/pake/v3, kept only +// for the P256 case this project actually uses, so no external module +// dependency (and no extra elliptic-curve implementations) are needed for +// it. See LICENSE for the original project's MIT license. +package pake + +import ( + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "math/big" +) + +// curve is fixed at P256; every peer link in this project uses the same +// curve, so there's no need for the multi-curve selection the original +// library offered. +var curve = elliptic.P256() + +// generator points U and V, fixed and known to both sides ahead of time +// (not sent over the wire). Same values as upstream pake's "p256" curve, +// verified below at init to still be valid P256 points. +var ( + Uᵤ, _ = new(big.Int).SetString("793136080485469241208656611513609866400481671852", 10) + Uᵥ, _ = new(big.Int).SetString("59748757929350367369315811184980635230185250460108398961713395032485227207304", 10) + Vᵤ, _ = new(big.Int).SetString("1086685267857089638167386722555472967068468061489", 10) + Vᵥ, _ = new(big.Int).SetString("9157340230202296554417312816309453883742349874205386245733062928888341584123", 10) + P = elliptic.P256().Params().P +) + +func init() { + if !curve.IsOnCurve(Uᵤ, Uᵥ) { + panic("pake: U is not on curve") + } + if !curve.IsOnCurve(Vᵤ, Vᵥ) { + panic("pake: V is not on curve") + } +} + +// Pake keeps public and private variables by only transmitting between +// parties after marshaling. +// +// This method follows +// https://crypto.stanford.edu/~dabo/cryptobook/BonehShoup_0_4.pdf +// Figure 21/15 +// http://www.lothar.com/~warner/MagicWormhole-PyCon2016.pdf +// Slide 11 +type Pake struct { + // Public variables + Role int + Xᵤ, Xᵥ *big.Int + Yᵤ, Yᵥ *big.Int + + // Private variables + Pw []byte + Vpwᵤ, Vpwᵥ *big.Int + Upwᵤ, Upwᵥ *big.Int + Aα []byte + Aαᵤ, Aαᵥ *big.Int + Zᵤ, Zᵥ *big.Int + K []byte +} + +// Public returns the public variables of Pake, safe to marshal and send. +func (p *Pake) Public() *Pake { + return &Pake{ + Role: p.Role, + Xᵤ: p.Xᵤ, + Xᵥ: p.Xᵥ, + Yᵤ: p.Yᵤ, + Yᵥ: p.Yᵥ, + } +} + +// InitCurve takes the shared weak passphrase (pw) to initialize the points +// on the curve. role is 0 for the side that goes first (e.g. the dialer) +// and 1 for the other side. +func InitCurve(pw []byte, role int) (p *Pake, err error) { + p = new(Pake) + p.Pw = pw + if role == 1 { + p.Role = 1 + return + } + p.Role = 0 + + // STEP: A computes X + p.Vpwᵤ, p.Vpwᵥ = curve.ScalarMult(Vᵤ, Vᵥ, p.Pw) + p.Upwᵤ, p.Upwᵥ = curve.ScalarMult(Uᵤ, Uᵥ, p.Pw) + p.Aα = make([]byte, 32) // randomly generated secret + if _, err = rand.Read(p.Aα); err != nil { + return + } + p.Aαᵤ, p.Aαᵥ = curve.ScalarBaseMult(p.Aα) + p.Xᵤ, p.Xᵥ = curve.Add(p.Upwᵤ, p.Upwᵥ, p.Aαᵤ, p.Aαᵥ) // "X" + // now X should be sent to B + return +} + +// Bytes marshals the Pake structure so that private variables are hidden. +func (p *Pake) Bytes() (b []byte) { + if p == nil { + panic("pake is not initialized") + } + b, err := json.Marshal(p.Public()) + if err != nil { + panic(err) + } + return +} + +// Update updates itself with the other party's marshaled Pake and +// automatically determines what stage it's at and what to generate next. +func (p *Pake) Update(qBytes []byte) (err error) { + if p == nil { + return fmt.Errorf("pake is not initialized") + } + var q *Pake + if err = json.Unmarshal(qBytes, &q); err != nil { + return + } + if p.Role == q.Role { + return errors.New("can't have its own role") + } + + if p.Role == 1 { + // copy over public variables + p.Xᵤ, p.Xᵥ = q.Xᵤ, q.Xᵥ + if !curve.IsOnCurve(p.Xᵤ, p.Xᵥ) { + return errors.New("X values not on curve") + } + + // STEP: B computes Y + p.Vpwᵤ, p.Vpwᵥ = curve.ScalarMult(Vᵤ, Vᵥ, p.Pw) + p.Upwᵤ, p.Upwᵥ = curve.ScalarMult(Uᵤ, Uᵥ, p.Pw) + p.Aα = make([]byte, 32) // randomly generated secret + if _, err = rand.Read(p.Aα); err != nil { + return + } + p.Aαᵤ, p.Aαᵥ = curve.ScalarBaseMult(p.Aα) + p.Yᵤ, p.Yᵥ = curve.Add(p.Vpwᵤ, p.Vpwᵥ, p.Aαᵤ, p.Aαᵥ) // "Y" + + // STEP: B computes Z + v := new(big.Int).Neg(p.Upwᵥ) + v.Mod(v, P) + p.Zᵤ, p.Zᵥ = curve.Add(p.Xᵤ, p.Xᵥ, p.Upwᵤ, v) + p.Zᵤ, p.Zᵥ = curve.ScalarMult(p.Zᵤ, p.Zᵥ, p.Aα) + + // STEP: B computes k + // H(pw,id_P,id_Q,X,Y,Z) + p.K = sessionKey(p.Pw, p.Xᵤ, p.Xᵥ, p.Yᵤ, p.Yᵥ, p.Zᵤ, p.Zᵥ) + } else { + p.Yᵤ, p.Yᵥ = q.Yᵤ, q.Yᵥ + if !curve.IsOnCurve(p.Yᵤ, p.Yᵥ) { + return errors.New("Y values not on curve") + } + + // STEP: A computes Z + v := new(big.Int).Neg(p.Vpwᵥ) + v.Mod(v, P) + p.Zᵤ, p.Zᵥ = curve.Add(p.Yᵤ, p.Yᵥ, p.Vpwᵤ, v) + p.Zᵤ, p.Zᵥ = curve.ScalarMult(p.Zᵤ, p.Zᵥ, p.Aα) + + // STEP: A computes k + // H(pw,id_P,id_Q,X,Y,Z) + p.K = sessionKey(p.Pw, p.Xᵤ, p.Xᵥ, p.Yᵤ, p.Yᵥ, p.Zᵤ, p.Zᵥ) + } + return +} + +func sessionKey(pw []byte, xu, xv, yu, yv, zu, zv *big.Int) []byte { + h := sha256.New() + h.Write(pw) + h.Write(xu.Bytes()) + h.Write(xv.Bytes()) + h.Write(yu.Bytes()) + h.Write(yv.Bytes()) + h.Write(zu.Bytes()) + h.Write(zv.Bytes()) + return h.Sum(nil) +} + +// SessionKey returns the derived session key, or an error if one hasn't +// been generated yet (i.e. Update hasn't completed the exchange). +func (p *Pake) SessionKey() ([]byte, error) { + if p == nil { + return nil, fmt.Errorf("pake is not initialized") + } + if p.K == nil { + return nil, errors.New("session key not generated") + } + return p.K, nil +} diff --git a/src/swarm/client.go b/src/swarm/client.go index 2ca3ef1..81c5c77 100644 --- a/src/swarm/client.go +++ b/src/swarm/client.go @@ -11,7 +11,7 @@ import ( "sync" "time" - log "github.com/schollz/logger" + log "github.com/tzgyn/share/src/logger" "github.com/tzgyn/share/src/comm" "github.com/tzgyn/share/src/tcp" diff --git a/src/swarm/handshake.go b/src/swarm/handshake.go index e80be60..c63f0d6 100644 --- a/src/swarm/handshake.go +++ b/src/swarm/handshake.go @@ -1,9 +1,8 @@ package swarm import ( - "github.com/schollz/pake/v3" - "github.com/tzgyn/share/src/comm" + "github.com/tzgyn/share/src/pake" ) // handshake runs a PAKE key exchange over c using the room's codephrase as @@ -16,7 +15,7 @@ func handshake(c *comm.Comm, codephrase string, initiator bool) (key []byte, err if initiator { role = 0 } - p, err := pake.InitCurve([]byte(codephrase), role, "siec") + p, err := pake.InitCurve([]byte(codephrase), role) if err != nil { return nil, err } diff --git a/src/tcp/ctx.go b/src/tcp/ctx.go index 073ee0e..02a8244 100644 --- a/src/tcp/ctx.go +++ b/src/tcp/ctx.go @@ -6,7 +6,7 @@ import ( "net" "sync" - log "github.com/schollz/logger" + log "github.com/tzgyn/share/src/logger" ) type stop struct { diff --git a/src/tcp/tcp.go b/src/tcp/tcp.go index 82da94f..4684746 100644 --- a/src/tcp/tcp.go +++ b/src/tcp/tcp.go @@ -9,7 +9,7 @@ import ( "sync" "time" - log "github.com/schollz/logger" + log "github.com/tzgyn/share/src/logger" "github.com/tzgyn/share/src/comm" ) diff --git a/src/utils/utils.go b/src/utils/utils.go index 1c616e2..d2040bc 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -26,8 +26,7 @@ import ( "github.com/kalafut/imohash" "github.com/minio/highwayhash" "github.com/tzgyn/share/src/mnemonicode" - log "github.com/schollz/logger" - "github.com/schollz/progressbar/v3" + log "github.com/tzgyn/share/src/logger" ) const NbPinNumbers = 4 @@ -74,11 +73,7 @@ func GetInput(prompt string) string { return strings.TrimSpace(text) } -func HashFile(fname string, algorithm string, showProgress ...bool) (hash256 []byte, err error) { - doShowProgress := false - if len(showProgress) > 0 { - doShowProgress = showProgress[0] - } +func HashFile(fname string, algorithm string) (hash256 []byte, err error) { var fstats os.FileInfo fstats, err = os.Lstat(fname) if err != nil { @@ -96,17 +91,17 @@ func HashFile(fname string, algorithm string, showProgress ...bool) (hash256 []b case "imohash": return IMOHashFile(fname) case "md5": - return MD5HashFile(fname, doShowProgress) + return MD5HashFile(fname) case "xxhash": - return XXHashFile(fname, doShowProgress) + return XXHashFile(fname) case "highway": - return HighwayHashFile(fname, doShowProgress) + return HighwayHashFile(fname) } err = fmt.Errorf("unspecified algorithm") return } -func HighwayHashFile(fname string, doShowProgress bool) (hashHighway []byte, err error) { +func HighwayHashFile(fname string) (hashHighway []byte, err error) { f, err := os.Open(fname) if err != nil { return @@ -121,33 +116,15 @@ func HighwayHashFile(fname string, doShowProgress bool) (hashHighway []byte, err err = fmt.Errorf("could not create highwayhash: %s", err.Error()) return } - if doShowProgress { - stat, _ := f.Stat() - fnameShort := path.Base(fname) - if len(fnameShort) > 20 { - fnameShort = fnameShort[:20] + "..." - } - bar := progressbar.NewOptions64(stat.Size(), - progressbar.OptionSetWriter(os.Stderr), - progressbar.OptionShowBytes(true), - progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)), - progressbar.OptionClearOnFinish(), - progressbar.OptionFullWidth(), - ) - if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil { - return - } - } else { - if _, err = io.Copy(h, f); err != nil { - return - } + if _, err = io.Copy(h, f); err != nil { + return } hashHighway = h.Sum(nil) return } -func MD5HashFile(fname string, doShowProgress bool) (hash256 []byte, err error) { +func MD5HashFile(fname string) (hash256 []byte, err error) { f, err := os.Open(fname) if err != nil { return @@ -155,26 +132,8 @@ func MD5HashFile(fname string, doShowProgress bool) (hash256 []byte, err error) defer f.Close() h := md5.New() - if doShowProgress { - stat, _ := f.Stat() - fnameShort := path.Base(fname) - if len(fnameShort) > 20 { - fnameShort = fnameShort[:20] + "..." - } - bar := progressbar.NewOptions64(stat.Size(), - progressbar.OptionSetWriter(os.Stderr), - progressbar.OptionShowBytes(true), - progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)), - progressbar.OptionClearOnFinish(), - progressbar.OptionFullWidth(), - ) - if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil { - return - } - } else { - if _, err = io.Copy(h, f); err != nil { - return - } + if _, err = io.Copy(h, f); err != nil { + return } hash256 = h.Sum(nil) @@ -196,7 +155,7 @@ func IMOHashFileFull(fname string) (hash []byte, err error) { return } -func XXHashFile(fname string, doShowProgress bool) (hash256 []byte, err error) { +func XXHashFile(fname string) (hash256 []byte, err error) { f, err := os.Open(fname) if err != nil { return @@ -204,26 +163,8 @@ func XXHashFile(fname string, doShowProgress bool) (hash256 []byte, err error) { defer f.Close() h := xxhash.New() - if doShowProgress { - stat, _ := f.Stat() - fnameShort := path.Base(fname) - if len(fnameShort) > 20 { - fnameShort = fnameShort[:20] + "..." - } - bar := progressbar.NewOptions64(stat.Size(), - progressbar.OptionSetWriter(os.Stderr), - progressbar.OptionShowBytes(true), - progressbar.OptionSetDescription(fmt.Sprintf("Hashing %s", fnameShort)), - progressbar.OptionClearOnFinish(), - progressbar.OptionFullWidth(), - ) - if _, err = io.Copy(io.MultiWriter(h, bar), f); err != nil { - return - } - } else { - if _, err = io.Copy(h, f); err != nil { - return - } + if _, err = io.Copy(h, f); err != nil { + return } hash256 = h.Sum(nil) @@ -310,23 +251,6 @@ func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int6 return } - var bar *progressbar.ProgressBar - showProgress := fsize > 10*1024*1024 - if showProgress { - fnameShort := path.Base(fname) - if len(fnameShort) > 20 { - fnameShort = fnameShort[:20] + "..." - } - bar = progressbar.NewOptions64(fsize, - progressbar.OptionSetWriter(os.Stderr), - progressbar.OptionShowBytes(true), - progressbar.OptionSetDescription(fmt.Sprintf("Checking %s", fnameShort)), - progressbar.OptionClearOnFinish(), - progressbar.OptionFullWidth(), - progressbar.OptionThrottle(100*time.Millisecond), - ) - } - emptyBuffer := make([]byte, chunkSize) chunkNum := 0 chunks := make([]int64, int64(math.Ceil(float64(fsize)/float64(chunkSize)))) @@ -342,12 +266,6 @@ func MissingChunks(fname string, fsize int64, chunkSize int) (chunkRanges []int6 chunkNum++ } currentLocation += int64(bytesread) - if showProgress && bar != nil { - bar.Add(bytesread) - } - } - if showProgress && bar != nil { - bar.Finish() } if chunkNum == 0 { chunkRanges = []int64{}