Skip to content

Commit

Permalink
Merge branch 'master' into staging-server
Browse files Browse the repository at this point in the history
  • Loading branch information
rod-hynes committed Jun 14, 2023
2 parents 2380174 + 281e2e7 commit 283d241
Show file tree
Hide file tree
Showing 17 changed files with 1,546 additions and 225 deletions.
2 changes: 1 addition & 1 deletion psiphon/common/obfuscator/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func NewSeedHistory(config *SeedHistoryConfig) *SeedHistory {
// positives.
//
// Limitation: As go-cache-lru does not currently support iterating over all
// items (without making a full copy of the enture cache), the client IP with
// items (without making a full copy of the entire cache), the client IP with
// shorter TTL is stored in a second, smaller cache instead of the same cache
// with a a pruner. This incurs some additional overhead, as the seed key is
// stored twice, once in each cache.
Expand Down
129 changes: 102 additions & 27 deletions psiphon/common/obfuscator/obfuscatedSshConn.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ const (
// 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
Expand All @@ -84,7 +83,8 @@ const (
type ObfuscatedSSHReadState int

const (
OBFUSCATION_READ_STATE_IDENTIFICATION_LINES = iota
OBFUSCATION_READ_STATE_CLIENT_READ_PREFIX = iota
OBFUSCATION_READ_STATE_IDENTIFICATION_LINES
OBFUSCATION_READ_STATE_KEX_PACKETS
OBFUSCATION_READ_STATE_FLUSH
OBFUSCATION_READ_STATE_FINISHED
Expand All @@ -93,8 +93,8 @@ const (
type ObfuscatedSSHWriteState int

const (
OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE = iota
OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING
OBFUSCATION_WRITE_STATE_CLIENT_SEND_PREAMBLE = iota
OBFUSCATION_WRITE_STATE_SERVER_SEND_PREFIX_AND_IDENTIFICATION_LINE_PADDING
OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE
OBFUSCATION_WRITE_STATE_KEX_PACKETS
OBFUSCATION_WRITE_STATE_FINISHED
Expand Down Expand Up @@ -127,6 +127,8 @@ func NewObfuscatedSSHConn(
obfuscationKeyword string,
obfuscationPaddingPRNGSeed *prng.Seed,
obfuscatorSeedTransformerParameters *transforms.ObfuscatorSeedTransformerParameters,
clientPrefixSpec *OSSHPrefixSpec,
serverPrefixSepcs transforms.Specs,
minPadding, maxPadding *int,
seedHistory *SeedHistory,
irregularLogger func(
Expand All @@ -139,11 +141,16 @@ func NewObfuscatedSSHConn(
var readDeobfuscate, writeObfuscate func([]byte)
var writeState ObfuscatedSSHWriteState

conn = WrapConnWithSkipReader(conn)

readState := ObfuscatedSSHReadState(OBFUSCATION_READ_STATE_IDENTIFICATION_LINES)

if mode == OBFUSCATION_CONN_MODE_CLIENT {
obfuscator, err = NewClientObfuscator(
&ObfuscatorConfig{
IsOSSH: true,
Keyword: obfuscationKeyword,
ClientPrefixSpec: clientPrefixSpec,
PaddingPRNGSeed: obfuscationPaddingPRNGSeed,
MinPadding: minPadding,
MaxPadding: maxPadding,
Expand All @@ -154,14 +161,21 @@ func NewObfuscatedSSHConn(
}
readDeobfuscate = obfuscator.ObfuscateServerToClient
writeObfuscate = obfuscator.ObfuscateClientToServer
writeState = OBFUSCATION_WRITE_STATE_CLIENT_SEND_SEED_MESSAGE
writeState = OBFUSCATION_WRITE_STATE_CLIENT_SEND_PREAMBLE

if obfuscator.prefixHeader != nil {
// Client expects prefix with terminator from the server.
readState = OBFUSCATION_READ_STATE_CLIENT_READ_PREFIX
}

} else {
// NewServerObfuscator reads a seed message from conn
obfuscator, err = NewServerObfuscator(
&ObfuscatorConfig{
Keyword: obfuscationKeyword,
SeedHistory: seedHistory,
IrregularLogger: irregularLogger,
Keyword: obfuscationKeyword,
ServerPrefixSpecs: serverPrefixSepcs,
SeedHistory: seedHistory,
IrregularLogger: irregularLogger,
},
common.IPAddressFromAddr(conn.RemoteAddr()),
conn)
Expand All @@ -178,7 +192,7 @@ func NewObfuscatedSSHConn(
}
readDeobfuscate = obfuscator.ObfuscateClientToServer
writeObfuscate = obfuscator.ObfuscateServerToClient
writeState = OBFUSCATION_WRITE_STATE_SERVER_SEND_IDENTIFICATION_LINE_PADDING
writeState = OBFUSCATION_WRITE_STATE_SERVER_SEND_PREFIX_AND_IDENTIFICATION_LINE_PADDING
}

paddingPRNG, err := obfuscator.GetDerivedPRNG("obfuscated-ssh-padding")
Expand All @@ -192,7 +206,7 @@ func NewObfuscatedSSHConn(
obfuscator: obfuscator,
readDeobfuscate: readDeobfuscate,
writeObfuscate: writeObfuscate,
readState: OBFUSCATION_READ_STATE_IDENTIFICATION_LINES,
readState: readState,
writeState: writeState,
readBuffer: new(bytes.Buffer),
writeBuffer: new(bytes.Buffer),
Expand All @@ -209,6 +223,7 @@ func NewClientObfuscatedSSHConn(
obfuscationKeyword string,
obfuscationPaddingPRNGSeed *prng.Seed,
obfuscatorSeedTransformerParameters *transforms.ObfuscatorSeedTransformerParameters,
prefixSpec *OSSHPrefixSpec,
minPadding, maxPadding *int) (*ObfuscatedSSHConn, error) {

return NewObfuscatedSSHConn(
Expand All @@ -217,6 +232,8 @@ func NewClientObfuscatedSSHConn(
obfuscationKeyword,
obfuscationPaddingPRNGSeed,
obfuscatorSeedTransformerParameters,
prefixSpec,
nil,
minPadding, maxPadding,
nil,
nil)
Expand All @@ -228,6 +245,7 @@ func NewServerObfuscatedSSHConn(
conn net.Conn,
obfuscationKeyword string,
seedHistory *SeedHistory,
serverPrefixSpecs transforms.Specs,
irregularLogger func(
clientIP string,
err error,
Expand All @@ -238,6 +256,8 @@ func NewServerObfuscatedSSHConn(
conn,
obfuscationKeyword,
nil, nil,
nil,
serverPrefixSpecs,
nil, nil,
seedHistory,
irregularLogger)
Expand Down Expand Up @@ -311,20 +331,26 @@ func (conn *ObfuscatedSSHConn) Write(buffer []byte) (int, error) {
//
// 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.
//
// 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.
//
// 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_CLIENT_READ_PREFIX: the initial
// state, when the client expects prefix with terminator before the
// rest of the tunnel. In this state, the prefix is read and discarded.
//
// 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
Expand All @@ -339,10 +365,36 @@ func (conn *ObfuscatedSSHConn) Write(buffer []byte) (int, error) {
// packet may need to be buffered due to partial reading.
func (conn *ObfuscatedSSHConn) readAndTransform(buffer []byte) (int, error) {

if conn.readState == OBFUSCATION_READ_STATE_CLIENT_READ_PREFIX {
skipReader, ok := conn.Conn.(*SkipReader)
if !ok {
return 0, errors.TraceNew("expected SkipReader")
}

preambleHeader := make([]byte, PREAMBLE_HEADER_LENGTH)
_, err := io.ReadFull(skipReader, preambleHeader)
if err != nil {
return 0, errors.Trace(err)
}

terminator, err := makeTerminator(conn.obfuscator.keyword,
preambleHeader, OBFUSCATE_SERVER_TO_CLIENT_IV)
if err != nil {
return 0, errors.Trace(err)
}

err = skipReader.SkipUpToToken(terminator, PREFIX_TERM_SEARCH_BUF_SIZE, PREFIX_MAX_LENGTH)
if err != nil {
return 0, errors.Trace(err)
}
conn.readState = OBFUSCATION_READ_STATE_IDENTIFICATION_LINES
}

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 {
Expand Down Expand Up @@ -410,7 +462,7 @@ func (conn *ObfuscatedSSHConn) readAndTransform(buffer []byte) (int, error) {
// 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
// State OBFUSCATION_WRITE_STATE_SERVER_SEND_PREFIX_AND_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
Expand All @@ -426,11 +478,13 @@ func (conn *ObfuscatedSSHConn) readAndTransform(buffer []byte) (int, error) {
// 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
//
// 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.
Expand All @@ -441,22 +495,43 @@ func (conn *ObfuscatedSSHConn) readAndTransform(buffer []byte) (int, error) {
// 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())
// The preamble (client) and requested prefix with
// identification line padding (server) are injected before any standard SSH traffic.
if conn.writeState == OBFUSCATION_WRITE_STATE_CLIENT_SEND_PREAMBLE {

preamble := conn.obfuscator.SendPreamble()

_, err := conn.Conn.Write(preamble)
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 {

} else if conn.writeState == OBFUSCATION_WRITE_STATE_SERVER_SEND_PREFIX_AND_IDENTIFICATION_LINE_PADDING {

var buffer bytes.Buffer

if preamble := conn.obfuscator.SendPreamble(); preamble != nil {
_, err := buffer.Write(preamble)
if err != nil {
return errors.Trace(err)
}
}

padding := makeServerIdentificationLinePadding(conn.paddingPRNG)
conn.paddingLength = len(padding)
conn.writeObfuscate(padding)
_, err := conn.Conn.Write(padding)
_, err := buffer.Write(padding)
if err != nil {
return errors.Trace(err)
}

_, err = conn.Conn.Write(buffer.Bytes())
if err != nil {
return errors.Trace(err)
}

conn.writeState = OBFUSCATION_WRITE_STATE_IDENTIFICATION_LINE
}

Expand Down
Loading

0 comments on commit 283d241

Please sign in to comment.