obfuscatedSshConn.go 23 KB

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