github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/obfuscator/obfuscator.go (about) 1 /* 2 * Copyright (c) 2015, 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 obfuscator 21 22 import ( 23 "bytes" 24 "crypto/rc4" 25 "crypto/sha1" 26 "encoding/binary" 27 "io" 28 29 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" 30 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 31 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng" 32 ) 33 34 const ( 35 OBFUSCATE_SEED_LENGTH = 16 36 OBFUSCATE_KEY_LENGTH = 16 37 OBFUSCATE_HASH_ITERATIONS = 6000 38 OBFUSCATE_MAX_PADDING = 8192 39 OBFUSCATE_MAGIC_VALUE = 0x0BF5CA7E 40 OBFUSCATE_CLIENT_TO_SERVER_IV = "client_to_server" 41 OBFUSCATE_SERVER_TO_CLIENT_IV = "server_to_client" 42 ) 43 44 // Obfuscator implements the seed message, key derivation, and 45 // stream ciphers for: 46 // https://github.com/brl/obfuscated-openssh/blob/master/README.obfuscation 47 // 48 // Limitation: the RC4 cipher is vulnerable to ciphertext malleability and 49 // the "magic" value provides only weak authentication due to its small 50 // size. Increasing the size of the magic field will break compatibility 51 // with legacy clients. New protocols and schemes should not use this 52 // obfuscator. 53 type Obfuscator struct { 54 seedMessage []byte 55 paddingLength int 56 clientToServerCipher *rc4.Cipher 57 serverToClientCipher *rc4.Cipher 58 paddingPRNGSeed *prng.Seed 59 paddingPRNG *prng.PRNG 60 } 61 62 // ObfuscatorConfig specifies an Obfuscator configuration. 63 type ObfuscatorConfig struct { 64 Keyword string 65 PaddingPRNGSeed *prng.Seed 66 MinPadding *int 67 MaxPadding *int 68 69 // SeedHistory and IrregularLogger are optional parameters used only by 70 // server obfuscators. 71 72 SeedHistory *SeedHistory 73 StrictHistoryMode bool 74 IrregularLogger func(clientIP string, err error, logFields common.LogFields) 75 } 76 77 // NewClientObfuscator creates a new Obfuscator, staging a seed message to be 78 // sent to the server (by the caller) and initializing stream ciphers to 79 // obfuscate data. 80 // 81 // ObfuscatorConfig.PaddingPRNGSeed allows for optional replay of the 82 // obfuscator padding and must not be nil. 83 func NewClientObfuscator( 84 config *ObfuscatorConfig) (obfuscator *Obfuscator, err error) { 85 86 if config.Keyword == "" { 87 return nil, errors.TraceNew("missing keyword") 88 } 89 90 if config.PaddingPRNGSeed == nil { 91 return nil, errors.TraceNew("missing padding seed") 92 } 93 94 paddingPRNG := prng.NewPRNGWithSeed(config.PaddingPRNGSeed) 95 96 obfuscatorSeed, err := common.MakeSecureRandomBytes(OBFUSCATE_SEED_LENGTH) 97 if err != nil { 98 return nil, errors.Trace(err) 99 } 100 101 clientToServerCipher, serverToClientCipher, err := initObfuscatorCiphers(config, obfuscatorSeed) 102 if err != nil { 103 return nil, errors.Trace(err) 104 } 105 106 // The first prng.SEED_LENGTH bytes of the initial obfuscator message 107 // padding field is used by the server as a seed for its obfuscator 108 // padding and other protocol attributes (directly and via 109 // GetDerivedPRNG). This allows for optional downstream replay of these 110 // protocol attributes. Accordingly, the minimum padding is set to at 111 // least prng.SEED_LENGTH. 112 113 minPadding := prng.SEED_LENGTH 114 if config.MinPadding != nil && 115 *config.MinPadding >= prng.SEED_LENGTH && 116 *config.MinPadding <= OBFUSCATE_MAX_PADDING { 117 minPadding = *config.MinPadding 118 } 119 120 maxPadding := OBFUSCATE_MAX_PADDING 121 if config.MaxPadding != nil && 122 *config.MaxPadding >= prng.SEED_LENGTH && 123 *config.MaxPadding <= OBFUSCATE_MAX_PADDING && 124 *config.MaxPadding >= minPadding { 125 maxPadding = *config.MaxPadding 126 } 127 128 seedMessage, paddingLength, err := makeSeedMessage( 129 paddingPRNG, minPadding, maxPadding, obfuscatorSeed, clientToServerCipher) 130 if err != nil { 131 return nil, errors.Trace(err) 132 } 133 134 return &Obfuscator{ 135 seedMessage: seedMessage, 136 paddingLength: paddingLength, 137 clientToServerCipher: clientToServerCipher, 138 serverToClientCipher: serverToClientCipher, 139 paddingPRNGSeed: config.PaddingPRNGSeed, 140 paddingPRNG: paddingPRNG}, nil 141 } 142 143 // NewServerObfuscator creates a new Obfuscator, reading a seed message directly 144 // from the clientReader and initializing stream ciphers to obfuscate data. 145 // 146 // ObfuscatorConfig.PaddingPRNGSeed is not used, as the server obtains a PRNG 147 // seed from the client's initial obfuscator message; this scheme allows for 148 // optional replay of the downstream obfuscator padding. 149 // 150 // The clientIP value is used by the SeedHistory, which retains client IP values 151 // for a short time. See SeedHistory documentation. 152 func NewServerObfuscator( 153 config *ObfuscatorConfig, clientIP string, clientReader io.Reader) (obfuscator *Obfuscator, err error) { 154 155 if config.Keyword == "" { 156 return nil, errors.TraceNew("missing keyword") 157 } 158 159 clientToServerCipher, serverToClientCipher, paddingPRNGSeed, err := readSeedMessage( 160 config, clientIP, clientReader) 161 if err != nil { 162 return nil, errors.Trace(err) 163 } 164 165 return &Obfuscator{ 166 paddingLength: -1, 167 clientToServerCipher: clientToServerCipher, 168 serverToClientCipher: serverToClientCipher, 169 paddingPRNGSeed: paddingPRNGSeed, 170 paddingPRNG: prng.NewPRNGWithSeed(paddingPRNGSeed), 171 }, nil 172 } 173 174 // GetDerivedPRNG creates a new PRNG with a seed derived from the obfuscator 175 // padding seed and distinguished by the salt, which should be a unique 176 // identifier for each usage context. 177 // 178 // For NewServerObfuscator, the obfuscator padding seed is obtained from the 179 // client, so derived PRNGs may be used to replay sequences post-initial 180 // obfuscator message. 181 func (obfuscator *Obfuscator) GetDerivedPRNG(salt string) (*prng.PRNG, error) { 182 return prng.NewPRNGWithSaltedSeed(obfuscator.paddingPRNGSeed, salt) 183 } 184 185 // GetPaddingLength returns the client seed message padding length. Only valid 186 // for NewClientObfuscator. 187 func (obfuscator *Obfuscator) GetPaddingLength() int { 188 return obfuscator.paddingLength 189 } 190 191 // SendSeedMessage returns the seed message created in NewObfuscatorClient, 192 // removing the reference so that it may be garbage collected. 193 func (obfuscator *Obfuscator) SendSeedMessage() []byte { 194 seedMessage := obfuscator.seedMessage 195 obfuscator.seedMessage = nil 196 return seedMessage 197 } 198 199 // ObfuscateClientToServer applies the client RC4 stream to the bytes in buffer. 200 func (obfuscator *Obfuscator) ObfuscateClientToServer(buffer []byte) { 201 obfuscator.clientToServerCipher.XORKeyStream(buffer, buffer) 202 } 203 204 // ObfuscateServerToClient applies the server RC4 stream to the bytes in buffer. 205 func (obfuscator *Obfuscator) ObfuscateServerToClient(buffer []byte) { 206 obfuscator.serverToClientCipher.XORKeyStream(buffer, buffer) 207 } 208 209 func initObfuscatorCiphers( 210 config *ObfuscatorConfig, obfuscatorSeed []byte) (*rc4.Cipher, *rc4.Cipher, error) { 211 212 clientToServerKey, err := deriveKey(obfuscatorSeed, []byte(config.Keyword), []byte(OBFUSCATE_CLIENT_TO_SERVER_IV)) 213 if err != nil { 214 return nil, nil, errors.Trace(err) 215 } 216 217 serverToClientKey, err := deriveKey(obfuscatorSeed, []byte(config.Keyword), []byte(OBFUSCATE_SERVER_TO_CLIENT_IV)) 218 if err != nil { 219 return nil, nil, errors.Trace(err) 220 } 221 222 clientToServerCipher, err := rc4.NewCipher(clientToServerKey) 223 if err != nil { 224 return nil, nil, errors.Trace(err) 225 } 226 227 serverToClientCipher, err := rc4.NewCipher(serverToClientKey) 228 if err != nil { 229 return nil, nil, errors.Trace(err) 230 } 231 232 return clientToServerCipher, serverToClientCipher, nil 233 } 234 235 func deriveKey(obfuscatorSeed, keyword, iv []byte) ([]byte, error) { 236 h := sha1.New() 237 h.Write(obfuscatorSeed) 238 h.Write(keyword) 239 h.Write(iv) 240 digest := h.Sum(nil) 241 for i := 0; i < OBFUSCATE_HASH_ITERATIONS; i++ { 242 h.Reset() 243 h.Write(digest) 244 digest = h.Sum(nil) 245 } 246 if len(digest) < OBFUSCATE_KEY_LENGTH { 247 return nil, errors.TraceNew("insufficient bytes for obfuscation key") 248 } 249 return digest[0:OBFUSCATE_KEY_LENGTH], nil 250 } 251 252 func makeSeedMessage( 253 paddingPRNG *prng.PRNG, 254 minPadding, maxPadding int, 255 obfuscatorSeed []byte, 256 clientToServerCipher *rc4.Cipher) ([]byte, int, error) { 257 258 padding := paddingPRNG.Padding(minPadding, maxPadding) 259 buffer := new(bytes.Buffer) 260 err := binary.Write(buffer, binary.BigEndian, obfuscatorSeed) 261 if err != nil { 262 return nil, 0, errors.Trace(err) 263 } 264 err = binary.Write(buffer, binary.BigEndian, uint32(OBFUSCATE_MAGIC_VALUE)) 265 if err != nil { 266 return nil, 0, errors.Trace(err) 267 } 268 err = binary.Write(buffer, binary.BigEndian, uint32(len(padding))) 269 if err != nil { 270 return nil, 0, errors.Trace(err) 271 } 272 err = binary.Write(buffer, binary.BigEndian, padding) 273 if err != nil { 274 return nil, 0, errors.Trace(err) 275 } 276 seedMessage := buffer.Bytes() 277 clientToServerCipher.XORKeyStream(seedMessage[len(obfuscatorSeed):], seedMessage[len(obfuscatorSeed):]) 278 return seedMessage, len(padding), nil 279 } 280 281 func readSeedMessage( 282 config *ObfuscatorConfig, 283 clientIP string, 284 clientReader io.Reader) (*rc4.Cipher, *rc4.Cipher, *prng.Seed, error) { 285 286 seed := make([]byte, OBFUSCATE_SEED_LENGTH) 287 _, err := io.ReadFull(clientReader, seed) 288 if err != nil { 289 return nil, nil, nil, errors.Trace(err) 290 } 291 292 // Irregular events that indicate an invalid client are logged via 293 // IrregularLogger. Note that event detection isn't infallible. For example, 294 // a man-in-the-middle may have manipulated the seed message sent by a valid 295 // client; or with a very small probability a valid client may generate a 296 // duplicate seed message. 297 // 298 // Another false positive case: a retired server IP may be recycled and 299 // deployed with a new obfuscation key; legitimate clients may still attempt 300 // to connect using the old obfuscation key; this case is partically 301 // mitigated by the server entry pruning mechanism. 302 // 303 // Network I/O failures (e.g., failure to read the expected number of seed 304 // message bytes) are not considered a reliable indicator of irregular 305 // events. 306 307 // To distinguish different cases, irregular tunnel logs should indicate 308 // which function called NewServerObfuscator. 309 errBackTrace := "obfuscator.NewServerObfuscator" 310 311 if config.SeedHistory != nil { 312 ok, duplicateLogFields := config.SeedHistory.AddNew( 313 config.StrictHistoryMode, clientIP, "obfuscator-seed", seed) 314 errStr := "duplicate obfuscation seed" 315 if duplicateLogFields != nil { 316 if config.IrregularLogger != nil { 317 config.IrregularLogger( 318 clientIP, 319 errors.BackTraceNew(errBackTrace, errStr), 320 *duplicateLogFields) 321 } 322 } 323 if !ok { 324 return nil, nil, nil, errors.TraceNew(errStr) 325 } 326 } 327 328 clientToServerCipher, serverToClientCipher, err := initObfuscatorCiphers(config, seed) 329 if err != nil { 330 return nil, nil, nil, errors.Trace(err) 331 } 332 333 fixedLengthFields := make([]byte, 8) // 4 bytes each for magic value and padding length 334 _, err = io.ReadFull(clientReader, fixedLengthFields) 335 if err != nil { 336 return nil, nil, nil, errors.Trace(err) 337 } 338 339 clientToServerCipher.XORKeyStream(fixedLengthFields, fixedLengthFields) 340 341 buffer := bytes.NewReader(fixedLengthFields) 342 343 // The magic value must be validated before acting on paddingLength as 344 // paddingLength validation is vulnerable to a chosen ciphertext probing 345 // attack: only a fixed number of any possible byte value for each 346 // paddingLength is valid. 347 348 var magicValue, paddingLength int32 349 err = binary.Read(buffer, binary.BigEndian, &magicValue) 350 if err != nil { 351 return nil, nil, nil, errors.Trace(err) 352 } 353 err = binary.Read(buffer, binary.BigEndian, &paddingLength) 354 if err != nil { 355 return nil, nil, nil, errors.Trace(err) 356 } 357 358 errStr := "" 359 360 if magicValue != OBFUSCATE_MAGIC_VALUE { 361 errStr = "invalid magic value" 362 } 363 364 if errStr == "" && (paddingLength < 0 || paddingLength > OBFUSCATE_MAX_PADDING) { 365 errStr = "invalid padding length" 366 } 367 368 if errStr != "" { 369 if config.IrregularLogger != nil { 370 config.IrregularLogger( 371 clientIP, 372 errors.BackTraceNew(errBackTrace, errStr), 373 nil) 374 } 375 return nil, nil, nil, errors.TraceNew(errStr) 376 } 377 378 padding := make([]byte, paddingLength) 379 _, err = io.ReadFull(clientReader, padding) 380 if err != nil { 381 return nil, nil, nil, errors.Trace(err) 382 } 383 384 clientToServerCipher.XORKeyStream(padding, padding) 385 386 // Use the first prng.SEED_LENGTH bytes of padding as a PRNG seed for 387 // subsequent operations. This allows the client to direct server-side 388 // replay of certain protocol attributes. 389 // 390 // Since legacy clients may send < prng.SEED_LENGTH bytes of padding, 391 // generate a new seed in that case. 392 393 var paddingPRNGSeed *prng.Seed 394 395 if len(padding) >= prng.SEED_LENGTH { 396 paddingPRNGSeed = new(prng.Seed) 397 copy(paddingPRNGSeed[:], padding[0:prng.SEED_LENGTH]) 398 } else { 399 paddingPRNGSeed, err = prng.NewSeed() 400 if err != nil { 401 return nil, nil, nil, errors.Trace(err) 402 } 403 } 404 405 return clientToServerCipher, serverToClientCipher, paddingPRNGSeed, nil 406 }