Rod Hynes 7 лет назад
Родитель
Сommit
baf4d73d5d
3 измененных файлов с 95 добавлено и 1 удалено
  1. 62 0
      psiphon/common/crypto/ssh/handshake.go
  2. 17 1
      psiphon/common/utils.go
  3. 16 0
      psiphon/common/utils_test.go

+ 62 - 0
psiphon/common/crypto/ssh/handshake.go

@@ -12,6 +12,8 @@ import (
 	"log"
 	"net"
 	"sync"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
 )
 
 // debugHandshake, if set, prints messages sent and received.  Key
@@ -457,6 +459,66 @@ func (t *handshakeTransport) sendKexInit() error {
 	} else {
 		msg.ServerHostKeyAlgos = t.hostKeyAlgorithms
 	}
+
+	// PSIPHON
+	// =======
+	//
+	// Randomize KEX. The offered algorithms are shuffled and
+	// truncated (longer lists are selected with higher
+	// probability).
+	//
+	// As the client and server have the same set of algorithms,
+	// almost any combination is expected to be workable.
+	//
+	// The compression algorithm is not actually supported, but
+	// the server will not negotiate it.
+	//
+	// common.MakeSecureRandomPerm and common.FlipCoin are
+	// unlikely to fail; if they do, proceed with the standard
+	// ordering, full lists, and standard compressions.
+	//
+	// The "t.remoteAddr != nil" condition should be true only
+	// for clients.
+	//
+	if t.remoteAddr != nil {
+
+		transform := func(list []string) []string {
+
+			newList := make([]string, len(list))
+			perm, err := common.MakeSecureRandomPerm(len(list))
+			if err == nil {
+				for i, j := range perm {
+					newList[j] = list[i]
+				}
+			}
+
+			cut := len(newList)
+			for ; cut > 1; cut-- {
+				if !common.FlipCoin() {
+					break
+				}
+			}
+
+			return newList[:cut]
+		}
+
+		msg.KexAlgos = transform(t.config.KeyExchanges)
+		ciphers := transform(t.config.Ciphers)
+		msg.CiphersClientServer = ciphers
+		msg.CiphersServerClient = ciphers
+		MACs := transform(t.config.MACs)
+		msg.MACsClientServer = MACs
+		msg.MACsServerClient = MACs
+
+		// Offer "zlib@openssh.com", which is offered by OpenSSH.
+		// Since server only supports "none", must always offer "none"
+		if common.FlipCoin() {
+			compressions := []string{"none", "zlib@openssh.com"}
+			msg.CompressionClientServer = compressions
+			msg.CompressionServerClient = compressions
+		}
+	}
+
 	packet := Marshal(msg)
 
 	// writePacket destroys the contents, so save a copy.

+ 17 - 1
psiphon/common/utils.go

@@ -93,7 +93,7 @@ func GetStringSlice(value interface{}) ([]string, bool) {
 // returns true or false.
 //
 // If the underlying random number generator fails,
-// FlipCoin still returns a result.
+// FlipCoin still returns false.
 func FlipCoin() bool {
 	randomInt, _ := MakeSecureRandomInt(2)
 	return randomInt == 1
@@ -138,6 +138,22 @@ func MakeSecureRandomInt64(max int64) (int64, error) {
 	return randomInt.Int64(), nil
 }
 
+// MakeSecureRandomPerm returns a random permutation of [0,max).
+func MakeSecureRandomPerm(max int) ([]int, error) {
+	// Based on math/rand.Rand.Perm:
+	// https://github.com/golang/go/blob/release-branch.go1.9/src/math/rand/rand.go#L189
+	perm := make([]int, max)
+	for i := 1; i < max; i++ {
+		j, err := MakeSecureRandomInt(i + 1)
+		if err != nil {
+			return nil, ContextError(err)
+		}
+		perm[i] = perm[j]
+		perm[j] = i
+	}
+	return perm, nil
+}
+
 // MakeSecureRandomBytes is a helper function that wraps
 // crypto/rand.Read.
 func MakeSecureRandomBytes(length int) ([]byte, error) {

+ 16 - 0
psiphon/common/utils_test.go

@@ -55,6 +55,22 @@ func TestGetStringSlice(t *testing.T) {
 	}
 }
 
+func TestMakeSecureRandomPerm(t *testing.T) {
+	for i := 0; i < 1000; i++ {
+		perm, err := MakeSecureRandomPerm(i)
+		if err != nil {
+			t.Errorf("MakeSecureRandomPerm failed: %s", err)
+		}
+		sum := 0
+		for j := 0; j < i; j++ {
+			sum += perm[j]
+		}
+		if sum != (i*(i+1))/2 {
+			t.Error("unexpected permutation")
+		}
+	}
+}
+
 func TestMakeRandomPeriod(t *testing.T) {
 	min := 1 * time.Nanosecond
 	max := 10000 * time.Nanosecond