예제 #1
0
// Reload [re]initializes the TrafficRulesSet with the rules data
// in the specified file. This function obtains a write lock on
// the database, blocking all readers. When Reload fails, the previous
// state is retained.
func (set *TrafficRulesSet) Reload(ruleSetFilename string) error {
	set.Lock()
	defer set.Unlock()

	if ruleSetFilename == "" {
		// No traffic rules filename in the config
		return nil
	}

	configJSON, err := ioutil.ReadFile(ruleSetFilename)
	if err != nil {
		return psiphon.ContextError(err)
	}

	var newSet TrafficRulesSet
	err = json.Unmarshal(configJSON, &newSet)
	if err != nil {
		return psiphon.ContextError(err)
	}

	set.DefaultRules = newSet.DefaultRules
	set.RegionalRules = newSet.RegionalRules

	return nil
}
예제 #2
0
// convertHTTPRequestToAPIRequest converts the HTTP request query
// parameters and request body to the JSON object import format
// expected by the API request handlers.
func convertHTTPRequestToAPIRequest(
	w http.ResponseWriter,
	r *http.Request,
	requestBodyName string) (requestJSONObject, error) {

	params := make(requestJSONObject)

	for name, values := range r.URL.Query() {
		for _, value := range values {
			params[name] = value
			// Note: multiple values per name are ignored
			break
		}
	}

	if requestBodyName != "" {
		r.Body = http.MaxBytesReader(w, r.Body, MAX_API_PARAMS_SIZE)
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			return nil, psiphon.ContextError(err)
		}
		var bodyParams requestJSONObject
		err = json.Unmarshal(body, &bodyParams)
		if err != nil {
			return nil, psiphon.ContextError(err)
		}
		params[requestBodyName] = bodyParams
	}

	return params, nil
}
예제 #3
0
// UpdateRedisForLegacyPsiWeb sets the Psiphon session and discovery records for
// a new SSH connection following the conventions of the legacy psi_web component.
// This facility is used so psi_web can use the GeoIP values the SSH server has
// resolved for the user connection.
// The redis database indexes, expiry values, and record schemas all match the
// legacy psi_web configuration.
func UpdateRedisForLegacyPsiWeb(psiphonSessionID string, geoIPData GeoIPData) error {

	redisSessionDBIndex := 0

	//  Discard sessions older than 60 minutes
	sessionExpireSeconds := 60 * 60

	sessionRecord, err := json.Marshal(
		struct {
			Country string `json:"region"`
			City    string `json:"city"`
			ISP     string `json:"isp"`
		}{geoIPData.Country, geoIPData.City, geoIPData.ISP})
	if err != nil {
		return psiphon.ContextError(err)
	}

	redisDiscoveryDBIndex := 1

	// Discard discovery records older than 5 minutes
	discoveryExpireSeconds := 60 * 5

	discoveryRecord, err := json.Marshal(
		struct {
			DiscoveryValue int `json:"client_ip_address_strategy_value"`
		}{geoIPData.DiscoveryValue})
	if err != nil {
		return psiphon.ContextError(err)
	}

	conn := redisPool.Get()

	// Note: using SET with NX (set if not exists) so as to not clobber
	// any existing records set by an upstream connection server (i.e.,
	// meek server). We allow expiry deadline extension unconditionally.

	conn.Send("MULTI")

	conn.Send("SELECT", redisSessionDBIndex)
	// http://redis.io/commands/set -- NX/EX options require Redis 2.6.12
	//conn.Send("SET", psiphonSessionID, string(sessionRecord), "NX", "EX", sessionExpireSeconds)
	conn.Send("SETNX", psiphonSessionID, string(sessionRecord))
	conn.Send("EXPIRE", psiphonSessionID, sessionExpireSeconds)

	conn.Send("SELECT", redisDiscoveryDBIndex)
	//conn.Send("SET", psiphonSessionID, string(discoveryRecord), "NX", "EX", discoveryExpireSeconds)
	conn.Send("SETNX", psiphonSessionID, string(discoveryRecord))
	conn.Send("EXPIRE", psiphonSessionID, discoveryExpireSeconds)

	_, err = conn.Do("EXEC")
	if err != nil {
		return psiphon.ContextError(err)
	}

	return nil
}
예제 #4
0
// makeMeekSessionID creates a new session ID. The variable size is intended to
// frustrate traffic analysis of both plaintext and TLS meek traffic.
func makeMeekSessionID() (string, error) {
	size := MEEK_MIN_SESSION_ID_LENGTH
	n, err := psiphon.MakeSecureRandomInt(MEEK_MAX_SESSION_ID_LENGTH - MEEK_MIN_SESSION_ID_LENGTH)
	if err != nil {
		return "", psiphon.ContextError(err)
	}
	size += n
	sessionID, err := psiphon.MakeRandomStringBase64(size)
	if err != nil {
		return "", psiphon.ContextError(err)
	}
	return sessionID, nil
}
예제 #5
0
func (sshClient *sshClient) passwordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {

	var sshPasswordPayload struct {
		SessionId   string `json:"SessionId"`
		SshPassword string `json:"SshPassword"`
	}
	err := json.Unmarshal(password, &sshPasswordPayload)
	if err != nil {

		// Backwards compatibility case: instead of a JSON payload, older clients
		// send the hex encoded session ID prepended to the SSH password.
		// Note: there's an even older case where clients don't send any session ID,
		// but that's no longer supported.
		if len(password) == 2*psiphon.PSIPHON_API_CLIENT_SESSION_ID_LENGTH+2*SSH_PASSWORD_BYTE_LENGTH {
			sshPasswordPayload.SessionId = string(password[0 : 2*psiphon.PSIPHON_API_CLIENT_SESSION_ID_LENGTH])
			sshPasswordPayload.SshPassword = string(password[2*psiphon.PSIPHON_API_CLIENT_SESSION_ID_LENGTH : len(password)])
		} else {
			return nil, psiphon.ContextError(fmt.Errorf("invalid password payload for %q", conn.User()))
		}
	}

	if !isHexDigits(sshClient.sshServer.support, sshPasswordPayload.SessionId) {
		return nil, psiphon.ContextError(fmt.Errorf("invalid session ID for %q", conn.User()))
	}

	userOk := (subtle.ConstantTimeCompare(
		[]byte(conn.User()), []byte(sshClient.sshServer.support.Config.SSHUserName)) == 1)

	passwordOk := (subtle.ConstantTimeCompare(
		[]byte(sshPasswordPayload.SshPassword), []byte(sshClient.sshServer.support.Config.SSHPassword)) == 1)

	if !userOk || !passwordOk {
		return nil, psiphon.ContextError(fmt.Errorf("invalid password for %q", conn.User()))
	}

	psiphonSessionID := sshPasswordPayload.SessionId

	sshClient.Lock()
	sshClient.psiphonSessionID = psiphonSessionID
	geoIPData := sshClient.geoIPData
	sshClient.Unlock()

	// Store the GeoIP data associated with the session ID. This makes the GeoIP data
	// available to the web server for web transport Psiphon API requests.
	sshClient.sshServer.support.GeoIPService.SetSessionCache(
		psiphonSessionID, geoIPData)

	return nil, nil
}
예제 #6
0
// NewMeekServer initializes a new meek server.
func NewMeekServer(
	support *SupportServices,
	listener net.Listener,
	useTLS bool,
	clientHandler func(clientConn net.Conn),
	stopBroadcast <-chan struct{}) (*MeekServer, error) {

	meekServer := &MeekServer{
		support:       support,
		listener:      listener,
		clientHandler: clientHandler,
		openConns:     new(psiphon.Conns),
		stopBroadcast: stopBroadcast,
		sessions:      make(map[string]*meekSession),
	}

	if useTLS {
		tlsConfig, err := makeMeekTLSConfig(support)
		if err != nil {
			return nil, psiphon.ContextError(err)
		}
		meekServer.tlsConfig = tlsConfig
	}

	return meekServer, nil
}
예제 #7
0
// makeMeekTLSConfig creates a TLS config for a meek HTTPS listener.
// Currently, this config is optimized for fronted meek where the nature
// of the connection is non-circumvention; it's optimized for performance
// assuming the peer is an uncensored CDN.
func makeMeekTLSConfig(support *SupportServices) (*tls.Config, error) {

	certificate, privateKey, err := GenerateWebServerCertificate(
		support.Config.MeekCertificateCommonName)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	tlsCertificate, err := tls.X509KeyPair(
		[]byte(certificate), []byte(privateKey))
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	return &tls.Config{
		Certificates: []tls.Certificate{tlsCertificate},
		NextProtos:   []string{"http/1.1"},
		MinVersion:   tls.VersionTLS10,

		// This is a reordering of the supported CipherSuites in golang 1.6. Non-ephemeral key
		// CipherSuites greatly reduce server load, and we try to select these since the meek
		// protocol is providing obfuscation, not privacy/integrity (this is provided by the
		// tunneled SSH), so we don't benefit from the perfect forward secrecy property provided
		// by ephemeral key CipherSuites.
		// https://github.com/golang/go/blob/1cb3044c9fcd88e1557eca1bf35845a4108bc1db/src/crypto/tls/cipher_suites.go#L75
		CipherSuites: []uint16{
			tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_RSA_WITH_RC4_128_SHA,
			tls.TLS_RSA_WITH_AES_128_CBC_SHA,
			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
			tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
			tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
			tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
		},
		PreferServerCipherSuites: true,
	}, nil
}
예제 #8
0
// InitLogging configures a logger according to the specified
// config params. If not called, the default logger set by the
// package init() is used.
// When configured, InitLogging also establishes a local syslog
// logger specifically for fail2ban integration.
// Concurrenty note: should only be called from the main
// goroutine.
func InitLogging(config *Config) error {

	level, err := logrus.ParseLevel(config.LogLevel)
	if err != nil {
		return psiphon.ContextError(err)
	}

	hooks := make(logrus.LevelHooks)

	var syslogHook *logrus_syslog.SyslogHook

	if config.SyslogFacility != "" {

		syslogHook, err = logrus_syslog.NewSyslogHook(
			"", "", getSyslogPriority(config), config.SyslogTag)
		if err != nil {
			return psiphon.ContextError(err)
		}

		hooks.Add(syslogHook)
	}

	log = &ContextLogger{
		&logrus.Logger{
			Out:       os.Stderr,
			Formatter: new(logrus.TextFormatter),
			Hooks:     hooks,
			Level:     level,
		},
	}

	if config.Fail2BanFormat != "" {
		fail2BanFormat = config.Fail2BanFormat
		fail2BanWriter, err = syslog.Dial(
			"", "", syslog.LOG_AUTH|syslog.LOG_INFO, config.SyslogTag)
		if err != nil {
			return psiphon.ContextError(err)
		}
	}

	return nil
}
예제 #9
0
func (sshClient *sshClient) passwordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
	var sshPasswordPayload struct {
		SessionId   string `json:"SessionId"`
		SshPassword string `json:"SshPassword"`
	}
	err := json.Unmarshal(password, &sshPasswordPayload)
	if err != nil {
		return nil, psiphon.ContextError(fmt.Errorf("invalid password payload for %q", conn.User()))
	}

	userOk := (subtle.ConstantTimeCompare(
		[]byte(conn.User()), []byte(sshClient.sshServer.config.SSHUserName)) == 1)

	passwordOk := (subtle.ConstantTimeCompare(
		[]byte(sshPasswordPayload.SshPassword), []byte(sshClient.sshServer.config.SSHPassword)) == 1)

	if !userOk || !passwordOk {
		return nil, psiphon.ContextError(fmt.Errorf("invalid password for %q", conn.User()))
	}

	psiphonSessionID := sshPasswordPayload.SessionId

	sshClient.Lock()
	sshClient.psiphonSessionID = psiphonSessionID
	geoIPData := sshClient.geoIPData
	sshClient.Unlock()

	if sshClient.sshServer.config.UseRedis() {
		err = UpdateRedisForLegacyPsiWeb(psiphonSessionID, geoIPData)
		if err != nil {
			log.WithContextFields(LogFields{
				"psiphonSessionID": psiphonSessionID,
				"error":            err}).Warning("UpdateRedisForLegacyPsiWeb failed")
			// Allow the connection to proceed; legacy psi_web will not get accurate GeoIP values.
		}
	}

	return nil, nil
}
예제 #10
0
// InitGeoIP opens a GeoIP2/GeoLite2 MaxMind database and prepares
// it for lookups.
func InitGeoIP(config *Config) error {

	discoveryValueHMACKey = config.DiscoveryValueHMACKey

	if config.GeoIPDatabaseFilename != "" {
		var err error
		geoIPReader, err = maxminddb.Open(config.GeoIPDatabaseFilename)
		if err != nil {
			return psiphon.ContextError(err)
		}
		log.WithContext().Info("GeoIP initialized")
	}

	return nil
}
예제 #11
0
func newSSHServer(
	support *SupportServices,
	shutdownBroadcast <-chan struct{}) (*sshServer, error) {

	privateKey, err := ssh.ParseRawPrivateKey([]byte(support.Config.SSHPrivateKey))
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	// TODO: use cert (ssh.NewCertSigner) for anti-fingerprint?
	signer, err := ssh.NewSignerFromKey(privateKey)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	return &sshServer{
		support:              support,
		shutdownBroadcast:    shutdownBroadcast,
		sshHostKey:           signer,
		nextClientID:         1,
		acceptedClientCounts: make(map[string]int64),
		clients:              make(map[sshClientID]*sshClient),
	}, nil
}
예제 #12
0
// NewSupportServices initializes a new SupportServices.
func NewSupportServices(config *Config) (*SupportServices, error) {
	trafficRulesSet, err := NewTrafficRulesSet(config.TrafficRulesFilename)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	psinetDatabase, err := psinet.NewDatabase(config.PsinetDatabaseFilename)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	geoIPService, err := NewGeoIPService(
		config.GeoIPDatabaseFilename, config.DiscoveryValueHMACKey)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	return &SupportServices{
		Config:          config,
		TrafficRulesSet: trafficRulesSet,
		PsinetDatabase:  psinetDatabase,
		GeoIPService:    geoIPService,
	}, nil
}
예제 #13
0
// NewTunnelServer initializes a new tunnel server.
func NewTunnelServer(
	support *SupportServices,
	shutdownBroadcast <-chan struct{}) (*TunnelServer, error) {

	sshServer, err := newSSHServer(support, shutdownBroadcast)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	return &TunnelServer{
		runWaitGroup:      new(sync.WaitGroup),
		listenerError:     make(chan error),
		shutdownBroadcast: shutdownBroadcast,
		sshServer:         sshServer,
	}, nil
}
예제 #14
0
// LoadConfig loads and validates a JSON encoded server config. If more than one
// JSON config is specified, then all are loaded and values are merged together,
// in order. Multiple configs allows for use cases like storing static, server-specific
// values in a base config while also deploying network-wide throttling settings
// in a secondary file that can be paved over on all server hosts.
func LoadConfig(configJSONs [][]byte) (*Config, error) {

	// Note: default values are set in GenerateConfig
	var config Config

	for _, configJSON := range configJSONs {
		err := json.Unmarshal(configJSON, &config)
		if err != nil {
			return nil, psiphon.ContextError(err)
		}
	}

	if config.Fail2BanFormat != "" && strings.Count(config.Fail2BanFormat, "%s") != 1 {
		return nil, errors.New("Fail2BanFormat must have one '%%s' placeholder")
	}

	if config.ServerIPAddress == "" {
		return nil, errors.New("ServerIPAddress is missing from config file")
	}

	if config.WebServerPort > 0 && (config.WebServerSecret == "" || config.WebServerCertificate == "" ||
		config.WebServerPrivateKey == "") {

		return nil, errors.New(
			"web server requires WebServerSecret, WebServerCertificate, WebServerPrivateKey")
	}

	if config.SSHServerPort > 0 && (config.SSHPrivateKey == "" || config.SSHServerVersion == "" ||
		config.SSHUserName == "" || config.SSHPassword == "") {

		return nil, errors.New(
			"SSH server requires SSHPrivateKey, SSHServerVersion, SSHUserName, SSHPassword")
	}

	if config.ObfuscatedSSHServerPort > 0 && (config.SSHPrivateKey == "" || config.SSHServerVersion == "" ||
		config.SSHUserName == "" || config.SSHPassword == "" || config.ObfuscatedSSHKey == "") {

		return nil, errors.New(
			"Obfuscated SSH server requires SSHPrivateKey, SSHServerVersion, SSHUserName, SSHPassword, ObfuscatedSSHKey")
	}

	return &config, nil
}
예제 #15
0
// getMeekCookiePayload extracts the payload from a meek cookie. The cookie
// paylod is base64 encoded, obfuscated, and NaCl encrypted.
func getMeekCookiePayload(support *SupportServices, cookieValue string) ([]byte, error) {
	decodedValue, err := base64.StdEncoding.DecodeString(cookieValue)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	// The data consists of an obfuscated seed message prepended
	// to the obfuscated, encrypted payload. The server obfuscator
	// will read the seed message, leaving the remaining encrypted
	// data in the reader.

	reader := bytes.NewReader(decodedValue[:])

	obfuscator, err := psiphon.NewServerObfuscator(
		reader,
		&psiphon.ObfuscatorConfig{Keyword: support.Config.MeekObfuscatedKey})
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	offset, err := reader.Seek(0, 1)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}
	encryptedPayload := decodedValue[offset:]

	obfuscator.ObfuscateClientToServer(encryptedPayload)

	var nonce [24]byte
	var privateKey, ephemeralPublicKey [32]byte

	decodedPrivateKey, err := base64.StdEncoding.DecodeString(
		support.Config.MeekCookieEncryptionPrivateKey)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}
	copy(privateKey[:], decodedPrivateKey)

	if len(encryptedPayload) < 32 {
		return nil, psiphon.ContextError(errors.New("unexpected encrypted payload size"))
	}
	copy(ephemeralPublicKey[0:32], encryptedPayload[0:32])

	payload, ok := box.Open(nil, encryptedPayload[32:], &nonce, &ephemeralPublicKey, &privateKey)
	if !ok {
		return nil, psiphon.ContextError(errors.New("open box failed"))
	}

	return payload, nil
}
예제 #16
0
func copyWithThrottle(dst io.Writer, src io.Reader, throttleSleepMilliseconds int) (int64, error) {
	// TODO: use a low-memory io.Copy?
	if throttleSleepMilliseconds <= 0 {
		// No throttle
		return io.Copy(dst, src)
	}
	var totalBytes int64
	for {
		bytes, err := io.CopyN(dst, src, SSH_THROTTLED_PORT_FORWARD_MAX_COPY)
		totalBytes += bytes
		if err == io.EOF {
			err = nil
			break
		}
		if err != nil {
			return totalBytes, psiphon.ContextError(err)
		}
		time.Sleep(time.Duration(throttleSleepMilliseconds) * time.Millisecond)
	}
	return totalBytes, nil
}
예제 #17
0
func generateWebServerCertificate() (string, string, error) {

	// Based on https://golang.org/src/crypto/tls/generate_cert.go

	// TODO: use other key types: anti-fingerprint by varying params

	rsaKey, err := rsa.GenerateKey(rand.Reader, WEB_SERVER_CERTIFICATE_RSA_KEY_BITS)
	if err != nil {
		return "", "", psiphon.ContextError(err)
	}

	notBefore := time.Now()
	notAfter := notBefore.Add(WEB_SERVER_CERTIFICATE_VALIDITY_PERIOD)

	// TODO: psi_ops_install sets serial number to 0?
	// TOSO: psi_ops_install sets RSA exponent to 3, digest type to 'sha1', and version to 2?

	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
	if err != nil {
		return "", "", psiphon.ContextError(err)
	}

	template := x509.Certificate{

		// TODO: psi_ops_install leaves subject blank?
		/*
			Subject: pkix.Name{
				Organization: []string{""},
			},
			IPAddresses: ...
		*/

		SerialNumber:          serialNumber,
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		IsCA: true,
	}

	derCert, err := x509.CreateCertificate(rand.Reader, &template, &template, rsaKey.Public(), rsaKey)
	if err != nil {
		return "", "", psiphon.ContextError(err)
	}

	webServerCertificate := pem.EncodeToMemory(
		&pem.Block{
			Type:  "CERTIFICATE",
			Bytes: derCert,
		},
	)

	webServerPrivateKey := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
		},
	)

	return string(webServerCertificate), string(webServerPrivateKey), nil
}
예제 #18
0
// GenerateConfig create a new Psiphon server config. It returns a JSON
// encoded config and a client-compatible "server entry" for the server. It
// generates all necessary secrets and key material, which are emitted in
// the config file and server entry as necessary.
func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, error) {

	// TODO: support disabling web server or a subset of protocols

	serverIPaddress := params.ServerIPAddress
	if serverIPaddress == "" {
		serverIPaddress = DEFAULT_SERVER_IP_ADDRESS
	}

	// Web server config

	webServerPort := params.WebServerPort
	if webServerPort == 0 {
		webServerPort = DEFAULT_WEB_SERVER_PORT
	}

	webServerSecret, err := psiphon.MakeRandomString(WEB_SERVER_SECRET_BYTE_LENGTH)
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	webServerCertificate, webServerPrivateKey, err := generateWebServerCertificate()
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	// SSH config

	sshServerPort := params.SSHServerPort
	if sshServerPort == 0 {
		sshServerPort = DEFAULT_SSH_SERVER_PORT
	}

	// TODO: use other key types: anti-fingerprint by varying params

	rsaKey, err := rsa.GenerateKey(rand.Reader, SSH_RSA_HOST_KEY_BITS)
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	sshPrivateKey := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
		},
	)

	signer, err := ssh.NewSignerFromKey(rsaKey)
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	sshPublicKey := signer.PublicKey()

	sshUserNameSuffix, err := psiphon.MakeRandomString(SSH_USERNAME_SUFFIX_BYTE_LENGTH)
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	sshUserName := "******" + sshUserNameSuffix

	sshPassword, err := psiphon.MakeRandomString(SSH_PASSWORD_BYTE_LENGTH)
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	// TODO: vary version string for anti-fingerprint
	sshServerVersion := "SSH-2.0-Psiphon"

	// Obfuscated SSH config

	obfuscatedSSHServerPort := params.ObfuscatedSSHServerPort
	if obfuscatedSSHServerPort == 0 {
		obfuscatedSSHServerPort = DEFAULT_OBFUSCATED_SSH_SERVER_PORT
	}

	obfuscatedSSHKey, err := psiphon.MakeRandomString(SSH_OBFUSCATED_KEY_BYTE_LENGTH)
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	// Assemble config and server entry

	config := &Config{
		LogLevel:                DEFAULT_LOG_LEVEL,
		SyslogFacility:          "",
		SyslogTag:               DEFAULT_SYSLOG_TAG,
		Fail2BanFormat:          "",
		DiscoveryValueHMACKey:   "",
		GeoIPDatabaseFilename:   DEFAULT_GEO_IP_DATABASE_FILENAME,
		ServerIPAddress:         serverIPaddress,
		WebServerPort:           webServerPort,
		WebServerSecret:         webServerSecret,
		WebServerCertificate:    webServerCertificate,
		WebServerPrivateKey:     webServerPrivateKey,
		SSHPrivateKey:           string(sshPrivateKey),
		SSHServerVersion:        sshServerVersion,
		SSHUserName:             sshUserName,
		SSHPassword:             sshPassword,
		SSHServerPort:           sshServerPort,
		ObfuscatedSSHKey:        obfuscatedSSHKey,
		ObfuscatedSSHServerPort: obfuscatedSSHServerPort,
		RedisServerAddress:      "",
	}

	encodedConfig, err := json.MarshalIndent(config, "\n", "    ")
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	// Server entry format omits the BEGIN/END lines and newlines
	lines := strings.Split(webServerCertificate, "\n")
	strippedWebServerCertificate := strings.Join(lines[1:len(lines)-2], "")

	capabilities := []string{
		psiphon.GetCapability(psiphon.TUNNEL_PROTOCOL_SSH),
		psiphon.GetCapability(psiphon.TUNNEL_PROTOCOL_OBFUSCATED_SSH),
	}

	serverEntry := &psiphon.ServerEntry{
		IpAddress:            serverIPaddress,
		WebServerPort:        fmt.Sprintf("%d", webServerPort),
		WebServerSecret:      webServerSecret,
		WebServerCertificate: strippedWebServerCertificate,
		SshPort:              sshServerPort,
		SshUsername:          sshUserName,
		SshPassword:          sshPassword,
		SshHostKey:           base64.RawStdEncoding.EncodeToString(sshPublicKey.Marshal()),
		SshObfuscatedPort:    obfuscatedSSHServerPort,
		SshObfuscatedKey:     obfuscatedSSHKey,
		Capabilities:         capabilities,
		Region:               "US",
	}

	encodedServerEntry, err := psiphon.EncodeServerEntry(serverEntry)
	if err != nil {
		return nil, nil, psiphon.ContextError(err)
	}

	return encodedConfig, []byte(encodedServerEntry), nil
}
예제 #19
0
// RunServices initializes support functions including logging, GeoIP service, and
// redis connection pooling; and then starts the server components and runs them
// until os.Interrupt or os.Kill signals are received. The config determines
// which components are run.
func RunServices(encodedConfigs [][]byte) error {

	config, err := LoadConfig(encodedConfigs)
	if err != nil {
		log.WithContextFields(LogFields{"error": err}).Error("load config failed")
		return psiphon.ContextError(err)
	}

	err = InitLogging(config)
	if err != nil {
		log.WithContextFields(LogFields{"error": err}).Error("init logging failed")
		return psiphon.ContextError(err)
	}

	err = InitGeoIP(config)
	if err != nil {
		log.WithContextFields(LogFields{"error": err}).Error("init GeoIP failed")
		return psiphon.ContextError(err)
	}

	if config.UseRedis() {
		err = InitRedis(config)
		if err != nil {
			log.WithContextFields(LogFields{"error": err}).Error("init redis failed")
			return psiphon.ContextError(err)
		}
	}

	waitGroup := new(sync.WaitGroup)
	shutdownBroadcast := make(chan struct{})
	errors := make(chan error)

	if config.RunWebServer() {
		waitGroup.Add(1)
		go func() {
			defer waitGroup.Done()
			err := RunWebServer(config, shutdownBroadcast)
			select {
			case errors <- err:
			default:
			}
		}()
	}

	if config.RunSSHServer() {
		waitGroup.Add(1)
		go func() {
			defer waitGroup.Done()
			err := RunSSHServer(config, shutdownBroadcast)
			select {
			case errors <- err:
			default:
			}
		}()
	}

	if config.RunObfuscatedSSHServer() {
		waitGroup.Add(1)
		go func() {
			defer waitGroup.Done()
			err := RunObfuscatedSSHServer(config, shutdownBroadcast)
			select {
			case errors <- err:
			default:
			}
		}()
	}

	// An OS signal triggers an orderly shutdown
	systemStopSignal := make(chan os.Signal, 1)
	signal.Notify(systemStopSignal, os.Interrupt, os.Kill)

	err = nil

	select {
	case <-systemStopSignal:
		log.WithContext().Info("shutdown by system")
	case err = <-errors:
		log.WithContextFields(LogFields{"error": err}).Error("service failed")
	}

	close(shutdownBroadcast)
	waitGroup.Wait()

	return err
}
예제 #20
0
// runSSHServer runs an SSH or Obfuscated SSH server. In the Obfuscated SSH case, an
// ObfuscatedSSHConn is layered in front of the client TCP connection; otherwise, both
// modes are identical.
//
// runSSHServer listens on the designated port and spawns new goroutines to handle
// each client connection. It halts when shutdownBroadcast is signaled. A list of active
// clients is maintained, and when halting all clients are first shutdown.
//
// Each client goroutine handles its own obfuscation (optional), SSH handshake, SSH
// authentication, and then looping on client new channel requests. At this time, only
// "direct-tcpip" channels, dynamic port fowards, are expected and supported.
//
// A new goroutine is spawned to handle each port forward. Each port forward tracks its
// bytes transferred. Overall per-client stats for connection duration, GeoIP, number of
// port forwards, and bytes transferred are tracked and logged when the client shuts down.
func runSSHServer(
	config *Config, useObfuscation bool, shutdownBroadcast <-chan struct{}) error {

	privateKey, err := ssh.ParseRawPrivateKey([]byte(config.SSHPrivateKey))
	if err != nil {
		return psiphon.ContextError(err)
	}

	// TODO: use cert (ssh.NewCertSigner) for anti-fingerprint?
	signer, err := ssh.NewSignerFromKey(privateKey)
	if err != nil {
		return psiphon.ContextError(err)
	}

	sshServer := &sshServer{
		config:            config,
		useObfuscation:    useObfuscation,
		shutdownBroadcast: shutdownBroadcast,
		sshHostKey:        signer,
		nextClientID:      1,
		clients:           make(map[sshClientID]*sshClient),
	}

	var serverPort int
	if useObfuscation {
		serverPort = config.ObfuscatedSSHServerPort
	} else {
		serverPort = config.SSHServerPort
	}

	listener, err := net.Listen(
		"tcp", fmt.Sprintf("%s:%d", config.ServerIPAddress, serverPort))
	if err != nil {
		return psiphon.ContextError(err)
	}

	log.WithContextFields(
		LogFields{
			"useObfuscation": useObfuscation,
			"port":           serverPort,
		}).Info("starting")

	err = nil
	errors := make(chan error)
	waitGroup := new(sync.WaitGroup)

	waitGroup.Add(1)
	go func() {
		defer waitGroup.Done()

	loop:
		for {
			conn, err := listener.Accept()

			select {
			case <-shutdownBroadcast:
				if err == nil {
					conn.Close()
				}
				break loop
			default:
			}

			if err != nil {
				if e, ok := err.(net.Error); ok && e.Temporary() {
					log.WithContextFields(LogFields{"error": err}).Error("accept failed")
					// Temporary error, keep running
					continue
				}

				select {
				case errors <- psiphon.ContextError(err):
				default:
				}

				break loop
			}

			// process each client connection concurrently
			go sshServer.handleClient(conn.(*net.TCPConn))
		}

		sshServer.stopClients()

		log.WithContextFields(
			LogFields{"useObfuscation": useObfuscation}).Info("stopped")
	}()

	select {
	case <-shutdownBroadcast:
	case err = <-errors:
	}

	listener.Close()

	waitGroup.Wait()

	log.WithContextFields(
		LogFields{"useObfuscation": useObfuscation}).Info("exiting")

	return err
}
예제 #21
0
// RunWebServer runs a web server which supports tunneled and untunneled
// Psiphon API requests.
//
// The HTTP request handlers are light wrappers around the base Psiphon
// API request handlers from the SSH API transport. The SSH API transport
// is preferred by new clients; however the web API transport is still
// required for untunneled final status requests. The web API transport
// may be retired once untunneled final status requests are made obsolete
// (e.g., by server-side bytes transferred stats, by client-side local
// storage of stats for retry, or some other future development).
//
// The API is compatible with all tunnel-core clients but not backwards
// compatible with older clients.
//
func RunWebServer(
	support *SupportServices,
	shutdownBroadcast <-chan struct{}) error {

	webServer := &webServer{
		support: support,
	}

	serveMux := http.NewServeMux()
	serveMux.HandleFunc("/handshake", webServer.handshakeHandler)
	serveMux.HandleFunc("/connected", webServer.connectedHandler)
	serveMux.HandleFunc("/status", webServer.statusHandler)
	serveMux.HandleFunc("/client_verification", webServer.clientVerificationHandler)

	certificate, err := tls.X509KeyPair(
		[]byte(support.Config.WebServerCertificate),
		[]byte(support.Config.WebServerPrivateKey))
	if err != nil {
		return psiphon.ContextError(err)
	}

	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{certificate},
	}

	// TODO: inherits global log config?
	logWriter := NewLogWriter()
	defer logWriter.Close()

	server := &psiphon.HTTPSServer{
		http.Server{
			MaxHeaderBytes: MAX_API_PARAMS_SIZE,
			Handler:        serveMux,
			TLSConfig:      tlsConfig,
			ReadTimeout:    WEB_SERVER_READ_TIMEOUT,
			WriteTimeout:   WEB_SERVER_WRITE_TIMEOUT,
			ErrorLog:       golanglog.New(logWriter, "", 0),
		},
	}

	listener, err := net.Listen(
		"tcp", fmt.Sprintf("%s:%d",
			support.Config.ServerIPAddress,
			support.Config.WebServerPort))
	if err != nil {
		return psiphon.ContextError(err)
	}

	log.WithContext().Info("starting")

	err = nil
	errors := make(chan error)
	waitGroup := new(sync.WaitGroup)

	waitGroup.Add(1)
	go func() {
		defer waitGroup.Done()

		// Note: will be interrupted by listener.Close()
		err := server.ServeTLS(listener)

		// Can't check for the exact error that Close() will cause in Accept(),
		// (see: https://code.google.com/p/go/issues/detail?id=4373). So using an
		// explicit stop signal to stop gracefully.
		select {
		case <-shutdownBroadcast:
		default:
			if err != nil {
				select {
				case errors <- psiphon.ContextError(err):
				default:
				}
			}
		}

		log.WithContext().Info("stopped")
	}()

	select {
	case <-shutdownBroadcast:
	case err = <-errors:
	}

	listener.Close()

	waitGroup.Wait()

	log.WithContext().Info("exiting")

	return err
}
예제 #22
0
// Run runs the tunnel server; this function blocks while running a selection of
// listeners that handle connection using various obfuscation protocols.
//
// Run listens on each designated tunnel port and spawns new goroutines to handle
// each client connection. It halts when shutdownBroadcast is signaled. A list of active
// clients is maintained, and when halting all clients are cleanly shutdown.
//
// Each client goroutine handles its own obfuscation (optional), SSH handshake, SSH
// authentication, and then looping on client new channel requests. "direct-tcpip"
// channels, dynamic port fowards, are supported. When the UDPInterceptUdpgwServerAddress
// config parameter is configured, UDP port forwards over a TCP stream, following
// the udpgw protocol, are handled.
//
// A new goroutine is spawned to handle each port forward for each client. Each port
// forward tracks its bytes transferred. Overall per-client stats for connection duration,
// GeoIP, number of port forwards, and bytes transferred are tracked and logged when the
// client shuts down.
func (server *TunnelServer) Run() error {

	type sshListener struct {
		net.Listener
		localAddress   string
		tunnelProtocol string
	}

	// TODO: should TunnelServer hold its own support pointer?
	support := server.sshServer.support

	// First bind all listeners; once all are successful,
	// start accepting connections on each.

	var listeners []*sshListener

	for tunnelProtocol, listenPort := range support.Config.TunnelProtocolPorts {

		localAddress := fmt.Sprintf(
			"%s:%d", support.Config.ServerIPAddress, listenPort)

		listener, err := net.Listen("tcp", localAddress)
		if err != nil {
			for _, existingListener := range listeners {
				existingListener.Listener.Close()
			}
			return psiphon.ContextError(err)
		}

		log.WithContextFields(
			LogFields{
				"localAddress":   localAddress,
				"tunnelProtocol": tunnelProtocol,
			}).Info("listening")

		listeners = append(
			listeners,
			&sshListener{
				Listener:       listener,
				localAddress:   localAddress,
				tunnelProtocol: tunnelProtocol,
			})
	}

	for _, listener := range listeners {
		server.runWaitGroup.Add(1)
		go func(listener *sshListener) {
			defer server.runWaitGroup.Done()

			log.WithContextFields(
				LogFields{
					"localAddress":   listener.localAddress,
					"tunnelProtocol": listener.tunnelProtocol,
				}).Info("running")

			server.sshServer.runListener(
				listener.Listener,
				server.listenerError,
				listener.tunnelProtocol)

			log.WithContextFields(
				LogFields{
					"localAddress":   listener.localAddress,
					"tunnelProtocol": listener.tunnelProtocol,
				}).Info("stopped")

		}(listener)
	}

	var err error
	select {
	case <-server.shutdownBroadcast:
	case err = <-server.listenerError:
	}

	for _, listener := range listeners {
		listener.Close()
	}
	server.sshServer.stopClients()
	server.runWaitGroup.Wait()

	log.WithContext().Info("stopped")

	return err
}
예제 #23
0
// getSession returns the meek client session corresponding the
// meek cookie/session ID. If no session is found, the cookie is
// treated as a meek cookie for a new session and its payload is
// extracted and used to establish a new session.
func (server *MeekServer) getSession(
	request *http.Request, meekCookie *http.Cookie) (string, *meekSession, error) {

	// Check for an existing session

	server.sessionsLock.RLock()
	existingSessionID := meekCookie.Value
	session, ok := server.sessions[existingSessionID]
	server.sessionsLock.RUnlock()
	if ok {
		session.touch()
		return existingSessionID, session, nil
	}

	// TODO: can multiple http client connections using same session cookie
	// cause race conditions on session struct?

	// The session is new (or expired). Treat the cookie value as a new meek
	// cookie, extract the payload, and create a new session.

	payloadJSON, err := getMeekCookiePayload(server.support, meekCookie.Value)
	if err != nil {
		return "", nil, psiphon.ContextError(err)
	}

	// Note: this meek server ignores all but Version MeekProtocolVersion;
	// the other values are legacy or currently unused.
	var clientSessionData struct {
		MeekProtocolVersion    int    `json:"v"`
		PsiphonClientSessionId string `json:"s"`
		PsiphonServerAddress   string `json:"p"`
	}

	err = json.Unmarshal(payloadJSON, &clientSessionData)
	if err != nil {
		return "", nil, psiphon.ContextError(err)
	}

	// Determine the client remote address, which is used for geolocation
	// and stats. When an intermediate proxy of CDN is in use, we may be
	// able to determine the original client address by inspecting HTTP
	// headers such as X-Forwarded-For.

	clientIP := strings.Split(request.RemoteAddr, ":")[0]

	if len(server.support.Config.MeekProxyForwardedForHeaders) > 0 {
		for _, header := range server.support.Config.MeekProxyForwardedForHeaders {
			value := request.Header.Get(header)
			if len(value) > 0 {
				// Some headers, such as X-Forwarded-For, are a comma-separated
				// list of IPs (each proxy in a chain). The first IP should be
				// the client IP.
				proxyClientIP := strings.Split(header, ",")[0]
				if net.ParseIP(clientIP) != nil {
					clientIP = proxyClientIP
					break
				}
			}
		}
	}

	// Create a new meek conn that will relay the payload
	// between meek request/responses and the tunnel server client
	// handler. The client IP is also used to initialize the
	// meek conn with a useful value to return when the tunnel
	// server calls conn.RemoteAddr() to get the client's IP address.

	// Assumes clientIP is a value IP address; the port value is a stub
	// and is expected to be ignored.
	clientConn := newMeekConn(
		&net.TCPAddr{
			IP:   net.ParseIP(clientIP),
			Port: 0,
		},
		clientSessionData.MeekProtocolVersion)

	session = &meekSession{
		clientConn:          clientConn,
		meekProtocolVersion: clientSessionData.MeekProtocolVersion,
		sessionIDSent:       false,
	}
	session.touch()

	// Note: MEEK_PROTOCOL_VERSION_1 doesn't support changing the
	// meek cookie to a session ID; v1 clients always send the
	// original meek cookie value with each request. The issue with
	// v1 is that clients which wake after a device sleep will attempt
	// to resume a meek session and the server can't differentiate
	// between resuming a session and creating a new session. This
	// causes the v1 client connection to hang/timeout.
	sessionID := meekCookie.Value
	if clientSessionData.MeekProtocolVersion >= MEEK_PROTOCOL_VERSION_2 {
		sessionID, err = makeMeekSessionID()
		if err != nil {
			return "", nil, psiphon.ContextError(err)
		}
	}

	server.sessionsLock.Lock()
	server.sessions[sessionID] = session
	server.sessionsLock.Unlock()

	// Note: from the tunnel server's perspective, this client connection
	// will close when closeSessionHelper calls Close() on the meekConn.
	server.clientHandler(session.clientConn)

	return sessionID, session, nil
}
예제 #24
0
// RunServices initializes support functions including logging and GeoIP services;
// and then starts the server components and runs them until os.Interrupt or
// os.Kill signals are received. The config determines which components are run.
func RunServices(configJSON []byte) error {

	config, err := LoadConfig(configJSON)
	if err != nil {
		log.WithContextFields(LogFields{"error": err}).Error("load config failed")
		return psiphon.ContextError(err)
	}

	err = InitLogging(config)
	if err != nil {
		log.WithContextFields(LogFields{"error": err}).Error("init logging failed")
		return psiphon.ContextError(err)
	}

	supportServices, err := NewSupportServices(config)
	if err != nil {
		log.WithContextFields(LogFields{"error": err}).Error("init support services failed")
		return psiphon.ContextError(err)
	}

	waitGroup := new(sync.WaitGroup)
	shutdownBroadcast := make(chan struct{})
	errors := make(chan error)

	tunnelServer, err := NewTunnelServer(supportServices, shutdownBroadcast)
	if err != nil {
		log.WithContextFields(LogFields{"error": err}).Error("init tunnel server failed")
		return psiphon.ContextError(err)
	}

	if config.RunLoadMonitor() {
		waitGroup.Add(1)
		go func() {
			waitGroup.Done()
			ticker := time.NewTicker(time.Duration(config.LoadMonitorPeriodSeconds) * time.Second)
			defer ticker.Stop()
			for {
				select {
				case <-shutdownBroadcast:
					return
				case <-ticker.C:
					logServerLoad(tunnelServer)
				}
			}
		}()
	}

	if config.RunWebServer() {
		waitGroup.Add(1)
		go func() {
			defer waitGroup.Done()
			err := RunWebServer(supportServices, shutdownBroadcast)
			select {
			case errors <- err:
			default:
			}
		}()
	}

	// The tunnel server is always run; it launches multiple
	// listeners, depending on which tunnel protocols are enabled.
	waitGroup.Add(1)
	go func() {
		defer waitGroup.Done()
		err := tunnelServer.Run()
		select {
		case errors <- err:
		default:
		}
	}()

	// An OS signal triggers an orderly shutdown
	systemStopSignal := make(chan os.Signal, 1)
	signal.Notify(systemStopSignal, os.Interrupt, os.Kill)

	// SIGUSR1 triggers a reload of support services
	reloadSupportServicesSignal := make(chan os.Signal, 1)
	signal.Notify(reloadSupportServicesSignal, syscall.SIGUSR1)

	// SIGUSR2 triggers an immediate load log
	logServerLoadSignal := make(chan os.Signal, 1)
	signal.Notify(logServerLoadSignal, syscall.SIGUSR2)

	err = nil

loop:
	for {
		select {
		case <-reloadSupportServicesSignal:
			supportServices.Reload()
		case <-logServerLoadSignal:
			logServerLoad(tunnelServer)
		case <-systemStopSignal:
			log.WithContext().Info("shutdown by system")
			break loop
		case err = <-errors:
			log.WithContextFields(LogFields{"error": err}).Error("service failed")
			break loop
		}
	}

	close(shutdownBroadcast)
	waitGroup.Wait()

	return err
}
예제 #25
0
// LoadConfig loads and validates a JSON encoded server config.
func LoadConfig(configJSON []byte) (*Config, error) {

	var config Config
	err := json.Unmarshal(configJSON, &config)
	if err != nil {
		return nil, psiphon.ContextError(err)
	}

	if config.Fail2BanFormat != "" && strings.Count(config.Fail2BanFormat, "%s") != 1 {
		return nil, errors.New("Fail2BanFormat must have one '%%s' placeholder")
	}

	if config.ServerIPAddress == "" {
		return nil, errors.New("ServerIPAddress is missing from config file")
	}

	if config.WebServerPort > 0 && (config.WebServerSecret == "" || config.WebServerCertificate == "" ||
		config.WebServerPrivateKey == "") {

		return nil, errors.New(
			"Web server requires WebServerSecret, WebServerCertificate, WebServerPrivateKey")
	}

	for tunnelProtocol, _ := range config.TunnelProtocolPorts {
		if psiphon.TunnelProtocolUsesSSH(tunnelProtocol) ||
			psiphon.TunnelProtocolUsesObfuscatedSSH(tunnelProtocol) {
			if config.SSHPrivateKey == "" || config.SSHServerVersion == "" ||
				config.SSHUserName == "" || config.SSHPassword == "" {
				return nil, fmt.Errorf(
					"Tunnel protocol %s requires SSHPrivateKey, SSHServerVersion, SSHUserName, SSHPassword",
					tunnelProtocol)
			}
		}
		if psiphon.TunnelProtocolUsesObfuscatedSSH(tunnelProtocol) {
			if config.ObfuscatedSSHKey == "" {
				return nil, fmt.Errorf(
					"Tunnel protocol %s requires ObfuscatedSSHKey",
					tunnelProtocol)
			}
		}
		if psiphon.TunnelProtocolUsesMeekHTTP(tunnelProtocol) ||
			psiphon.TunnelProtocolUsesMeekHTTPS(tunnelProtocol) {
			if config.MeekCookieEncryptionPrivateKey == "" || config.MeekObfuscatedKey == "" {
				return nil, fmt.Errorf(
					"Tunnel protocol %s requires MeekCookieEncryptionPrivateKey, MeekObfuscatedKey",
					tunnelProtocol)
			}
		}
		if psiphon.TunnelProtocolUsesMeekHTTPS(tunnelProtocol) {
			if config.MeekCertificateCommonName == "" {
				return nil, fmt.Errorf(
					"Tunnel protocol %s requires MeekCertificateCommonName",
					tunnelProtocol)
			}
		}
	}

	validateNetworkAddress := func(address string) error {
		host, port, err := net.SplitHostPort(address)
		if err == nil && net.ParseIP(host) == nil {
			err = errors.New("Host must be an IP address")
		}
		if err == nil {
			_, err = strconv.Atoi(port)
		}
		return err
	}

	if config.UDPForwardDNSServerAddress != "" {
		if err := validateNetworkAddress(config.UDPForwardDNSServerAddress); err != nil {
			return nil, fmt.Errorf("UDPForwardDNSServerAddress is invalid: %s", err)
		}
	}

	if config.UDPInterceptUdpgwServerAddress != "" {
		if err := validateNetworkAddress(config.UDPInterceptUdpgwServerAddress); err != nil {
			return nil, fmt.Errorf("UDPInterceptUdpgwServerAddress is invalid: %s", err)
		}
	}

	return &config, nil
}
예제 #26
0
// Stub implementation of net.Conn.SetWriteDeadline
func (conn *meekConn) SetWriteDeadline(t time.Time) error {
	return psiphon.ContextError(errors.New("not supported"))
}
예제 #27
0
func readUdpgwMessage(
	reader io.Reader, buffer []byte) (*udpProtocolMessage, error) {

	// udpgw message layout:
	//
	// | 2 byte size | 3 byte header | 6 or 18 byte address | variable length packet |

	for {
		// Read message

		_, err := io.ReadFull(reader, buffer[0:2])
		if err != nil {
			return nil, psiphon.ContextError(err)
		}

		size := uint16(buffer[0]) + uint16(buffer[1])<<8

		if int(size) > len(buffer)-2 {
			return nil, psiphon.ContextError(errors.New("invalid udpgw message size"))
		}

		_, err = io.ReadFull(reader, buffer[2:2+size])
		if err != nil {
			return nil, psiphon.ContextError(err)
		}

		flags := buffer[2]

		connID := uint16(buffer[3]) + uint16(buffer[4])<<8

		// Ignore udpgw keep-alive messages -- read another message

		if flags&udpgwProtocolFlagKeepalive == udpgwProtocolFlagKeepalive {
			continue
		}

		// Read address

		var remoteIP []byte
		var remotePort uint16
		var packetStart, packetEnd int

		if flags&udpgwProtocolFlagIPv6 == udpgwProtocolFlagIPv6 {

			if size < 21 {
				return nil, psiphon.ContextError(errors.New("invalid udpgw message size"))
			}

			remoteIP = make([]byte, 16)
			copy(remoteIP, buffer[5:21])
			remotePort = uint16(buffer[21]) + uint16(buffer[22])<<8
			packetStart = 23
			packetEnd = 23 + int(size) - 2

		} else {

			if size < 9 {
				return nil, psiphon.ContextError(errors.New("invalid udpgw message size"))
			}

			remoteIP = make([]byte, 4)
			copy(remoteIP, buffer[5:9])
			remotePort = uint16(buffer[9]) + uint16(buffer[10])<<8
			packetStart = 11
			packetEnd = 11 + int(size) - 2
		}

		// Assemble message
		// Note: udpProtocolMessage.packet references memory in the input buffer

		message := &udpProtocolMessage{
			connID:              connID,
			preambleSize:        packetStart,
			remoteIP:            remoteIP,
			remotePort:          remotePort,
			discardExistingConn: flags&udpgwProtocolFlagRebind == udpgwProtocolFlagRebind,
			forwardDNS:          flags&udpgwProtocolFlagDNS == udpgwProtocolFlagDNS,
			packet:              buffer[packetStart:packetEnd],
		}

		return message, nil
	}
}
예제 #28
0
// runListener is intended to run an a goroutine; it blocks
// running a particular listener. If an unrecoverable error
// occurs, it will send the error to the listenerError channel.
func (sshServer *sshServer) runListener(
	listener net.Listener,
	listenerError chan<- error,
	tunnelProtocol string) {

	handleClient := func(clientConn net.Conn) {
		// process each client connection concurrently
		go sshServer.handleClient(tunnelProtocol, clientConn)
	}

	// Note: when exiting due to a unrecoverable error, be sure
	// to try to send the error to listenerError so that the outer
	// TunnelServer.Run will properly shut down instead of remaining
	// running.

	if psiphon.TunnelProtocolUsesMeekHTTP(tunnelProtocol) ||
		psiphon.TunnelProtocolUsesMeekHTTPS(tunnelProtocol) {

		meekServer, err := NewMeekServer(
			sshServer.support,
			listener,
			psiphon.TunnelProtocolUsesMeekHTTPS(tunnelProtocol),
			handleClient,
			sshServer.shutdownBroadcast)
		if err != nil {
			select {
			case listenerError <- psiphon.ContextError(err):
			default:
			}
			return
		}

		meekServer.Run()

	} else {

		for {
			conn, err := listener.Accept()

			select {
			case <-sshServer.shutdownBroadcast:
				if err == nil {
					conn.Close()
				}
				return
			default:
			}

			if err != nil {
				if e, ok := err.(net.Error); ok && e.Temporary() {
					log.WithContextFields(LogFields{"error": err}).Error("accept failed")
					// Temporary error, keep running
					continue
				}

				select {
				case listenerError <- psiphon.ContextError(err):
				default:
				}
				return
			}

			handleClient(conn)
		}
	}
}
예제 #29
0
// GenerateConfig creates a new Psiphon server config. It returns JSON
// encoded configs and a client-compatible "server entry" for the server. It
// generates all necessary secrets and key material, which are emitted in
// the config file and server entry as necessary.
// GenerateConfig uses sample values for many fields. The intention is for
// generated configs to be used for testing or as a template for production
// setup, not to generate production-ready configurations.
func GenerateConfig(params *GenerateConfigParams) ([]byte, []byte, []byte, error) {

	// Input validation

	if net.ParseIP(params.ServerIPAddress) == nil {
		return nil, nil, nil, psiphon.ContextError(errors.New("invalid IP address"))
	}

	if len(params.TunnelProtocolPorts) == 0 {
		return nil, nil, nil, psiphon.ContextError(errors.New("no tunnel protocols"))
	}

	usedPort := make(map[int]bool)
	if params.WebServerPort != 0 {
		usedPort[params.WebServerPort] = true
	}

	usingMeek := false

	for protocol, port := range params.TunnelProtocolPorts {

		if !psiphon.Contains(psiphon.SupportedTunnelProtocols, protocol) {
			return nil, nil, nil, psiphon.ContextError(errors.New("invalid tunnel protocol"))
		}

		if usedPort[port] {
			return nil, nil, nil, psiphon.ContextError(errors.New("duplicate listening port"))
		}
		usedPort[port] = true

		if psiphon.TunnelProtocolUsesMeekHTTP(protocol) ||
			psiphon.TunnelProtocolUsesMeekHTTPS(protocol) {
			usingMeek = true
		}
	}

	// Web server config

	var webServerSecret, webServerCertificate, webServerPrivateKey string

	if params.WebServerPort != 0 {
		var err error
		webServerSecret, err = psiphon.MakeRandomStringHex(WEB_SERVER_SECRET_BYTE_LENGTH)
		if err != nil {
			return nil, nil, nil, psiphon.ContextError(err)
		}

		webServerCertificate, webServerPrivateKey, err = GenerateWebServerCertificate("")
		if err != nil {
			return nil, nil, nil, psiphon.ContextError(err)
		}
	}

	// SSH config

	// TODO: use other key types: anti-fingerprint by varying params
	rsaKey, err := rsa.GenerateKey(rand.Reader, SSH_RSA_HOST_KEY_BITS)
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	sshPrivateKey := pem.EncodeToMemory(
		&pem.Block{
			Type:  "RSA PRIVATE KEY",
			Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
		},
	)

	signer, err := ssh.NewSignerFromKey(rsaKey)
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	sshPublicKey := signer.PublicKey()

	sshUserNameSuffix, err := psiphon.MakeRandomStringHex(SSH_USERNAME_SUFFIX_BYTE_LENGTH)
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	sshUserName := "******" + sshUserNameSuffix

	sshPassword, err := psiphon.MakeRandomStringHex(SSH_PASSWORD_BYTE_LENGTH)
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	// TODO: vary version string for anti-fingerprint
	sshServerVersion := "SSH-2.0-Psiphon"

	// Obfuscated SSH config

	obfuscatedSSHKey, err := psiphon.MakeRandomStringHex(SSH_OBFUSCATED_KEY_BYTE_LENGTH)
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	// Meek config

	var meekCookieEncryptionPublicKey, meekCookieEncryptionPrivateKey, meekObfuscatedKey string

	if usingMeek {
		rawMeekCookieEncryptionPublicKey, rawMeekCookieEncryptionPrivateKey, err :=
			box.GenerateKey(rand.Reader)
		if err != nil {
			return nil, nil, nil, psiphon.ContextError(err)
		}

		meekCookieEncryptionPublicKey = base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPublicKey[:])
		meekCookieEncryptionPrivateKey = base64.StdEncoding.EncodeToString(rawMeekCookieEncryptionPrivateKey[:])

		meekObfuscatedKey, err = psiphon.MakeRandomStringHex(SSH_OBFUSCATED_KEY_BYTE_LENGTH)
		if err != nil {
			return nil, nil, nil, psiphon.ContextError(err)
		}
	}

	// Other config

	discoveryValueHMACKey, err := psiphon.MakeRandomStringBase64(DISCOVERY_VALUE_KEY_BYTE_LENGTH)
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	// Assemble configs and server entry

	// Note: this config is intended for either testing or as an illustrative
	// example or template and is not intended for production deployment.

	config := &Config{
		LogLevel:                       "info",
		Fail2BanFormat:                 "Authentication failure for psiphon-client from %s",
		GeoIPDatabaseFilename:          "",
		HostID:                         "example-host-id",
		ServerIPAddress:                params.ServerIPAddress,
		DiscoveryValueHMACKey:          discoveryValueHMACKey,
		WebServerPort:                  params.WebServerPort,
		WebServerSecret:                webServerSecret,
		WebServerCertificate:           webServerCertificate,
		WebServerPrivateKey:            webServerPrivateKey,
		SSHPrivateKey:                  string(sshPrivateKey),
		SSHServerVersion:               sshServerVersion,
		SSHUserName:                    sshUserName,
		SSHPassword:                    sshPassword,
		ObfuscatedSSHKey:               obfuscatedSSHKey,
		TunnelProtocolPorts:            params.TunnelProtocolPorts,
		UDPForwardDNSServerAddress:     "8.8.8.8:53",
		UDPInterceptUdpgwServerAddress: "127.0.0.1:7300",
		MeekCookieEncryptionPrivateKey: meekCookieEncryptionPrivateKey,
		MeekObfuscatedKey:              meekObfuscatedKey,
		MeekCertificateCommonName:      "www.example.org",
		MeekProhibitedHeaders:          nil,
		MeekProxyForwardedForHeaders:   []string{"X-Forwarded-For"},
		LoadMonitorPeriodSeconds:       300,
		TrafficRulesFilename:           params.TrafficRulesFilename,
		LogFilename:                    params.LogFilename,
		Fail2BanLogFilename:            params.Fail2BanLogFilename,
	}

	encodedConfig, err := json.MarshalIndent(config, "\n", "    ")
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	trafficRulesSet := &TrafficRulesSet{
		DefaultRules: TrafficRules{
			DefaultLimits: RateLimits{
				DownstreamUnlimitedBytes: 0,
				DownstreamBytesPerSecond: 0,
				UpstreamUnlimitedBytes:   0,
				UpstreamBytesPerSecond:   0,
			},
			IdleTCPPortForwardTimeoutMilliseconds: 30000,
			IdleUDPPortForwardTimeoutMilliseconds: 30000,
			MaxTCPPortForwardCount:                1024,
			MaxUDPPortForwardCount:                32,
			AllowTCPPorts:                         nil,
			AllowUDPPorts:                         nil,
			DenyTCPPorts:                          nil,
			DenyUDPPorts:                          nil,
		},
	}

	encodedTrafficRulesSet, err := json.MarshalIndent(trafficRulesSet, "\n", "    ")
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	capabilities := []string{}

	if params.EnableSSHAPIRequests {
		capabilities = append(capabilities, psiphon.CAPABILITY_SSH_API_REQUESTS)
	}

	if params.WebServerPort != 0 {
		capabilities = append(capabilities, psiphon.CAPABILITY_UNTUNNELED_WEB_API_REQUESTS)
	}

	for protocol, _ := range params.TunnelProtocolPorts {
		capabilities = append(capabilities, psiphon.GetCapability(protocol))
	}

	sshPort := params.TunnelProtocolPorts["SSH"]
	obfuscatedSSHPort := params.TunnelProtocolPorts["OSSH"]

	// Meek port limitations
	// - fronted meek protocols are hard-wired in the client to be port 443 or 80.
	// - only one other meek port may be specified.
	meekPort := params.TunnelProtocolPorts["UNFRONTED-MEEK-OSSH"]
	if meekPort == 0 {
		meekPort = params.TunnelProtocolPorts["UNFRONTED-MEEK-HTTPS-OSSH"]
	}

	// Note: fronting params are a stub; this server entry will exercise
	// client and server fronting code paths, but not actually traverse
	// a fronting hop.

	serverEntryWebServerPort := ""
	strippedWebServerCertificate := ""

	if params.WebServerPort != 0 {
		serverEntryWebServerPort = fmt.Sprintf("%d", params.WebServerPort)

		// Server entry format omits the BEGIN/END lines and newlines
		lines := strings.Split(webServerCertificate, "\n")
		strippedWebServerCertificate = strings.Join(lines[1:len(lines)-2], "")
	}

	serverEntry := &psiphon.ServerEntry{
		IpAddress:                     params.ServerIPAddress,
		WebServerPort:                 serverEntryWebServerPort,
		WebServerSecret:               webServerSecret,
		WebServerCertificate:          strippedWebServerCertificate,
		SshPort:                       sshPort,
		SshUsername:                   sshUserName,
		SshPassword:                   sshPassword,
		SshHostKey:                    base64.RawStdEncoding.EncodeToString(sshPublicKey.Marshal()),
		SshObfuscatedPort:             obfuscatedSSHPort,
		SshObfuscatedKey:              obfuscatedSSHKey,
		Capabilities:                  capabilities,
		Region:                        "US",
		MeekServerPort:                meekPort,
		MeekCookieEncryptionPublicKey: meekCookieEncryptionPublicKey,
		MeekObfuscatedKey:             meekObfuscatedKey,
		MeekFrontingHosts:             []string{params.ServerIPAddress},
		MeekFrontingAddresses:         []string{params.ServerIPAddress},
		MeekFrontingDisableSNI:        false,
	}

	encodedServerEntry, err := psiphon.EncodeServerEntry(serverEntry)
	if err != nil {
		return nil, nil, nil, psiphon.ContextError(err)
	}

	return encodedConfig, encodedTrafficRulesSet, []byte(encodedServerEntry), nil
}
예제 #30
0
func (sshServer *sshServer) handleClient(tunnelProtocol string, clientConn net.Conn) {

	sshServer.registerAcceptedClient(tunnelProtocol)
	defer sshServer.unregisterAcceptedClient(tunnelProtocol)

	geoIPData := sshServer.support.GeoIPService.Lookup(
		psiphon.IPAddressFromAddr(clientConn.RemoteAddr()))

	// TODO: apply reload of TrafficRulesSet to existing clients

	sshClient := newSshClient(
		sshServer,
		tunnelProtocol,
		geoIPData,
		sshServer.support.TrafficRulesSet.GetTrafficRules(geoIPData.Country))

	// Wrap the base client connection with an ActivityMonitoredConn which will
	// terminate the connection if no data is received before the deadline. This
	// timeout is in effect for the entire duration of the SSH connection. Clients
	// must actively use the connection or send SSH keep alive requests to keep
	// the connection active.

	activityConn := psiphon.NewActivityMonitoredConn(
		clientConn,
		SSH_CONNECTION_READ_DEADLINE,
		false,
		nil)
	clientConn = activityConn

	// Further wrap the connection in a rate limiting ThrottledConn.

	rateLimits := sshClient.trafficRules.GetRateLimits(tunnelProtocol)
	clientConn = psiphon.NewThrottledConn(
		clientConn,
		rateLimits.DownstreamUnlimitedBytes,
		int64(rateLimits.DownstreamBytesPerSecond),
		rateLimits.UpstreamUnlimitedBytes,
		int64(rateLimits.UpstreamBytesPerSecond))

	// Run the initial [obfuscated] SSH handshake in a goroutine so we can both
	// respect shutdownBroadcast and implement a specific handshake timeout.
	// The timeout is to reclaim network resources in case the handshake takes
	// too long.

	type sshNewServerConnResult struct {
		conn     net.Conn
		sshConn  *ssh.ServerConn
		channels <-chan ssh.NewChannel
		requests <-chan *ssh.Request
		err      error
	}

	resultChannel := make(chan *sshNewServerConnResult, 2)

	if SSH_HANDSHAKE_TIMEOUT > 0 {
		time.AfterFunc(time.Duration(SSH_HANDSHAKE_TIMEOUT), func() {
			resultChannel <- &sshNewServerConnResult{err: errors.New("ssh handshake timeout")}
		})
	}

	go func(conn net.Conn) {
		sshServerConfig := &ssh.ServerConfig{
			PasswordCallback: sshClient.passwordCallback,
			AuthLogCallback:  sshClient.authLogCallback,
			ServerVersion:    sshServer.support.Config.SSHServerVersion,
		}
		sshServerConfig.AddHostKey(sshServer.sshHostKey)

		result := &sshNewServerConnResult{}

		// Wrap the connection in an SSH deobfuscator when required.

		if psiphon.TunnelProtocolUsesObfuscatedSSH(tunnelProtocol) {
			// Note: NewObfuscatedSshConn blocks on network I/O
			// TODO: ensure this won't block shutdown
			conn, result.err = psiphon.NewObfuscatedSshConn(
				psiphon.OBFUSCATION_CONN_MODE_SERVER,
				clientConn,
				sshServer.support.Config.ObfuscatedSSHKey)
			if result.err != nil {
				result.err = psiphon.ContextError(result.err)
			}
		}

		if result.err == nil {
			result.sshConn, result.channels, result.requests, result.err =
				ssh.NewServerConn(conn, sshServerConfig)
		}

		resultChannel <- result

	}(clientConn)

	var result *sshNewServerConnResult
	select {
	case result = <-resultChannel:
	case <-sshServer.shutdownBroadcast:
		// Close() will interrupt an ongoing handshake
		// TODO: wait for goroutine to exit before returning?
		clientConn.Close()
		return
	}

	if result.err != nil {
		clientConn.Close()
		// This is a Debug log due to noise. The handshake often fails due to I/O
		// errors as clients frequently interrupt connections in progress when
		// client-side load balancing completes a connection to a different server.
		log.WithContextFields(LogFields{"error": result.err}).Debug("handshake failed")
		return
	}

	sshClient.Lock()
	sshClient.sshConn = result.sshConn
	sshClient.activityConn = activityConn
	sshClient.Unlock()

	clientID, ok := sshServer.registerEstablishedClient(sshClient)
	if !ok {
		clientConn.Close()
		log.WithContext().Warning("register failed")
		return
	}
	defer sshServer.unregisterEstablishedClient(clientID)

	sshClient.runClient(result.channels, result.requests)

	// Note: sshServer.unregisterClient calls sshClient.Close(),
	// which also closes underlying transport Conn.
}