mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-19 06:31:57 +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
@@ -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