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 }