diff options
59 files changed, 1340 insertions, 667 deletions
@@ -161,6 +161,12 @@ and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`) ```json { + "config": { + "chainId": 0, + "homesteadBlock": 0, + "eip155Block": 0, + "eip158Block": 0 + }, "alloc" : {}, "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index f87cfdb72..1c5c43edc 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -61,7 +61,7 @@ var ( apiPortFlag = flag.Int("apiport", 8080, "Listener port for the HTTP API connection") ethPortFlag = flag.Int("ethport", 30303, "Listener port for the devp2p connection") bootFlag = flag.String("bootnodes", "", "Comma separated bootnode enode URLs to seed with") - netFlag = flag.Int("network", 0, "Network ID to use for the Ethereum protocol") + netFlag = flag.Uint64("network", 0, "Network ID to use for the Ethereum protocol") statsFlag = flag.String("ethstats", "", "Ethstats network monitoring auth string") netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet") @@ -179,7 +179,7 @@ type faucet struct { lock sync.RWMutex // Lock protecting the faucet's internals } -func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network int, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { +func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { // Assemble the raw devp2p protocol stack stack, err := node.New(&node.Config{ Name: "geth", diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index 90f79a47e..1a3c63da9 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -31,41 +31,33 @@ import ( var ( walletCommand = cli.Command{ - Name: "wallet", - Usage: "Manage Ethereum presale wallets", - ArgsUsage: "", - Category: "ACCOUNT COMMANDS", + Name: "wallet", + Usage: "Import Ethereum presale wallets", + Action: utils.MigrateFlags(importWallet), + Category: "ACCOUNT COMMANDS", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + utils.PasswordFileFlag, + utils.LightKDFFlag, + }, Description: ` - geth wallet import /path/to/my/presale.wallet + geth wallet [options] /path/to/my/presale.wallet -will prompt for your password and imports your ether presale account. -It can be used non-interactively with the --password option taking a -passwordfile as argument containing the wallet password in plaintext. + will prompt for your password and imports your ether presale account. + It can be used non-interactively with the --password option taking a + passwordfile as argument containing the wallet password in plaintext. -`, - Subcommands: []cli.Command{ - { - Action: importWallet, - Name: "import", - Usage: "Import Ethereum presale wallet", - ArgsUsage: "<keyFile>", - Description: ` -TODO: Please write this -`, - }, - }, + `, } accountCommand = cli.Command{ - Action: accountList, - Name: "account", - Usage: "Manage accounts", - ArgsUsage: "", - Category: "ACCOUNT COMMANDS", + Name: "account", + Usage: "Manage accounts", + Category: "ACCOUNT COMMANDS", Description: ` -Manage accounts lets you create new accounts, list all existing accounts, -import a private key into a new account. -' help' shows a list of subcommands or help for one subcommand. +Manage accounts, list all existing accounts, import a private key into a new +account, create a new account or update an existing account. It supports interactive mode, when you are prompted for password as well as non-interactive mode where passwords are supplied via a given password file. @@ -80,36 +72,34 @@ Note that exporting your key in unencrypted format is NOT supported. Keys are stored under <DATADIR>/keystore. It is safe to transfer the entire directory or the individual keys therein between ethereum nodes by simply copying. -Make sure you backup your keys regularly. -In order to use your account to send transactions, you need to unlock them using -the '--unlock' option. The argument is a space separated list of addresses or -indexes. If used non-interactively with a passwordfile, the file should contain -the respective passwords one per line. If you unlock n accounts and the password -file contains less than n entries, then the last password is meant to apply to -all remaining accounts. - -And finally. DO NOT FORGET YOUR PASSWORD. -`, +Make sure you backup your keys regularly.`, Subcommands: []cli.Command{ { - Action: accountList, - Name: "list", - Usage: "Print account addresses", - ArgsUsage: " ", + Name: "list", + Usage: "Print summary of existing accounts", + Action: utils.MigrateFlags(accountList), + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + }, Description: ` -TODO: Please write this -`, +Print a short summary of all accounts`, }, { - Action: accountCreate, - Name: "new", - Usage: "Create a new account", - ArgsUsage: " ", + Name: "new", + Usage: "Create a new account", + Action: utils.MigrateFlags(accountCreate), + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + utils.PasswordFileFlag, + utils.LightKDFFlag, + }, Description: ` geth account new -Creates a new account. Prints the address. +Creates a new account and prints the address. The account is saved in encrypted format, you are prompted for a passphrase. @@ -117,17 +107,20 @@ You must remember this passphrase to unlock your account in the future. For non-interactive use the passphrase can be specified with the --password flag: - geth --password <passwordfile> account new - Note, this is meant to be used for testing only, it is a bad idea to save your password to file or expose in any other way. `, }, { - Action: accountUpdate, Name: "update", Usage: "Update an existing account", + Action: utils.MigrateFlags(accountUpdate), ArgsUsage: "<address>", + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + utils.LightKDFFlag, + }, Description: ` geth account update <address> @@ -141,16 +134,22 @@ format to the newest format or change the password for an account. For non-interactive use the passphrase can be specified with the --password flag: - geth --password <passwordfile> account update <address> + geth account update [options] <address> Since only one password can be given, only format update can be performed, changing your password is only possible interactively. `, }, { - Action: accountImport, - Name: "import", - Usage: "Import a private key into a new account", + Name: "import", + Usage: "Import a private key into a new account", + Action: utils.MigrateFlags(accountImport), + Flags: []cli.Flag{ + utils.DataDirFlag, + utils.KeyStoreDirFlag, + utils.PasswordFileFlag, + utils.LightKDFFlag, + }, ArgsUsage: "<keyFile>", Description: ` geth account import <keyfile> @@ -166,7 +165,7 @@ You must remember this passphrase to unlock your account in the future. For non-interactive use the passphrase can be specified with the -password flag: - geth --password <passwordfile> account import <keyfile> + geth account import [options] <keyfile> Note: As you can directly copy your encrypted accounts to another ethereum instance, @@ -298,10 +297,12 @@ func accountUpdate(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - account, oldPassword := unlockAccount(ctx, ks, ctx.Args().First(), 0, nil) - newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) - if err := ks.Update(account, oldPassword, newPassword); err != nil { - utils.Fatalf("Could not update the account: %v", err) + for _, addr := range ctx.Args() { + account, oldPassword := unlockAccount(ctx, ks, addr, 0, nil) + newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) + if err := ks.Update(account, oldPassword, newPassword); err != nil { + utils.Fatalf("Could not update the account: %v", err) + } } return nil } diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index adcb72454..5f9f67677 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -43,13 +43,13 @@ func tmpDatadirWithKeystore(t *testing.T) string { } func TestAccountListEmpty(t *testing.T) { - geth := runGeth(t, "account") + geth := runGeth(t, "account", "list") geth.expectExit() } func TestAccountList(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, "--datadir", datadir, "account") + geth := runGeth(t, "account", "list", "--datadir", datadir) defer geth.expectExit() if runtime.GOOS == "windows" { geth.expect(` @@ -67,7 +67,7 @@ Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}/k } func TestAccountNew(t *testing.T) { - geth := runGeth(t, "--lightkdf", "account", "new") + geth := runGeth(t, "account", "new", "--lightkdf") defer geth.expectExit() geth.expect(` Your new account is locked with a password. Please give a password. Do not forget this password. @@ -79,7 +79,7 @@ Repeat passphrase: {{.InputLine "foobar"}} } func TestAccountNewBadRepeat(t *testing.T) { - geth := runGeth(t, "--lightkdf", "account", "new") + geth := runGeth(t, "account", "new", "--lightkdf") defer geth.expectExit() geth.expect(` Your new account is locked with a password. Please give a password. Do not forget this password. @@ -92,9 +92,9 @@ Fatal: Passphrases do not match func TestAccountUpdate(t *testing.T) { datadir := tmpDatadirWithKeystore(t) - geth := runGeth(t, + geth := runGeth(t, "account", "update", "--datadir", datadir, "--lightkdf", - "account", "update", "f466859ead1932d743d622cb74fc058882e8648a") + "f466859ead1932d743d622cb74fc058882e8648a") defer geth.expectExit() geth.expect(` Unlocking account f466859ead1932d743d622cb74fc058882e8648a | Attempt 1/3 @@ -107,7 +107,7 @@ Repeat passphrase: {{.InputLine "foobar2"}} } func TestWalletImport(t *testing.T) { - geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json") + geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.expectExit() geth.expect(` !! Unsupported terminal, password will be echoed. @@ -122,7 +122,7 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} } func TestWalletImportBadPassword(t *testing.T) { - geth := runGeth(t, "--lightkdf", "wallet", "import", "testdata/guswallet.json") + geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.expectExit() geth.expect(` !! Unsupported terminal, password will be echoed. diff --git a/cmd/puppeth/ssh.go b/cmd/puppeth/ssh.go index 34fbc566d..93668945c 100644 --- a/cmd/puppeth/ssh.go +++ b/cmd/puppeth/ssh.go @@ -17,6 +17,8 @@ package main import ( + "bufio" + "bytes" "errors" "fmt" "io/ioutil" @@ -37,18 +39,26 @@ import ( type sshClient struct { server string // Server name or IP without port number address string // IP address of the remote server + pubkey []byte // RSA public key to authenticate the server client *ssh.Client logger log.Logger } // dial establishes an SSH connection to a remote node using the current user and -// the user's configured private RSA key. -func dial(server string) (*sshClient, error) { +// the user's configured private RSA key. If that fails, password authentication +// is fallen back to. The caller may override the login user via user@server:port. +func dial(server string, pubkey []byte) (*sshClient, error) { // Figure out a label for the server and a logger label := server if strings.Contains(label, ":") { label = label[:strings.Index(label, ":")] } + login := "" + if strings.Contains(server, "@") { + login = label[:strings.Index(label, "@")] + label = label[strings.Index(label, "@")+1:] + server = server[strings.Index(server, "@")+1:] + } logger := log.New("server", label) logger.Debug("Attempting to establish SSH connection") @@ -56,6 +66,9 @@ func dial(server string) (*sshClient, error) { if err != nil { return nil, err } + if login == "" { + login = user.Username + } // Configure the supported authentication methods (private key and password) var auths []ssh.AuthMethod @@ -71,7 +84,7 @@ func dial(server string) (*sshClient, error) { } } auths = append(auths, ssh.PasswordCallback(func() (string, error) { - fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", user.Username, server) + fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server) blob, err := terminal.ReadPassword(int(syscall.Stdin)) fmt.Println() @@ -86,11 +99,36 @@ func dial(server string) (*sshClient, error) { return nil, errors.New("no IPs associated with domain") } // Try to dial in to the remote server - logger.Trace("Dialing remote SSH server", "user", user.Username, "key", path) + logger.Trace("Dialing remote SSH server", "user", login) if !strings.Contains(server, ":") { server += ":22" } - client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: user.Username, Auth: auths}) + keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error { + // If no public key is known for SSH, ask the user to confirm + if pubkey == nil { + fmt.Printf("The authenticity of host '%s (%s)' can't be established.\n", hostname, remote) + fmt.Printf("SSH key fingerprint is %s [MD5]\n", ssh.FingerprintLegacyMD5(key)) + fmt.Printf("Are you sure you want to continue connecting (yes/no)? ") + + text, err := bufio.NewReader(os.Stdin).ReadString('\n') + switch { + case err != nil: + return err + case strings.TrimSpace(text) == "yes": + pubkey = key.Marshal() + return nil + default: + return fmt.Errorf("unknown auth choice: %v", text) + } + } + // If a public key exists for this SSH server, check that it matches + if bytes.Compare(pubkey, key.Marshal()) == 0 { + return nil + } + // We have a mismatch, forbid connecting + return errors.New("ssh key mismatch, readd the machine to update") + } + client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: login, Auth: auths, HostKeyCallback: keycheck}) if err != nil { return nil, err } @@ -98,6 +136,7 @@ func dial(server string) (*sshClient, error) { c := &sshClient{ server: label, address: addr[0], + pubkey: pubkey, client: client, logger: logger, } diff --git a/cmd/puppeth/wizard.go b/cmd/puppeth/wizard.go index 92d7962a0..9687d5e0d 100644 --- a/cmd/puppeth/wizard.go +++ b/cmd/puppeth/wizard.go @@ -44,14 +44,24 @@ type config struct { bootLight []string // Bootnodes to always connect to by light nodes ethstats string // Ethstats settings to cache for node deploys - Servers []string `json:"servers,omitempty"` + Servers map[string][]byte `json:"servers,omitempty"` +} + +// servers retrieves an alphabetically sorted list of servers. +func (c config) servers() []string { + servers := make([]string, 0, len(c.Servers)) + for server := range c.Servers { + servers = append(servers, server) + } + sort.Strings(servers) + + return servers } // flush dumps the contents of config to disk. func (c config) flush() { os.MkdirAll(filepath.Dir(c.path), 0755) - sort.Strings(c.Servers) out, _ := json.MarshalIndent(c, "", " ") if err := ioutil.WriteFile(c.path, out, 0644); err != nil { log.Warn("Failed to save puppeth configs", "file", c.path, "err", err) diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go index a67812e92..705e01031 100644 --- a/cmd/puppeth/wizard_genesis.go +++ b/cmd/puppeth/wizard_genesis.go @@ -120,7 +120,7 @@ func (w *wizard) makeGenesis() { // Query the user for some custom extras fmt.Println() fmt.Println("Specify your chain/network ID if you want an explicit one (default = random)") - genesis.Config.ChainId = big.NewInt(int64(w.readDefaultInt(rand.Intn(65536)))) + genesis.Config.ChainId = new(big.Int).SetUint64(uint64(w.readDefaultInt(rand.Intn(65536)))) fmt.Println() fmt.Println("Anything fun to embed into the genesis block? (max 32 bytes)") diff --git a/cmd/puppeth/wizard_intro.go b/cmd/puppeth/wizard_intro.go index 46383bb54..c3eaf5324 100644 --- a/cmd/puppeth/wizard_intro.go +++ b/cmd/puppeth/wizard_intro.go @@ -31,7 +31,10 @@ import ( // makeWizard creates and returns a new puppeth wizard. func makeWizard(network string) *wizard { return &wizard{ - network: network, + network: network, + conf: config{ + Servers: make(map[string][]byte), + }, servers: make(map[string]*sshClient), services: make(map[string][]string), in: bufio.NewReader(os.Stdin), @@ -77,9 +80,9 @@ func (w *wizard) run() { } else if err := json.Unmarshal(blob, &w.conf); err != nil { log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err) } else { - for _, server := range w.conf.Servers { + for server, pubkey := range w.conf.Servers { log.Info("Dialing previously configured server", "server", server) - client, err := dial(server) + client, err := dial(server, pubkey) if err != nil { log.Error("Previous server unreachable", "server", server, "err", err) } diff --git a/cmd/puppeth/wizard_netstats.go b/cmd/puppeth/wizard_netstats.go index c2a933a55..1225abb75 100644 --- a/cmd/puppeth/wizard_netstats.go +++ b/cmd/puppeth/wizard_netstats.go @@ -41,14 +41,14 @@ func (w *wizard) networkStats(tips bool) { stats.SetHeader([]string{"Server", "IP", "Status", "Service", "Details"}) stats.SetColWidth(128) - for _, server := range w.conf.Servers { + for server, pubkey := range w.conf.Servers { client := w.servers[server] logger := log.New("server", server) logger.Info("Starting remote server health-check") // If the server is not connected, try to connect again if client == nil { - conn, err := dial(server) + conn, err := dial(server, pubkey) if err != nil { logger.Error("Failed to establish remote connection", "err", err) stats.Append([]string{server, "", err.Error(), "", ""}) diff --git a/cmd/puppeth/wizard_network.go b/cmd/puppeth/wizard_network.go index 001d4e5b4..0455e1ef3 100644 --- a/cmd/puppeth/wizard_network.go +++ b/cmd/puppeth/wizard_network.go @@ -28,7 +28,9 @@ import ( func (w *wizard) manageServers() { // List all the servers we can disconnect, along with an entry to connect a new one fmt.Println() - for i, server := range w.conf.Servers { + + servers := w.conf.servers() + for i, server := range servers { fmt.Printf(" %d. Disconnect %s\n", i+1, server) } fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1) @@ -40,14 +42,14 @@ func (w *wizard) manageServers() { } // If the user selected an existing server, drop it if choice <= len(w.conf.Servers) { - server := w.conf.Servers[choice-1] + server := servers[choice-1] client := w.servers[server] delete(w.servers, server) if client != nil { client.Close() } - w.conf.Servers = append(w.conf.Servers[:choice-1], w.conf.Servers[choice:]...) + delete(w.conf.Servers, server) w.conf.flush() log.Info("Disconnected existing server", "server", server) @@ -73,14 +75,14 @@ func (w *wizard) makeServer() string { // Read and fial the server to ensure docker is present input := w.readString() - client, err := dial(input) + client, err := dial(input, nil) if err != nil { log.Error("Server not ready for puppeth", "err", err) return "" } // All checks passed, start tracking the server w.servers[input] = client - w.conf.Servers = append(w.conf.Servers, input) + w.conf.Servers[input] = client.pubkey w.conf.flush() return input @@ -93,7 +95,9 @@ func (w *wizard) selectServer() string { // List the available server to the user and wait for a choice fmt.Println() fmt.Println("Which server do you want to interact with?") - for i, server := range w.conf.Servers { + + servers := w.conf.servers() + for i, server := range servers { fmt.Printf(" %d. %s\n", i+1, server) } fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1) @@ -105,7 +109,7 @@ func (w *wizard) selectServer() string { } // If the user requested connecting to a new server, go for it if choice <= len(w.conf.Servers) { - return w.conf.Servers[choice-1] + return servers[choice-1] } return w.makeServer() } diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index af9585bd0..9e80bba60 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -52,7 +52,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv2" + whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" "gopkg.in/urfave/cli.v1" ) @@ -148,7 +148,7 @@ var ( Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)", Value: eth.DefaultConfig.EthashDatasetsOnDisk, } - NetworkIdFlag = cli.IntFlag{ + NetworkIdFlag = cli.Uint64Flag{ Name: "networkid", Usage: "Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten)", Value: eth.DefaultConfig.NetworkId, @@ -647,7 +647,7 @@ func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) { } } -// MakePasswordList reads password lines from the file specified by --password. +// MakePasswordList reads password lines from the file specified by the global --password flag. func MakePasswordList(ctx *cli.Context) []string { path := ctx.GlobalString(PasswordFileFlag.Name) if path == "" { @@ -806,7 +806,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { cfg.LightPeers = ctx.GlobalInt(LightPeersFlag.Name) } if ctx.GlobalIsSet(NetworkIdFlag.Name) { - cfg.NetworkId = ctx.GlobalInt(NetworkIdFlag.Name) + cfg.NetworkId = ctx.GlobalUint64(NetworkIdFlag.Name) } // Ethereum needs to know maxPeers to calculate the light server peer ratio. @@ -979,3 +979,27 @@ func MakeConsolePreloads(ctx *cli.Context) []string { } return preloads } + +// MigrateFlags sets the global flag from a local flag when it's set. +// This is a temporary function used for migrating old command/flags to the +// new format. +// +// e.g. geth account new --keystore /tmp/mykeystore --lightkdf +// +// is equivalent after calling this method with: +// +// geth --keystore /tmp/mykeystore --lightkdf account new +// +// This allows the use of the existing configuration functionality. +// When all flags are migrated this function can be removed and the existing +// configuration functionality must be changed that is uses local flags +func MigrateFlags(action func(ctx *cli.Context) error) func(*cli.Context) error { + return func(ctx *cli.Context) error { + for _, name := range ctx.FlagNames() { + if ctx.IsSet(name) { + ctx.GlobalSet(name, ctx.String(name)) + } + } + return action(ctx) + } +} diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 23b180487..f18025dff 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -65,7 +65,7 @@ var ( pub *ecdsa.PublicKey asymKey *ecdsa.PrivateKey nodeid *ecdsa.PrivateKey - topic []byte + topic whisper.TopicType asymKeyID string filterID string symPass string @@ -84,7 +84,7 @@ var ( testMode = flag.Bool("test", false, "use of predefined parameters for diagnostics") echoMode = flag.Bool("echo", false, "echo mode: prints some arguments for diagnostics") - argVerbosity = flag.Int("verbosity", int(log.LvlWarn), "log verbosity level") + argVerbosity = flag.Int("verbosity", int(log.LvlError), "log verbosity level") argTTL = flag.Uint("ttl", 30, "time-to-live for messages in seconds") argWorkTime = flag.Uint("work", 5, "work time in seconds") argMaxSize = flag.Int("maxsize", whisper.DefaultMaxMessageLength, "max size of message") @@ -129,7 +129,7 @@ func processArgs() { if err != nil { utils.Fatalf("Failed to parse the topic: %s", err) } - topic = x + topic = whisper.BytesToTopic(x) } if *asymmetricMode && len(*argPub) > 0 { @@ -183,7 +183,7 @@ func initialize() { if *testMode { symPass = "wwww" // ascii code: 0x77777777 - msPassword = "mail server test password" + msPassword = "wwww" } if *bootstrapMode { @@ -307,7 +307,11 @@ func configureNode() { if *asymmetricMode { if len(*argPub) == 0 { s := scanLine("Please enter the peer's public key: ") - pub = crypto.ToECDSAPub(common.FromHex(s)) + b := common.FromHex(s) + if b == nil { + utils.Fatalf("Error: can not convert hexadecimal string") + } + pub = crypto.ToECDSAPub(b) if !isKeyValid(pub) { utils.Fatalf("Error: invalid public key") } @@ -326,7 +330,7 @@ func configureNode() { if !*asymmetricMode && !*forwarderMode { if len(symPass) == 0 { - symPass, err = console.Stdin.PromptPassword("Please enter the password: ") + symPass, err = console.Stdin.PromptPassword("Please enter the password for symmetric encryption: ") if err != nil { utils.Fatalf("Failed to read passphrase: %v", err) } @@ -343,6 +347,8 @@ func configureNode() { if len(*argTopic) == 0 { generateTopic([]byte(symPass)) } + + fmt.Printf("Filter is configured for the topic: %x \n", topic) } if *mailServerMode { @@ -354,18 +360,17 @@ func configureNode() { filter := whisper.Filter{ KeySym: symKey, KeyAsym: asymKey, - Topics: [][]byte{topic}, + Topics: [][]byte{topic[:]}, AllowP2P: p2pAccept, } filterID, err = shh.Subscribe(&filter) if err != nil { utils.Fatalf("Failed to install filter: %s", err) } - fmt.Printf("Filter is configured for the topic: %x \n", topic) } func generateTopic(password []byte) { - x := pbkdf2.Key(password, password, 8196, 128, sha512.New) + x := pbkdf2.Key(password, password, 4096, 128, sha512.New) for i := 0; i < len(x); i++ { topic[i%whisper.TopicLength] ^= x[i] } @@ -485,16 +490,15 @@ func sendMsg(payload []byte) common.Hash { Dst: pub, KeySym: symKey, Payload: payload, - Topic: whisper.BytesToTopic(topic), + Topic: topic, TTL: uint32(*argTTL), PoW: *argPoW, WorkTime: uint32(*argWorkTime), } - msg := whisper.NewSentMessage(¶ms) - if msg == nil { - fmt.Printf("failed to create new message (OS level error)") - os.Exit(0) + msg, err := whisper.NewSentMessage(¶ms) + if err != nil { + utils.Fatalf("failed to create new message: %s", err) } envelope, err := msg.Wrap(¶ms) if err != nil { @@ -624,9 +628,9 @@ func requestExpiredMessagesLoop() { params.Src = nodeid params.WorkTime = 5 - msg := whisper.NewSentMessage(¶ms) - if msg == nil { - utils.Fatalf("failed to create new message (OS level error)") + msg, err := whisper.NewSentMessage(¶ms) + if err != nil { + utils.Fatalf("failed to create new message: %s", err) } env, err := msg.Wrap(¶ms) if err != nil { diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index b028f50e6..94a9ea332 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -467,8 +467,9 @@ func (ethash *Ethash) cache(block uint64) []uint32 { future = &cache{epoch: epoch + 1} ethash.fcache = future } + // New current cache, set its initial timestamp + current.used = time.Now() } - current.used = time.Now() ethash.lock.Unlock() // Wait for generation finish, bump the timestamp and finalize the cache @@ -529,8 +530,9 @@ func (ethash *Ethash) dataset(block uint64) []uint32 { future = &dataset{epoch: epoch + 1} ethash.fdataset = future } + // New current dataset, set its initial timestamp + current.used = time.Now() } - current.used = time.Now() ethash.lock.Unlock() // Wait for generation finish, bump the timestamp and finalize the cache diff --git a/console/bridge.go b/console/bridge.go index 6db54eb21..75be68188 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io" + "strings" "time" "github.com/ethereum/go-ethereum/log" @@ -240,17 +241,19 @@ func (b *bridge) Send(call otto.FunctionCall) (response otto.Value) { throwJSException(err.Error()) } var ( - rawReq = []byte(reqVal.String()) + rawReq = reqVal.String() + dec = json.NewDecoder(strings.NewReader(rawReq)) reqs []jsonrpcCall batch bool ) + dec.UseNumber() // avoid float64s if rawReq[0] == '[' { batch = true - json.Unmarshal(rawReq, &reqs) + dec.Decode(&reqs) } else { batch = false reqs = make([]jsonrpcCall, 1) - json.Unmarshal(rawReq, &reqs[0]) + dec.Decode(&reqs[0]) } // Execute the requests. diff --git a/eth/backend.go b/eth/backend.go index 03c2e38e5..f864b1d88 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -80,7 +80,7 @@ type Ethereum struct { MinerThreads int etherbase common.Address - netVersionId int + networkId uint64 netRPCService *ethapi.PublicNetAPI } @@ -118,7 +118,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { engine: CreateConsensusEngine(ctx, config, chainConfig, chainDb), shutdownChan: make(chan bool), stopDbUpgrade: stopDbUpgrade, - netVersionId: config.NetworkId, + networkId: config.NetworkId, etherbase: config.Etherbase, MinerThreads: config.MinerThreads, } @@ -347,7 +347,7 @@ func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) EthVersion() int { return int(s.protocolManager.SubProtocols[0].Version) } -func (s *Ethereum) NetVersion() int { return s.netVersionId } +func (s *Ethereum) NetVersion() uint64 { return s.networkId } func (s *Ethereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader } // Protocols implements node.Service, returning all the currently configured diff --git a/eth/config.go b/eth/config.go index 7049940d3..a09ca76f3 100644 --- a/eth/config.go +++ b/eth/config.go @@ -72,7 +72,7 @@ type Config struct { Genesis *core.Genesis `toml:",omitempty"` // Protocol options - NetworkId int // Network ID to use for selecting peers to connect to + NetworkId uint64 // Network ID to use for selecting peers to connect to SyncMode downloader.SyncMode // Light client options diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index d26995782..839969f03 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -1491,6 +1491,10 @@ func (d *Downloader) qosTuner() { func (d *Downloader) qosReduceConfidence() { // If we have a single peer, confidence is always 1 peers := uint64(d.peers.Len()) + if peers == 0 { + // Ensure peer connectivity races don't catch us off guard + return + } if peers == 1 { atomic.StoreUint64(&d.rttConfidence, 1000000) return diff --git a/eth/gen_config.go b/eth/gen_config.go index 56fba1d89..955facf8f 100644 --- a/eth/gen_config.go +++ b/eth/gen_config.go @@ -15,7 +15,7 @@ import ( func (c Config) MarshalTOML() (interface{}, error) { type Config struct { Genesis *core.Genesis `toml:",omitempty"` - NetworkId int + NetworkId uint64 SyncMode downloader.SyncMode LightServ int `toml:",omitempty"` LightPeers int `toml:",omitempty"` @@ -72,7 +72,7 @@ func (c Config) MarshalTOML() (interface{}, error) { func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { type Config struct { Genesis *core.Genesis `toml:",omitempty"` - NetworkId *int + NetworkId *uint64 SyncMode *downloader.SyncMode LightServ *int `toml:",omitempty"` LightPeers *int `toml:",omitempty"` diff --git a/eth/handler.go b/eth/handler.go index fb8a0fd57..16e371227 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -60,7 +60,7 @@ func errResp(code errCode, format string, v ...interface{}) error { } type ProtocolManager struct { - networkId int + networkId uint64 fastSync uint32 // Flag whether fast sync is enabled (gets disabled if we already have blocks) acceptTxs uint32 // Flag whether we're considered synchronised (enables transaction processing) @@ -96,7 +96,7 @@ type ProtocolManager struct { // NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable // with the ethereum network. -func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkId int, maxPeers int, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) { +func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, networkId uint64, maxPeers int, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database) (*ProtocolManager, error) { // Create the protocol manager with the base fields manager := &ProtocolManager{ networkId: networkId, @@ -733,7 +733,7 @@ func (self *ProtocolManager) txBroadcastLoop() { // EthNodeInfo represents a short summary of the Ethereum sub-protocol metadata known // about the host peer. type EthNodeInfo struct { - Network int `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3) + Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3) Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block diff --git a/eth/helper_test.go b/eth/helper_test.go index 21ac3724e..0260b9d77 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -173,7 +173,7 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te func (p *testPeer) handshake(t *testing.T, td *big.Int, head common.Hash, genesis common.Hash) { msg := &statusData{ ProtocolVersion: uint32(p.version), - NetworkId: uint32(DefaultConfig.NetworkId), + NetworkId: DefaultConfig.NetworkId, TD: td, CurrentBlock: head, GenesisBlock: genesis, diff --git a/eth/peer.go b/eth/peer.go index 6884fee8e..42ead5396 100644 --- a/eth/peer.go +++ b/eth/peer.go @@ -230,7 +230,7 @@ func (p *peer) RequestReceipts(hashes []common.Hash) error { // Handshake executes the eth protocol handshake, negotiating version number, // network IDs, difficulties, head and genesis blocks. -func (p *peer) Handshake(network int, td *big.Int, head common.Hash, genesis common.Hash) error { +func (p *peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis common.Hash) error { // Send out own handshake in a new thread errc := make(chan error, 2) var status statusData // safe to read after two values have been received from errc @@ -238,7 +238,7 @@ func (p *peer) Handshake(network int, td *big.Int, head common.Hash, genesis com go func() { errc <- p2p.Send(p.rw, StatusMsg, &statusData{ ProtocolVersion: uint32(p.version), - NetworkId: uint32(network), + NetworkId: network, TD: td, CurrentBlock: head, GenesisBlock: genesis, @@ -263,7 +263,7 @@ func (p *peer) Handshake(network int, td *big.Int, head common.Hash, genesis com return nil } -func (p *peer) readStatus(network int, status *statusData, genesis common.Hash) (err error) { +func (p *peer) readStatus(network uint64, status *statusData, genesis common.Hash) (err error) { msg, err := p.rw.ReadMsg() if err != nil { return err @@ -281,7 +281,7 @@ func (p *peer) readStatus(network int, status *statusData, genesis common.Hash) if status.GenesisBlock != genesis { return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", status.GenesisBlock[:8], genesis[:8]) } - if int(status.NetworkId) != network { + if status.NetworkId != network { return errResp(ErrNetworkIdMismatch, "%d (!= %d)", status.NetworkId, network) } if int(status.ProtocolVersion) != p.version { diff --git a/eth/protocol.go b/eth/protocol.go index 40997da7a..4bc8bee72 100644 --- a/eth/protocol.go +++ b/eth/protocol.go @@ -105,7 +105,7 @@ type txPool interface { // statusData is the network packet for the status message. type statusData struct { ProtocolVersion uint32 - NetworkId uint32 + NetworkId uint64 TD *big.Int CurrentBlock common.Hash GenesisBlock common.Hash diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 74180bedd..2056ee0a8 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -55,7 +55,7 @@ func testStatusMsgErrors(t *testing.T, protocol int) { wantError: errResp(ErrNoStatusMsg, "first msg has code 2 (!= 0)"), }, { - code: StatusMsg, data: statusData{10, uint32(DefaultConfig.NetworkId), td, currentBlock, genesis}, + code: StatusMsg, data: statusData{10, DefaultConfig.NetworkId, td, currentBlock, genesis}, wantError: errResp(ErrProtocolVersionMismatch, "10 (!= %d)", protocol), }, { @@ -63,7 +63,7 @@ func testStatusMsgErrors(t *testing.T, protocol int) { wantError: errResp(ErrNetworkIdMismatch, "999 (!= 1)"), }, { - code: StatusMsg, data: statusData{uint32(protocol), uint32(DefaultConfig.NetworkId), td, currentBlock, common.Hash{3}}, + code: StatusMsg, data: statusData{uint32(protocol), DefaultConfig.NetworkId, td, currentBlock, common.Hash{3}}, wantError: errResp(ErrGenesisBlockMismatch, "0300000000000000 (!= %x)", genesis[:8]), }, } diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index c16163ace..8765da8fa 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -323,10 +323,10 @@ func (s *Service) login(conn *websocket.Conn) error { var network, protocol string if info := infos.Protocols["eth"]; info != nil { - network = strconv.Itoa(info.(*eth.EthNodeInfo).Network) + network = fmt.Sprintf("%d", info.(*eth.EthNodeInfo).Network) protocol = fmt.Sprintf("eth/%d", eth.ProtocolVersions[0]) } else { - network = strconv.Itoa(infos.Protocols["les"].(*eth.EthNodeInfo).Network) + network = fmt.Sprintf("%d", infos.Protocols["les"].(*eth.EthNodeInfo).Network) protocol = fmt.Sprintf("les/%d", les.ProtocolVersions[0]) } auth := &authMsg{ diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index f0bc3aa4b..f9eed8797 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1435,11 +1435,11 @@ func (api *PrivateDebugAPI) SetHead(number hexutil.Uint64) { // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { net *p2p.Server - networkVersion int + networkVersion uint64 } // NewPublicNetAPI creates a new net API instance. -func NewPublicNetAPI(net *p2p.Server, networkVersion int) *PublicNetAPI { +func NewPublicNetAPI(net *p2p.Server, networkVersion uint64) *PublicNetAPI { return &PublicNetAPI{net, networkVersion} } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index c9cac125d..e35d74ae1 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -525,7 +525,105 @@ web3._extend({ const Shh_JS = ` web3._extend({ property: 'shh', - methods: [], + methods: [ + new web3._extend.Method({ + name: 'info', + call: 'shh_info' + }), + new web3._extend.Method({ + name: 'setMaxMessageLength', + call: 'shh_setMaxMessageLength', + params: 1 + }), + new web3._extend.Method({ + name: 'setMinimumPoW', + call: 'shh_setMinimumPoW', + params: 1 + }), + new web3._extend.Method({ + name: 'allowP2PMessagesFromPeer', + call: 'shh_allowP2PMessagesFromPeer', + params: 1 + }), + new web3._extend.Method({ + name: 'hasKeyPair', + call: 'shh_hasKeyPair', + params: 1 + }), + new web3._extend.Method({ + name: 'deleteKeyPair', + call: 'shh_deleteKeyPair', + params: 1 + }), + new web3._extend.Method({ + name: 'newKeyPair', + call: 'shh_newKeyPair' + }), + new web3._extend.Method({ + name: 'getPublicKey', + call: 'shh_getPublicKey', + params: 1 + }), + new web3._extend.Method({ + name: 'getPrivateKey', + call: 'shh_getPrivateKey', + params: 1 + }), + new web3._extend.Method({ + name: 'generateSymmetricKey', + call: 'shh_generateSymmetricKey', + }), + new web3._extend.Method({ + name: 'addSymmetricKeyDirect', + call: 'shh_addSymmetricKeyDirect', + params: 1 + }), + new web3._extend.Method({ + name: 'addSymmetricKeyFromPassword', + call: 'shh_addSymmetricKeyFromPassword', + params: 1 + }), + new web3._extend.Method({ + name: 'hasSymmetricKey', + call: 'shh_hasSymmetricKey', + params: 1 + }), + new web3._extend.Method({ + name: 'getSymmetricKey', + call: 'shh_getSymmetricKey', + params: 1 + }), + new web3._extend.Method({ + name: 'deleteSymmetricKey', + call: 'shh_deleteSymmetricKey', + params: 1 + }), + new web3._extend.Method({ + name: 'subscribe', + call: 'shh_subscribe', + params: 1 + }), + new web3._extend.Method({ + name: 'unsubscribe', + call: 'shh_unsubscribe', + params: 1 + }), + new web3._extend.Method({ + name: 'getNewSubscriptionMessages', + call: 'shh_getNewSubscriptionMessages', + params: 1 + }), + new web3._extend.Method({ + name: 'getFloatingMessages', + call: 'shh_getFloatingMessages', + params: 1 + }), + new web3._extend.Method({ + name: 'post', + call: 'shh_post', + params: 1 + }) + ], properties: [ new web3._extend.Property({ @@ -536,6 +634,7 @@ web3._extend({ ] }); ` + const SWARMFS_JS = ` web3._extend({ property: 'swarmfs', diff --git a/les/backend.go b/les/backend.go index 783e6e94e..646c81a7b 100644 --- a/les/backend.go +++ b/les/backend.go @@ -61,7 +61,7 @@ type LightEthereum struct { engine consensus.Engine accountManager *accounts.Manager - netVersionId int + networkId uint64 netRPCService *ethapi.PublicNetAPI } @@ -87,7 +87,7 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { accountManager: ctx.AccountManager, engine: eth.CreateConsensusEngine(ctx, config, chainConfig, chainDb), shutdownChan: make(chan bool), - netVersionId: config.NetworkId, + networkId: config.NetworkId, } if eth.blockchain, err = light.NewLightChain(odr, eth.chainConfig, eth.engine, eth.eventMux); err != nil { return nil, err @@ -187,7 +187,7 @@ func (s *LightEthereum) Protocols() []p2p.Protocol { // Ethereum protocol implementation. func (s *LightEthereum) Start(srvr *p2p.Server) error { log.Warn("Light client mode is an experimental feature") - s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.netVersionId) + s.netRPCService = ethapi.NewPublicNetAPI(srvr, s.networkId) s.protocolManager.Start(srvr) return nil } diff --git a/les/handler.go b/les/handler.go index fbb9e9906..64023af0f 100644 --- a/les/handler.go +++ b/les/handler.go @@ -95,7 +95,7 @@ type ProtocolManager struct { lightSync bool txpool txPool txrelay *LesTxRelay - networkId int + networkId uint64 chainConfig *params.ChainConfig blockchain BlockChain chainDb ethdb.Database @@ -128,7 +128,7 @@ type ProtocolManager struct { // NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable // with the ethereum network. -func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, networkId int, mux *event.TypeMux, engine consensus.Engine, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay) (*ProtocolManager, error) { +func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, networkId uint64, mux *event.TypeMux, engine consensus.Engine, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay) (*ProtocolManager, error) { // Create the protocol manager with the base fields manager := &ProtocolManager{ lightSync: lightSync, @@ -310,7 +310,7 @@ func (pm *ProtocolManager) Stop() { log.Info("Light Ethereum protocol stopped") } -func (pm *ProtocolManager) newPeer(pv, nv int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { +func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { return newPeer(pv, nv, p, newMeteredMsgWriter(rw)) } diff --git a/les/peer.go b/les/peer.go index f45605593..ab55bafe3 100644 --- a/les/peer.go +++ b/les/peer.go @@ -48,8 +48,8 @@ type peer struct { rw p2p.MsgReadWriter - version int // Protocol version negotiated - network int // Network ID being on + version int // Protocol version negotiated + network uint64 // Network ID being on id string @@ -69,7 +69,7 @@ type peer struct { fcCosts requestCostTable } -func newPeer(version, network int, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { +func newPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer { id := p.ID() return &peer{ @@ -384,7 +384,7 @@ func (p *peer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis if rGenesis != genesis { return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis[:8], genesis[:8]) } - if int(rNetwork) != p.network { + if rNetwork != p.network { return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network) } if int(rVersion) != p.version { diff --git a/mobile/geth.go b/mobile/geth.go index be04e4603..f254d39bb 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -34,7 +34,7 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" - whisper "github.com/ethereum/go-ethereum/whisper/whisperv2" + whisper "github.com/ethereum/go-ethereum/whisper/whisperv5" ) // NodeConfig represents the collection of configuration values to fine tune the Geth @@ -54,7 +54,7 @@ type NodeConfig struct { // EthereumNetworkID is the network identifier used by the Ethereum protocol to // decide if remote peers should be accepted or not. - EthereumNetworkID int + EthereumNetworkID int64 // uint64 in truth, but Java can't handle that... // EthereumGenesis is the genesis JSON to use to seed the blockchain with. An // empty genesis state is equivalent to using the mainnet's state. @@ -148,7 +148,7 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { ethConf := eth.DefaultConfig ethConf.Genesis = genesis ethConf.SyncMode = downloader.LightSync - ethConf.NetworkId = config.EthereumNetworkID + ethConf.NetworkId = uint64(config.EthereumNetworkID) ethConf.DatabaseCache = config.EthereumDatabaseCache if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return les.New(ctx, ðConf) diff --git a/node/node.go b/node/node.go index dc2ff0701..a372b1c25 100644 --- a/node/node.go +++ b/node/node.go @@ -536,6 +536,7 @@ func (n *Node) Stop() error { func (n *Node) Wait() { n.lock.RLock() if n.server == nil { + n.lock.RUnlock() return } stop := n.stop diff --git a/rpc/http.go b/rpc/http.go index 022f9ce8f..6bab02ab6 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -162,6 +162,11 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler { + // disable CORS support if user has not specified a custom CORS configuration + if len(allowedOrigins) == 0 { + return srv + } + c := cors.New(cors.Options{ AllowedOrigins: allowedOrigins, AllowedMethods: []string{"POST", "GET"}, diff --git a/vendor/golang.org/x/crypto/curve25519/cswap_amd64.s b/vendor/golang.org/x/crypto/curve25519/cswap_amd64.s index 45484d1b5..cd793a5b5 100644 --- a/vendor/golang.org/x/crypto/curve25519/cswap_amd64.s +++ b/vendor/golang.org/x/crypto/curve25519/cswap_amd64.s @@ -2,87 +2,64 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This code was translated into a form compatible with 6a from the public -// domain sources in SUPERCOP: http://bench.cr.yp.to/supercop.html - // +build amd64,!gccgo,!appengine -// func cswap(inout *[5]uint64, v uint64) +// func cswap(inout *[4][5]uint64, v uint64) TEXT ·cswap(SB),7,$0 MOVQ inout+0(FP),DI MOVQ v+8(FP),SI - CMPQ SI,$1 - MOVQ 0(DI),SI - MOVQ 80(DI),DX - MOVQ 8(DI),CX - MOVQ 88(DI),R8 - MOVQ SI,R9 - CMOVQEQ DX,SI - CMOVQEQ R9,DX - MOVQ CX,R9 - CMOVQEQ R8,CX - CMOVQEQ R9,R8 - MOVQ SI,0(DI) - MOVQ DX,80(DI) - MOVQ CX,8(DI) - MOVQ R8,88(DI) - MOVQ 16(DI),SI - MOVQ 96(DI),DX - MOVQ 24(DI),CX - MOVQ 104(DI),R8 - MOVQ SI,R9 - CMOVQEQ DX,SI - CMOVQEQ R9,DX - MOVQ CX,R9 - CMOVQEQ R8,CX - CMOVQEQ R9,R8 - MOVQ SI,16(DI) - MOVQ DX,96(DI) - MOVQ CX,24(DI) - MOVQ R8,104(DI) - MOVQ 32(DI),SI - MOVQ 112(DI),DX - MOVQ 40(DI),CX - MOVQ 120(DI),R8 - MOVQ SI,R9 - CMOVQEQ DX,SI - CMOVQEQ R9,DX - MOVQ CX,R9 - CMOVQEQ R8,CX - CMOVQEQ R9,R8 - MOVQ SI,32(DI) - MOVQ DX,112(DI) - MOVQ CX,40(DI) - MOVQ R8,120(DI) - MOVQ 48(DI),SI - MOVQ 128(DI),DX - MOVQ 56(DI),CX - MOVQ 136(DI),R8 - MOVQ SI,R9 - CMOVQEQ DX,SI - CMOVQEQ R9,DX - MOVQ CX,R9 - CMOVQEQ R8,CX - CMOVQEQ R9,R8 - MOVQ SI,48(DI) - MOVQ DX,128(DI) - MOVQ CX,56(DI) - MOVQ R8,136(DI) - MOVQ 64(DI),SI - MOVQ 144(DI),DX - MOVQ 72(DI),CX - MOVQ 152(DI),R8 - MOVQ SI,R9 - CMOVQEQ DX,SI - CMOVQEQ R9,DX - MOVQ CX,R9 - CMOVQEQ R8,CX - CMOVQEQ R9,R8 - MOVQ SI,64(DI) - MOVQ DX,144(DI) - MOVQ CX,72(DI) - MOVQ R8,152(DI) - MOVQ DI,AX - MOVQ SI,DX + SUBQ $1, SI + NOTQ SI + MOVQ SI, X15 + PSHUFD $0x44, X15, X15 + + MOVOU 0(DI), X0 + MOVOU 16(DI), X2 + MOVOU 32(DI), X4 + MOVOU 48(DI), X6 + MOVOU 64(DI), X8 + MOVOU 80(DI), X1 + MOVOU 96(DI), X3 + MOVOU 112(DI), X5 + MOVOU 128(DI), X7 + MOVOU 144(DI), X9 + + MOVO X1, X10 + MOVO X3, X11 + MOVO X5, X12 + MOVO X7, X13 + MOVO X9, X14 + + PXOR X0, X10 + PXOR X2, X11 + PXOR X4, X12 + PXOR X6, X13 + PXOR X8, X14 + PAND X15, X10 + PAND X15, X11 + PAND X15, X12 + PAND X15, X13 + PAND X15, X14 + PXOR X10, X0 + PXOR X10, X1 + PXOR X11, X2 + PXOR X11, X3 + PXOR X12, X4 + PXOR X12, X5 + PXOR X13, X6 + PXOR X13, X7 + PXOR X14, X8 + PXOR X14, X9 + + MOVOU X0, 0(DI) + MOVOU X2, 16(DI) + MOVOU X4, 32(DI) + MOVOU X6, 48(DI) + MOVOU X8, 64(DI) + MOVOU X1, 80(DI) + MOVOU X3, 96(DI) + MOVOU X5, 112(DI) + MOVOU X7, 128(DI) + MOVOU X9, 144(DI) RET diff --git a/vendor/golang.org/x/crypto/curve25519/curve25519.go b/vendor/golang.org/x/crypto/curve25519/curve25519.go index 6918c47fc..2d14c2a78 100644 --- a/vendor/golang.org/x/crypto/curve25519/curve25519.go +++ b/vendor/golang.org/x/crypto/curve25519/curve25519.go @@ -8,6 +8,10 @@ package curve25519 +import ( + "encoding/binary" +) + // This code is a port of the public domain, "ref10" implementation of // curve25519 from SUPERCOP 20130419 by D. J. Bernstein. @@ -50,17 +54,11 @@ func feCopy(dst, src *fieldElement) { // // Preconditions: b in {0,1}. func feCSwap(f, g *fieldElement, b int32) { - var x fieldElement b = -b - for i := range x { - x[i] = b & (f[i] ^ g[i]) - } - for i := range f { - f[i] ^= x[i] - } - for i := range g { - g[i] ^= x[i] + t := b & (f[i] ^ g[i]) + f[i] ^= t + g[i] ^= t } } @@ -75,12 +73,7 @@ func load3(in []byte) int64 { // load4 reads a 32-bit, little-endian value from in. func load4(in []byte) int64 { - var r int64 - r = int64(in[0]) - r |= int64(in[1]) << 8 - r |= int64(in[2]) << 16 - r |= int64(in[3]) << 24 - return r + return int64(binary.LittleEndian.Uint32(in)) } func feFromBytes(dst *fieldElement, src *[32]byte) { diff --git a/vendor/golang.org/x/crypto/ssh/certs.go b/vendor/golang.org/x/crypto/ssh/certs.go index 6331c94d5..67600e240 100644 --- a/vendor/golang.org/x/crypto/ssh/certs.go +++ b/vendor/golang.org/x/crypto/ssh/certs.go @@ -268,7 +268,7 @@ type CertChecker struct { // HostKeyFallback is called when CertChecker.CheckHostKey encounters a // public key that is not a certificate. It must implement host key // validation or else, if nil, all such keys are rejected. - HostKeyFallback func(addr string, remote net.Addr, key PublicKey) error + HostKeyFallback HostKeyCallback // IsRevoked is called for each certificate so that revocation checking // can be implemented. It should return true if the given certificate diff --git a/vendor/golang.org/x/crypto/ssh/client.go b/vendor/golang.org/x/crypto/ssh/client.go index c97f2978e..a7e3263bc 100644 --- a/vendor/golang.org/x/crypto/ssh/client.go +++ b/vendor/golang.org/x/crypto/ssh/client.go @@ -5,6 +5,7 @@ package ssh import ( + "bytes" "errors" "fmt" "net" @@ -13,7 +14,7 @@ import ( ) // Client implements a traditional SSH client that supports shells, -// subprocesses, port forwarding and tunneled dialing. +// subprocesses, TCP port/streamlocal forwarding and tunneled dialing. type Client struct { Conn @@ -59,6 +60,7 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client { conn.forwards.closeAll() }() go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-tcpip")) + go conn.forwards.handleChannels(conn.HandleChannelOpen("forwarded-streamlocal@openssh.com")) return conn } @@ -68,6 +70,11 @@ func NewClient(c Conn, chans <-chan NewChannel, reqs <-chan *Request) *Client { func NewClientConn(c net.Conn, addr string, config *ClientConfig) (Conn, <-chan NewChannel, <-chan *Request, error) { fullConf := *config fullConf.SetDefaults() + if fullConf.HostKeyCallback == nil { + c.Close() + return nil, nil, nil, errors.New("ssh: must specify HostKeyCallback") + } + conn := &connection{ sshConn: sshConn{conn: c}, } @@ -173,6 +180,13 @@ func Dial(network, addr string, config *ClientConfig) (*Client, error) { return NewClient(c, chans, reqs), nil } +// HostKeyCallback is the function type used for verifying server +// keys. A HostKeyCallback must return nil if the host key is OK, or +// an error to reject it. It receives the hostname as passed to Dial +// or NewClientConn. The remote address is the RemoteAddr of the +// net.Conn underlying the the SSH connection. +type HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error + // A ClientConfig structure is used to configure a Client. It must not be // modified after having been passed to an SSH function. type ClientConfig struct { @@ -188,10 +202,12 @@ type ClientConfig struct { // be used during authentication. Auth []AuthMethod - // HostKeyCallback, if not nil, is called during the cryptographic - // handshake to validate the server's host key. A nil HostKeyCallback - // implies that all host keys are accepted. - HostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error + // HostKeyCallback is called during the cryptographic + // handshake to validate the server's host key. The client + // configuration must supply this callback for the connection + // to succeed. The functions InsecureIgnoreHostKey or + // FixedHostKey can be used for simplistic host key checks. + HostKeyCallback HostKeyCallback // ClientVersion contains the version identification string that will // be used for the connection. If empty, a reasonable default is used. @@ -209,3 +225,33 @@ type ClientConfig struct { // A Timeout of zero means no timeout. Timeout time.Duration } + +// InsecureIgnoreHostKey returns a function that can be used for +// ClientConfig.HostKeyCallback to accept any host key. It should +// not be used for production code. +func InsecureIgnoreHostKey() HostKeyCallback { + return func(hostname string, remote net.Addr, key PublicKey) error { + return nil + } +} + +type fixedHostKey struct { + key PublicKey +} + +func (f *fixedHostKey) check(hostname string, remote net.Addr, key PublicKey) error { + if f.key == nil { + return fmt.Errorf("ssh: required host key was nil") + } + if !bytes.Equal(key.Marshal(), f.key.Marshal()) { + return fmt.Errorf("ssh: host key mismatch") + } + return nil +} + +// FixedHostKey returns a function for use in +// ClientConfig.HostKeyCallback to accept only a specific host key. +func FixedHostKey(key PublicKey) HostKeyCallback { + hk := &fixedHostKey{key} + return hk.check +} diff --git a/vendor/golang.org/x/crypto/ssh/client_auth.go b/vendor/golang.org/x/crypto/ssh/client_auth.go index fd1ec5dda..b882da086 100644 --- a/vendor/golang.org/x/crypto/ssh/client_auth.go +++ b/vendor/golang.org/x/crypto/ssh/client_auth.go @@ -179,31 +179,26 @@ func (cb publicKeyCallback) method() string { } func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) { - // Authentication is performed in two stages. The first stage sends an - // enquiry to test if each key is acceptable to the remote. The second - // stage attempts to authenticate with the valid keys obtained in the - // first stage. + // Authentication is performed by sending an enquiry to test if a key is + // acceptable to the remote. If the key is acceptable, the client will + // attempt to authenticate with the valid key. If not the client will repeat + // the process with the remaining keys. signers, err := cb() if err != nil { return false, nil, err } - var validKeys []Signer + var methods []string for _, signer := range signers { - if ok, err := validateKey(signer.PublicKey(), user, c); ok { - validKeys = append(validKeys, signer) - } else { - if err != nil { - return false, nil, err - } + ok, err := validateKey(signer.PublicKey(), user, c) + if err != nil { + return false, nil, err + } + if !ok { + continue } - } - // methods that may continue if this auth is not successful. - var methods []string - for _, signer := range validKeys { pub := signer.PublicKey() - pubKey := pub.Marshal() sign, err := signer.Sign(rand, buildDataSignedForAuth(session, userAuthRequestMsg{ User: user, @@ -236,13 +231,29 @@ func (cb publicKeyCallback) auth(session []byte, user string, c packetConn, rand if err != nil { return false, nil, err } - if success { + + // If authentication succeeds or the list of available methods does not + // contain the "publickey" method, do not attempt to authenticate with any + // other keys. According to RFC 4252 Section 7, the latter can occur when + // additional authentication methods are required. + if success || !containsMethod(methods, cb.method()) { return success, methods, err } } + return false, methods, nil } +func containsMethod(methods []string, method string) bool { + for _, m := range methods { + if m == method { + return true + } + } + + return false +} + // validateKey validates the key provided is acceptable to the server. func validateKey(key PublicKey, user string, c packetConn) (bool, error) { pubKey := key.Marshal() diff --git a/vendor/golang.org/x/crypto/ssh/common.go b/vendor/golang.org/x/crypto/ssh/common.go index 8656d0f85..dc39e4d23 100644 --- a/vendor/golang.org/x/crypto/ssh/common.go +++ b/vendor/golang.org/x/crypto/ssh/common.go @@ -9,6 +9,7 @@ import ( "crypto/rand" "fmt" "io" + "math" "sync" _ "crypto/sha1" @@ -40,7 +41,7 @@ var supportedKexAlgos = []string{ kexAlgoDH14SHA1, kexAlgoDH1SHA1, } -// supportedKexAlgos specifies the supported host-key algorithms (i.e. methods +// supportedHostKeyAlgos specifies the supported host-key algorithms (i.e. methods // of authenticating servers) in preference order. var supportedHostKeyAlgos = []string{ CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, @@ -186,7 +187,7 @@ type Config struct { // The maximum number of bytes sent or received after which a // new key is negotiated. It must be at least 256. If - // unspecified, 1 gigabyte is used. + // unspecified, a size suitable for the chosen cipher is used. RekeyThreshold uint64 // The allowed key exchanges algorithms. If unspecified then a @@ -230,11 +231,12 @@ func (c *Config) SetDefaults() { } if c.RekeyThreshold == 0 { - // RFC 4253, section 9 suggests rekeying after 1G. - c.RekeyThreshold = 1 << 30 - } - if c.RekeyThreshold < minRekeyThreshold { + // cipher specific default + } else if c.RekeyThreshold < minRekeyThreshold { c.RekeyThreshold = minRekeyThreshold + } else if c.RekeyThreshold >= math.MaxInt64 { + // Avoid weirdness if somebody uses -1 as a threshold. + c.RekeyThreshold = math.MaxInt64 } } diff --git a/vendor/golang.org/x/crypto/ssh/doc.go b/vendor/golang.org/x/crypto/ssh/doc.go index d6be89466..67b7322c0 100644 --- a/vendor/golang.org/x/crypto/ssh/doc.go +++ b/vendor/golang.org/x/crypto/ssh/doc.go @@ -14,5 +14,8 @@ others. References: [PROTOCOL.certkeys]: http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys?rev=HEAD [SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1 + +This package does not fall under the stability promise of the Go language itself, +so its API may be changed when pressing needs arise. */ package ssh // import "golang.org/x/crypto/ssh" diff --git a/vendor/golang.org/x/crypto/ssh/handshake.go b/vendor/golang.org/x/crypto/ssh/handshake.go index 8de650644..932ce8393 100644 --- a/vendor/golang.org/x/crypto/ssh/handshake.go +++ b/vendor/golang.org/x/crypto/ssh/handshake.go @@ -74,7 +74,7 @@ type handshakeTransport struct { startKex chan *pendingKex // data for host key checking - hostKeyCallback func(hostname string, remote net.Addr, key PublicKey) error + hostKeyCallback HostKeyCallback dialAddress string remoteAddr net.Addr @@ -107,6 +107,8 @@ func newHandshakeTransport(conn keyingTransport, config *Config, clientVersion, config: config, } + t.resetReadThresholds() + t.resetWriteThresholds() // We always start with a mandatory key exchange. t.requestKex <- struct{}{} @@ -237,6 +239,17 @@ func (t *handshakeTransport) requestKeyExchange() { } } +func (t *handshakeTransport) resetWriteThresholds() { + t.writePacketsLeft = packetRekeyThreshold + if t.config.RekeyThreshold > 0 { + t.writeBytesLeft = int64(t.config.RekeyThreshold) + } else if t.algorithms != nil { + t.writeBytesLeft = t.algorithms.w.rekeyBytes() + } else { + t.writeBytesLeft = 1 << 30 + } +} + func (t *handshakeTransport) kexLoop() { write: @@ -285,12 +298,8 @@ write: t.writeError = err t.sentInitPacket = nil t.sentInitMsg = nil - t.writePacketsLeft = packetRekeyThreshold - if t.config.RekeyThreshold > 0 { - t.writeBytesLeft = int64(t.config.RekeyThreshold) - } else if t.algorithms != nil { - t.writeBytesLeft = t.algorithms.w.rekeyBytes() - } + + t.resetWriteThresholds() // we have completed the key exchange. Since the // reader is still blocked, it is safe to clear out @@ -344,6 +353,17 @@ write: // key exchange itself. const packetRekeyThreshold = (1 << 31) +func (t *handshakeTransport) resetReadThresholds() { + t.readPacketsLeft = packetRekeyThreshold + if t.config.RekeyThreshold > 0 { + t.readBytesLeft = int64(t.config.RekeyThreshold) + } else if t.algorithms != nil { + t.readBytesLeft = t.algorithms.r.rekeyBytes() + } else { + t.readBytesLeft = 1 << 30 + } +} + func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) { p, err := t.conn.readPacket() if err != nil { @@ -391,12 +411,7 @@ func (t *handshakeTransport) readOnePacket(first bool) ([]byte, error) { return nil, err } - t.readPacketsLeft = packetRekeyThreshold - if t.config.RekeyThreshold > 0 { - t.readBytesLeft = int64(t.config.RekeyThreshold) - } else { - t.readBytesLeft = t.algorithms.r.rekeyBytes() - } + t.resetReadThresholds() // By default, a key exchange is hidden from higher layers by // translating it into msgIgnore. @@ -574,7 +589,9 @@ func (t *handshakeTransport) enterKeyExchange(otherInitPacket []byte) error { } result.SessionID = t.sessionID - t.conn.prepareKeyChange(t.algorithms, result) + if err := t.conn.prepareKeyChange(t.algorithms, result); err != nil { + return err + } if err = t.conn.writePacket([]byte{msgNewKeys}); err != nil { return err } @@ -614,11 +631,9 @@ func (t *handshakeTransport) client(kex kexAlgorithm, algs *algorithms, magics * return nil, err } - if t.hostKeyCallback != nil { - err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey) - if err != nil { - return nil, err - } + err = t.hostKeyCallback(t.dialAddress, t.remoteAddr, hostKey) + if err != nil { + return nil, err } return result, nil diff --git a/vendor/golang.org/x/crypto/ssh/keys.go b/vendor/golang.org/x/crypto/ssh/keys.go index f38de9898..cf6853232 100644 --- a/vendor/golang.org/x/crypto/ssh/keys.go +++ b/vendor/golang.org/x/crypto/ssh/keys.go @@ -824,7 +824,7 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) { // Implemented based on the documentation at // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key -func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) { +func parseOpenSSHPrivateKey(key []byte) (crypto.PrivateKey, error) { magic := append([]byte("openssh-key-v1"), 0) if !bytes.Equal(magic, key[0:len(magic)]) { return nil, errors.New("ssh: invalid openssh private key format") @@ -844,14 +844,15 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) { return nil, err } + if w.KdfName != "none" || w.CipherName != "none" { + return nil, errors.New("ssh: cannot decode encrypted private keys") + } + pk1 := struct { Check1 uint32 Check2 uint32 Keytype string - Pub []byte - Priv []byte - Comment string - Pad []byte `ssh:"rest"` + Rest []byte `ssh:"rest"` }{} if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil { @@ -862,24 +863,75 @@ func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) { return nil, errors.New("ssh: checkint mismatch") } - // we only handle ed25519 keys currently - if pk1.Keytype != KeyAlgoED25519 { - return nil, errors.New("ssh: unhandled key type") - } + // we only handle ed25519 and rsa keys currently + switch pk1.Keytype { + case KeyAlgoRSA: + // https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773 + key := struct { + N *big.Int + E *big.Int + D *big.Int + Iqmp *big.Int + P *big.Int + Q *big.Int + Comment string + Pad []byte `ssh:"rest"` + }{} + + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } - for i, b := range pk1.Pad { - if int(b) != i+1 { - return nil, errors.New("ssh: padding not as expected") + for i, b := range key.Pad { + if int(b) != i+1 { + return nil, errors.New("ssh: padding not as expected") + } } - } - if len(pk1.Priv) != ed25519.PrivateKeySize { - return nil, errors.New("ssh: private key unexpected length") - } + pk := &rsa.PrivateKey{ + PublicKey: rsa.PublicKey{ + N: key.N, + E: int(key.E.Int64()), + }, + D: key.D, + Primes: []*big.Int{key.P, key.Q}, + } - pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) - copy(pk, pk1.Priv) - return &pk, nil + if err := pk.Validate(); err != nil { + return nil, err + } + + pk.Precompute() + + return pk, nil + case KeyAlgoED25519: + key := struct { + Pub []byte + Priv []byte + Comment string + Pad []byte `ssh:"rest"` + }{} + + if err := Unmarshal(pk1.Rest, &key); err != nil { + return nil, err + } + + if len(key.Priv) != ed25519.PrivateKeySize { + return nil, errors.New("ssh: private key unexpected length") + } + + for i, b := range key.Pad { + if int(b) != i+1 { + return nil, errors.New("ssh: padding not as expected") + } + } + + pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) + copy(pk, key.Priv) + return &pk, nil + default: + return nil, errors.New("ssh: unhandled key type") + } } // FingerprintLegacyMD5 returns the user presentation of the key's diff --git a/vendor/golang.org/x/crypto/ssh/server.go b/vendor/golang.org/x/crypto/ssh/server.go index 77c84d165..8e95acc6a 100644 --- a/vendor/golang.org/x/crypto/ssh/server.go +++ b/vendor/golang.org/x/crypto/ssh/server.go @@ -45,6 +45,12 @@ type ServerConfig struct { // authenticating. NoClientAuth bool + // MaxAuthTries specifies the maximum number of authentication attempts + // permitted per connection. If set to a negative number, the number of + // attempts are unlimited. If set to zero, the number of attempts are limited + // to 6. + MaxAuthTries int + // PasswordCallback, if non-nil, is called when a user // attempts to authenticate using a password. PasswordCallback func(conn ConnMetadata, password []byte) (*Permissions, error) @@ -141,6 +147,10 @@ type ServerConn struct { // Request and NewChannel channels must be serviced, or the connection // will hang. func NewServerConn(c net.Conn, config *ServerConfig) (*ServerConn, <-chan NewChannel, <-chan *Request, error) { + if config.MaxAuthTries == 0 { + config.MaxAuthTries = 6 + } + fullConf := *config fullConf.SetDefaults() s := &connection{ @@ -267,8 +277,23 @@ func (s *connection) serverAuthenticate(config *ServerConfig) (*Permissions, err var cache pubKeyCache var perms *Permissions + authFailures := 0 + userAuthLoop: for { + if authFailures >= config.MaxAuthTries && config.MaxAuthTries > 0 { + discMsg := &disconnectMsg{ + Reason: 2, + Message: "too many authentication failures", + } + + if err := s.transport.writePacket(Marshal(discMsg)); err != nil { + return nil, err + } + + return nil, discMsg + } + var userAuthReq userAuthRequestMsg if packet, err := s.transport.readPacket(); err != nil { return nil, err @@ -289,6 +314,11 @@ userAuthLoop: if config.NoClientAuth { authErr = nil } + + // allow initial attempt of 'none' without penalty + if authFailures == 0 { + authFailures-- + } case "password": if config.PasswordCallback == nil { authErr = errors.New("ssh: password auth not configured") @@ -360,6 +390,7 @@ userAuthLoop: if isQuery { // The client can query if the given public key // would be okay. + if len(payload) > 0 { return nil, parseError(msgUserAuthRequest) } @@ -409,6 +440,8 @@ userAuthLoop: break userAuthLoop } + authFailures++ + var failureMsg userAuthFailureMsg if config.PasswordCallback != nil { failureMsg.Methods = append(failureMsg.Methods, "password") diff --git a/vendor/golang.org/x/crypto/ssh/streamlocal.go b/vendor/golang.org/x/crypto/ssh/streamlocal.go new file mode 100644 index 000000000..a2dccc64c --- /dev/null +++ b/vendor/golang.org/x/crypto/ssh/streamlocal.go @@ -0,0 +1,115 @@ +package ssh + +import ( + "errors" + "io" + "net" +) + +// streamLocalChannelOpenDirectMsg is a struct used for SSH_MSG_CHANNEL_OPEN message +// with "direct-streamlocal@openssh.com" string. +// +// See openssh-portable/PROTOCOL, section 2.4. connection: Unix domain socket forwarding +// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL#L235 +type streamLocalChannelOpenDirectMsg struct { + socketPath string + reserved0 string + reserved1 uint32 +} + +// forwardedStreamLocalPayload is a struct used for SSH_MSG_CHANNEL_OPEN message +// with "forwarded-streamlocal@openssh.com" string. +type forwardedStreamLocalPayload struct { + SocketPath string + Reserved0 string +} + +// streamLocalChannelForwardMsg is a struct used for SSH2_MSG_GLOBAL_REQUEST message +// with "streamlocal-forward@openssh.com"/"cancel-streamlocal-forward@openssh.com" string. +type streamLocalChannelForwardMsg struct { + socketPath string +} + +// ListenUnix is similar to ListenTCP but uses a Unix domain socket. +func (c *Client) ListenUnix(socketPath string) (net.Listener, error) { + m := streamLocalChannelForwardMsg{ + socketPath, + } + // send message + ok, _, err := c.SendRequest("streamlocal-forward@openssh.com", true, Marshal(&m)) + if err != nil { + return nil, err + } + if !ok { + return nil, errors.New("ssh: streamlocal-forward@openssh.com request denied by peer") + } + ch := c.forwards.add(&net.UnixAddr{Name: socketPath, Net: "unix"}) + + return &unixListener{socketPath, c, ch}, nil +} + +func (c *Client) dialStreamLocal(socketPath string) (Channel, error) { + msg := streamLocalChannelOpenDirectMsg{ + socketPath: socketPath, + } + ch, in, err := c.OpenChannel("direct-streamlocal@openssh.com", Marshal(&msg)) + if err != nil { + return nil, err + } + go DiscardRequests(in) + return ch, err +} + +type unixListener struct { + socketPath string + + conn *Client + in <-chan forward +} + +// Accept waits for and returns the next connection to the listener. +func (l *unixListener) Accept() (net.Conn, error) { + s, ok := <-l.in + if !ok { + return nil, io.EOF + } + ch, incoming, err := s.newCh.Accept() + if err != nil { + return nil, err + } + go DiscardRequests(incoming) + + return &chanConn{ + Channel: ch, + laddr: &net.UnixAddr{ + Name: l.socketPath, + Net: "unix", + }, + raddr: &net.UnixAddr{ + Name: "@", + Net: "unix", + }, + }, nil +} + +// Close closes the listener. +func (l *unixListener) Close() error { + // this also closes the listener. + l.conn.forwards.remove(&net.UnixAddr{Name: l.socketPath, Net: "unix"}) + m := streamLocalChannelForwardMsg{ + l.socketPath, + } + ok, _, err := l.conn.SendRequest("cancel-streamlocal-forward@openssh.com", true, Marshal(&m)) + if err == nil && !ok { + err = errors.New("ssh: cancel-streamlocal-forward@openssh.com failed") + } + return err +} + +// Addr returns the listener's network address. +func (l *unixListener) Addr() net.Addr { + return &net.UnixAddr{ + Name: l.socketPath, + Net: "unix", + } +} diff --git a/vendor/golang.org/x/crypto/ssh/tcpip.go b/vendor/golang.org/x/crypto/ssh/tcpip.go index 6151241ff..acf17175d 100644 --- a/vendor/golang.org/x/crypto/ssh/tcpip.go +++ b/vendor/golang.org/x/crypto/ssh/tcpip.go @@ -20,12 +20,20 @@ import ( // addr. Incoming connections will be available by calling Accept on // the returned net.Listener. The listener must be serviced, or the // SSH connection may hang. +// N must be "tcp", "tcp4", "tcp6", or "unix". func (c *Client) Listen(n, addr string) (net.Listener, error) { - laddr, err := net.ResolveTCPAddr(n, addr) - if err != nil { - return nil, err + switch n { + case "tcp", "tcp4", "tcp6": + laddr, err := net.ResolveTCPAddr(n, addr) + if err != nil { + return nil, err + } + return c.ListenTCP(laddr) + case "unix": + return c.ListenUnix(addr) + default: + return nil, fmt.Errorf("ssh: unsupported protocol: %s", n) } - return c.ListenTCP(laddr) } // Automatic port allocation is broken with OpenSSH before 6.0. See @@ -116,7 +124,7 @@ func (c *Client) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) { } // Register this forward, using the port number we obtained. - ch := c.forwards.add(*laddr) + ch := c.forwards.add(laddr) return &tcpListener{laddr, c, ch}, nil } @@ -131,7 +139,7 @@ type forwardList struct { // forwardEntry represents an established mapping of a laddr on a // remote ssh server to a channel connected to a tcpListener. type forwardEntry struct { - laddr net.TCPAddr + laddr net.Addr c chan forward } @@ -139,16 +147,16 @@ type forwardEntry struct { // arguments to add/remove/lookup should be address as specified in // the original forward-request. type forward struct { - newCh NewChannel // the ssh client channel underlying this forward - raddr *net.TCPAddr // the raddr of the incoming connection + newCh NewChannel // the ssh client channel underlying this forward + raddr net.Addr // the raddr of the incoming connection } -func (l *forwardList) add(addr net.TCPAddr) chan forward { +func (l *forwardList) add(addr net.Addr) chan forward { l.Lock() defer l.Unlock() f := forwardEntry{ - addr, - make(chan forward, 1), + laddr: addr, + c: make(chan forward, 1), } l.entries = append(l.entries, f) return f.c @@ -176,44 +184,69 @@ func parseTCPAddr(addr string, port uint32) (*net.TCPAddr, error) { func (l *forwardList) handleChannels(in <-chan NewChannel) { for ch := range in { - var payload forwardedTCPPayload - if err := Unmarshal(ch.ExtraData(), &payload); err != nil { - ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error()) - continue + var ( + laddr net.Addr + raddr net.Addr + err error + ) + switch channelType := ch.ChannelType(); channelType { + case "forwarded-tcpip": + var payload forwardedTCPPayload + if err = Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-tcpip payload: "+err.Error()) + continue + } + + // RFC 4254 section 7.2 specifies that incoming + // addresses should list the address, in string + // format. It is implied that this should be an IP + // address, as it would be impossible to connect to it + // otherwise. + laddr, err = parseTCPAddr(payload.Addr, payload.Port) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + raddr, err = parseTCPAddr(payload.OriginAddr, payload.OriginPort) + if err != nil { + ch.Reject(ConnectionFailed, err.Error()) + continue + } + + case "forwarded-streamlocal@openssh.com": + var payload forwardedStreamLocalPayload + if err = Unmarshal(ch.ExtraData(), &payload); err != nil { + ch.Reject(ConnectionFailed, "could not parse forwarded-streamlocal@openssh.com payload: "+err.Error()) + continue + } + laddr = &net.UnixAddr{ + Name: payload.SocketPath, + Net: "unix", + } + raddr = &net.UnixAddr{ + Name: "@", + Net: "unix", + } + default: + panic(fmt.Errorf("ssh: unknown channel type %s", channelType)) } - - // RFC 4254 section 7.2 specifies that incoming - // addresses should list the address, in string - // format. It is implied that this should be an IP - // address, as it would be impossible to connect to it - // otherwise. - laddr, err := parseTCPAddr(payload.Addr, payload.Port) - if err != nil { - ch.Reject(ConnectionFailed, err.Error()) - continue - } - raddr, err := parseTCPAddr(payload.OriginAddr, payload.OriginPort) - if err != nil { - ch.Reject(ConnectionFailed, err.Error()) - continue - } - - if ok := l.forward(*laddr, *raddr, ch); !ok { + if ok := l.forward(laddr, raddr, ch); !ok { // Section 7.2, implementations MUST reject spurious incoming // connections. ch.Reject(Prohibited, "no forward for address") continue } + } } // remove removes the forward entry, and the channel feeding its // listener. -func (l *forwardList) remove(addr net.TCPAddr) { +func (l *forwardList) remove(addr net.Addr) { l.Lock() defer l.Unlock() for i, f := range l.entries { - if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port { + if addr.Network() == f.laddr.Network() && addr.String() == f.laddr.String() { l.entries = append(l.entries[:i], l.entries[i+1:]...) close(f.c) return @@ -231,12 +264,12 @@ func (l *forwardList) closeAll() { l.entries = nil } -func (l *forwardList) forward(laddr, raddr net.TCPAddr, ch NewChannel) bool { +func (l *forwardList) forward(laddr, raddr net.Addr, ch NewChannel) bool { l.Lock() defer l.Unlock() for _, f := range l.entries { - if laddr.IP.Equal(f.laddr.IP) && laddr.Port == f.laddr.Port { - f.c <- forward{ch, &raddr} + if laddr.Network() == f.laddr.Network() && laddr.String() == f.laddr.String() { + f.c <- forward{newCh: ch, raddr: raddr} return true } } @@ -262,7 +295,7 @@ func (l *tcpListener) Accept() (net.Conn, error) { } go DiscardRequests(incoming) - return &tcpChanConn{ + return &chanConn{ Channel: ch, laddr: l.laddr, raddr: s.raddr, @@ -277,7 +310,7 @@ func (l *tcpListener) Close() error { } // this also closes the listener. - l.conn.forwards.remove(*l.laddr) + l.conn.forwards.remove(l.laddr) ok, _, err := l.conn.SendRequest("cancel-tcpip-forward", true, Marshal(&m)) if err == nil && !ok { err = errors.New("ssh: cancel-tcpip-forward failed") @@ -293,29 +326,52 @@ func (l *tcpListener) Addr() net.Addr { // Dial initiates a connection to the addr from the remote host. // The resulting connection has a zero LocalAddr() and RemoteAddr(). func (c *Client) Dial(n, addr string) (net.Conn, error) { - // Parse the address into host and numeric port. - host, portString, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - port, err := strconv.ParseUint(portString, 10, 16) - if err != nil { - return nil, err - } - // Use a zero address for local and remote address. - zeroAddr := &net.TCPAddr{ - IP: net.IPv4zero, - Port: 0, - } - ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port)) - if err != nil { - return nil, err + var ch Channel + switch n { + case "tcp", "tcp4", "tcp6": + // Parse the address into host and numeric port. + host, portString, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.ParseUint(portString, 10, 16) + if err != nil { + return nil, err + } + ch, err = c.dial(net.IPv4zero.String(), 0, host, int(port)) + if err != nil { + return nil, err + } + // Use a zero address for local and remote address. + zeroAddr := &net.TCPAddr{ + IP: net.IPv4zero, + Port: 0, + } + return &chanConn{ + Channel: ch, + laddr: zeroAddr, + raddr: zeroAddr, + }, nil + case "unix": + var err error + ch, err = c.dialStreamLocal(addr) + if err != nil { + return nil, err + } + return &chanConn{ + Channel: ch, + laddr: &net.UnixAddr{ + Name: "@", + Net: "unix", + }, + raddr: &net.UnixAddr{ + Name: addr, + Net: "unix", + }, + }, nil + default: + return nil, fmt.Errorf("ssh: unsupported protocol: %s", n) } - return &tcpChanConn{ - Channel: ch, - laddr: zeroAddr, - raddr: zeroAddr, - }, nil } // DialTCP connects to the remote address raddr on the network net, @@ -332,7 +388,7 @@ func (c *Client) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) if err != nil { return nil, err } - return &tcpChanConn{ + return &chanConn{ Channel: ch, laddr: laddr, raddr: raddr, @@ -366,26 +422,26 @@ type tcpChan struct { Channel // the backing channel } -// tcpChanConn fulfills the net.Conn interface without +// chanConn fulfills the net.Conn interface without // the tcpChan having to hold laddr or raddr directly. -type tcpChanConn struct { +type chanConn struct { Channel laddr, raddr net.Addr } // LocalAddr returns the local network address. -func (t *tcpChanConn) LocalAddr() net.Addr { +func (t *chanConn) LocalAddr() net.Addr { return t.laddr } // RemoteAddr returns the remote network address. -func (t *tcpChanConn) RemoteAddr() net.Addr { +func (t *chanConn) RemoteAddr() net.Addr { return t.raddr } // SetDeadline sets the read and write deadlines associated // with the connection. -func (t *tcpChanConn) SetDeadline(deadline time.Time) error { +func (t *chanConn) SetDeadline(deadline time.Time) error { if err := t.SetReadDeadline(deadline); err != nil { return err } @@ -396,12 +452,14 @@ func (t *tcpChanConn) SetDeadline(deadline time.Time) error { // A zero value for t means Read will not time out. // After the deadline, the error from Read will implement net.Error // with Timeout() == true. -func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error { +func (t *chanConn) SetReadDeadline(deadline time.Time) error { + // for compatibility with previous version, + // the error message contains "tcpChan" return errors.New("ssh: tcpChan: deadline not supported") } // SetWriteDeadline exists to satisfy the net.Conn interface // but is not implemented by this type. It always returns an error. -func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error { +func (t *chanConn) SetWriteDeadline(deadline time.Time) error { return errors.New("ssh: tcpChan: deadline not supported") } diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go b/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go index 07eb5edd7..a2e1b57dc 100644 --- a/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_solaris.go @@ -14,14 +14,12 @@ import ( // State contains the state of a terminal. type State struct { - termios syscall.Termios + state *unix.Termios } // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(fd int) bool { - // see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c - var termio unix.Termio - err := unix.IoctlSetTermio(fd, unix.TCGETA, &termio) + _, err := unix.IoctlGetTermio(fd, unix.TCGETA) return err == nil } @@ -71,3 +69,60 @@ func ReadPassword(fd int) ([]byte, error) { return ret, nil } + +// MakeRaw puts the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +// see http://cr.illumos.org/~webrev/andy_js/1060/ +func MakeRaw(fd int) (*State, error) { + oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + oldTermios := *oldTermiosPtr + + newTermios := oldTermios + newTermios.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + newTermios.Oflag &^= syscall.OPOST + newTermios.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newTermios.Cflag &^= syscall.CSIZE | syscall.PARENB + newTermios.Cflag |= syscall.CS8 + newTermios.Cc[unix.VMIN] = 1 + newTermios.Cc[unix.VTIME] = 0 + + if err := unix.IoctlSetTermios(fd, unix.TCSETS, &newTermios); err != nil { + return nil, err + } + + return &State{ + state: oldTermiosPtr, + }, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func Restore(fd int, oldState *State) error { + return unix.IoctlSetTermios(fd, unix.TCSETS, oldState.state) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + oldTermiosPtr, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + + return &State{ + state: oldTermiosPtr, + }, nil +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index d79c80a67..607f193a3 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -384,92 +384,92 @@ { "checksumSHA1": "TT1rac6kpQp2vz24m5yDGUNQ/QQ=", "path": "golang.org/x/crypto/cast5", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { - "checksumSHA1": "C1KKOxFoW7/W/NFNpiXK+boguNo=", + "checksumSHA1": "nAu0XmCeC6WnUySyI8R7w4cxAqU=", "path": "golang.org/x/crypto/curve25519", - "revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", - "revisionTime": "2017-03-17T13:29:17Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", "path": "golang.org/x/crypto/ed25519", - "revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", - "revisionTime": "2017-03-17T13:29:17Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", "path": "golang.org/x/crypto/ed25519/internal/edwards25519", - "revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", - "revisionTime": "2017-03-17T13:29:17Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "IIhFTrLlmlc6lEFSitqi4aw2lw0=", "path": "golang.org/x/crypto/openpgp", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "olOKkhrdkYQHZ0lf1orrFQPQrv4=", "path": "golang.org/x/crypto/openpgp/armor", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "eo/KtdjieJQXH7Qy+faXFcF70ME=", "path": "golang.org/x/crypto/openpgp/elgamal", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "rlxVSaGgqdAgwblsErxTxIfuGfg=", "path": "golang.org/x/crypto/openpgp/errors", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "LWdaR8Q9yn6eBCcnGl0HvJRDUBE=", "path": "golang.org/x/crypto/openpgp/packet", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "s2qT4UwvzBSkzXuiuMkowif1Olw=", "path": "golang.org/x/crypto/openpgp/s2k", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "1MGpGDQqnUoRpv7VEcQrXOBydXE=", "path": "golang.org/x/crypto/pbkdf2", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "y/oIaxq2d3WPizRZfVjo8RCRYTU=", "path": "golang.org/x/crypto/ripemd160", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "E8pDMGySfy5Mw+jzXOkOxo35bww=", "path": "golang.org/x/crypto/scrypt", - "revision": "453249f01cfeb54c3d549ddb75ff152ca243f9d8", - "revisionTime": "2017-02-08T20:51:15Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { - "checksumSHA1": "fsrFs762jlaILyqqQImS1GfvIvw=", + "checksumSHA1": "8sVsMTphul+B0sI0qAv4TE1ZxUk=", "path": "golang.org/x/crypto/ssh", - "revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", - "revisionTime": "2017-03-17T13:29:17Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { - "checksumSHA1": "xiderUuvye8Kpn7yX3niiJg32bE=", + "checksumSHA1": "ZaU56svwLgiJD0y8JOB3+/mpYBA=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "459e26527287adbc2adcc5d0d49abff9a5f315a7", - "revisionTime": "2017-03-17T13:29:17Z" + "revision": "c7af5bf2638a1164f2eb5467c39c6cffbd13a02e", + "revisionTime": "2017-04-25T18:31:00Z" }, { "checksumSHA1": "Y+HGqEkYM15ir+J93MEaHdyFy0c=", diff --git a/whisper/mailserver/server_test.go b/whisper/mailserver/server_test.go index 15652ea4a..64dbcd783 100644 --- a/whisper/mailserver/server_test.go +++ b/whisper/mailserver/server_test.go @@ -58,15 +58,19 @@ func TestDBKey(t *testing.T) { } func generateEnvelope(t *testing.T) *whisper.Envelope { + h := crypto.Keccak256Hash([]byte("test sample data")) params := &whisper.MessageParams{ - KeySym: []byte("test key"), + KeySym: h[:], Topic: whisper.TopicType{}, Payload: []byte("test payload"), PoW: powRequirement, WorkTime: 2, } - msg := whisper.NewSentMessage(params) + msg, err := whisper.NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed to wrap with seed %d: %s.", seed, err) @@ -188,7 +192,10 @@ func createRequest(t *testing.T, p *ServerTestParams) *whisper.Envelope { Src: p.key, } - msg := whisper.NewSentMessage(params) + msg, err := whisper.NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed to wrap with seed %d: %s.", seed, err) diff --git a/whisper/whisperv5/api.go b/whisper/whisperv5/api.go index 579efba9e..1a4e4d879 100644 --- a/whisper/whisperv5/api.go +++ b/whisper/whisperv5/api.go @@ -214,7 +214,6 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { } filter := Filter{ - Src: crypto.ToECDSAPub(common.FromHex(args.SignedWith)), PoW: args.MinPoW, Messages: make(map[common.Hash]*ReceivedMessage), AllowP2P: args.AllowP2P, @@ -232,9 +231,14 @@ func (api *PublicWhisperAPI) Subscribe(args WhisperFilterArgs) (string, error) { return "", errors.New("subscribe: " + err.Error()) } - if len(args.SignedWith) > 0 { + if len(args.Sig) > 0 { + sb := common.FromHex(args.Sig) + if sb == nil { + return "", errors.New("subscribe: sig parameter is invalid") + } + filter.Src = crypto.ToECDSAPub(sb) if !ValidatePublicKey(filter.Src) { - return "", errors.New("subscribe: invalid 'SignedWith' field") + return "", errors.New("subscribe: invalid 'sig' field") } } @@ -269,9 +273,10 @@ func (api *PublicWhisperAPI) Unsubscribe(id string) { api.whisper.Unsubscribe(id) } -// GetSubscriptionMessages retrieves all the new messages matched by a filter since the last retrieval. -func (api *PublicWhisperAPI) GetSubscriptionMessages(filterId string) []*WhisperMessage { - f := api.whisper.GetFilter(filterId) +// GetSubscriptionMessages retrieves all the new messages matched by the corresponding +// subscription filter since the last retrieval. +func (api *PublicWhisperAPI) GetNewSubscriptionMessages(id string) []*WhisperMessage { + f := api.whisper.GetFilter(id) if f != nil { newMail := f.Retrieve() return toWhisperMessages(newMail) @@ -279,10 +284,10 @@ func (api *PublicWhisperAPI) GetSubscriptionMessages(filterId string) []*Whisper return toWhisperMessages(nil) } -// GetMessages retrieves all the floating messages that match a specific filter. +// GetMessages retrieves all the floating messages that match a specific subscription filter. // It is likely to be called once per session, right after Subscribe call. -func (api *PublicWhisperAPI) GetMessages(filterId string) []*WhisperMessage { - all := api.whisper.Messages(filterId) +func (api *PublicWhisperAPI) GetFloatingMessages(id string) []*WhisperMessage { + all := api.whisper.Messages(id) return toWhisperMessages(all) } @@ -314,8 +319,8 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { return errors.New("post: key is missing") } - if len(args.SignWith) > 0 { - params.Src, err = api.whisper.GetPrivateKey(args.SignWith) + if len(args.Sig) > 0 { + params.Src, err = api.whisper.GetPrivateKey(args.Sig) if err != nil { return err } @@ -345,7 +350,11 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { return errors.New("post: topic is missing for symmetric encryption") } } else if args.Type == "asym" { - params.Dst = crypto.ToECDSAPub(common.FromHex(args.Key)) + kb := common.FromHex(args.Key) + if kb == nil { + return errors.New("post: public key for asymmetric encryption is invalid") + } + params.Dst = crypto.ToECDSAPub(kb) if !ValidatePublicKey(params.Dst) { return errors.New("post: public key for asymmetric encryption is invalid") } @@ -354,9 +363,9 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { } // encrypt and send - message := NewSentMessage(¶ms) - if message == nil { - return errors.New("post: failed create new message, probably due to failed rand function (OS level)") + message, err := NewSentMessage(¶ms) + if err != nil { + return err } envelope, err := message.Wrap(¶ms) if err != nil { @@ -382,8 +391,8 @@ func (api *PublicWhisperAPI) Post(args PostArgs) error { type PostArgs struct { Type string `json:"type"` // "sym"/"asym" (symmetric or asymmetric) TTL uint32 `json:"ttl"` // time-to-live in seconds - SignWith string `json:"signWith"` // id of the signing key - Key string `json:"key"` // id of encryption key + Sig string `json:"sig"` // id of the signing key + Key string `json:"key"` // key id (in case of sym) or public key (in case of asym) Topic hexutil.Bytes `json:"topic"` // topic (4 bytes) Padding hexutil.Bytes `json:"padding"` // optional padding bytes Payload hexutil.Bytes `json:"payload"` // payload to be encrypted @@ -393,12 +402,12 @@ type PostArgs struct { } type WhisperFilterArgs struct { - Symmetric bool // encryption type - Key string // id of the key to be used for decryption - SignedWith string // public key of the sender to be verified - MinPoW float64 // minimal PoW requirement - Topics [][]byte // list of topics (up to 4 bytes each) to match - AllowP2P bool // indicates wheather direct p2p messages are allowed for this filter + Symmetric bool // encryption type + Key string // id of the key to be used for decryption + Sig string // public key of the sender to be verified + MinPoW float64 // minimal PoW requirement + Topics [][]byte // list of topics (up to 4 bytes each) to match + AllowP2P bool // indicates wheather direct p2p messages are allowed for this filter } // UnmarshalJSON implements the json.Unmarshaler interface, invoked to convert a @@ -406,12 +415,12 @@ type WhisperFilterArgs struct { func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { // Unmarshal the JSON message and sanity check var obj struct { - Type string `json:"type"` - Key string `json:"key"` - SignedWith string `json:"signedWith"` - MinPoW float64 `json:"minPoW"` - Topics []interface{} `json:"topics"` - AllowP2P bool `json:"allowP2P"` + Type string `json:"type"` + Key string `json:"key"` + Sig string `json:"sig"` + MinPoW float64 `json:"minPoW"` + Topics []interface{} `json:"topics"` + AllowP2P bool `json:"allowP2P"` } if err := json.Unmarshal(b, &obj); err != nil { return err @@ -427,7 +436,7 @@ func (args *WhisperFilterArgs) UnmarshalJSON(b []byte) (err error) { } args.Key = obj.Key - args.SignedWith = obj.SignedWith + args.Sig = obj.Sig args.MinPoW = obj.MinPoW args.AllowP2P = obj.AllowP2P @@ -463,7 +472,7 @@ type WhisperMessage struct { Topic string `json:"topic"` Payload string `json:"payload"` Padding string `json:"padding"` - Src string `json:"signedWith"` + Src string `json:"sig"` Dst string `json:"recipientPublicKey"` Timestamp uint32 `json:"timestamp"` TTL uint32 `json:"ttl"` @@ -474,7 +483,6 @@ type WhisperMessage struct { // NewWhisperMessage converts an internal message into an API version. func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage { msg := WhisperMessage{ - Topic: common.ToHex(message.Topic[:]), Payload: common.ToHex(message.Payload), Padding: common.ToHex(message.Padding), Timestamp: message.Sent, @@ -483,11 +491,20 @@ func NewWhisperMessage(message *ReceivedMessage) *WhisperMessage { Hash: common.ToHex(message.EnvelopeHash.Bytes()), } + if len(message.Topic) == TopicLength { + msg.Topic = common.ToHex(message.Topic[:]) + } if message.Dst != nil { - msg.Dst = common.ToHex(crypto.FromECDSAPub(message.Dst)) + b := crypto.FromECDSAPub(message.Dst) + if b != nil { + msg.Dst = common.ToHex(b) + } } if isMessageSigned(message.Raw[0]) { - msg.Src = common.ToHex(crypto.FromECDSAPub(message.SigToPubKey())) + b := crypto.FromECDSAPub(message.SigToPubKey()) + if b != nil { + msg.Src = common.ToHex(b) + } } return &msg } diff --git a/whisper/whisperv5/api_test.go b/whisper/whisperv5/api_test.go index 9207c6f10..a6d82d850 100644 --- a/whisper/whisperv5/api_test.go +++ b/whisper/whisperv5/api_test.go @@ -43,7 +43,7 @@ func TestBasic(t *testing.T) { t.Fatalf("wrong version: %d.", ver) } - mail := api.GetSubscriptionMessages("non-existent-id") + mail := api.GetNewSubscriptionMessages("non-existent-id") if len(mail) != 0 { t.Fatalf("failed GetFilterChanges: premature result") } @@ -173,7 +173,7 @@ func TestUnmarshalFilterArgs(t *testing.T) { s := []byte(`{ "type":"sym", "key":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", - "signedWith":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", + "sig":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", "minPoW":2.34, "topics":["0x00000000", "0x007f80ff", "0xff807f00", "0xf26e7779"], "allowP2P":true @@ -191,8 +191,8 @@ func TestUnmarshalFilterArgs(t *testing.T) { if f.Key != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { t.Fatalf("wrong key: %s.", f.Key) } - if f.SignedWith != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { - t.Fatalf("wrong SignedWith: %s.", f.SignedWith) + if f.Sig != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { + t.Fatalf("wrong sig: %s.", f.Sig) } if f.MinPoW != 2.34 { t.Fatalf("wrong MinPoW: %f.", f.MinPoW) @@ -229,7 +229,7 @@ func TestUnmarshalPostArgs(t *testing.T) { s := []byte(`{ "type":"sym", "ttl":12345, - "signWith":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", + "sig":"0x70c87d191324e6712a591f304b4eedef6ad9bb9d", "key":"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83", "topic":"0xf26e7779", "padding":"0x74686973206973206D79207465737420737472696E67", @@ -251,8 +251,8 @@ func TestUnmarshalPostArgs(t *testing.T) { if a.TTL != 12345 { t.Fatalf("wrong ttl: %d.", a.TTL) } - if a.SignWith != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { - t.Fatalf("wrong From: %s.", a.SignWith) + if a.Sig != "0x70c87d191324e6712a591f304b4eedef6ad9bb9d" { + t.Fatalf("wrong From: %s.", a.Sig) } if a.Key != "0x9b2055d370f73ec7d8a03e965129118dc8f5bf83" { t.Fatalf("wrong Key: %s.", a.Key) @@ -282,7 +282,7 @@ func waitForMessages(api *PublicWhisperAPI, id string, target int) []*WhisperMes // timeout: 2 seconds result := make([]*WhisperMessage, 0, target) for i := 0; i < 100; i++ { - mail := api.GetSubscriptionMessages(id) + mail := api.GetNewSubscriptionMessages(id) if len(mail) > 0 { for _, m := range mail { result = append(result, m) @@ -347,7 +347,7 @@ func TestIntegrationAsym(t *testing.T) { var f WhisperFilterArgs f.Symmetric = false f.Key = key - f.SignedWith = sigPubKey.String() + f.Sig = sigPubKey.String() f.Topics = make([][]byte, 2) f.Topics[0] = topics[0][:] f.Topics[1] = topics[1][:] @@ -362,7 +362,7 @@ func TestIntegrationAsym(t *testing.T) { var p PostArgs p.Type = "asym" p.TTL = 2 - p.SignWith = sig + p.Sig = sig p.Key = dstPubKey.String() p.Padding = []byte("test string") p.Payload = []byte("extended test string") @@ -448,8 +448,8 @@ func TestIntegrationSym(t *testing.T) { f.Topics = make([][]byte, 2) f.Topics[0] = topics[0][:] f.Topics[1] = topics[1][:] - f.MinPoW = 0.324 - f.SignedWith = sigPubKey.String() + f.MinPoW = DefaultMinimumPoW / 2 + f.Sig = sigPubKey.String() f.AllowP2P = false id, err := api.Subscribe(f) @@ -461,7 +461,7 @@ func TestIntegrationSym(t *testing.T) { p.Type = "sym" p.TTL = 1 p.Key = symKeyID - p.SignWith = sig + p.Sig = sig p.Padding = []byte("test string") p.Payload = []byte("extended test string") p.PowTarget = DefaultMinimumPoW @@ -546,8 +546,8 @@ func TestIntegrationSymWithFilter(t *testing.T) { f.Topics = make([][]byte, 2) f.Topics[0] = topics[0][:] f.Topics[1] = topics[1][:] - f.MinPoW = 0.324 - f.SignedWith = sigPubKey.String() + f.MinPoW = DefaultMinimumPoW / 2 + f.Sig = sigPubKey.String() f.AllowP2P = false id, err := api.Subscribe(f) @@ -559,7 +559,7 @@ func TestIntegrationSymWithFilter(t *testing.T) { p.Type = "sym" p.TTL = 1 p.Key = symKeyID - p.SignWith = sigKeyID + p.Sig = sigKeyID p.Padding = []byte("test string") p.Payload = []byte("extended test string") p.PowTarget = DefaultMinimumPoW diff --git a/whisper/whisperv5/benchmarks_test.go b/whisper/whisperv5/benchmarks_test.go index 417b2881b..dcfbcb56d 100644 --- a/whisper/whisperv5/benchmarks_test.go +++ b/whisper/whisperv5/benchmarks_test.go @@ -28,12 +28,6 @@ func BenchmarkDeriveKeyMaterial(b *testing.B) { } } -func BenchmarkDeriveOneTimeKey(b *testing.B) { - for i := 0; i < b.N; i++ { - DeriveOneTimeKey([]byte("test value 1"), []byte("test value 2"), 0) - } -} - func BenchmarkEncryptionSym(b *testing.B) { InitSingleTest() @@ -43,7 +37,7 @@ func BenchmarkEncryptionSym(b *testing.B) { } for i := 0; i < b.N; i++ { - msg := NewSentMessage(params) + msg, _ := NewSentMessage(params) _, err := msg.Wrap(params) if err != nil { b.Errorf("failed Wrap with seed %d: %s.", seed, err) @@ -68,7 +62,7 @@ func BenchmarkEncryptionAsym(b *testing.B) { params.Dst = &key.PublicKey for i := 0; i < b.N; i++ { - msg := NewSentMessage(params) + msg, _ := NewSentMessage(params) _, err := msg.Wrap(params) if err != nil { b.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -83,7 +77,7 @@ func BenchmarkDecryptionSymValid(b *testing.B) { if err != nil { b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - msg := NewSentMessage(params) + msg, _ := NewSentMessage(params) env, err := msg.Wrap(params) if err != nil { b.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -105,7 +99,7 @@ func BenchmarkDecryptionSymInvalid(b *testing.B) { if err != nil { b.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - msg := NewSentMessage(params) + msg, _ := NewSentMessage(params) env, err := msg.Wrap(params) if err != nil { b.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -134,7 +128,7 @@ func BenchmarkDecryptionAsymValid(b *testing.B) { f := Filter{KeyAsym: key} params.KeySym = nil params.Dst = &key.PublicKey - msg := NewSentMessage(params) + msg, _ := NewSentMessage(params) env, err := msg.Wrap(params) if err != nil { b.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -161,7 +155,7 @@ func BenchmarkDecryptionAsymInvalid(b *testing.B) { } params.KeySym = nil params.Dst = &key.PublicKey - msg := NewSentMessage(params) + msg, _ := NewSentMessage(params) env, err := msg.Wrap(params) if err != nil { b.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -203,7 +197,7 @@ func BenchmarkPoW(b *testing.B) { for i := 0; i < b.N; i++ { increment(params.Payload) - msg := NewSentMessage(params) + msg, _ := NewSentMessage(params) _, err := msg.Wrap(params) if err != nil { b.Fatalf("failed Wrap with seed %d: %s.", seed, err) diff --git a/whisper/whisperv5/doc.go b/whisper/whisperv5/doc.go index d60868f67..768291a16 100644 --- a/whisper/whisperv5/doc.go +++ b/whisper/whisperv5/doc.go @@ -49,18 +49,16 @@ const ( paddingMask = byte(3) signatureFlag = byte(4) - TopicLength = 4 - signatureLength = 65 - aesKeyLength = 32 - saltLength = 12 - AESNonceMaxLength = 12 - keyIdSize = 32 + TopicLength = 4 + signatureLength = 65 + aesKeyLength = 32 + AESNonceLength = 12 + keyIdSize = 32 DefaultMaxMessageLength = 1024 * 1024 - DefaultMinimumPoW = 1.0 // todo: review after testing. + DefaultMinimumPoW = 0.2 - padSizeLimitLower = 128 // it can not be less - we don't want to reveal the absence of signature - padSizeLimitUpper = 256 // just an arbitrary number, could be changed without losing compatibility + padSizeLimit = 256 // just an arbitrary number, could be changed without breaking the protocol (must not exceed 2^24) messageQueueLimit = 1024 expirationCycle = time.Second diff --git a/whisper/whisperv5/envelope.go b/whisper/whisperv5/envelope.go index dffa7b286..d95fcab75 100644 --- a/whisper/whisperv5/envelope.go +++ b/whisper/whisperv5/envelope.go @@ -40,7 +40,6 @@ type Envelope struct { Expiry uint32 TTL uint32 Topic TopicType - Salt []byte AESNonce []byte Data []byte EnvNonce uint64 @@ -50,15 +49,25 @@ type Envelope struct { // Don't access hash directly, use Hash() function instead. } +// size returns the size of envelope as it is sent (i.e. public fields only) +func (e *Envelope) size() int { + return 20 + len(e.Version) + len(e.AESNonce) + len(e.Data) +} + +// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce. +func (e *Envelope) rlpWithoutNonce() []byte { + res, _ := rlp.EncodeToBytes([]interface{}{e.Version, e.Expiry, e.TTL, e.Topic, e.AESNonce, e.Data}) + return res +} + // NewEnvelope wraps a Whisper message with expiration and destination data // included into an envelope for network forwarding. -func NewEnvelope(ttl uint32, topic TopicType, salt []byte, aesNonce []byte, msg *SentMessage) *Envelope { +func NewEnvelope(ttl uint32, topic TopicType, aesNonce []byte, msg *SentMessage) *Envelope { env := Envelope{ Version: make([]byte, 1), Expiry: uint32(time.Now().Add(time.Second * time.Duration(ttl)).Unix()), TTL: ttl, Topic: topic, - Salt: salt, AESNonce: aesNonce, Data: msg.Raw, EnvNonce: 0, @@ -126,10 +135,6 @@ func (e *Envelope) Seal(options *MessageParams) error { return nil } -func (e *Envelope) size() int { - return len(e.Data) + len(e.Version) + len(e.AESNonce) + len(e.Salt) + 20 -} - func (e *Envelope) PoW() float64 { if e.pow == 0 { e.calculatePoW(0) @@ -159,12 +164,6 @@ func (e *Envelope) powToFirstBit(pow float64) int { return int(bits) } -// rlpWithoutNonce returns the RLP encoded envelope contents, except the nonce. -func (e *Envelope) rlpWithoutNonce() []byte { - res, _ := rlp.EncodeToBytes([]interface{}{e.Expiry, e.TTL, e.Topic, e.Salt, e.AESNonce, e.Data}) - return res -} - // Hash returns the SHA3 hash of the envelope, calculating it if not yet done. func (e *Envelope) Hash() common.Hash { if (e.hash == common.Hash{}) { @@ -210,7 +209,7 @@ func (e *Envelope) OpenAsymmetric(key *ecdsa.PrivateKey) (*ReceivedMessage, erro // OpenSymmetric tries to decrypt an envelope, potentially encrypted with a particular key. func (e *Envelope) OpenSymmetric(key []byte) (msg *ReceivedMessage, err error) { msg = &ReceivedMessage{Raw: e.Data} - err = msg.decryptSymmetric(key, e.Salt, e.AESNonce) + err = msg.decryptSymmetric(key, e.AESNonce) if err != nil { msg = nil } diff --git a/whisper/whisperv5/filter_test.go b/whisper/whisperv5/filter_test.go index ae21d1739..dd4ab9e8d 100644 --- a/whisper/whisperv5/filter_test.go +++ b/whisper/whisperv5/filter_test.go @@ -68,7 +68,7 @@ func generateFilter(t *testing.T, symmetric bool) (*Filter, error) { f.Src = &key.PublicKey if symmetric { - f.KeySym = make([]byte, 12) + f.KeySym = make([]byte, aesKeyLength) mrand.Read(f.KeySym) f.SymKeyHash = crypto.Keccak256Hash(f.KeySym) } else { @@ -179,7 +179,10 @@ func TestMatchEnvelope(t *testing.T) { params.Topic[0] = 0xFF // ensure mismatch // mismatch with pseudo-random data - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -197,7 +200,10 @@ func TestMatchEnvelope(t *testing.T) { i := mrand.Int() % 4 fsym.Topics[i] = params.Topic[:] fasym.Topics[i] = params.Topic[:] - msg = NewSentMessage(params) + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err = msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap() with seed %d: %s.", seed, err) @@ -245,7 +251,10 @@ func TestMatchEnvelope(t *testing.T) { } params.KeySym = nil params.Dst = &key.PublicKey - msg = NewSentMessage(params) + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err = msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap() with seed %d: %s.", seed, err) @@ -323,12 +332,14 @@ func TestMatchMessageSym(t *testing.T) { params.KeySym = f.KeySym params.Topic = BytesToTopic(f.Topics[index]) - sentMessage := NewSentMessage(params) + sentMessage, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := sentMessage.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) } - msg := env.Open(f) if msg == nil { t.Fatalf("failed Open with seed %d.", seed) @@ -419,12 +430,14 @@ func TestMatchMessageAsym(t *testing.T) { keySymOrig := params.KeySym params.KeySym = nil - sentMessage := NewSentMessage(params) + sentMessage, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := sentMessage.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) } - msg := env.Open(f) if msg == nil { t.Fatalf("failed to open with seed %d.", seed) @@ -506,7 +519,10 @@ func generateCompatibeEnvelope(t *testing.T, f *Filter) *Envelope { params.KeySym = f.KeySym params.Topic = BytesToTopic(f.Topics[2]) - sentMessage := NewSentMessage(params) + sentMessage, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := sentMessage.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -678,7 +694,10 @@ func TestVariableTopics(t *testing.T) { if err != nil { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) diff --git a/whisper/whisperv5/message.go b/whisper/whisperv5/message.go index 9b9c389a6..4ef469b51 100644 --- a/whisper/whisperv5/message.go +++ b/whisper/whisperv5/message.go @@ -23,14 +23,14 @@ import ( "crypto/cipher" "crypto/ecdsa" crand "crypto/rand" - "crypto/sha256" + "encoding/binary" "errors" + "strconv" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/ecies" "github.com/ethereum/go-ethereum/log" - "golang.org/x/crypto/pbkdf2" ) // Options specifies the exact way a message should be wrapped into an Envelope. @@ -86,58 +86,76 @@ func (msg *ReceivedMessage) isAsymmetricEncryption() bool { return msg.Dst != nil } -func DeriveOneTimeKey(key []byte, salt []byte, version uint64) ([]byte, error) { - if version == 0 { - derivedKey := pbkdf2.Key(key, salt, 8, aesKeyLength, sha256.New) - return derivedKey, nil - } else { - return nil, unknownVersionError(version) - } -} - // NewMessage creates and initializes a non-signed, non-encrypted Whisper message. -func NewSentMessage(params *MessageParams) *SentMessage { +func NewSentMessage(params *MessageParams) (*SentMessage, error) { msg := SentMessage{} - msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Payload)+signatureLength+padSizeLimitUpper) + msg.Raw = make([]byte, 1, len(params.Payload)+len(params.Padding)+signatureLength+padSizeLimit) msg.Raw[0] = 0 // set all the flags to zero err := msg.appendPadding(params) if err != nil { - log.Error("failed to create NewSentMessage", "err", err) - return nil + return nil, err } msg.Raw = append(msg.Raw, params.Payload...) - return &msg + return &msg, nil +} + +// getSizeOfLength returns the number of bytes necessary to encode the entire size padding (including these bytes) +func getSizeOfLength(b []byte) (sz int, err error) { + sz = intSize(len(b)) // first iteration + sz = intSize(len(b) + sz) // second iteration + if sz > 3 { + err = errors.New("oversized padding parameter") + } + return sz, err +} + +// sizeOfIntSize returns minimal number of bytes necessary to encode an integer value +func intSize(i int) (s int) { + for s = 1; i >= 256; s++ { + i /= 256 + } + return s } // appendPadding appends the pseudorandom padding bytes and sets the padding flag. // The last byte contains the size of padding (thus, its size must not exceed 256). func (msg *SentMessage) appendPadding(params *MessageParams) error { - total := len(params.Payload) + 1 + rawSize := len(params.Payload) + 1 if params.Src != nil { - total += signatureLength + rawSize += signatureLength } - padChunk := padSizeLimitUpper - if total <= padSizeLimitLower { - padChunk = padSizeLimitLower - } - odd := total % padChunk - if odd > 0 { - padSize := padChunk - odd - if padSize > 255 { - // this algorithm is only valid if padSizeLimitUpper <= 256. - // if padSizeLimitUpper will ever change, please fix the algorithm - // (for more information see ReceivedMessage.extractPadding() function). + odd := rawSize % padSizeLimit + + if len(params.Padding) != 0 { + padSize := len(params.Padding) + padLengthSize, err := getSizeOfLength(params.Padding) + if err != nil { + return err + } + totalPadSize := padSize + padLengthSize + buf := make([]byte, 8) + binary.LittleEndian.PutUint32(buf, uint32(totalPadSize)) + buf = buf[:padLengthSize] + msg.Raw = append(msg.Raw, buf...) + msg.Raw = append(msg.Raw, params.Padding...) + msg.Raw[0] |= byte(padLengthSize) // number of bytes indicating the padding size + } else if odd != 0 { + totalPadSize := padSizeLimit - odd + if totalPadSize > 255 { + // this algorithm is only valid if padSizeLimit < 256. + // if padSizeLimit will ever change, please fix the algorithm + // (please see also ReceivedMessage.extractPadding() function). panic("please fix the padding algorithm before releasing new version") } - buf := make([]byte, padSize) + buf := make([]byte, totalPadSize) _, err := crand.Read(buf[1:]) if err != nil { return err } - buf[0] = byte(padSize) - if params.Padding != nil { - copy(buf[1:], params.Padding) + if totalPadSize > 6 && !validateSymmetricKey(buf) { + return errors.New("failed to generate random padding of size " + strconv.Itoa(totalPadSize)) } + buf[0] = byte(totalPadSize) msg.Raw = append(msg.Raw, buf...) msg.Raw[0] |= byte(0x1) // number of bytes indicating the padding size } @@ -178,46 +196,31 @@ func (msg *SentMessage) encryptAsymmetric(key *ecdsa.PublicKey) error { // encryptSymmetric encrypts a message with a topic key, using AES-GCM-256. // nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). -func (msg *SentMessage) encryptSymmetric(key []byte) (salt []byte, nonce []byte, err error) { +func (msg *SentMessage) encryptSymmetric(key []byte) (nonce []byte, err error) { if !validateSymmetricKey(key) { - return nil, nil, errors.New("invalid key provided for symmetric encryption") - } - - salt = make([]byte, saltLength) - _, err = crand.Read(salt) - if err != nil { - return nil, nil, err - } else if !validateSymmetricKey(salt) { - return nil, nil, errors.New("crypto/rand failed to generate salt") + return nil, errors.New("invalid key provided for symmetric encryption") } - derivedKey, err := DeriveOneTimeKey(key, salt, EnvelopeVersion) - if err != nil { - return nil, nil, err - } - if !validateSymmetricKey(derivedKey) { - return nil, nil, errors.New("failed to derive one-time key") - } - block, err := aes.NewCipher(derivedKey) + block, err := aes.NewCipher(key) if err != nil { - return nil, nil, err + return nil, err } aesgcm, err := cipher.NewGCM(block) if err != nil { - return nil, nil, err + return nil, err } // never use more than 2^32 random nonces with a given key nonce = make([]byte, aesgcm.NonceSize()) _, err = crand.Read(nonce) if err != nil { - return nil, nil, err + return nil, err } else if !validateSymmetricKey(nonce) { - return nil, nil, errors.New("crypto/rand failed to generate nonce") + return nil, errors.New("crypto/rand failed to generate nonce") } msg.Raw = aesgcm.Seal(nil, nonce, msg.Raw, nil) - return salt, nonce, nil + return nonce, nil } // Wrap bundles the message into an Envelope to transmit over the network. @@ -231,11 +234,11 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er return nil, err } } - var salt, nonce []byte + var nonce []byte if options.Dst != nil { err = msg.encryptAsymmetric(options.Dst) } else if options.KeySym != nil { - salt, nonce, err = msg.encryptSymmetric(options.KeySym) + nonce, err = msg.encryptSymmetric(options.KeySym) } else { err = errors.New("unable to encrypt the message: neither symmetric nor assymmetric key provided") } @@ -244,7 +247,7 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er return nil, err } - envelope = NewEnvelope(options.TTL, options.Topic, salt, nonce, msg) + envelope = NewEnvelope(options.TTL, options.Topic, nonce, msg) err = envelope.Seal(options) if err != nil { return nil, err @@ -254,13 +257,8 @@ func (msg *SentMessage) Wrap(options *MessageParams) (envelope *Envelope, err er // decryptSymmetric decrypts a message with a topic key, using AES-GCM-256. // nonce size should be 12 bytes (see cipher.gcmStandardNonceSize). -func (msg *ReceivedMessage) decryptSymmetric(key []byte, salt []byte, nonce []byte) error { - derivedKey, err := DeriveOneTimeKey(key, salt, msg.EnvelopeVersion) - if err != nil { - return err - } - - block, err := aes.NewCipher(derivedKey) +func (msg *ReceivedMessage) decryptSymmetric(key []byte, nonce []byte) error { + block, err := aes.NewCipher(key) if err != nil { return err } @@ -323,7 +321,8 @@ func (msg *ReceivedMessage) Validate() bool { // can be successfully decrypted. func (msg *ReceivedMessage) extractPadding(end int) (int, bool) { paddingSize := 0 - sz := int(msg.Raw[0] & paddingMask) // number of bytes containing the entire size of padding, could be zero + sz := int(msg.Raw[0] & paddingMask) // number of bytes indicating the entire size of padding (including these bytes) + // could be zero -- it means no padding if sz != 0 { paddingSize = int(bytesToUintLittleEndian(msg.Raw[1 : 1+sz])) if paddingSize < sz || paddingSize+1 > end { diff --git a/whisper/whisperv5/message_test.go b/whisper/whisperv5/message_test.go index 1ed7250d3..aa82a02f3 100644 --- a/whisper/whisperv5/message_test.go +++ b/whisper/whisperv5/message_test.go @@ -31,9 +31,9 @@ func copyFromBuf(dst []byte, src []byte, beg int) int { } func generateMessageParams() (*MessageParams, error) { - // set all the parameters except p.Dst + // set all the parameters except p.Dst and p.Padding - buf := make([]byte, 1024) + buf := make([]byte, 4) mrand.Read(buf) sz := mrand.Intn(400) @@ -42,14 +42,10 @@ func generateMessageParams() (*MessageParams, error) { p.WorkTime = 1 p.TTL = uint32(mrand.Intn(1024)) p.Payload = make([]byte, sz) - p.Padding = make([]byte, padSizeLimitUpper) p.KeySym = make([]byte, aesKeyLength) - - var b int - b = copyFromBuf(p.Payload, buf, b) - b = copyFromBuf(p.Padding, buf, b) - b = copyFromBuf(p.KeySym, buf, b) - p.Topic = BytesToTopic(buf[b:]) + mrand.Read(p.Payload) + mrand.Read(p.KeySym) + p.Topic = BytesToTopic(buf) var err error p.Src, err = crypto.GenerateKey() @@ -77,11 +73,12 @@ func singleMessageTest(t *testing.T, symmetric bool) { } text := make([]byte, 0, 512) - steg := make([]byte, 0, 512) text = append(text, params.Payload...) - steg = append(steg, params.Padding...) - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -102,10 +99,6 @@ func singleMessageTest(t *testing.T, symmetric bool) { t.Fatalf("failed to validate with seed %d.", seed) } - padsz := len(decrypted.Padding) - if !bytes.Equal(steg[:padsz], decrypted.Padding) { - t.Fatalf("failed with seed %d: compare padding.", seed) - } if !bytes.Equal(text, decrypted.Payload) { t.Fatalf("failed with seed %d: compare payload.", seed) } @@ -140,7 +133,10 @@ func TestMessageWrap(t *testing.T) { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } params.TTL = 1 params.WorkTime = 12 params.PoW = target @@ -155,7 +151,10 @@ func TestMessageWrap(t *testing.T) { } // set PoW target too high, expect error - msg2 := NewSentMessage(params) + msg2, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } params.TTL = 1000000 params.WorkTime = 1 params.PoW = 10000000.0 @@ -175,14 +174,15 @@ func TestMessageSeal(t *testing.T) { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } params.TTL = 1 aesnonce := make([]byte, 12) - salt := make([]byte, 12) mrand.Read(aesnonce) - mrand.Read(salt) - env := NewEnvelope(params.TTL, params.Topic, salt, aesnonce, msg) + env := NewEnvelope(params.TTL, params.Topic, aesnonce, msg) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) } @@ -236,11 +236,12 @@ func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { } text := make([]byte, 0, 512) - steg := make([]byte, 0, 512) text = append(text, params.Payload...) - steg = append(steg, params.Padding...) - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -252,10 +253,6 @@ func singleEnvelopeOpenTest(t *testing.T, symmetric bool) { t.Fatalf("failed to open with seed %d.", seed) } - padsz := len(decrypted.Padding) - if !bytes.Equal(steg[:padsz], decrypted.Padding) { - t.Fatalf("failed with seed %d: compare padding.", seed) - } if !bytes.Equal(text, decrypted.Payload) { t.Fatalf("failed with seed %d: compare payload.", seed) } @@ -291,21 +288,38 @@ func TestEncryptWithZeroKey(t *testing.T) { if err != nil { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - - msg := NewSentMessage(params) - + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } params.KeySym = make([]byte, aesKeyLength) _, err = msg.Wrap(params) if err == nil { t.Fatalf("wrapped with zero key, seed: %d.", seed) } + params, err = generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } params.KeySym = make([]byte, 0) _, err = msg.Wrap(params) if err == nil { t.Fatalf("wrapped with empty key, seed: %d.", seed) } + params, err = generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) + } + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } params.KeySym = nil _, err = msg.Wrap(params) if err == nil { @@ -320,7 +334,10 @@ func TestRlpEncode(t *testing.T) { if err != nil { t.Fatalf("failed generateMessageParams with seed %d: %s.", seed, err) } - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("wrapped with zero key, seed: %d.", seed) @@ -344,3 +361,60 @@ func TestRlpEncode(t *testing.T) { t.Fatalf("Hashes are not equal: %x vs. %x", he, hd) } } + +func singlePaddingTest(t *testing.T, padSize int) { + params, err := generateMessageParams() + if err != nil { + t.Fatalf("failed generateMessageParams with seed %d and sz=%d: %s.", seed, padSize, err) + } + params.Padding = make([]byte, padSize) + params.PoW = 0.0000000001 + pad := make([]byte, padSize) + _, err = mrand.Read(pad) + if err != nil { + t.Fatalf("padding is not generated (seed %d): %s", seed, err) + } + n := copy(params.Padding, pad) + if n != padSize { + t.Fatalf("padding is not copied (seed %d): %s", seed, err) + } + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } + env, err := msg.Wrap(params) + if err != nil { + t.Fatalf("failed to wrap, seed: %d and sz=%d.", seed, padSize) + } + f := Filter{KeySym: params.KeySym} + decrypted := env.Open(&f) + if decrypted == nil { + t.Fatalf("failed to open, seed and sz=%d: %d.", seed, padSize) + } + if !bytes.Equal(pad, decrypted.Padding) { + t.Fatalf("padding is not retireved as expected with seed %d and sz=%d:\n[%x]\n[%x].", seed, padSize, pad, decrypted.Padding) + } +} + +func TestPadding(t *testing.T) { + InitSingleTest() + + for i := 1; i < 260; i++ { + singlePaddingTest(t, i) + } + + lim := 256 * 256 + for i := lim - 5; i < lim+2; i++ { + singlePaddingTest(t, i) + } + + for i := 0; i < 256; i++ { + n := mrand.Intn(256*254) + 256 + singlePaddingTest(t, n) + } + + for i := 0; i < 256; i++ { + n := mrand.Intn(256*1024) + 256*256 + singlePaddingTest(t, n) + } +} diff --git a/whisper/whisperv5/peer.go b/whisper/whisperv5/peer.go index 184c4ebf8..179c93179 100644 --- a/whisper/whisperv5/peer.go +++ b/whisper/whisperv5/peer.go @@ -149,23 +149,22 @@ func (peer *Peer) expire() { // broadcast iterates over the collection of envelopes and transmits yet unknown // ones over the network. func (p *Peer) broadcast() error { - // Fetch the envelopes and collect the unknown ones + var cnt int envelopes := p.host.Envelopes() - transmit := make([]*Envelope, 0, len(envelopes)) for _, envelope := range envelopes { if !p.marked(envelope) { - transmit = append(transmit, envelope) - p.mark(envelope) + err := p2p.Send(p.ws, messagesCode, envelope) + if err != nil { + return err + } else { + p.mark(envelope) + cnt++ + } } } - if len(transmit) == 0 { - return nil - } - // Transmit the unknown batch (potentially empty) - if err := p2p.Send(p.ws, messagesCode, transmit); err != nil { - return err + if cnt > 0 { + log.Trace("broadcast", "num. messages", cnt) } - log.Trace("broadcast", "num. messages", len(transmit)) return nil } diff --git a/whisper/whisperv5/peer_test.go b/whisper/whisperv5/peer_test.go index a79b6ad14..d3cd63b0b 100644 --- a/whisper/whisperv5/peer_test.go +++ b/whisper/whisperv5/peer_test.go @@ -265,7 +265,10 @@ func sendMsg(t *testing.T, expected bool, id int) { opt.Payload = opt.Payload[1:] } - msg := NewSentMessage(&opt) + msg, err := NewSentMessage(&opt) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } envelope, err := msg.Wrap(&opt) if err != nil { t.Fatalf("failed to seal message: %s", err) @@ -286,7 +289,10 @@ func TestPeerBasic(t *testing.T) { } params.PoW = 0.001 - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d.", seed) diff --git a/whisper/whisperv5/whisper.go b/whisper/whisperv5/whisper.go index c4d5d04a7..f2aad08ef 100644 --- a/whisper/whisperv5/whisper.go +++ b/whisper/whisperv5/whisper.go @@ -262,24 +262,14 @@ func (w *Whisper) GetPrivateKey(id string) (*ecdsa.PrivateKey, error) { // GenerateSymKey generates a random symmetric key and stores it under id, // which is then returned. Will be used in the future for session key exchange. func (w *Whisper) GenerateSymKey() (string, error) { - const size = aesKeyLength * 2 - buf := make([]byte, size) - _, err := crand.Read(buf) + key := make([]byte, aesKeyLength) + _, err := crand.Read(key) if err != nil { return "", err - } else if !validateSymmetricKey(buf) { + } else if !validateSymmetricKey(key) { return "", fmt.Errorf("error in GenerateSymKey: crypto/rand failed to generate random data") } - key := buf[:aesKeyLength] - salt := buf[aesKeyLength:] - derived, err := DeriveOneTimeKey(key, salt, EnvelopeVersion) - if err != nil { - return "", err - } else if !validateSymmetricKey(derived) { - return "", fmt.Errorf("failed to derive valid key") - } - id, err := GenerateRandomID() if err != nil { return "", fmt.Errorf("failed to generate ID: %s", err) @@ -291,7 +281,7 @@ func (w *Whisper) GenerateSymKey() (string, error) { if w.symKeys[id] != nil { return "", fmt.Errorf("failed to generate unique ID") } - w.symKeys[id] = derived + w.symKeys[id] = key return id, nil } @@ -395,6 +385,9 @@ func (w *Whisper) Unsubscribe(id string) error { // network in the coming cycles. func (w *Whisper) Send(envelope *Envelope) error { ok, err := w.add(envelope) + if err != nil { + return err + } if !ok { return fmt.Errorf("failed to add envelope") } @@ -469,21 +462,18 @@ func (wh *Whisper) runMessageLoop(p *Peer, rw p2p.MsgReadWriter) error { log.Warn("unxepected status message received", "peer", p.peer.ID()) case messagesCode: // decode the contained envelopes - var envelopes []*Envelope - if err := packet.Decode(&envelopes); err != nil { + var envelope Envelope + if err := packet.Decode(&envelope); err != nil { log.Warn("failed to decode envelope, peer will be disconnected", "peer", p.peer.ID(), "err", err) return errors.New("invalid envelope") } - // inject all envelopes into the internal pool - for _, envelope := range envelopes { - cached, err := wh.add(envelope) - if err != nil { - log.Warn("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err) - return errors.New("invalid envelope") - } - if cached { - p.mark(envelope) - } + cached, err := wh.add(&envelope) + if err != nil { + log.Warn("bad envelope received, peer will be disconnected", "peer", p.peer.ID(), "err", err) + return errors.New("invalid envelope") + } + if cached { + p.mark(&envelope) } case p2pCode: // peer-to-peer message, sent directly to peer bypassing PoW checks, etc. @@ -550,14 +540,11 @@ func (wh *Whisper) add(envelope *Envelope) (bool, error) { return false, fmt.Errorf("oversized version [%x]", envelope.Hash()) } - if len(envelope.AESNonce) > AESNonceMaxLength { - // the standard AES GSM nonce size is 12, - // but const gcmStandardNonceSize cannot be accessed directly - return false, fmt.Errorf("oversized AESNonce [%x]", envelope.Hash()) - } - - if len(envelope.Salt) > saltLength { - return false, fmt.Errorf("oversized salt [%x]", envelope.Hash()) + aesNonceSize := len(envelope.AESNonce) + if aesNonceSize != 0 && aesNonceSize != AESNonceLength { + // the standard AES GCM nonce size is 12 bytes, + // but constant gcmStandardNonceSize cannot be accessed (not exported) + return false, fmt.Errorf("wrong size of AESNonce: %d bytes [env: %x]", aesNonceSize, envelope.Hash()) } if envelope.PoW() < wh.minPoW { diff --git a/whisper/whisperv5/whisper_test.go b/whisper/whisperv5/whisper_test.go index d5668259e..225728c42 100644 --- a/whisper/whisperv5/whisper_test.go +++ b/whisper/whisperv5/whisper_test.go @@ -455,7 +455,10 @@ func TestExpiry(t *testing.T) { } params.TTL = 1 - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -515,7 +518,10 @@ func TestCustomization(t *testing.T) { params.Topic = BytesToTopic(f.Topics[2]) params.PoW = smallPoW params.TTL = 3600 * 24 // one day - msg := NewSentMessage(params) + msg, err := NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err := msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) @@ -533,7 +539,10 @@ func TestCustomization(t *testing.T) { } params.TTL++ - msg = NewSentMessage(params) + msg, err = NewSentMessage(params) + if err != nil { + t.Fatalf("failed to create new message with seed %d: %s.", seed, err) + } env, err = msg.Wrap(params) if err != nil { t.Fatalf("failed Wrap with seed %d: %s.", seed, err) |