obfuscatedSshConn.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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. )
  27. type ObfuscatedSshReadState int
  28. const (
  29. OBFUSCATION_READ_STATE_SERVER_IDENTIFICATION_LINE = iota
  30. OBFUSCATION_READ_STATE_SERVER_KEX_PACKETS
  31. OBFUSCATION_READ_STATE_FLUSH
  32. OBFUSCATION_READ_STATE_FINISHED
  33. )
  34. type ObfuscatedSshWriteState int
  35. const (
  36. OBFUSCATION_WRITE_STATE_SEND_CLIENT_SEED_MESSAGE = iota
  37. OBFUSCATION_WRITE_STATE_CLIENT_IDENTIFICATION_LINE
  38. OBFUSCATION_WRITE_STATE_CLIENT_KEX_PACKETS
  39. OBFUSCATION_WRITE_STATE_FINISHED
  40. )
  41. // ObfuscatedSshConn wraps a Conn and applies the obfuscated SSH protocol
  42. // to the traffic on the connection:
  43. // https://github.com/brl/obfuscated-openssh/blob/master/README.obfuscation
  44. // ObfuscatedSshConn is used to add obfuscation to go's stock ssh client
  45. // without modification to that standard library code.
  46. // The underlying connection must be used for SSH client traffic. This code
  47. // injects the obfuscated seed message, applies obfuscated stream cipher
  48. // transformations, and performs minimal parsing of the SSH protocol to
  49. // determine when to stop obfuscation (after the first SSH_MSG_NEWKEYS is
  50. // sent by the client and received from the server).
  51. //
  52. // WARNING: doesn't fully conform to net.Conn concurrency semantics: there's
  53. // no synchronization of access to the read/writeBuffers, so concurrent
  54. // calls to one of Read or Write will result in undefined behavior.
  55. //
  56. type ObfuscatedSshConn struct {
  57. net.Conn
  58. obfuscator *Obfuscator
  59. readState ObfuscatedSshReadState
  60. writeState ObfuscatedSshWriteState
  61. readBuffer []byte
  62. writeBuffer []byte
  63. }
  64. const (
  65. SSH_MAX_SERVER_LINE_LENGTH = 1024
  66. SSH_PACKET_PREFIX_LENGTH = 5 // uint32 + byte
  67. SSH_MAX_PACKET_LENGTH = 256 * 1024 // OpenSSH max packet length
  68. SSH_MSG_NEWKEYS = 21
  69. SSH_MAX_PADDING_LENGTH = 255 // RFC 4253 sec. 6
  70. SSH_PADDING_MULTIPLE = 16 // Default cipher block size
  71. )
  72. // NewObfuscatedSshConn creates a new ObfuscatedSshConn. The underlying
  73. // conn must be used for SSH client traffic and must have transferred
  74. // no traffic.
  75. func NewObfuscatedSshConn(conn net.Conn, obfuscationKeyword string) (*ObfuscatedSshConn, error) {
  76. obfuscator, err := NewObfuscator(&ObfuscatorConfig{Keyword: obfuscationKeyword})
  77. if err != nil {
  78. return nil, ContextError(err)
  79. }
  80. return &ObfuscatedSshConn{
  81. Conn: conn,
  82. obfuscator: obfuscator,
  83. readState: OBFUSCATION_READ_STATE_SERVER_IDENTIFICATION_LINE,
  84. writeState: OBFUSCATION_WRITE_STATE_SEND_CLIENT_SEED_MESSAGE,
  85. }, nil
  86. }
  87. // Read wraps standard Read, transparently applying the obfusation
  88. // transformations.
  89. func (conn *ObfuscatedSshConn) Read(buffer []byte) (n int, err error) {
  90. if conn.readState == OBFUSCATION_READ_STATE_FINISHED {
  91. return conn.Conn.Read(buffer)
  92. }
  93. return conn.readAndTransform(buffer)
  94. }
  95. // Write wraps standard Write, transparently applying the obfuscation
  96. // transformations.
  97. func (conn *ObfuscatedSshConn) Write(buffer []byte) (n int, err error) {
  98. if conn.writeState == OBFUSCATION_WRITE_STATE_FINISHED {
  99. return conn.Conn.Write(buffer)
  100. }
  101. err = conn.transformAndWrite(buffer)
  102. if err != nil {
  103. return 0, ContextError(err)
  104. }
  105. // Reports that we wrote all the bytes
  106. // (althogh we may have buffered some or all)
  107. return len(buffer), nil
  108. }
  109. // readAndTransform reads and transforms the server->client bytes stream
  110. // while in an obfucation state. It parses the stream of bytes read
  111. // looking for the first SSH_MSG_NEWKEYS packet sent from the server,
  112. // after which obfuscation is turned off.
  113. //
  114. // readAndTransform also implements a workaround for issues with
  115. // ssh/transport.go exchangeVersions/readVersion and Psiphon's openssh
  116. // server.
  117. //
  118. // Psiphon's server sends extra lines before the version line, as
  119. // permitted by http://www.ietf.org/rfc/rfc4253.txt sec 4.2:
  120. // The server MAY send other lines of data before sending the
  121. // version string. [...] Clients MUST be able to process such lines.
  122. //
  123. // A comment in exchangeVersions explains that the go code doesn't
  124. // support this:
  125. // Contrary to the RFC, we do not ignore lines that don't
  126. // start with "SSH-2.0-" to make the library usable with
  127. // nonconforming servers.
  128. //
  129. // In addition, Psiphon's server sends up to 512 characters per extra
  130. // line. It's not clear that the 255 max string size in sec 4.2 refers
  131. // to the extra lines as well, but in any case go's code only supports
  132. // a 255 character lines.
  133. //
  134. // State OBFUSCATION_READ_STATE_SERVER_IDENTIFICATION_LINE: in this
  135. // state, extra lines are read and discarded. Once the server
  136. // identification string line is read, it is buffered and returned
  137. // as per the requested read buffer size.
  138. //
  139. // State OBFUSCATION_READ_STATE_SERVER_KEX_PACKETS: reads, deobfuscates,
  140. // and buffers full SSH packets, checking for SSH_MSG_NEWKEYS. Packet
  141. // data is returned as per the requested read buffer size.
  142. //
  143. // State OBFUSCATION_READ_STATE_FLUSH: after SSH_MSG_NEWKEYS, no more
  144. // packets are read by this function, but bytes from the SSH_MSG_NEWKEYS
  145. // packet may need to be buffered due to partial reading.
  146. func (conn *ObfuscatedSshConn) readAndTransform(buffer []byte) (n int, err error) {
  147. nextState := conn.readState
  148. switch conn.readState {
  149. case OBFUSCATION_READ_STATE_SERVER_IDENTIFICATION_LINE:
  150. if len(conn.readBuffer) == 0 {
  151. for {
  152. // TODO: use bufio.BufferedReader? less redundant string searching?
  153. var oneByte [1]byte
  154. var validLine = false
  155. for len(conn.readBuffer) < SSH_MAX_SERVER_LINE_LENGTH {
  156. _, err := io.ReadFull(conn.Conn, oneByte[:])
  157. if err != nil {
  158. return 0, ContextError(err)
  159. }
  160. conn.obfuscator.ObfuscateServerToClient(oneByte[:])
  161. conn.readBuffer = append(conn.readBuffer, oneByte[0])
  162. if bytes.HasSuffix(conn.readBuffer, []byte("\r\n")) {
  163. validLine = true
  164. break
  165. }
  166. }
  167. if !validLine {
  168. return 0, ContextError(errors.New("ObfuscatedSshConn: invalid server line"))
  169. }
  170. if bytes.HasPrefix(conn.readBuffer, []byte("SSH-")) {
  171. break
  172. }
  173. // Discard extra line
  174. conn.readBuffer = nil
  175. }
  176. }
  177. nextState = OBFUSCATION_READ_STATE_SERVER_KEX_PACKETS
  178. case OBFUSCATION_READ_STATE_SERVER_KEX_PACKETS:
  179. if len(conn.readBuffer) == 0 {
  180. prefix := make([]byte, SSH_PACKET_PREFIX_LENGTH)
  181. _, err := io.ReadFull(conn.Conn, prefix)
  182. if err != nil {
  183. return 0, ContextError(err)
  184. }
  185. conn.obfuscator.ObfuscateServerToClient(prefix)
  186. packetLength, _, payloadLength, messageLength := getSshPacketPrefix(prefix)
  187. if packetLength > SSH_MAX_PACKET_LENGTH {
  188. return 0, ContextError(errors.New("ObfuscatedSshConn: ssh packet length too large"))
  189. }
  190. conn.readBuffer = make([]byte, messageLength)
  191. copy(conn.readBuffer, prefix)
  192. _, err = io.ReadFull(conn.Conn, conn.readBuffer[len(prefix):])
  193. if err != nil {
  194. return 0, ContextError(err)
  195. }
  196. conn.obfuscator.ObfuscateServerToClient(conn.readBuffer[len(prefix):])
  197. if payloadLength > 0 {
  198. packetType := int(conn.readBuffer[SSH_PACKET_PREFIX_LENGTH])
  199. if packetType == SSH_MSG_NEWKEYS {
  200. nextState = OBFUSCATION_READ_STATE_FLUSH
  201. }
  202. }
  203. }
  204. case OBFUSCATION_READ_STATE_FLUSH:
  205. nextState = OBFUSCATION_READ_STATE_FINISHED
  206. case OBFUSCATION_READ_STATE_FINISHED:
  207. panic("ObfuscatedSshConn: invalid read state")
  208. }
  209. n = copy(buffer, conn.readBuffer)
  210. conn.readBuffer = conn.readBuffer[n:]
  211. if len(conn.readBuffer) == 0 {
  212. conn.readState = nextState
  213. conn.readBuffer = nil
  214. }
  215. return n, nil
  216. }
  217. // transformAndWrite transforms the client->server bytes stream while in an
  218. // obfucation state, buffers bytes as necessary for parsing, and writes
  219. // transformed bytes to the network connection. Bytes are obfuscated until
  220. // the first client SSH_MSG_NEWKEYS packet is sent.
  221. //
  222. // State OBFUSCATION_WRITE_STATE_SEND_CLIENT_SEED_MESSAGE: the initial state,
  223. // when the client has not sent any data. In this state, the seed message is
  224. // injected into the client output stream.
  225. //
  226. // State OBFUSCATION_WRITE_STATE_CLIENT_IDENTIFICATION_LINE: before packets are
  227. // sent, the client sends an identification line terminated by CRLF:
  228. // http://www.ietf.org/rfc/rfc4253.txt sec 4.2.
  229. // In this state, the CRLF terminator is used to parse message boundaries.
  230. //
  231. // State OBFUSCATION_WRITE_STATE_CLIENT_KEX_PACKETS: follows the binary packet
  232. // protocol, parsing each packet until the first SSH_MSG_NEWKEYS.
  233. // http://www.ietf.org/rfc/rfc4253.txt sec 6:
  234. // uint32 packet_length
  235. // byte padding_length
  236. // byte[n1] payload; n1 = packet_length - padding_length - 1
  237. // byte[n2] random padding; n2 = padding_length
  238. // byte[m] mac (Message Authentication Code - MAC); m = mac_length
  239. // m is 0 as no MAC ha yet been negotiated.
  240. // http://www.ietf.org/rfc/rfc4253.txt sec 7.3, 12:
  241. // The payload for SSH_MSG_NEWKEYS is one byte, the packet type, value 21.
  242. //
  243. // SSH packet padding values are transformed to achive random, variable length
  244. // padding during the KEX phase as a partial defense against traffic analysis.
  245. // (The transformer can do this since only the payload and not the padding of
  246. // these packets is authenticated in the "exchange hash").
  247. func (conn *ObfuscatedSshConn) transformAndWrite(buffer []byte) (err error) {
  248. if conn.writeState == OBFUSCATION_WRITE_STATE_SEND_CLIENT_SEED_MESSAGE {
  249. _, err = conn.Conn.Write(conn.obfuscator.ConsumeSeedMessage())
  250. if err != nil {
  251. return ContextError(err)
  252. }
  253. conn.writeState = OBFUSCATION_WRITE_STATE_CLIENT_IDENTIFICATION_LINE
  254. }
  255. conn.writeBuffer = append(conn.writeBuffer, buffer...)
  256. var messageBuffer []byte
  257. switch conn.writeState {
  258. case OBFUSCATION_WRITE_STATE_CLIENT_IDENTIFICATION_LINE:
  259. index := bytes.Index(conn.writeBuffer, []byte("\r\n"))
  260. if index != -1 {
  261. messageLength := index + 2 // + 2 for \r\n
  262. messageBuffer = append([]byte(nil), conn.writeBuffer[:messageLength]...)
  263. conn.writeBuffer = conn.writeBuffer[messageLength:]
  264. conn.writeState = OBFUSCATION_WRITE_STATE_CLIENT_KEX_PACKETS
  265. }
  266. case OBFUSCATION_WRITE_STATE_CLIENT_KEX_PACKETS:
  267. for len(conn.writeBuffer) >= SSH_PACKET_PREFIX_LENGTH {
  268. packetLength, paddingLength, payloadLength, messageLength := getSshPacketPrefix(conn.writeBuffer)
  269. if len(conn.writeBuffer) < messageLength {
  270. // We don't have the complete packet yet
  271. break
  272. }
  273. messageBuffer = append([]byte(nil), conn.writeBuffer[:messageLength]...)
  274. conn.writeBuffer = conn.writeBuffer[messageLength:]
  275. if payloadLength > 0 {
  276. packetType := int(messageBuffer[SSH_PACKET_PREFIX_LENGTH])
  277. if packetType == SSH_MSG_NEWKEYS {
  278. conn.writeState = OBFUSCATION_WRITE_STATE_FINISHED
  279. }
  280. }
  281. // Padding transformation
  282. // See RFC 4253 sec. 6 for constraints
  283. possiblePaddings := (SSH_MAX_PADDING_LENGTH - paddingLength) / SSH_PADDING_MULTIPLE
  284. if possiblePaddings > 0 {
  285. // selectedPadding is integer in range [0, possiblePaddings)
  286. selectedPadding, err := MakeSecureRandomInt(possiblePaddings)
  287. if err != nil {
  288. return ContextError(err)
  289. }
  290. extraPaddingLength := selectedPadding * SSH_PADDING_MULTIPLE
  291. extraPadding, err := MakeSecureRandomBytes(extraPaddingLength)
  292. if err != nil {
  293. return ContextError(err)
  294. }
  295. setSshPacketPrefix(
  296. messageBuffer, packetLength+extraPaddingLength, paddingLength+extraPaddingLength)
  297. messageBuffer = append(messageBuffer, extraPadding...)
  298. }
  299. }
  300. case OBFUSCATION_WRITE_STATE_FINISHED:
  301. panic("ObfuscatedSshConn: invalid write state")
  302. }
  303. if messageBuffer != nil {
  304. conn.obfuscator.ObfuscateClientToServer(messageBuffer)
  305. _, err := conn.Conn.Write(messageBuffer)
  306. if err != nil {
  307. return ContextError(err)
  308. }
  309. }
  310. if conn.writeState == OBFUSCATION_WRITE_STATE_FINISHED {
  311. // After SSH_MSG_NEWKEYS, any remaining bytes are un-obfuscated
  312. _, err := conn.Conn.Write(conn.writeBuffer)
  313. if err != nil {
  314. return ContextError(err)
  315. }
  316. // The buffer memory is no longer used
  317. conn.writeBuffer = nil
  318. }
  319. return nil
  320. }
  321. func getSshPacketPrefix(buffer []byte) (packetLength, paddingLength, payloadLength, messageLength int) {
  322. // TODO: handle malformed packet [lengths]
  323. packetLength = int(binary.BigEndian.Uint32(buffer[0 : SSH_PACKET_PREFIX_LENGTH-1]))
  324. paddingLength = int(buffer[SSH_PACKET_PREFIX_LENGTH-1])
  325. payloadLength = packetLength - paddingLength - 1
  326. messageLength = SSH_PACKET_PREFIX_LENGTH + packetLength - 1
  327. return
  328. }
  329. func setSshPacketPrefix(buffer []byte, packetLength, paddingLength int) {
  330. binary.BigEndian.PutUint32(buffer, uint32(packetLength))
  331. buffer[SSH_PACKET_PREFIX_LENGTH-1] = byte(paddingLength)
  332. }