| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- /*
- * Copyright (c) 2016, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- package server
- import (
- "crypto/rand"
- "crypto/rsa"
- "crypto/x509"
- "encoding/base64"
- "encoding/json"
- "encoding/pem"
- "fmt"
- "math/big"
- "strings"
- "time"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
- "golang.org/x/crypto/ssh"
- )
- const (
- SERVER_CONFIG_FILENAME = "psiphon-server.config"
- SERVER_ENTRY_FILENAME = "serverEntry.dat"
- DEFAULT_SERVER_IP_ADDRESS = "127.0.0.1"
- WEB_SERVER_SECRET_BYTE_LENGTH = 32
- WEB_SERVER_CERTIFICATE_RSA_KEY_BITS = 2048
- WEB_SERVER_CERTIFICATE_VALIDITY_PERIOD = 10 * 365 * 24 * time.Hour // approx. 10 years
- DEFAULT_WEB_SERVER_PORT = 8000
- WEB_SERVER_READ_TIMEOUT = 10 * time.Second
- WEB_SERVER_WRITE_TIMEOUT = 10 * time.Second
- SSH_USERNAME_SUFFIX_BYTE_LENGTH = 8
- SSH_PASSWORD_BYTE_LENGTH = 32
- SSH_RSA_HOST_KEY_BITS = 2048
- DEFAULT_SSH_SERVER_PORT = 2222
- SSH_HANDSHAKE_TIMEOUT = 30 * time.Second
- SSH_OBFUSCATED_KEY_BYTE_LENGTH = 32
- DEFAULT_OBFUSCATED_SSH_SERVER_PORT = 3333
- )
- type Config struct {
- ServerIPAddress string
- WebServerPort int
- WebServerSecret string
- WebServerCertificate string
- WebServerPrivateKey string
- SSHPrivateKey string
- SSHServerVersion string
- SSHUserName string
- SSHPassword string
- SSHServerPort int
- ObfuscatedSSHKey string
- ObfuscatedSSHServerPort int
- }
- func LoadConfig(configJson []byte) (*Config, error) {
- var config Config
- err := json.Unmarshal(configJson, &config)
- if err != nil {
- return nil, psiphon.ContextError(err)
- }
- // TODO: config field validation
- // TODO: validation case: OSSH requires extra fields
- return &config, nil
- }
- type GenerateConfigParams struct {
- ServerIPAddress string
- WebServerPort int
- SSHServerPort int
- ObfuscatedSSHServerPort int
- }
- 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 := "psiphon_" + 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{
- 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,
- }
- encodedConfig, err := json.Marshal(config)
- 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
- }
- 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
- }
|