Compare commits

...

6 Commits
main ... fix3

Author SHA1 Message Date
Zack Scholl fffdf39bea use relay public key and password to connect 2021-04-23 16:59:28 -07:00
Zack Scholl 675f896d61 beter tests 2021-04-23 14:40:31 -07:00
Zack Scholl 496a6d3a05 works 2021-04-23 14:10:54 -07:00
Zack Scholl f68e194a4a bug fixes 2021-04-23 13:50:12 -07:00
Zack Scholl c3c892f95d use age for tcp 2021-04-23 13:48:19 -07:00
Zack Scholl 9db87434a7 add age encryption 2021-04-23 12:39:27 -07:00
9 changed files with 312 additions and 172 deletions

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/schollz/croc/v9
go 1.13 go 1.13
require ( require (
filippo.io/age v1.0.0-rc.1
github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/OneOfOne/xxhash v1.2.5 // indirect
github.com/cespare/xxhash v1.1.0 github.com/cespare/xxhash v1.1.0
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect

5
go.sum
View File

@ -1,3 +1,5 @@
filippo.io/age v1.0.0-rc.1 h1:jQ+dz16Xxx3W/WY+YS0J96nVAAidLHO3kfQe0eOmKgI=
filippo.io/age v1.0.0-rc.1/go.mod h1:Vvd9IlwNo4Au31iqNZeZVnYtGcOf/wT4mtvZQ2ODlSk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI= github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
@ -13,8 +15,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kalafut/imohash v1.0.0 h1:LgCJ+p/BwM2HKpOxFopkeddpzVCfm15EtXMroXD1SYE=
github.com/kalafut/imohash v1.0.0/go.mod h1:c3RHT80ZAp5C/aYgQI92ZlrOymqkZnRDprU87kg75HI=
github.com/kalafut/imohash v1.0.2 h1:j/cUPa15YvXv7abJlM+kdJIycbBMpmO7WqhPl4YB76I= github.com/kalafut/imohash v1.0.2 h1:j/cUPa15YvXv7abJlM+kdJIycbBMpmO7WqhPl4YB76I=
github.com/kalafut/imohash v1.0.2/go.mod h1:PjHBF0vpo1q7zMqiTn0qwSTQU2wDn5QIe8S8sFQuZS8= github.com/kalafut/imohash v1.0.2/go.mod h1:PjHBF0vpo1q7zMqiTn0qwSTQU2wDn5QIe8S8sFQuZS8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
@ -61,6 +61,7 @@ github.com/tscholl2/siec v0.0.0-20191122224205-8da93652b094/go.mod h1:KL9+ubr1JZ
github.com/twmb/murmur3 v1.1.5 h1:i9OLS9fkuLzBXjt6dptlAEyk58fJsSTXbRg3SgVyqgk= github.com/twmb/murmur3 v1.1.5 h1:i9OLS9fkuLzBXjt6dptlAEyk58fJsSTXbRg3SgVyqgk=
github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA= golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=

View File

@ -16,6 +16,7 @@ import (
"github.com/schollz/cli/v2" "github.com/schollz/cli/v2"
"github.com/schollz/croc/v9/src/comm" "github.com/schollz/croc/v9/src/comm"
"github.com/schollz/croc/v9/src/croc" "github.com/schollz/croc/v9/src/croc"
"github.com/schollz/croc/v9/src/crypt"
"github.com/schollz/croc/v9/src/models" "github.com/schollz/croc/v9/src/models"
"github.com/schollz/croc/v9/src/tcp" "github.com/schollz/croc/v9/src/tcp"
"github.com/schollz/croc/v9/src/utils" "github.com/schollz/croc/v9/src/utils"
@ -75,6 +76,7 @@ func Run() (err error) {
}, },
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"}, &cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"},
&cli.StringFlag{Name: "identity", Value: "", Usage: "identity file with credentials"},
}, },
}, },
} }
@ -93,7 +95,8 @@ func Run() (err error) {
&cli.StringFlag{Name: "relay", Value: models.DEFAULT_RELAY, Usage: "address of the relay", EnvVars: []string{"CROC_RELAY"}}, &cli.StringFlag{Name: "relay", Value: models.DEFAULT_RELAY, Usage: "address of the relay", EnvVars: []string{"CROC_RELAY"}},
&cli.StringFlag{Name: "relay6", Value: models.DEFAULT_RELAY6, Usage: "ipv6 address of the relay", EnvVars: []string{"CROC_RELAY6"}}, &cli.StringFlag{Name: "relay6", Value: models.DEFAULT_RELAY6, Usage: "ipv6 address of the relay", EnvVars: []string{"CROC_RELAY6"}},
&cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"}, &cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"},
&cli.StringFlag{Name: "pass", Value: models.DEFAULT_PASSPHRASE, Usage: "password for the relay", EnvVars: []string{"CROC_PASS"}}, &cli.StringFlag{Name: "relay-pass", Value: models.DEFAULT_RELAY_PASSWORD, Usage: "password for the relay (defaults to public relay)", EnvVars: []string{"CROC_PASS"}},
&cli.StringFlag{Name: "relay-key", Value: models.DEFAULT_RELAY_KEYPUBLIC, Usage: "public key for the relay (defaults to public relay)", EnvVars: []string{"CROC_KEY"}},
&cli.StringFlag{Name: "socks5", Value: "", Usage: "add a socks5 proxy", EnvVars: []string{"SOCKS5_PROXY"}}, &cli.StringFlag{Name: "socks5", Value: "", Usage: "add a socks5 proxy", EnvVars: []string{"SOCKS5_PROXY"}},
} }
app.EnableBashCompletion = true app.EnableBashCompletion = true
@ -167,15 +170,6 @@ func getConfigFile() string {
return path.Join(configFile, "send.json") return path.Join(configFile, "send.json")
} }
func determinePass(c *cli.Context) (pass string) {
pass = c.String("pass")
b, err := ioutil.ReadFile(pass)
if err == nil {
pass = strings.TrimSpace(string(b))
}
return
}
func send(c *cli.Context) (err error) { func send(c *cli.Context) (err error) {
setDebugLevel(c) setDebugLevel(c)
comm.Socks5Proxy = c.String("socks5") comm.Socks5Proxy = c.String("socks5")
@ -193,7 +187,8 @@ func send(c *cli.Context) (err error) {
RelayPorts: strings.Split(c.String("ports"), ","), RelayPorts: strings.Split(c.String("ports"), ","),
Ask: c.Bool("ask"), Ask: c.Bool("ask"),
NoMultiplexing: c.Bool("no-multi"), NoMultiplexing: c.Bool("no-multi"),
RelayPassword: determinePass(c), RelayPassword: c.String("relay-pass"),
RelayKeyPublic: c.String("relay-key"),
SendingText: c.String("text") != "", SendingText: c.String("text") != "",
NoCompress: c.Bool("no-compress"), NoCompress: c.Bool("no-compress"),
Overwrite: c.Bool("overwrite"), Overwrite: c.Bool("overwrite"),
@ -226,9 +221,12 @@ func send(c *cli.Context) (err error) {
if !c.IsSet("code") { if !c.IsSet("code") {
crocOptions.SharedSecret = rememberedOptions.SharedSecret crocOptions.SharedSecret = rememberedOptions.SharedSecret
} }
if !c.IsSet("pass") { if !c.IsSet("relay-pass") {
crocOptions.RelayPassword = rememberedOptions.RelayPassword crocOptions.RelayPassword = rememberedOptions.RelayPassword
} }
if !c.IsSet("relay-key") {
crocOptions.RelayKeyPublic = rememberedOptions.RelayKeyPublic
}
} }
var fnames []string var fnames []string
@ -383,20 +381,21 @@ func saveConfig(c *cli.Context, crocOptions croc.Options) {
func receive(c *cli.Context) (err error) { func receive(c *cli.Context) (err error) {
comm.Socks5Proxy = c.String("socks5") comm.Socks5Proxy = c.String("socks5")
crocOptions := croc.Options{ crocOptions := croc.Options{
SharedSecret: c.String("code"), SharedSecret: c.String("code"),
IsSender: false, IsSender: false,
Debug: c.Bool("debug"), Debug: c.Bool("debug"),
NoPrompt: c.Bool("yes"), NoPrompt: c.Bool("yes"),
RelayAddress: c.String("relay"), RelayAddress: c.String("relay"),
RelayAddress6: c.String("relay6"), RelayAddress6: c.String("relay6"),
Stdout: c.Bool("stdout"), Stdout: c.Bool("stdout"),
Ask: c.Bool("ask"), Ask: c.Bool("ask"),
RelayPassword: determinePass(c), RelayPassword: c.String("relay-pass"),
OnlyLocal: c.Bool("local"), RelayKeyPublic: c.String("relay-key"),
IP: c.String("ip"), OnlyLocal: c.Bool("local"),
Overwrite: c.Bool("overwrite"), IP: c.String("ip"),
Curve: c.String("curve"), Overwrite: c.Bool("overwrite"),
HashAlgorithm: "xxhash", Curve: c.String("curve"),
HashAlgorithm: "xxhash",
} }
if crocOptions.RelayAddress != models.DEFAULT_RELAY { if crocOptions.RelayAddress != models.DEFAULT_RELAY {
crocOptions.RelayAddress6 = "" crocOptions.RelayAddress6 = ""
@ -481,23 +480,40 @@ func receive(c *cli.Context) (err error) {
} }
func relay(c *cli.Context) (err error) { func relay(c *cli.Context) (err error) {
log.Infof("starting croc relay version %v", Version) identityFile := "identity.croc"
if c.String("identity") != "" {
identityFile = c.String("identity")
}
if !utils.Exists(identityFile) {
err = crypt.GenerateIdentityAndPassword(identityFile)
if err != nil {
return
}
}
keyPrivate, keyPublic, password, err := crypt.LoadIdentityAndPassword(identityFile)
if err != nil {
return
}
ports := strings.Split(c.String("ports"), ",")
tcpPorts := strings.Join(ports[1:], ",")
fmt.Printf("starting croc relay version %v\n", Version)
identityFileAbs, _ := filepath.Abs(identityFile)
fmt.Printf("identity file = %s\n", identityFileAbs)
fmt.Printf("\n\nexample to send files:\n\n croc --relay localhost:%s --relay-pass %s --relay-key %s send file.txt", ports[0], password, keyPublic)
debugString := "info" debugString := "info"
if c.Bool("debug") { if c.Bool("debug") {
debugString = "debug" debugString = "debug"
} }
ports := strings.Split(c.String("ports"), ",")
tcpPorts := strings.Join(ports[1:], ",")
for i, port := range ports { for i, port := range ports {
if i == 0 { if i == 0 {
continue continue
} }
go func(portStr string) { go func(portStr string) {
err = tcp.Run(debugString, portStr, determinePass(c)) err = tcp.Run(debugString, portStr, password, keyPublic, keyPrivate)
if err != nil { if err != nil {
panic(err) panic(err)
} }
}(port) }(port)
} }
return tcp.Run(debugString, ports[0], determinePass(c), tcpPorts) return tcp.Run(debugString, ports[0], password, keyPublic, keyPrivate, tcpPorts)
} }

View File

@ -48,26 +48,31 @@ func Debug(debug bool) {
// Options specifies user specific options // Options specifies user specific options
type Options struct { type Options struct {
IsSender bool IsSender bool
SharedSecret string SharedSecret string
Debug bool Debug bool
RelayAddress string RelayAddress string
RelayAddress6 string RelayAddress6 string
RelayPorts []string RelayPorts []string
RelayPassword string RelayPassword string
Stdout bool RelayKeyPrivate string
NoPrompt bool RelayKeyPublic string
NoMultiplexing bool LocalRelayPassword string
DisableLocal bool LocalRelayKeyPrivate string
OnlyLocal bool LocalRelayKeyPublic string
IgnoreStdin bool Stdout bool
Ask bool NoPrompt bool
SendingText bool NoMultiplexing bool
NoCompress bool DisableLocal bool
IP string OnlyLocal bool
Overwrite bool IgnoreStdin bool
Curve string Ask bool
HashAlgorithm string SendingText bool
NoCompress bool
IP string
Overwrite bool
Curve string
HashAlgorithm string
} }
// Client holds the state of the croc transfer // Client holds the state of the croc transfer
@ -288,7 +293,7 @@ func (c *Client) setupLocalRelay() {
if c.Options.Debug { if c.Options.Debug {
debugString = "debug" debugString = "debug"
} }
err := tcp.Run(debugString, portStr, c.Options.RelayPassword, strings.Join(c.Options.RelayPorts[1:], ",")) err := tcp.Run(debugString, portStr, c.Options.LocalRelayPassword, c.Options.LocalRelayKeyPublic, c.Options.LocalRelayKeyPrivate, strings.Join(c.Options.RelayPorts[1:], ","))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -320,7 +325,7 @@ func (c *Client) transferOverLocalRelay(options TransferOptions, errchan chan<-
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
log.Debug("establishing connection") log.Debug("establishing connection")
var banner string var banner string
conn, banner, ipaddr, err := tcp.ConnectToTCPServer("localhost:"+c.Options.RelayPorts[0], c.Options.RelayPassword, c.Options.SharedSecret[:3]) conn, banner, ipaddr, err := tcp.ConnectToTCPServer("localhost:"+c.Options.RelayPorts[0], c.Options.LocalRelayPassword, c.Options.LocalRelayKeyPublic, c.Options.SharedSecret[:3])
log.Debugf("banner: %s", banner) log.Debugf("banner: %s", banner)
if err != nil { if err != nil {
err = fmt.Errorf("could not connect to localhost:%s: %w", c.Options.RelayPorts[0], err) err = fmt.Errorf("could not connect to localhost:%s: %w", c.Options.RelayPorts[0], err)
@ -361,8 +366,9 @@ func (c *Client) Send(options TransferOptions) (err error) {
if c.Options.RelayAddress != models.DEFAULT_RELAY { if c.Options.RelayAddress != models.DEFAULT_RELAY {
flags.WriteString("--relay " + c.Options.RelayAddress + " ") flags.WriteString("--relay " + c.Options.RelayAddress + " ")
} }
if c.Options.RelayPassword != models.DEFAULT_PASSPHRASE { if c.Options.RelayPassword != models.DEFAULT_RELAY_PASSWORD || c.Options.RelayKeyPublic != models.DEFAULT_RELAY_KEYPUBLIC {
flags.WriteString("--pass " + c.Options.RelayPassword + " ") flags.WriteString("--relay-pass " + c.Options.RelayPassword + " ")
flags.WriteString("--relay-key " + c.Options.RelayKeyPublic + " ")
} }
fmt.Fprintf(os.Stderr, "Code is: %[1]s\nOn the other computer run\n\ncroc %[2]s%[1]s\n", c.Options.SharedSecret, flags.String()) fmt.Fprintf(os.Stderr, "Code is: %[1]s\nOn the other computer run\n\ncroc %[2]s%[1]s\n", c.Options.SharedSecret, flags.String())
if c.Options.Ask { if c.Options.Ask {
@ -376,6 +382,8 @@ func (c *Client) Send(options TransferOptions) (err error) {
errchan := make(chan error, 1) errchan := make(chan error, 1)
if !c.Options.DisableLocal { if !c.Options.DisableLocal {
c.Options.LocalRelayKeyPublic, c.Options.LocalRelayKeyPrivate, _ = crypt.NewAge()
c.Options.LocalRelayPassword, _ = crypt.GenerateRandomString(6)
// add two things to the error channel // add two things to the error channel
errchan = make(chan error, 2) errchan = make(chan error, 2)
c.setupLocalRelay() c.setupLocalRelay()
@ -405,7 +413,7 @@ func (c *Client) Send(options TransferOptions) (err error) {
log.Debugf("got host '%v' and port '%v'", host, port) log.Debugf("got host '%v' and port '%v'", host, port)
address = net.JoinHostPort(host, port) address = net.JoinHostPort(host, port)
log.Debugf("trying connection to %s", address) log.Debugf("trying connection to %s", address)
conn, banner, ipaddr, err = tcp.ConnectToTCPServer(address, c.Options.RelayPassword, c.Options.SharedSecret[:3], durations[i]) conn, banner, ipaddr, err = tcp.ConnectToTCPServer(address, c.Options.RelayPassword, c.Options.RelayKeyPublic, c.Options.SharedSecret[:3], durations[i])
if err == nil { if err == nil {
c.Options.RelayAddress = address c.Options.RelayAddress = address
break break
@ -599,7 +607,7 @@ func (c *Client) Receive() (err error) {
log.Debugf("got host '%v' and port '%v'", host, port) log.Debugf("got host '%v' and port '%v'", host, port)
address = net.JoinHostPort(host, port) address = net.JoinHostPort(host, port)
log.Debugf("trying connection to %s", address) log.Debugf("trying connection to %s", address)
c.conn[0], banner, c.ExternalIP, err = tcp.ConnectToTCPServer(address, c.Options.RelayPassword, c.Options.SharedSecret[:3], durations[i]) c.conn[0], banner, c.ExternalIP, err = tcp.ConnectToTCPServer(address, c.Options.RelayPassword, c.Options.RelayKeyPublic, c.Options.SharedSecret[:3], durations[i])
if err == nil { if err == nil {
c.Options.RelayAddress = address c.Options.RelayAddress = address
break break
@ -652,7 +660,7 @@ func (c *Client) Receive() (err error) {
} }
serverTry := fmt.Sprintf("%s:%s", ip, port) serverTry := fmt.Sprintf("%s:%s", ip, port)
conn, banner2, externalIP, errConn := tcp.ConnectToTCPServer(serverTry, c.Options.RelayPassword, c.Options.SharedSecret[:3], 50*time.Millisecond) conn, banner2, externalIP, errConn := tcp.ConnectToTCPServer(serverTry, c.Options.RelayPassword, c.Options.RelayKeyPublic, c.Options.SharedSecret[:3], 50*time.Millisecond)
if errConn != nil { if errConn != nil {
log.Debugf("could not connect to " + serverTry) log.Debugf("could not connect to " + serverTry)
continue continue
@ -909,6 +917,7 @@ func (c *Client) processMessagePake(m message.Message) (err error) {
c.conn[j+1], _, _, err = tcp.ConnectToTCPServer( c.conn[j+1], _, _, err = tcp.ConnectToTCPServer(
server, server,
c.Options.RelayPassword, c.Options.RelayPassword,
c.Options.RelayKeyPublic,
fmt.Sprintf("%s-%d", utils.SHA256(c.Options.SharedSecret[:5])[:6], j), fmt.Sprintf("%s-%d", utils.SHA256(c.Options.SharedSecret[:5])[:6], j),
) )
if err != nil { if err != nil {

View File

@ -1,13 +1,19 @@
package crypt package crypt
import ( import (
"bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"io"
"io/ioutil"
"log" "log"
"math/big"
"strings"
"filippo.io/age"
"golang.org/x/crypto/argon2" "golang.org/x/crypto/argon2"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/pbkdf2" "golang.org/x/crypto/pbkdf2"
@ -73,6 +79,118 @@ func Decrypt(encrypted []byte, key []byte) (plaintext []byte, err error) {
return return
} }
// GenerateIdentityAndPassword will generate a file with a public age identity and password
func GenerateIdentityAndPassword(fname string) (err error) {
keyPublic, keyPrivate, err := NewAge()
if err != nil {
return
}
password, err := GenerateRandomString(6)
if err != nil {
return
}
err = ioutil.WriteFile(fname, []byte(fmt.Sprintf("%s\n%s\n%s", keyPrivate, keyPublic, password)), 0644)
return
}
// LoadIdentityAndPassword will load a file with a public age identity and password
func LoadIdentityAndPassword(fname string) (keyPrivate string, keyPublic string, password string, err error) {
b, err := ioutil.ReadFile(fname)
if err != nil {
return
}
foo := strings.Fields(string(b))
if len(foo) < 3 {
err = fmt.Errorf("malformed file")
return
}
keyPrivate = foo[0]
keyPublic = foo[1]
password = foo[2]
_, err = age.ParseX25519Identity(keyPrivate)
if err != nil {
return
}
_, err = age.ParseX25519Recipient(keyPublic)
if err != nil {
return
}
return
}
// GenerateRandomString returns a securely generated random string.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomString(n int) (string, error) {
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
ret := make([]byte, n)
for i := 0; i < n; i++ {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
if err != nil {
return "", err
}
ret[i] = letters[num.Int64()]
}
return string(ret), nil
}
func NewAge() (pubkey string, privkey string, err error) {
identity, err := age.GenerateX25519Identity()
if err != nil {
return
}
pubkey = identity.Recipient().String()
privkey = identity.String()
return
}
func EncryptAge(data []byte, pubkey string) (encrypted []byte, err error) {
recipient, err := age.ParseX25519Recipient(pubkey)
if err != nil {
return
}
out := &bytes.Buffer{}
w, err := age.Encrypt(out, recipient)
if err != nil {
return
}
_, err = w.Write(data)
if err != nil {
return
}
err = w.Close()
if err != nil {
return
}
encrypted = out.Bytes()
return
}
func DecryptAge(encrypted []byte, privkey string) (data []byte, err error) {
identity, err := age.ParseX25519Identity(privkey)
if err != nil {
return
}
r, err := age.Decrypt(bytes.NewReader(encrypted), identity)
if err != nil {
return
}
out := &bytes.Buffer{}
_, err = io.Copy(out, r)
if err != nil {
return
}
data = out.Bytes()
return
}
// NewArgon2 generates a new key based on a passphrase and salt // NewArgon2 generates a new key based on a passphrase and salt
// using argon2 // using argon2
// https://pkg.go.dev/golang.org/x/crypto/argon2 // https://pkg.go.dev/golang.org/x/crypto/argon2

View File

@ -2,8 +2,10 @@ package crypt
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/schollz/croc/v9/src/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -103,3 +105,31 @@ func TestEncryptionChaCha(t *testing.T) {
_, _, err = NewArgon2([]byte(""), nil) _, _, err = NewArgon2([]byte(""), nil)
assert.NotNil(t, err) assert.NotNil(t, err)
} }
func TestEncryptionAge(t *testing.T) {
pub, priv, err := NewAge()
fmt.Printf("key: %s\n", pub)
assert.Nil(t, err)
msg := []byte("hello, world")
enc, err := EncryptAge(msg, pub)
assert.Nil(t, err)
dec, err := DecryptAge(enc, priv)
assert.Nil(t, err)
assert.Equal(t, msg, dec)
}
func TestGenerate(t *testing.T) {
err := GenerateIdentityAndPassword("test")
assert.Nil(t, err)
assert.True(t, utils.Exists("test"))
keyPrivate, keyPublic, password, err := LoadIdentityAndPassword("test")
assert.Nil(t, err)
fmt.Println(keyPrivate)
fmt.Println(keyPublic)
fmt.Println(password)
_, _, _, err = LoadIdentityAndPassword("crypt.go")
assert.NotNil(t, err)
_, _, _, err = LoadIdentityAndPassword("doesntexist")
assert.NotNil(t, err)
os.Remove("test")
}

View File

@ -9,12 +9,13 @@ import (
// TCP_BUFFER_SIZE is the maximum packet size // TCP_BUFFER_SIZE is the maximum packet size
const TCP_BUFFER_SIZE = 1024 * 64 const TCP_BUFFER_SIZE = 1024 * 64
// DEFAULT_RELAY is the default relay used (can be set using --relay) // DEFAULT_RELAY is the default relay used (can be set using --relay-pass)
var ( var (
DEFAULT_RELAY = "croc.schollz.com" DEFAULT_RELAY = "croc.schollz.com"
DEFAULT_RELAY6 = "croc6.schollz.com" DEFAULT_RELAY6 = "croc6.schollz.com"
DEFAULT_PORT = "9009" DEFAULT_PORT = "9009"
DEFAULT_PASSPHRASE = "pass123" DEFAULT_RELAY_PASSWORD = "ZiNO9Y"
DEFAULT_RELAY_KEYPUBLIC = "age10yrxthzjrcr0e59nucg0epgnn0qpjv9rhsxqs90rdn335edgnueqrtdnyh"
) )
func init() { func init() {

View File

@ -8,8 +8,9 @@ import (
"sync" "sync"
"time" "time"
"filippo.io/age"
log "github.com/schollz/logger" log "github.com/schollz/logger"
"github.com/schollz/pake/v3" "golang.org/x/crypto/bcrypt"
"github.com/schollz/croc/v9/src/comm" "github.com/schollz/croc/v9/src/comm"
"github.com/schollz/croc/v9/src/crypt" "github.com/schollz/croc/v9/src/crypt"
@ -18,6 +19,8 @@ import (
type server struct { type server struct {
port string port string
keyPrivate string
keyPublic string
debugLevel string debugLevel string
banner string banner string
password string password string
@ -40,9 +43,11 @@ var timeToRoomDeletion = 10 * time.Minute
var pingRoom = "pinglkasjdlfjsaldjf" var pingRoom = "pinglkasjdlfjsaldjf"
// Run starts a tcp listener, run async // Run starts a tcp listener, run async
func Run(debugLevel, port, password string, banner ...string) (err error) { func Run(debugLevel, port, password string, keyPublic string, keyPrivate string, banner ...string) (err error) {
s := new(server) s := new(server)
s.port = port s.port = port
s.keyPrivate = keyPrivate
s.keyPublic = keyPublic
s.password = password s.password = password
s.debugLevel = debugLevel s.debugLevel = debugLevel
if len(banner) > 0 { if len(banner) > 0 {
@ -85,7 +90,7 @@ func (s *server) start() (err error) {
} }
func (s *server) run() (err error) { func (s *server) run() (err error) {
log.Infof("starting TCP server on " + s.port) log.Debugf("starting TCP server on " + s.port)
server, err := net.Listen("tcp", ":"+s.port) server, err := net.Listen("tcp", ":"+s.port)
if err != nil { if err != nil {
return fmt.Errorf("error listening on %s: %w", s.port, err) return fmt.Errorf("error listening on %s: %w", s.port, err)
@ -151,59 +156,34 @@ func (s *server) run() (err error) {
var weakKey = []byte{1, 2, 3} var weakKey = []byte{1, 2, 3}
func (s *server) clientCommunication(port string, c *comm.Comm) (room string, err error) { func (s *server) clientCommunication(port string, c *comm.Comm) (room string, err error) {
// establish secure password with PAKE for communication with relay // get public key of the connecting client
B, err := pake.InitCurve(weakKey, 1, "siec") retBytesEnc, err := c.Receive()
if err != nil { if err != nil {
return return
} }
Abytes, err := c.Receive() if bytes.Equal(retBytesEnc, []byte("ping")) {
if err != nil {
return
}
if bytes.Equal(Abytes, []byte("ping")) {
room = pingRoom room = pingRoom
c.Send([]byte("pong")) c.Send([]byte("pong"))
return return
} }
err = B.Update(Abytes) retBytes, err := crypt.DecryptAge(retBytesEnc, s.keyPrivate)
if err != nil { if err != nil {
return return
} }
err = c.Send(B.Bytes()) // check whether we have a valid public key from client
if err != nil { foo := bytes.Split(retBytes, []byte("--"))
return keyPublic := string(foo[0])
} hashedPassword := foo[1]
strongKey, err := B.SessionKey()
if err != nil {
return
}
log.Debugf("strongkey: %x", strongKey)
// receive salt err = bcrypt.CompareHashAndPassword(hashedPassword, []byte(s.password))
salt, err := c.Receive()
if err != nil { if err != nil {
return
}
strongKeyForEncryption, _, err := crypt.New(strongKey, salt)
if err != nil {
return
}
log.Debugf("waiting for password")
passwordBytesEnc, err := c.Receive()
if err != nil {
return
}
passwordBytes, err := crypt.Decrypt(passwordBytesEnc, strongKeyForEncryption)
if err != nil {
return
}
if strings.TrimSpace(string(passwordBytes)) != s.password {
err = fmt.Errorf("bad password") err = fmt.Errorf("bad password")
enc, _ := crypt.Decrypt([]byte(err.Error()), strongKeyForEncryption) return
if err := c.Send(enc); err != nil { }
return "", fmt.Errorf("send error: %w", err)
} _, err = age.ParseX25519Recipient(keyPublic)
if err != nil {
err = fmt.Errorf("bad public key: %s", keyPublic)
return return
} }
@ -213,7 +193,7 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
banner = "ok" banner = "ok"
} }
log.Debugf("sending '%s'", banner) log.Debugf("sending '%s'", banner)
bSend, err := crypt.Encrypt([]byte(banner+"|||"+c.Connection().RemoteAddr().String()), strongKeyForEncryption) bSend, err := crypt.EncryptAge([]byte(banner+"|||"+c.Connection().RemoteAddr().String()), keyPublic)
if err != nil { if err != nil {
return return
} }
@ -228,7 +208,7 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
if err != nil { if err != nil {
return return
} }
roomBytes, err := crypt.Decrypt(enc, strongKeyForEncryption) roomBytes, err := crypt.DecryptAge(enc, s.keyPrivate)
if err != nil { if err != nil {
return return
} }
@ -244,7 +224,7 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
s.rooms.Unlock() s.rooms.Unlock()
// tell the client that they got the room // tell the client that they got the room
bSend, err = crypt.Encrypt([]byte("ok"), strongKeyForEncryption) bSend, err = crypt.EncryptAge([]byte("ok"), keyPublic)
if err != nil { if err != nil {
return return
} }
@ -259,7 +239,7 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
} }
if s.rooms.rooms[room].full { if s.rooms.rooms[room].full {
s.rooms.Unlock() s.rooms.Unlock()
bSend, err = crypt.Encrypt([]byte("room full"), strongKeyForEncryption) bSend, err = crypt.EncryptAge([]byte("room full"), keyPublic)
if err != nil { if err != nil {
return return
} }
@ -293,7 +273,7 @@ func (s *server) clientCommunication(port string, c *comm.Comm) (room string, er
}(otherConnection, c, &wg) }(otherConnection, c, &wg)
// tell the sender everything is ready // tell the sender everything is ready
bSend, err = crypt.Encrypt([]byte("ok"), strongKeyForEncryption) bSend, err = crypt.EncryptAge([]byte("ok"), keyPublic)
if err != nil { if err != nil {
return return
} }
@ -405,7 +385,7 @@ func PingServer(address string) (err error) {
// ConnectToTCPServer will initiate a new connection // ConnectToTCPServer will initiate a new connection
// to the specified address, room with optional time limit // to the specified address, room with optional time limit
func ConnectToTCPServer(address, password, room string, timelimit ...time.Duration) (c *comm.Comm, banner string, ipaddr string, err error) { func ConnectToTCPServer(address, password, keyPublicRelay, room string, timelimit ...time.Duration) (c *comm.Comm, banner string, ipaddr string, err error) {
if len(timelimit) > 0 { if len(timelimit) > 0 {
c, err = comm.NewConnection(address, timelimit[0]) c, err = comm.NewConnection(address, timelimit[0])
} else { } else {
@ -415,65 +395,41 @@ func ConnectToTCPServer(address, password, room string, timelimit ...time.Durati
return return
} }
// get PAKE connection with server to establish strong key to transfer info // generate ephermeral key
A, err := pake.InitCurve(weakKey, 0, "siec") keyPublic, keyPrivate, err := crypt.NewAge()
if err != nil {
return
}
err = c.Send(A.Bytes())
if err != nil {
return
}
Bbytes, err := c.Receive()
if err != nil {
return
}
err = A.Update(Bbytes)
if err != nil {
return
}
strongKey, err := A.SessionKey()
if err != nil {
return
}
log.Debugf("strong key: %x", strongKey)
strongKeyForEncryption, salt, err := crypt.New(strongKey, nil) // send epheremal public key + bcrypted password, encrypted using the server's public key
if err != nil { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 10)
return
}
// send salt
err = c.Send(salt)
if err != nil { if err != nil {
return return
} }
log.Debug("sending password") sendBytesEnc, err := crypt.EncryptAge(append([]byte(keyPublic+"--"), hashedPassword...), keyPublicRelay)
bSend, err := crypt.Encrypt([]byte(password), strongKeyForEncryption)
if err != nil { if err != nil {
return return
} }
err = c.Send(bSend)
err = c.Send(sendBytesEnc)
if err != nil { if err != nil {
return return
} }
log.Debug("waiting for first ok") retBytesEnc, err := c.Receive()
enc, err := c.Receive()
if err != nil { if err != nil {
return return
} }
data, err := crypt.Decrypt(enc, strongKeyForEncryption) retBytes, err := crypt.DecryptAge(retBytesEnc, keyPrivate)
if err != nil { if err != nil {
return return
} }
if !strings.Contains(string(data), "|||") { if !strings.Contains(string(retBytes), "|||") {
err = fmt.Errorf("bad response: %s", string(data)) err = fmt.Errorf("bad response: %s", string(retBytes))
return return
} }
banner = strings.Split(string(data), "|||")[0] banner = strings.Split(string(retBytes), "|||")[0]
ipaddr = strings.Split(string(data), "|||")[1] ipaddr = strings.Split(string(retBytes), "|||")[1]
log.Debug("sending room") log.Debug("sending room")
bSend, err = crypt.Encrypt([]byte(room), strongKeyForEncryption) bSend, err := crypt.EncryptAge([]byte(room), keyPublicRelay)
if err != nil { if err != nil {
return return
} }
@ -482,11 +438,11 @@ func ConnectToTCPServer(address, password, room string, timelimit ...time.Durati
return return
} }
log.Debug("waiting for room confirmation") log.Debug("waiting for room confirmation")
enc, err = c.Receive() enc, err := c.Receive()
if err != nil { if err != nil {
return return
} }
data, err = crypt.Decrypt(enc, strongKeyForEncryption) data, err := crypt.DecryptAge(enc, keyPrivate)
if err != nil { if err != nil {
return return
} }

View File

@ -2,44 +2,52 @@ package tcp
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
"time" "time"
"github.com/schollz/croc/v9/src/crypt"
log "github.com/schollz/logger" log "github.com/schollz/logger"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func BenchmarkConnection(b *testing.B) { // func BenchmarkConnection(b *testing.B) {
log.SetLevel("trace") // log.SetLevel("trace")
go Run("debug", "8283", "pass123", "8284") // go Run("debug", "8283", "pass123", "8284")
time.Sleep(100 * time.Millisecond) // time.Sleep(100 * time.Millisecond)
b.ResetTimer() // b.ResetTimer()
for i := 0; i < b.N; i++ { // for i := 0; i < b.N; i++ {
c, _, _, _ := ConnectToTCPServer("localhost:8283", "pass123", fmt.Sprintf("testroom%d", i), 1*time.Minute) // c, _, _, _ := ConnectToTCPServer("localhost:8283", "pass123", fmt.Sprintf("testroom%d", i), 1*time.Minute)
c.Close() // c.Close()
} // }
} // }
func TestTCP(t *testing.T) { func TestTCP(t *testing.T) {
log.SetLevel("error") log.SetLevel("error")
timeToRoomDeletion = 100 * time.Millisecond timeToRoomDeletion = 100 * time.Millisecond
go Run("debug", "8281", "pass123", "8282") keyPublic, keyPrivate, err := crypt.NewAge()
if err != nil {
panic(err)
}
go Run("debug", "8281", "pass123", keyPublic, keyPrivate, "8282")
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
err := PingServer("localhost:8281") err = PingServer("localhost:8281")
assert.Nil(t, err) assert.Nil(t, err)
err = PingServer("localhost:8333") err = PingServer("localhost:8333")
assert.NotNil(t, err) assert.NotNil(t, err)
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
c1, banner, _, err := ConnectToTCPServer("localhost:8281", "pass123", "testRoom", 1*time.Minute) c1, banner, _, err := ConnectToTCPServer("localhost:8281", "pass123", keyPublic, "testRoom", 1*time.Minute)
assert.Equal(t, banner, "8282") assert.Equal(t, banner, "8282")
assert.Nil(t, err) assert.Nil(t, err)
c2, _, _, err := ConnectToTCPServer("localhost:8281", "pass123", "testRoom") c2, _, _, err := ConnectToTCPServer("localhost:8281", "pass123", keyPublic, "testRoom")
assert.Nil(t, err) assert.Nil(t, err)
_, _, _, err = ConnectToTCPServer("localhost:8281", "pass123", "testRoom") _, _, _, err = ConnectToTCPServer("localhost:8281", "pass123", keyPublic, "testRoom")
assert.NotNil(t, err) assert.NotNil(t, err)
_, _, _, err = ConnectToTCPServer("localhost:8281", "pass123", "testRoom", 1*time.Nanosecond) _, _, _, err = ConnectToTCPServer("localhost:8281", "pass123", keyPublic, "testRoom", 1*time.Nanosecond)
assert.NotNil(t, err)
_, _, _, err = ConnectToTCPServer("localhost:8281", "pass123", keyPublic+"askldjfklsajdf", "testRoom2")
assert.NotNil(t, err)
_, _, _, err = ConnectToTCPServer("localhost:8281", "pass125", keyPublic, "testRoom3")
assert.NotNil(t, err) assert.NotNil(t, err)
// try sending data // try sending data