|
@@ -27,6 +27,7 @@ import (
|
|
|
"encoding/binary"
|
|
"encoding/binary"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
"io"
|
|
"io"
|
|
|
|
|
+ "time"
|
|
|
|
|
|
|
|
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
|
|
"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/errors"
|
|
@@ -67,6 +68,14 @@ type OSSHPrefixHeader struct {
|
|
|
SpecName string
|
|
SpecName string
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// OSSHPrefixSplitConfig are parameters for splitting the
|
|
|
|
|
+// preamble into two writes: prefix followed by rest of the preamble.
|
|
|
|
|
+type OSSHPrefixSplitConfig struct {
|
|
|
|
|
+ Seed *prng.Seed
|
|
|
|
|
+ MinDelay time.Duration
|
|
|
|
|
+ MaxDelay time.Duration
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// Obfuscator implements the seed message, key derivation, and
|
|
// Obfuscator implements the seed message, key derivation, and
|
|
|
// stream ciphers for:
|
|
// stream ciphers for:
|
|
|
// https://github.com/brl/obfuscated-openssh/blob/master/README.obfuscation
|
|
// https://github.com/brl/obfuscated-openssh/blob/master/README.obfuscation
|
|
@@ -79,9 +88,14 @@ type OSSHPrefixHeader struct {
|
|
|
type Obfuscator struct {
|
|
type Obfuscator struct {
|
|
|
preamble []byte
|
|
preamble []byte
|
|
|
|
|
|
|
|
- // prefixHeader is the prefix header written by the client,
|
|
|
|
|
|
|
+ // Length of the prefix in the preamble.
|
|
|
|
|
+ preambleOSSHPrefixLength int
|
|
|
|
|
+
|
|
|
|
|
+ // osshPrefixHeader is the prefix header written by the client,
|
|
|
// or the prefix header read by the server.
|
|
// or the prefix header read by the server.
|
|
|
- prefixHeader *OSSHPrefixHeader
|
|
|
|
|
|
|
+ osshPrefixHeader *OSSHPrefixHeader
|
|
|
|
|
+
|
|
|
|
|
+ osshPrefixSplitConfig *OSSHPrefixSplitConfig
|
|
|
|
|
|
|
|
keyword string
|
|
keyword string
|
|
|
paddingLength int
|
|
paddingLength int
|
|
@@ -97,6 +111,7 @@ type ObfuscatorConfig struct {
|
|
|
Keyword string
|
|
Keyword string
|
|
|
ClientPrefixSpec *OSSHPrefixSpec
|
|
ClientPrefixSpec *OSSHPrefixSpec
|
|
|
ServerPrefixSpecs transforms.Specs
|
|
ServerPrefixSpecs transforms.Specs
|
|
|
|
|
+ OSSHPrefixSplitConfig *OSSHPrefixSplitConfig
|
|
|
PaddingPRNGSeed *prng.Seed
|
|
PaddingPRNGSeed *prng.Seed
|
|
|
MinPadding *int
|
|
MinPadding *int
|
|
|
MaxPadding *int
|
|
MaxPadding *int
|
|
@@ -171,7 +186,7 @@ func NewClientObfuscator(
|
|
|
maxPadding = *config.MaxPadding
|
|
maxPadding = *config.MaxPadding
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- preamble, prefixHeader, paddingLength, err := makeClientPreamble(
|
|
|
|
|
|
|
+ preamble, prefixLen, prefixHeader, paddingLength, err := makeClientPreamble(
|
|
|
config.Keyword, config.ClientPrefixSpec,
|
|
config.Keyword, config.ClientPrefixSpec,
|
|
|
paddingPRNG, minPadding, maxPadding, obfuscatorSeed,
|
|
paddingPRNG, minPadding, maxPadding, obfuscatorSeed,
|
|
|
clientToServerCipher)
|
|
clientToServerCipher)
|
|
@@ -180,14 +195,16 @@ func NewClientObfuscator(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return &Obfuscator{
|
|
return &Obfuscator{
|
|
|
- preamble: preamble,
|
|
|
|
|
- prefixHeader: prefixHeader,
|
|
|
|
|
- keyword: config.Keyword,
|
|
|
|
|
- paddingLength: paddingLength,
|
|
|
|
|
- clientToServerCipher: clientToServerCipher,
|
|
|
|
|
- serverToClientCipher: serverToClientCipher,
|
|
|
|
|
- paddingPRNGSeed: config.PaddingPRNGSeed,
|
|
|
|
|
- paddingPRNG: paddingPRNG}, nil
|
|
|
|
|
|
|
+ preamble: preamble,
|
|
|
|
|
+ preambleOSSHPrefixLength: prefixLen,
|
|
|
|
|
+ osshPrefixHeader: prefixHeader,
|
|
|
|
|
+ osshPrefixSplitConfig: config.OSSHPrefixSplitConfig,
|
|
|
|
|
+ keyword: config.Keyword,
|
|
|
|
|
+ paddingLength: paddingLength,
|
|
|
|
|
+ clientToServerCipher: clientToServerCipher,
|
|
|
|
|
+ serverToClientCipher: serverToClientCipher,
|
|
|
|
|
+ paddingPRNGSeed: config.PaddingPRNGSeed,
|
|
|
|
|
+ paddingPRNG: paddingPRNG}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// NewServerObfuscator creates a new Obfuscator, reading a seed message directly
|
|
// NewServerObfuscator creates a new Obfuscator, reading a seed message directly
|
|
@@ -212,20 +229,22 @@ func NewServerObfuscator(
|
|
|
return nil, errors.Trace(err)
|
|
return nil, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- preamble, err := makeServerPreamble(prefixHeader, config.ServerPrefixSpecs, config.Keyword)
|
|
|
|
|
|
|
+ preamble, prefixLen, err := makeServerPreamble(prefixHeader, config.ServerPrefixSpecs, config.Keyword)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, errors.Trace(err)
|
|
return nil, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return &Obfuscator{
|
|
return &Obfuscator{
|
|
|
- preamble: preamble,
|
|
|
|
|
- prefixHeader: prefixHeader,
|
|
|
|
|
- keyword: config.Keyword,
|
|
|
|
|
- paddingLength: -1,
|
|
|
|
|
- clientToServerCipher: clientToServerCipher,
|
|
|
|
|
- serverToClientCipher: serverToClientCipher,
|
|
|
|
|
- paddingPRNGSeed: paddingPRNGSeed,
|
|
|
|
|
- paddingPRNG: prng.NewPRNGWithSeed(paddingPRNGSeed),
|
|
|
|
|
|
|
+ preamble: preamble,
|
|
|
|
|
+ preambleOSSHPrefixLength: prefixLen,
|
|
|
|
|
+ osshPrefixHeader: prefixHeader,
|
|
|
|
|
+ osshPrefixSplitConfig: config.OSSHPrefixSplitConfig,
|
|
|
|
|
+ keyword: config.Keyword,
|
|
|
|
|
+ paddingLength: -1,
|
|
|
|
|
+ clientToServerCipher: clientToServerCipher,
|
|
|
|
|
+ serverToClientCipher: serverToClientCipher,
|
|
|
|
|
+ paddingPRNGSeed: paddingPRNGSeed,
|
|
|
|
|
+ paddingPRNG: prng.NewPRNGWithSeed(paddingPRNGSeed),
|
|
|
}, nil
|
|
}, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -248,10 +267,12 @@ func (obfuscator *Obfuscator) GetPaddingLength() int {
|
|
|
|
|
|
|
|
// SendPreamble returns the preamble created in NewObfuscatorClient or
|
|
// SendPreamble returns the preamble created in NewObfuscatorClient or
|
|
|
// NewServerObfuscator, removing the reference so that it may be garbage collected.
|
|
// NewServerObfuscator, removing the reference so that it may be garbage collected.
|
|
|
-func (obfuscator *Obfuscator) SendPreamble() []byte {
|
|
|
|
|
|
|
+func (obfuscator *Obfuscator) SendPreamble() ([]byte, int) {
|
|
|
msg := obfuscator.preamble
|
|
msg := obfuscator.preamble
|
|
|
|
|
+ prefixLen := obfuscator.preambleOSSHPrefixLength
|
|
|
obfuscator.preamble = nil
|
|
obfuscator.preamble = nil
|
|
|
- return msg
|
|
|
|
|
|
|
+ obfuscator.preambleOSSHPrefixLength = 0
|
|
|
|
|
+ return msg, prefixLen
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ObfuscateClientToServer applies the client RC4 stream to the bytes in buffer.
|
|
// ObfuscateClientToServer applies the client RC4 stream to the bytes in buffer.
|
|
@@ -341,42 +362,45 @@ func makeClientPreamble(
|
|
|
paddingPRNG *prng.PRNG,
|
|
paddingPRNG *prng.PRNG,
|
|
|
minPadding, maxPadding int,
|
|
minPadding, maxPadding int,
|
|
|
obfuscatorSeed []byte,
|
|
obfuscatorSeed []byte,
|
|
|
- clientToServerCipher *rc4.Cipher) ([]byte, *OSSHPrefixHeader, int, error) {
|
|
|
|
|
|
|
+ clientToServerCipher *rc4.Cipher) ([]byte, int, *OSSHPrefixHeader, int, error) {
|
|
|
|
|
|
|
|
padding := paddingPRNG.Padding(minPadding, maxPadding)
|
|
padding := paddingPRNG.Padding(minPadding, maxPadding)
|
|
|
buffer := new(bytes.Buffer)
|
|
buffer := new(bytes.Buffer)
|
|
|
magicValueStartIndex := len(obfuscatorSeed)
|
|
magicValueStartIndex := len(obfuscatorSeed)
|
|
|
|
|
|
|
|
|
|
+ prefixLen := 0
|
|
|
|
|
+
|
|
|
if prefixSpec != nil {
|
|
if prefixSpec != nil {
|
|
|
- // Writes the prefix and terminator to the buffer.
|
|
|
|
|
- prefix, err := makePrefix(prefixSpec, keyword, OBFUSCATE_CLIENT_TO_SERVER_IV)
|
|
|
|
|
|
|
+ var b []byte
|
|
|
|
|
+ var err error
|
|
|
|
|
+ b, prefixLen, err = makeTerminatedPrefixWithPadding(prefixSpec, keyword, OBFUSCATE_CLIENT_TO_SERVER_IV)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, nil, 0, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- _, err = buffer.Write(prefix)
|
|
|
|
|
|
|
+ _, err = buffer.Write(b)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, nil, 0, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- magicValueStartIndex += len(prefix)
|
|
|
|
|
|
|
+ magicValueStartIndex += len(b)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
err := binary.Write(buffer, binary.BigEndian, obfuscatorSeed)
|
|
err := binary.Write(buffer, binary.BigEndian, obfuscatorSeed)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, nil, 0, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
err = binary.Write(buffer, binary.BigEndian, uint32(OBFUSCATE_MAGIC_VALUE))
|
|
err = binary.Write(buffer, binary.BigEndian, uint32(OBFUSCATE_MAGIC_VALUE))
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, nil, 0, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
err = binary.Write(buffer, binary.BigEndian, uint32(len(padding)))
|
|
err = binary.Write(buffer, binary.BigEndian, uint32(len(padding)))
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, nil, 0, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
err = binary.Write(buffer, binary.BigEndian, padding)
|
|
err = binary.Write(buffer, binary.BigEndian, padding)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, nil, 0, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var prefixHeader *OSSHPrefixHeader = nil
|
|
var prefixHeader *OSSHPrefixHeader = nil
|
|
@@ -384,7 +408,7 @@ func makeClientPreamble(
|
|
|
// Writes the prefix header after the padding.
|
|
// Writes the prefix header after the padding.
|
|
|
err := prefixSpec.writePrefixHeader(buffer)
|
|
err := prefixSpec.writePrefixHeader(buffer)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, nil, 0, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
prefixHeader = &OSSHPrefixHeader{
|
|
prefixHeader = &OSSHPrefixHeader{
|
|
@@ -399,7 +423,7 @@ func makeClientPreamble(
|
|
|
preamble[magicValueStartIndex:],
|
|
preamble[magicValueStartIndex:],
|
|
|
preamble[magicValueStartIndex:])
|
|
preamble[magicValueStartIndex:])
|
|
|
|
|
|
|
|
- return preamble, prefixHeader, len(padding), nil
|
|
|
|
|
|
|
+ return preamble, prefixLen, prefixHeader, len(padding), nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// makeServerPreamble generates a server preamble (prefix or nil).
|
|
// makeServerPreamble generates a server preamble (prefix or nil).
|
|
@@ -410,10 +434,10 @@ func makeClientPreamble(
|
|
|
func makeServerPreamble(
|
|
func makeServerPreamble(
|
|
|
header *OSSHPrefixHeader,
|
|
header *OSSHPrefixHeader,
|
|
|
serverSpecs transforms.Specs,
|
|
serverSpecs transforms.Specs,
|
|
|
- keyword string) ([]byte, error) {
|
|
|
|
|
|
|
+ keyword string) ([]byte, int, error) {
|
|
|
|
|
|
|
|
if header == nil {
|
|
if header == nil {
|
|
|
- return nil, nil
|
|
|
|
|
|
|
+ return nil, 0, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
spec, ok := serverSpecs[header.SpecName]
|
|
spec, ok := serverSpecs[header.SpecName]
|
|
@@ -424,7 +448,7 @@ func makeServerPreamble(
|
|
|
|
|
|
|
|
seed, err := prng.NewSeed()
|
|
seed, err := prng.NewSeed()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
prefixSpec := &OSSHPrefixSpec{
|
|
prefixSpec := &OSSHPrefixSpec{
|
|
@@ -432,7 +456,7 @@ func makeServerPreamble(
|
|
|
Spec: spec,
|
|
Spec: spec,
|
|
|
Seed: seed,
|
|
Seed: seed,
|
|
|
}
|
|
}
|
|
|
- return makePrefix(prefixSpec, keyword, OBFUSCATE_SERVER_TO_CLIENT_IV)
|
|
|
|
|
|
|
+ return makeTerminatedPrefixWithPadding(prefixSpec, keyword, OBFUSCATE_SERVER_TO_CLIENT_IV)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// readPreamble reads the preamble bytes from the client. If it does not detect
|
|
// readPreamble reads the preamble bytes from the client. If it does not detect
|
|
@@ -622,12 +646,12 @@ func readPreambleHelper(
|
|
|
|
|
|
|
|
// makeTerminator generates a prefix terminator used in finding end of prefix
|
|
// makeTerminator generates a prefix terminator used in finding end of prefix
|
|
|
// placed before OSSH stream.
|
|
// placed before OSSH stream.
|
|
|
-// prefix should be at least PREAMBLE_HEADER_LENGTH bytes and contain enough entropy.
|
|
|
|
|
-func makeTerminator(keyword string, prefix []byte, direction string) ([]byte, error) {
|
|
|
|
|
|
|
+// b should be at least PREAMBLE_HEADER_LENGTH bytes and contain enough entropy.
|
|
|
|
|
+func makeTerminator(keyword string, b []byte, direction string) ([]byte, error) {
|
|
|
|
|
|
|
|
- // prefix length is at least equal to obfuscator seed message.
|
|
|
|
|
- if len(prefix) < PREAMBLE_HEADER_LENGTH {
|
|
|
|
|
- return nil, errors.TraceNew("prefix too short")
|
|
|
|
|
|
|
+ // Bytes length is at least equal to obfuscator seed message.
|
|
|
|
|
+ if len(b) < PREAMBLE_HEADER_LENGTH {
|
|
|
|
|
+ return nil, errors.TraceNew("bytes too short")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (direction != OBFUSCATE_CLIENT_TO_SERVER_IV) &&
|
|
if (direction != OBFUSCATE_CLIENT_TO_SERVER_IV) &&
|
|
@@ -637,7 +661,7 @@ func makeTerminator(keyword string, prefix []byte, direction string) ([]byte, er
|
|
|
|
|
|
|
|
hkdf := hkdf.New(sha256.New,
|
|
hkdf := hkdf.New(sha256.New,
|
|
|
[]byte(keyword),
|
|
[]byte(keyword),
|
|
|
- prefix[:PREAMBLE_HEADER_LENGTH],
|
|
|
|
|
|
|
+ b[:PREAMBLE_HEADER_LENGTH],
|
|
|
[]byte(direction))
|
|
[]byte(direction))
|
|
|
|
|
|
|
|
terminator := make([]byte, PREFIX_TERMINATOR_LENGTH)
|
|
terminator := make([]byte, PREFIX_TERMINATOR_LENGTH)
|
|
@@ -649,24 +673,26 @@ func makeTerminator(keyword string, prefix []byte, direction string) ([]byte, er
|
|
|
return terminator, nil
|
|
return terminator, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// makePrefix generates a prefix followed by it's terminator using the given spec.
|
|
|
|
|
|
|
+// makeTerminatedPrefixWithPadding generates bytes starting with the prefix bytes defiend
|
|
|
|
|
+// by spec and ending with the generated terminator.
|
|
|
// If the generated prefix is shorter than PREAMBLE_HEADER_LENGTH, it is padded
|
|
// If the generated prefix is shorter than PREAMBLE_HEADER_LENGTH, it is padded
|
|
|
// with random bytes.
|
|
// with random bytes.
|
|
|
-func makePrefix(spec *OSSHPrefixSpec, keyword, direction string) ([]byte, error) {
|
|
|
|
|
|
|
+// Returns the generated prefix with teminator, and the length of the prefix if no error.
|
|
|
|
|
+func makeTerminatedPrefixWithPadding(spec *OSSHPrefixSpec, keyword, direction string) ([]byte, int, error) {
|
|
|
|
|
|
|
|
- prefix, err := spec.Spec.ApplyPrefix(spec.Seed, PREAMBLE_HEADER_LENGTH)
|
|
|
|
|
|
|
+ prefix, prefixLen, err := spec.Spec.ApplyPrefix(spec.Seed, PREAMBLE_HEADER_LENGTH)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
terminator, err := makeTerminator(keyword, prefix, direction)
|
|
terminator, err := makeTerminator(keyword, prefix, direction)
|
|
|
|
|
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, errors.Trace(err)
|
|
|
|
|
|
|
+ return nil, 0, errors.Trace(err)
|
|
|
}
|
|
}
|
|
|
terminatedPrefix := append(prefix, terminator...)
|
|
terminatedPrefix := append(prefix, terminator...)
|
|
|
|
|
|
|
|
- return terminatedPrefix, nil
|
|
|
|
|
|
|
+ return terminatedPrefix, prefixLen, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// writePrefixHeader writes the prefix header to the given writer.
|
|
// writePrefixHeader writes the prefix header to the given writer.
|