mirror of
https://github.com/tendermint/tendermint.git
synced 2026-08-01 21:06:11 +00:00
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
This commit is contained in:
committed by
Tess Rinearson
parent
ad715fe966
commit
af3afc2817
+11
-9
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
transcript := merlin.NewTranscript("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH")
|
||||
|
||||
hkdfLoEphPub := hkdf.New(hash, loEphPub[:], hdkfState[:], []byte("TENDERMINT_SECRET_CONNECTION_TRANSCRIPT_HASH"))
|
||||
if _, err := io.ReadFull(hkdfLoEphPub, hdkfState[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user