Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
/*
* 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)
}