Browse Source

p2p/conn: simplify secret connection handshake malleability fix with merlin (#4185)

* p2p/conn: simplify secret connection handshake malleability fix with merlin

Introduces new dependencies on github.com/gtank/merlin and sha3 as a cryptographic primitive

This also only uses the transcript hash as a MAC.

* p2p/conn: avoid string to byte conversion

https://github.com/uber-go/guide/blob/master/style.md#avoid-string-to-byte-conversion
pull/4200/head
Zaki Manian 5 years ago
committed by Tess Rinearson
parent
commit
af3afc2817
5 changed files with 49 additions and 48 deletions
  1. +11
    -9
      CHANGELOG_PENDING.md
  2. +1
    -0
      go.mod
  3. +4
    -0
      go.sum
  4. +31
    -33
      p2p/conn/secret_connection.go
  5. +2
    -6
      p2p/conn/secret_connection_test.go

+ 11
- 9
CHANGELOG_PENDING.md View File

@ -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": ...}` `{"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 method=/subscribe
id=0 result=... id=0 result=...
@ -70,12 +70,12 @@ program](https://hackerone.com/tendermint).
- Blockchain Protocol - Blockchain Protocol
- [abci] \#2521 Remove `TotalTxs` and `NumTxs` from `Header` - [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 - [types] \#1648 Change `Commit` to consist of just signatures
- P2P Protocol - 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. - [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/` - 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` - [rpc] \#3188 Added `block_size` to `BlockMeta` this is reflected in `/blockchain`
- [types] \#2521 Add `NumTxs` to `BlockMeta` and `EventDataNewBlockHeader` - [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: ### 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) - [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)

+ 1
- 0
go.mod View File

@ -14,6 +14,7 @@ require (
github.com/golang/protobuf v1.3.2 github.com/golang/protobuf v1.3.2
github.com/google/gofuzz v1.0.0 // indirect github.com/google/gofuzz v1.0.0 // indirect
github.com/gorilla/websocket v1.4.1 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/inconshreveable/mousetrap v1.0.0 // indirect
github.com/libp2p/go-buffer-pool v0.0.2 github.com/libp2p/go-buffer-pool v0.0.2
github.com/magiconair/properties v1.8.1 github.com/magiconair/properties v1.8.1


+ 4
- 0
go.sum View File

@ -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-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/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/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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 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= 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/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 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 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= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=


+ 31
- 33
p2p/conn/secret_connection.go View File

@ -13,6 +13,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/gtank/merlin"
pool "github.com/libp2p/go-buffer-pool" pool "github.com/libp2p/go-buffer-pool"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
@ -26,16 +27,25 @@ import (
) )
// 4 + 1024 == 1028 total frame size // 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 ( var (
ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer") ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer")
ErrSharedSecretIsZero = errors.New("shared secret is all zeroes") 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. // SecretConnection implements net.Conn.
@ -78,8 +88,6 @@ type SecretConnection struct {
// See docs/sts-final.pdf for more information. // See docs/sts-final.pdf for more information.
func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) { func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*SecretConnection, error) {
var ( var (
hash = sha256.New
hdkfState = new([32]byte)
locPubKey = locPrivKey.PubKey() locPubKey = locPrivKey.PubKey()
) )
@ -97,17 +105,10 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*
// Sort by lexical order. // Sort by lexical order.
loEphPub, hiEphPub := sort32(locEphPub, remEphPub) 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 // Check if the local ephemeral public key was the least, lexicographically
// sorted. // sorted.
@ -118,19 +119,19 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*
if err != nil { if err != nil {
return nil, err 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 // 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 // on the transcript state (which itself also uses HKDF-SHA2 to derive a key
// from the dhSecret). // 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[:]) sendAead, err := chacha20poly1305.New(sendSecret[:])
if err != nil { if err != nil {
@ -151,7 +152,7 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*
} }
// Sign the challenge bytes for authentication. // Sign the challenge bytes for authentication.
locSignature := signChallenge(challenge, locPrivKey)
locSignature := signChallenge(&challenge, locPrivKey)
// Share (in secret) each other's pubkey & challenge signature // Share (in secret) each other's pubkey & challenge signature
authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature) authSigMsg, err := shareAuthSignature(sc, locPubKey, locSignature)
@ -324,12 +325,12 @@ func shareEphPubKey(conn io.ReadWriter, locEphPub *[32]byte) (remEphPub *[32]byt
return &_remEphPub, nil return &_remEphPub, nil
} }
func deriveSecretAndChallenge(
func deriveSecrets(
dhSecret *[32]byte, dhSecret *[32]byte,
locIsLeast bool, locIsLeast bool,
) (recvSecret, sendSecret *[aeadKeySize]byte, challenge *[32]byte) {
) (recvSecret, sendSecret *[aeadKeySize]byte) {
hash := sha256.New 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 // get enough data for 2 aead keys, and a 32 byte challenge
res := new([2*aeadKeySize + 32]byte) res := new([2*aeadKeySize + 32]byte)
_, err := io.ReadFull(hkdf, res[:]) _, err := io.ReadFull(hkdf, res[:])
@ -337,11 +338,8 @@ func deriveSecretAndChallenge(
panic(err) panic(err)
} }
challenge = new([32]byte)
recvSecret = new([aeadKeySize]byte) recvSecret = new([aeadKeySize]byte)
sendSecret = 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 0 through aeadKeySize - 1 are one aead key.
// bytes aeadKeySize through 2*aeadKeySize -1 are another aead key. // bytes aeadKeySize through 2*aeadKeySize -1 are another aead key.


+ 2
- 6
p2p/conn/secret_connection_test.go View File

@ -312,13 +312,10 @@ func TestDeriveSecretsAndChallengeGolden(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
expectedSendSecret, err := hex.DecodeString(params[3]) expectedSendSecret, err := hex.DecodeString(params[3])
require.Nil(t, err) 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, expectedRecvSecret, (*recvSecret)[:], "Recv Secrets aren't equal")
require.Equal(t, expectedSendSecret, (*sendSecret)[:], "Send 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)[:]) + "," data += hex.EncodeToString((*randSecret)[:]) + ","
locIsLeast := cmn.RandBool() locIsLeast := cmn.RandBool()
data += strconv.FormatBool(locIsLeast) + "," data += strconv.FormatBool(locIsLeast) + ","
recvSecret, sendSecret, challenge := deriveSecretAndChallenge(randSecret, locIsLeast)
recvSecret, sendSecret := deriveSecrets(randSecret, locIsLeast)
data += hex.EncodeToString((*recvSecret)[:]) + "," data += hex.EncodeToString((*recvSecret)[:]) + ","
data += hex.EncodeToString((*sendSecret)[:]) + "," data += hex.EncodeToString((*sendSecret)[:]) + ","
data += hex.EncodeToString((*challenge)[:]) + "\n"
} }
return data return data
} }


Loading…
Cancel
Save