Sfoglia il codice sorgente

Merge pull request #535 from rod-hynes/tls-protocol-updates

TLS protocol updates
Rod Hynes 6 anni fa
parent
commit
a7f59c0a91

+ 6 - 0
psiphon/common/parameters/clientParameters.go

@@ -185,6 +185,8 @@ const (
 	MeekRoundTripTimeout                             = "MeekRoundTripTimeout"
 	MeekRoundTripTimeout                             = "MeekRoundTripTimeout"
 	MeekTrafficShapingProbability                    = "MeekTrafficShapingProbability"
 	MeekTrafficShapingProbability                    = "MeekTrafficShapingProbability"
 	MeekTrafficShapingLimitProtocols                 = "MeekTrafficShapingLimitProtocols"
 	MeekTrafficShapingLimitProtocols                 = "MeekTrafficShapingLimitProtocols"
+	MeekMinTLSPadding                                = "MeekMinTLSPadding"
+	MeekMaxTLSPadding                                = "MeekMaxTLSPadding"
 	MeekMinLimitRequestPayloadLength                 = "MeekMinLimitRequestPayloadLength"
 	MeekMinLimitRequestPayloadLength                 = "MeekMinLimitRequestPayloadLength"
 	MeekMaxLimitRequestPayloadLength                 = "MeekMaxLimitRequestPayloadLength"
 	MeekMaxLimitRequestPayloadLength                 = "MeekMaxLimitRequestPayloadLength"
 	MeekRedialTLSProbability                         = "MeekRedialTLSProbability"
 	MeekRedialTLSProbability                         = "MeekRedialTLSProbability"
@@ -392,6 +394,8 @@ var defaultClientParameters = map[string]struct {
 	//
 	//
 	// MeekCookieMaxPadding cannot exceed common.OBFUSCATE_SEED_LENGTH.
 	// MeekCookieMaxPadding cannot exceed common.OBFUSCATE_SEED_LENGTH.
 	//
 	//
+	// MeekMinTLSPadding/MeekMinTLSPadding are subject to TLS server limitations.
+	//
 	// MeekMinLimitRequestPayloadLength/MeekMaxLimitRequestPayloadLength
 	// MeekMinLimitRequestPayloadLength/MeekMaxLimitRequestPayloadLength
 	// cannot exceed server.MEEK_MAX_REQUEST_PAYLOAD_LENGTH.
 	// cannot exceed server.MEEK_MAX_REQUEST_PAYLOAD_LENGTH.
 
 
@@ -417,6 +421,8 @@ var defaultClientParameters = map[string]struct {
 
 
 	MeekTrafficShapingProbability:    {value: 1.0, minimum: 0.0},
 	MeekTrafficShapingProbability:    {value: 1.0, minimum: 0.0},
 	MeekTrafficShapingLimitProtocols: {value: protocol.TunnelProtocols{}},
 	MeekTrafficShapingLimitProtocols: {value: protocol.TunnelProtocols{}},
+	MeekMinTLSPadding:                {value: 0, minimum: 0},
+	MeekMaxTLSPadding:                {value: 0, minimum: 0},
 	MeekMinLimitRequestPayloadLength: {value: 65536, minimum: 1},
 	MeekMinLimitRequestPayloadLength: {value: 65536, minimum: 1},
 	MeekMaxLimitRequestPayloadLength: {value: 65536, minimum: 1},
 	MeekMaxLimitRequestPayloadLength: {value: 65536, minimum: 1},
 	MeekRedialTLSProbability:         {value: 0.0, minimum: 0.0},
 	MeekRedialTLSProbability:         {value: 0.0, minimum: 0.0},

+ 10 - 0
psiphon/config.go

@@ -522,6 +522,8 @@ type Config struct {
 	// purposes.
 	// purposes.
 	MeekTrafficShapingProbability    *float64
 	MeekTrafficShapingProbability    *float64
 	MeekTrafficShapingLimitProtocols []string
 	MeekTrafficShapingLimitProtocols []string
+	MeekMinTLSPadding                *int
+	MeekMaxTLSPadding                *int
 	MeekMinLimitRequestPayloadLength *int
 	MeekMinLimitRequestPayloadLength *int
 	MeekMaxLimitRequestPayloadLength *int
 	MeekMaxLimitRequestPayloadLength *int
 	MeekRedialTLSProbability         *float64
 	MeekRedialTLSProbability         *float64
@@ -1064,6 +1066,14 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 		applyParameters[parameters.MeekTrafficShapingLimitProtocols] = protocol.TunnelProtocols(config.MeekTrafficShapingLimitProtocols)
 		applyParameters[parameters.MeekTrafficShapingLimitProtocols] = protocol.TunnelProtocols(config.MeekTrafficShapingLimitProtocols)
 	}
 	}
 
 
+	if config.MeekMinTLSPadding != nil {
+		applyParameters[parameters.MeekMinTLSPadding] = *config.MeekMinTLSPadding
+	}
+
+	if config.MeekMaxTLSPadding != nil {
+		applyParameters[parameters.MeekMaxTLSPadding] = *config.MeekMaxTLSPadding
+	}
+
 	if config.MeekMinLimitRequestPayloadLength != nil {
 	if config.MeekMinLimitRequestPayloadLength != nil {
 		applyParameters[parameters.MeekMinLimitRequestPayloadLength] = *config.MeekMinLimitRequestPayloadLength
 		applyParameters[parameters.MeekMinLimitRequestPayloadLength] = *config.MeekMinLimitRequestPayloadLength
 	}
 	}

+ 1 - 0
psiphon/dialParameters.go

@@ -90,6 +90,7 @@ type DialParameters struct {
 	MeekSNIServerName         string
 	MeekSNIServerName         string
 	MeekHostHeader            string
 	MeekHostHeader            string
 	MeekObfuscatorPaddingSeed *prng.Seed
 	MeekObfuscatorPaddingSeed *prng.Seed
+	MeekTLSPaddingSize        int
 	MeekResolvedIPAddress     atomic.Value `json:"-"`
 	MeekResolvedIPAddress     atomic.Value `json:"-"`
 
 
 	SelectedUserAgent bool
 	SelectedUserAgent bool

+ 87 - 60
psiphon/meekConn.go

@@ -162,6 +162,7 @@ type MeekConn struct {
 	additionalHeaders         http.Header
 	additionalHeaders         http.Header
 	cookie                    *http.Cookie
 	cookie                    *http.Cookie
 	cookieSize                int
 	cookieSize                int
+	tlsPadding                int
 	limitRequestPayloadLength int
 	limitRequestPayloadLength int
 	redialTLSProbability      float64
 	redialTLSProbability      float64
 	cachedTLSDialer           *cachedTLSDialer
 	cachedTLSDialer           *cachedTLSDialer
@@ -228,17 +229,47 @@ func DialMeek(
 		}
 		}
 	}()
 	}()
 
 
+	meek = &MeekConn{
+		clientParameters:         meekConfig.ClientParameters,
+		networkLatencyMultiplier: meekConfig.NetworkLatencyMultiplier,
+		isClosed:                 false,
+		runCtx:                   runCtx,
+		stopRunning:              stopRunning,
+		relayWaitGroup:           new(sync.WaitGroup),
+		roundTripperOnly:         meekConfig.RoundTripperOnly,
+	}
+
+	if !meek.roundTripperOnly {
+
+		meek.cookie,
+			meek.tlsPadding,
+			meek.limitRequestPayloadLength,
+			meek.redialTLSProbability,
+			err =
+			makeMeekObfuscationValues(
+				meek.getCustomClientParameters(),
+				meekConfig.MeekCookieEncryptionPublicKey,
+				meekConfig.MeekObfuscatedKey,
+				meekConfig.MeekObfuscatorPaddingSeed,
+				meekConfig.ClientTunnelProtocol,
+				"")
+		if err != nil {
+			return nil, errors.Trace(err)
+		}
+	}
+
 	// Configure transport: QUIC or HTTPS or HTTP
 	// Configure transport: QUIC or HTTPS or HTTP
 
 
-	var isQUIC bool
-	var scheme string
-	var transport transporter
-	var additionalHeaders http.Header
-	var proxyUrl func(*http.Request) (*url.URL, error)
+	var (
+		scheme            string
+		transport         transporter
+		additionalHeaders http.Header
+		proxyUrl          func(*http.Request) (*url.URL, error)
+	)
 
 
 	if meekConfig.UseQUIC {
 	if meekConfig.UseQUIC {
 
 
-		isQUIC = true
+		meek.isQUIC = true
 
 
 		scheme = "https"
 		scheme = "https"
 
 
@@ -311,6 +342,7 @@ func DialMeek(
 			TLSProfile:                    meekConfig.TLSProfile,
 			TLSProfile:                    meekConfig.TLSProfile,
 			NoDefaultTLSSessionID:         &meekConfig.NoDefaultTLSSessionID,
 			NoDefaultTLSSessionID:         &meekConfig.NoDefaultTLSSessionID,
 			RandomizedTLSProfileSeed:      meekConfig.RandomizedTLSProfileSeed,
 			RandomizedTLSProfileSeed:      meekConfig.RandomizedTLSProfileSeed,
+			TLSPadding:                    meek.tlsPadding,
 			TrustedCACertificatesFilename: dialConfig.TrustedCACertificatesFilename,
 			TrustedCACertificatesFilename: dialConfig.TrustedCACertificatesFilename,
 		}
 		}
 		tlsConfig.EnableClientSessionCache()
 		tlsConfig.EnableClientSessionCache()
@@ -459,36 +491,10 @@ func DialMeek(
 		}
 		}
 	}
 	}
 
 
-	// The main loop of a MeekConn is run in the relay() goroutine.
-	// A MeekConn implements net.Conn concurrency semantics:
-	// "Multiple goroutines may invoke methods on a Conn simultaneously."
-	//
-	// Read() calls and relay() are synchronized by exchanging control of a single
-	// receiveBuffer (bytes.Buffer). This single buffer may be:
-	// - in the emptyReceiveBuffer channel when it is available and empty;
-	// - in the partialReadBuffer channel when it is available and contains data;
-	// - in the fullReadBuffer channel when it is available and full of data;
-	// - "checked out" by relay or Read when they are are writing to or reading from the
-	//   buffer, respectively.
-	// relay() will obtain the buffer from either the empty or partial channel but block when
-	// the buffer is full. Read will obtain the buffer from the partial or full channel when
-	// there is data to read but block when the buffer is empty.
-	// Write() calls and relay() are synchronized in a similar way, using a single
-	// sendBuffer.
-	meek = &MeekConn{
-		clientParameters:         meekConfig.ClientParameters,
-		networkLatencyMultiplier: meekConfig.NetworkLatencyMultiplier,
-		isQUIC:                   isQUIC,
-		url:                      url,
-		additionalHeaders:        additionalHeaders,
-		cachedTLSDialer:          cachedTLSDialer,
-		transport:                transport,
-		isClosed:                 false,
-		runCtx:                   runCtx,
-		stopRunning:              stopRunning,
-		relayWaitGroup:           new(sync.WaitGroup),
-		roundTripperOnly:         meekConfig.RoundTripperOnly,
-	}
+	meek.url = url
+	meek.additionalHeaders = additionalHeaders
+	meek.cachedTLSDialer = cachedTLSDialer
+	meek.transport = transport
 
 
 	// stopRunning and cachedTLSDialer will now be closed in meek.Close()
 	// stopRunning and cachedTLSDialer will now be closed in meek.Close()
 	cleanupStopRunning = false
 	cleanupStopRunning = false
@@ -498,22 +504,22 @@ func DialMeek(
 	// go routine, only when running in relay mode.
 	// go routine, only when running in relay mode.
 	if !meek.roundTripperOnly {
 	if !meek.roundTripperOnly {
 
 
-		cookie, limitRequestPayloadLength, redialTLSProbability, err :=
-			makeMeekObfuscationValues(
-				meek.getCustomClientParameters(),
-				meekConfig.MeekCookieEncryptionPublicKey,
-				meekConfig.MeekObfuscatedKey,
-				meekConfig.MeekObfuscatorPaddingSeed,
-				meekConfig.ClientTunnelProtocol,
-				"")
-		if err != nil {
-			return nil, errors.Trace(err)
-		}
-
-		meek.cookie = cookie
-		meek.cookieSize = len(cookie.Name) + len(cookie.Value)
-		meek.limitRequestPayloadLength = limitRequestPayloadLength
-		meek.redialTLSProbability = redialTLSProbability
+		// The main loop of a MeekConn is run in the relay() goroutine.
+		// A MeekConn implements net.Conn concurrency semantics:
+		// "Multiple goroutines may invoke methods on a Conn simultaneously."
+		//
+		// Read() calls and relay() are synchronized by exchanging control of a single
+		// receiveBuffer (bytes.Buffer). This single buffer may be:
+		// - in the emptyReceiveBuffer channel when it is available and empty;
+		// - in the partialReadBuffer channel when it is available and contains data;
+		// - in the fullReadBuffer channel when it is available and full of data;
+		// - "checked out" by relay or Read when they are are writing to or reading from the
+		//   buffer, respectively.
+		// relay() will obtain the buffer from either the empty or partial channel but block when
+		// the buffer is full. Read will obtain the buffer from the partial or full channel when
+		// there is data to read but block when the buffer is empty.
+		// Write() calls and relay() are synchronized in a similar way, using a single
+		// sendBuffer.
 
 
 		p := meek.getCustomClientParameters()
 		p := meek.getCustomClientParameters()
 		if p.Bool(parameters.MeekLimitBufferSizes) {
 		if p.Bool(parameters.MeekLimitBufferSizes) {
@@ -650,6 +656,7 @@ func (meek *MeekConn) IsClosed() bool {
 func (meek *MeekConn) GetMetrics() common.LogFields {
 func (meek *MeekConn) GetMetrics() common.LogFields {
 	logFields := make(common.LogFields)
 	logFields := make(common.LogFields)
 	logFields["meek_cookie_size"] = meek.cookieSize
 	logFields["meek_cookie_size"] = meek.cookieSize
+	logFields["meek_tls_padding"] = meek.tlsPadding
 	logFields["meek_limit_request"] = meek.limitRequestPayloadLength
 	logFields["meek_limit_request"] = meek.limitRequestPayloadLength
 	return logFields
 	return logFields
 }
 }
@@ -671,7 +678,7 @@ func (meek *MeekConn) RoundTrip(
 		return nil, errors.TraceNew("operation unsupported")
 		return nil, errors.TraceNew("operation unsupported")
 	}
 	}
 
 
-	cookie, _, _, err := makeMeekObfuscationValues(
+	cookie, _, _, _, err := makeMeekObfuscationValues(
 		meek.getCustomClientParameters(),
 		meek.getCustomClientParameters(),
 		meek.meekCookieEncryptionPublicKey,
 		meek.meekCookieEncryptionPublicKey,
 		meek.meekObfuscatedKey,
 		meek.meekObfuscatedKey,
@@ -1355,6 +1362,7 @@ func makeMeekObfuscationValues(
 	endPoint string,
 	endPoint string,
 
 
 ) (cookie *http.Cookie,
 ) (cookie *http.Cookie,
+	tlsPadding int,
 	limitRequestPayloadLength int,
 	limitRequestPayloadLength int,
 	redialTLSProbability float64,
 	redialTLSProbability float64,
 	err error) {
 	err error) {
@@ -1366,7 +1374,7 @@ func makeMeekObfuscationValues(
 	}
 	}
 	serializedCookie, err := json.Marshal(cookieData)
 	serializedCookie, err := json.Marshal(cookieData)
 	if err != nil {
 	if err != nil {
-		return nil, 0, 0, errors.Trace(err)
+		return nil, 0, 0, 0.0, errors.Trace(err)
 	}
 	}
 
 
 	// Encrypt the JSON data
 	// Encrypt the JSON data
@@ -1380,12 +1388,12 @@ func makeMeekObfuscationValues(
 	var publicKey [32]byte
 	var publicKey [32]byte
 	decodedPublicKey, err := base64.StdEncoding.DecodeString(meekCookieEncryptionPublicKey)
 	decodedPublicKey, err := base64.StdEncoding.DecodeString(meekCookieEncryptionPublicKey)
 	if err != nil {
 	if err != nil {
-		return nil, 0, 0, errors.Trace(err)
+		return nil, 0, 0, 0.0, errors.Trace(err)
 	}
 	}
 	copy(publicKey[:], decodedPublicKey)
 	copy(publicKey[:], decodedPublicKey)
 	ephemeralPublicKey, ephemeralPrivateKey, err := box.GenerateKey(rand.Reader)
 	ephemeralPublicKey, ephemeralPrivateKey, err := box.GenerateKey(rand.Reader)
 	if err != nil {
 	if err != nil {
-		return nil, 0, 0, errors.Trace(err)
+		return nil, 0, 0, 0.0, errors.Trace(err)
 	}
 	}
 	box := box.Seal(nil, serializedCookie, &nonce, &publicKey, ephemeralPrivateKey)
 	box := box.Seal(nil, serializedCookie, &nonce, &publicKey, ephemeralPrivateKey)
 	encryptedCookie := make([]byte, 32+len(box))
 	encryptedCookie := make([]byte, 32+len(box))
@@ -1401,7 +1409,7 @@ func makeMeekObfuscationValues(
 			PaddingPRNGSeed: meekObfuscatorPaddingPRNGSeed,
 			PaddingPRNGSeed: meekObfuscatorPaddingPRNGSeed,
 			MaxPadding:      &maxPadding})
 			MaxPadding:      &maxPadding})
 	if err != nil {
 	if err != nil {
-		return nil, 0, 0, errors.Trace(err)
+		return nil, 0, 0, 0.0, errors.Trace(err)
 	}
 	}
 	obfuscatedCookie := obfuscator.SendSeedMessage()
 	obfuscatedCookie := obfuscator.SendSeedMessage()
 	seedLen := len(obfuscatedCookie)
 	seedLen := len(obfuscatedCookie)
@@ -1410,7 +1418,7 @@ func makeMeekObfuscationValues(
 
 
 	cookieNamePRNG, err := obfuscator.GetDerivedPRNG("meek-cookie-name")
 	cookieNamePRNG, err := obfuscator.GetDerivedPRNG("meek-cookie-name")
 	if err != nil {
 	if err != nil {
-		return nil, 0, 0, errors.Trace(err)
+		return nil, 0, 0, 0.0, errors.Trace(err)
 	}
 	}
 
 
 	// Format the HTTP cookie
 	// Format the HTTP cookie
@@ -1424,6 +1432,7 @@ func makeMeekObfuscationValues(
 		Name:  string(byte(A + letterIndex)),
 		Name:  string(byte(A + letterIndex)),
 		Value: base64.StdEncoding.EncodeToString(obfuscatedCookie)}
 		Value: base64.StdEncoding.EncodeToString(obfuscatedCookie)}
 
 
+	tlsPadding = 0
 	limitRequestPayloadLength = MEEK_MAX_REQUEST_PAYLOAD_LENGTH
 	limitRequestPayloadLength = MEEK_MAX_REQUEST_PAYLOAD_LENGTH
 	redialTLSProbability = 0.0
 	redialTLSProbability = 0.0
 
 
@@ -1435,7 +1444,7 @@ func makeMeekObfuscationValues(
 		limitRequestPayloadLengthPRNG, err := obfuscator.GetDerivedPRNG(
 		limitRequestPayloadLengthPRNG, err := obfuscator.GetDerivedPRNG(
 			"meek-limit-request-payload-length")
 			"meek-limit-request-payload-length")
 		if err != nil {
 		if err != nil {
-			return nil, 0, 0, errors.Trace(err)
+			return nil, 0, 0, 0.0, errors.Trace(err)
 		}
 		}
 
 
 		minLength := p.Int(parameters.MeekMinLimitRequestPayloadLength)
 		minLength := p.Int(parameters.MeekMinLimitRequestPayloadLength)
@@ -1450,8 +1459,26 @@ func makeMeekObfuscationValues(
 		limitRequestPayloadLength = limitRequestPayloadLengthPRNG.Range(
 		limitRequestPayloadLength = limitRequestPayloadLengthPRNG.Range(
 			minLength, maxLength)
 			minLength, maxLength)
 
 
+		minPadding := p.Int(parameters.MeekMinTLSPadding)
+		maxPadding := p.Int(parameters.MeekMaxTLSPadding)
+
+		// Maximum padding size per RFC 7685
+		if maxPadding > 65535 {
+			maxPadding = 65535
+		}
+
+		if maxPadding > 0 {
+			tlsPaddingPRNG, err := obfuscator.GetDerivedPRNG(
+				"meek-tls-padding")
+			if err != nil {
+				return nil, 0, 0, 0.0, errors.Trace(err)
+			}
+
+			tlsPadding = tlsPaddingPRNG.Range(minPadding, maxPadding)
+		}
+
 		redialTLSProbability = p.Float(parameters.MeekRedialTLSProbability)
 		redialTLSProbability = p.Float(parameters.MeekRedialTLSProbability)
 	}
 	}
 
 
-	return cookie, limitRequestPayloadLength, redialTLSProbability, nil
+	return cookie, tlsPadding, limitRequestPayloadLength, redialTLSProbability, nil
 }
 }

+ 1 - 0
psiphon/server/api.go

@@ -743,6 +743,7 @@ var baseRequestParams = []requestParamSpec{
 	{"upstream_ossh_padding", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"upstream_ossh_padding", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"meek_cookie_size", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"meek_cookie_size", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"meek_limit_request", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"meek_limit_request", isIntString, requestParamOptional | requestParamLogStringAsInt},
+	{"meek_tls_padding", isIntString, requestParamOptional | requestParamLogStringAsInt},
 	{"network_latency_multiplier", isFloatString, requestParamOptional | requestParamLogStringAsFloat},
 	{"network_latency_multiplier", isFloatString, requestParamOptional | requestParamLogStringAsFloat},
 }
 }
 
 

+ 37 - 1
psiphon/server/server_test.go

@@ -128,6 +128,7 @@ func TestSSH(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    true,
 		})
 		})
 }
 }
 
 
@@ -146,6 +147,7 @@ func TestOSSH(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    true,
 		})
 		})
 }
 }
 
 
@@ -164,6 +166,7 @@ func TestFragmentedOSSH(t *testing.T) {
 			forceFragmenting:     true,
 			forceFragmenting:     true,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    true,
 		})
 		})
 }
 }
 
 
@@ -182,6 +185,7 @@ func TestUnfrontedMeek(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    true,
 		})
 		})
 }
 }
 
 
@@ -201,6 +205,7 @@ func TestUnfrontedMeekHTTPS(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    true,
 		})
 		})
 }
 }
 
 
@@ -220,6 +225,7 @@ func TestUnfrontedMeekHTTPSTLS13(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    true,
 		})
 		})
 }
 }
 
 
@@ -239,6 +245,7 @@ func TestUnfrontedMeekSessionTicket(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    true,
 		})
 		})
 }
 }
 
 
@@ -258,6 +265,7 @@ func TestUnfrontedMeekSessionTicketTLS13(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    true,
 		})
 		})
 }
 }
 
 
@@ -276,6 +284,7 @@ func TestQUICOSSH(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -297,6 +306,7 @@ func TestMarionetteOSSH(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -315,6 +325,7 @@ func TestWebTransportAPIRequests(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -333,6 +344,7 @@ func TestHotReload(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -351,6 +363,7 @@ func TestDefaultSponsorID(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -369,6 +382,7 @@ func TestDenyTrafficRules(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -387,6 +401,7 @@ func TestOmitAuthorization(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -405,6 +420,7 @@ func TestNoAuthorization(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -423,6 +439,7 @@ func TestUnusedAuthorization(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -441,6 +458,7 @@ func TestTCPOnlySLOK(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -459,6 +477,7 @@ func TestUDPOnlySLOK(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    false,
 			forceLivenessTest:    false,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -477,6 +496,7 @@ func TestLivenessTest(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    true,
 			forceLivenessTest:    true,
 			doPruneServerEntries: false,
 			doPruneServerEntries: false,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -495,6 +515,7 @@ func TestPruneServerEntries(t *testing.T) {
 			forceFragmenting:     false,
 			forceFragmenting:     false,
 			forceLivenessTest:    true,
 			forceLivenessTest:    true,
 			doPruneServerEntries: true,
 			doPruneServerEntries: true,
+			doDanglingTCPConn:    false,
 		})
 		})
 }
 }
 
 
@@ -512,6 +533,7 @@ type runServerConfig struct {
 	forceFragmenting     bool
 	forceFragmenting     bool
 	forceLivenessTest    bool
 	forceLivenessTest    bool
 	doPruneServerEntries bool
 	doPruneServerEntries bool
+	doDanglingTCPConn    bool
 }
 }
 
 
 var (
 var (
@@ -573,12 +595,13 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 		// Workaround for macOS firewall.
 		// Workaround for macOS firewall.
 		psiphonServerIPAddress = "127.0.0.1"
 		psiphonServerIPAddress = "127.0.0.1"
 	}
 	}
+	psiphonServerPort := 4000
 
 
 	generateConfigParams := &GenerateConfigParams{
 	generateConfigParams := &GenerateConfigParams{
 		ServerIPAddress:      psiphonServerIPAddress,
 		ServerIPAddress:      psiphonServerIPAddress,
 		EnableSSHAPIRequests: runConfig.enableSSHAPIRequests,
 		EnableSSHAPIRequests: runConfig.enableSSHAPIRequests,
 		WebServerPort:        8000,
 		WebServerPort:        8000,
-		TunnelProtocolPorts:  map[string]int{runConfig.tunnelProtocol: 4000},
+		TunnelProtocolPorts:  map[string]int{runConfig.tunnelProtocol: psiphonServerPort},
 	}
 	}
 
 
 	if protocol.TunnelProtocolUsesMarionette(runConfig.tunnelProtocol) {
 	if protocol.TunnelProtocolUsesMarionette(runConfig.tunnelProtocol) {
@@ -1062,6 +1085,19 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 			"prune server entries timeout exceeded")
 			"prune server entries timeout exceeded")
 	}
 	}
 
 
+	if runConfig.doDanglingTCPConn {
+
+		// Test: client that has established TCP connection but not completed
+		// any handshakes must not block/delay server shutdown
+
+		danglingConn, err := net.Dial(
+			"tcp", net.JoinHostPort(psiphonServerIPAddress, strconv.Itoa(psiphonServerPort)))
+		if err != nil {
+			t.Fatalf("TCP dial failed: %s", err)
+		}
+		defer danglingConn.Close()
+	}
+
 	// Shutdown to ensure logs/notices are flushed
 	// Shutdown to ensure logs/notices are flushed
 
 
 	stopClient()
 	stopClient()

+ 37 - 0
psiphon/tlsDialer.go

@@ -127,6 +127,11 @@ type CustomTLSConfig struct {
 	// optional replay of a particular randomized Client Hello.
 	// optional replay of a particular randomized Client Hello.
 	RandomizedTLSProfileSeed *prng.Seed
 	RandomizedTLSProfileSeed *prng.Seed
 
 
+	// TLSPadding indicates whether to move or add a TLS padding extension to the
+	// front of the exension list and apply the specified padding length. Ignored
+	// when 0.
+	TLSPadding int
+
 	// TrustedCACertificatesFilename specifies a file containing trusted
 	// TrustedCACertificatesFilename specifies a file containing trusted
 	// CA certs. See Config.TrustedCACertificatesFilename.
 	// CA certs. See Config.TrustedCACertificatesFilename.
 	TrustedCACertificatesFilename string
 	TrustedCACertificatesFilename string
@@ -575,6 +580,38 @@ func CustomTLSDial(
 		needRemarshal = true
 		needRemarshal = true
 	}
 	}
 
 
+	if config.TLSPadding > 0 {
+
+		tlsPadding := config.TLSPadding
+
+		// Maximum padding size per RFC 7685
+		if tlsPadding > 65535 {
+			tlsPadding = 65535
+		}
+
+		// Assumes only one PaddingExtension.
+		deleteIndex := -1
+		for index, extension := range conn.Extensions {
+			if _, ok := extension.(*utls.UtlsPaddingExtension); ok {
+				deleteIndex = index
+				break
+			}
+		}
+		if deleteIndex != -1 {
+			conn.Extensions = append(
+				conn.Extensions[:deleteIndex], conn.Extensions[deleteIndex+1:]...)
+		}
+
+		paddingExtension := &utls.UtlsPaddingExtension{
+			PaddingLen: tlsPadding,
+			WillPad:    true,
+		}
+		conn.Extensions = append([]utls.TLSExtension{paddingExtension}, conn.Extensions...)
+
+		needRemarshal = true
+
+	}
+
 	if needRemarshal {
 	if needRemarshal {
 		// Apply changes to utls
 		// Apply changes to utls
 		err = conn.MarshalClientHello()
 		err = conn.MarshalClientHello()

+ 22 - 17
vendor/github.com/Psiphon-Labs/tls-tris/conn.go

@@ -34,6 +34,14 @@ type Conn struct {
 	// confirmMutex is held by any read operation before handshakeConfirmed
 	// confirmMutex is held by any read operation before handshakeConfirmed
 	confirmMutex sync.Mutex
 	confirmMutex sync.Mutex
 
 
+	// [Psiphon]
+	// https://github.com/golang/go/commit/e5b13401c6b19f58a8439f1019a80fe540c0c687
+	//
+	// handshakeStatus is 1 if the connection is currently transferring
+	// application data (i.e. is not currently processing a handshake).
+	// This field is only to be accessed with sync/atomic.
+	handshakeStatus uint32
+
 	// constant after handshake; protected by handshakeMutex
 	// constant after handshake; protected by handshakeMutex
 	handshakeMutex sync.Mutex // handshakeMutex < in.Mutex, out.Mutex, errMutex
 	handshakeMutex sync.Mutex // handshakeMutex < in.Mutex, out.Mutex, errMutex
 	handshakeErr   error      // error resulting from handshake
 	handshakeErr   error      // error resulting from handshake
@@ -42,9 +50,6 @@ type Conn struct {
 	vers           uint16     // TLS version
 	vers           uint16     // TLS version
 	haveVers       bool       // version has been negotiated
 	haveVers       bool       // version has been negotiated
 	config         *Config    // configuration passed to constructor
 	config         *Config    // configuration passed to constructor
-	// handshakeComplete is true if the connection reached application data
-	// and it's equivalent to phase > handshakeRunning
-	handshakeComplete bool
 	// handshakes counts the number of handshakes performed on the
 	// handshakes counts the number of handshakes performed on the
 	// connection so far. If renegotiation is disabled then this is either
 	// connection so far. If renegotiation is disabled then this is either
 	// zero or one.
 	// zero or one.
@@ -1241,7 +1246,7 @@ func (c *Conn) Write(b []byte) (int, error) {
 		return 0, err
 		return 0, err
 	}
 	}
 
 
-	if !c.handshakeComplete {
+	if !c.handshakeComplete() {
 		return 0, alertInternalError
 		return 0, alertInternalError
 	}
 	}
 
 
@@ -1325,7 +1330,7 @@ func (c *Conn) handleRenegotiation(*helloRequestMsg) error {
 	defer c.handshakeMutex.Unlock()
 	defer c.handshakeMutex.Unlock()
 
 
 	c.phase = handshakeRunning
 	c.phase = handshakeRunning
-	c.handshakeComplete = false
+	atomic.StoreUint32(&c.handshakeStatus, 0)
 	if c.handshakeErr = c.clientHandshake(); c.handshakeErr == nil {
 	if c.handshakeErr = c.clientHandshake(); c.handshakeErr == nil {
 		c.handshakes++
 		c.handshakes++
 	}
 	}
@@ -1573,11 +1578,9 @@ func (c *Conn) Close() error {
 
 
 	var alertErr error
 	var alertErr error
 
 
-	c.handshakeMutex.Lock()
-	if c.handshakeComplete {
+	if c.handshakeComplete() {
 		alertErr = c.closeNotify()
 		alertErr = c.closeNotify()
 	}
 	}
-	c.handshakeMutex.Unlock()
 
 
 	if err := c.conn.Close(); err != nil {
 	if err := c.conn.Close(); err != nil {
 		return err
 		return err
@@ -1591,9 +1594,7 @@ var errEarlyCloseWrite = errors.New("tls: CloseWrite called before handshake com
 // called once the handshake has completed and does not call CloseWrite on the
 // called once the handshake has completed and does not call CloseWrite on the
 // underlying connection. Most callers should just use Close.
 // underlying connection. Most callers should just use Close.
 func (c *Conn) CloseWrite() error {
 func (c *Conn) CloseWrite() error {
-	c.handshakeMutex.Lock()
-	defer c.handshakeMutex.Unlock()
-	if !c.handshakeComplete {
+	if !c.handshakeComplete() {
 		return errEarlyCloseWrite
 		return errEarlyCloseWrite
 	}
 	}
 
 
@@ -1625,7 +1626,7 @@ func (c *Conn) Handshake() error {
 	if err := c.handshakeErr; err != nil {
 	if err := c.handshakeErr; err != nil {
 		return err
 		return err
 	}
 	}
-	if c.handshakeComplete {
+	if c.handshakeComplete() {
 		return nil
 		return nil
 	}
 	}
 
 
@@ -1634,7 +1635,7 @@ func (c *Conn) Handshake() error {
 
 
 	// The handshake cannot have completed when handshakeMutex was unlocked
 	// The handshake cannot have completed when handshakeMutex was unlocked
 	// because this goroutine set handshakeCond.
 	// because this goroutine set handshakeCond.
-	if c.handshakeErr != nil || c.handshakeComplete {
+	if c.handshakeErr != nil || c.handshakeComplete() {
 		panic("handshake should not have been able to complete after handshakeCond was set")
 		panic("handshake should not have been able to complete after handshakeCond was set")
 	}
 	}
 
 
@@ -1656,7 +1657,7 @@ func (c *Conn) Handshake() error {
 		c.flush()
 		c.flush()
 	}
 	}
 
 
-	if c.handshakeErr == nil && !c.handshakeComplete {
+	if c.handshakeErr == nil && !c.handshakeComplete() {
 		panic("handshake should have had a result.")
 		panic("handshake should have had a result.")
 	}
 	}
 
 
@@ -1669,10 +1670,10 @@ func (c *Conn) ConnectionState() ConnectionState {
 	defer c.handshakeMutex.Unlock()
 	defer c.handshakeMutex.Unlock()
 
 
 	var state ConnectionState
 	var state ConnectionState
-	state.HandshakeComplete = c.handshakeComplete
+	state.HandshakeComplete = c.handshakeComplete()
 	state.ServerName = c.serverName
 	state.ServerName = c.serverName
 
 
-	if c.handshakeComplete {
+	if state.HandshakeComplete {
 		state.ConnectionID = c.connID
 		state.ConnectionID = c.connID
 		state.ClientHello = c.clientHello
 		state.ClientHello = c.clientHello
 		state.Version = c.vers
 		state.Version = c.vers
@@ -1721,7 +1722,7 @@ func (c *Conn) VerifyHostname(host string) error {
 	if !c.isClient {
 	if !c.isClient {
 		return errors.New("tls: VerifyHostname called on TLS server connection")
 		return errors.New("tls: VerifyHostname called on TLS server connection")
 	}
 	}
-	if !c.handshakeComplete {
+	if !c.handshakeComplete() {
 		return errors.New("tls: handshake has not yet been performed")
 		return errors.New("tls: handshake has not yet been performed")
 	}
 	}
 	if len(c.verifiedChains) == 0 {
 	if len(c.verifiedChains) == 0 {
@@ -1729,3 +1730,7 @@ func (c *Conn) VerifyHostname(host string) error {
 	}
 	}
 	return c.peerCertificates[0].VerifyHostname(host)
 	return c.peerCertificates[0].VerifyHostname(host)
 }
 }
+
+func (c *Conn) handshakeComplete() bool {
+	return atomic.LoadUint32(&c.handshakeStatus) == 1
+}

+ 4 - 1
vendor/github.com/Psiphon-Labs/tls-tris/handshake_client.go

@@ -331,7 +331,10 @@ func (hs *clientHandshakeState) handshake() error {
 	c.didResume = isResume
 	c.didResume = isResume
 	c.phase = handshakeConfirmed
 	c.phase = handshakeConfirmed
 	atomic.StoreInt32(&c.handshakeConfirmed, 1)
 	atomic.StoreInt32(&c.handshakeConfirmed, 1)
-	c.handshakeComplete = true
+
+	// [Psiphon]
+	// https://github.com/golang/go/commit/e5b13401c6b19f58a8439f1019a80fe540c0c687
+	atomic.StoreUint32(&c.handshakeStatus, 1)
 
 
 	return nil
 	return nil
 }
 }

+ 84 - 32
vendor/github.com/Psiphon-Labs/tls-tris/handshake_messages.go

@@ -1325,6 +1325,10 @@ type serverHelloMsg struct {
 
 
 	// RFC7627
 	// RFC7627
 	extendedMSSupported bool
 	extendedMSSupported bool
+
+	// [Psiphon]
+	// https://github.com/golang/go/commit/02a5502ab8d862309aaec3c5ec293b57b913d01d
+	supportedPoints []uint8
 }
 }
 
 
 func (m *serverHelloMsg) equal(i interface{}) bool {
 func (m *serverHelloMsg) equal(i interface{}) bool {
@@ -1359,7 +1363,10 @@ func (m *serverHelloMsg) equal(i interface{}) bool {
 		bytes.Equal(m.keyShare.data, m1.keyShare.data) &&
 		bytes.Equal(m.keyShare.data, m1.keyShare.data) &&
 		m.psk == m1.psk &&
 		m.psk == m1.psk &&
 		m.pskIdentity == m1.pskIdentity &&
 		m.pskIdentity == m1.pskIdentity &&
-		m.extendedMSSupported == m1.extendedMSSupported
+		m.extendedMSSupported == m1.extendedMSSupported &&
+
+		// [Psiphon]
+		bytes.Equal(m.supportedPoints, m1.supportedPoints)
 }
 }
 
 
 func (m *serverHelloMsg) marshal() []byte {
 func (m *serverHelloMsg) marshal() []byte {
@@ -1422,6 +1429,12 @@ func (m *serverHelloMsg) marshal() []byte {
 		numExtensions++
 		numExtensions++
 	}
 	}
 
 
+	// [Psiphon]
+	if len(m.supportedPoints) > 0 {
+		extensionsLength += 1 + len(m.supportedPoints)
+		numExtensions++
+	}
+
 	if numExtensions > 0 {
 	if numExtensions > 0 {
 		extensionsLength += 4 * numExtensions
 		extensionsLength += 4 * numExtensions
 		length += 2 + extensionsLength
 		length += 2 + extensionsLength
@@ -1454,13 +1467,48 @@ func (m *serverHelloMsg) marshal() []byte {
 		z[1] = byte(extensionsLength)
 		z[1] = byte(extensionsLength)
 		z = z[2:]
 		z = z[2:]
 	}
 	}
-	if m.vers >= VersionTLS13 {
-		z[0] = byte(extensionSupportedVersions >> 8)
-		z[1] = byte(extensionSupportedVersions)
-		z[3] = 2
-		z[4] = uint8(m.vers >> 8)
-		z[5] = uint8(m.vers)
-		z = z[6:]
+
+	// [Psiphon]
+	// Reorder extensions to match OpenSSL order:
+	// https://github.com/openssl/openssl/blob/2a5385511051d33be8d2b20d7669d8b1862fe510/ssl/statem/extensions.c#L119
+
+	if m.secureRenegotiationSupported {
+		z[0] = byte(extensionRenegotiationInfo >> 8)
+		z[1] = byte(extensionRenegotiationInfo & 0xff)
+		z[2] = 0
+		z[3] = byte(len(m.secureRenegotiation) + 1)
+		z[4] = byte(len(m.secureRenegotiation))
+		z = z[5:]
+		copy(z, m.secureRenegotiation)
+		z = z[len(m.secureRenegotiation):]
+	}
+
+	// [Psiphon]
+	// https://github.com/golang/go/commit/02a5502ab8d862309aaec3c5ec293b57b913d01d
+	if len(m.supportedPoints) > 0 {
+		// http://tools.ietf.org/html/rfc4492#section-5.5.2
+		z[0] = byte(extensionSupportedPoints >> 8)
+		z[1] = byte(extensionSupportedPoints)
+		l := 1 + len(m.supportedPoints)
+		z[2] = byte(l >> 8)
+		z[3] = byte(l)
+		l--
+		z[4] = byte(l)
+		z = z[5:]
+		for _, pointFormat := range m.supportedPoints {
+			z[0] = pointFormat
+			z = z[1:]
+		}
+	}
+	if m.ticketSupported {
+		z[0] = byte(extensionSessionTicket >> 8)
+		z[1] = byte(extensionSessionTicket)
+		z = z[4:]
+	}
+	if m.ocspStapling {
+		z[0] = byte(extensionStatusRequest >> 8)
+		z[1] = byte(extensionStatusRequest)
+		z = z[4:]
 	}
 	}
 	if m.nextProtoNeg {
 	if m.nextProtoNeg {
 		z[0] = byte(extensionNextProtoNeg >> 8)
 		z[0] = byte(extensionNextProtoNeg >> 8)
@@ -1479,26 +1527,6 @@ func (m *serverHelloMsg) marshal() []byte {
 			z = z[1+l:]
 			z = z[1+l:]
 		}
 		}
 	}
 	}
-	if m.ocspStapling {
-		z[0] = byte(extensionStatusRequest >> 8)
-		z[1] = byte(extensionStatusRequest)
-		z = z[4:]
-	}
-	if m.ticketSupported {
-		z[0] = byte(extensionSessionTicket >> 8)
-		z[1] = byte(extensionSessionTicket)
-		z = z[4:]
-	}
-	if m.secureRenegotiationSupported {
-		z[0] = byte(extensionRenegotiationInfo >> 8)
-		z[1] = byte(extensionRenegotiationInfo & 0xff)
-		z[2] = 0
-		z[3] = byte(len(m.secureRenegotiation) + 1)
-		z[4] = byte(len(m.secureRenegotiation))
-		z = z[5:]
-		copy(z, m.secureRenegotiation)
-		z = z[len(m.secureRenegotiation):]
-	}
 	if alpnLen := len(m.alpnProtocol); alpnLen > 0 {
 	if alpnLen := len(m.alpnProtocol); alpnLen > 0 {
 		z[0] = byte(extensionALPN >> 8)
 		z[0] = byte(extensionALPN >> 8)
 		z[1] = byte(extensionALPN & 0xff)
 		z[1] = byte(extensionALPN & 0xff)
@@ -1530,6 +1558,18 @@ func (m *serverHelloMsg) marshal() []byte {
 			z = z[len(sct)+2:]
 			z = z[len(sct)+2:]
 		}
 		}
 	}
 	}
+	if m.extendedMSSupported {
+		binary.BigEndian.PutUint16(z, extensionEMS)
+		z = z[4:]
+	}
+	if m.vers >= VersionTLS13 {
+		z[0] = byte(extensionSupportedVersions >> 8)
+		z[1] = byte(extensionSupportedVersions)
+		z[3] = 2
+		z[4] = uint8(m.vers >> 8)
+		z[5] = uint8(m.vers)
+		z = z[6:]
+	}
 	if m.keyShare.group != 0 {
 	if m.keyShare.group != 0 {
 		z[0] = uint8(extensionKeyShare >> 8)
 		z[0] = uint8(extensionKeyShare >> 8)
 		z[1] = uint8(extensionKeyShare)
 		z[1] = uint8(extensionKeyShare)
@@ -1553,10 +1593,6 @@ func (m *serverHelloMsg) marshal() []byte {
 		z[5] = byte(m.pskIdentity)
 		z[5] = byte(m.pskIdentity)
 		z = z[6:]
 		z = z[6:]
 	}
 	}
-	if m.extendedMSSupported {
-		binary.BigEndian.PutUint16(z, extensionEMS)
-		z = z[4:]
-	}
 
 
 	m.raw = x
 	m.raw = x
 
 
@@ -1595,6 +1631,9 @@ func (m *serverHelloMsg) unmarshal(data []byte) alert {
 	m.pskIdentity = 0
 	m.pskIdentity = 0
 	m.extendedMSSupported = false
 	m.extendedMSSupported = false
 
 
+	// [Psiphon]
+	m.supportedPoints = nil
+
 	if len(data) == 0 {
 	if len(data) == 0 {
 		// ServerHello is optionally followed by extension data
 		// ServerHello is optionally followed by extension data
 		return alertSuccess
 		return alertSuccess
@@ -1737,6 +1776,19 @@ func (m *serverHelloMsg) unmarshal(data []byte) alert {
 			m.pskIdentity = uint16(data[0])<<8 | uint16(data[1])
 			m.pskIdentity = uint16(data[0])<<8 | uint16(data[1])
 		case extensionEMS:
 		case extensionEMS:
 			m.extendedMSSupported = true
 			m.extendedMSSupported = true
+
+		// [Psiphon]
+		case extensionSupportedPoints:
+			// http://tools.ietf.org/html/rfc4492#section-5.5.2
+			if length < 1 {
+				return alertDecodeError
+			}
+			l := int(data[0])
+			if length != l+1 {
+				return alertDecodeError
+			}
+			m.supportedPoints = make([]uint8, l)
+			copy(m.supportedPoints, data[1:])
 		}
 		}
 		data = data[length:]
 		data = data[length:]
 	}
 	}

+ 20 - 2
vendor/github.com/Psiphon-Labs/tls-tris/handshake_server.go

@@ -87,7 +87,7 @@ func (c *Conn) serverHandshake() error {
 				return err
 				return err
 			}
 			}
 		}
 		}
-		c.handshakeComplete = true
+		atomic.StoreUint32(&c.handshakeStatus, 1)
 		return nil
 		return nil
 	} else if isResume {
 	} else if isResume {
 		// The client has included a session ticket and so we do an abbreviated handshake.
 		// The client has included a session ticket and so we do an abbreviated handshake.
@@ -145,7 +145,10 @@ func (c *Conn) serverHandshake() error {
 	}
 	}
 	c.phase = handshakeConfirmed
 	c.phase = handshakeConfirmed
 	atomic.StoreInt32(&c.handshakeConfirmed, 1)
 	atomic.StoreInt32(&c.handshakeConfirmed, 1)
-	c.handshakeComplete = true
+
+	// [Psiphon]
+	// https://github.com/golang/go/commit/e5b13401c6b19f58a8439f1019a80fe540c0c687
+	atomic.StoreUint32(&c.handshakeStatus, 1)
 
 
 	return nil
 	return nil
 }
 }
@@ -208,6 +211,9 @@ Curves:
 		}
 		}
 	}
 	}
 
 
+	// [Psiphon]
+	hasSupportedPoints := false
+
 	// If present, the supported points extension must include uncompressed.
 	// If present, the supported points extension must include uncompressed.
 	// Can be absent. This behavior mirrors BoringSSL.
 	// Can be absent. This behavior mirrors BoringSSL.
 	if hs.clientHello.supportedPoints != nil {
 	if hs.clientHello.supportedPoints != nil {
@@ -222,6 +228,7 @@ Curves:
 			c.sendAlert(alertHandshakeFailure)
 			c.sendAlert(alertHandshakeFailure)
 			return false, errors.New("tls: client does not support uncompressed points")
 			return false, errors.New("tls: client does not support uncompressed points")
 		}
 		}
+		hasSupportedPoints = true
 	}
 	}
 
 
 	foundCompression := false
 	foundCompression := false
@@ -271,6 +278,17 @@ Curves:
 		}
 		}
 	}
 	}
 
 
+	// [Psiphon]
+	// https://github.com/golang/go/commit/02a5502ab8d862309aaec3c5ec293b57b913d01d
+	if hasSupportedPoints && c.vers < VersionTLS13 {
+		// Although omitting the ec_point_formats extension is permitted, some
+		// old OpenSSL versions will refuse to handshake if not present.
+		//
+		// Per RFC 4492, section 5.1.2, implementations MUST support the
+		// uncompressed point format. See golang.org/issue/31943.
+		hs.hello.supportedPoints = []uint8{pointFormatUncompressed}
+	}
+
 	if len(hs.clientHello.serverName) > 0 {
 	if len(hs.clientHello.serverName) > 0 {
 		c.serverName = hs.clientHello.serverName
 		c.serverName = hs.clientHello.serverName
 	}
 	}

+ 3 - 3
vendor/vendor.json

@@ -63,10 +63,10 @@
 			"revisionTime": "2019-12-04T18:36:04Z"
 			"revisionTime": "2019-12-04T18:36:04Z"
 		},
 		},
 		{
 		{
-			"checksumSHA1": "DmQc8vfP44VMUTIZJGM4WslOyk0=",
+			"checksumSHA1": "+lsQUKG+zIU9IxrQf5pJBSRE79Q=",
 			"path": "github.com/Psiphon-Labs/tls-tris",
 			"path": "github.com/Psiphon-Labs/tls-tris",
-			"revision": "b5083341bf6cb581f3319c6dfbb39dd6ae3a97ea",
-			"revisionTime": "2019-03-21T17:45:24Z"
+			"revision": "78f52e6d62437fc594a15aafbd8c583d5eb48d46",
+			"revisionTime": "2019-12-06T19:09:01Z"
 		},
 		},
 		{
 		{
 			"checksumSHA1": "30PBqj9BW03KCVqASvLg3bR+xYc=",
 			"checksumSHA1": "30PBqj9BW03KCVqASvLg3bR+xYc=",