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  }