github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/osl/paver/main.go (about)

     1  /*
     2   * Copyright (c) 2016, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main
    21  
    22  import (
    23  	"crypto/x509"
    24  	"encoding/base64"
    25  	"encoding/json"
    26  	"encoding/pem"
    27  	"flag"
    28  	"fmt"
    29  	"io/ioutil"
    30  	"os"
    31  	"path/filepath"
    32  	"strconv"
    33  	"time"
    34  
    35  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/osl"
    36  )
    37  
    38  func main() {
    39  
    40  	var configFilename string
    41  	flag.StringVar(&configFilename, "config", "", "OSL configuration filename")
    42  
    43  	var offset time.Duration
    44  	flag.DurationVar(
    45  		&offset, "offset", 0,
    46  		"pave OSL start time (now minus offset); default, 0, selects earliest epoch")
    47  
    48  	var period time.Duration
    49  	flag.DurationVar(
    50  		&period, "period", 0,
    51  		"pave OSL total period (starting from offset); default, 0, selects at least one OSL period from now for all schemes")
    52  
    53  	var signingKeyPairFilename string
    54  	flag.StringVar(&signingKeyPairFilename, "key", "", "signing public key pair filename")
    55  
    56  	var payloadFilename string
    57  	flag.StringVar(&payloadFilename, "payload", "", "server entries to pave into OSLs")
    58  
    59  	var destinationDirectory string
    60  	flag.StringVar(
    61  		&destinationDirectory, "output", "",
    62  		"destination directory for output files; when omitted, no files are written (dry run mode)")
    63  
    64  	var listScheme int
    65  	flag.IntVar(&listScheme, "list-scheme", -1, "list current period OSL IDs for specified scheme; no files are written")
    66  
    67  	var omitMD5SumsSchemes ints
    68  	flag.Var(&omitMD5SumsSchemes, "omit-md5sums", "omit MD5Sum fields for specified scheme(s)")
    69  
    70  	var omitEmptyOSLsSchemes ints
    71  	flag.Var(&omitEmptyOSLsSchemes, "omit-empty", "omit empty OSLs for specified scheme(s)")
    72  
    73  	flag.Parse()
    74  
    75  	// load config
    76  
    77  	configJSON, err := ioutil.ReadFile(configFilename)
    78  	if err != nil {
    79  		fmt.Printf("failed loading configuration file: %s\n", err)
    80  		os.Exit(1)
    81  	}
    82  
    83  	config, err := osl.LoadConfig(configJSON)
    84  	if err != nil {
    85  		fmt.Printf("failed processing configuration file: %s\n", err)
    86  		os.Exit(1)
    87  	}
    88  
    89  	if listScheme != -1 {
    90  		OSLIDs, err := config.CurrentOSLIDs(listScheme)
    91  		if err != nil {
    92  			fmt.Printf("failed listing scheme OSL IDs: %s\n", err)
    93  			os.Exit(1)
    94  		}
    95  		for propagationChannelID, OSLID := range OSLIDs {
    96  			fmt.Printf("%s %s\n", propagationChannelID, OSLID)
    97  		}
    98  		return
    99  	}
   100  
   101  	// load key pair
   102  
   103  	keyPairPEM, err := ioutil.ReadFile(signingKeyPairFilename)
   104  	if err != nil {
   105  		fmt.Printf("failed loading signing public key pair file: %s\n", err)
   106  		os.Exit(1)
   107  	}
   108  
   109  	// Password "none" from psi_ops:
   110  	// https://bitbucket.org/psiphon/psiphon-circumvention-system/src/ef4f3d4893bd5259ef24f0cb4525cbbbb0854cf9/Automation/psi_ops.py?at=default&fileviewer=file-view-default#psi_ops.py-297
   111  
   112  	block, _ := pem.Decode(keyPairPEM)
   113  	decryptedKeyPairPEM, err := x509.DecryptPEMBlock(block, []byte("none"))
   114  	if err != nil {
   115  		fmt.Printf("failed decrypting signing public key pair file: %s\n", err)
   116  		os.Exit(1)
   117  	}
   118  
   119  	rsaKey, err := x509.ParsePKCS1PrivateKey(decryptedKeyPairPEM)
   120  	if err != nil {
   121  		fmt.Printf("failed parsing signing public key pair file: %s\n", err)
   122  		os.Exit(1)
   123  	}
   124  
   125  	publicKeyBytes, err := x509.MarshalPKIXPublicKey(rsaKey.Public())
   126  	if err != nil {
   127  		fmt.Printf("failed marshaling signing public key: %s\n", err)
   128  		os.Exit(1)
   129  	}
   130  
   131  	privateKeyBytes := x509.MarshalPKCS1PrivateKey(rsaKey)
   132  
   133  	signingPublicKey := base64.StdEncoding.EncodeToString(publicKeyBytes)
   134  	signingPrivateKey := base64.StdEncoding.EncodeToString(privateKeyBytes)
   135  
   136  	// load payload
   137  
   138  	paveServerEntries := make(map[string][]string)
   139  
   140  	pavedPayloadOSLID := make(map[string]bool)
   141  
   142  	if payloadFilename != "" {
   143  		payloadJSON, err := ioutil.ReadFile(payloadFilename)
   144  		if err != nil {
   145  			fmt.Printf("failed loading payload file: %s\n", err)
   146  			os.Exit(1)
   147  		}
   148  
   149  		var payload []*struct {
   150  			OSLIDs      []string
   151  			ServerEntry string
   152  		}
   153  
   154  		err = json.Unmarshal(payloadJSON, &payload)
   155  		if err != nil {
   156  			fmt.Printf("failed unmarshaling payload file: %s\n", err)
   157  			os.Exit(1)
   158  		}
   159  
   160  		for _, item := range payload {
   161  			for _, oslID := range item.OSLIDs {
   162  				paveServerEntries[oslID] = append(
   163  					paveServerEntries[oslID], item.ServerEntry)
   164  				pavedPayloadOSLID[oslID] = false
   165  			}
   166  		}
   167  	}
   168  
   169  	// determine pave time range
   170  
   171  	paveTime := time.Now().UTC()
   172  
   173  	var startTime, endTime time.Time
   174  
   175  	if offset != 0 {
   176  		startTime = paveTime.Add(-offset)
   177  	} else {
   178  		// Default to the earliest scheme epoch.
   179  		startTime = paveTime
   180  		for _, scheme := range config.Schemes {
   181  			epoch, _ := time.Parse(time.RFC3339, scheme.Epoch)
   182  			if epoch.Before(startTime) {
   183  				startTime = epoch
   184  			}
   185  		}
   186  	}
   187  
   188  	if period != 0 {
   189  		endTime = startTime.Add(period)
   190  	} else {
   191  		// Default to at least one OSL period after "now",
   192  		// considering all schemes.
   193  		endTime = paveTime
   194  		for _, scheme := range config.Schemes {
   195  			oslDuration := scheme.GetOSLDuration()
   196  			if endTime.Add(oslDuration).After(endTime) {
   197  				endTime = endTime.Add(oslDuration)
   198  			}
   199  		}
   200  	}
   201  
   202  	// build list of all participating propagation channel IDs
   203  
   204  	allPropagationChannelIDs := make(map[string]bool)
   205  	for _, scheme := range config.Schemes {
   206  		for _, propagationChannelID := range scheme.PropagationChannelIDs {
   207  			allPropagationChannelIDs[propagationChannelID] = true
   208  		}
   209  	}
   210  
   211  	// pave a directory for each propagation channel
   212  
   213  	for propagationChannelID := range allPropagationChannelIDs {
   214  
   215  		paveFiles, err := config.Pave(
   216  			startTime,
   217  			endTime,
   218  			propagationChannelID,
   219  			signingPublicKey,
   220  			signingPrivateKey,
   221  			paveServerEntries,
   222  			omitMD5SumsSchemes,
   223  			omitEmptyOSLsSchemes,
   224  			func(logInfo *osl.PaveLogInfo) {
   225  				pavedPayloadOSLID[logInfo.OSLID] = true
   226  				fmt.Printf(
   227  					"paved %s: scheme %d, propagation channel ID %s, "+
   228  						"OSL time %s, OSL duration %s, server entries: %d\n",
   229  					logInfo.FileName,
   230  					logInfo.SchemeIndex,
   231  					logInfo.PropagationChannelID,
   232  					logInfo.OSLTime,
   233  					logInfo.OSLDuration,
   234  					logInfo.ServerEntryCount)
   235  			})
   236  		if err != nil {
   237  			fmt.Printf("failed paving: %s\n", err)
   238  			os.Exit(1)
   239  		}
   240  
   241  		if destinationDirectory != "" {
   242  
   243  			directory := filepath.Join(destinationDirectory, propagationChannelID)
   244  
   245  			err = os.MkdirAll(directory, 0755)
   246  			if err != nil {
   247  				fmt.Printf("failed creating output directory: %s\n", err)
   248  				os.Exit(1)
   249  			}
   250  
   251  			for _, paveFile := range paveFiles {
   252  				filename := filepath.Join(directory, paveFile.Name)
   253  				err = ioutil.WriteFile(filename, paveFile.Contents, 0755)
   254  				if err != nil {
   255  					fmt.Printf("error writing output file: %s\n", err)
   256  					os.Exit(1)
   257  				}
   258  			}
   259  		}
   260  	}
   261  
   262  	// fail if payload contains OSL IDs not in the config and time range
   263  
   264  	unknown := false
   265  	for oslID, paved := range pavedPayloadOSLID {
   266  		if !paved {
   267  			fmt.Printf(
   268  				"ignored %d server entries for unknown OSL ID: %s\n",
   269  				len(paveServerEntries[oslID]),
   270  				oslID)
   271  			unknown = true
   272  		}
   273  	}
   274  	if unknown {
   275  		fmt.Printf("payload contains unknown OSL IDs\n")
   276  		os.Exit(1)
   277  	}
   278  }
   279  
   280  type ints []int
   281  
   282  func (i *ints) String() string {
   283  	return fmt.Sprint(*i)
   284  }
   285  
   286  func (i *ints) Set(strValue string) error {
   287  	value, err := strconv.Atoi(strValue)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	*i = append(*i, value)
   292  	return nil
   293  }