diff --git a/ClientLibrary/clientlib/clientlib.go b/ClientLibrary/clientlib/clientlib.go index 1577de0d3..8b8f9a6af 100644 --- a/ClientLibrary/clientlib/clientlib.go +++ b/ClientLibrary/clientlib/clientlib.go @@ -156,7 +156,7 @@ func StartTunnel(ctx context.Context, // config.Commit must be called before calling config.SetClientParameters // or attempting to connect. - err = config.Commit() + err = config.Commit(true) if err != nil { return nil, errors.TraceMsg(err, "config.Commit failed") } diff --git a/ConsoleClient/main.go b/ConsoleClient/main.go index 509890066..451714fea 100644 --- a/ConsoleClient/main.go +++ b/ConsoleClient/main.go @@ -209,7 +209,7 @@ func main() { // All config fields should be set before calling Commit. - err = config.Commit() + err = config.Commit(true) if err != nil { psiphon.SetEmitDiagnosticNotices(true, false) psiphon.NoticeError("error loading configuration file: %s", err) diff --git a/MobileLibrary/psi/psi.go b/MobileLibrary/psi/psi.go index 3a78d19d9..1a4bff7a7 100644 --- a/MobileLibrary/psi/psi.go +++ b/MobileLibrary/psi/psi.go @@ -152,7 +152,7 @@ func Start( // All config fields should be set before calling Commit. - err = config.Commit() + err = config.Commit(true) if err != nil { return fmt.Errorf("error committing configuration file: %s", err) } diff --git a/psiphon/common/logger.go b/psiphon/common/logger.go index a36fa5d80..a2cb7bfbf 100644 --- a/psiphon/common/logger.go +++ b/psiphon/common/logger.go @@ -50,3 +50,9 @@ type MetricsSource interface { // metrics from the MetricsSource GetMetrics() LogFields } + +func SetIrregularTunnelErrorLogField( + logFields LogFields, tunnelError error) { + + logFields["tunnel_error"] = tunnelError +} diff --git a/psiphon/common/net.go b/psiphon/common/net.go index 61cdd061f..d8a49ee9f 100644 --- a/psiphon/common/net.go +++ b/psiphon/common/net.go @@ -39,18 +39,25 @@ type NetDialer interface { DialContext(ctx context.Context, network, address string) (net.Conn, error) } -// Closer defines the interface to a type, typically -// a net.Conn, that can be closed. +// Closer defines the interface to a type, typically a net.Conn, that can be +// closed. type Closer interface { IsClosed() bool } -// CloseWriter defines the interface to a type, typically -// a net.TCPConn, that implements CloseWrite. +// CloseWriter defines the interface to a type, typically a net.TCPConn, that +// implements CloseWrite. type CloseWriter interface { CloseWrite() error } +// IrregularIndicator defines the interface for a type, typically a net.Conn, +// that detects and reports irregular conditions during initial network +// connection establishment. +type IrregularIndicator interface { + IrregularTunnelError() error +} + // TerminateHTTPConnection sends a 404 response to a client and also closes // the persistent connection. func TerminateHTTPConnection( diff --git a/psiphon/common/obfuscator/obfuscator.go b/psiphon/common/obfuscator/obfuscator.go index 8514ae415..c6e709996 100644 --- a/psiphon/common/obfuscator/obfuscator.go +++ b/psiphon/common/obfuscator/obfuscator.go @@ -304,7 +304,7 @@ func readSeedMessage( errStr := "duplicate obfuscation seed" if duplicateLogFields != nil { if config.IrregularLogger != nil { - setIrregularTunnelErrorLogField( + common.SetIrregularTunnelErrorLogField( *duplicateLogFields, errors.BackTraceNew(errBackTrace, errStr)) config.IrregularLogger(clientIP, *duplicateLogFields) } @@ -357,7 +357,7 @@ func readSeedMessage( if errStr != "" { if config.IrregularLogger != nil { errLogFields := make(common.LogFields) - setIrregularTunnelErrorLogField( + common.SetIrregularTunnelErrorLogField( errLogFields, errors.BackTraceNew(errBackTrace, errStr)) config.IrregularLogger(clientIP, errLogFields) } @@ -393,9 +393,3 @@ func readSeedMessage( return clientToServerCipher, serverToClientCipher, paddingPRNGSeed, nil } - -func setIrregularTunnelErrorLogField( - logFields common.LogFields, tunnelError error) { - - logFields["tunnel_error"] = tunnelError -} diff --git a/psiphon/common/tapdance/tapdance.go b/psiphon/common/tapdance/tapdance.go index d61df195a..a890f8d51 100644 --- a/psiphon/common/tapdance/tapdance.go +++ b/psiphon/common/tapdance/tapdance.go @@ -117,8 +117,8 @@ func (l *stationListener) Accept() (net.Conn, error) { return nil, errors.TraceNew("missing station address") } return &stationConn{ - Conn: conn, - stationRemoteAddr: stationRemoteAddr, + Conn: conn, + stationIPAddress: common.IPAddressFromAddr(stationRemoteAddr), }, nil } @@ -132,13 +132,38 @@ func (l *stationListener) Addr() net.Addr { type stationConn struct { net.Conn - stationRemoteAddr net.Addr + stationIPAddress string +} + +// IrregularTunnelError implements the common.IrregularIndicator interface. +func (c *stationConn) IrregularTunnelError() error { + + // We expect a PROXY protocol header, but go-proxyproto does not produce an + // error if the "PROXY " prefix is absent; instead the connection will + // proceed. To detect this case, check if the go-proxyproto RemoteAddr IP + // address matches the underlying connection IP address. When these values + // match, there was no PROXY protocol header. + // + // Limitation: the values will match if there is a PROXY protocol header + // containing the same IP address as the underlying connection. This is not + // an expected case. + + if common.IPAddressFromAddr(c.RemoteAddr()) == c.stationIPAddress { + return errors.TraceNew("unexpected station IP address") + } + return nil } // GetMetrics implements the common.MetricsSource interface. func (c *stationConn) GetMetrics() common.LogFields { + logFields := make(common.LogFields) - logFields["station_ip_address"] = common.IPAddressFromAddr(c.stationRemoteAddr) + + // Ensure we don't log a potential non-station IP address. + if c.IrregularTunnelError() == nil { + logFields["station_ip_address"] = c.stationIPAddress + } + return logFields } diff --git a/psiphon/config.go b/psiphon/config.go index 6ab7fd9da..9905fa033 100755 --- a/psiphon/config.go +++ b/psiphon/config.go @@ -765,7 +765,10 @@ func (config *Config) IsCommitted() bool { // Config fields should not be set after calling Config, as any changes may // not be reflected in internal data structures. // -// File migrations: +// If migrateFromLegacyFields is set to true, then an attempt to migrate from +// legacy fields is made. +// +// Migration from legacy fields: // Config fields of the naming Migrate* (e.g. MigrateDataStoreDirectory) specify // a file migration operation which should be performed. These fields correspond // to deprecated fields, which previously could be used to specify where Psiphon @@ -793,7 +796,7 @@ func (config *Config) IsCommitted() bool { // fails. It is better to not endlessly retry file migrations on each Commit() // because file system errors are expected to be rare and persistent files will // be re-populated over time. -func (config *Config) Commit() error { +func (config *Config) Commit(migrateFromLegacyFields bool) error { // Do SetEmitDiagnosticNotices first, to ensure config file errors are // emitted. @@ -842,26 +845,28 @@ func (config *Config) Commit() error { homepageFilePath := config.GetHomePageFilename() noticesFilePath := config.GetNoticesFilename() - if needMigration { + if migrateFromLegacyFields { + if needMigration { - // Move notice files that exist at legacy file paths under the data root - // directory. + // Move notice files that exist at legacy file paths under the data root + // directory. - noticeMigrationInfoMsgs = append(noticeMigrationInfoMsgs, "Config migration: need migration") - noticeMigrations := migrationsFromLegacyNoticeFilePaths(config) + noticeMigrationInfoMsgs = append(noticeMigrationInfoMsgs, "Config migration: need migration") + noticeMigrations := migrationsFromLegacyNoticeFilePaths(config) - for _, migration := range noticeMigrations { - err := common.DoFileMigration(migration) - if err != nil { - alertMsg := fmt.Sprintf("Config migration: %s", errors.Trace(err)) - noticeMigrationAlertMsgs = append(noticeMigrationAlertMsgs, alertMsg) - } else { - infoMsg := fmt.Sprintf("Config migration: moved %s to %s", migration.OldPath, migration.NewPath) - noticeMigrationInfoMsgs = append(noticeMigrationInfoMsgs, infoMsg) + for _, migration := range noticeMigrations { + err := common.DoFileMigration(migration) + if err != nil { + alertMsg := fmt.Sprintf("Config migration: %s", errors.Trace(err)) + noticeMigrationAlertMsgs = append(noticeMigrationAlertMsgs, alertMsg) + } else { + infoMsg := fmt.Sprintf("Config migration: moved %s to %s", migration.OldPath, migration.NewPath) + noticeMigrationInfoMsgs = append(noticeMigrationInfoMsgs, infoMsg) + } } + } else { + noticeMigrationInfoMsgs = append(noticeMigrationInfoMsgs, "Config migration: migration already completed") } - } else { - noticeMigrationInfoMsgs = append(noticeMigrationInfoMsgs, "Config migration: migration already completed") } if config.UseNoticeFiles != nil { @@ -1093,19 +1098,18 @@ func (config *Config) Commit() error { // Migrate from old config fields. This results in files being moved under // a config specified data root directory. + if migrateFromLegacyFields && needMigration { - // If unset, set MigrateDataStoreDirectory to the previous default value for - // DataStoreDirectory to ensure that datastore files are migrated. - if config.MigrateDataStoreDirectory == "" { - wd, err := os.Getwd() - if err != nil { - return errors.Trace(err) + // If unset, set MigrateDataStoreDirectory to the previous default value for + // DataStoreDirectory to ensure that datastore files are migrated. + if config.MigrateDataStoreDirectory == "" { + wd, err := os.Getwd() + if err != nil { + return errors.Trace(err) + } + NoticeInfo("MigrateDataStoreDirectory unset, using working directory %s", wd) + config.MigrateDataStoreDirectory = wd } - NoticeInfo("MigrateDataStoreDirectory unset, using working directory %s", wd) - config.MigrateDataStoreDirectory = wd - } - - if needMigration { // Move files that exist at legacy file paths under the data root // directory. diff --git a/psiphon/config_test.go b/psiphon/config_test.go index 5ecaa29a7..01974dfb4 100644 --- a/psiphon/config_test.go +++ b/psiphon/config_test.go @@ -104,7 +104,7 @@ func TestConfigTestSuite(t *testing.T) { func (suite *ConfigTestSuite) Test_LoadConfig_BasicGood() { config, err := LoadConfig(suite.confStubBlob) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.Nil(err, "a basic config should succeed") } @@ -129,7 +129,7 @@ func (suite *ConfigTestSuite) Test_LoadConfig_BadJson() { `{"f1": 11, "f2": "two", "DataRootDirectory" : %s}`, suite.testDirectory))) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.NotNil(err, "JSON with none of our fields should fail") @@ -141,7 +141,7 @@ func (suite *ConfigTestSuite) Test_LoadConfig_BadJson() { testObjJSON, _ = json.Marshal(testObj) config, err = LoadConfig(testObjJSON) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.NotNil(err, "JSON with one of our required fields missing should fail: %s", field) @@ -151,7 +151,7 @@ func (suite *ConfigTestSuite) Test_LoadConfig_BadJson() { testObjJSON, _ = json.Marshal(testObj) config, err = LoadConfig(testObjJSON) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.NotNil(err, "JSON with one of our required fields with the wrong type should fail: %s", field) @@ -161,7 +161,7 @@ func (suite *ConfigTestSuite) Test_LoadConfig_BadJson() { testObjJSON, _ = json.Marshal(testObj) config, err = LoadConfig(testObjJSON) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.NotNil(err, "JSON with one of our required fields set to null should fail: %s", field) @@ -171,7 +171,7 @@ func (suite *ConfigTestSuite) Test_LoadConfig_BadJson() { testObjJSON, _ = json.Marshal(testObj) config, err = LoadConfig(testObjJSON) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.NotNil(err, "JSON with one of our required fields set to an empty string should fail: %s", field) } @@ -184,7 +184,7 @@ func (suite *ConfigTestSuite) Test_LoadConfig_BadJson() { testObjJSON, _ = json.Marshal(testObj) config, err = LoadConfig(testObjJSON) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.NotNil(err, "JSON with one of our optional fields with the wrong type should fail: %s", field) } @@ -205,14 +205,14 @@ func (suite *ConfigTestSuite) Test_LoadConfig_GoodJson() { testObjJSON, _ = json.Marshal(testObj) config, err := LoadConfig(testObjJSON) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.Nil(err, "JSON with good values for our required fields but no optional fields should succeed") // Has all of our required fields, and all optional fields config, err = LoadConfig(suite.confStubBlob) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.Nil(err, "JSON with all good values for required and optional fields should succeed") @@ -224,7 +224,7 @@ func (suite *ConfigTestSuite) Test_LoadConfig_GoodJson() { testObjJSON, _ = json.Marshal(testObj) config, err = LoadConfig(testObjJSON) if err == nil { - err = config.Commit() + err = config.Commit(false) } suite.Nil(err, "JSON with null for optional values should succeed") } @@ -402,7 +402,7 @@ func LoadConfigMigrateTest(oslDirChildrenPreMigration []FileTree, oslDirChildren } // Commit config, this is where file migration happens - err = config.Commit() + err = config.Commit(true) if err != nil { suite.T().Fatal("Error committing config:", err) return diff --git a/psiphon/controller_test.go b/psiphon/controller_test.go index 8f762753e..26c2fdf9a 100644 --- a/psiphon/controller_test.go +++ b/psiphon/controller_test.go @@ -561,7 +561,7 @@ func controllerRun(t *testing.T, runConfig *controllerRunConfig) { } // All config fields should be set before calling Commit. - err = config.Commit() + err = config.Commit(false) if err != nil { t.Fatalf("error committing configuration file: %s", err) } diff --git a/psiphon/dataStoreRecovery_test.go b/psiphon/dataStoreRecovery_test.go index b8a36a089..7231a511d 100644 --- a/psiphon/dataStoreRecovery_test.go +++ b/psiphon/dataStoreRecovery_test.go @@ -66,7 +66,7 @@ func TestBoltResiliency(t *testing.T) { if err != nil { t.Fatalf("LoadConfig failed: %s", err) } - err = clientConfig.Commit() + err = clientConfig.Commit(false) if err != nil { t.Fatalf("Commit failed: %s", err) } diff --git a/psiphon/dialParameters_test.go b/psiphon/dialParameters_test.go index 27570bfd3..f9ab87364 100644 --- a/psiphon/dialParameters_test.go +++ b/psiphon/dialParameters_test.go @@ -71,7 +71,7 @@ func runDialParametersAndReplay(t *testing.T, tunnelProtocol string) { NetworkIDGetter: new(testNetworkGetter), } - err = clientConfig.Commit() + err = clientConfig.Commit(false) if err != nil { t.Fatalf("error committing configuration file: %s", err) } diff --git a/psiphon/exchange_test.go b/psiphon/exchange_test.go index 36fc8641d..bc6bd501f 100644 --- a/psiphon/exchange_test.go +++ b/psiphon/exchange_test.go @@ -82,7 +82,7 @@ func TestServerEntryExchange(t *testing.T) { if err != nil { t.Fatalf("LoadConfig failed: %s", err) } - err = config.Commit() + err = config.Commit(false) if err != nil { t.Fatalf("Commit failed: %s", err) } diff --git a/psiphon/feedback.go b/psiphon/feedback.go index c70dbd1f4..9d25edbed 100644 --- a/psiphon/feedback.go +++ b/psiphon/feedback.go @@ -107,7 +107,7 @@ func SendFeedback(configJson, diagnosticsJson, b64EncodedPublicKey, uploadServer if err != nil { return errors.Trace(err) } - err = config.Commit() + err = config.Commit(true) if err != nil { return errors.Trace(err) } diff --git a/psiphon/limitProtocols_test.go b/psiphon/limitProtocols_test.go index bd74660b9..fb943b733 100644 --- a/psiphon/limitProtocols_test.go +++ b/psiphon/limitProtocols_test.go @@ -109,7 +109,7 @@ func TestLimitTunnelProtocols(t *testing.T) { clientConfig.DataRootDirectory = testDataDirName - err = clientConfig.Commit() + err = clientConfig.Commit(false) if err != nil { t.Fatalf("error committing configuration file: %s", err) } diff --git a/psiphon/memory_test/memory_test.go b/psiphon/memory_test/memory_test.go index 57df4644b..670bd0da2 100644 --- a/psiphon/memory_test/memory_test.go +++ b/psiphon/memory_test/memory_test.go @@ -115,7 +115,7 @@ func runMemoryTest(t *testing.T, testMode int) { if err != nil { t.Fatalf("error processing configuration file: %s", err) } - err = config.Commit() + err = config.Commit(false) if err != nil { t.Fatalf("error committing configuration file: %s", err) } diff --git a/psiphon/remoteServerList_test.go b/psiphon/remoteServerList_test.go index 5bb7ab0c2..45cfe27fe 100644 --- a/psiphon/remoteServerList_test.go +++ b/psiphon/remoteServerList_test.go @@ -204,7 +204,7 @@ func testObfuscatedRemoteServerLists(t *testing.T, omitMD5Sums bool) { DataRootDirectory: testDataDirName, PropagationChannelId: "0", SponsorId: "0"} - err = config.Commit() + err = config.Commit(false) if err != nil { t.Fatalf("Error initializing config: %s", err) } @@ -403,7 +403,7 @@ func testObfuscatedRemoteServerLists(t *testing.T, omitMD5Sums bool) { if err != nil { t.Fatalf("error processing configuration file: %s", err) } - err = clientConfig.Commit() + err = clientConfig.Commit(false) if err != nil { t.Fatalf("error committing configuration file: %s", err) } diff --git a/psiphon/server/server_test.go b/psiphon/server/server_test.go index be9752f62..c0e443236 100644 --- a/psiphon/server/server_test.go +++ b/psiphon/server/server_test.go @@ -853,7 +853,7 @@ func runServer(t *testing.T, runConfig *runServerConfig) { clientConfig.Authorizations = []string{clientAuthorization} } - err = clientConfig.Commit() + err = clientConfig.Commit(false) if err != nil { t.Fatalf("error committing configuration file: %s", err) } @@ -2023,7 +2023,7 @@ func storePruneServerEntriesTest( // working directory. DataRootDirectory: testDataDirName, } - err := clientConfig.Commit() + err := clientConfig.Commit(false) if err != nil { t.Fatalf("Commit failed: %s", err) } @@ -2100,7 +2100,7 @@ func checkPruneServerEntriesTest( // working directory. DataRootDirectory: testDataDirName, } - err := clientConfig.Commit() + err := clientConfig.Commit(false) if err != nil { t.Fatalf("Commit failed: %s", err) } diff --git a/psiphon/server/sessionID_test.go b/psiphon/server/sessionID_test.go index 46d6d509c..df97fecb0 100644 --- a/psiphon/server/sessionID_test.go +++ b/psiphon/server/sessionID_test.go @@ -139,7 +139,7 @@ func TestDuplicateSessionID(t *testing.T) { if err != nil { t.Fatalf("LoadConfig failed: %s", err) } - err = clientConfig.Commit() + err = clientConfig.Commit(false) if err != nil { t.Fatalf("Commit failed: %s", err) } diff --git a/psiphon/server/tunnelServer.go b/psiphon/server/tunnelServer.go index 8a5e71b9b..aaece5a4a 100644 --- a/psiphon/server/tunnelServer.go +++ b/psiphon/server/tunnelServer.go @@ -961,8 +961,47 @@ func (sshServer *sshServer) handleClient( // Calling clientConn.RemoteAddr at this point, before any Read calls, // satisfies the constraint documented in tapdance.Listen. + clientAddr := clientConn.RemoteAddr() + + // Check if there were irregularities during the network connection + // establishment. When present, log and then behave as Obfuscated SSH does + // when the client fails to provide a valid seed message. + // + // One concrete irregular case is failure to send a PROXY protocol header for + // TAPDANCE-OSSH. + + if indicator, ok := clientConn.(common.IrregularIndicator); ok { + + tunnelErr := indicator.IrregularTunnelError() + + if tunnelErr != nil { + + logFields := make(common.LogFields) + common.SetIrregularTunnelErrorLogField( + logFields, errors.Trace(tunnelErr)) + logIrregularTunnel( + sshServer.support, + listenerTunnelProtocol, + listenerPort, + common.IPAddressFromAddr(clientAddr), + LogFields(logFields)) + + var afterFunc *time.Timer + if sshServer.support.Config.sshHandshakeTimeout > 0 { + afterFunc = time.AfterFunc(sshServer.support.Config.sshHandshakeTimeout, func() { + clientConn.Close() + }) + } + io.Copy(ioutil.Discard, clientConn) + clientConn.Close() + afterFunc.Stop() + + return + } + } + geoIPData := sshServer.support.GeoIPService.Lookup( - common.IPAddressFromAddr(clientConn.RemoteAddr())) + common.IPAddressFromAddr(clientAddr)) sshServer.registerAcceptedClient(tunnelProtocol, geoIPData.Country) defer sshServer.unregisterAcceptedClient(tunnelProtocol, geoIPData.Country) diff --git a/psiphon/userAgent_test.go b/psiphon/userAgent_test.go index 3fb72f553..21354c684 100644 --- a/psiphon/userAgent_test.go +++ b/psiphon/userAgent_test.go @@ -209,7 +209,7 @@ func attemptConnectionsWithUserAgent( clientConfig.TunnelProtocol = tunnelProtocol clientConfig.DataRootDirectory = testDataDirName - err = clientConfig.Commit() + err = clientConfig.Commit(false) if err != nil { t.Fatalf("error committing configuration file: %s", err) } diff --git a/vendor/github.com/marten-seemann/chacha20/README.md b/vendor/github.com/Psiphon-Labs/chacha20/README.md similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/README.md rename to vendor/github.com/Psiphon-Labs/chacha20/README.md diff --git a/vendor/github.com/marten-seemann/chacha20/asm_arm64.s b/vendor/github.com/Psiphon-Labs/chacha20/asm_arm64.s similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/asm_arm64.s rename to vendor/github.com/Psiphon-Labs/chacha20/asm_arm64.s diff --git a/vendor/github.com/marten-seemann/chacha20/asm_ppc64le.s b/vendor/github.com/Psiphon-Labs/chacha20/asm_ppc64le.s similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/asm_ppc64le.s rename to vendor/github.com/Psiphon-Labs/chacha20/asm_ppc64le.s diff --git a/vendor/github.com/marten-seemann/chacha20/chacha_arm64.go b/vendor/github.com/Psiphon-Labs/chacha20/chacha_arm64.go similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/chacha_arm64.go rename to vendor/github.com/Psiphon-Labs/chacha20/chacha_arm64.go diff --git a/vendor/github.com/marten-seemann/chacha20/chacha_generic.go b/vendor/github.com/Psiphon-Labs/chacha20/chacha_generic.go similarity index 85% rename from vendor/github.com/marten-seemann/chacha20/chacha_generic.go rename to vendor/github.com/Psiphon-Labs/chacha20/chacha_generic.go index 8e04f30ec..612a773e6 100644 --- a/vendor/github.com/marten-seemann/chacha20/chacha_generic.go +++ b/vendor/github.com/Psiphon-Labs/chacha20/chacha_generic.go @@ -10,7 +10,7 @@ import ( "crypto/cipher" "encoding/binary" - "github.com/marten-seemann/chacha20/internal/subtle" + "github.com/Psiphon-Labs/chacha20/internal/subtle" ) // assert that *Cipher implements cipher.Stream @@ -19,11 +19,12 @@ var _ cipher.Stream = (*Cipher)(nil) // Cipher is a stateful instance of ChaCha20 using a particular key // and nonce. A *Cipher implements the cipher.Stream interface. type Cipher struct { - key [8]uint32 - counter uint32 // incremented after each block - nonce [3]uint32 - buf [bufSize]byte // buffer for unused keystream bytes - len int // number of unused keystream bytes at end of buf + key [8]uint32 + counter uint32 // incremented after each block + overflow bool + nonce [3]uint32 + buf [bufSize]byte // buffer for unused keystream bytes + len int // number of unused keystream bytes at end of buf } // New creates a new ChaCha20 stream cipher with the given key and nonce. @@ -97,7 +98,12 @@ func (s *Cipher) XORKeyStream(dst, src []byte) { return } if haveAsm { - if uint64(len(src))+uint64(s.counter)*64 > (1<<38)-64 { + + // [Psiphon] + // + // Allow up to 2^32 blocks. + + if uint64(len(src))+uint64(s.counter)*64 > (1 << 38) { panic("chacha20: counter overflow") } s.xorKeyStreamAsm(dst, src) @@ -120,6 +126,11 @@ func (s *Cipher) XORKeyStream(dst, src []byte) { n := len(src) src, dst = src[:n:n], dst[:n:n] // BCE hint for i := 0; i < n; i += 64 { + + if s.overflow { + panic("chacha20: counter overflow") + } + // calculate the remainder of the first round s0, s4, s8, s12 := quarterRound(j0, s.key[0], s.key[4], s.counter) @@ -164,7 +175,25 @@ func (s *Cipher) XORKeyStream(dst, src []byte) { // increment the counter s.counter += 1 if s.counter == 0 { - panic("chacha20: counter overflow") + + // [Psiphon] + // + // Don't panic immediately, as the counter will wrap here when it's 2^31-1, + // and that's a valid value. Do panic after overflow is set and any further + // blocks are processed. + // + // https://tools.ietf.org/html/rfc7539#section-2.3.2: ChaCha20 "limits the + // use of a single (key,nonce) combination to 2^32 blocks". + // + // The 2^31-1 counter value occurs in practise in QUIC header protection, + // https://tools.ietf.org/html/draft-ietf-quic-tls-24#section-5.4.4, which + // initializes the counter using 4 bytes of sampled ciphertext. + // + // In OpenSSL, chacha20 will operate on 2^32 blocks before applying its + // overflow logic: + // https://github.com/openssl/openssl/blob/706457b7bda7fdbab426b8dce83b318908339da4/crypto/evp/e_chacha20_poly1305.c#L94-L104. + + s.overflow = true } // pad to 64 bytes if needed diff --git a/vendor/github.com/marten-seemann/chacha20/chacha_noasm.go b/vendor/github.com/Psiphon-Labs/chacha20/chacha_noasm.go similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/chacha_noasm.go rename to vendor/github.com/Psiphon-Labs/chacha20/chacha_noasm.go diff --git a/vendor/github.com/marten-seemann/chacha20/chacha_ppc64le.go b/vendor/github.com/Psiphon-Labs/chacha20/chacha_ppc64le.go similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/chacha_ppc64le.go rename to vendor/github.com/Psiphon-Labs/chacha20/chacha_ppc64le.go diff --git a/vendor/github.com/marten-seemann/chacha20/chacha_s390x.go b/vendor/github.com/Psiphon-Labs/chacha20/chacha_s390x.go similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/chacha_s390x.go rename to vendor/github.com/Psiphon-Labs/chacha20/chacha_s390x.go diff --git a/vendor/github.com/marten-seemann/chacha20/chacha_s390x.s b/vendor/github.com/Psiphon-Labs/chacha20/chacha_s390x.s similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/chacha_s390x.s rename to vendor/github.com/Psiphon-Labs/chacha20/chacha_s390x.s diff --git a/vendor/github.com/marten-seemann/chacha20/internal/subtle/aliasing.go b/vendor/github.com/Psiphon-Labs/chacha20/internal/subtle/aliasing.go similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/internal/subtle/aliasing.go rename to vendor/github.com/Psiphon-Labs/chacha20/internal/subtle/aliasing.go diff --git a/vendor/github.com/marten-seemann/chacha20/internal/subtle/aliasing_appengine.go b/vendor/github.com/Psiphon-Labs/chacha20/internal/subtle/aliasing_appengine.go similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/internal/subtle/aliasing_appengine.go rename to vendor/github.com/Psiphon-Labs/chacha20/internal/subtle/aliasing_appengine.go diff --git a/vendor/github.com/marten-seemann/chacha20/xor.go b/vendor/github.com/Psiphon-Labs/chacha20/xor.go similarity index 100% rename from vendor/github.com/marten-seemann/chacha20/xor.go rename to vendor/github.com/Psiphon-Labs/chacha20/xor.go diff --git a/vendor/github.com/Psiphon-Labs/quic-go/internal/handshake/header_protector.go b/vendor/github.com/Psiphon-Labs/quic-go/internal/handshake/header_protector.go index 019d57036..77b33f20a 100644 --- a/vendor/github.com/Psiphon-Labs/quic-go/internal/handshake/header_protector.go +++ b/vendor/github.com/Psiphon-Labs/quic-go/internal/handshake/header_protector.go @@ -5,7 +5,7 @@ import ( "crypto/cipher" "fmt" - "github.com/marten-seemann/chacha20" + "github.com/Psiphon-Labs/chacha20" "github.com/marten-seemann/qtls" ) diff --git a/vendor/vendor.json b/vendor/vendor.json index 97f17f36c..5dd5638eb 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -20,6 +20,18 @@ "revision": "94750aa2185e6ee4217105064949acace0156564", "revisionTime": "2019-07-31T17:17:12Z" }, + { + "checksumSHA1": "C5OwxfDa6nvLoxP3WBaCp7ufW60=", + "path": "github.com/Psiphon-Labs/chacha20", + "revision": "899a4be528633ecf678f45e4f6b177d0f89b9e7c", + "revisionTime": "2020-01-28T19:13:10Z" + }, + { + "checksumSHA1": "zNTA9RmD/BcIWRfZWF/DIhULpK0=", + "path": "github.com/Psiphon-Labs/chacha20/internal/subtle", + "revision": "899a4be528633ecf678f45e4f6b177d0f89b9e7c", + "revisionTime": "2020-01-28T19:13:10Z" + }, { "checksumSHA1": "d3DwsdacdFn1/KCG/2uPV1PwR3s=", "path": "github.com/Psiphon-Labs/dns", @@ -65,8 +77,8 @@ { "checksumSHA1": "8MdwAjQlha5clFXwY1ayF4vNGAQ=", "path": "github.com/Psiphon-Labs/quic-go", - "revision": "abf539ac596a6017b6eb8904f7342da8daab8df1", - "revisionTime": "2020-01-16T02:28:06Z" + "revision": "738e15bfe6c3d7a0ccc91e2f237e5554ab6a35a6", + "revisionTime": "2020-01-28T19:39:28Z" }, { "checksumSHA1": "VMJLFpeoJ56PTQxR0wEkkiQTr1s=", @@ -340,22 +352,6 @@ "revision": "ae77be60afb1dcacde03767a8c37337fad28ac14", "revisionTime": "2017-05-10T13:15:34Z" }, - { - "checksumSHA1": "j4eMhpVKh7QbPBE/vZL+VxQwJT0=", - "path": "github.com/marten-seemann/chacha20", - "revision": "36564989294fee5f3957d3e3fbfc655e10786ec0", - "revisionTime": "2019-09-06T10:21:14Z", - "version": "v0.2.0", - "versionExact": "v0.2.0" - }, - { - "checksumSHA1": "xJ/ZPgaoP3Gd5ETWGhqufsqptuw=", - "path": "github.com/marten-seemann/chacha20/internal/subtle", - "revision": "36564989294fee5f3957d3e3fbfc655e10786ec0", - "revisionTime": "2019-09-06T10:21:14Z", - "version": "v0.2.0", - "versionExact": "v0.2.0" - }, { "checksumSHA1": "Urc++6mqm/jcr3SSL/MMN5v7Owk=", "path": "github.com/marten-seemann/qpack",