Просмотр исходного кода

Fix: all relevant config fields reflected in dial parameters state

Rod Hynes 7 лет назад
Родитель
Сommit
7242aa62e8
2 измененных файлов с 129 добавлено и 8 удалено
  1. 118 2
      psiphon/config.go
  2. 11 6
      psiphon/dialParameters.go

+ 118 - 2
psiphon/config.go

@@ -20,7 +20,9 @@
 package psiphon
 
 import (
+	"crypto/md5"
 	"encoding/base64"
+	"encoding/binary"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -519,6 +521,8 @@ type Config struct {
 	// calling clientParameters.Set directly will fail to add config values.
 	clientParameters *parameters.ClientParameters
 
+	dialParametersHash []byte
+
 	dynamicConfigMutex sync.Mutex
 	sponsorID          string
 	authorizations     []string
@@ -584,6 +588,10 @@ func (config *Config) Commit() error {
 		config.UpgradeDownloadURLs = promoteLegacyDownloadURL(config.UpgradeDownloadUrl)
 	}
 
+	if config.TunnelProtocol != "" && len(config.LimitTunnelProtocols) == 0 {
+		config.LimitTunnelProtocols = []string{config.TunnelProtocol}
+	}
+
 	// Supply default values.
 
 	if config.DataStoreDirectory == "" {
@@ -711,6 +719,11 @@ func (config *Config) Commit() error {
 		return common.ContextError(err)
 	}
 
+	// Calculate and set the dial parameters hash. After this point, related
+	// config fields must not change.
+
+	config.setDialParametersHash()
+
 	// Set defaults for dynamic config fields.
 
 	config.SetDynamicConfig(config.SponsorId, config.Authorizations)
@@ -847,8 +860,6 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 
 	if len(config.LimitTunnelProtocols) > 0 {
 		applyParameters[parameters.LimitTunnelProtocols] = protocol.TunnelProtocols(config.LimitTunnelProtocols)
-	} else if config.TunnelProtocol != "" {
-		applyParameters[parameters.LimitTunnelProtocols] = protocol.TunnelProtocols{config.TunnelProtocol}
 	}
 
 	if len(config.InitialLimitTunnelProtocols) > 0 && config.InitialLimitTunnelProtocolsCandidateCount > 0 {
@@ -1002,6 +1013,111 @@ func (config *Config) makeConfigParameters() map[string]interface{} {
 	return applyParameters
 }
 
+func (config *Config) setDialParametersHash() {
+
+	// Calculate and store a hash of the config values that may impact
+	// dial parameters. This hash is used as part of the dial parameters
+	// replay mechanism to detect when persisted dial parameters must
+	// be discarded due to conflicting config changes.
+	//
+	// MD5 hash is used solely as a data checksum and not for any security
+	// purpose.
+
+	hash := md5.New()
+
+	if len(config.LimitTunnelProtocols) > 0 {
+		for _, protocol := range config.LimitTunnelProtocols {
+			hash.Write([]byte(protocol))
+		}
+	}
+
+	if len(config.InitialLimitTunnelProtocols) > 0 && config.InitialLimitTunnelProtocolsCandidateCount > 0 {
+		for _, protocol := range config.InitialLimitTunnelProtocols {
+			hash.Write([]byte(protocol))
+		}
+		binary.Write(hash, binary.LittleEndian, config.InitialLimitTunnelProtocolsCandidateCount)
+	}
+
+	if len(config.LimitTLSProfiles) > 0 {
+		for _, profile := range config.LimitTLSProfiles {
+			hash.Write([]byte(profile))
+		}
+	}
+
+	if len(config.LimitQUICVersions) > 0 {
+		for _, version := range config.LimitQUICVersions {
+			hash.Write([]byte(version))
+		}
+	}
+
+	// *TODO*
+	if _, ok := config.CustomHeaders["User-Agent"]; ok {
+		hash.Write([]byte{1})
+	}
+
+	if config.UpstreamProxyURL != "" {
+		hash.Write([]byte(config.UpstreamProxyURL))
+	}
+
+	if config.TransformHostNames != "" {
+		hash.Write([]byte(config.TransformHostNames))
+	}
+
+	if config.UseFragmentor != "" {
+		hash.Write([]byte(config.UseFragmentor))
+	}
+
+	if config.FragmentorMinTotalBytes != nil {
+		binary.Write(hash, binary.LittleEndian, *config.FragmentorMinTotalBytes)
+	}
+
+	if config.FragmentorMaxTotalBytes != nil {
+		binary.Write(hash, binary.LittleEndian, *config.FragmentorMaxTotalBytes)
+	}
+
+	if config.FragmentorMinWriteBytes != nil {
+		binary.Write(hash, binary.LittleEndian, *config.FragmentorMinWriteBytes)
+	}
+
+	if config.FragmentorMaxWriteBytes != nil {
+		binary.Write(hash, binary.LittleEndian, *config.FragmentorMaxWriteBytes)
+	}
+
+	if config.FragmentorMinDelayMicroseconds != nil {
+		binary.Write(hash, binary.LittleEndian, *config.FragmentorMinDelayMicroseconds)
+	}
+
+	if config.FragmentorMaxDelayMicroseconds != nil {
+		binary.Write(hash, binary.LittleEndian, *config.FragmentorMaxDelayMicroseconds)
+	}
+
+	if config.ObfuscatedSSHMinPadding != nil {
+		binary.Write(hash, binary.LittleEndian, *config.ObfuscatedSSHMinPadding)
+	}
+
+	if config.ObfuscatedSSHMaxPadding != nil {
+		binary.Write(hash, binary.LittleEndian, *config.ObfuscatedSSHMaxPadding)
+	}
+
+	if config.LivenessTestMinUpstreamBytes != nil {
+		binary.Write(hash, binary.LittleEndian, *config.LivenessTestMinUpstreamBytes)
+	}
+
+	if config.LivenessTestMaxUpstreamBytes != nil {
+		binary.Write(hash, binary.LittleEndian, *config.LivenessTestMaxUpstreamBytes)
+	}
+
+	if config.LivenessTestMinDownstreamBytes != nil {
+		binary.Write(hash, binary.LittleEndian, *config.LivenessTestMinDownstreamBytes)
+	}
+
+	if config.LivenessTestMaxDownstreamBytes != nil {
+		binary.Write(hash, binary.LittleEndian, *config.LivenessTestMaxDownstreamBytes)
+	}
+
+	config.dialParametersHash = hash.Sum(nil)
+}
+
 func promoteLegacyDownloadURL(URL string) parameters.DownloadURLs {
 	downloadURLs := make(parameters.DownloadURLs, 1)
 	downloadURLs[0] = &parameters.DownloadURL{

+ 11 - 6
psiphon/dialParameters.go

@@ -620,13 +620,22 @@ func getConfigStateHash(
 	// of these input values change in a way that invalidates any stored dial
 	// parameters.
 
-	// MD5 hash is used solely as a data checksum and not for any security purpose.
+	// MD5 hash is used solely as a data checksum and not for any security
+	// purpose.
 	hash := md5.New()
 
+	// Add a hash of relevant config fields.
+	// Limitation: the config hash may change even when tactics will override the
+	// changed config field.
+	hash.Write(config.dialParametersHash)
+
+	// Add the active tactics tag.
 	hash.Write([]byte(p.Tag()))
 
+	// Add the server entry version and local timestamp, both of which should
+	// change when the server entry contents change and/or a new local copy is
+	// imported.
 	// TODO: marshal entire server entry?
-
 	var serverEntryConfigurationVersion [8]byte
 	binary.BigEndian.PutUint64(
 		serverEntryConfigurationVersion[:],
@@ -634,10 +643,6 @@ func getConfigStateHash(
 	hash.Write(serverEntryConfigurationVersion[:])
 	hash.Write([]byte(serverEntry.LocalTimestamp))
 
-	// TODO: add config.CustomHeaders, which could impact User-Agent header?
-
-	hash.Write([]byte(config.UpstreamProxyURL))
-
 	return hash.Sum(nil)
 }