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:
Zaki Manian
2019-11-26 12:40:06 +01:00
committed by Tess Rinearson
parent ad715fe966
commit af3afc2817
5 changed files with 49 additions and 48 deletions
+31 -33
View File
@@ -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.
+2 -6
View File
@@ -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
}