github.com/Psiphon-Labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/obfuscator/obfuscatedSshConn.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 "encoding/binary" 25 std_errors "errors" 26 "io" 27 "io/ioutil" 28 "net" 29 30 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" 31 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors" 32 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng" 33 ) 34 35 const ( 36 SSH_MAX_SERVER_LINE_LENGTH = 1024 37 SSH_PACKET_PREFIX_LENGTH = 5 // uint32 + byte 38 SSH_MAX_PACKET_LENGTH = 256 * 1024 // OpenSSH max packet length 39 SSH_MSG_NEWKEYS = 21 40 SSH_MAX_PADDING_LENGTH = 255 // RFC 4253 sec. 6 41 SSH_PADDING_MULTIPLE = 16 // Default cipher block size 42 ) 43 44 // ObfuscatedSSHConn wraps a Conn and applies the obfuscated SSH protocol 45 // to the traffic on the connection: 46 // https://github.com/brl/obfuscated-openssh/blob/master/README.obfuscation 47 // 48 // ObfuscatedSSHConn is used to add obfuscation to golang's stock "ssh" 49 // client and server without modification to that standard library code. 50 // The underlying connection must be used for SSH traffic. This code 51 // injects the obfuscated seed message, applies obfuscated stream cipher 52 // transformations, and performs minimal parsing of the SSH protocol to 53 // determine when to stop obfuscation (after the first SSH_MSG_NEWKEYS is 54 // sent and received). 55 // 56 // WARNING: doesn't fully conform to net.Conn concurrency semantics: there's 57 // no synchronization of access to the read/writeBuffers, so concurrent 58 // calls to one of Read or Write will result in undefined behavior. 59 // 60 type ObfuscatedSSHConn struct { 61 net.Conn 62 mode ObfuscatedSSHConnMode 63 obfuscator *Obfuscator 64 readDeobfuscate func([]byte) 65 writeObfuscate func([]byte) 66 readState ObfuscatedSSHReadState 67 writeState ObfuscatedSSHWriteState 68 readBuffer *bytes.Buffer 69 writeBuffer *bytes.Buffer 70 transformBuffer *bytes.Buffer 71 legacyPadding bool 72 paddingLength int 73 paddingPRNG *prng.PRNG 74 } 75 76 type ObfuscatedSSHConnMode int 77 78 const ( 79 OBFUSCATION_CONN_MODE_CLIENT = iota 80 OBFUSCATION_CONN_MODE_SERVER 81 ) 82 83 type ObfuscatedSSHReadState int 84 85 const ( 86 OBFUSCATION_READ_STATE_IDENTIFICATION_LINES = iota 87 OBFUSCATION_READ_STATE_KEX_PACKETS 88 OBFUSCATION_READ_STATE_FLUSH 89 OBFUSCATION_READ_STATE_FINISHED 90 ) 91 92 type ObfuscatedSSHWriteState int 93 94 const ( 95 OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE = iota 96 OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING 97 OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE 98 OBFUSCATION_WRITE_STATE_KEX_PACKETS 99 OBFUSCATION_WRITE_STATE_FINISHED 100 ) 101 102 // NewObfuscatedSSHConn creates a new ObfuscatedSSHConn. 103 // The underlying conn must be used for SSH traffic and must have 104 // transferred no traffic. 105 // 106 // In client mode, NewObfuscatedSSHConn does not block or initiate network 107 // I/O. The obfuscation seed message is sent when Write() is first called. 108 // 109 // In server mode, NewObfuscatedSSHConn cannot completely initialize itself 110 // without the seed message from the client to derive obfuscation keys. So 111 // NewObfuscatedSSHConn blocks on reading the client seed message from the 112 // underlying conn. 113 // 114 // obfuscationPaddingPRNGSeed is required and used only in 115 // OBFUSCATION_CONN_MODE_CLIENT mode and allows for optional replay of the 116 // same padding: both in the initial obfuscator message and in the SSH KEX 117 // sequence. In OBFUSCATION_CONN_MODE_SERVER mode, the server obtains its PRNG 118 // seed from the client's initial obfuscator message, resulting in the server 119 // replaying its padding as well. 120 // 121 // seedHistory and irregularLogger are optional ObfuscatorConfig parameters 122 // used only in OBFUSCATION_CONN_MODE_SERVER. 123 func NewObfuscatedSSHConn( 124 mode ObfuscatedSSHConnMode, 125 conn net.Conn, 126 obfuscationKeyword string, 127 obfuscationPaddingPRNGSeed *prng.Seed, 128 minPadding, maxPadding *int, 129 seedHistory *SeedHistory, 130 irregularLogger func( 131 clientIP string, 132 err error, 133 logFields common.LogFields)) (*ObfuscatedSSHConn, error) { 134 135 var err error 136 var obfuscator *Obfuscator 137 var readDeobfuscate, writeObfuscate func([]byte) 138 var writeState ObfuscatedSSHWriteState 139 140 if mode == OBFUSCATION_CONN_MODE_CLIENT { 141 obfuscator, err = NewClientObfuscator( 142 &ObfuscatorConfig{ 143 Keyword: obfuscationKeyword, 144 PaddingPRNGSeed: obfuscationPaddingPRNGSeed, 145 MinPadding: minPadding, 146 MaxPadding: maxPadding, 147 }) 148 if err != nil { 149 return nil, errors.Trace(err) 150 } 151 readDeobfuscate = obfuscator.ObfuscateServerToClient 152 writeObfuscate = obfuscator.ObfuscateClientToServer 153 writeState = OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE 154 } else { 155 // NewServerObfuscator reads a seed message from conn 156 obfuscator, err = NewServerObfuscator( 157 &ObfuscatorConfig{ 158 Keyword: obfuscationKeyword, 159 SeedHistory: seedHistory, 160 IrregularLogger: irregularLogger, 161 }, 162 common.IPAddressFromAddr(conn.RemoteAddr()), 163 conn) 164 if err != nil { 165 166 // Obfuscated SSH protocol spec: 167 // "If these checks fail the server will continue reading and discarding all data 168 // until the client closes the connection without sending anything in response." 169 // 170 // This may be terminated by a server-side connection establishment timeout. 171 io.Copy(ioutil.Discard, conn) 172 173 return nil, errors.Trace(err) 174 } 175 readDeobfuscate = obfuscator.ObfuscateClientToServer 176 writeObfuscate = obfuscator.ObfuscateServerToClient 177 writeState = OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING 178 } 179 180 paddingPRNG, err := obfuscator.GetDerivedPRNG("obfuscated-ssh-padding") 181 if err != nil { 182 return nil, errors.Trace(err) 183 } 184 185 return &ObfuscatedSSHConn{ 186 Conn: conn, 187 mode: mode, 188 obfuscator: obfuscator, 189 readDeobfuscate: readDeobfuscate, 190 writeObfuscate: writeObfuscate, 191 readState: OBFUSCATION_READ_STATE_IDENTIFICATION_LINES, 192 writeState: writeState, 193 readBuffer: new(bytes.Buffer), 194 writeBuffer: new(bytes.Buffer), 195 transformBuffer: new(bytes.Buffer), 196 paddingLength: -1, 197 paddingPRNG: paddingPRNG, 198 }, nil 199 } 200 201 // NewClientObfuscatedSSHConn creates a client ObfuscatedSSHConn. See 202 // documentation in NewObfuscatedSSHConn. 203 func NewClientObfuscatedSSHConn( 204 conn net.Conn, 205 obfuscationKeyword string, 206 obfuscationPaddingPRNGSeed *prng.Seed, 207 minPadding, maxPadding *int) (*ObfuscatedSSHConn, error) { 208 209 return NewObfuscatedSSHConn( 210 OBFUSCATION_CONN_MODE_CLIENT, 211 conn, 212 obfuscationKeyword, 213 obfuscationPaddingPRNGSeed, 214 minPadding, maxPadding, 215 nil, 216 nil) 217 } 218 219 // NewServerObfuscatedSSHConn creates a server ObfuscatedSSHConn. See 220 // documentation in NewObfuscatedSSHConn. 221 func NewServerObfuscatedSSHConn( 222 conn net.Conn, 223 obfuscationKeyword string, 224 seedHistory *SeedHistory, 225 irregularLogger func( 226 clientIP string, 227 err error, 228 logFields common.LogFields)) (*ObfuscatedSSHConn, error) { 229 230 return NewObfuscatedSSHConn( 231 OBFUSCATION_CONN_MODE_SERVER, 232 conn, 233 obfuscationKeyword, 234 nil, 235 nil, nil, 236 seedHistory, 237 irregularLogger) 238 } 239 240 // GetDerivedPRNG creates a new PRNG with a seed derived from the 241 // ObfuscatedSSHConn padding seed and distinguished by the salt, which should 242 // be a unique identifier for each usage context. 243 // 244 // In OBFUSCATION_CONN_MODE_SERVER mode, the ObfuscatedSSHConn padding seed is 245 // obtained from the client, so derived PRNGs may be used to replay sequences 246 // post-initial obfuscator message. 247 func (conn *ObfuscatedSSHConn) GetDerivedPRNG(salt string) (*prng.PRNG, error) { 248 return conn.obfuscator.GetDerivedPRNG(salt) 249 } 250 251 // GetMetrics implements the common.MetricsSource interface. 252 func (conn *ObfuscatedSSHConn) GetMetrics() common.LogFields { 253 logFields := make(common.LogFields) 254 if conn.mode == OBFUSCATION_CONN_MODE_CLIENT { 255 paddingLength := conn.obfuscator.GetPaddingLength() 256 if paddingLength != -1 { 257 logFields["upstream_ossh_padding"] = paddingLength 258 } 259 } else { 260 if conn.paddingLength != -1 { 261 logFields["downstream_ossh_padding"] = conn.paddingLength 262 } 263 } 264 return logFields 265 } 266 267 // Read wraps standard Read, transparently applying the obfuscation 268 // transformations. 269 func (conn *ObfuscatedSSHConn) Read(buffer []byte) (int, error) { 270 if conn.readState == OBFUSCATION_READ_STATE_FINISHED { 271 return conn.Conn.Read(buffer) 272 } 273 n, err := conn.readAndTransform(buffer) 274 if err != nil { 275 err = errors.Trace(err) 276 } 277 return n, err 278 } 279 280 // Write wraps standard Write, transparently applying the obfuscation 281 // transformations. 282 func (conn *ObfuscatedSSHConn) Write(buffer []byte) (int, error) { 283 if conn.writeState == OBFUSCATION_WRITE_STATE_FINISHED { 284 return conn.Conn.Write(buffer) 285 } 286 err := conn.transformAndWrite(buffer) 287 if err != nil { 288 return 0, errors.Trace(err) 289 } 290 // Reports that we wrote all the bytes 291 // (although we may have buffered some or all) 292 return len(buffer), nil 293 } 294 295 // readAndTransform reads and transforms the downstream bytes stream 296 // while in an obfucation state. It parses the stream of bytes read 297 // looking for the first SSH_MSG_NEWKEYS packet sent from the peer, 298 // after which obfuscation is turned off. Since readAndTransform may 299 // read in more bytes that the higher-level conn.Read() can consume, 300 // read bytes are buffered and may be returned in subsequent calls. 301 // 302 // readAndTransform also implements a workaround for issues with 303 // ssh/transport.go exchangeVersions/readVersion and Psiphon's openssh 304 // server. 305 // 306 // Psiphon's server sends extra lines before the version line, as 307 // permitted by http://www.ietf.org/rfc/rfc4253.txt sec 4.2: 308 // The server MAY send other lines of data before sending the 309 // version string. [...] Clients MUST be able to process such lines. 310 // 311 // A comment in exchangeVersions explains that the golang code doesn't 312 // support this: 313 // Contrary to the RFC, we do not ignore lines that don't 314 // start with "SSH-2.0-" to make the library usable with 315 // nonconforming servers. 316 // 317 // In addition, Psiphon's server sends up to 512 characters per extra 318 // line. It's not clear that the 255 max string size in sec 4.2 refers 319 // to the extra lines as well, but in any case golang's code only 320 // supports 255 character lines. 321 // 322 // State OBFUSCATION_READ_STATE_IDENTIFICATION_LINES: in this 323 // state, extra lines are read and discarded. Once the peer's 324 // identification string line is read, it is buffered and returned 325 // as per the requested read buffer size. 326 // 327 // State OBFUSCATION_READ_STATE_KEX_PACKETS: reads, deobfuscates, 328 // and buffers full SSH packets, checking for SSH_MSG_NEWKEYS. Packet 329 // data is returned as per the requested read buffer size. 330 // 331 // State OBFUSCATION_READ_STATE_FLUSH: after SSH_MSG_NEWKEYS, no more 332 // packets are read by this function, but bytes from the SSH_MSG_NEWKEYS 333 // packet may need to be buffered due to partial reading. 334 func (conn *ObfuscatedSSHConn) readAndTransform(buffer []byte) (int, error) { 335 336 nextState := conn.readState 337 338 switch conn.readState { 339 case OBFUSCATION_READ_STATE_IDENTIFICATION_LINES: 340 // TODO: only client should accept multiple lines? 341 if conn.readBuffer.Len() == 0 { 342 for { 343 err := readSSHIdentificationLine( 344 conn.Conn, conn.readDeobfuscate, conn.readBuffer) 345 if err != nil { 346 return 0, errors.Trace(err) 347 } 348 if bytes.HasPrefix(conn.readBuffer.Bytes(), []byte("SSH-")) { 349 if bytes.Contains(conn.readBuffer.Bytes(), []byte("Ganymed")) { 350 conn.legacyPadding = true 351 } 352 break 353 } 354 // Discard extra line 355 conn.readBuffer.Truncate(0) 356 } 357 } 358 nextState = OBFUSCATION_READ_STATE_KEX_PACKETS 359 360 case OBFUSCATION_READ_STATE_KEX_PACKETS: 361 if conn.readBuffer.Len() == 0 { 362 isMsgNewKeys, err := readSSHPacket( 363 conn.Conn, conn.readDeobfuscate, conn.readBuffer) 364 if err != nil { 365 return 0, errors.Trace(err) 366 } 367 if isMsgNewKeys { 368 nextState = OBFUSCATION_READ_STATE_FLUSH 369 } 370 } 371 372 case OBFUSCATION_READ_STATE_FLUSH: 373 nextState = OBFUSCATION_READ_STATE_FINISHED 374 375 case OBFUSCATION_READ_STATE_FINISHED: 376 return 0, errors.TraceNew("invalid read state") 377 } 378 379 n, err := conn.readBuffer.Read(buffer) 380 if err == io.EOF { 381 err = nil 382 } 383 if err != nil { 384 return n, errors.Trace(err) 385 } 386 if conn.readBuffer.Len() == 0 { 387 conn.readState = nextState 388 if conn.readState == OBFUSCATION_READ_STATE_FINISHED { 389 // The buffer memory is no longer used 390 conn.readBuffer = nil 391 } 392 } 393 return n, nil 394 } 395 396 // transformAndWrite transforms the upstream bytes stream while in an 397 // obfucation state, buffers bytes as necessary for parsing, and writes 398 // transformed bytes to the network connection. Bytes are obfuscated until 399 // after the first SSH_MSG_NEWKEYS packet is sent. 400 // 401 // There are two mode-specific states: 402 // 403 // State OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE: the initial 404 // state, when the client has not sent any data. In this state, the seed message 405 // is injected into the client output stream. 406 // 407 // State OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING: the 408 // initial state, when the server has not sent any data. In this state, the 409 // additional lines of padding are injected into the server output stream. 410 // This padding is a partial defense against traffic analysis against the 411 // otherwise-fixed size server version line. This makes use of the 412 // "other lines of data" allowance, before the version line, which clients 413 // will ignore (http://tools.ietf.org/html/rfc4253#section-4.2). 414 // 415 // State OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE: before 416 // packets are sent, the SSH peer sends an identification line terminated by CRLF: 417 // http://www.ietf.org/rfc/rfc4253.txt sec 4.2. 418 // In this state, the CRLF terminator is used to parse message boundaries. 419 // 420 // State OBFUSCATION_WRITE_STATE_KEX_PACKETS: follows the binary 421 // packet protocol, parsing each packet until the first SSH_MSG_NEWKEYS. 422 // http://www.ietf.org/rfc/rfc4253.txt sec 6: 423 // uint32 packet_length 424 // byte padding_length 425 // byte[n1] payload; n1 = packet_length - padding_length - 1 426 // byte[n2] random padding; n2 = padding_length 427 // byte[m] mac (Message Authentication Code - MAC); m = mac_length 428 // m is 0 as no MAC ha yet been negotiated. 429 // http://www.ietf.org/rfc/rfc4253.txt sec 7.3, 12: 430 // The payload for SSH_MSG_NEWKEYS is one byte, the packet type, value 21. 431 // 432 // SSH packet padding values are transformed to achieve random, variable length 433 // padding during the KEX phase as a partial defense against traffic analysis. 434 // (The transformer can do this since only the payload and not the padding of 435 // these packets is authenticated in the "exchange hash"). 436 func (conn *ObfuscatedSSHConn) transformAndWrite(buffer []byte) error { 437 438 // The seed message (client) and identification line padding (server) 439 // are injected before any standard SSH traffic. 440 if conn.writeState == OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE { 441 _, err := conn.Conn.Write(conn.obfuscator.SendSeedMessage()) 442 if err != nil { 443 return errors.Trace(err) 444 } 445 conn.writeState = OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE 446 } else if conn.writeState == OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING { 447 padding := makeServerIdentificationLinePadding(conn.paddingPRNG) 448 conn.paddingLength = len(padding) 449 conn.writeObfuscate(padding) 450 _, err := conn.Conn.Write(padding) 451 if err != nil { 452 return errors.Trace(err) 453 } 454 conn.writeState = OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE 455 } 456 457 // writeBuffer is used to buffer bytes received from Write() until a 458 // complete SSH message is received. transformBuffer is used as a scratch 459 // buffer for size-changing tranformations, including padding transforms. 460 // All data flows as follows: 461 // conn.Write() -> writeBuffer -> transformBuffer -> conn.Conn.Write() 462 463 conn.writeBuffer.Write(buffer) 464 465 switch conn.writeState { 466 case OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE: 467 hasIdentificationLine := extractSSHIdentificationLine( 468 conn.writeBuffer, conn.transformBuffer) 469 if hasIdentificationLine { 470 conn.writeState = OBFUSCATION_WRITE_STATE_KEX_PACKETS 471 } 472 473 case OBFUSCATION_WRITE_STATE_KEX_PACKETS: 474 hasMsgNewKeys, err := extractSSHPackets( 475 conn.paddingPRNG, 476 conn.legacyPadding, 477 conn.writeBuffer, 478 conn.transformBuffer) 479 if err != nil { 480 return errors.Trace(err) 481 } 482 if hasMsgNewKeys { 483 conn.writeState = OBFUSCATION_WRITE_STATE_FINISHED 484 } 485 486 case OBFUSCATION_WRITE_STATE_FINISHED: 487 return errors.TraceNew("invalid write state") 488 } 489 490 if conn.transformBuffer.Len() > 0 { 491 sendData := conn.transformBuffer.Next(conn.transformBuffer.Len()) 492 conn.writeObfuscate(sendData) 493 _, err := conn.Conn.Write(sendData) 494 if err != nil { 495 return errors.Trace(err) 496 } 497 } 498 499 if conn.writeState == OBFUSCATION_WRITE_STATE_FINISHED { 500 if conn.writeBuffer.Len() > 0 { 501 // After SSH_MSG_NEWKEYS, any remaining bytes are un-obfuscated 502 _, err := conn.Conn.Write(conn.writeBuffer.Bytes()) 503 if err != nil { 504 return errors.Trace(err) 505 } 506 } 507 // The buffer memory is no longer used 508 conn.writeBuffer = nil 509 conn.transformBuffer = nil 510 } 511 return nil 512 } 513 514 func readSSHIdentificationLine( 515 conn net.Conn, 516 deobfuscate func([]byte), 517 readBuffer *bytes.Buffer) error { 518 519 // TODO: less redundant string searching? 520 var oneByte [1]byte 521 var validLine = false 522 readBuffer.Grow(SSH_MAX_SERVER_LINE_LENGTH) 523 for i := 0; i < SSH_MAX_SERVER_LINE_LENGTH; i++ { 524 _, err := io.ReadFull(conn, oneByte[:]) 525 if err != nil { 526 return errors.Trace(err) 527 } 528 deobfuscate(oneByte[:]) 529 readBuffer.WriteByte(oneByte[0]) 530 if bytes.HasSuffix(readBuffer.Bytes(), []byte("\r\n")) { 531 validLine = true 532 break 533 } 534 } 535 if !validLine { 536 return errors.TraceNew("invalid identification line") 537 } 538 return nil 539 } 540 541 func readSSHPacket( 542 conn net.Conn, 543 deobfuscate func([]byte), 544 readBuffer *bytes.Buffer) (bool, error) { 545 546 prefixOffset := readBuffer.Len() 547 548 readBuffer.Grow(SSH_PACKET_PREFIX_LENGTH) 549 n, err := readBuffer.ReadFrom(io.LimitReader(conn, SSH_PACKET_PREFIX_LENGTH)) 550 if err == nil && n != SSH_PACKET_PREFIX_LENGTH { 551 err = std_errors.New("unxpected number of bytes read") 552 } 553 if err != nil { 554 return false, errors.Trace(err) 555 } 556 557 prefix := readBuffer.Bytes()[prefixOffset : prefixOffset+SSH_PACKET_PREFIX_LENGTH] 558 deobfuscate(prefix) 559 560 _, _, payloadLength, messageLength, err := getSSHPacketPrefix(prefix) 561 if err != nil { 562 return false, errors.Trace(err) 563 } 564 565 remainingReadLength := messageLength - SSH_PACKET_PREFIX_LENGTH 566 readBuffer.Grow(remainingReadLength) 567 n, err = readBuffer.ReadFrom(io.LimitReader(conn, int64(remainingReadLength))) 568 if err == nil && n != int64(remainingReadLength) { 569 err = std_errors.New("unxpected number of bytes read") 570 } 571 if err != nil { 572 return false, errors.Trace(err) 573 } 574 575 remainingBytes := readBuffer.Bytes()[prefixOffset+SSH_PACKET_PREFIX_LENGTH:] 576 deobfuscate(remainingBytes) 577 578 isMsgNewKeys := false 579 if payloadLength > 0 { 580 packetType := int(readBuffer.Bytes()[prefixOffset+SSH_PACKET_PREFIX_LENGTH]) 581 if packetType == SSH_MSG_NEWKEYS { 582 isMsgNewKeys = true 583 } 584 } 585 return isMsgNewKeys, nil 586 } 587 588 // From the original patch to sshd.c: 589 // https://bitbucket.org/psiphon/psiphon-circumvention-system/commits/f40865ce624b680be840dc2432283c8137bd896d 590 func makeServerIdentificationLinePadding(prng *prng.PRNG) []byte { 591 592 paddingLength := prng.Intn(OBFUSCATE_MAX_PADDING - 2 + 1) // 2 = CRLF 593 paddingLength += 2 594 595 padding := make([]byte, paddingLength) 596 597 // For backwards compatibility with some clients, send no more than 512 characters 598 // per line (including CRLF). To keep the padding distribution between 0 and OBFUSCATE_MAX_PADDING 599 // characters, we send lines that add up to padding_length characters including all CRLFs. 600 601 minLineLength := 2 602 maxLineLength := 512 603 lineStartIndex := 0 604 for paddingLength > 0 { 605 lineLength := paddingLength 606 if lineLength > maxLineLength { 607 lineLength = maxLineLength 608 } 609 // Leave enough padding allowance to send a full CRLF on the last line 610 if paddingLength-lineLength > 0 && 611 paddingLength-lineLength < minLineLength { 612 lineLength -= minLineLength - (paddingLength - lineLength) 613 } 614 padding[lineStartIndex+lineLength-2] = '\r' 615 padding[lineStartIndex+lineLength-1] = '\n' 616 lineStartIndex += lineLength 617 paddingLength -= lineLength 618 } 619 620 return padding 621 } 622 623 func extractSSHIdentificationLine(writeBuffer, transformBuffer *bytes.Buffer) bool { 624 index := bytes.Index(writeBuffer.Bytes(), []byte("\r\n")) 625 if index != -1 { 626 lineLength := index + 2 // + 2 for \r\n 627 transformBuffer.Write(writeBuffer.Next(lineLength)) 628 return true 629 } 630 return false 631 } 632 633 func extractSSHPackets( 634 prng *prng.PRNG, 635 legacyPadding bool, 636 writeBuffer, transformBuffer *bytes.Buffer) (bool, error) { 637 638 hasMsgNewKeys := false 639 for writeBuffer.Len() >= SSH_PACKET_PREFIX_LENGTH { 640 641 packetLength, paddingLength, payloadLength, messageLength, err := getSSHPacketPrefix( 642 writeBuffer.Bytes()[:SSH_PACKET_PREFIX_LENGTH]) 643 if err != nil { 644 return false, errors.Trace(err) 645 } 646 647 if writeBuffer.Len() < messageLength { 648 // We don't have the complete packet yet 649 break 650 } 651 652 packet := writeBuffer.Next(messageLength) 653 654 if payloadLength > 0 { 655 packetType := int(packet[SSH_PACKET_PREFIX_LENGTH]) 656 if packetType == SSH_MSG_NEWKEYS { 657 hasMsgNewKeys = true 658 } 659 } 660 661 transformedPacketOffset := transformBuffer.Len() 662 transformBuffer.Write(packet) 663 transformedPacket := transformBuffer.Bytes()[transformedPacketOffset:] 664 665 // Padding transformation 666 667 extraPaddingLength := 0 668 669 if !legacyPadding { 670 // This does not satisfy RFC 4253 sec. 6 constraints: 671 // - The goal is to vary packet sizes as much as possible. 672 // - We implement both the client and server sides and both sides accept 673 // less constrained paddings (for plaintext packets). 674 possibleExtraPaddingLength := (SSH_MAX_PADDING_LENGTH - paddingLength) 675 if possibleExtraPaddingLength > 0 { 676 677 // extraPaddingLength is integer in range [0, possiblePadding + 1) 678 extraPaddingLength = prng.Intn(possibleExtraPaddingLength + 1) 679 } 680 } else { 681 // See RFC 4253 sec. 6 for constraints 682 possiblePaddings := (SSH_MAX_PADDING_LENGTH - paddingLength) / SSH_PADDING_MULTIPLE 683 if possiblePaddings > 0 { 684 685 // selectedPadding is integer in range [0, possiblePaddings) 686 selectedPadding := prng.Intn(possiblePaddings) 687 extraPaddingLength = selectedPadding * SSH_PADDING_MULTIPLE 688 } 689 } 690 691 extraPadding := prng.Bytes(extraPaddingLength) 692 693 setSSHPacketPrefix( 694 transformedPacket, 695 packetLength+extraPaddingLength, 696 paddingLength+extraPaddingLength) 697 698 transformBuffer.Write(extraPadding) 699 } 700 701 return hasMsgNewKeys, nil 702 } 703 704 func getSSHPacketPrefix(buffer []byte) (int, int, int, int, error) { 705 706 packetLength := int(binary.BigEndian.Uint32(buffer[0 : SSH_PACKET_PREFIX_LENGTH-1])) 707 708 if packetLength < 1 || packetLength > SSH_MAX_PACKET_LENGTH { 709 return 0, 0, 0, 0, errors.TraceNew("invalid SSH packet length") 710 } 711 712 paddingLength := int(buffer[SSH_PACKET_PREFIX_LENGTH-1]) 713 payloadLength := packetLength - paddingLength - 1 714 messageLength := SSH_PACKET_PREFIX_LENGTH + packetLength - 1 715 716 return packetLength, paddingLength, payloadLength, messageLength, nil 717 } 718 719 func setSSHPacketPrefix(buffer []byte, packetLength, paddingLength int) { 720 binary.BigEndian.PutUint32(buffer, uint32(packetLength)) 721 buffer[SSH_PACKET_PREFIX_LENGTH-1] = byte(paddingLength) 722 }