obfuscatedSshConn.go 18 KB

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