obfuscatedSshConn.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  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. package psiphon
  20. import (
  21. "bytes"
  22. "encoding/binary"
  23. "errors"
  24. "io"
  25. "net"
  26. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  27. )
  28. const (
  29. SSH_MAX_SERVER_LINE_LENGTH = 1024
  30. SSH_PACKET_PREFIX_LENGTH = 5 // uint32 + byte
  31. SSH_MAX_PACKET_LENGTH = 256 * 1024 // OpenSSH max packet length
  32. SSH_MSG_NEWKEYS = 21
  33. SSH_MAX_PADDING_LENGTH = 255 // RFC 4253 sec. 6
  34. SSH_PADDING_MULTIPLE = 16 // Default cipher block size
  35. )
  36. // ObfuscatedSshConn wraps a Conn and applies the obfuscated SSH protocol
  37. // to the traffic on the connection:
  38. // https://github.com/brl/obfuscated-openssh/blob/master/README.obfuscation
  39. //
  40. // ObfuscatedSshConn is used to add obfuscation to golang's stock ssh
  41. // client and server without modification to that standard library code.
  42. // The underlying connection must be used for SSH traffic. This code
  43. // injects the obfuscated seed message, applies obfuscated stream cipher
  44. // transformations, and performs minimal parsing of the SSH protocol to
  45. // determine when to stop obfuscation (after the first SSH_MSG_NEWKEYS is
  46. // sent and received).
  47. //
  48. // WARNING: doesn't fully conform to net.Conn concurrency semantics: there's
  49. // no synchronization of access to the read/writeBuffers, so concurrent
  50. // calls to one of Read or Write will result in undefined behavior.
  51. //
  52. type ObfuscatedSshConn struct {
  53. net.Conn
  54. mode ObfuscatedSshConnMode
  55. obfuscator *Obfuscator
  56. readDeobfuscate func([]byte)
  57. writeObfuscate func([]byte)
  58. readState ObfuscatedSshReadState
  59. writeState ObfuscatedSshWriteState
  60. readBuffer []byte
  61. writeBuffer []byte
  62. }
  63. type ObfuscatedSshConnMode int
  64. const (
  65. OBFUSCATION_CONN_MODE_CLIENT = iota
  66. OBFUSCATION_CONN_MODE_SERVER
  67. )
  68. type ObfuscatedSshReadState int
  69. const (
  70. OBFUSCATION_READ_STATE_IDENTIFICATION_LINES = iota
  71. OBFUSCATION_READ_STATE_KEX_PACKETS
  72. OBFUSCATION_READ_STATE_FLUSH
  73. OBFUSCATION_READ_STATE_FINISHED
  74. )
  75. type ObfuscatedSshWriteState int
  76. const (
  77. OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE = iota
  78. OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING
  79. OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE
  80. OBFUSCATION_WRITE_STATE_KEX_PACKETS
  81. OBFUSCATION_WRITE_STATE_FINISHED
  82. )
  83. // NewObfuscatedSshConn creates a new ObfuscatedSshConn.
  84. // The underlying conn must be used for SSH traffic and must have
  85. // transferred no traffic.
  86. //
  87. // In client mode, NewObfuscatedSshConn does not block or initiate network
  88. // I/O. The obfuscation seed message is sent when Write() is first called.
  89. //
  90. // In server mode, NewObfuscatedSshConn cannot completely initialize itself
  91. // without the seed message from the client to derive obfuscation keys. So
  92. // NewObfuscatedSshConn blocks on reading the client seed message from the
  93. // underlying conn.
  94. //
  95. func NewObfuscatedSshConn(
  96. mode ObfuscatedSshConnMode,
  97. conn net.Conn,
  98. obfuscationKeyword string) (*ObfuscatedSshConn, error) {
  99. var err error
  100. var obfuscator *Obfuscator
  101. var readDeobfuscate, writeObfuscate func([]byte)
  102. var writeState ObfuscatedSshWriteState
  103. if mode == OBFUSCATION_CONN_MODE_CLIENT {
  104. obfuscator, err = NewClientObfuscator(&ObfuscatorConfig{Keyword: obfuscationKeyword})
  105. if err != nil {
  106. return nil, common.ContextError(err)
  107. }
  108. readDeobfuscate = obfuscator.ObfuscateServerToClient
  109. writeObfuscate = obfuscator.ObfuscateClientToServer
  110. writeState = OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE
  111. } else {
  112. // NewServerObfuscator reads a seed message from conn
  113. obfuscator, err = NewServerObfuscator(
  114. conn, &ObfuscatorConfig{Keyword: obfuscationKeyword})
  115. if err != nil {
  116. // TODO: readForver() equivilent
  117. return nil, common.ContextError(err)
  118. }
  119. readDeobfuscate = obfuscator.ObfuscateClientToServer
  120. writeObfuscate = obfuscator.ObfuscateServerToClient
  121. writeState = OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING
  122. }
  123. return &ObfuscatedSshConn{
  124. Conn: conn,
  125. mode: mode,
  126. obfuscator: obfuscator,
  127. readDeobfuscate: readDeobfuscate,
  128. writeObfuscate: writeObfuscate,
  129. readState: OBFUSCATION_READ_STATE_IDENTIFICATION_LINES,
  130. writeState: writeState,
  131. }, nil
  132. }
  133. // Read wraps standard Read, transparently applying the obfuscation
  134. // transformations.
  135. func (conn *ObfuscatedSshConn) Read(buffer []byte) (n int, err error) {
  136. if conn.readState == OBFUSCATION_READ_STATE_FINISHED {
  137. return conn.Conn.Read(buffer)
  138. }
  139. return conn.readAndTransform(buffer)
  140. }
  141. // Write wraps standard Write, transparently applying the obfuscation
  142. // transformations.
  143. func (conn *ObfuscatedSshConn) Write(buffer []byte) (n int, err error) {
  144. if conn.writeState == OBFUSCATION_WRITE_STATE_FINISHED {
  145. return conn.Conn.Write(buffer)
  146. }
  147. err = conn.transformAndWrite(buffer)
  148. if err != nil {
  149. return 0, common.ContextError(err)
  150. }
  151. // Reports that we wrote all the bytes
  152. // (althogh we may have buffered some or all)
  153. return len(buffer), nil
  154. }
  155. // readAndTransform reads and transforms the downstream bytes stream
  156. // while in an obfucation state. It parses the stream of bytes read
  157. // looking for the first SSH_MSG_NEWKEYS packet sent from the peer,
  158. // after which obfuscation is turned off. Since readAndTransform may
  159. // read in more bytes that the higher-level conn.Read() can consume,
  160. // read bytes are buffered and may be returned in subsequent calls.
  161. //
  162. // readAndTransform also implements a workaround for issues with
  163. // ssh/transport.go exchangeVersions/readVersion and Psiphon's openssh
  164. // server.
  165. //
  166. // Psiphon's server sends extra lines before the version line, as
  167. // permitted by http://www.ietf.org/rfc/rfc4253.txt sec 4.2:
  168. // The server MAY send other lines of data before sending the
  169. // version string. [...] Clients MUST be able to process such lines.
  170. //
  171. // A comment in exchangeVersions explains that the golang code doesn't
  172. // support this:
  173. // Contrary to the RFC, we do not ignore lines that don't
  174. // start with "SSH-2.0-" to make the library usable with
  175. // nonconforming servers.
  176. //
  177. // In addition, Psiphon's server sends up to 512 characters per extra
  178. // line. It's not clear that the 255 max string size in sec 4.2 refers
  179. // to the extra lines as well, but in any case golang's code only
  180. // supports 255 character lines.
  181. //
  182. // State OBFUSCATION_READ_STATE_IDENTIFICATION_LINES: in this
  183. // state, extra lines are read and discarded. Once the peer's
  184. // identification string line is read, it is buffered and returned
  185. // as per the requested read buffer size.
  186. //
  187. // State OBFUSCATION_READ_STATE_KEX_PACKETS: reads, deobfuscates,
  188. // and buffers full SSH packets, checking for SSH_MSG_NEWKEYS. Packet
  189. // data is returned as per the requested read buffer size.
  190. //
  191. // State OBFUSCATION_READ_STATE_FLUSH: after SSH_MSG_NEWKEYS, no more
  192. // packets are read by this function, but bytes from the SSH_MSG_NEWKEYS
  193. // packet may need to be buffered due to partial reading.
  194. func (conn *ObfuscatedSshConn) readAndTransform(buffer []byte) (n int, err error) {
  195. nextState := conn.readState
  196. switch conn.readState {
  197. case OBFUSCATION_READ_STATE_IDENTIFICATION_LINES:
  198. // TODO: only client should accept multiple lines?
  199. if len(conn.readBuffer) == 0 {
  200. for {
  201. conn.readBuffer, err = readSshIdentificationLine(
  202. conn.Conn, conn.readDeobfuscate)
  203. if err != nil {
  204. return 0, common.ContextError(err)
  205. }
  206. if bytes.HasPrefix(conn.readBuffer, []byte("SSH-")) {
  207. break
  208. }
  209. // Discard extra line
  210. conn.readBuffer = nil
  211. }
  212. }
  213. nextState = OBFUSCATION_READ_STATE_KEX_PACKETS
  214. case OBFUSCATION_READ_STATE_KEX_PACKETS:
  215. if len(conn.readBuffer) == 0 {
  216. var isMsgNewKeys bool
  217. conn.readBuffer, isMsgNewKeys, err = readSshPacket(
  218. conn.Conn, conn.readDeobfuscate)
  219. if err != nil {
  220. return 0, common.ContextError(err)
  221. }
  222. if isMsgNewKeys {
  223. nextState = OBFUSCATION_READ_STATE_FLUSH
  224. }
  225. }
  226. case OBFUSCATION_READ_STATE_FLUSH:
  227. nextState = OBFUSCATION_READ_STATE_FINISHED
  228. case OBFUSCATION_READ_STATE_FINISHED:
  229. return 0, common.ContextError(errors.New("invalid read state"))
  230. }
  231. n = copy(buffer, conn.readBuffer)
  232. conn.readBuffer = conn.readBuffer[n:]
  233. if len(conn.readBuffer) == 0 {
  234. conn.readState = nextState
  235. conn.readBuffer = nil
  236. }
  237. return n, nil
  238. }
  239. // transformAndWrite transforms the upstream bytes stream while in an
  240. // obfucation state, buffers bytes as necessary for parsing, and writes
  241. // transformed bytes to the network connection. Bytes are obfuscated until
  242. // after the first SSH_MSG_NEWKEYS packet is sent.
  243. //
  244. // There are two mode-specific states:
  245. //
  246. // State OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE: the initial
  247. // state, when the client has not sent any data. In this state, the seed message
  248. // is injected into the client output stream.
  249. //
  250. // State OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING: the
  251. // initial state, when the server has not sent any data. In this state, the
  252. // additional lines of padding are injected into the server output stream.
  253. // This padding is a partial defense against traffic analysis against the
  254. // otherwise-fixed size server version line. This makes use of the
  255. // "other lines of data" allowance, before the version line, which clients
  256. // will ignore (http://tools.ietf.org/html/rfc4253#section-4.2).
  257. //
  258. // State OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE: before
  259. // packets are sent, the ssh peer sends an identification line terminated by CRLF:
  260. // http://www.ietf.org/rfc/rfc4253.txt sec 4.2.
  261. // In this state, the CRLF terminator is used to parse message boundaries.
  262. //
  263. // State OBFUSCATION_WRITE_STATE_KEX_PACKETS: follows the binary
  264. // packet protocol, parsing each packet until the first SSH_MSG_NEWKEYS.
  265. // http://www.ietf.org/rfc/rfc4253.txt sec 6:
  266. // uint32 packet_length
  267. // byte padding_length
  268. // byte[n1] payload; n1 = packet_length - padding_length - 1
  269. // byte[n2] random padding; n2 = padding_length
  270. // byte[m] mac (Message Authentication Code - MAC); m = mac_length
  271. // m is 0 as no MAC ha yet been negotiated.
  272. // http://www.ietf.org/rfc/rfc4253.txt sec 7.3, 12:
  273. // The payload for SSH_MSG_NEWKEYS is one byte, the packet type, value 21.
  274. //
  275. // SSH packet padding values are transformed to achive random, variable length
  276. // padding during the KEX phase as a partial defense against traffic analysis.
  277. // (The transformer can do this since only the payload and not the padding of
  278. // these packets is authenticated in the "exchange hash").
  279. func (conn *ObfuscatedSshConn) transformAndWrite(buffer []byte) (err error) {
  280. // The seed message (client) and identification line padding (server)
  281. // are injected before any standard SSH traffic.
  282. if conn.writeState == OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE {
  283. _, err = conn.Conn.Write(conn.obfuscator.SendSeedMessage())
  284. if err != nil {
  285. return common.ContextError(err)
  286. }
  287. conn.writeState = OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE
  288. } else if conn.writeState == OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING {
  289. padding, err := makeServerIdentificationLinePadding()
  290. if err != nil {
  291. return common.ContextError(err)
  292. }
  293. conn.writeObfuscate(padding)
  294. _, err = conn.Conn.Write(padding)
  295. if err != nil {
  296. return common.ContextError(err)
  297. }
  298. conn.writeState = OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE
  299. }
  300. conn.writeBuffer = append(conn.writeBuffer, buffer...)
  301. var sendBuffer []byte
  302. switch conn.writeState {
  303. case OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE:
  304. conn.writeBuffer, sendBuffer = extractSshIdentificationLine(conn.writeBuffer)
  305. if sendBuffer != nil {
  306. conn.writeState = OBFUSCATION_WRITE_STATE_KEX_PACKETS
  307. }
  308. case OBFUSCATION_WRITE_STATE_KEX_PACKETS:
  309. var hasMsgNewKeys bool
  310. conn.writeBuffer, sendBuffer, hasMsgNewKeys, err = extractSshPackets(conn.writeBuffer)
  311. if err != nil {
  312. return common.ContextError(err)
  313. }
  314. if hasMsgNewKeys {
  315. conn.writeState = OBFUSCATION_WRITE_STATE_FINISHED
  316. }
  317. case OBFUSCATION_WRITE_STATE_FINISHED:
  318. return common.ContextError(errors.New("invalid write state"))
  319. }
  320. if sendBuffer != nil {
  321. conn.writeObfuscate(sendBuffer)
  322. _, err := conn.Conn.Write(sendBuffer)
  323. if err != nil {
  324. return common.ContextError(err)
  325. }
  326. }
  327. if conn.writeState == OBFUSCATION_WRITE_STATE_FINISHED {
  328. // After SSH_MSG_NEWKEYS, any remaining bytes are un-obfuscated
  329. _, err := conn.Conn.Write(conn.writeBuffer)
  330. if err != nil {
  331. return common.ContextError(err)
  332. }
  333. // The buffer memory is no longer used
  334. conn.writeBuffer = nil
  335. }
  336. return nil
  337. }
  338. func readSshIdentificationLine(
  339. conn net.Conn, deobfuscate func([]byte)) ([]byte, error) {
  340. // TODO: use bufio.BufferedReader? less redundant string searching?
  341. var oneByte [1]byte
  342. var validLine = false
  343. readBuffer := make([]byte, 0)
  344. for len(readBuffer) < SSH_MAX_SERVER_LINE_LENGTH {
  345. _, err := io.ReadFull(conn, oneByte[:])
  346. if err != nil {
  347. return nil, common.ContextError(err)
  348. }
  349. deobfuscate(oneByte[:])
  350. readBuffer = append(readBuffer, oneByte[0])
  351. if bytes.HasSuffix(readBuffer, []byte("\r\n")) {
  352. validLine = true
  353. break
  354. }
  355. }
  356. if !validLine {
  357. return nil, common.ContextError(errors.New("invalid identification line"))
  358. }
  359. return readBuffer, nil
  360. }
  361. func readSshPacket(
  362. conn net.Conn, deobfuscate func([]byte)) ([]byte, bool, error) {
  363. prefix := make([]byte, SSH_PACKET_PREFIX_LENGTH)
  364. _, err := io.ReadFull(conn, prefix)
  365. if err != nil {
  366. return nil, false, common.ContextError(err)
  367. }
  368. deobfuscate(prefix)
  369. packetLength, _, payloadLength, messageLength := getSshPacketPrefix(prefix)
  370. if packetLength > SSH_MAX_PACKET_LENGTH {
  371. return nil, false, common.ContextError(errors.New("ssh packet length too large"))
  372. }
  373. readBuffer := make([]byte, messageLength)
  374. copy(readBuffer, prefix)
  375. _, err = io.ReadFull(conn, readBuffer[len(prefix):])
  376. if err != nil {
  377. return nil, false, common.ContextError(err)
  378. }
  379. deobfuscate(readBuffer[len(prefix):])
  380. isMsgNewKeys := false
  381. if payloadLength > 0 {
  382. packetType := int(readBuffer[SSH_PACKET_PREFIX_LENGTH])
  383. if packetType == SSH_MSG_NEWKEYS {
  384. isMsgNewKeys = true
  385. }
  386. }
  387. return readBuffer, isMsgNewKeys, nil
  388. }
  389. // From the original patch to sshd.c:
  390. // https://bitbucket.org/psiphon/psiphon-circumvention-system/commits/f40865ce624b680be840dc2432283c8137bd896d
  391. func makeServerIdentificationLinePadding() ([]byte, error) {
  392. paddingLength, err := common.MakeSecureRandomInt(OBFUSCATE_MAX_PADDING - 2) // 2 = CRLF
  393. if err != nil {
  394. return nil, common.ContextError(err)
  395. }
  396. paddingLength += 2
  397. padding := make([]byte, paddingLength)
  398. // For backwards compatibility with some clients, send no more than 512 characters
  399. // per line (including CRLF). To keep the padding distribution between 0 and OBFUSCATE_MAX_PADDING
  400. // characters, we send lines that add up to padding_length characters including all CRLFs.
  401. minLineLength := 2
  402. maxLineLength := 512
  403. lineStartIndex := 0
  404. for paddingLength > 0 {
  405. lineLength := paddingLength
  406. if lineLength > maxLineLength {
  407. lineLength = maxLineLength
  408. }
  409. // Leave enough padding allowance to send a full CRLF on the last line
  410. if paddingLength-lineLength > 0 &&
  411. paddingLength-lineLength < minLineLength {
  412. lineLength -= minLineLength - (paddingLength - lineLength)
  413. }
  414. padding[lineStartIndex+lineLength-2] = '\r'
  415. padding[lineStartIndex+lineLength-1] = '\n'
  416. lineStartIndex += lineLength
  417. paddingLength -= lineLength
  418. }
  419. return padding, nil
  420. }
  421. func extractSshIdentificationLine(writeBuffer []byte) ([]byte, []byte) {
  422. var lineBuffer []byte
  423. index := bytes.Index(writeBuffer, []byte("\r\n"))
  424. if index != -1 {
  425. messageLength := index + 2 // + 2 for \r\n
  426. lineBuffer = append([]byte(nil), writeBuffer[:messageLength]...)
  427. writeBuffer = writeBuffer[messageLength:]
  428. }
  429. return writeBuffer, lineBuffer
  430. }
  431. func extractSshPackets(writeBuffer []byte) ([]byte, []byte, bool, error) {
  432. var packetBuffer, packetsBuffer []byte
  433. hasMsgNewKeys := false
  434. for len(writeBuffer) >= SSH_PACKET_PREFIX_LENGTH {
  435. packetLength, paddingLength, payloadLength, messageLength := getSshPacketPrefix(writeBuffer)
  436. if len(writeBuffer) < messageLength {
  437. // We don't have the complete packet yet
  438. break
  439. }
  440. packetBuffer = append([]byte(nil), writeBuffer[:messageLength]...)
  441. writeBuffer = writeBuffer[messageLength:]
  442. if payloadLength > 0 {
  443. packetType := int(packetBuffer[SSH_PACKET_PREFIX_LENGTH])
  444. if packetType == SSH_MSG_NEWKEYS {
  445. hasMsgNewKeys = true
  446. }
  447. }
  448. // Padding transformation
  449. // See RFC 4253 sec. 6 for constraints
  450. possiblePaddings := (SSH_MAX_PADDING_LENGTH - paddingLength) / SSH_PADDING_MULTIPLE
  451. if possiblePaddings > 0 {
  452. // selectedPadding is integer in range [0, possiblePaddings)
  453. selectedPadding, err := common.MakeSecureRandomInt(possiblePaddings)
  454. if err != nil {
  455. return nil, nil, false, common.ContextError(err)
  456. }
  457. extraPaddingLength := selectedPadding * SSH_PADDING_MULTIPLE
  458. extraPadding, err := common.MakeSecureRandomBytes(extraPaddingLength)
  459. if err != nil {
  460. return nil, nil, false, common.ContextError(err)
  461. }
  462. setSshPacketPrefix(
  463. packetBuffer, packetLength+extraPaddingLength, paddingLength+extraPaddingLength)
  464. packetBuffer = append(packetBuffer, extraPadding...)
  465. }
  466. packetsBuffer = append(packetsBuffer, packetBuffer...)
  467. }
  468. return writeBuffer, packetsBuffer, hasMsgNewKeys, nil
  469. }
  470. func getSshPacketPrefix(buffer []byte) (packetLength, paddingLength, payloadLength, messageLength int) {
  471. // TODO: handle malformed packet [lengths]
  472. packetLength = int(binary.BigEndian.Uint32(buffer[0 : SSH_PACKET_PREFIX_LENGTH-1]))
  473. paddingLength = int(buffer[SSH_PACKET_PREFIX_LENGTH-1])
  474. payloadLength = packetLength - paddingLength - 1
  475. messageLength = SSH_PACKET_PREFIX_LENGTH + packetLength - 1
  476. return
  477. }
  478. func setSshPacketPrefix(buffer []byte, packetLength, paddingLength int) {
  479. binary.BigEndian.PutUint32(buffer, uint32(packetLength))
  480. buffer[SSH_PACKET_PREFIX_LENGTH-1] = byte(paddingLength)
  481. }