diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index d9fe0c751..837b87a90 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -44,9 +44,9 @@ program](https://hackerone.com/tendermint). } } ``` - - [rpc][\#4141](https://github.com/tendermint/tendermint/pull/4141) Remove `#event` suffix from the ID in event responses. + - [rpc] [\#4141](https://github.com/tendermint/tendermint/pull/4141) Remove `#event` suffix from the ID in event responses. `{"jsonrpc": "2.0", "id": 0, "result": ...}` - - [rpc][\#4141](https://github.com/tendermint/tendermint/pull/4141) Switch to integer IDs instead of `json-client-XYZ` + - [rpc] [\#4141](https://github.com/tendermint/tendermint/pull/4141) Switch to integer IDs instead of `json-client-XYZ` ``` id=0 method=/subscribe id=0 result=... @@ -70,12 +70,12 @@ program](https://hackerone.com/tendermint). - Blockchain Protocol - [abci] \#2521 Remove `TotalTxs` and `NumTxs` from `Header` - - [types][\#4151](https://github.com/tendermint/tendermint/pull/4151) Enforce ordering of votes in DuplicateVoteEvidence to be lexicographically sorted on BlockID + - [types] [\#4151](https://github.com/tendermint/tendermint/pull/4151) Enforce ordering of votes in DuplicateVoteEvidence to be lexicographically sorted on BlockID - [types] \#1648 Change `Commit` to consist of just signatures - P2P Protocol - - [p2p][\3668](https://github.com/tendermint/tendermint/pull/3668) Make `SecretConnection` non-malleable + - [p2p] [\#3668](https://github.com/tendermint/tendermint/pull/3668) Make `SecretConnection` non-malleable - [proto] [\#3986](https://github.com/tendermint/tendermint/pull/3986) Prefix protobuf types to avoid name conflicts. - ABCI becomes `tendermint.abci.types` with the new API endpoint `/tendermint.abci.types.ABCIApplication/` @@ -90,12 +90,14 @@ program](https://hackerone.com/tendermint). - [rpc] \#3188 Added `block_size` to `BlockMeta` this is reflected in `/blockchain` - [types] \#2521 Add `NumTxs` to `BlockMeta` and `EventDataNewBlockHeader` -- [docs][\#4111](https://github.com/tendermint/tendermint/issues/4111) Replaced dead whitepaper link in README.md +- [docs] [\#4111](https://github.com/tendermint/tendermint/issues/4111) Replaced dead whitepaper link in README.md +- [p2p] [\#4185](https://github.com/tendermint/tendermint/pull/4185) Simplify `SecretConnection` handshake with merlin ### BUG FIXES: -- [rpc/lib][\#4051](https://github.com/tendermint/tendermint/pull/4131) Fix RPC client, which was previously resolving https protocol to http (@yenkhoon) -- [rpc][\#4141](https://github.com/tendermint/tendermint/pull/4141) JSONRPCClient: validate that Response.ID matches Request.ID -- [rpc][\#4141](https://github.com/tendermint/tendermint/pull/4141) WSClient: check for unsolicited responses -- [types][\4164](https://github.com/tendermint/tendermint/pull/4164) Prevent temporary power overflows on validator updates +- [rpc/lib] [\#4051](https://github.com/tendermint/tendermint/pull/4131) Fix RPC client, which was previously resolving https protocol to http (@yenkhoon) +- [rpc] [\#4141](https://github.com/tendermint/tendermint/pull/4141) JSONRPCClient: validate that Response.ID matches Request.ID +- [rpc] [\#4141](https://github.com/tendermint/tendermint/pull/4141) WSClient: check for unsolicited responses +- [types] [\4164](https://github.com/tendermint/tendermint/pull/4164) Prevent temporary power overflows on validator updates - [cs] \#4069 Don't panic when block meta is not found in store (@gregzaitsev) +- [p2p] \#4140 `SecretConnection`: use the transcript solely for authentication (i.e. MAC) diff --git a/go.mod b/go.mod index 78897e24f..d0875f578 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/golang/protobuf v1.3.2 github.com/google/gofuzz v1.0.0 // indirect github.com/gorilla/websocket v1.4.1 + github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/libp2p/go-buffer-pool v0.0.2 github.com/magiconair/properties v1.8.1 diff --git a/go.sum b/go.sum index e69e42720..bf36c3206 100644 --- a/go.sum +++ b/go.sum @@ -88,6 +88,8 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -118,6 +120,8 @@ github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzR github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= diff --git a/p2p/conn/secret_connection.go b/p2p/conn/secret_connection.go index 5e5dfd1bb..e26fd6943 100644 --- a/p2p/conn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -13,6 +13,7 @@ import ( "sync" "time" + "github.com/gtank/merlin" pool "github.com/libp2p/go-buffer-pool" "github.com/pkg/errors" "golang.org/x/crypto/chacha20poly1305" @@ -26,16 +27,25 @@ import ( ) // 4 + 1024 == 1028 total frame size -const dataLenSize = 4 -const dataMaxSize = 1024 -const totalFrameSize = dataMaxSize + dataLenSize -const aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag -const aeadKeySize = chacha20poly1305.KeySize -const aeadNonceSize = chacha20poly1305.NonceSize +const ( + dataLenSize = 4 + dataMaxSize = 1024 + totalFrameSize = dataMaxSize + dataLenSize + aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag + aeadKeySize = chacha20poly1305.KeySize + aeadNonceSize = chacha20poly1305.NonceSize +) var ( ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer") ErrSharedSecretIsZero = errors.New("shared secret is all zeroes") + + labelEphemeralLowerPublicKey = []byte("EPHEMERAL_LOWER_PUBLIC_KEY") + labelEphemeralUpperPublicKey = []byte("EPHEMERAL_UPPER_PUBLIC_KEY") + labelDHSecret = []byte("DH_SECRET") + labelSecretConnectionMac = []byte("SECRET_CONNECTION_MAC") + + secretConnKeyAndChallengeGen = []byte("TENDERMINT_SECRET_CONNECTION_KEY_AND_CHALLENGE_GEN") ) // SecretConnection implements net.Conn. @@ -78,8 +88,6 @@ type SecretConnection struct { // See docs/sts-final.pdf for more information. func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) { var ( - hash = sha256.New - hdkfState = new([32]byte) locPubKey = locPrivKey.PubKey() ) @@ -97,17 +105,10 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (* // Sort by lexical order. loEphPub, hiEphPub := sort32(locEphPub, remEphPub) - hkdfInit := hkdf.New(hash, []byte("INIT_HANDSHAKE"), nil, []byte("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")) - if _, err := io.ReadFull(hkdfInit, hdkfState[:]); err != nil { - return nil, err - } - - hkdfLoEphPub := hkdf.New(hash, loEphPub[:], hdkfState[:], []byte("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")) - if _, err := io.ReadFull(hkdfLoEphPub, hdkfState[:]); err != nil { - return nil, err - } + transcript := merlin.NewTranscript("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH") - hkdfHiEphPub := hkdf.New(hash, hiEphPub[:], hdkfState[:], []byte("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")) + transcript.AppendMessage(labelEphemeralLowerPublicKey, loEphPub[:]) + transcript.AppendMessage(labelEphemeralUpperPublicKey, hiEphPub[:]) // Check if the local ephemeral public key was the least, lexicographically // sorted. @@ -118,19 +119,19 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (* if err != nil { return nil, err } - if _, err := io.ReadFull(hkdfHiEphPub, hdkfState[:]); err != nil { - return nil, err - } - hkdfSecret := hkdf.New(hash, dhSecret[:], hdkfState[:], []byte("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")) - if _, err := io.ReadFull(hkdfSecret, hdkfState[:]); err != nil { - return nil, err - } + transcript.AppendMessage(labelDHSecret, dhSecret[:]) // Generate the secret used for receiving, sending, challenge via HKDF-SHA2 // on the transcript state (which itself also uses HKDF-SHA2 to derive a key // from the dhSecret). - recvSecret, sendSecret, challenge := deriveSecretAndChallenge(hdkfState, locIsLeast) + recvSecret, sendSecret := deriveSecrets(dhSecret, locIsLeast) + + const challengeSize = 32 + var challenge [challengeSize]byte + challengeSlice := transcript.ExtractBytes(labelSecretConnectionMac, challengeSize) + + copy(challenge[:], challengeSlice[0:challengeSize]) sendAead, err := chacha20poly1305.New(sendSecret[:]) if err != nil { @@ -151,7 +152,7 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (* } // Sign the challenge bytes for authentication. - locSignature := signChallenge(challenge, locPrivKey) + locSignature := signChallenge(&challenge, locPrivKey) // Share (in secret) each other's pubkey & challenge signature authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature) @@ -324,12 +325,12 @@ func shareEphPubKey(conn io.ReadWriter, locEphPub *[32]byte) (remEphPub *[32]byt return &_remEphPub, nil } -func deriveSecretAndChallenge( +func deriveSecrets( dhSecret *[32]byte, locIsLeast bool, -) (recvSecret, sendSecret *[aeadKeySize]byte, challenge *[32]byte) { +) (recvSecret, sendSecret *[aeadKeySize]byte) { hash := sha256.New - hkdf := hkdf.New(hash, dhSecret[:], nil, []byte("TENDERMINT_SECRET_CONNECTION_KEY_AND_CHALLENGE_GEN")) + hkdf := hkdf.New(hash, dhSecret[:], nil, secretConnKeyAndChallengeGen) // get enough data for 2 aead keys, and a 32 byte challenge res := new([2*aeadKeySize + 32]byte) _, err := io.ReadFull(hkdf, res[:]) @@ -337,11 +338,8 @@ func deriveSecretAndChallenge( panic(err) } - challenge = new([32]byte) recvSecret = new([aeadKeySize]byte) sendSecret = new([aeadKeySize]byte) - // Use the last 32 bytes as the challenge - copy(challenge[:], res[2*aeadKeySize:2*aeadKeySize+32]) // bytes 0 through aeadKeySize - 1 are one aead key. // bytes aeadKeySize through 2*aeadKeySize -1 are another aead key. diff --git a/p2p/conn/secret_connection_test.go b/p2p/conn/secret_connection_test.go index b9b7b482e..8faa895b0 100644 --- a/p2p/conn/secret_connection_test.go +++ b/p2p/conn/secret_connection_test.go @@ -312,13 +312,10 @@ func TestDeriveSecretsAndChallengeGolden(t *testing.T) { require.Nil(t, err) expectedSendSecret, err := hex.DecodeString(params[3]) require.Nil(t, err) - expectedChallenge, err := hex.DecodeString(params[4]) - require.Nil(t, err) - recvSecret, sendSecret, challenge := deriveSecretAndChallenge(randSecret, locIsLeast) + recvSecret, sendSecret := deriveSecrets(randSecret, locIsLeast) require.Equal(t, expectedRecvSecret, (*recvSecret)[:], "Recv Secrets aren't equal") require.Equal(t, expectedSendSecret, (*sendSecret)[:], "Send Secrets aren't equal") - require.Equal(t, expectedChallenge, (*challenge)[:], "challenges aren't equal") } } @@ -379,10 +376,9 @@ func createGoldenTestVectors(t *testing.T) string { data += hex.EncodeToString((*randSecret)[:]) + "," locIsLeast := cmn.RandBool() data += strconv.FormatBool(locIsLeast) + "," - recvSecret, sendSecret, challenge := deriveSecretAndChallenge(randSecret, locIsLeast) + recvSecret, sendSecret := deriveSecrets(randSecret, locIsLeast) data += hex.EncodeToString((*recvSecret)[:]) + "," data += hex.EncodeToString((*sendSecret)[:]) + "," - data += hex.EncodeToString((*challenge)[:]) + "\n" } return data }