obfuscatedSshConn.go 29 KB

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