Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

secret connection check all zeroes #3347

Merged
merged 5 commits into from
Feb 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG_PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ Special thanks to external contributors on this release:
### IMPROVEMENTS:

### BUG FIXES:
- [libs/pubsub] \#951, \#1880 use non-blocking send when dispatching messages [ADR-33](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-033-pubsub.md)

- [p2p/conn] \#3347 Reject all-zero shared secrets in the Diffie-Hellman step of secret-connection
- [libs/pubsub] \#951, \#1880 use non-blocking send when dispatching messages [ADR-33](https://github.com/tendermint/tendermint/blob/develop/docs/architecture/adr-033-pubsub.md)
28 changes: 24 additions & 4 deletions p2p/conn/secret_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ const aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag
const aeadKeySize = chacha20poly1305.KeySize
const aeadNonceSize = chacha20poly1305.NonceSize

var ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer")
var (
ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer")
ErrSharedSecretIsZero = errors.New("shared secret is all zeroes")
)

// SecretConnection implements net.Conn.
// It is an implementation of the STS protocol.
Expand Down Expand Up @@ -90,7 +93,10 @@ func MakeSecretConnection(conn io.ReadWriteCloser, locPrivKey crypto.PrivKey) (*
locIsLeast := bytes.Equal(locEphPub[:], loEphPub[:])

// Compute common diffie hellman secret using X25519.
dhSecret := computeDHSecret(remEphPub, locEphPriv)
dhSecret, err := computeDHSecret(remEphPub, locEphPriv)
if err != nil {
return nil, err
}

// generate the secret used for receiving, sending, challenge via hkdf-sha2 on dhSecret
recvSecret, sendSecret, challenge := deriveSecretAndChallenge(dhSecret, locIsLeast)
Expand Down Expand Up @@ -230,9 +236,12 @@ func (sc *SecretConnection) SetWriteDeadline(t time.Time) error {

func genEphKeys() (ephPub, ephPriv *[32]byte) {
var err error
// TODO: Probably not a problem but ask Tony: different from the rust implementation (uses x25519-dalek),
// we do not "clamp" the private key scalar:
// see: https://github.com/dalek-cryptography/x25519-dalek/blob/34676d336049df2bba763cc076a75e47ae1f170f/src/x25519.rs#L56-L74
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarcieri: box.GenerateKey does not "clamp" the private key. Does this pose a problem? Or should we just ignore this as we will switch to noise soon anyways?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curve25519 canonical scalars are always unclamped. Clamping is performed immediately prior to scalar multiplication (which avoids your exact worry about canonical scalars being "pre-clamped" or not):

https://github.com/golang/crypto/blob/master/curve25519/curve25519.go#L789

ephPub, ephPriv, err = box.GenerateKey(crand.Reader)
if err != nil {
panic("Could not generate ephemeral keypairs")
panic("Could not generate ephemeral key-pair")
}
return
}
Expand Down Expand Up @@ -349,9 +358,20 @@ func deriveSecretAndChallenge(dhSecret *[32]byte, locIsLeast bool) (recvSecret,
return
}

func computeDHSecret(remPubKey, locPrivKey *[32]byte) (shrKey *[32]byte) {
// computeDHSecret computes a shared secret Diffie-Hellman secret
// from a the own local private key and the others public key.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// from a the own local private key and the others public key.
// from our own local private key and the other's public key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ouch 🙈 Will submit a mini-followup PR

//
// It returns an error if the computed shared secret is all zeroes.
func computeDHSecret(remPubKey, locPrivKey *[32]byte) (shrKey *[32]byte, err error) {
shrKey = new([32]byte)
curve25519.ScalarMult(shrKey, locPrivKey, remPubKey)

// reject if the returned shared secret is all zeroes
// related to: https://github.com/tendermint/tendermint/issues/3010
zero := new([32]byte)
if subtle.ConstantTimeCompare(shrKey[:], zero[:]) == 1 {
return nil, ErrSharedSecretIsZero
}
return
}

Expand Down
17 changes: 17 additions & 0 deletions p2p/conn/secret_connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,12 @@ func TestSecretConnectionHandshake(t *testing.T) {
}
}

// Test that shareEphPubKey rejects lower order public keys based on an
// (incomplete) blacklist.
func TestShareLowOrderPubkey(t *testing.T) {
var fooConn, barConn = makeKVStoreConnPair()
defer fooConn.Close()
defer barConn.Close()
locEphPub, _ := genEphKeys()

// all blacklisted low order points:
Expand All @@ -126,6 +130,19 @@ func TestShareLowOrderPubkey(t *testing.T) {
}
}

// Test that additionally that the Diffie-Hellman shared secret is non-zero.
// The shared secret would be zero for lower order pub-keys (but tested against the blacklist only).
func TestComputeDHFailsOnLowOrder(t *testing.T) {
_, locPrivKey := genEphKeys()
for _, remLowOrderPubKey := range blacklist {
shared, err := computeDHSecret(&remLowOrderPubKey, locPrivKey)
assert.Error(t, err)

assert.Equal(t, err, ErrSharedSecretIsZero)
assert.Empty(t, shared)
}
}

func TestConcurrentWrite(t *testing.T) {
fooSecConn, barSecConn := makeSecretConnPair(t)
fooWriteText := cmn.RandStr(dataMaxSize)
Expand Down