func newSSHServer(
	support *SupportServices,
	shutdownBroadcast <-chan struct{}) (*sshServer, error) {

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

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

	return &sshServer{
		support:              support,
		shutdownBroadcast:    shutdownBroadcast,
		sshHostKey:           signer,
		acceptedClientCounts: make(map[string]int64),
		clients:              make(map[string]*sshClient),
	}, nil
}
Example #2
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, common.ContextError(errors.New("invalid IP address"))
	}

	if len(params.TunnelProtocolPorts) == 0 {
		return nil, nil, nil, common.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 !common.Contains(common.SupportedTunnelProtocols, protocol) {
			return nil, nil, nil, common.ContextError(errors.New("invalid tunnel protocol"))
		}

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

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

	// Web server config

	var webServerSecret, webServerCertificate,
		webServerPrivateKey, webServerPortForwardAddress string

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

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

		webServerPortForwardAddress = net.JoinHostPort(
			params.ServerIPAddress, strconv.Itoa(params.WebServerPort))
	}

	// 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, common.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, common.ContextError(err)
	}

	sshPublicKey := signer.PublicKey()

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

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

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

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

	// Obfuscated SSH config

	obfuscatedSSHKey, err := common.MakeRandomStringHex(SSH_OBFUSCATED_KEY_BYTE_LENGTH)
	if err != nil {
		return nil, nil, nil, common.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, common.ContextError(err)
		}

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

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

	// Other config

	discoveryValueHMACKey, err := common.MakeRandomStringBase64(DISCOVERY_VALUE_KEY_BYTE_LENGTH)
	if err != nil {
		return nil, nil, nil, common.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",
		LogFilename:                    params.LogFilename,
		GeoIPDatabaseFilenames:         nil,
		HostID:                         "example-host-id",
		ServerIPAddress:                params.ServerIPAddress,
		DiscoveryValueHMACKey:          discoveryValueHMACKey,
		WebServerPort:                  params.WebServerPort,
		WebServerSecret:                webServerSecret,
		WebServerCertificate:           webServerCertificate,
		WebServerPrivateKey:            webServerPrivateKey,
		WebServerPortForwardAddress:    webServerPortForwardAddress,
		SSHPrivateKey:                  string(sshPrivateKey),
		SSHServerVersion:               sshServerVersion,
		SSHUserName:                    sshUserName,
		SSHPassword:                    sshPassword,
		ObfuscatedSSHKey:               obfuscatedSSHKey,
		TunnelProtocolPorts:            params.TunnelProtocolPorts,
		DNSResolverIPAddress:           "8.8.8.8",
		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,
	}

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

	intPtr := func(i int) *int {
		return &i
	}

	trafficRulesSet := &TrafficRulesSet{
		DefaultRules: TrafficRules{
			RateLimits: RateLimits{
				ReadUnthrottledBytes:  new(int64),
				ReadBytesPerSecond:    new(int64),
				WriteUnthrottledBytes: new(int64),
				WriteBytesPerSecond:   new(int64),
			},
			IdleTCPPortForwardTimeoutMilliseconds: intPtr(DEFAULT_IDLE_TCP_PORT_FORWARD_TIMEOUT_MILLISECONDS),
			IdleUDPPortForwardTimeoutMilliseconds: intPtr(DEFAULT_IDLE_UDP_PORT_FORWARD_TIMEOUT_MILLISECONDS),
			MaxTCPPortForwardCount:                intPtr(DEFAULT_MAX_TCP_PORT_FORWARD_COUNT),
			MaxUDPPortForwardCount:                intPtr(DEFAULT_MAX_UDP_PORT_FORWARD_COUNT),
			AllowTCPPorts:                         nil,
			AllowUDPPorts:                         nil,
		},
	}

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

	capabilities := []string{}

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

	if params.WebServerPort != 0 {
		capabilities = append(capabilities, common.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, common.ContextError(err)
	}

	return encodedConfig, encodedTrafficRulesSet, []byte(encodedServerEntry), nil
}
func TestObfuscatedSSHConn(t *testing.T) {

	keyword, _ := MakeRandomStringHex(32)

	serverAddress := "127.0.0.1:2222"

	listener, err := net.Listen("tcp", serverAddress)
	if err != nil {
		t.Fatalf("Listen failed: %s", err)
	}

	rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		t.Fatalf("GenerateKey failed: %s", err)
	}

	hostKey, err := ssh.NewSignerFromKey(rsaKey)
	if err != nil {
		t.Fatalf("NewSignerFromKey failed: %s", err)
	}

	sshCertChecker := &ssh.CertChecker{
		HostKeyFallback: func(addr string, remote net.Addr, publicKey ssh.PublicKey) error {
			if !bytes.Equal(hostKey.PublicKey().Marshal(), publicKey.Marshal()) {
				return errors.New("unexpected host public key")
			}
			return nil
		},
	}

	result := make(chan error, 1)

	go func() {

		conn, err := listener.Accept()

		if err == nil {
			conn, err = NewObfuscatedSshConn(
				OBFUSCATION_CONN_MODE_SERVER, conn, keyword)
		}

		if err == nil {
			config := &ssh.ServerConfig{
				NoClientAuth: true,
			}
			config.AddHostKey(hostKey)

			_, _, _, err = ssh.NewServerConn(conn, config)
		}

		if err != nil {
			select {
			case result <- err:
			default:
			}
		}
	}()

	go func() {

		conn, err := net.DialTimeout("tcp", serverAddress, 5*time.Second)

		if err == nil {
			conn, err = NewObfuscatedSshConn(
				OBFUSCATION_CONN_MODE_CLIENT, conn, keyword)
		}

		if err == nil {
			config := &ssh.ClientConfig{
				HostKeyCallback: sshCertChecker.CheckHostKey,
			}
			_, _, _, err = ssh.NewClientConn(conn, "", config)
		}

		// Sends nil on success
		select {
		case result <- err:
		default:
		}
	}()

	err = <-result
	if err != nil {
		t.Fatalf("obfuscated SSH handshake failed: %s", err)
	}
}