| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722 |
- /*
- * Copyright (c) 2015, Psiphon Inc.
- * All rights reserved.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- package obfuscator
- import (
- "bytes"
- "encoding/binary"
- std_errors "errors"
- "io"
- "io/ioutil"
- "net"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
- "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
- )
- const (
- SSH_MAX_SERVER_LINE_LENGTH = 1024
- SSH_PACKET_PREFIX_LENGTH = 5 // uint32 + byte
- SSH_MAX_PACKET_LENGTH = 256 * 1024 // OpenSSH max packet length
- SSH_MSG_NEWKEYS = 21
- SSH_MAX_PADDING_LENGTH = 255 // RFC 4253 sec. 6
- SSH_PADDING_MULTIPLE = 16 // Default cipher block size
- )
- // ObfuscatedSSHConn wraps a Conn and applies the obfuscated SSH protocol
- // to the traffic on the connection:
- // https://github.com/brl/obfuscated-openssh/blob/master/README.obfuscation
- //
- // ObfuscatedSSHConn is used to add obfuscation to golang's stock "ssh"
- // client and server without modification to that standard library code.
- // The underlying connection must be used for SSH traffic. This code
- // injects the obfuscated seed message, applies obfuscated stream cipher
- // transformations, and performs minimal parsing of the SSH protocol to
- // determine when to stop obfuscation (after the first SSH_MSG_NEWKEYS is
- // sent and received).
- //
- // WARNING: doesn't fully conform to net.Conn concurrency semantics: there's
- // no synchronization of access to the read/writeBuffers, so concurrent
- // calls to one of Read or Write will result in undefined behavior.
- //
- type ObfuscatedSSHConn struct {
- net.Conn
- mode ObfuscatedSSHConnMode
- obfuscator *Obfuscator
- readDeobfuscate func([]byte)
- writeObfuscate func([]byte)
- readState ObfuscatedSSHReadState
- writeState ObfuscatedSSHWriteState
- readBuffer *bytes.Buffer
- writeBuffer *bytes.Buffer
- transformBuffer *bytes.Buffer
- legacyPadding bool
- paddingLength int
- paddingPRNG *prng.PRNG
- }
- type ObfuscatedSSHConnMode int
- const (
- OBFUSCATION_CONN_MODE_CLIENT = iota
- OBFUSCATION_CONN_MODE_SERVER
- )
- type ObfuscatedSSHReadState int
- const (
- OBFUSCATION_READ_STATE_IDENTIFICATION_LINES = iota
- OBFUSCATION_READ_STATE_KEX_PACKETS
- OBFUSCATION_READ_STATE_FLUSH
- OBFUSCATION_READ_STATE_FINISHED
- )
- type ObfuscatedSSHWriteState int
- const (
- OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE = iota
- OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING
- OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE
- OBFUSCATION_WRITE_STATE_KEX_PACKETS
- OBFUSCATION_WRITE_STATE_FINISHED
- )
- // NewObfuscatedSSHConn creates a new ObfuscatedSSHConn.
- // The underlying conn must be used for SSH traffic and must have
- // transferred no traffic.
- //
- // In client mode, NewObfuscatedSSHConn does not block or initiate network
- // I/O. The obfuscation seed message is sent when Write() is first called.
- //
- // In server mode, NewObfuscatedSSHConn cannot completely initialize itself
- // without the seed message from the client to derive obfuscation keys. So
- // NewObfuscatedSSHConn blocks on reading the client seed message from the
- // underlying conn.
- //
- // obfuscationPaddingPRNGSeed is required and used only in
- // OBFUSCATION_CONN_MODE_CLIENT mode and allows for optional replay of the
- // same padding: both in the initial obfuscator message and in the SSH KEX
- // sequence. In OBFUSCATION_CONN_MODE_SERVER mode, the server obtains its PRNG
- // seed from the client's initial obfuscator message, resulting in the server
- // replaying its padding as well.
- //
- // seedHistory and irregularLogger are optional ObfuscatorConfig parameters
- // used only in OBFUSCATION_CONN_MODE_SERVER.
- func NewObfuscatedSSHConn(
- mode ObfuscatedSSHConnMode,
- conn net.Conn,
- obfuscationKeyword string,
- obfuscationPaddingPRNGSeed *prng.Seed,
- minPadding, maxPadding *int,
- seedHistory *SeedHistory,
- irregularLogger func(
- clientIP string,
- err error,
- logFields common.LogFields)) (*ObfuscatedSSHConn, error) {
- var err error
- var obfuscator *Obfuscator
- var readDeobfuscate, writeObfuscate func([]byte)
- var writeState ObfuscatedSSHWriteState
- if mode == OBFUSCATION_CONN_MODE_CLIENT {
- obfuscator, err = NewClientObfuscator(
- &ObfuscatorConfig{
- Keyword: obfuscationKeyword,
- PaddingPRNGSeed: obfuscationPaddingPRNGSeed,
- MinPadding: minPadding,
- MaxPadding: maxPadding,
- })
- if err != nil {
- return nil, errors.Trace(err)
- }
- readDeobfuscate = obfuscator.ObfuscateServerToClient
- writeObfuscate = obfuscator.ObfuscateClientToServer
- writeState = OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE
- } else {
- // NewServerObfuscator reads a seed message from conn
- obfuscator, err = NewServerObfuscator(
- &ObfuscatorConfig{
- Keyword: obfuscationKeyword,
- SeedHistory: seedHistory,
- IrregularLogger: irregularLogger,
- },
- common.IPAddressFromAddr(conn.RemoteAddr()),
- conn)
- if err != nil {
- // Obfuscated SSH protocol spec:
- // "If these checks fail the server will continue reading and discarding all data
- // until the client closes the connection without sending anything in response."
- //
- // This may be terminated by a server-side connection establishment timeout.
- io.Copy(ioutil.Discard, conn)
- return nil, errors.Trace(err)
- }
- readDeobfuscate = obfuscator.ObfuscateClientToServer
- writeObfuscate = obfuscator.ObfuscateServerToClient
- writeState = OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING
- }
- paddingPRNG, err := obfuscator.GetDerivedPRNG("obfuscated-ssh-padding")
- if err != nil {
- return nil, errors.Trace(err)
- }
- return &ObfuscatedSSHConn{
- Conn: conn,
- mode: mode,
- obfuscator: obfuscator,
- readDeobfuscate: readDeobfuscate,
- writeObfuscate: writeObfuscate,
- readState: OBFUSCATION_READ_STATE_IDENTIFICATION_LINES,
- writeState: writeState,
- readBuffer: new(bytes.Buffer),
- writeBuffer: new(bytes.Buffer),
- transformBuffer: new(bytes.Buffer),
- paddingLength: -1,
- paddingPRNG: paddingPRNG,
- }, nil
- }
- // NewClientObfuscatedSSHConn creates a client ObfuscatedSSHConn. See
- // documentation in NewObfuscatedSSHConn.
- func NewClientObfuscatedSSHConn(
- conn net.Conn,
- obfuscationKeyword string,
- obfuscationPaddingPRNGSeed *prng.Seed,
- minPadding, maxPadding *int) (*ObfuscatedSSHConn, error) {
- return NewObfuscatedSSHConn(
- OBFUSCATION_CONN_MODE_CLIENT,
- conn,
- obfuscationKeyword,
- obfuscationPaddingPRNGSeed,
- minPadding, maxPadding,
- nil,
- nil)
- }
- // NewServerObfuscatedSSHConn creates a server ObfuscatedSSHConn. See
- // documentation in NewObfuscatedSSHConn.
- func NewServerObfuscatedSSHConn(
- conn net.Conn,
- obfuscationKeyword string,
- seedHistory *SeedHistory,
- irregularLogger func(
- clientIP string,
- err error,
- logFields common.LogFields)) (*ObfuscatedSSHConn, error) {
- return NewObfuscatedSSHConn(
- OBFUSCATION_CONN_MODE_SERVER,
- conn,
- obfuscationKeyword,
- nil,
- nil, nil,
- seedHistory,
- irregularLogger)
- }
- // GetDerivedPRNG creates a new PRNG with a seed derived from the
- // ObfuscatedSSHConn padding seed and distinguished by the salt, which should
- // be a unique identifier for each usage context.
- //
- // In OBFUSCATION_CONN_MODE_SERVER mode, the ObfuscatedSSHConn padding seed is
- // obtained from the client, so derived PRNGs may be used to replay sequences
- // post-initial obfuscator message.
- func (conn *ObfuscatedSSHConn) GetDerivedPRNG(salt string) (*prng.PRNG, error) {
- return conn.obfuscator.GetDerivedPRNG(salt)
- }
- // GetMetrics implements the common.MetricsSource interface.
- func (conn *ObfuscatedSSHConn) GetMetrics() common.LogFields {
- logFields := make(common.LogFields)
- if conn.mode == OBFUSCATION_CONN_MODE_CLIENT {
- paddingLength := conn.obfuscator.GetPaddingLength()
- if paddingLength != -1 {
- logFields["upstream_ossh_padding"] = paddingLength
- }
- } else {
- if conn.paddingLength != -1 {
- logFields["downstream_ossh_padding"] = conn.paddingLength
- }
- }
- return logFields
- }
- // Read wraps standard Read, transparently applying the obfuscation
- // transformations.
- func (conn *ObfuscatedSSHConn) Read(buffer []byte) (int, error) {
- if conn.readState == OBFUSCATION_READ_STATE_FINISHED {
- return conn.Conn.Read(buffer)
- }
- n, err := conn.readAndTransform(buffer)
- if err != nil {
- err = errors.Trace(err)
- }
- return n, err
- }
- // Write wraps standard Write, transparently applying the obfuscation
- // transformations.
- func (conn *ObfuscatedSSHConn) Write(buffer []byte) (int, error) {
- if conn.writeState == OBFUSCATION_WRITE_STATE_FINISHED {
- return conn.Conn.Write(buffer)
- }
- err := conn.transformAndWrite(buffer)
- if err != nil {
- return 0, errors.Trace(err)
- }
- // Reports that we wrote all the bytes
- // (although we may have buffered some or all)
- return len(buffer), nil
- }
- // readAndTransform reads and transforms the downstream bytes stream
- // while in an obfucation state. It parses the stream of bytes read
- // looking for the first SSH_MSG_NEWKEYS packet sent from the peer,
- // after which obfuscation is turned off. Since readAndTransform may
- // read in more bytes that the higher-level conn.Read() can consume,
- // read bytes are buffered and may be returned in subsequent calls.
- //
- // readAndTransform also implements a workaround for issues with
- // ssh/transport.go exchangeVersions/readVersion and Psiphon's openssh
- // server.
- //
- // Psiphon's server sends extra lines before the version line, as
- // permitted by http://www.ietf.org/rfc/rfc4253.txt sec 4.2:
- // The server MAY send other lines of data before sending the
- // version string. [...] Clients MUST be able to process such lines.
- //
- // A comment in exchangeVersions explains that the golang code doesn't
- // support this:
- // Contrary to the RFC, we do not ignore lines that don't
- // start with "SSH-2.0-" to make the library usable with
- // nonconforming servers.
- //
- // In addition, Psiphon's server sends up to 512 characters per extra
- // line. It's not clear that the 255 max string size in sec 4.2 refers
- // to the extra lines as well, but in any case golang's code only
- // supports 255 character lines.
- //
- // State OBFUSCATION_READ_STATE_IDENTIFICATION_LINES: in this
- // state, extra lines are read and discarded. Once the peer's
- // identification string line is read, it is buffered and returned
- // as per the requested read buffer size.
- //
- // State OBFUSCATION_READ_STATE_KEX_PACKETS: reads, deobfuscates,
- // and buffers full SSH packets, checking for SSH_MSG_NEWKEYS. Packet
- // data is returned as per the requested read buffer size.
- //
- // State OBFUSCATION_READ_STATE_FLUSH: after SSH_MSG_NEWKEYS, no more
- // packets are read by this function, but bytes from the SSH_MSG_NEWKEYS
- // packet may need to be buffered due to partial reading.
- func (conn *ObfuscatedSSHConn) readAndTransform(buffer []byte) (int, error) {
- nextState := conn.readState
- switch conn.readState {
- case OBFUSCATION_READ_STATE_IDENTIFICATION_LINES:
- // TODO: only client should accept multiple lines?
- if conn.readBuffer.Len() == 0 {
- for {
- err := readSSHIdentificationLine(
- conn.Conn, conn.readDeobfuscate, conn.readBuffer)
- if err != nil {
- return 0, errors.Trace(err)
- }
- if bytes.HasPrefix(conn.readBuffer.Bytes(), []byte("SSH-")) {
- if bytes.Contains(conn.readBuffer.Bytes(), []byte("Ganymed")) {
- conn.legacyPadding = true
- }
- break
- }
- // Discard extra line
- conn.readBuffer.Truncate(0)
- }
- }
- nextState = OBFUSCATION_READ_STATE_KEX_PACKETS
- case OBFUSCATION_READ_STATE_KEX_PACKETS:
- if conn.readBuffer.Len() == 0 {
- isMsgNewKeys, err := readSSHPacket(
- conn.Conn, conn.readDeobfuscate, conn.readBuffer)
- if err != nil {
- return 0, errors.Trace(err)
- }
- if isMsgNewKeys {
- nextState = OBFUSCATION_READ_STATE_FLUSH
- }
- }
- case OBFUSCATION_READ_STATE_FLUSH:
- nextState = OBFUSCATION_READ_STATE_FINISHED
- case OBFUSCATION_READ_STATE_FINISHED:
- return 0, errors.TraceNew("invalid read state")
- }
- n, err := conn.readBuffer.Read(buffer)
- if err == io.EOF {
- err = nil
- }
- if err != nil {
- return n, errors.Trace(err)
- }
- if conn.readBuffer.Len() == 0 {
- conn.readState = nextState
- if conn.readState == OBFUSCATION_READ_STATE_FINISHED {
- // The buffer memory is no longer used
- conn.readBuffer = nil
- }
- }
- return n, nil
- }
- // transformAndWrite transforms the upstream bytes stream while in an
- // obfucation state, buffers bytes as necessary for parsing, and writes
- // transformed bytes to the network connection. Bytes are obfuscated until
- // after the first SSH_MSG_NEWKEYS packet is sent.
- //
- // There are two mode-specific states:
- //
- // State OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE: the initial
- // state, when the client has not sent any data. In this state, the seed message
- // is injected into the client output stream.
- //
- // State OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING: the
- // initial state, when the server has not sent any data. In this state, the
- // additional lines of padding are injected into the server output stream.
- // This padding is a partial defense against traffic analysis against the
- // otherwise-fixed size server version line. This makes use of the
- // "other lines of data" allowance, before the version line, which clients
- // will ignore (http://tools.ietf.org/html/rfc4253#section-4.2).
- //
- // State OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE: before
- // packets are sent, the SSH peer sends an identification line terminated by CRLF:
- // http://www.ietf.org/rfc/rfc4253.txt sec 4.2.
- // In this state, the CRLF terminator is used to parse message boundaries.
- //
- // State OBFUSCATION_WRITE_STATE_KEX_PACKETS: follows the binary
- // packet protocol, parsing each packet until the first SSH_MSG_NEWKEYS.
- // http://www.ietf.org/rfc/rfc4253.txt sec 6:
- // uint32 packet_length
- // byte padding_length
- // byte[n1] payload; n1 = packet_length - padding_length - 1
- // byte[n2] random padding; n2 = padding_length
- // byte[m] mac (Message Authentication Code - MAC); m = mac_length
- // m is 0 as no MAC ha yet been negotiated.
- // http://www.ietf.org/rfc/rfc4253.txt sec 7.3, 12:
- // The payload for SSH_MSG_NEWKEYS is one byte, the packet type, value 21.
- //
- // SSH packet padding values are transformed to achieve random, variable length
- // padding during the KEX phase as a partial defense against traffic analysis.
- // (The transformer can do this since only the payload and not the padding of
- // these packets is authenticated in the "exchange hash").
- func (conn *ObfuscatedSSHConn) transformAndWrite(buffer []byte) error {
- // The seed message (client) and identification line padding (server)
- // are injected before any standard SSH traffic.
- if conn.writeState == OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE {
- _, err := conn.Conn.Write(conn.obfuscator.SendSeedMessage())
- if err != nil {
- return errors.Trace(err)
- }
- conn.writeState = OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE
- } else if conn.writeState == OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING {
- padding := makeServerIdentificationLinePadding(conn.paddingPRNG)
- conn.paddingLength = len(padding)
- conn.writeObfuscate(padding)
- _, err := conn.Conn.Write(padding)
- if err != nil {
- return errors.Trace(err)
- }
- conn.writeState = OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE
- }
- // writeBuffer is used to buffer bytes received from Write() until a
- // complete SSH message is received. transformBuffer is used as a scratch
- // buffer for size-changing tranformations, including padding transforms.
- // All data flows as follows:
- // conn.Write() -> writeBuffer -> transformBuffer -> conn.Conn.Write()
- conn.writeBuffer.Write(buffer)
- switch conn.writeState {
- case OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE:
- hasIdentificationLine := extractSSHIdentificationLine(
- conn.writeBuffer, conn.transformBuffer)
- if hasIdentificationLine {
- conn.writeState = OBFUSCATION_WRITE_STATE_KEX_PACKETS
- }
- case OBFUSCATION_WRITE_STATE_KEX_PACKETS:
- hasMsgNewKeys, err := extractSSHPackets(
- conn.paddingPRNG,
- conn.legacyPadding,
- conn.writeBuffer,
- conn.transformBuffer)
- if err != nil {
- return errors.Trace(err)
- }
- if hasMsgNewKeys {
- conn.writeState = OBFUSCATION_WRITE_STATE_FINISHED
- }
- case OBFUSCATION_WRITE_STATE_FINISHED:
- return errors.TraceNew("invalid write state")
- }
- if conn.transformBuffer.Len() > 0 {
- sendData := conn.transformBuffer.Next(conn.transformBuffer.Len())
- conn.writeObfuscate(sendData)
- _, err := conn.Conn.Write(sendData)
- if err != nil {
- return errors.Trace(err)
- }
- }
- if conn.writeState == OBFUSCATION_WRITE_STATE_FINISHED {
- if conn.writeBuffer.Len() > 0 {
- // After SSH_MSG_NEWKEYS, any remaining bytes are un-obfuscated
- _, err := conn.Conn.Write(conn.writeBuffer.Bytes())
- if err != nil {
- return errors.Trace(err)
- }
- }
- // The buffer memory is no longer used
- conn.writeBuffer = nil
- conn.transformBuffer = nil
- }
- return nil
- }
- func readSSHIdentificationLine(
- conn net.Conn,
- deobfuscate func([]byte),
- readBuffer *bytes.Buffer) error {
- // TODO: less redundant string searching?
- var oneByte [1]byte
- var validLine = false
- readBuffer.Grow(SSH_MAX_SERVER_LINE_LENGTH)
- for i := 0; i < SSH_MAX_SERVER_LINE_LENGTH; i++ {
- _, err := io.ReadFull(conn, oneByte[:])
- if err != nil {
- return errors.Trace(err)
- }
- deobfuscate(oneByte[:])
- readBuffer.WriteByte(oneByte[0])
- if bytes.HasSuffix(readBuffer.Bytes(), []byte("\r\n")) {
- validLine = true
- break
- }
- }
- if !validLine {
- return errors.TraceNew("invalid identification line")
- }
- return nil
- }
- func readSSHPacket(
- conn net.Conn,
- deobfuscate func([]byte),
- readBuffer *bytes.Buffer) (bool, error) {
- prefixOffset := readBuffer.Len()
- readBuffer.Grow(SSH_PACKET_PREFIX_LENGTH)
- n, err := readBuffer.ReadFrom(io.LimitReader(conn, SSH_PACKET_PREFIX_LENGTH))
- if err == nil && n != SSH_PACKET_PREFIX_LENGTH {
- err = std_errors.New("unxpected number of bytes read")
- }
- if err != nil {
- return false, errors.Trace(err)
- }
- prefix := readBuffer.Bytes()[prefixOffset : prefixOffset+SSH_PACKET_PREFIX_LENGTH]
- deobfuscate(prefix)
- _, _, payloadLength, messageLength, err := getSSHPacketPrefix(prefix)
- if err != nil {
- return false, errors.Trace(err)
- }
- remainingReadLength := messageLength - SSH_PACKET_PREFIX_LENGTH
- readBuffer.Grow(remainingReadLength)
- n, err = readBuffer.ReadFrom(io.LimitReader(conn, int64(remainingReadLength)))
- if err == nil && n != int64(remainingReadLength) {
- err = std_errors.New("unxpected number of bytes read")
- }
- if err != nil {
- return false, errors.Trace(err)
- }
- remainingBytes := readBuffer.Bytes()[prefixOffset+SSH_PACKET_PREFIX_LENGTH:]
- deobfuscate(remainingBytes)
- isMsgNewKeys := false
- if payloadLength > 0 {
- packetType := int(readBuffer.Bytes()[prefixOffset+SSH_PACKET_PREFIX_LENGTH])
- if packetType == SSH_MSG_NEWKEYS {
- isMsgNewKeys = true
- }
- }
- return isMsgNewKeys, nil
- }
- // From the original patch to sshd.c:
- // https://bitbucket.org/psiphon/psiphon-circumvention-system/commits/f40865ce624b680be840dc2432283c8137bd896d
- func makeServerIdentificationLinePadding(prng *prng.PRNG) []byte {
- paddingLength := prng.Intn(OBFUSCATE_MAX_PADDING - 2 + 1) // 2 = CRLF
- paddingLength += 2
- padding := make([]byte, paddingLength)
- // For backwards compatibility with some clients, send no more than 512 characters
- // per line (including CRLF). To keep the padding distribution between 0 and OBFUSCATE_MAX_PADDING
- // characters, we send lines that add up to padding_length characters including all CRLFs.
- minLineLength := 2
- maxLineLength := 512
- lineStartIndex := 0
- for paddingLength > 0 {
- lineLength := paddingLength
- if lineLength > maxLineLength {
- lineLength = maxLineLength
- }
- // Leave enough padding allowance to send a full CRLF on the last line
- if paddingLength-lineLength > 0 &&
- paddingLength-lineLength < minLineLength {
- lineLength -= minLineLength - (paddingLength - lineLength)
- }
- padding[lineStartIndex+lineLength-2] = '\r'
- padding[lineStartIndex+lineLength-1] = '\n'
- lineStartIndex += lineLength
- paddingLength -= lineLength
- }
- return padding
- }
- func extractSSHIdentificationLine(writeBuffer, transformBuffer *bytes.Buffer) bool {
- index := bytes.Index(writeBuffer.Bytes(), []byte("\r\n"))
- if index != -1 {
- lineLength := index + 2 // + 2 for \r\n
- transformBuffer.Write(writeBuffer.Next(lineLength))
- return true
- }
- return false
- }
- func extractSSHPackets(
- prng *prng.PRNG,
- legacyPadding bool,
- writeBuffer, transformBuffer *bytes.Buffer) (bool, error) {
- hasMsgNewKeys := false
- for writeBuffer.Len() >= SSH_PACKET_PREFIX_LENGTH {
- packetLength, paddingLength, payloadLength, messageLength, err := getSSHPacketPrefix(
- writeBuffer.Bytes()[:SSH_PACKET_PREFIX_LENGTH])
- if err != nil {
- return false, errors.Trace(err)
- }
- if writeBuffer.Len() < messageLength {
- // We don't have the complete packet yet
- break
- }
- packet := writeBuffer.Next(messageLength)
- if payloadLength > 0 {
- packetType := int(packet[SSH_PACKET_PREFIX_LENGTH])
- if packetType == SSH_MSG_NEWKEYS {
- hasMsgNewKeys = true
- }
- }
- transformedPacketOffset := transformBuffer.Len()
- transformBuffer.Write(packet)
- transformedPacket := transformBuffer.Bytes()[transformedPacketOffset:]
- // Padding transformation
- extraPaddingLength := 0
- if !legacyPadding {
- // This does not satisfy RFC 4253 sec. 6 constraints:
- // - The goal is to vary packet sizes as much as possible.
- // - We implement both the client and server sides and both sides accept
- // less constrained paddings (for plaintext packets).
- possibleExtraPaddingLength := (SSH_MAX_PADDING_LENGTH - paddingLength)
- if possibleExtraPaddingLength > 0 {
- // extraPaddingLength is integer in range [0, possiblePadding + 1)
- extraPaddingLength = prng.Intn(possibleExtraPaddingLength + 1)
- }
- } else {
- // See RFC 4253 sec. 6 for constraints
- possiblePaddings := (SSH_MAX_PADDING_LENGTH - paddingLength) / SSH_PADDING_MULTIPLE
- if possiblePaddings > 0 {
- // selectedPadding is integer in range [0, possiblePaddings)
- selectedPadding := prng.Intn(possiblePaddings)
- extraPaddingLength = selectedPadding * SSH_PADDING_MULTIPLE
- }
- }
- extraPadding := prng.Bytes(extraPaddingLength)
- setSSHPacketPrefix(
- transformedPacket,
- packetLength+extraPaddingLength,
- paddingLength+extraPaddingLength)
- transformBuffer.Write(extraPadding)
- }
- return hasMsgNewKeys, nil
- }
- func getSSHPacketPrefix(buffer []byte) (int, int, int, int, error) {
- packetLength := int(binary.BigEndian.Uint32(buffer[0 : SSH_PACKET_PREFIX_LENGTH-1]))
- if packetLength < 1 || packetLength > SSH_MAX_PACKET_LENGTH {
- return 0, 0, 0, 0, errors.TraceNew("invalid SSH packet length")
- }
- paddingLength := int(buffer[SSH_PACKET_PREFIX_LENGTH-1])
- payloadLength := packetLength - paddingLength - 1
- messageLength := SSH_PACKET_PREFIX_LENGTH + packetLength - 1
- return packetLength, paddingLength, payloadLength, messageLength, nil
- }
- func setSSHPacketPrefix(buffer []byte, packetLength, paddingLength int) {
- binary.BigEndian.PutUint32(buffer, uint32(packetLength))
- buffer[SSH_PACKET_PREFIX_LENGTH-1] = byte(paddingLength)
- }
|