Пример #1
0
func main() {

	var configFilename string
	flag.StringVar(&configFilename, "config", "", "OSL configuration file")

	var scheme int
	flag.IntVar(&scheme, "scheme", 0, "scheme to pave")

	var oslOffset int
	flag.IntVar(&oslOffset, "offset", 0, "OSL offset")

	var oslCount int
	flag.IntVar(&oslCount, "count", 1, "OSL count")

	var signingKeyPairFilename string
	flag.StringVar(&signingKeyPairFilename, "key", "", "signing public key pair")

	var destinationDirectory string
	flag.StringVar(&destinationDirectory, "output", "", "destination directory for output files")

	flag.Parse()

	configJSON, err := ioutil.ReadFile(configFilename)
	if err != nil {
		fmt.Printf("failed loading configuration file: %s\n", err)
		os.Exit(1)
	}

	config, err := osl.LoadConfig(configJSON)
	if err != nil {
		fmt.Printf("failed processing configuration file: %s\n", err)
		os.Exit(1)
	}

	if scheme < 0 || scheme >= len(config.Schemes) {
		fmt.Printf("failed: invalid scheme\n")
		os.Exit(1)
	}

	keyPairPEM, err := ioutil.ReadFile(signingKeyPairFilename)
	if err != nil {
		fmt.Printf("failed loading signing public key pair file: %s\n", err)
		os.Exit(1)
	}

	// Password "none" from psi_ops:
	// https://bitbucket.org/psiphon/psiphon-circumvention-system/src/ef4f3d4893bd5259ef24f0cb4525cbbbb0854cf9/Automation/psi_ops.py?at=default&fileviewer=file-view-default#psi_ops.py-297

	block, _ := pem.Decode(keyPairPEM)
	decryptedKeyPairPEM, err := x509.DecryptPEMBlock(block, []byte("none"))
	if err != nil {
		fmt.Printf("failed decrypting signing public key pair file: %s\n", err)
		os.Exit(1)
	}

	rsaKey, err := x509.ParsePKCS1PrivateKey(decryptedKeyPairPEM)
	if err != nil {
		fmt.Printf("failed parsing signing public key pair file: %s\n", err)
		os.Exit(1)
	}

	publicKeyBytes, err := x509.MarshalPKIXPublicKey(rsaKey.Public())
	if err != nil {
		fmt.Printf("failed marshaling signing public key: %s\n", err)
		os.Exit(1)
	}

	privateKeyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)

	signingPublicKey := base64.StdEncoding.EncodeToString(publicKeyBytes)
	signingPrivateKey := base64.StdEncoding.EncodeToString(privateKeyBytes)

	slokTimePeriodsPerOSL := 1
	for _, keySplit := range config.Schemes[scheme].SeedPeriodKeySplits {
		slokTimePeriodsPerOSL *= keySplit.Total
	}
	oslTimePeriod := time.Duration(config.Schemes[0].SeedPeriodNanoseconds * int64(slokTimePeriodsPerOSL))

	for _, propagationChannelID := range config.Schemes[0].PropagationChannelIDs {

		paveServerEntries := make([]map[time.Time]string, len(config.Schemes))
		paveServerEntries[0] = make(map[time.Time]string)

		epoch, _ := time.Parse(time.RFC3339, config.Schemes[0].Epoch)
		for i := oslOffset; i < oslOffset+oslCount; i++ {
			paveServerEntries[0][epoch.Add(time.Duration(i)*oslTimePeriod)] = ""
		}

		paveFiles, err := config.Pave(
			epoch.Add(time.Duration(oslCount)*oslTimePeriod),
			propagationChannelID,
			signingPublicKey,
			signingPrivateKey,
			paveServerEntries)
		if err != nil {
			fmt.Printf("failed paving: %s\n", err)
			os.Exit(1)
		}

		directory := filepath.Join(destinationDirectory, propagationChannelID)

		err = os.MkdirAll(directory, 0755)
		if err != nil {
			fmt.Printf("failed creating output directory: %s\n", err)
			os.Exit(1)
		}

		for _, paveFile := range paveFiles {
			filename := filepath.Join(directory, paveFile.Name)
			err = ioutil.WriteFile(filename, paveFile.Contents, 0755)
			if err != nil {
				fmt.Printf("error writing output file: %s\n", err)
				os.Exit(1)
			}
		}
	}
}
func TestObfuscatedRemoteServerLists(t *testing.T) {

	testDataDirName, err := ioutil.TempDir("", "psiphon-remote-server-list-test")
	if err != nil {
		t.Fatalf("TempDir failed: %s", err)
	}
	defer os.RemoveAll(testDataDirName)

	//
	// create a server
	//

	serverIPaddress := ""
	for _, interfaceName := range []string{"eth0", "en0"} {
		serverIPaddress, err = common.GetInterfaceIPAddress(interfaceName)
		if err == nil {
			break
		}
	}
	if err != nil {
		t.Fatalf("error getting server IP address: %s", err)
	}

	serverConfigJSON, _, encodedServerEntry, err := server.GenerateConfig(
		&server.GenerateConfigParams{
			ServerIPAddress:      serverIPaddress,
			EnableSSHAPIRequests: true,
			WebServerPort:        8001,
			TunnelProtocolPorts:  map[string]int{"OSSH": 4001},
		})
	if err != nil {
		t.Fatalf("error generating server config: %s", err)
	}

	//
	// pave OSLs
	//

	oslConfigJSONTemplate := `
    {
      "Schemes" : [
        {
          "Epoch" : "%s",
          "Regions" : [],
          "PropagationChannelIDs" : ["%s"],
          "MasterKey" : "vwab2WY3eNyMBpyFVPtsivMxF4MOpNHM/T7rHJIXctg=",
          "SeedSpecs" : [
            {
              "ID" : "KuP2V6gLcROIFzb/27fUVu4SxtEfm2omUoISlrWv1mA=",
              "UpstreamSubnets" : ["0.0.0.0/0"],
              "Targets" :
              {
                  "BytesRead" : 1,
                  "BytesWritten" : 1,
                  "PortForwardDurationNanoseconds" : 1
              }
            }
          ],
          "SeedSpecThreshold" : 1,
          "SeedPeriodNanoseconds" : %d,
          "SeedPeriodKeySplits": [
            {
              "Total": 1,
              "Threshold": 1
            }
          ]
        }
      ]
    }`

	now := time.Now().UTC()
	seedPeriod := 24 * time.Hour
	epoch := now.Truncate(seedPeriod)
	epochStr := epoch.Format(time.RFC3339Nano)

	propagationChannelID, _ := common.MakeRandomStringHex(8)

	oslConfigJSON := fmt.Sprintf(
		oslConfigJSONTemplate,
		epochStr,
		propagationChannelID,
		seedPeriod)

	oslConfig, err := osl.LoadConfig([]byte(oslConfigJSON))
	if err != nil {
		t.Fatalf("error loading OSL config: %s", err)
	}

	signingPublicKey, signingPrivateKey, err := common.GenerateAuthenticatedDataPackageKeys()
	if err != nil {
		t.Fatalf("error generating package keys: %s", err)
	}

	paveFiles, err := oslConfig.Pave(
		epoch,
		propagationChannelID,
		signingPublicKey,
		signingPrivateKey,
		[]map[time.Time]string{
			map[time.Time]string{
				epoch: string(encodedServerEntry),
			},
		})
	if err != nil {
		t.Fatalf("error paving OSL files: %s", err)
	}

	//
	// mock seeding SLOKs
	//

	singleton = dataStore{}
	os.Remove(filepath.Join(testDataDirName, DATA_STORE_FILENAME))

	err = InitDataStore(&Config{DataStoreDirectory: testDataDirName})
	if err != nil {
		t.Fatalf("error initializing client datastore: %s", err)
	}

	if CountServerEntries("", "") > 0 {
		t.Fatalf("unexpected server entries")
	}

	seedState := oslConfig.NewClientSeedState("", propagationChannelID, nil)
	seedPortForward := seedState.NewClientSeedPortForward(net.ParseIP("0.0.0.0"))
	seedPortForward.UpdateProgress(1, 1, 1)
	payload := seedState.GetSeedPayload()
	if len(payload.SLOKs) != 1 {
		t.Fatalf("expected 1 SLOKs, got %d", len(payload.SLOKs))
	}

	SetSLOK(payload.SLOKs[0].ID, payload.SLOKs[0].Key)

	//
	// run mock remote server list host
	//

	remoteServerListHostAddress := net.JoinHostPort(serverIPaddress, "8081")

	// The common remote server list fetches will 404
	remoteServerListURL := fmt.Sprintf("http://%s/server_list_compressed", remoteServerListHostAddress)
	remoteServerListDownloadFilename := filepath.Join(testDataDirName, "server_list_compressed")

	obfuscatedServerListRootURL := fmt.Sprintf("http://%s/", remoteServerListHostAddress)
	obfuscatedServerListDownloadDirectory := testDataDirName

	go func() {
		startTime := time.Now()
		serveMux := http.NewServeMux()
		for _, paveFile := range paveFiles {
			file := paveFile
			serveMux.HandleFunc("/"+file.Name, func(w http.ResponseWriter, req *http.Request) {
				md5sum := md5.Sum(file.Contents)
				w.Header().Add("Content-Type", "application/octet-stream")
				w.Header().Add("ETag", hex.EncodeToString(md5sum[:]))
				http.ServeContent(w, req, file.Name, startTime, bytes.NewReader(file.Contents))
			})
		}
		httpServer := &http.Server{
			Addr:    remoteServerListHostAddress,
			Handler: serveMux,
		}
		err := httpServer.ListenAndServe()
		if err != nil {
			// TODO: wrong goroutine for t.FatalNow()
			t.Fatalf("error running remote server list host: %s", err)

		}
	}()

	//
	// run Psiphon server
	//

	go func() {
		err := server.RunServices(serverConfigJSON)
		if err != nil {
			// TODO: wrong goroutine for t.FatalNow()
			t.Fatalf("error running server: %s", err)
		}
	}()

	//
	// disrupt remote server list downloads
	//

	disruptorProxyAddress := "127.0.0.1:2162"
	disruptorProxyURL := "socks4a://" + disruptorProxyAddress

	go func() {
		listener, err := socks.ListenSocks("tcp", disruptorProxyAddress)
		if err != nil {
			fmt.Errorf("disruptor proxy listen error: %s", err)
			return
		}
		for {
			localConn, err := listener.AcceptSocks()
			if err != nil {
				fmt.Errorf("disruptor proxy accept error: %s", err)
				return
			}
			go func() {
				remoteConn, err := net.Dial("tcp", localConn.Req.Target)
				if err != nil {
					fmt.Errorf("disruptor proxy dial error: %s", err)
					return
				}
				err = localConn.Grant(&net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: 0})
				if err != nil {
					fmt.Errorf("disruptor proxy grant error: %s", err)
					return
				}

				waitGroup := new(sync.WaitGroup)
				waitGroup.Add(1)
				go func() {
					defer waitGroup.Done()
					io.Copy(remoteConn, localConn)
				}()
				if localConn.Req.Target == remoteServerListHostAddress {
					io.CopyN(localConn, remoteConn, 500)
				} else {
					io.Copy(localConn, remoteConn)
				}
				localConn.Close()
				remoteConn.Close()
				waitGroup.Wait()
			}()
		}
	}()

	//
	// connect to Psiphon server with Psiphon client
	//

	SetEmitDiagnosticNotices(true)

	// Note: calling LoadConfig ensures all *int config fields are initialized
	clientConfigJSONTemplate := `
    {
        "ClientPlatform" : "",
        "ClientVersion" : "0",
        "SponsorId" : "0",
        "PropagationChannelId" : "0",
        "ConnectionPoolSize" : 1,
        "EstablishTunnelPausePeriodSeconds" : 1,
        "FetchRemoteServerListRetryPeriodSeconds" : 1,
		"RemoteServerListSignaturePublicKey" : "%s",
		"RemoteServerListUrl" : "%s",
		"RemoteServerListDownloadFilename" : "%s",
		"ObfuscatedServerListRootURL" : "%s",
		"ObfuscatedServerListDownloadDirectory" : "%s",
		"UpstreamProxyUrl" : "%s"
    }`

	clientConfigJSON := fmt.Sprintf(
		clientConfigJSONTemplate,
		signingPublicKey,
		remoteServerListURL,
		remoteServerListDownloadFilename,
		obfuscatedServerListRootURL,
		obfuscatedServerListDownloadDirectory,
		disruptorProxyURL)

	clientConfig, _ := LoadConfig([]byte(clientConfigJSON))

	controller, err := NewController(clientConfig)
	if err != nil {
		t.Fatalf("error creating client controller: %s", err)
	}

	tunnelEstablished := make(chan struct{}, 1)

	SetNoticeOutput(NewNoticeReceiver(
		func(notice []byte) {

			noticeType, payload, err := GetNotice(notice)
			if err != nil {
				return
			}

			printNotice := false

			switch noticeType {
			case "Tunnels":
				printNotice = true
				count := int(payload["count"].(float64))
				if count == 1 {
					tunnelEstablished <- *new(struct{})
				}
			case "RemoteServerListResourceDownloadedBytes":
				// TODO: check for resumed download for each URL
				//url := payload["url"].(string)
				printNotice = true
			case "RemoteServerListResourceDownloaded":
				printNotice = true
			}

			if printNotice {
				fmt.Printf("%s\n", string(notice))
			}
		}))

	go func() {
		controller.Run(make(chan struct{}))
	}()

	establishTimeout := time.NewTimer(30 * time.Second)
	select {
	case <-tunnelEstablished:
	case <-establishTimeout.C:
		t.Fatalf("tunnel establish timeout exceeded")
	}

	for _, paveFile := range paveFiles {
		u, _ := url.Parse(obfuscatedServerListRootURL)
		u.Path = path.Join(u.Path, paveFile.Name)
		etag, _ := GetUrlETag(u.String())
		md5sum := md5.Sum(paveFile.Contents)
		if etag != hex.EncodeToString(md5sum[:]) {
			t.Fatalf("unexpected ETag for %s", u)
		}
	}
}