// 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 }
// 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 }